diff --git a/repl/repl.go b/repl/repl.go index 94f8947e..0bb43ad6 100644 --- a/repl/repl.go +++ b/repl/repl.go @@ -33,7 +33,6 @@ var interrupted = make(chan os.Signal, 1) // variable named "context" to a context.Context that is cancelled by a // SIGINT (Control-C). Client-supplied global functions may use this // context to make long-running operations interruptable. -// func REPL(thread *starlark.Thread, globals starlark.StringDict) { signal.Notify(interrupted, os.Interrupt) defer signal.Stop(interrupted) @@ -152,7 +151,7 @@ func PrintError(err error) { // MakeLoad returns a simple sequential implementation of module loading // suitable for use in the REPL. // Each function returned by MakeLoad accesses a distinct private cache. -func MakeLoad() func(thread *starlark.Thread, module string) (starlark.StringDict, error) { +func MakeLoad() func(thread *starlark.Thread, module string) (starlark.StringDictLike, error) { type entry struct { globals starlark.StringDict err error @@ -160,7 +159,7 @@ func MakeLoad() func(thread *starlark.Thread, module string) (starlark.StringDic var cache = make(map[string]*entry) - return func(thread *starlark.Thread, module string) (starlark.StringDict, error) { + return func(thread *starlark.Thread, module string) (starlark.StringDictLike, error) { e, ok := cache[module] if e == nil { if ok { diff --git a/starlark/eval.go b/starlark/eval.go index 949cb934..b515948a 100644 --- a/starlark/eval.go +++ b/starlark/eval.go @@ -24,6 +24,13 @@ import ( "go.starlark.net/syntax" ) +// StringDictLike is an interface for a dictionary of Starlark values. +type StringDictLike interface { + Get(string) Value + Has(string) bool + Keys() []string +} + // A Thread contains the state of a Starlark thread, // such as its call stack and thread-local storage. // The Thread is threaded throughout the evaluator. @@ -45,7 +52,7 @@ type Thread struct { // The error message need not include the module name. // // See example_test.go for some example implementations of Load. - Load func(thread *Thread, module string) (StringDict, error) + Load func(thread *Thread, module string) (StringDictLike, error) // OnMaxSteps is called when the thread reaches the limit set by SetMaxExecutionSteps. // The default behavior is to call thread.Cancel("too many steps"). @@ -184,6 +191,9 @@ func (d StringDict) Freeze() { // Has reports whether the dictionary contains the specified key. func (d StringDict) Has(key string) bool { _, ok := d[key]; return ok } +// Get returns the value associated with the specified key. +func (d StringDict) Get(key string) Value { return d[key] } + // A frame records a call to a Starlark function (including module toplevel) // or a built-in function or method. type frame struct { @@ -342,9 +352,16 @@ func (prog *Program) Write(out io.Writer) error { // // If ExecFile fails during evaluation, it returns an *EvalError // containing a backtrace. -func ExecFile(thread *Thread, filename string, src interface{}, predeclared StringDict) (StringDict, error) { +func ExecFile(thread *Thread, filename string, src interface{}, predeclared StringDictLike) (StringDict, error) { + var has func(string) bool + if predeclared != nil { + has = predeclared.Has + } else { + has = func(string) bool { return false } + } + // Parse, resolve, and compile a Starlark source file. - _, mod, err := SourceProgram(filename, src, predeclared.Has) + _, mod, err := SourceProgram(filename, src, has) if err != nil { return nil, err } @@ -418,7 +435,7 @@ func CompiledProgram(in io.Reader) (*Program, error) { // Init creates a set of global variables for the program, // executes the toplevel code of the specified program, // and returns a new, unfrozen dictionary of the globals. -func (prog *Program) Init(thread *Thread, predeclared StringDict) (StringDict, error) { +func (prog *Program) Init(thread *Thread, predeclared StringDictLike) (StringDict, error) { toplevel := makeToplevelFunction(prog.compiled, predeclared) _, err := Call(thread, toplevel, nil, nil) @@ -479,7 +496,7 @@ func ExecREPLChunk(f *syntax.File, thread *Thread, globals StringDict) error { return err } -func makeToplevelFunction(prog *compile.Program, predeclared StringDict) *Function { +func makeToplevelFunction(prog *compile.Program, predeclared StringDictLike) *Function { // Create the Starlark value denoted by each program constant c. constants := make([]Value, len(prog.Constants)) for i, c := range prog.Constants { @@ -556,7 +573,7 @@ func EvalExpr(thread *Thread, expr syntax.Expr, env StringDict) (Value, error) { // ExprFunc returns a no-argument function // that evaluates the expression whose source is src. -func ExprFunc(filename string, src interface{}, env StringDict) (*Function, error) { +func ExprFunc(filename string, src interface{}, env StringDictLike) (*Function, error) { expr, err := syntax.ParseExpr(filename, src, 0) if err != nil { return nil, err @@ -565,7 +582,7 @@ func ExprFunc(filename string, src interface{}, env StringDict) (*Function, erro } // makeExprFunc returns a no-argument function whose body is expr. -func makeExprFunc(expr syntax.Expr, env StringDict) (*Function, error) { +func makeExprFunc(expr syntax.Expr, env StringDictLike) (*Function, error) { locals, err := resolve.Expr(expr, env.Has, Universe.Has) if err != nil { return nil, err diff --git a/starlark/eval_test.go b/starlark/eval_test.go index 9ffd1794..8f7af9c3 100644 --- a/starlark/eval_test.go +++ b/starlark/eval_test.go @@ -195,7 +195,7 @@ func (it *fibIterator) Next(p *starlark.Value) bool { func (it *fibIterator) Done() {} // load implements the 'load' operation as used in the evaluator tests. -func load(thread *starlark.Thread, module string) (starlark.StringDict, error) { +func load(thread *starlark.Thread, module string) (starlark.StringDictLike, error) { if module == "assert.star" { return starlarktest.LoadAssertModule() } @@ -588,7 +588,7 @@ def f(x): f(0) ` thread := new(starlark.Thread) - thread.Load = func(t *starlark.Thread, module string) (starlark.StringDict, error) { + thread.Load = func(t *starlark.Thread, module string) (starlark.StringDictLike, error) { return starlark.ExecFile(new(starlark.Thread), module, loadedSrc, nil) } _, err := starlark.ExecFile(thread, "root.star", src, nil) diff --git a/starlark/example_test.go b/starlark/example_test.go index 5feca385..1625c58d 100644 --- a/starlark/example_test.go +++ b/starlark/example_test.go @@ -92,8 +92,8 @@ func ExampleThread_Load_sequential() { cache := make(map[string]*entry) - var load func(_ *starlark.Thread, module string) (starlark.StringDict, error) - load = func(_ *starlark.Thread, module string) (starlark.StringDict, error) { + var load func(_ *starlark.Thread, module string) (starlark.StringDictLike, error) + load = func(_ *starlark.Thread, module string) (starlark.StringDictLike, error) { e, ok := cache[module] if e == nil { if ok { @@ -120,7 +120,7 @@ func ExampleThread_Load_sequential() { if err != nil { log.Fatal(err) } - fmt.Println(globals["c"]) + fmt.Println(globals.Get("c")) // Output: // "Hello, world!" @@ -267,7 +267,7 @@ func (c *cache) doLoad(cc *cycleChecker, module string) (starlark.StringDict, er thread := &starlark.Thread{ Name: "exec " + module, Print: func(_ *starlark.Thread, msg string) { fmt.Println(msg) }, - Load: func(_ *starlark.Thread, module string) (starlark.StringDict, error) { + Load: func(_ *starlark.Thread, module string) (starlark.StringDictLike, error) { // Tunnel the cycle-checker state for this "thread of loading". return c.get(cc, module) }, diff --git a/starlark/interp.go b/starlark/interp.go index b41905a0..57a6a308 100644 --- a/starlark/interp.go +++ b/starlark/interp.go @@ -575,15 +575,14 @@ loop: for i := 0; i < n; i++ { from := string(stack[sp-1-i].(String)) - v, ok := dict[from] - if !ok { + if ok := dict.Has(from); !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-i] = dict.Get(from) } case compile.SETLOCAL: @@ -640,7 +639,7 @@ loop: case compile.PREDECLARED: name := f.Prog.Names[arg] - x := fn.module.predeclared[name] + x := fn.module.predeclared.Get(name) if x == nil { err = fmt.Errorf("internal error: predeclared variable %s is uninitialized", name) break loop diff --git a/starlark/value.go b/starlark/value.go index da217951..7aee0535 100644 --- a/starlark/value.go +++ b/starlark/value.go @@ -691,7 +691,7 @@ type Function struct { // All functions in the same program share a module. type module struct { program *compile.Program - predeclared StringDict + predeclared StringDictLike globals []Value constants []Value } diff --git a/starlarkstruct/struct_test.go b/starlarkstruct/struct_test.go index 4f103bdf..52dd9929 100644 --- a/starlarkstruct/struct_test.go +++ b/starlarkstruct/struct_test.go @@ -32,7 +32,7 @@ func Test(t *testing.T) { } // load implements the 'load' operation as used in the evaluator tests. -func load(thread *starlark.Thread, module string) (starlark.StringDict, error) { +func load(thread *starlark.Thread, module string) (starlark.StringDictLike, error) { if module == "assert.star" { return starlarktest.LoadAssertModule() }