quorum/vendor/github.com/robertkrimen/otto/runtime.go

712 lines
19 KiB
Go

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
}