Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/codecov.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
coverage:
ignore:
- "modfetch"
- "sumfile"
8 changes: 4 additions & 4 deletions modfile/gop_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -326,10 +326,10 @@ project ." Game math
doTestParseErr(t, `gop.mod:2: "." is not a valid package path`, `
project . Game math
`)
doTestParseErr(t, `gop.mod:2: symbol game invalid: invalid Go export symbol format`, `
doTestParseErr(t, `gop.mod:2: symbol game invalid: invalid Go export type`, `
project .gmx game math
`)
doTestParseErr(t, `gop.mod:2: symbol . invalid: invalid Go export symbol format`, `
doTestParseErr(t, `gop.mod:2: symbol . invalid: invalid Go export type`, `
project .gmx . math
`)
doTestParseErr(t, `gop.mod:2: invalid quoted string: invalid syntax`, `
Expand Down Expand Up @@ -370,11 +370,11 @@ pack ."spx Sprite
project github.com/goplus/spx math
pack "" ."spx
`)
doTestParseErr(t, `gop.mod:3: symbol .abc invalid: invalid Go export symbol format`, `
doTestParseErr(t, `gop.mod:3: symbol .abc invalid: invalid Go export type`, `
project github.com/goplus/spx math
class .spx Sprite .abc
`)
doTestParseErr(t, `gop.mod:3: symbol sprite invalid: invalid Go export symbol format`, `
doTestParseErr(t, `gop.mod:3: symbol sprite invalid: invalid Go export type`, `
project github.com/goplus/spx math
class .spx sprite
`)
Expand Down
94 changes: 87 additions & 7 deletions modfile/rule.go
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,7 @@ func (f *File) parseVerb(errs *ErrorList, verb string, line *Line, args []string
wrapError(err)
return
}
class, err := parseSymbol(&args[1])
class, err := parseType(&args[1])
if err != nil {
wrapError(err)
return
Expand Down Expand Up @@ -307,14 +307,14 @@ usage: class [-embed -prefix=Prefix] *.workExt WorkClass [WorkPrototype]`, sw)
wrapError(err)
return
}
class, err := parseSymbol(&args[1])
class, err := parseType(&args[1])
if err != nil {
wrapError(err)
return
}
protoClass := ""
if len(args) > 2 {
protoClass, err = parseSymbol(&args[2])
protoClass, err = parseType(&args[2])
if err != nil {
wrapError(err)
return
Expand Down Expand Up @@ -390,13 +390,76 @@ usage: class [-embed -prefix=Prefix] *.workExt WorkClass [WorkPrototype]`, sw)
return
}
proj.Pack = &Pack{Directory: dir, IndexFile: indexFile, Syntax: line}
case "autolambda":
proj := f.proj()
if proj == nil {
errorf("autolambda must declare after a project definition")
return
}
if len(args) == 0 {
errorf("usage: autolambda name(n), ...")
return
}
if proj.AutoLambdas == nil {
proj.AutoLambdas = make(map[string]int)
}
err := parseAutoLambdas(proj.AutoLambdas, args)
if err != nil {
wrapError(err)
return
}
default:
if strict {
errorf("unknown directive: %s", verb)
}
}
}

// parseAutoLambdas parses the argument tokens of an autolambda directive into a
// list of `name(n)` entries. The gox.mod tokenizer splits parentheses and commas
// into their own tokens, so a directive like:
//
// autolambda times(1), forEver(0), onKey(1)
//
// arrives here as: [times ( 1 ) , forEver ( 0 ) , onKey ( 1 )].
func parseAutoLambdas(ret map[string]int, args []string) error {
i, n := 0, len(args)
for i < n {
name, err := parseIdent(&args[i])
if err != nil {
return fmt.Errorf("autolambda: invalid command name %q", args[i])
}
i++
if i >= n || args[i] != "(" {
return fmt.Errorf("autolambda %s: expect '(' after command name", name)
}
i++
nArgs, e := strconv.Atoi(args[i])
if e != nil || nArgs < 0 {
return fmt.Errorf("autolambda %s: invalid number of arguments %q", name, args[i])
}
i++
if i >= n || args[i] != ")" {
return fmt.Errorf("autolambda %s: expect ')' after number of arguments", name)
}
i++
if _, ok := ret[name]; ok {
return fmt.Errorf("autolambda: duplicate command %q", name)
}
ret[name] = nArgs
if i < n {
if args[i] != "," {
return fmt.Errorf("autolambda: expect ',' between entries, got %q", args[i])
}
i++
if i >= n {
return fmt.Errorf("autolambda: trailing ',' without an entry")
}
}
}
return nil
}

