mirror of
https://github.com/robertkrimen/otto
synced 2025-10-12 20:27:30 +08:00
528 lines
15 KiB
Go
528 lines
15 KiB
Go
package otto
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"math"
|
|
"reflect"
|
|
"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
|
|
|
|
labels []string // FIXME
|
|
lck sync.Mutex
|
|
}
|
|
|
|
func (self *_runtime) enterScope(scope *_scope) {
|
|
scope.outer = self.scope
|
|
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()))
|
|
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:
|
|
isObject = false
|
|
mustCoerce = true
|
|
case valueObject:
|
|
isObject = true
|
|
mustCoerce = false
|
|
}
|
|
return
|
|
}
|
|
|
|
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 convertNumeric(val reflect.Value, t reflect.Type) reflect.Value {
|
|
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("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(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(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(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(fmt.Sprintf("converting %v to %v would underflow", val.Type(), t))
|
|
}
|
|
if reflect.Zero(t).OverflowUint(uint64(i64)) {
|
|
panic(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(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(fmt.Sprintf("converting %v to %v would overflow", val.Type(), t))
|
|
}
|
|
return val.Convert(t)
|
|
}
|
|
}
|
|
|
|
panic(fmt.Sprintf("unsupported type %v for numeric conversion", val.Type()))
|
|
}
|
|
|
|
// callParamConvert 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 callParamConvert(val reflect.Value, t reflect.Type) reflect.Value {
|
|
if val.Kind() == reflect.Interface {
|
|
val = reflect.ValueOf(val.Interface())
|
|
}
|
|
|
|
switch t.Kind() {
|
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Float32, reflect.Float64:
|
|
if val.Kind() == t.Kind() {
|
|
// Types already match
|
|
return val
|
|
}
|
|
return convertNumeric(val, t)
|
|
case reflect.Slice:
|
|
if val.Kind() != reflect.Slice {
|
|
// Conversion from none slice type to slice not possible
|
|
panic(fmt.Sprintf("cannot use %v as type %v", val, t))
|
|
}
|
|
default:
|
|
// No supported conversion
|
|
return val
|
|
}
|
|
|
|
elemType := t.Elem()
|
|
switch elemType.Kind() {
|
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Float32, reflect.Float64, reflect.Slice:
|
|
// Attempt to convert to slice of the type t
|
|
s := reflect.MakeSlice(reflect.SliceOf(elemType), val.Len(), val.Len())
|
|
for i := 0; i < val.Len(); i++ {
|
|
s.Index(i).Set(callParamConvert(val.Index(i), elemType))
|
|
}
|
|
|
|
return s
|
|
}
|
|
|
|
// Not a slice type we can convert
|
|
return val
|
|
}
|
|
|
|
// callSliceRequired returns true if CallSlice is required instead of Call.
|
|
func callSliceRequired(param reflect.Type, val reflect.Value) bool {
|
|
vt := val.Type()
|
|
for param.Kind() == reflect.Slice {
|
|
if val.Kind() == reflect.Interface {
|
|
val = reflect.ValueOf(val.Interface())
|
|
vt = val.Type()
|
|
}
|
|
|
|
if vt.Kind() != reflect.Slice {
|
|
return false
|
|
}
|
|
|
|
vt = vt.Elem()
|
|
if val.Kind() != reflect.Invalid {
|
|
if val.Len() > 0 {
|
|
val = val.Index(0)
|
|
} else {
|
|
val = reflect.Value{}
|
|
}
|
|
}
|
|
param = param.Elem()
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
func (self *_runtime) toValue(value interface{}) Value {
|
|
switch value := value.(type) {
|
|
case Value:
|
|
return value
|
|
case func(FunctionCall) Value:
|
|
return toValue_object(self.newNativeFunction("", value))
|
|
case _nativeFunction:
|
|
return toValue_object(self.newNativeFunction("", 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.Func:
|
|
// TODO Maybe cache this?
|
|
return toValue_object(self.newNativeFunction("", func(call FunctionCall) Value {
|
|
argsCount := len(call.ArgumentList)
|
|
in := make([]reflect.Value, argsCount)
|
|
t := value.Type()
|
|
callSlice := false
|
|
paramsCount := t.NumIn()
|
|
lastParam := paramsCount - 1
|
|
lastArg := argsCount - 1
|
|
isVariadic := t.IsVariadic()
|
|
for i, value := range call.ArgumentList {
|
|
var paramType reflect.Type
|
|
if isVariadic && i == lastArg && argsCount == paramsCount {
|
|
// Variadic functions last parameter and parameter numbers match incoming args
|
|
paramType = t.In(lastArg)
|
|
val := reflect.ValueOf(value.export())
|
|
callSlice = callSliceRequired(paramType, val)
|
|
if callSlice {
|
|
in[i] = callParamConvert(reflect.ValueOf(value.export()), paramType)
|
|
continue
|
|
}
|
|
}
|
|
|
|
if i >= lastParam {
|
|
if isVariadic {
|
|
paramType = t.In(lastParam).Elem()
|
|
} else {
|
|
paramType = t.In(lastParam)
|
|
}
|
|
} else {
|
|
paramType = t.In(i)
|
|
}
|
|
in[i] = callParamConvert(reflect.ValueOf(value.export()), paramType)
|
|
}
|
|
|
|
var out []reflect.Value
|
|
if callSlice {
|
|
out = value.CallSlice(in)
|
|
} else {
|
|
out = value.Call(in)
|
|
}
|
|
|
|
l := len(out)
|
|
switch l {
|
|
case 0:
|
|
return Value{}
|
|
case 1:
|
|
return self.toValue(out[0].Interface())
|
|
}
|
|
|
|
// Return an array of the values to emulate multi value return.
|
|
// In the future this can be used along side destructuring assignment.
|
|
s := make([]interface{}, l)
|
|
for i, v := range out {
|
|
s[i] = self.toValue(v.Interface())
|
|
}
|
|
return self.toValue(s)
|
|
}))
|
|
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))
|
|
}
|
|
}
|
|
}
|
|
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 interface{}) (*ast.Program, error) {
|
|
return parser.ParseFile(nil, filename, src, 0)
|
|
}
|
|
|
|
func (runtime *_runtime) cmpl_parse(filename string, src interface{}) (*_nodeProgram, error) {
|
|
program, err := parser.ParseFile(nil, filename, src, 0)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return cmpl_parse(program), nil
|
|
}
|
|
|
|
func (self *_runtime) parseSource(src 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)
|
|
return nil, program, err
|
|
}
|
|
|
|
func (self *_runtime) cmpl_runOrEval(src interface{}, eval bool) (Value, error) {
|
|
result := Value{}
|
|
cmpl_program, program, err := self.parseSource(src)
|
|
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 interface{}) (Value, error) {
|
|
return self.cmpl_runOrEval(src, false)
|
|
}
|
|
|
|
func (self *_runtime) cmpl_eval(src interface{}) (Value, error) {
|
|
return self.cmpl_runOrEval(src, 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) parseOrThrow(source string) *ast.Program {
|
|
program, err := self.parse("", source)
|
|
self.parseThrow(err) // Will panic/throw appropriately
|
|
return program
|
|
}
|
|
|
|
func (self *_runtime) cmpl_parseOrThrow(source string) *_nodeProgram {
|
|
program, err := self.cmpl_parse("", source)
|
|
self.parseThrow(err) // Will panic/throw appropriately
|
|
return program
|
|
}
|