diff options
Diffstat (limited to 'vendor/github.com/robertkrimen/otto/runtime.go')
-rw-r--r-- | vendor/github.com/robertkrimen/otto/runtime.go | 711 |
1 files changed, 711 insertions, 0 deletions
diff --git a/vendor/github.com/robertkrimen/otto/runtime.go b/vendor/github.com/robertkrimen/otto/runtime.go new file mode 100644 index 000000000..5762bd0c3 --- /dev/null +++ b/vendor/github.com/robertkrimen/otto/runtime.go @@ -0,0 +1,711 @@ +package otto + +import ( + "errors" + "fmt" + "math" + "path" + "reflect" + "runtime" + "strconv" + "sync" + + "github.com/robertkrimen/otto/ast" + "github.com/robertkrimen/otto/parser" +) + +type _global struct { + Object *_object // Object( ... ), new Object( ... ) - 1 (length) + Function *_object // Function( ... ), new Function( ... ) - 1 + Array *_object // Array( ... ), new Array( ... ) - 1 + String *_object // String( ... ), new String( ... ) - 1 + Boolean *_object // Boolean( ... ), new Boolean( ... ) - 1 + Number *_object // Number( ... ), new Number( ... ) - 1 + Math *_object + Date *_object // Date( ... ), new Date( ... ) - 7 + RegExp *_object // RegExp( ... ), new RegExp( ... ) - 2 + Error *_object // Error( ... ), new Error( ... ) - 1 + EvalError *_object + TypeError *_object + RangeError *_object + ReferenceError *_object + SyntaxError *_object + URIError *_object + JSON *_object + + ObjectPrototype *_object // Object.prototype + FunctionPrototype *_object // Function.prototype + ArrayPrototype *_object // Array.prototype + StringPrototype *_object // String.prototype + BooleanPrototype *_object // Boolean.prototype + NumberPrototype *_object // Number.prototype + DatePrototype *_object // Date.prototype + RegExpPrototype *_object // RegExp.prototype + ErrorPrototype *_object // Error.prototype + EvalErrorPrototype *_object + TypeErrorPrototype *_object + RangeErrorPrototype *_object + ReferenceErrorPrototype *_object + SyntaxErrorPrototype *_object + URIErrorPrototype *_object +} + +type _runtime struct { + global _global + globalObject *_object + globalStash *_objectStash + scope *_scope + otto *Otto + eval *_object // The builtin eval, for determine indirect versus direct invocation + debugger func(*Otto) + random func() float64 + stackLimit int + traceLimit int + + labels []string // FIXME + lck sync.Mutex +} + +func (self *_runtime) enterScope(scope *_scope) { + scope.outer = self.scope + if self.scope != nil { + if self.stackLimit != 0 && self.scope.depth+1 >= self.stackLimit { + panic(self.panicRangeError("Maximum call stack size exceeded")) + } + + scope.depth = self.scope.depth + 1 + } + + self.scope = scope +} + +func (self *_runtime) leaveScope() { + self.scope = self.scope.outer +} + +// FIXME This is used in two places (cloning) +func (self *_runtime) enterGlobalScope() { + self.enterScope(newScope(self.globalStash, self.globalStash, self.globalObject)) +} + +func (self *_runtime) enterFunctionScope(outer _stash, this Value) *_fnStash { + if outer == nil { + outer = self.globalStash + } + stash := self.newFunctionStash(outer) + var thisObject *_object + switch this.kind { + case valueUndefined, valueNull: + thisObject = self.globalObject + default: + thisObject = self.toObject(this) + } + self.enterScope(newScope(stash, stash, thisObject)) + return stash +} + +func (self *_runtime) putValue(reference _reference, value Value) { + name := reference.putValue(value) + if name != "" { + // Why? -- If reference.base == nil + // strict = false + self.globalObject.defineProperty(name, value, 0111, false) + } +} + +func (self *_runtime) tryCatchEvaluate(inner func() Value) (tryValue Value, exception bool) { + // resultValue = The value of the block (e.g. the last statement) + // throw = Something was thrown + // throwValue = The value of what was thrown + // other = Something that changes flow (return, break, continue) that is not a throw + // Otherwise, some sort of unknown panic happened, we'll just propagate it + defer func() { + if caught := recover(); caught != nil { + if exception, ok := caught.(*_exception); ok { + caught = exception.eject() + } + switch caught := caught.(type) { + case _error: + exception = true + tryValue = toValue_object(self.newError(caught.name, caught.messageValue(), 0)) + case Value: + exception = true + tryValue = caught + default: + panic(caught) + } + } + }() + + tryValue = inner() + return +} + +// toObject + +func (self *_runtime) toObject(value Value) *_object { + switch value.kind { + case valueEmpty, valueUndefined, valueNull: + panic(self.panicTypeError()) + case valueBoolean: + return self.newBoolean(value) + case valueString: + return self.newString(value) + case valueNumber: + return self.newNumber(value) + case valueObject: + return value._object() + } + panic(self.panicTypeError()) +} + +func (self *_runtime) objectCoerce(value Value) (*_object, error) { + switch value.kind { + case valueUndefined: + return nil, errors.New("undefined") + case valueNull: + return nil, errors.New("null") + case valueBoolean: + return self.newBoolean(value), nil + case valueString: + return self.newString(value), nil + case valueNumber: + return self.newNumber(value), nil + case valueObject: + return value._object(), nil + } + panic(self.panicTypeError()) +} + +func checkObjectCoercible(rt *_runtime, value Value) { + isObject, mustCoerce := testObjectCoercible(value) + if !isObject && !mustCoerce { + panic(rt.panicTypeError()) + } +} + +// testObjectCoercible + +func testObjectCoercible(value Value) (isObject bool, mustCoerce bool) { + switch value.kind { + case valueReference, valueEmpty, valueNull, valueUndefined: + return false, false + case valueNumber, valueString, valueBoolean: + return false, true + case valueObject: + return true, false + default: + panic("this should never happen") + } +} + +func (self *_runtime) safeToValue(value interface{}) (Value, error) { + result := Value{} + err := catchPanic(func() { + result = self.toValue(value) + }) + return result, err +} + +// convertNumeric converts numeric parameter val from js to that of type t if it is safe to do so, otherwise it panics. +// This allows literals (int64), bitwise values (int32) and the general form (float64) of javascript numerics to be passed as parameters to go functions easily. +func (self *_runtime) convertNumeric(v Value, t reflect.Type) reflect.Value { + val := reflect.ValueOf(v.export()) + + if val.Kind() == t.Kind() { + return val + } + + if val.Kind() == reflect.Interface { + val = reflect.ValueOf(val.Interface()) + } + + switch val.Kind() { + case reflect.Float32, reflect.Float64: + f64 := val.Float() + switch t.Kind() { + case reflect.Float64: + return reflect.ValueOf(f64) + case reflect.Float32: + if reflect.Zero(t).OverflowFloat(f64) { + panic(self.panicRangeError("converting float64 to float32 would overflow")) + } + + return val.Convert(t) + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + i64 := int64(f64) + if float64(i64) != f64 { + panic(self.panicRangeError(fmt.Sprintf("converting %v to %v would cause loss of precision", val.Type(), t))) + } + + // The float represents an integer + val = reflect.ValueOf(i64) + default: + panic(self.panicTypeError(fmt.Sprintf("cannot convert %v to %v", val.Type(), t))) + } + } + + switch val.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + i64 := val.Int() + switch t.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + if reflect.Zero(t).OverflowInt(i64) { + panic(self.panicRangeError(fmt.Sprintf("converting %v to %v would overflow", val.Type(), t))) + } + return val.Convert(t) + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + if i64 < 0 { + panic(self.panicRangeError(fmt.Sprintf("converting %v to %v would underflow", val.Type(), t))) + } + if reflect.Zero(t).OverflowUint(uint64(i64)) { + panic(self.panicRangeError(fmt.Sprintf("converting %v to %v would overflow", val.Type(), t))) + } + return val.Convert(t) + } + + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + u64 := val.Uint() + switch t.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + if u64 > math.MaxInt64 || reflect.Zero(t).OverflowInt(int64(u64)) { + panic(self.panicRangeError(fmt.Sprintf("converting %v to %v would overflow", val.Type(), t))) + } + return val.Convert(t) + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + if reflect.Zero(t).OverflowUint(u64) { + panic(self.panicRangeError(fmt.Sprintf("converting %v to %v would overflow", val.Type(), t))) + } + return val.Convert(t) + } + } + + panic(self.panicTypeError(fmt.Sprintf("unsupported type %v for numeric conversion", val.Type()))) +} + +var typeOfValue = reflect.TypeOf(Value{}) + +// convertCallParameter converts request val to type t if possible. +// If the conversion fails due to overflow or type miss-match then it panics. +// If no conversion is known then the original value is returned. +func (self *_runtime) convertCallParameter(v Value, t reflect.Type) reflect.Value { + if t == typeOfValue { + return reflect.ValueOf(v) + } + + if v.kind == valueObject { + if gso, ok := v._object().value.(*_goStructObject); ok { + if gso.value.Type().AssignableTo(t) { + return gso.value + } + } + } + + if t.Kind() == reflect.Interface { + iv := reflect.ValueOf(v.export()) + if iv.Type().AssignableTo(t) { + return iv + } + } + + tk := t.Kind() + + if tk == reflect.Ptr { + switch v.kind { + case valueEmpty, valueNull, valueUndefined: + return reflect.Zero(t) + default: + var vv reflect.Value + if err := catchPanic(func() { vv = self.convertCallParameter(v, t.Elem()) }); err == nil { + if vv.CanAddr() { + return vv.Addr() + } + + pv := reflect.New(vv.Type()) + pv.Elem().Set(vv) + return pv + } + } + } + + switch tk { + case reflect.Bool: + return reflect.ValueOf(v.bool()) + case reflect.String: + switch v.kind { + case valueString: + return reflect.ValueOf(v.value) + case valueNumber: + return reflect.ValueOf(fmt.Sprintf("%v", v.value)) + } + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Float32, reflect.Float64: + switch v.kind { + case valueNumber: + return self.convertNumeric(v, t) + } + case reflect.Slice: + if o := v._object(); o != nil { + if lv := o.get("length"); lv.IsNumber() { + l := lv.number().int64 + + s := reflect.MakeSlice(t, int(l), int(l)) + + tt := t.Elem() + + if o.class == "Array" { + for i := int64(0); i < l; i++ { + p, ok := o.property[strconv.FormatInt(i, 10)] + if !ok { + continue + } + + e, ok := p.value.(Value) + if !ok { + continue + } + + ev := self.convertCallParameter(e, tt) + + s.Index(int(i)).Set(ev) + } + } else if o.class == "GoArray" { + + var gslice bool + switch o.value.(type) { + case *_goSliceObject: + gslice = true + case *_goArrayObject: + gslice = false + } + + for i := int64(0); i < l; i++ { + var p *_property + if gslice { + p = goSliceGetOwnProperty(o, strconv.FormatInt(i, 10)) + } else { + p = goArrayGetOwnProperty(o, strconv.FormatInt(i, 10)) + } + if p == nil { + continue + } + + e, ok := p.value.(Value) + if !ok { + continue + } + + ev := self.convertCallParameter(e, tt) + + s.Index(int(i)).Set(ev) + } + } + + return s + } + } + case reflect.Map: + if o := v._object(); o != nil && t.Key().Kind() == reflect.String { + m := reflect.MakeMap(t) + + o.enumerate(false, func(k string) bool { + m.SetMapIndex(reflect.ValueOf(k), self.convertCallParameter(o.get(k), t.Elem())) + return true + }) + + return m + } + case reflect.Func: + if t.NumOut() > 1 { + panic(self.panicTypeError("converting JavaScript values to Go functions with more than one return value is currently not supported")) + } + + if o := v._object(); o != nil && o.class == "Function" { + return reflect.MakeFunc(t, func(args []reflect.Value) []reflect.Value { + l := make([]interface{}, len(args)) + for i, a := range args { + if a.CanInterface() { + l[i] = a.Interface() + } + } + + rv, err := v.Call(nullValue, l...) + if err != nil { + panic(err) + } + + if t.NumOut() == 0 { + return nil + } + + return []reflect.Value{self.convertCallParameter(rv, t.Out(0))} + }) + } + } + + if tk == reflect.String { + if o := v._object(); o != nil && o.hasProperty("toString") { + if fn := o.get("toString"); fn.IsFunction() { + sv, err := fn.Call(v) + if err != nil { + panic(err) + } + + var r reflect.Value + if err := catchPanic(func() { r = self.convertCallParameter(sv, t) }); err == nil { + return r + } + } + } + + return reflect.ValueOf(v.String()) + } + + s := "OTTO DOES NOT UNDERSTAND THIS TYPE" + switch v.kind { + case valueBoolean: + s = "boolean" + case valueNull: + s = "null" + case valueNumber: + s = "number" + case valueString: + s = "string" + case valueUndefined: + s = "undefined" + case valueObject: + s = v.Class() + } + + panic(self.panicTypeError("can't convert from %q to %q", s, t.String())) +} + +func (self *_runtime) toValue(value interface{}) Value { + switch value := value.(type) { + case Value: + return value + case func(FunctionCall) Value: + var name, file string + var line int + pc := reflect.ValueOf(value).Pointer() + fn := runtime.FuncForPC(pc) + if fn != nil { + name = fn.Name() + file, line = fn.FileLine(pc) + file = path.Base(file) + } + return toValue_object(self.newNativeFunction(name, file, line, value)) + case _nativeFunction: + var name, file string + var line int + pc := reflect.ValueOf(value).Pointer() + fn := runtime.FuncForPC(pc) + if fn != nil { + name = fn.Name() + file, line = fn.FileLine(pc) + file = path.Base(file) + } + return toValue_object(self.newNativeFunction(name, file, line, value)) + case Object, *Object, _object, *_object: + // Nothing happens. + // FIXME We should really figure out what can come here. + // This catch-all is ugly. + default: + { + value := reflect.ValueOf(value) + + switch value.Kind() { + case reflect.Ptr: + switch reflect.Indirect(value).Kind() { + case reflect.Struct: + return toValue_object(self.newGoStructObject(value)) + case reflect.Array: + return toValue_object(self.newGoArray(value)) + } + case reflect.Struct: + return toValue_object(self.newGoStructObject(value)) + case reflect.Map: + return toValue_object(self.newGoMapObject(value)) + case reflect.Slice: + return toValue_object(self.newGoSlice(value)) + case reflect.Array: + return toValue_object(self.newGoArray(value)) + case reflect.Func: + var name, file string + var line int + if v := reflect.ValueOf(value); v.Kind() == reflect.Ptr { + pc := v.Pointer() + fn := runtime.FuncForPC(pc) + if fn != nil { + name = fn.Name() + file, line = fn.FileLine(pc) + file = path.Base(file) + } + } + + typ := value.Type() + + return toValue_object(self.newNativeFunction(name, file, line, func(c FunctionCall) Value { + nargs := typ.NumIn() + + if len(c.ArgumentList) != nargs { + if typ.IsVariadic() { + if len(c.ArgumentList) < nargs-1 { + panic(self.panicRangeError(fmt.Sprintf("expected at least %d arguments; got %d", nargs-1, len(c.ArgumentList)))) + } + } else { + panic(self.panicRangeError(fmt.Sprintf("expected %d argument(s); got %d", nargs, len(c.ArgumentList)))) + } + } + + in := make([]reflect.Value, len(c.ArgumentList)) + + callSlice := false + + for i, a := range c.ArgumentList { + var t reflect.Type + + n := i + if n >= nargs-1 && typ.IsVariadic() { + if n > nargs-1 { + n = nargs - 1 + } + + t = typ.In(n).Elem() + } else { + t = typ.In(n) + } + + // if this is a variadic Go function, and the caller has supplied + // exactly the number of JavaScript arguments required, and this + // is the last JavaScript argument, try treating the it as the + // actual set of variadic Go arguments. if that succeeds, break + // out of the loop. + if typ.IsVariadic() && len(c.ArgumentList) == nargs && i == nargs-1 { + var v reflect.Value + if err := catchPanic(func() { v = self.convertCallParameter(a, typ.In(n)) }); err == nil { + in[i] = v + callSlice = true + break + } + } + + in[i] = self.convertCallParameter(a, t) + } + + var out []reflect.Value + if callSlice { + out = value.CallSlice(in) + } else { + out = value.Call(in) + } + + switch len(out) { + case 0: + return Value{} + case 1: + return self.toValue(out[0].Interface()) + default: + s := make([]interface{}, len(out)) + for i, v := range out { + s[i] = self.toValue(v.Interface()) + } + + return self.toValue(s) + } + })) + } + } + } + + return toValue(value) +} + +func (runtime *_runtime) newGoSlice(value reflect.Value) *_object { + self := runtime.newGoSliceObject(value) + self.prototype = runtime.global.ArrayPrototype + return self +} + +func (runtime *_runtime) newGoArray(value reflect.Value) *_object { + self := runtime.newGoArrayObject(value) + self.prototype = runtime.global.ArrayPrototype + return self +} + +func (runtime *_runtime) parse(filename string, src, sm interface{}) (*ast.Program, error) { + return parser.ParseFileWithSourceMap(nil, filename, src, sm, 0) +} + +func (runtime *_runtime) cmpl_parse(filename string, src, sm interface{}) (*_nodeProgram, error) { + program, err := parser.ParseFileWithSourceMap(nil, filename, src, sm, 0) + if err != nil { + return nil, err + } + + return cmpl_parse(program), nil +} + +func (self *_runtime) parseSource(src, sm interface{}) (*_nodeProgram, *ast.Program, error) { + switch src := src.(type) { + case *ast.Program: + return nil, src, nil + case *Script: + return src.program, nil, nil + } + + program, err := self.parse("", src, sm) + + return nil, program, err +} + +func (self *_runtime) cmpl_runOrEval(src, sm interface{}, eval bool) (Value, error) { + result := Value{} + cmpl_program, program, err := self.parseSource(src, sm) + if err != nil { + return result, err + } + if cmpl_program == nil { + cmpl_program = cmpl_parse(program) + } + err = catchPanic(func() { + result = self.cmpl_evaluate_nodeProgram(cmpl_program, eval) + }) + switch result.kind { + case valueEmpty: + result = Value{} + case valueReference: + result = result.resolve() + } + return result, err +} + +func (self *_runtime) cmpl_run(src, sm interface{}) (Value, error) { + return self.cmpl_runOrEval(src, sm, false) +} + +func (self *_runtime) cmpl_eval(src, sm interface{}) (Value, error) { + return self.cmpl_runOrEval(src, sm, true) +} + +func (self *_runtime) parseThrow(err error) { + if err == nil { + return + } + switch err := err.(type) { + case parser.ErrorList: + { + err := err[0] + if err.Message == "Invalid left-hand side in assignment" { + panic(self.panicReferenceError(err.Message)) + } + panic(self.panicSyntaxError(err.Message)) + } + } + panic(self.panicSyntaxError(err.Error())) +} + +func (self *_runtime) cmpl_parseOrThrow(src, sm interface{}) *_nodeProgram { + program, err := self.cmpl_parse("", src, sm) + self.parseThrow(err) // Will panic/throw appropriately + return program +} |