func fileLine(n int) (file string, line int) {
_, file, line, _ = runtime.Caller(n)
return
Expand All @@ -422,19 +485,36 @@ func AutoQuote(s string) string {
}

var (
symbolRE = regexp.MustCompile(`\*?[A-Z]\w*`)
typeRE = regexp.MustCompile(`\*?[A-Z]\w*`)
idenRE = regexp.MustCompile(`\w+`)
)

// TODO(xsw): to be optimized
func parseSymbol(s *string) (t string, err error) {
func parseType(s *string) (t string, err error) {
t, err = parseString(s)
if err != nil {
goto failed
}
if typeRE.MatchString(t) {
return
}
err = errors.New("invalid Go export type")
failed:
return "", &InvalidSymbolError{
Sym: *s,
Err: err,
}
}

func parseIdent(s *string) (t string, err error) {
t, err = parseString(s)
if err != nil {
goto failed
}
if symbolRE.MatchString(t) {
if idenRE.MatchString(t) {
return
}
err = errors.New("invalid Go export symbol format")
err = errors.New("invalid XGo identifier")
failed:
return "", &InvalidSymbolError{
Sym: *s,
Expand Down
128 changes: 128 additions & 0 deletions modfile/rule_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -248,3 +248,131 @@ pack assets sub\index.json
}

// -----------------------------------------------------------------------------

const goxmodWithAutoLambda = `
xgo 1.6

project main.spx Game github.com/goplus/spx/v2 math
class -embed *.spx SpriteImpl
autolambda times(1), forEver(0), onKey(1)
`

func TestParseAutoLambda(t *testing.T) {
f, err := ParseLax("gox.mod", []byte(goxmodWithAutoLambda), nil)
if err != nil {
t.Fatal("ParseLax failed:", err)
}
proj := f.proj()
if proj == nil {
t.Fatal("expected a project")
}
want := map[string]int{"times": 1, "forEver": 0, "onKey": 1}
if len(proj.AutoLambdas) != len(want) {
t.Fatalf("expected %d autolambda entries, got %d: %v", len(want), len(proj.AutoLambdas), proj.AutoLambdas)
}
for name, n := range want {
if got, ok := proj.AutoLambdas[name]; !ok || got != n {
t.Errorf("autolambda[%s] expected %d, got %d (ok=%v)", name, n, got, ok)
}
}
}

const goxmodMultiAutoLambda = `
xgo 1.6

project main.spx Game github.com/goplus/spx/v2 math
class -embed *.spx SpriteImpl
autolambda times(1)
autolambda forEver(0), onKey(1)
`

func TestParseMultiAutoLambda(t *testing.T) {
f, err := ParseLax("gox.mod", []byte(goxmodMultiAutoLambda), nil)
if err != nil {
t.Fatal("ParseLax failed:", err)
}
proj := f.proj()
if proj == nil {
t.Fatal("expected a project")
}
want := map[string]int{"times": 1, "forEver": 0, "onKey": 1}
if len(proj.AutoLambdas) != len(want) {
t.Fatalf("expected %d autolambda entries, got %d: %v", len(want), len(proj.AutoLambdas), proj.AutoLambdas)
}
for name, n := range want {
if got, ok := proj.AutoLambdas[name]; !ok || got != n {
t.Errorf("autolambda[%s] expected %d, got %d (ok=%v)", name, n, got, ok)
}
}
}

const goxmodNoAutoLambda = `
xgo 1.6

project main.spx Game github.com/goplus/spx/v2 math
class -embed *.spx SpriteImpl
`

func TestParseNoAutoLambda(t *testing.T) {
f, err := ParseLax("gox.mod", []byte(goxmodNoAutoLambda), nil)
if err != nil {
t.Fatal("ParseLax failed:", err)
}
if f.proj().AutoLambdas != nil {
t.Error("expected no autolambda directive")
}
}

func TestParseAutoLambdaErr(t *testing.T) {
// autolambda before project
doTestParseErr(t, `gop.mod:2: autolambda must declare after a project definition`, `
autolambda times(1)
`)
// missing arguments
doTestParseErr(t, `gop.mod:3: usage: autolambda name(n), ...`, `
project github.com/goplus/spx math
autolambda
`)
// missing '('
doTestParseErr(t, `gop.mod:3: autolambda times: expect '(' after command name`, `
project github.com/goplus/spx math
autolambda times 1)
`)
// invalid number
doTestParseErr(t, `gop.mod:3: autolambda times: invalid number of arguments "x"`, `
project github.com/goplus/spx math
autolambda times(x)
`)
// missing ')'
doTestParseErr(t, `gop.mod:3: autolambda times: expect ')' after number of arguments`, `
project github.com/goplus/spx math
autolambda times(1
`)
// missing ',' between entries
doTestParseErr(t, `gop.mod:3: autolambda: expect ',' between entries, got "forEver"`, `
project github.com/goplus/spx math
autolambda times(1) forEver(0)
`)
// trailing ','
doTestParseErr(t, `gop.mod:3: autolambda: trailing ',' without an entry`, `
project github.com/goplus/spx math
autolambda times(1),
`)
// invalid command name
doTestParseErr(t, `gop.mod:3: autolambda: invalid command name "!"`, `
project github.com/goplus/spx math
autolambda !(0)
`)
// invalid command name
doTestParseErr(t, `gop.mod:3: autolambda: invalid command name "'"`, `
project github.com/goplus/spx math
autolambda '
`)
// duplicate command
doTestParseErr(t, `gop.mod:3: autolambda: duplicate command "times"`, `
project github.com/goplus/spx math
autolambda times(1), times(2)
`)
}

// -----------------------------------------------------------------------------