diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml new file mode 100644 index 00000000..80e6fc0e --- /dev/null +++ b/.github/workflows/go.yml @@ -0,0 +1,18 @@ +on: [pull_request] +name: Test +jobs: + test: + strategy: + matrix: + go-version: [1.14.x] + platform: [ubuntu-latest] + runs-on: ${{ matrix.platform }} + steps: + - name: Install Go + uses: actions/setup-go@v2 + with: + go-version: ${{ matrix.go-version }} + - name: Checkout code + uses: actions/checkout@v2 + - name: Run all tests + run: make test diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 2c97810b..00000000 --- a/.travis.yml +++ /dev/null @@ -1,20 +0,0 @@ -language: go - -go_import_path: go.starlark.net - -go: - - "1.12.x" - - "1.13.x" - - "1.14.x" - - "master" - -env: - - "GO111MODULE=on" - -script: - - "go test -mod=readonly ./..." - - "cp go.mod go.mod.orig" - - "cp go.sum go.sum.orig" - - "go mod tidy" - - "diff go.mod.orig go.mod" - - "diff go.sum.orig go.sum" diff --git a/.vscode/numbered-bookmarks.json b/.vscode/numbered-bookmarks.json new file mode 100644 index 00000000..4c08963a --- /dev/null +++ b/.vscode/numbered-bookmarks.json @@ -0,0 +1,3 @@ +{ + "bookmarks": [] +} \ No newline at end of file diff --git a/internal/compile/compile.go b/internal/compile/compile.go index eb8e1627..165d7441 100644 --- a/internal/compile/compile.go +++ b/internal/compile/compile.go @@ -1207,9 +1207,11 @@ func (fcomp *fcomp) stmt(stmt syntax.Stmt) { fcomp.block = fcomp.newBlock() // dead code case *syntax.LoadStmt: - for i := range stmt.From { - fcomp.string(stmt.From[i].Name) - } + // TODO: maxmcd + // for i := range stmt.From { + // fcomp.string(stmt.From[i].Name) + // } + fcomp.string(stmt.Alias.Name) module := stmt.Module.Value.(string) fcomp.pcomp.prog.Loads = append(fcomp.pcomp.prog.Loads, Binding{ Name: module, @@ -1217,10 +1219,12 @@ func (fcomp *fcomp) stmt(stmt syntax.Stmt) { }) fcomp.string(module) fcomp.setPos(stmt.Load) - fcomp.emit1(LOAD, uint32(len(stmt.From))) - for i := range stmt.To { - fcomp.set(stmt.To[len(stmt.To)-1-i]) - } + fcomp.emit1(LOAD, uint32(0)) + fcomp.set(stmt.Alias) + // fcomp.emit1(LOAD, uint32(len(stmt.From))) + // for i := range stmt.To { + // fcomp.set(stmt.To[len(stmt.To)-1-i]) + // } default: start, _ := stmt.Span() diff --git a/resolve/resolve.go b/resolve/resolve.go index e4b67521..efc4c508 100644 --- a/resolve/resolve.go +++ b/resolve/resolve.go @@ -563,26 +563,21 @@ func (r *resolver) stmt(stmt syntax.Stmt) { r.errorf(stmt.Load, "load statement within a conditional") } - for i, from := range stmt.From { - if from.Name == "" { - r.errorf(from.NamePos, "load: empty identifier") - continue - } - if from.Name[0] == '_' { - r.errorf(from.NamePos, "load: names with leading underscores are not exported: %s", from.Name) - } - - id := stmt.To[i] - if LoadBindsGlobally { - r.bind(id) - } else if r.bindLocal(id) && !AllowGlobalReassign { - // "Global" in AllowGlobalReassign is a misnomer for "toplevel". - // Sadly we can't report the previous declaration - // as id.Binding may not be set yet. - r.errorf(id.NamePos, "cannot reassign top-level %s", id.Name) - } + if stmt.Alias.Name == "" { + r.errorf(stmt.Alias.NamePos, "load: empty identifier") } - + if stmt.Alias.Name[0] == '_' { + r.errorf(stmt.Alias.NamePos, "load: names with leading underscores are not exported: %s", stmt.Alias.Name) + } + if LoadBindsGlobally { + r.bind(stmt.Alias) + } else if r.bindLocal(stmt.Alias) && !AllowGlobalReassign { + // "Global" in AllowGlobalReassign is a misnomer for "toplevel". + // Sadly we can't report the previous declaration + // as id.Binding may not be set yet. + r.errorf(stmt.Alias.NamePos, "cannot reassign top-level %s", stmt.Alias.Name) + } + // TODO (maxmcd) default: log.Panicf("unexpected stmt %T", stmt) } diff --git a/resolve/resolve_test.go b/resolve/resolve_test.go index 090dbc54..1099a967 100644 --- a/resolve/resolve_test.go +++ b/resolve/resolve_test.go @@ -10,7 +10,6 @@ import ( "go.starlark.net/internal/chunkedfile" "go.starlark.net/resolve" - "go.starlark.net/starlarktest" "go.starlark.net/syntax" ) @@ -30,7 +29,8 @@ func option(chunk, name string) bool { func TestResolve(t *testing.T) { defer setOptions("") - filename := starlarktest.DataFile("resolve", "testdata/resolve.star") + // filename := starlarktest.DataFile("resolve", "testdata/resolve.star") + filename := "./testdata/resolve.star" for _, chunk := range chunkedfile.Read(filename, t) { f, err := syntax.Parse(filename, chunk.Source, 0) if err != nil { diff --git a/resolve/testdata/resolve.star b/resolve/testdata/resolve.star index 4f1a270d..6d828e7f 100644 --- a/resolve/testdata/resolve.star +++ b/resolve/testdata/resolve.star @@ -130,32 +130,26 @@ def f(): f() --- -load("module", "name") # ok +load(name="module") # ok def f(): - load("foo", "bar") ### "load statement within a function" + load("foo") ### "load statement within a function" -load("foo", - "", ### "load: empty identifier" - "_a", ### "load: names with leading underscores are not exported: _a" - b="", ### "load: empty identifier" - c="_d", ### "load: names with leading underscores are not exported: _d" - _e="f") # ok --- # option:globalreassign if M: - load("foo", "bar") ### "load statement within a conditional" + load("bar") ### "load statement within a conditional" --- # option:globalreassign for x in M: - load("foo", "bar") ### "load statement within a loop" + load("bar") ### "load statement within a loop" --- # option:recursion option:globalreassign while M: - load("foo", "bar") ### "load statement within a loop" + load("bar") ### "load statement within a loop" --- # return statements must be within a function @@ -361,28 +355,28 @@ def f(abc): print(goodbye) ### `undefined: goodbye$` --- -load("module", "x") # ok +load(x="module") # ok x = 1 ### `cannot reassign local x` -load("module", "x") ### `cannot reassign top-level x` +load(x="module") ### `cannot reassign top-level x` --- # option:loadbindsglobally -load("module", "x") # ok +load(x="module") # ok x = 1 ### `cannot reassign global x` -load("module", "x") ### `cannot reassign global x` +load(x="module") ### `cannot reassign global x` --- # option:globalreassign -load("module", "x") # ok +load(x="module") # ok x = 1 # ok -load("module", "x") # ok +load(x="module") # ok --- # option:globalreassign option:loadbindsglobally -load("module", "x") # ok +load(x="module") # ok x = 1 -load("module", "x") # ok +load(x="module") # ok --- _ = x # forward ref to file-local -load("module", "x") # ok +load(x="module") # ok diff --git a/starlark/eval_test.go b/starlark/eval_test.go index a57ccad5..3bab422a 100644 --- a/starlark/eval_test.go +++ b/starlark/eval_test.go @@ -109,7 +109,8 @@ func TestEvalExpr(t *testing.T) { func TestExecFile(t *testing.T) { defer setOptions("") - testdata := starlarktest.DataFile("starlark", ".") + // testdata := starlarktest.DataFile("starlark", ".") + testdata := "." thread := &starlark.Thread{Load: load} starlarktest.SetReporter(thread, t) for _, file := range []string{ @@ -564,7 +565,7 @@ func TestLoadBacktrace(t *testing.T) { // For discussion, see: // https://github.com/google/starlark-go/pull/244 const src = ` -load('crash.star', 'x') +load('crash.star') ` const loadedSrc = ` def f(x): diff --git a/starlark/example_test.go b/starlark/example_test.go index 4bd0c742..a0a85f1e 100644 --- a/starlark/example_test.go +++ b/starlark/example_test.go @@ -80,8 +80,8 @@ squares = [x*x for x in range(10)] // implementation of 'load' that works sequentially. func ExampleThread_Load_sequential() { fakeFilesystem := map[string]string{ - "c.star": `load("b.star", "b"); c = b + "!"`, - "b.star": `load("a.star", "a"); b = a + ", world"`, + "c.star": `load("b.star"); c = b.b + "!"`, + "b.star": `load("a.star"); b = a.a + ", world"`, "a.star": `a = "Hello"`, } @@ -133,8 +133,8 @@ func ExampleThread_Load_parallel() { cache := &cache{ cache: make(map[string]*entry), fakeFilesystem: map[string]string{ - "c.star": `load("a.star", "a"); c = a * 2`, - "b.star": `load("a.star", "a"); b = a * 3`, + "c.star": `load("a.star"); c = a.a * 2`, + "b.star": `load("a.star"); b = a.a * 3`, "a.star": `a = 1; print("loaded a")`, }, } @@ -170,9 +170,9 @@ func TestThreadLoad_ParallelCycle(t *testing.T) { cache := &cache{ cache: make(map[string]*entry), fakeFilesystem: map[string]string{ - "c.star": `load("b.star", "b"); c = b * 2`, - "b.star": `load("a.star", "a"); b = a * 3`, - "a.star": `load("c.star", "c"); a = c * 5; print("loaded a")`, + "c.star": `load("b.star"); c = b.b * 2`, + "b.star": `load("a.star"); b = a.a * 3`, + "a.star": `load("c.star"); a = c.c * 5; print("loaded a")`, }, } diff --git a/starlark/interp.go b/starlark/interp.go index f00382ce..b98259f8 100644 --- a/starlark/interp.go +++ b/starlark/interp.go @@ -9,7 +9,6 @@ import ( "unsafe" "go.starlark.net/internal/compile" - "go.starlark.net/internal/spell" "go.starlark.net/resolve" "go.starlark.net/syntax" ) @@ -510,7 +509,7 @@ loop: } case compile.LOAD: - n := int(arg) + // hasAlias := int(arg) module := string(stack[sp-1].(String)) sp-- @@ -529,19 +528,23 @@ loop: } break loop } - - for i := 0; i < n; i++ { - from := string(stack[sp-1-i].(String)) - v, ok := dict[from] - if !ok { - err = fmt.Errorf("load: name %s not found in module %s", from, module) - if n := spell.Nearest(from, dict.Keys()); n != "" { - err = fmt.Errorf("%s (did you mean %s?)", err, n) - } - break loop - } - stack[sp-1-i] = v - } + stack[sp-1-0] = &Module{ + Name: module, + Members: dict, + } + + // for i := 0; i < n; i++ { + // from := string(stack[sp-1-i].(String)) + // v, ok := dict[from] + // if !ok { + // err = fmt.Errorf("load: name %s not found in module %s", from, module) + // if n := spell.Nearest(from, dict.Keys()); n != "" { + // err = fmt.Errorf("%s (did you mean %s?)", err, n) + // } + // break loop + // } + // stack[sp-1-i] = v + // } case compile.SETLOCAL: locals[arg] = stack[sp-1] diff --git a/starlark/module.go b/starlark/module.go new file mode 100644 index 00000000..91c0dfd5 --- /dev/null +++ b/starlark/module.go @@ -0,0 +1,34 @@ +package starlark + +import "fmt" + +type Module struct { + Name string + Members StringDict +} + +var _ HasAttrs = (*Module)(nil) + +func (m *Module) Attr(name string) (Value, error) { return m.Members[name], nil } +func (m *Module) AttrNames() []string { return m.Members.Keys() } +func (m *Module) Freeze() { m.Members.Freeze() } +func (m *Module) Hash() (uint32, error) { return 0, fmt.Errorf("unhashable: %s", m.Type()) } +func (m *Module) String() string { return fmt.Sprintf("", m.Name) } +func (m *Module) Truth() Bool { return true } +func (m *Module) Type() string { return "module" } + +// MakeModule may be used as the implementation of a Starlark built-in +// function, module(name, **kwargs). It returns a new module with the +// specified name and members. +func MakeModule(thread *Thread, b *Builtin, args Tuple, kwargs []Tuple) (Value, error) { + var name string + if err := UnpackPositionalArgs(b.Name(), args, nil, 1, &name); err != nil { + return nil, err + } + members := make(StringDict, len(kwargs)) + for _, kwarg := range kwargs { + k := string(kwarg[0].(String)) + members[k] = kwarg[1] + } + return &Module{name, members}, nil +} diff --git a/starlark/testdata/assign.star b/starlark/testdata/assign.star index 38108f94..f79e3e1e 100644 --- a/starlark/testdata/assign.star +++ b/starlark/testdata/assign.star @@ -3,7 +3,8 @@ # This is a "chunked" file: each "---" effectively starts a new file. # tuple assignment -load("assert.star", "assert") +load(lib="assert.star") +assert = lib.assert () = () # empty ok @@ -31,7 +32,8 @@ assert.eq(c, 3) () = (1, 2) ### "too many values to unpack" --- # list assignment -load("assert.star", "assert") +load(lib="assert.star") +assert = lib.assert [] = [] # empty ok @@ -59,7 +61,9 @@ assert.eq(c, 3) [] = [1, 2] ### "too many values to unpack" --- # list-tuple assignment -load("assert.star", "assert") +load(lib="assert.star") +assert = lib.assert + # empty ok [] = () @@ -92,7 +96,9 @@ assert.eq(n, 4) --- # option:nesteddef # misc assignment -load("assert.star", "assert") +load(lib="assert.star") +assert = lib.assert + def assignment(): a = [1, 2, 3] @@ -113,7 +119,9 @@ assignment() --- # augmented assignment -load("assert.star", "assert") +load(lib="assert.star") +assert = lib.assert + def f(): x = 1 @@ -126,7 +134,9 @@ f() --- # effects of evaluating LHS occur only once -load("assert.star", "assert") +load(lib="assert.star") +assert = lib.assert + count = [0] # count[0] is the number of calls to f @@ -143,7 +153,9 @@ assert.eq(count[0], 1) # f was called only once --- # Order of evaluation. -load("assert.star", "assert") +load(lib="assert.star") +assert = lib.assert + calls = [] @@ -178,7 +190,8 @@ g = 1 --- # option:nesteddef # Free variables are captured by reference, so this is ok. -load("assert.star", "assert") +load(lib="assert.star") +assert = lib.assert def f(): def g(): @@ -189,7 +202,8 @@ def f(): assert.eq(f(), 1) --- -load("assert.star", "assert") +load(lib="assert.star") +assert = lib.assert printok = [False] @@ -219,7 +233,8 @@ f() z += 3 ### "global variable z referenced before assignment" --- -load("assert.star", "assert") +load(lib="assert.star") +assert = lib.assert # It's ok to define a global that shadows a built-in... list = [] @@ -235,7 +250,8 @@ tuple = () # option:float option:set # Same as above, but set and float are dialect-specific; # we shouldn't notice any difference. -load("assert.star", "assert") +load(lib="assert.star") +assert = lib.assert float = 1.0 assert.eq(type(float), "float") @@ -258,7 +274,8 @@ x = [1, 2] _ = [x for _ in [3] for x in x] ### "local variable x referenced before assignment" --- -load("assert.star", "assert") +load(lib="assert.star") +assert = lib.assert # assign singleton sequence to 1-tuple (x,) = (1,) @@ -278,7 +295,9 @@ assert.eq(a, 1) --- # assignment to/from fields. -load("assert.star", "assert", "freeze") +load(lib="assert.star") +assert = lib.assert +freeze = lib.freeze hf = hasfields() hf.x = 1 @@ -295,8 +314,9 @@ assert.fails(lambda: setX(hf), "cannot set field on a frozen hasfields") assert.fails(lambda: setY(hf), "cannot set field on a frozen hasfields") --- -# destucturing assignment in a for loop. -load("assert.star", "assert") +# destructuring assignment in a for loop. +load(lib="assert.star") +assert = lib.assert def f(): res = [] @@ -315,7 +335,8 @@ assert.eq(g(), {"one": 1, "two": 2}) --- # parenthesized LHS in augmented assignment (success) # option:globalreassign -load("assert.star", "assert") +load(lib="assert.star") +assert = lib.assert a = 5 (a) += 3 @@ -328,32 +349,34 @@ assert.eq(a, 8) --- # option:globalreassign -load("assert.star", "assert") -assert = 1 -load("assert.star", "assert") +load(lib="assert.star") +lib = 1 +load(lib="assert.star") --- # option:globalreassign option:loadbindsglobally -load("assert.star", "assert") -assert = 1 -load("assert.star", "assert") +load(lib="assert.star") +lib = 1 +load(lib="assert.star") --- # option:loadbindsglobally _ = assert ### "global variable assert referenced before assignment" -load("assert.star", "assert") +load("assert.star") --- _ = assert ### "local variable assert referenced before assignment" -load("assert.star", "assert") +load("assert.star") --- def f(): assert.eq(1, 1) # forward ref OK -load("assert.star", "assert") +load(lib="assert.star") +assert = lib.assert f() --- # option:loadbindsglobally def f(): assert.eq(1, 1) # forward ref OK -load("assert.star", "assert") +load(lib="assert.star") +assert = lib.assert f() diff --git a/starlark/testdata/bool.star b/starlark/testdata/bool.star index 78ef825f..71401fb2 100644 --- a/starlark/testdata/bool.star +++ b/starlark/testdata/bool.star @@ -1,7 +1,8 @@ # Tests of Starlark 'bool' # option:float -load("assert.star", "assert") +load(lib="assert.star") +assert = lib.assert # truth assert.true(True) diff --git a/starlark/testdata/builtins.star b/starlark/testdata/builtins.star index 2385c16a..103ae51e 100644 --- a/starlark/testdata/builtins.star +++ b/starlark/testdata/builtins.star @@ -1,7 +1,8 @@ # Tests of Starlark built-in functions # option:float option:set -load("assert.star", "assert") +load(lib="assert.star") +assert = lib.assert # len assert.eq(len([1, 2, 3]), 3) diff --git a/starlark/testdata/control.star b/starlark/testdata/control.star index 554ab25a..3a556a97 100644 --- a/starlark/testdata/control.star +++ b/starlark/testdata/control.star @@ -1,6 +1,7 @@ # Tests of Starlark control flow -load("assert.star", "assert") +load(lib="assert.star") +assert = lib.assert def controlflow(): # elif diff --git a/starlark/testdata/dict.star b/starlark/testdata/dict.star index 9864be73..015225d3 100644 --- a/starlark/testdata/dict.star +++ b/starlark/testdata/dict.star @@ -1,7 +1,9 @@ # Tests of Starlark 'dict' # option:nesteddef -load("assert.star", "assert", "freeze") +load(lib="assert.star") +assert = lib.assert +freeze = lib.freeze # literals assert.eq({}, {}) diff --git a/starlark/testdata/float.star b/starlark/testdata/float.star index 4d7a2695..5ad60726 100644 --- a/starlark/testdata/float.star +++ b/starlark/testdata/float.star @@ -1,7 +1,8 @@ # Tests of Starlark 'float' # option:float option:set -load("assert.star", "assert") +load(lib="assert.star") +assert = lib.assert # TODO(adonovan): more tests: # - precision diff --git a/starlark/testdata/function.star b/starlark/testdata/function.star index a1096844..dcf838bf 100644 --- a/starlark/testdata/function.star +++ b/starlark/testdata/function.star @@ -6,7 +6,8 @@ # and test that functions have correct position, free vars, names of locals, etc. # - move the hard-coded tests of parameter passing from eval_test.go to here. -load("assert.star", "assert", "freeze") +load(lib="assert.star") +assert = lib.assert # Test lexical scope and closures: def outer(x): @@ -44,7 +45,7 @@ assert.eq(sq(), 9) assert.eq(sq(), 16) # Freezing a closure -sq2 = freeze(sq) +sq2 = lib.freeze(sq) assert.fails(sq2, "frozen list") # recursion detection, simple @@ -116,7 +117,9 @@ assert.eq(len(closures), 10) --- # Default values of function parameters are mutable. -load("assert.star", "assert", "freeze") +load(lib="assert.star") +assert = lib.assert + def f(x=[0]): return x @@ -127,12 +130,13 @@ f().append(1) assert.eq(f(), [0, 1]) # Freezing a function value freezes its parameter defaults. -freeze(f) +lib.freeze(f) assert.fails(lambda: f().append(2), "cannot append to frozen list") --- # This is a well known corner case of parsing in Python. -load("assert.star", "assert") +load(lib="assert.star") +assert = lib.assert f = lambda x: 1 if x else 0 assert.eq(f(True), 1) @@ -152,7 +156,8 @@ assert.true(not tf[1]()) # (This tests a corner case of the implementation: # we avoid a map allocation for <64 parameters) -load("assert.star", "assert") +load(lib="assert.star") +assert = lib.assert def f(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, @@ -192,7 +197,8 @@ assert.fails(lambda: f( # Related: https://github.com/bazelbuild/starlark/issues/21, # which concerns static checks. -load("assert.star", "assert") +load(lib="assert.star") +assert = lib.assert def f(*args, **kwargs): return args, kwargs @@ -209,7 +215,8 @@ assert.fails(lambda: g(1, y=2, **{'y': 3}), 'multiple values for parameter "y"') --- # Regression test for a bug in CALL_VAR_KW. -load("assert.star", "assert") +load(lib="assert.star") +assert = lib.assert def f(a, b, x, y): return a+b+x+y @@ -218,7 +225,8 @@ assert.eq(f(*("a", "b"), **dict(y="y", x="x")) + ".", 'abxy.') --- # Order of evaluation of function arguments. # Regression test for github.com/google/skylark/issues/135. -load("assert.star", "assert") +load(lib="assert.star") +assert = lib.assert r = [] @@ -241,7 +249,8 @@ assert.eq(r, [1, 2, 3, 5, 4, 6]) --- # option:nesteddef option:recursion # See github.com/bazelbuild/starlark#170 -load("assert.star", "assert") +load(lib="assert.star") +assert = lib.assert def a(): list = [] @@ -277,7 +286,8 @@ assert.eq(e(), 1) --- # option:nesteddef -load("assert.star", "assert") +load(lib="assert.star") +assert = lib.assert def e(): x = 1 diff --git a/starlark/testdata/int.star b/starlark/testdata/int.star index c987fd17..3f193da8 100644 --- a/starlark/testdata/int.star +++ b/starlark/testdata/int.star @@ -1,7 +1,8 @@ # Tests of Starlark 'int' # option:float -load("assert.star", "assert") +load(lib="assert.star") +assert = lib.assert # basic arithmetic assert.eq(0 - 1, -1) diff --git a/starlark/testdata/json.star b/starlark/testdata/json.star index 615d70bf..5495d965 100644 --- a/starlark/testdata/json.star +++ b/starlark/testdata/json.star @@ -1,8 +1,10 @@ # Tests of json module. # option:float -load("assert.star", "assert") -load("json.star", "json") +load(lib="assert.star") +assert = lib.assert +load(json_lib="json.star") +json = json_lib.json assert.eq(dir(json), ["decode", "encode", "indent"]) diff --git a/starlark/testdata/list.star b/starlark/testdata/list.star index ff98b968..81a8fe77 100644 --- a/starlark/testdata/list.star +++ b/starlark/testdata/list.star @@ -1,7 +1,9 @@ # Tests of Starlark 'list' # option:nesteddef -load("assert.star", "assert", "freeze") +load(lib="assert.star") +assert = lib.assert +freeze = lib.freeze # literals assert.eq([], []) diff --git a/starlark/testdata/misc.star b/starlark/testdata/misc.star index 0cb973ab..4d3ab0a7 100644 --- a/starlark/testdata/misc.star +++ b/starlark/testdata/misc.star @@ -38,7 +38,8 @@ # option:float -load("assert.star", "assert") +load(lib="assert.star") +assert = lib.assert # Ordered comparisons require values of the same type. assert.fails(lambda: None < False, "not impl") @@ -55,7 +56,8 @@ assert.lt(2.0, 3) --- # cyclic data structures -load("assert.star", "assert") +load(lib="assert.star") +assert = lib.assert cyclic = [1, 2, 3] # list cycle cyclic[1] = cyclic @@ -83,7 +85,8 @@ assert.fails(lambda: cyclic5 == cyclic6, "maximum recursion") --- # regression -load("assert.star", "assert") +load(lib="assert.star") +assert = lib.assert # was a parse error: assert.eq(("ababab"[2:]).replace("b", "c"), "acac") @@ -102,22 +105,26 @@ _ = {}.get(1, default=2) ### "get: unexpected keyword arguments" --- # Load exposes explicitly declared globals from other modules. -load('assert.star', 'assert', 'freeze') -assert.eq(str(freeze), '') +load(lib="assert.star") +assert = lib.assert +assert.eq(str(lib.freeze), '') --- # Load does not expose pre-declared globals from other modules. # See github.com/google/skylark/issues/75. -load('assert.star', 'assert', 'matches') ### "matches not found in module" +load(lib='assert.star') +lib.matches ### "module has no .matches field or method" --- # Load does not expose universals accessible in other modules. -load('assert.star', 'len') ### "len not found in module" +load('assert.star') +assert.len ### "module has no .len field or method" --- # Test plus folding optimization. -load('assert.star', 'assert') +load(lib="assert.star") +assert = lib.assert s = "s" l = [4] @@ -137,4 +144,5 @@ assert.fails(lambda: [] + [] + 1 + [], "unknown binary op: list \\+ int") --- -load('assert.star', 'froze') ### `name froze not found .*did you mean freeze` +load(lib="assert.star") +assert = lib.froze ### `module has no \.froze field or method \(did you mean \.freeze\?\)` diff --git a/starlark/testdata/module.star b/starlark/testdata/module.star index 6aac2e2d..fb2fdf1f 100644 --- a/starlark/testdata/module.star +++ b/starlark/testdata/module.star @@ -1,6 +1,8 @@ # Tests of Module. -load("assert.star", "assert") +load(lib="assert.star") +assert = lib.assert + assert.eq(type(assert), "module") assert.eq(str(assert), '') diff --git a/starlark/testdata/recursion.star b/starlark/testdata/recursion.star index 3368614e..e91f64fe 100644 --- a/starlark/testdata/recursion.star +++ b/starlark/testdata/recursion.star @@ -4,7 +4,9 @@ # option:recursion -load("assert.star", "assert") +load(lib="assert.star") +assert = lib.assert + def sum(n): r = 0 diff --git a/starlark/testdata/set.star b/starlark/testdata/set.star index bca41448..e7dae6ec 100644 --- a/starlark/testdata/set.star +++ b/starlark/testdata/set.star @@ -15,7 +15,9 @@ # - set += iterable, perhaps? # Test iterator invalidation. -load("assert.star", "assert") +load(lib="assert.star") +assert = lib.assert + # literals # Parser does not currently support {1, 2, 3}. diff --git a/starlark/testdata/string.star b/starlark/testdata/string.star index 1d78dd7b..b2a0dbd2 100644 --- a/starlark/testdata/string.star +++ b/starlark/testdata/string.star @@ -1,7 +1,9 @@ # Tests of Starlark 'string' # option:float option:set -load("assert.star", "assert") +load(lib="assert.star") +assert = lib.assert + # raw string literals: assert.eq(r'a\bc', "a\\bc") diff --git a/starlark/testdata/tuple.star b/starlark/testdata/tuple.star index f3061333..7523096f 100644 --- a/starlark/testdata/tuple.star +++ b/starlark/testdata/tuple.star @@ -1,6 +1,8 @@ # Tests of Starlark 'tuple' -load("assert.star", "assert") +load(lib="assert.star") +assert = lib.assert + # literal assert.eq((), ()) diff --git a/starlarkstruct/struct_test.go b/starlarkstruct/struct_test.go index 8e6a93df..3a7b237d 100644 --- a/starlarkstruct/struct_test.go +++ b/starlarkstruct/struct_test.go @@ -24,7 +24,8 @@ func init() { } func Test(t *testing.T) { - testdata := starlarktest.DataFile("starlarkstruct", ".") + // testdata := starlarktest.DataFile("starlarkstruct", ".") + testdata := "." thread := &starlark.Thread{Load: load} starlarktest.SetReporter(thread, t) filename := filepath.Join(testdata, "testdata/struct.star") diff --git a/starlarkstruct/testdata/struct.star b/starlarkstruct/testdata/struct.star index e54fe046..d2700e0e 100644 --- a/starlarkstruct/testdata/struct.star +++ b/starlarkstruct/testdata/struct.star @@ -1,7 +1,9 @@ # Tests of Starlark 'struct' extension. # This is not a standard feature and the Go and Starlark APIs may yet change. -load("assert.star", "assert") +load(lib="assert.star") +assert = lib.assert + assert.eq(str(struct), "") diff --git a/syntax/parse.go b/syntax/parse.go index 0281e4b8..fbd89221 100644 --- a/syntax/parse.go +++ b/syntax/parse.go @@ -11,7 +11,11 @@ package syntax // package. Verify that error positions are correct using the // chunkedfile mechanism. -import "log" +import ( + "log" + "path" + "strings" +) // Enable this flag to print the token stream and log.Fatal on the first error. const debug = false @@ -315,65 +319,53 @@ func (p *parser) parseSmallStmt() Stmt { // stmt = LOAD '(' STRING {',' (IDENT '=')? STRING} [','] ')' func (p *parser) parseLoadStmt() *LoadStmt { loadPos := p.nextToken() // consume LOAD - lparen := p.consume(LPAREN) - if p.tok != STRING { + lparen := p.consume(LPAREN) + _ = lparen // TODO + + var module *Literal + var alias *Ident + if p.tok == STRING { + module = p.parsePrimary().(*Literal) + _, filename := path.Split(module.Value.(string)) + parts := strings.Split(filename, ".") + if len(parts) > 1 { + filename = strings.Join(parts[:len(parts)-1], "") + } + // TODO: replace characters as needed, eg: max.com/foo-bar module + // or just let them error and require an alias??? + alias = &Ident{ + NamePos: module.TokenPos.add(`"`), + Name: filename, + } + } else if p.tok == IDENT { + alias = p.parseIdent() + if p.tok != EQ { + p.in.errorf(p.in.pos, `load operand must be "%[1]s" or %[1]s="originalname" (want '=' after %[1]s)`, alias.Name) + } + p.consume(EQ) + if p.tok != STRING { + p.in.errorf(p.in.pos, `original name of loaded symbol must be quoted: %s="originalname"`, alias.Name) + } + module = p.parsePrimary().(*Literal) + } else { p.in.errorf(p.in.pos, "first operand of load statement must be a string literal") } - module := p.parsePrimary().(*Literal) - var from, to []*Ident - for p.tok != RPAREN && p.tok != EOF { + if p.tok == COMMA { p.consume(COMMA) - if p.tok == RPAREN { - break // allow trailing comma - } - switch p.tok { - case STRING: - // load("module", "id") - // To name is same as original. - lit := p.parsePrimary().(*Literal) - id := &Ident{ - NamePos: lit.TokenPos.add(`"`), - Name: lit.Value.(string), - } - to = append(to, id) - from = append(from, id) - - case IDENT: - // load("module", to="from") - id := p.parseIdent() - to = append(to, id) - if p.tok != EQ { - p.in.errorf(p.in.pos, `load operand must be "%[1]s" or %[1]s="originalname" (want '=' after %[1]s)`, id.Name) - } - p.consume(EQ) - if p.tok != STRING { - p.in.errorf(p.in.pos, `original name of loaded symbol must be quoted: %s="originalname"`, id.Name) - } - lit := p.parsePrimary().(*Literal) - from = append(from, &Ident{ - NamePos: lit.TokenPos.add(`"`), - Name: lit.Value.(string), - }) - - case RPAREN: - p.in.errorf(p.in.pos, "trailing comma in load statement") + } - default: - p.in.errorf(p.in.pos, `load operand must be "name" or localname="name" (got %#v)`, p.tok) - } + if p.tok != RPAREN { + p.in.errorf(p.in.pos, `load only takes one value, either "name" or localname="name" (got %#v)`, p.tok) } + rparen := p.consume(RPAREN) - if len(to) == 0 { - p.in.errorf(lparen, "load statement must import at least 1 symbol") - } return &LoadStmt{ Load: loadPos, Module: module, - To: to, - From: from, + Alias: alias, Rparen: rparen, } } diff --git a/syntax/parse_test.go b/syntax/parse_test.go index 76f9eb38..6d25a4a2 100644 --- a/syntax/parse_test.go +++ b/syntax/parse_test.go @@ -16,7 +16,6 @@ import ( "testing" "go.starlark.net/internal/chunkedfile" - "go.starlark.net/starlarktest" "go.starlark.net/syntax" ) @@ -168,10 +167,16 @@ else: `(AssignStmt Op== LHS=(DotExpr X=x Name=f) RHS=1)`}, {`(x, y) = 1`, `(AssignStmt Op== LHS=(ParenExpr X=(TupleExpr List=(x y))) RHS=1)`}, - {`load("", "a", b="c")`, - `(LoadStmt Module="" From=(a c) To=(a b))`}, - {`if True: load("", "a", b="c")`, // load needn't be at toplevel - `(IfStmt Cond=True True=((LoadStmt Module="" From=(a c) To=(a b))))`}, + {`load("")`, + `(LoadStmt Module="" Alias=)`}, + {`load("hello/there")`, + `(LoadStmt Module="hello/there" Alias=there)`}, + {`load("hello/there.star")`, + `(LoadStmt Module="hello/there.star" Alias=there)`}, + {`load(a="b")`, + `(LoadStmt Module="b" Alias=a)`}, + {`if True: load("c")`, // load needn't be at toplevel + `(IfStmt Cond=True True=((LoadStmt Module="c" Alias=c)))`}, {`def f(x, *args, **kwargs): pass`, `(DefStmt Name=f Params=(x (UnaryExpr Op=* X=args) (UnaryExpr Op=** X=kwargs)) Body=((BranchStmt Token=pass)))`}, @@ -424,7 +429,8 @@ func writeTree(out *bytes.Buffer, x reflect.Value) { } func TestParseErrors(t *testing.T) { - filename := starlarktest.DataFile("syntax", "testdata/errors.star") + // filename := starlarktest.DataFile("syntax", "testdata/errors.star") + filename := "./testdata/errors.star" for _, chunk := range chunkedfile.Read(filename, t) { _, err := syntax.Parse(filename, chunk.Source, 0) switch err := err.(type) { @@ -442,7 +448,7 @@ func TestParseErrors(t *testing.T) { // dataFile is the same as starlarktest.DataFile. // We make a copy to avoid a dependency cycle. var dataFile = func(pkgdir, filename string) string { - return filepath.Join(build.Default.GOPATH, "src/go.starlark.net", pkgdir, filename) + return filepath.Join(build.Default.GOPATH, "src/github.com/maxmcd/starlark-go", pkgdir, filename) } func BenchmarkParse(b *testing.B) { diff --git a/syntax/syntax.go b/syntax/syntax.go index b4817c1a..95d891c7 100644 --- a/syntax/syntax.go +++ b/syntax/syntax.go @@ -174,8 +174,7 @@ type LoadStmt struct { commentsRef Load Position Module *Literal // a string - From []*Ident // name defined in loading module - To []*Ident // name in loaded module + Alias *Ident Rparen Position } diff --git a/syntax/testdata/errors.star b/syntax/testdata/errors.star index cee1fc97..5ee2b945 100644 --- a/syntax/testdata/errors.star +++ b/syntax/testdata/errors.star @@ -140,22 +140,17 @@ print(1, 2, 3 --- _ = a if b ### "conditional expression without else clause" --- -load("") ### "load statement must import at least 1 symbol" +load("") # ok --- -load("", 1) ### `load operand must be "name" or localname="name" \(got int literal\)` +load("", 1) ### `load only takes one value, either "name" or localname="name" \(got int literal\)` --- -load("a", "x") # ok +load("a", "x") ### `load only takes one value, either "name" or localname="name" \(got string literal\)` --- -load(1, 2) ### "first operand of load statement must be a string literal" ---- -load("a", x) ### `load operand must be "x" or x="originalname"` ---- -load("a", x2=x) ### `original name of loaded symbol must be quoted: x2="originalname"` +load(1) ### "first operand of load statement must be a string literal" --- # All of these parse. -load("a", "x") -load("a", "x", y2="y") -load("a", x2="x", "y") # => positional-before-named arg check happens later (!) +load(b="a") +load("a") --- # 'load' is not an identifier load = 1 ### `got '=', want '\('` @@ -172,7 +167,7 @@ def f(load): ### `not an identifier` pass --- # A load statement allows a trailing comma. -load("module", "x",) +load("module",) --- x = 1 + 2 ### "got newline, want primary expression" diff --git a/syntax/walk.go b/syntax/walk.go index 1491149c..786e8b0b 100644 --- a/syntax/walk.go +++ b/syntax/walk.go @@ -55,13 +55,10 @@ func Walk(n Node, f func(Node) bool) { } case *LoadStmt: - Walk(n.Module, f) - for _, from := range n.From { - Walk(from, f) - } - for _, to := range n.To { - Walk(to, f) + if n.Alias != nil { + Walk(n.Alias, f) } + Walk(n.Module, f) case *Ident, *Literal: // no-op diff --git a/syntax/walk_test.go b/syntax/walk_test.go index 00d97843..ab9a9170 100644 --- a/syntax/walk_test.go +++ b/syntax/walk_test.go @@ -72,7 +72,7 @@ File // containing a nonsense program with varied grammar. func ExampleWalk() { const src = ` -load("library", "a") +load(a="library") def b(c, *, d=e): f += {g: h} @@ -96,8 +96,6 @@ for o in [p for q, r in s if t]: }) fmt.Println(strings.Join(idents, " ")) - // The identifer 'a' appears in both LoadStmt.From[0] and LoadStmt.To[0]. - // Output: - // a a b c d e f g h i j k l m n o p q r s t u v w x y z + // a b c d e f g h i j k l m n o p q r s t u v w x y z }