1
0
mirror of https://github.com/robertkrimen/otto synced 2025-10-12 20:27:30 +08:00
otto/runtime.go
2013-03-05 09:08:46 -08:00

363 lines
10 KiB
Go

package otto
import (
"reflect"
"strconv"
)
type _runtime struct {
Stack [](*_executionContext)
GlobalObject *_object
GlobalEnvironment *_objectEnvironment
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
// JSON
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
}
_newError map[string]func(Value) *_object
eval *_object // The builtin eval, for determine indirect versus direct invocation
}
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._Function.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, self.GlobalObject)
// 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) Return(value Value) {
panic(newReturnResult(value))
}
func (self *_runtime) Continue(target string) {
panic(newContinueResult(target))
}
func (self *_runtime) Break(target string) {
panic(newBreakResult(target))
}
func (self *_runtime) Throw(value Value) {
panic(newThrowResult(value))
}
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.set(reference.GetName(), value, strict)
}
}
func (self *_runtime) _callNode(function *_object, environment *_functionEnvironment, node *_functionNode, this Value, argumentList []Value) Value {
indexOfParameterName := make([]string, len(node.ParameterList))
// function(abc, def, ghi)
// indexOfParameterName[0] = "abc"
// indexOfParameterName[1] = "def"
// indexOfParameterName[2] = "ghi"
// ...
for index, name := range node.ParameterList {
value := UndefinedValue()
if index < len(argumentList) {
value = argumentList[index]
}
indexOfParameterName[index] = name
self.localSet(name, value)
}
if !node.ArgumentsIsParameter {
arguments := self.newArgumentsObject(indexOfParameterName, environment, len(argumentList))
arguments.stash.set("callee", toValue(function), 0101)
environment.arguments = arguments
self.localSet("arguments", toValue(arguments))
for index, _ := range argumentList {
if index < len(node.ParameterList) {
continue
}
indexAsString := strconv.FormatInt(int64(index), 10)
arguments.set(indexAsString, argumentList[index], false)
}
}
self.declare("function", node.FunctionList)
self.declare("variable", node.VariableList)
self.evaluateBody(node.Body)
return UndefinedValue()
}
func (self *_runtime) Call(function *_object, this Value, argumentList []Value, evalHint bool) (returnValue Value) {
// Pass eval boolean through to EnterFunctionExecutionContext for further testing
_functionEnvironment := self.EnterFunctionExecutionContext(function, this)
defer func() {
// TODO Catch any errant break/continue, etc. here?
// They should never get here, but we want to be
// very vocal if they do.
self.LeaveExecutionContext()
if caught := recover(); caught != nil {
if result, ok := caught.(_result); ok {
if result.Kind == resultReturn {
returnValue = result.Value
return
}
}
panic(caught)
}
}()
if evalHint {
evalHint = function == self.eval // If evalHint is true, then it IS a direct eval
}
returnValue = function._Function.Call.Dispatch(function, _functionEnvironment, self, this, argumentList, evalHint)
return
}
func (self *_runtime) tryEvaluate(inner func() Value) (tryValue Value, throw bool, throwValue Value, other *_result) {
defer func() {
if caught := recover(); caught != nil {
switch caught := caught.(type) {
case _result:
switch caught.Kind {
case resultThrow:
throw = true
throwValue = caught.Value
return
case resultReturn, resultBreak, resultContinue:
fallthrough
default:
other = &caught
return
}
case _error:
throw = true
throwValue = toValue(self.newError(caught.Name, caught.MessageValue()))
return
case *_syntaxError:
throw = true
throwValue = toValue(self.newError("SyntaxError", toValue(caught.String())))
return
}
panic(caught)
}
}()
tryValue = inner()
return
}
func (self *_runtime) breakEvaluate(_labelSet map[string]bool, inner func() Value) Value {
defer func() {
if caught := recover(); caught != nil {
if result, ok := caught.(_result); ok {
if result.Kind == resultBreak && _labelSet[result.Target] == true {
return
}
}
panic(caught)
}
}()
return inner()
}
func (self *_runtime) continueEvaluate(node _node, _labelSet map[string]bool) (returnResult Value) {
defer func() {
if caught := recover(); caught != nil {
if result, ok := caught.(_result); ok {
if result.Kind == resultContinue && _labelSet[result.Target] == true {
returnResult = emptyValue()
return
}
}
panic(caught)
}
}()
return self.evaluate(node)
}
func (self *_runtime) declare(kind string, declarationList []_declaration) {
executionContext := self._executionContext(0)
eval := executionContext.eval
environment := executionContext.VariableEnvironment
for _, _declaration := range declarationList {
name := _declaration.Name
if kind == "function" {
value := self.evaluate(_declaration.Definition)
self.localSet(_declaration.Name, value)
} else {
if !environment.HasBinding(name) {
environment.CreateMutableBinding(name, eval == true)
environment.SetMutableBinding(name, UndefinedValue(), false) // TODO strict
}
}
}
}
// _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 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 func(FunctionCall) Value:
return toValue(self.newNativeFunction(value, 0, "nativeFunction"))
case _nativeFunction:
return toValue(self.newNativeFunction(value, 0, "nativeFunction"))
case Object, *Object, _object, *_object:
// Nothing happens.
default:
{
value := reflect.Indirect(reflect.ValueOf(value))
switch value.Kind() {
case reflect.Struct:
return toValue(self.newGoStructObject(value))
case reflect.Map:
return toValue(self.newGoMapObject(value))
case reflect.Slice, reflect.Array:
object := self.newGoArrayObject(value)
object.prototype = self.Global.ArrayPrototype
return toValue(object)
}
}
}
return toValue(value)
}