// Copyright 2017 The Bazel Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package starlark import ( "fmt" "io" "io/ioutil" "log" "math/big" "sort" "strings" "sync/atomic" "time" "unicode" "unicode/utf8" "unsafe" "go.starlark.net/internal/compile" "go.starlark.net/internal/spell" "go.starlark.net/resolve" "go.starlark.net/syntax" ) // 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. type Thread struct { // Name is an optional name that describes the thread, for debugging. Name string // stack is the stack of (internal) call frames. stack []*frame // Print is the client-supplied implementation of the Starlark // 'print' function. If nil, fmt.Fprintln(os.Stderr, msg) is // used instead. Print func(thread *Thread, msg string) // Load is the client-supplied implementation of module loading. // Repeated calls with the same module name must return the same // module environment or error. // 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) // steps counts abstract computation steps executed by this thread. steps, maxSteps uint64 // cancelReason records the reason from the first call to Cancel. cancelReason *string // locals holds arbitrary "thread-local" Go values belonging to the client. // They are accessible to the client but not to any Starlark program. locals map[string]interface{} // proftime holds the accumulated execution time since the last profile event. proftime time.Duration } // ExecutionSteps returns a count of abstract computation steps executed // by this thread. It is incremented by the interpreter. It may be used // as a measure of the approximate cost of Starlark execution, by // computing the difference in its value before and after a computation. // // The precise meaning of "step" is not specified and may change. func (thread *Thread) ExecutionSteps() uint64 { return thread.steps } // SetMaxExecutionSteps sets a limit on the number of Starlark // computation steps that may be executed by this thread. If the // thread's step counter exceeds this limit, the interpreter calls // thread.Cancel("too many steps"). func (thread *Thread) SetMaxExecutionSteps(max uint64) { thread.maxSteps = max } // Cancel causes execution of Starlark code in the specified thread to // promptly fail with an EvalError that includes the specified reason. // There may be a delay before the interpreter observes the cancellation // if the thread is currently in a call to a built-in function. // // Cancellation cannot be undone. // // Unlike most methods of Thread, it is safe to call Cancel from any // goroutine, even if the thread is actively executing. func (thread *Thread) Cancel(reason string) { // Atomically set cancelReason, preserving earlier reason if any. atomic.CompareAndSwapPointer((*unsafe.Pointer)(unsafe.Pointer(&thread.cancelReason)), nil, unsafe.Pointer(&reason)) } // SetLocal sets the thread-local value associated with the specified key. // It must not be called after execution begins. func (thread *Thread) SetLocal(key string, value interface{}) { if thread.locals == nil { thread.locals = make(map[string]interface{}) } thread.locals[key] = value } // Local returns the thread-local value associated with the specified key. func (thread *Thread) Local(key string) interface{} { return thread.locals[key] } // CallFrame returns a copy of the specified frame of the callstack. // It should only be used in built-ins called from Starlark code. // Depth 0 means the frame of the built-in itself, 1 is its caller, and so on. // // It is equivalent to CallStack().At(depth), but more efficient. func (thread *Thread) CallFrame(depth int) CallFrame { return thread.frameAt(depth).asCallFrame() } func (thread *Thread) frameAt(depth int) *frame { return thread.stack[len(thread.stack)-1-depth] } // CallStack returns a new slice containing the thread's stack of call frames. func (thread *Thread) CallStack() CallStack { frames := make([]CallFrame, len(thread.stack)) for i, fr := range thread.stack { frames[i] = fr.asCallFrame() } return frames } // CallStackDepth returns the number of frames in the current call stack. func (thread *Thread) CallStackDepth() int { return len(thread.stack) } // A StringDict is a mapping from names to values, and represents // an environment such as the global variables of a module. // It is not a true starlark.Value. type StringDict map[string]Value // Keys returns a new sorted slice of d's keys. func (d StringDict) Keys() []string { names := make([]string, 0, len(d)) for name := range d { names = append(names, name) } sort.Strings(names) return names } func (d StringDict) String() string { buf := new(strings.Builder) buf.WriteByte('{') sep := "" for _, name := range d.Keys() { buf.WriteString(sep) buf.WriteString(name) buf.WriteString(": ") writeValue(buf, d[name], nil) sep = ", " } buf.WriteByte('}') return buf.String() } func (d StringDict) Freeze() { for _, v := range d { v.Freeze() } } // Has reports whether the dictionary contains the specified key. func (d StringDict) Has(key string) bool { _, ok := d[key]; return ok } // A frame records a call to a Starlark function (including module toplevel) // or a built-in function or method. type frame struct { callable Callable // current function (or toplevel) or built-in pc uint32 // program counter (Starlark frames only) locals []Value // local variables (Starlark frames only) spanStart int64 // start time of current profiler span } // Position returns the source position of the current point of execution in this frame. func (fr *frame) Position() syntax.Position { switch c := fr.callable.(type) { case *Function: // Starlark function return c.funcode.Position(fr.pc) case callableWithPosition: // If a built-in Callable defines // a Position method, use it. return c.Position() } return syntax.MakePosition(&builtinFilename, 0, 0) } var builtinFilename = "" // Function returns the frame's function or built-in. func (fr *frame) Callable() Callable { return fr.callable } // A CallStack is a stack of call frames, outermost first. type CallStack []CallFrame // At returns a copy of the frame at depth i. // At(0) returns the topmost frame. func (stack CallStack) At(i int) CallFrame { return stack[len(stack)-1-i] } // Pop removes and returns the topmost frame. func (stack *CallStack) Pop() CallFrame { last := len(*stack) - 1 top := (*stack)[last] *stack = (*stack)[:last] return top } // String returns a user-friendly description of the stack. func (stack CallStack) String() string { out := new(strings.Builder) if len(stack) > 0 { fmt.Fprintf(out, "Traceback (most recent call last):\n") } for _, fr := range stack { fmt.Fprintf(out, " %s: in %s\n", fr.Pos, fr.Name) } return out.String() } // An EvalError is a Starlark evaluation error and // a copy of the thread's stack at the moment of the error. type EvalError struct { Msg string CallStack CallStack cause error } // A CallFrame represents the function name and current // position of execution of an enclosing call frame. type CallFrame struct { Name string Pos syntax.Position } func (fr *frame) asCallFrame() CallFrame { return CallFrame{ Name: fr.Callable().Name(), Pos: fr.Position(), } } func (thread *Thread) evalError(err error) *EvalError { return &EvalError{ Msg: err.Error(), CallStack: thread.CallStack(), cause: err, } } func (e *EvalError) Error() string { return e.Msg } // Backtrace returns a user-friendly error message describing the stack // of calls that led to this error. func (e *EvalError) Backtrace() string { // If the topmost stack frame is a built-in function, // remove it from the stack and add print "Error in fn:". stack := e.CallStack suffix := "" if last := len(stack) - 1; last >= 0 && stack[last].Pos.Filename() == builtinFilename { suffix = " in " + stack[last].Name stack = stack[:last] } return fmt.Sprintf("%sError%s: %s", stack, suffix, e.Msg) } func (e *EvalError) Unwrap() error { return e.cause } // A Program is a compiled Starlark program. // // Programs are immutable, and contain no Values. // A Program may be created by parsing a source file (see SourceProgram) // or by loading a previously saved compiled program (see CompiledProgram). type Program struct { compiled *compile.Program } // CompilerVersion is the version number of the protocol for compiled // files. Applications must not run programs compiled by one version // with an interpreter at another version, and should thus incorporate // the compiler version into the cache key when reusing compiled code. const CompilerVersion = compile.Version // Filename returns the name of the file from which this program was loaded. func (prog *Program) Filename() string { return prog.compiled.Toplevel.Pos.Filename() } func (prog *Program) String() string { return prog.Filename() } // NumLoads returns the number of load statements in the compiled program. func (prog *Program) NumLoads() int { return len(prog.compiled.Loads) } // Load(i) returns the name and position of the i'th module directly // loaded by this one, where 0 <= i < NumLoads(). // The name is unresolved---exactly as it appears in the source. func (prog *Program) Load(i int) (string, syntax.Position) { id := prog.compiled.Loads[i] return id.Name, id.Pos } // WriteTo writes the compiled module to the specified output stream. func (prog *Program) Write(out io.Writer) error { data := prog.compiled.Encode() _, err := out.Write(data) return err } // ExecFile parses, resolves, and executes a Starlark file in the // specified global environment, which may be modified during execution. // // Thread is the state associated with the Starlark thread. // // The filename and src parameters are as for syntax.Parse: // filename is the name of the file to execute, // and the name that appears in error messages; // src is an optional source of bytes to use // instead of filename. // // predeclared defines the predeclared names specific to this module. // Execution does not modify this dictionary, though it may mutate // its values. // // If ExecFile fails during evaluation, it returns an *EvalError // containing a backtrace. func ExecFile(thread *Thread, filename string, src interface{}, predeclared StringDict) (StringDict, error) { // Parse, resolve, and compile a Starlark source file. _, mod, err := SourceProgram(filename, src, predeclared.Has) if err != nil { return nil, err } g, err := mod.Init(thread, predeclared) g.Freeze() return g, err } // SourceProgram produces a new program by parsing, resolving, // and compiling a Starlark source file. // On success, it returns the parsed file and the compiled program. // The filename and src parameters are as for syntax.Parse. // // The isPredeclared predicate reports whether a name is // a pre-declared identifier of the current module. // Its typical value is predeclared.Has, // where predeclared is a StringDict of pre-declared values. func SourceProgram(filename string, src interface{}, isPredeclared func(string) bool) (*syntax.File, *Program, error) { f, err := syntax.Parse(filename, src, 0) if err != nil { return nil, nil, err } prog, err := FileProgram(f, isPredeclared) return f, prog, err } // FileProgram produces a new program by resolving, // and compiling the Starlark source file syntax tree. // On success, it returns the compiled program. // // Resolving a syntax tree mutates it. // Do not call FileProgram more than once on the same file. // // The isPredeclared predicate reports whether a name is // a pre-declared identifier of the current module. // Its typical value is predeclared.Has, // where predeclared is a StringDict of pre-declared values. func FileProgram(f *syntax.File, isPredeclared func(string) bool) (*Program, error) { if err := resolve.File(f, isPredeclared, Universe.Has); err != nil { return nil, err } var pos syntax.Position if len(f.Stmts) > 0 { pos = syntax.Start(f.Stmts[0]) } else { pos = syntax.MakePosition(&f.Path, 1, 1) } module := f.Module.(*resolve.Module) compiled := compile.File(f.Stmts, pos, "", module.Locals, module.Globals) return &Program{compiled}, nil } // CompiledProgram produces a new program from the representation // of a compiled program previously saved by Program.Write. func CompiledProgram(in io.Reader) (*Program, error) { data, err := ioutil.ReadAll(in) if err != nil { return nil, err } compiled, err := compile.DecodeProgram(data) if err != nil { return nil, err } return &Program{compiled}, nil } // 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) { toplevel := makeToplevelFunction(prog.compiled, predeclared) _, err := Call(thread, toplevel, nil, nil) // Convert the global environment to a map. // We return a (partial) map even in case of error. return toplevel.Globals(), err } // ExecREPLChunk compiles and executes file f in the specified thread // and global environment. This is a variant of ExecFile specialized to // the needs of a REPL, in which a sequence of input chunks, each // syntactically a File, manipulates the same set of module globals, // which are not frozen after execution. // // This function is intended to support only go.starlark.net/repl. // Its API stability is not guaranteed. func ExecREPLChunk(f *syntax.File, thread *Thread, globals StringDict) error { var predeclared StringDict // -- variant of FileProgram -- if err := resolve.REPLChunk(f, globals.Has, predeclared.Has, Universe.Has); err != nil { return err } var pos syntax.Position if len(f.Stmts) > 0 { pos = syntax.Start(f.Stmts[0]) } else { pos = syntax.MakePosition(&f.Path, 1, 1) } module := f.Module.(*resolve.Module) compiled := compile.File(f.Stmts, pos, "", module.Locals, module.Globals) prog := &Program{compiled} // -- variant of Program.Init -- toplevel := makeToplevelFunction(prog.compiled, predeclared) // Initialize module globals from parameter. for i, id := range prog.compiled.Globals { if v := globals[id.Name]; v != nil { toplevel.module.globals[i] = v } } _, err := Call(thread, toplevel, nil, nil) // Reflect changes to globals back to parameter, even after an error. for i, id := range prog.compiled.Globals { if v := toplevel.module.globals[i]; v != nil { globals[id.Name] = v } } return err } func makeToplevelFunction(prog *compile.Program, predeclared StringDict) *Function { // Create the Starlark value denoted by each program constant c. constants := make([]Value, len(prog.Constants)) for i, c := range prog.Constants { var v Value switch c := c.(type) { case int64: v = MakeInt64(c) case *big.Int: v = MakeBigInt(c) case string: v = String(c) case compile.Bytes: v = Bytes(c) case float64: v = Float(c) default: log.Panicf("unexpected constant %T: %v", c, c) } constants[i] = v } return &Function{ funcode: prog.Toplevel, module: &module{ program: prog, predeclared: predeclared, globals: make([]Value, len(prog.Globals)), constants: constants, }, } } // Eval parses, resolves, and evaluates an expression within the // specified (predeclared) environment. // // Evaluation cannot mutate the environment dictionary itself, // though it may modify variables reachable from the dictionary. // // The filename and src parameters are as for syntax.Parse. // // If Eval fails during evaluation, it returns an *EvalError // containing a backtrace. func Eval(thread *Thread, filename string, src interface{}, env StringDict) (Value, error) { expr, err := syntax.ParseExpr(filename, src, 0) if err != nil { return nil, err } f, err := makeExprFunc(expr, env) if err != nil { return nil, err } return Call(thread, f, nil, nil) } // EvalExpr resolves and evaluates an expression within the // specified (predeclared) environment. // Evaluating a comma-separated list of expressions yields a tuple value. // // Resolving an expression mutates it. // Do not call EvalExpr more than once for the same expression. // // Evaluation cannot mutate the environment dictionary itself, // though it may modify variables reachable from the dictionary. // // If Eval fails during evaluation, it returns an *EvalError // containing a backtrace. func EvalExpr(thread *Thread, expr syntax.Expr, env StringDict) (Value, error) { fn, err := makeExprFunc(expr, env) if err != nil { return nil, err } return Call(thread, fn, nil, nil) } // ExprFunc returns a no-argument function // that evaluates the expression whose source is src. func ExprFunc(filename string, src interface{}, env StringDict) (*Function, error) { expr, err := syntax.ParseExpr(filename, src, 0) if err != nil { return nil, err } return makeExprFunc(expr, env) } // makeExprFunc returns a no-argument function whose body is expr. func makeExprFunc(expr syntax.Expr, env StringDict) (*Function, error) { locals, err := resolve.Expr(expr, env.Has, Universe.Has) if err != nil { return nil, err } return makeToplevelFunction(compile.Expr(expr, "", locals), env), nil } // The following functions are primitive operations of the byte code interpreter. // list += iterable func listExtend(x *List, y Iterable) { if ylist, ok := y.(*List); ok { // fast path: list += list x.elems = append(x.elems, ylist.elems...) } else { iter := y.Iterate() defer iter.Done() var z Value for iter.Next(&z) { x.elems = append(x.elems, z) } } } // getAttr implements x.dot. func getAttr(x Value, name string) (Value, error) { hasAttr, ok := x.(HasAttrs) if !ok { return nil, fmt.Errorf("%s has no .%s field or method", x.Type(), name) } var errmsg string v, err := hasAttr.Attr(name) if err == nil { if v != nil { return v, nil // success } // (nil, nil) => generic error errmsg = fmt.Sprintf("%s has no .%s field or method", x.Type(), name) } else if nsa, ok := err.(NoSuchAttrError); ok { errmsg = string(nsa) } else { return nil, err // return error as is } // add spelling hint if n := spell.Nearest(name, hasAttr.AttrNames()); n != "" { errmsg = fmt.Sprintf("%s (did you mean .%s?)", errmsg, n) } return nil, fmt.Errorf("%s", errmsg) } // setField implements x.name = y. func setField(x Value, name string, y Value) error { if x, ok := x.(HasSetField); ok { err := x.SetField(name, y) if _, ok := err.(NoSuchAttrError); ok { // No such field: check spelling. if n := spell.Nearest(name, x.AttrNames()); n != "" { err = fmt.Errorf("%s (did you mean .%s?)", err, n) } } return err } return fmt.Errorf("can't assign to .%s field of %s", name, x.Type()) } // getIndex implements x[y]. func getIndex(x, y Value) (Value, error) { switch x := x.(type) { case Mapping: // dict z, found, err := x.Get(y) if err != nil { return nil, err } if !found { return nil, fmt.Errorf("key %v not in %s", y, x.Type()) } return z, nil case Indexable: // string, list, tuple n := x.Len() i, err := AsInt32(y) if err != nil { return nil, fmt.Errorf("%s index: %s", x.Type(), err) } origI := i if i < 0 { i += n } if i < 0 || i >= n { return nil, outOfRange(origI, n, x) } return x.Index(i), nil } return nil, fmt.Errorf("unhandled index operation %s[%s]", x.Type(), y.Type()) } func outOfRange(i, n int, x Value) error { if n == 0 { return fmt.Errorf("index %d out of range: empty %s", i, x.Type()) } else { return fmt.Errorf("%s index %d out of range [%d:%d]", x.Type(), i, -n, n-1) } } // setIndex implements x[y] = z. func setIndex(x, y, z Value) error { switch x := x.(type) { case HasSetKey: if err := x.SetKey(y, z); err != nil { return err } case HasSetIndex: n := x.Len() i, err := AsInt32(y) if err != nil { return err } origI := i if i < 0 { i += n } if i < 0 || i >= n { return outOfRange(origI, n, x) } return x.SetIndex(i, z) default: return fmt.Errorf("%s value does not support item assignment", x.Type()) } return nil } // Unary applies a unary operator (+, -, ~, not) to its operand. func Unary(op syntax.Token, x Value) (Value, error) { // The NOT operator is not customizable. if op == syntax.NOT { return !x.Truth(), nil } // Int, Float, and user-defined types if x, ok := x.(HasUnary); ok { // (nil, nil) => unhandled y, err := x.Unary(op) if y != nil || err != nil { return y, err } } return nil, fmt.Errorf("unknown unary op: %s %s", op, x.Type()) } // Binary applies a strict binary operator (not AND or OR) to its operands. // For equality tests or ordered comparisons, use Compare instead. func Binary(op syntax.Token, x, y Value) (Value, error) { switch op { case syntax.PLUS: switch x := x.(type) { case String: if y, ok := y.(String); ok { return x + y, nil } case Int: switch y := y.(type) { case Int: return x.Add(y), nil case Float: xf, err := x.finiteFloat() if err != nil { return nil, err } return xf + y, nil } case Float: switch y := y.(type) { case Float: return x + y, nil case Int: yf, err := y.finiteFloat() if err != nil { return nil, err } return x + yf, nil } case *List: if y, ok := y.(*List); ok { z := make([]Value, 0, x.Len()+y.Len()) z = append(z, x.elems...) z = append(z, y.elems...) return NewList(z), nil } case Tuple: if y, ok := y.(Tuple); ok { z := make(Tuple, 0, len(x)+len(y)) z = append(z, x...) z = append(z, y...) return z, nil } } case syntax.MINUS: switch x := x.(type) { case Int: switch y := y.(type) { case Int: return x.Sub(y), nil case Float: xf, err := x.finiteFloat() if err != nil { return nil, err } return xf - y, nil } case Float: switch y := y.(type) { case Float: return x - y, nil case Int: yf, err := y.finiteFloat() if err != nil { return nil, err } return x - yf, nil } } case syntax.STAR: switch x := x.(type) { case Int: switch y := y.(type) { case Int: return x.Mul(y), nil case Float: xf, err := x.finiteFloat() if err != nil { return nil, err } return xf * y, nil case String: return stringRepeat(y, x) case Bytes: return bytesRepeat(y, x) case *List: elems, err := tupleRepeat(Tuple(y.elems), x) if err != nil { return nil, err } return NewList(elems), nil case Tuple: return tupleRepeat(y, x) } case Float: switch y := y.(type) { case Float: return x * y, nil case Int: yf, err := y.finiteFloat() if err != nil { return nil, err } return x * yf, nil } case String: if y, ok := y.(Int); ok { return stringRepeat(x, y) } case Bytes: if y, ok := y.(Int); ok { return bytesRepeat(x, y) } case *List: if y, ok := y.(Int); ok { elems, err := tupleRepeat(Tuple(x.elems), y) if err != nil { return nil, err } return NewList(elems), nil } case Tuple: if y, ok := y.(Int); ok { return tupleRepeat(x, y) } } case syntax.SLASH: switch x := x.(type) { case Int: xf, err := x.finiteFloat() if err != nil { return nil, err } switch y := y.(type) { case Int: yf, err := y.finiteFloat() if err != nil { return nil, err } if yf == 0.0 { return nil, fmt.Errorf("floating-point division by zero") } return xf / yf, nil case Float: if y == 0.0 { return nil, fmt.Errorf("floating-point division by zero") } return xf / y, nil } case Float: switch y := y.(type) { case Float: if y == 0.0 { return nil, fmt.Errorf("floating-point division by zero") } return x / y, nil case Int: yf, err := y.finiteFloat() if err != nil { return nil, err } if yf == 0.0 { return nil, fmt.Errorf("floating-point division by zero") } return x / yf, nil } } case syntax.SLASHSLASH: switch x := x.(type) { case Int: switch y := y.(type) { case Int: if y.Sign() == 0 { return nil, fmt.Errorf("floored division by zero") } return x.Div(y), nil case Float: xf, err := x.finiteFloat() if err != nil { return nil, err } if y == 0.0 { return nil, fmt.Errorf("floored division by zero") } return floor(xf / y), nil } case Float: switch y := y.(type) { case Float: if y == 0.0 { return nil, fmt.Errorf("floored division by zero") } return floor(x / y), nil case Int: yf, err := y.finiteFloat() if err != nil { return nil, err } if yf == 0.0 { return nil, fmt.Errorf("floored division by zero") } return floor(x / yf), nil } } case syntax.PERCENT: switch x := x.(type) { case Int: switch y := y.(type) { case Int: if y.Sign() == 0 { return nil, fmt.Errorf("integer modulo by zero") } return x.Mod(y), nil case Float: xf, err := x.finiteFloat() if err != nil { return nil, err } if y == 0 { return nil, fmt.Errorf("floating-point modulo by zero") } return xf.Mod(y), nil } case Float: switch y := y.(type) { case Float: if y == 0.0 { return nil, fmt.Errorf("floating-point modulo by zero") } return x.Mod(y), nil case Int: if y.Sign() == 0 { return nil, fmt.Errorf("floating-point modulo by zero") } yf, err := y.finiteFloat() if err != nil { return nil, err } return x.Mod(yf), nil } case String: return interpolate(string(x), y) } case syntax.NOT_IN: z, err := Binary(syntax.IN, x, y) if err != nil { return nil, err } return !z.Truth(), nil case syntax.IN: switch y := y.(type) { case *List: for _, elem := range y.elems { if eq, err := Equal(elem, x); err != nil { return nil, err } else if eq { return True, nil } } return False, nil case Tuple: for _, elem := range y { if eq, err := Equal(elem, x); err != nil { return nil, err } else if eq { return True, nil } } return False, nil case Mapping: // e.g. dict // Ignore error from Get as we cannot distinguish true // errors (value cycle, type error) from "key not found". _, found, _ := y.Get(x) return Bool(found), nil case *Set: ok, err := y.Has(x) return Bool(ok), err case String: needle, ok := x.(String) if !ok { return nil, fmt.Errorf("'in ' requires string as left operand, not %s", x.Type()) } return Bool(strings.Contains(string(y), string(needle))), nil case Bytes: switch needle := x.(type) { case Bytes: return Bool(strings.Contains(string(y), string(needle))), nil case Int: var b byte if err := AsInt(needle, &b); err != nil { return nil, fmt.Errorf("int in bytes: %s", err) } return Bool(strings.IndexByte(string(y), b) >= 0), nil default: return nil, fmt.Errorf("'in bytes' requires bytes or int as left operand, not %s", x.Type()) } case rangeValue: i, err := NumberToInt(x) if err != nil { return nil, fmt.Errorf("'in ' requires integer as left operand, not %s", x.Type()) } return Bool(y.contains(i)), nil } case syntax.PIPE: switch x := x.(type) { case Int: if y, ok := y.(Int); ok { return x.Or(y), nil } case *Set: // union if y, ok := y.(*Set); ok { iter := Iterate(y) defer iter.Done() return x.Union(iter) } } case syntax.AMP: switch x := x.(type) { case Int: if y, ok := y.(Int); ok { return x.And(y), nil } case *Set: // intersection if y, ok := y.(*Set); ok { set := new(Set) if x.Len() > y.Len() { x, y = y, x // opt: range over smaller set } for _, xelem := range x.elems() { // Has, Insert cannot fail here. if found, _ := y.Has(xelem); found { set.Insert(xelem) } } return set, nil } } case syntax.CIRCUMFLEX: switch x := x.(type) { case Int: if y, ok := y.(Int); ok { return x.Xor(y), nil } case *Set: // symmetric difference if y, ok := y.(*Set); ok { set := new(Set) for _, xelem := range x.elems() { if found, _ := y.Has(xelem); !found { set.Insert(xelem) } } for _, yelem := range y.elems() { if found, _ := x.Has(yelem); !found { set.Insert(yelem) } } return set, nil } } case syntax.LTLT, syntax.GTGT: if x, ok := x.(Int); ok { y, err := AsInt32(y) if err != nil { return nil, err } if y < 0 { return nil, fmt.Errorf("negative shift count: %v", y) } if op == syntax.LTLT { if y >= 512 { return nil, fmt.Errorf("shift count too large: %v", y) } return x.Lsh(uint(y)), nil } else { return x.Rsh(uint(y)), nil } } default: // unknown operator goto unknown } // user-defined types // (nil, nil) => unhandled if x, ok := x.(HasBinary); ok { z, err := x.Binary(op, y, Left) if z != nil || err != nil { return z, err } } if y, ok := y.(HasBinary); ok { z, err := y.Binary(op, x, Right) if z != nil || err != nil { return z, err } } // unsupported operand types unknown: return nil, fmt.Errorf("unknown binary op: %s %s %s", x.Type(), op, y.Type()) } // It's always possible to overeat in small bites but we'll // try to stop someone swallowing the world in one gulp. const maxAlloc = 1 << 30 func tupleRepeat(elems Tuple, n Int) (Tuple, error) { if len(elems) == 0 { return nil, nil } i, err := AsInt32(n) if err != nil { return nil, fmt.Errorf("repeat count %s too large", n) } if i < 1 { return nil, nil } // Inv: i > 0, len > 0 sz := len(elems) * i if sz < 0 || sz >= maxAlloc { // sz < 0 => overflow // Don't print sz. return nil, fmt.Errorf("excessive repeat (%d * %d elements)", len(elems), i) } res := make([]Value, sz) // copy elems into res, doubling each time x := copy(res, elems) for x < len(res) { copy(res[x:], res[:x]) x *= 2 } return res, nil } func bytesRepeat(b Bytes, n Int) (Bytes, error) { res, err := stringRepeat(String(b), n) return Bytes(res), err } func stringRepeat(s String, n Int) (String, error) { if s == "" { return "", nil } i, err := AsInt32(n) if err != nil { return "", fmt.Errorf("repeat count %s too large", n) } if i < 1 { return "", nil } // Inv: i > 0, len > 0 sz := len(s) * i if sz < 0 || sz >= maxAlloc { // sz < 0 => overflow // Don't print sz. return "", fmt.Errorf("excessive repeat (%d * %d elements)", len(s), i) } return String(strings.Repeat(string(s), i)), nil } // Call calls the function fn with the specified positional and keyword arguments. func Call(thread *Thread, fn Value, args Tuple, kwargs []Tuple) (Value, error) { c, ok := fn.(Callable) if !ok { return nil, fmt.Errorf("invalid call of non-function (%s)", fn.Type()) } // Allocate and push a new frame. var fr *frame // Optimization: use slack portion of thread.stack // slice as a freelist of empty frames. if n := len(thread.stack); n < cap(thread.stack) { fr = thread.stack[n : n+1][0] } if fr == nil { fr = new(frame) } if thread.stack == nil { // one-time initialization of thread if thread.maxSteps == 0 { thread.maxSteps-- // (MaxUint64) } } thread.stack = append(thread.stack, fr) // push fr.callable = c thread.beginProfSpan() result, err := c.CallInternal(thread, args, kwargs) thread.endProfSpan() // Sanity check: nil is not a valid Starlark value. if result == nil && err == nil { err = fmt.Errorf("internal error: nil (not None) returned from %s", fn) } // Always return an EvalError with an accurate frame. if err != nil { if _, ok := err.(*EvalError); !ok { err = thread.evalError(err) } } *fr = frame{} // clear out any references thread.stack = thread.stack[:len(thread.stack)-1] // pop return result, err } func slice(x, lo, hi, step_ Value) (Value, error) { sliceable, ok := x.(Sliceable) if !ok { return nil, fmt.Errorf("invalid slice operand %s", x.Type()) } n := sliceable.Len() step := 1 if step_ != None { var err error step, err = AsInt32(step_) if err != nil { return nil, fmt.Errorf("invalid slice step: %s", err) } if step == 0 { return nil, fmt.Errorf("zero is not a valid slice step") } } // TODO(adonovan): opt: preallocate result array. var start, end int if step > 0 { // positive stride // default indices are [0:n]. var err error start, end, err = indices(lo, hi, n) if err != nil { return nil, err } if end < start { end = start // => empty result } } else { // negative stride // default indices are effectively [n-1:-1], though to // get this effect using explicit indices requires // [n-1:-1-n:-1] because of the treatment of -ve values. start = n - 1 if err := asIndex(lo, n, &start); err != nil { return nil, fmt.Errorf("invalid start index: %s", err) } if start >= n { start = n - 1 } end = -1 if err := asIndex(hi, n, &end); err != nil { return nil, fmt.Errorf("invalid end index: %s", err) } if end < -1 { end = -1 } if start < end { start = end // => empty result } } return sliceable.Slice(start, end, step), nil } // From Hacker's Delight, section 2.8. func signum64(x int64) int { return int(uint64(x>>63) | uint64(-x)>>63) } func signum(x int) int { return signum64(int64(x)) } // indices converts start_ and end_ to indices in the range [0:len]. // The start index defaults to 0 and the end index defaults to len. // An index -len < i < 0 is treated like i+len. // All other indices outside the range are clamped to the nearest value in the range. // Beware: start may be greater than end. // This function is suitable only for slices with positive strides. func indices(start_, end_ Value, len int) (start, end int, err error) { start = 0 if err := asIndex(start_, len, &start); err != nil { return 0, 0, fmt.Errorf("invalid start index: %s", err) } // Clamp to [0:len]. if start < 0 { start = 0 } else if start > len { start = len } end = len if err := asIndex(end_, len, &end); err != nil { return 0, 0, fmt.Errorf("invalid end index: %s", err) } // Clamp to [0:len]. if end < 0 { end = 0 } else if end > len { end = len } return start, end, nil } // asIndex sets *result to the integer value of v, adding len to it // if it is negative. If v is nil or None, *result is unchanged. func asIndex(v Value, len int, result *int) error { if v != nil && v != None { var err error *result, err = AsInt32(v) if err != nil { return err } if *result < 0 { *result += len } } return nil } // setArgs sets the values of the formal parameters of function fn in // based on the actual parameter values in args and kwargs. func setArgs(locals []Value, fn *Function, args Tuple, kwargs []Tuple) error { // This is the general schema of a function: // // def f(p1, p2=dp2, p3=dp3, *args, k1, k2=dk2, k3, **kwargs) // // The p parameters are non-kwonly, and may be specified positionally. // The k parameters are kwonly, and must be specified by name. // The defaults tuple is (dp2, dp3, mandatory, dk2, mandatory). // // Arguments are processed as follows: // - positional arguments are bound to a prefix of [p1, p2, p3]. // - surplus positional arguments are bound to *args. // - keyword arguments are bound to any of {p1, p2, p3, k1, k2, k3}; // duplicate bindings are rejected. // - surplus keyword arguments are bound to **kwargs. // - defaults are bound to each parameter from p2 to k3 if no value was set. // default values come from the tuple above. // It is an error if the tuple entry for an unset parameter is 'mandatory'. // Nullary function? if fn.NumParams() == 0 { if nactual := len(args) + len(kwargs); nactual > 0 { return fmt.Errorf("function %s accepts no arguments (%d given)", fn.Name(), nactual) } return nil } cond := func(x bool, y, z interface{}) interface{} { if x { return y } return z } // nparams is the number of ordinary parameters (sans *args and **kwargs). nparams := fn.NumParams() var kwdict *Dict if fn.HasKwargs() { nparams-- kwdict = new(Dict) locals[nparams] = kwdict } if fn.HasVarargs() { nparams-- } // nonkwonly is the number of non-kwonly parameters. nonkwonly := nparams - fn.NumKwonlyParams() // Too many positional args? n := len(args) if len(args) > nonkwonly { if !fn.HasVarargs() { return fmt.Errorf("function %s accepts %s%d positional argument%s (%d given)", fn.Name(), cond(len(fn.defaults) > fn.NumKwonlyParams(), "at most ", ""), nonkwonly, cond(nonkwonly == 1, "", "s"), len(args)) } n = nonkwonly } // Bind positional arguments to non-kwonly parameters. for i := 0; i < n; i++ { locals[i] = args[i] } // Bind surplus positional arguments to *args parameter. if fn.HasVarargs() { tuple := make(Tuple, len(args)-n) for i := n; i < len(args); i++ { tuple[i-n] = args[i] } locals[nparams] = tuple } // Bind keyword arguments to parameters. paramIdents := fn.funcode.Locals[:nparams] for _, pair := range kwargs { k, v := pair[0].(String), pair[1] if i := findParam(paramIdents, string(k)); i >= 0 { if locals[i] != nil { return fmt.Errorf("function %s got multiple values for parameter %s", fn.Name(), k) } locals[i] = v continue } if kwdict == nil { return fmt.Errorf("function %s got an unexpected keyword argument %s", fn.Name(), k) } oldlen := kwdict.Len() kwdict.SetKey(k, v) if kwdict.Len() == oldlen { return fmt.Errorf("function %s got multiple values for parameter %s", fn.Name(), k) } } // Are defaults required? if n < nparams || fn.NumKwonlyParams() > 0 { m := nparams - len(fn.defaults) // first default // Report errors for missing required arguments. var missing []string var i int for i = n; i < m; i++ { if locals[i] == nil { missing = append(missing, paramIdents[i].Name) } } // Bind default values to parameters. for ; i < nparams; i++ { if locals[i] == nil { dflt := fn.defaults[i-m] if _, ok := dflt.(mandatory); ok { missing = append(missing, paramIdents[i].Name) continue } locals[i] = dflt } } if missing != nil { return fmt.Errorf("function %s missing %d argument%s (%s)", fn.Name(), len(missing), cond(len(missing) > 1, "s", ""), strings.Join(missing, ", ")) } } return nil } func findParam(params []compile.Binding, name string) int { for i, param := range params { if param.Name == name { return i } } return -1 } // https://github.com/google/starlark-go/blob/master/doc/spec.md#string-interpolation func interpolate(format string, x Value) (Value, error) { buf := new(strings.Builder) index := 0 nargs := 1 if tuple, ok := x.(Tuple); ok { nargs = len(tuple) } for { i := strings.IndexByte(format, '%') if i < 0 { buf.WriteString(format) break } buf.WriteString(format[:i]) format = format[i+1:] if format != "" && format[0] == '%' { buf.WriteByte('%') format = format[1:] continue } var arg Value if format != "" && format[0] == '(' { // keyword argument: %(name)s. format = format[1:] j := strings.IndexByte(format, ')') if j < 0 { return nil, fmt.Errorf("incomplete format key") } key := format[:j] if dict, ok := x.(Mapping); !ok { return nil, fmt.Errorf("format requires a mapping") } else if v, found, _ := dict.Get(String(key)); found { arg = v } else { return nil, fmt.Errorf("key not found: %s", key) } format = format[j+1:] } else { // positional argument: %s. if index >= nargs { return nil, fmt.Errorf("not enough arguments for format string") } if tuple, ok := x.(Tuple); ok { arg = tuple[index] } else { arg = x } } // NOTE: Starlark does not support any of these optional Python features: // - optional conversion flags: [#0- +], etc. // - optional minimum field width (number or *). // - optional precision (.123 or *) // - optional length modifier // conversion type if format == "" { return nil, fmt.Errorf("incomplete format") } switch c := format[0]; c { case 's', 'r': if str, ok := AsString(arg); ok && c == 's' { buf.WriteString(str) } else { writeValue(buf, arg, nil) } case 'd', 'i', 'o', 'x', 'X': i, err := NumberToInt(arg) if err != nil { return nil, fmt.Errorf("%%%c format requires integer: %v", c, err) } switch c { case 'd', 'i': fmt.Fprintf(buf, "%d", i) case 'o': fmt.Fprintf(buf, "%o", i) case 'x': fmt.Fprintf(buf, "%x", i) case 'X': fmt.Fprintf(buf, "%X", i) } case 'e', 'f', 'g', 'E', 'F', 'G': f, ok := AsFloat(arg) if !ok { return nil, fmt.Errorf("%%%c format requires float, not %s", c, arg.Type()) } Float(f).format(buf, c) case 'c': switch arg := arg.(type) { case Int: // chr(int) r, err := AsInt32(arg) if err != nil || r < 0 || r > unicode.MaxRune { return nil, fmt.Errorf("%%c format requires a valid Unicode code point, got %s", arg) } buf.WriteRune(rune(r)) case String: r, size := utf8.DecodeRuneInString(string(arg)) if size != len(arg) || len(arg) == 0 { return nil, fmt.Errorf("%%c format requires a single-character string") } buf.WriteRune(r) default: return nil, fmt.Errorf("%%c format requires int or single-character string, not %s", arg.Type()) } case '%': buf.WriteByte('%') default: return nil, fmt.Errorf("unknown conversion %%%c", c) } format = format[1:] index++ } if index < nargs { return nil, fmt.Errorf("too many arguments for format string") } return String(buf.String()), nil }