diff options
-rw-r--r-- | eval.go | 45 | ||||
-rw-r--r-- | internal/compile/compile.go | 64 | ||||
-rw-r--r-- | interp.go | 53 | ||||
-rw-r--r-- | value.go | 16 |
4 files changed, 68 insertions, 110 deletions
@@ -8,7 +8,9 @@ import ( "bytes" "fmt" "io" + "log" "math" + "math/big" "sort" "strings" "unicode" @@ -266,11 +268,7 @@ func CompiledProgram(in io.Reader) (*Program, error) { // 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 := &Function{ - funcode: prog.compiled.Toplevel, - predeclared: predeclared, - globals: make([]Value, len(prog.compiled.Globals)), - } + toplevel := makeToplevelFunction(prog.compiled.Toplevel, predeclared) _, err := toplevel.Call(thread, nil, nil) @@ -279,6 +277,34 @@ func (prog *Program) Init(thread *Thread, predeclared StringDict) (StringDict, e return toplevel.Globals(), err } +func makeToplevelFunction(funcode *compile.Funcode, predeclared StringDict) *Function { + // Create the Skylark value denoted by each program constant c. + constants := make([]Value, len(funcode.Prog.Constants)) + for i, c := range funcode.Prog.Constants { + var v Value + switch c := c.(type) { + case int64: + v = MakeInt64(c) + case *big.Int: + v = Int{c} + case string: + v = String(c) + case float64: + v = Float(c) + default: + log.Fatalf("unexpected constant %T: %v", c, c) + } + constants[i] = v + } + + return &Function{ + funcode: funcode, + predeclared: predeclared, + globals: make([]Value, len(funcode.Prog.Globals)), + constants: constants, + } +} + // Eval parses, resolves, and evaluates an expression within the // specified (predeclared) environment. // @@ -300,12 +326,9 @@ func Eval(thread *Thread, filename string, src interface{}, env StringDict) (Val return nil, err } - f := &Function{ - funcode: compile.Expr(expr, locals), - predeclared: env, - globals: nil, - } - return f.Call(thread, nil, nil) + fn := makeToplevelFunction(compile.Expr(expr, locals), env) + + return fn.Call(thread, nil, nil) } // The following functions are primitive operations of the byte code interpreter. diff --git a/internal/compile/compile.go b/internal/compile/compile.go index 01d33a1..6d86b4b 100644 --- a/internal/compile/compile.go +++ b/internal/compile/compile.go @@ -26,9 +26,9 @@ import ( "bytes" "fmt" "log" - "math/big" "os" "path/filepath" + "strconv" "github.com/google/skylark/resolve" "github.com/google/skylark/syntax" @@ -37,7 +37,7 @@ import ( const debug = false // TODO(adonovan): use a bitmap of options; and regexp to match files // Increment this to force recompilation of saved bytecode files. -const Version = 1 +const Version = 2 type Opcode uint8 @@ -106,10 +106,7 @@ const ( ITERJMP // - ITERJMP<addr> elem (and fall through) [acts on topmost iterator] // or: - ITERJMP<addr> - (and jump) - INT // - INT<constant> value - FLOAT // - FLOAT<constant> value - BIGINT // - BIGINT<constant> value - STRING // - STRING<constant> value + CONSTANT // - CONSTANT<constant> value MAKETUPLE // x1 ... xn MAKETUPLE<n> tuple MAKELIST // x1 ... xn MAKELIST<n> list MAKEFUNC // args kwargs MAKEFUNC<func> fn @@ -141,17 +138,16 @@ var opcodeNames = [...]string{ AMP: "amp", APPEND: "append", ATTR: "attr", - BIGINT: "bigint", CALL: "call", CALL_KW: "call_kw ", CALL_VAR: "call_var", CALL_VAR_KW: "call_var_kw", CJMP: "cjmp", - DUP: "dup", + CONSTANT: "constant", DUP2: "dup2", + DUP: "dup", EQL: "eql", FALSE: "false", - FLOAT: "float", FREE: "free", GE: "ge", GLOBAL: "global", @@ -159,7 +155,6 @@ var opcodeNames = [...]string{ IN: "in", INDEX: "index", INPLACE_ADD: "inplace_add", - INT: "int", ITERJMP: "iterjmp", ITERPOP: "iterpop", ITERPUSH: "iterpush", @@ -193,7 +188,6 @@ var opcodeNames = [...]string{ SLASHSLASH: "slashslash", SLICE: "slice", STAR: "star", - STRING: "string", TRUE: "true", UMINUS: "uminus", UNIVERSAL: "universal", @@ -209,17 +203,16 @@ var stackEffect = [...]int8{ AMP: -1, APPEND: -2, ATTR: 0, - BIGINT: +1, CALL: variableStackEffect, CALL_KW: variableStackEffect, CALL_VAR: variableStackEffect, CALL_VAR_KW: variableStackEffect, CJMP: -1, - DUP: +1, + CONSTANT: +1, DUP2: +2, + DUP: +1, EQL: -1, FALSE: +1, - FLOAT: +1, FREE: +1, GE: -1, GLOBAL: +1, @@ -227,10 +220,9 @@ var stackEffect = [...]int8{ IN: -1, INDEX: -1, INPLACE_ADD: -1, - INT: +1, ITERJMP: variableStackEffect, - ITERPUSH: -1, ITERPOP: 0, + ITERPUSH: -1, JMP: 0, LE: -1, LOAD: -1, @@ -246,10 +238,10 @@ var stackEffect = [...]int8{ NOP: 0, NOT: 0, PERCENT: -1, - PREDECLARED: +1, PIPE: -1, PLUS: -1, POP: -1, + PREDECLARED: +1, RETURN: -1, SETDICT: -3, SETDICTUNIQ: -3, @@ -261,10 +253,9 @@ var stackEffect = [...]int8{ SLASHSLASH: -1, SLICE: -3, STAR: -1, - STRING: +1, TRUE: +1, - UNPACK: variableStackEffect, UNIVERSAL: +1, + UNPACK: variableStackEffect, } func (op Opcode) String() string { @@ -281,7 +272,7 @@ func (op Opcode) String() string { type Program struct { Loads []Ident // name (really, string) and position of each load stmt Names []string // names of attributes and predeclared variables - Constants []interface{} // = string | int64 | float64 + Constants []interface{} // = string | int64 | float64 | *big.Int Functions []*Funcode Globals []Ident // for error messages and tracing Toplevel *Funcode // module initialization function @@ -741,10 +732,13 @@ func PrintOp(fn *Funcode, pc uint32, op Opcode, arg uint32) { var comment string switch op { - case INT, FLOAT, BIGINT: - comment = fmt.Sprint(fn.Prog.Constants[arg]) - case STRING: - comment = fmt.Sprintf("%q", fn.Prog.Constants[arg]) + case CONSTANT: + switch x := fn.Prog.Constants[arg].(type) { + case string: + comment = strconv.Quote(x) + default: + comment = fmt.Sprint(x) + } case MAKEFUNC: comment = fn.Prog.Functions[arg].Name case SETLOCAL, LOCAL: @@ -856,12 +850,7 @@ func (pcomp *pcomp) functionIndex(fn *Funcode) uint32 { // string emits code to push the specified string. func (fcomp *fcomp) string(s string) { - fcomp.emit1(STRING, fcomp.pcomp.constantIndex(s)) -} - -// int64 emits code to push the specified integer. -func (fcomp *fcomp) int64(i int64) { - fcomp.emit1(INT, fcomp.pcomp.constantIndex(i)) + fcomp.emit1(CONSTANT, fcomp.pcomp.constantIndex(s)) } // setPos sets the current source position. @@ -1137,19 +1126,8 @@ func (fcomp *fcomp) expr(e syntax.Expr) { fcomp.lookup(e) case *syntax.Literal: - switch e.Token { - case syntax.INT: - switch e.Value.(type) { - case int64: - fcomp.int64(e.Value.(int64)) - case *big.Int: - fcomp.emit1(BIGINT, fcomp.pcomp.constantIndex(e.Value.(*big.Int).String())) - } - case syntax.FLOAT: - fcomp.emit1(FLOAT, fcomp.pcomp.constantIndex(e.Value.(float64))) - case syntax.STRING: - fcomp.string(e.Value.(string)) - } + // e.Value is int64, float64, *bigInt, or string. + fcomp.emit1(CONSTANT, fcomp.pcomp.constantIndex(e.Value)) case *syntax.ListExpr: for _, x := range e.List { @@ -4,9 +4,7 @@ package skylark import ( "fmt" - "math/big" "os" - "unsafe" "github.com/google/skylark/internal/compile" "github.com/google/skylark/syntax" @@ -433,29 +431,8 @@ loop: } sp-- - case compile.INT: - stack[sp] = MakeInt64(f.Prog.Constants[arg].(int64)) - sp++ - - case compile.BIGINT: - s := f.Prog.Constants[arg].(string) - bigint := new(big.Int) - if _, ok := bigint.SetString(s, 10); !ok { - err = fmt.Errorf("internal error: failed to parse compiled large integer constant") - break loop - } - stack[sp] = Int{bigint} - sp++ - - case compile.FLOAT: - stack[sp] = Float(f.Prog.Constants[arg].(float64)) - sp++ - - case compile.STRING: - // TODO(adonovan): opt: avoid allocation here - // by prematerializing String values. - // stack[sp] = String(f.Prog.Constants[arg].(string)) - stack[sp] = string2String(f.Prog.Constants[arg]) + case compile.CONSTANT: + stack[sp] = fr.fn.constants[arg] sp++ case compile.MAKETUPLE: @@ -483,6 +460,7 @@ loop: funcode: funcode, predeclared: fr.fn.predeclared, globals: fr.fn.globals, + constants: fr.fn.constants, defaults: defaults, freevars: freevars, } @@ -585,28 +563,3 @@ loop: } return result, err } - -// string2String converts an empty interface containing a string into a -// Value containing a String, without allocating a new string header; -// see github.com/golang/go/issues/24582. -// TODO(adonovan): do this safely by preconverting constants a priori. -// This requires that Program provide a field for use by the interpreter. -func string2String(x interface{}) (y Value) { - // Equivalent to: - // return String(x.(string)) - // but avoids allocation. - - _ = x.(string) - type iface struct { - typ, data unsafe.Pointer - } - xi := (*iface)((unsafe.Pointer)(&x)) - yi := (*iface)((unsafe.Pointer)(&y)) - si := (*iface)((unsafe.Pointer)(&dummystr)) - - yi.typ = si.typ - yi.data = xi.data - return y -} - -var dummystr Value = String("") @@ -464,13 +464,17 @@ func (it *stringIterator) Next(p *Value) bool { func (*stringIterator) Done() {} -// A Function is a function defined by a Skylark def statement. +// A Function is a function defined by a Skylark def statement or lambda expression. +// The initialization behavior of a Skylark module is also represented by a Function. type Function struct { - funcode *compile.Funcode - predeclared StringDict // names predeclared in the current module - globals []Value // globals of the current module - defaults Tuple - freevars Tuple + funcode *compile.Funcode + defaults Tuple + freevars Tuple + + // These fields are shared by all functions in a module. + predeclared StringDict + globals []Value + constants []Value } func (fn *Function) Name() string { return fn.funcode.Name } // "lambda" for anonymous functions |