395 lines
11 KiB
Go
395 lines
11 KiB
Go
|
package otto
|
||
|
|
||
|
import (
|
||
|
"errors"
|
||
|
"reflect"
|
||
|
|
||
|
"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 {
|
||
|
Stack [](*_executionContext)
|
||
|
|
||
|
GlobalObject *_object
|
||
|
GlobalEnvironment *_objectEnvironment
|
||
|
|
||
|
Global _global
|
||
|
|
||
|
eval *_object // The builtin eval, for determine indirect versus direct invocation
|
||
|
|
||
|
Otto *Otto
|
||
|
|
||
|
labels []string // FIXME
|
||
|
}
|
||
|
|
||
|
func (self *_runtime) EnterGlobalExecutionContext() {
|
||
|
self.EnterExecutionContext(newExecutionContext(self.GlobalEnvironment, self.GlobalEnvironment, self.GlobalObject))
|
||
|
}
|
||
|
|
||
|
func (self *_runtime) EnterExecutionContext(scope *_executionContext) {
|
||
|
self.Stack = append(self.Stack, scope)
|
||
|
}
|
||
|
|
||
|
func (self *_runtime) LeaveExecutionContext() {
|
||
|
self.Stack = self.Stack[:len(self.Stack)-1]
|
||
|
}
|
||
|
|
||
|
func (self *_runtime) _executionContext(depth int) *_executionContext {
|
||
|
if depth == 0 {
|
||
|
return self.Stack[len(self.Stack)-1]
|
||
|
}
|
||
|
if len(self.Stack)-1+depth >= 0 {
|
||
|
return self.Stack[len(self.Stack)-1+depth]
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func (self *_runtime) EnterFunctionExecutionContext(function *_object, this Value) *_functionEnvironment {
|
||
|
scopeEnvironment := function.functionValue().call.ScopeEnvironment()
|
||
|
if scopeEnvironment == nil {
|
||
|
scopeEnvironment = self.GlobalEnvironment
|
||
|
}
|
||
|
environment := self.newFunctionEnvironment(scopeEnvironment)
|
||
|
var thisObject *_object
|
||
|
switch this._valueType {
|
||
|
case valueUndefined, valueNull:
|
||
|
thisObject = self.GlobalObject
|
||
|
default:
|
||
|
thisObject = self.toObject(this)
|
||
|
}
|
||
|
self.EnterExecutionContext(newExecutionContext(environment, environment, thisObject))
|
||
|
return environment
|
||
|
}
|
||
|
|
||
|
func (self *_runtime) EnterEvalExecutionContext(call FunctionCall) {
|
||
|
// Skip the current function lexical/variable environment, which is of the function execution context call
|
||
|
// to eval (the global execution context). Instead, execute in the context of where the eval was called,
|
||
|
// which is essentially dynamic scoping
|
||
|
parent := self._executionContext(-1)
|
||
|
new := newExecutionContext(parent.LexicalEnvironment, parent.VariableEnvironment, parent.this)
|
||
|
// FIXME Make passing through of self.GlobalObject more general? Whenever newExecutionContext is passed a nil object?
|
||
|
new.eval = true
|
||
|
self.EnterExecutionContext(new)
|
||
|
}
|
||
|
|
||
|
func (self *_runtime) GetValue(value Value) Value {
|
||
|
if value.isReference() {
|
||
|
return value.reference().GetValue()
|
||
|
}
|
||
|
return value
|
||
|
}
|
||
|
|
||
|
func (self *_runtime) PutValue(reference _reference, value Value) {
|
||
|
if !reference.PutValue(value) {
|
||
|
// Why? -- If reference.Base == nil
|
||
|
strict := false
|
||
|
self.GlobalObject.defineProperty(reference.GetName(), value, 0111, strict)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (self *_runtime) Call(function *_object, this Value, argumentList []Value, evalHint bool) Value {
|
||
|
// Pass eval boolean through to EnterFunctionExecutionContext for further testing
|
||
|
_functionEnvironment := self.EnterFunctionExecutionContext(function, this)
|
||
|
defer func() {
|
||
|
self.LeaveExecutionContext()
|
||
|
}()
|
||
|
|
||
|
if evalHint {
|
||
|
evalHint = function == self.eval // If evalHint is true, then it IS a direct eval
|
||
|
}
|
||
|
callValue := function.functionValue().call.Dispatch(function, _functionEnvironment, self, this, argumentList, evalHint)
|
||
|
if value, valid := callValue.value.(_result); valid {
|
||
|
return value.value
|
||
|
}
|
||
|
return callValue
|
||
|
}
|
||
|
|
||
|
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 *_syntaxError:
|
||
|
// exception = true
|
||
|
// tryValue = toValue_object(self.newError("SyntaxError", toValue_string(caught.Message)))
|
||
|
case Value:
|
||
|
exception = true
|
||
|
tryValue = caught
|
||
|
default:
|
||
|
panic(caught)
|
||
|
}
|
||
|
}
|
||
|
}()
|
||
|
|
||
|
tryValue = inner()
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// _executionContext Proxy
|
||
|
|
||
|
func (self *_runtime) localGet(name string) Value {
|
||
|
return self._executionContext(0).getValue(name)
|
||
|
}
|
||
|
|
||
|
func (self *_runtime) localSet(name string, value Value) {
|
||
|
self._executionContext(0).setValue(name, value, false)
|
||
|
}
|
||
|
|
||
|
func (self *_runtime) VariableEnvironment() _environment {
|
||
|
return self._executionContext(0).VariableEnvironment
|
||
|
}
|
||
|
|
||
|
func (self *_runtime) LexicalEnvironment() _environment {
|
||
|
return self._executionContext(0).LexicalEnvironment
|
||
|
}
|
||
|
|
||
|
// toObject
|
||
|
|
||
|
func (self *_runtime) toObject(value Value) *_object {
|
||
|
switch value._valueType {
|
||
|
case valueEmpty, valueUndefined, valueNull:
|
||
|
panic(newTypeError())
|
||
|
case valueBoolean:
|
||
|
return self.newBoolean(value)
|
||
|
case valueString:
|
||
|
return self.newString(value)
|
||
|
case valueNumber:
|
||
|
return self.newNumber(value)
|
||
|
case valueObject:
|
||
|
return value._object()
|
||
|
}
|
||
|
panic(newTypeError())
|
||
|
}
|
||
|
|
||
|
func (self *_runtime) objectCoerce(value Value) (*_object, error) {
|
||
|
switch value._valueType {
|
||
|
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(newTypeError())
|
||
|
}
|
||
|
|
||
|
func checkObjectCoercible(value Value) {
|
||
|
isObject, mustCoerce := testObjectCoercible(value)
|
||
|
if !isObject && !mustCoerce {
|
||
|
panic(newTypeError())
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// testObjectCoercible
|
||
|
|
||
|
func testObjectCoercible(value Value) (isObject bool, mustCoerce bool) {
|
||
|
switch value._valueType {
|
||
|
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) ToValue(value interface{}) (Value, error) {
|
||
|
result := UndefinedValue()
|
||
|
err := catchPanic(func() {
|
||
|
result = self.toValue(value)
|
||
|
})
|
||
|
return result, err
|
||
|
}
|
||
|
|
||
|
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
|
||
|
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:
|
||
|
return toValue_object(self.newNativeFunction("", func(call FunctionCall) Value {
|
||
|
args := make([]reflect.Value, len(call.ArgumentList))
|
||
|
for i, a := range call.ArgumentList {
|
||
|
args[i] = reflect.ValueOf(a.export())
|
||
|
}
|
||
|
|
||
|
retvals := value.Call(args)
|
||
|
if len(retvals) > 1 {
|
||
|
panic(newTypeError())
|
||
|
} else if len(retvals) == 1 {
|
||
|
return toValue(retvals[0].Interface())
|
||
|
}
|
||
|
return UndefinedValue()
|
||
|
}))
|
||
|
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_run(src interface{}) (Value, error) {
|
||
|
result := UndefinedValue()
|
||
|
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)
|
||
|
})
|
||
|
switch result._valueType {
|
||
|
case valueEmpty:
|
||
|
result = UndefinedValue()
|
||
|
case valueReference:
|
||
|
result = self.GetValue(result)
|
||
|
}
|
||
|
return result, err
|
||
|
}
|
||
|
|
||
|
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(newReferenceError(err.Message))
|
||
|
}
|
||
|
panic(newSyntaxError(err.Message))
|
||
|
}
|
||
|
}
|
||
|
panic(newSyntaxError(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
|
||
|
}
|