package otto // _constructFunction type _constructFunction func(*_object, []Value) Value // 13.2.2 [[Construct]] func defaultConstruct(fn *_object, argumentList []Value) Value { object := fn.runtime.newObject() object.class = "Object" prototype := fn.get("prototype") if prototype.kind != valueObject { prototype = toValue_object(fn.runtime.global.ObjectPrototype) } object.prototype = prototype._object() this := toValue_object(object) value := fn.call(this, argumentList, false, nativeFrame) if value.kind == valueObject { return value } return this } // _nativeFunction type _nativeFunction func(FunctionCall) Value // ===================== // // _nativeFunctionObject // // ===================== // type _nativeFunctionObject struct { name string file string line int call _nativeFunction // [[Call]] construct _constructFunction // [[Construct]] } func (runtime *_runtime) newNativeFunctionObject(name, file string, line int, native _nativeFunction, length int) *_object { self := runtime.newClassObject("Function") self.value = _nativeFunctionObject{ name: name, file: file, line: line, call: native, construct: defaultConstruct, } self.defineProperty("length", toValue_int(length), 0000, false) return self } // =================== // // _bindFunctionObject // // =================== // type _bindFunctionObject struct { target *_object this Value argumentList []Value } func (runtime *_runtime) newBoundFunctionObject(target *_object, this Value, argumentList []Value) *_object { self := runtime.newClassObject("Function") self.value = _bindFunctionObject{ target: target, this: this, argumentList: argumentList, } length := int(toInt32(target.get("length"))) length -= len(argumentList) if length < 0 { length = 0 } self.defineProperty("length", toValue_int(length), 0000, false) self.defineProperty("caller", Value{}, 0000, false) // TODO Should throw a TypeError self.defineProperty("arguments", Value{}, 0000, false) // TODO Should throw a TypeError return self } // [[Construct]] func (fn _bindFunctionObject) construct(argumentList []Value) Value { object := fn.target switch value := object.value.(type) { case _nativeFunctionObject: return value.construct(object, fn.argumentList) case _nodeFunctionObject: argumentList = append(fn.argumentList, argumentList...) return object.construct(argumentList) } panic(fn.target.runtime.panicTypeError()) } // =================== // // _nodeFunctionObject // // =================== // type _nodeFunctionObject struct { node *_nodeFunctionLiteral stash _stash } func (runtime *_runtime) newNodeFunctionObject(node *_nodeFunctionLiteral, stash _stash) *_object { self := runtime.newClassObject("Function") self.value = _nodeFunctionObject{ node: node, stash: stash, } self.defineProperty("length", toValue_int(len(node.parameterList)), 0000, false) return self } // ======= // // _object // // ======= // func (self *_object) isCall() bool { switch fn := self.value.(type) { case _nativeFunctionObject: return fn.call != nil case _bindFunctionObject: return true case _nodeFunctionObject: return true } return false } func (self *_object) call(this Value, argumentList []Value, eval bool, frame _frame) Value { switch fn := self.value.(type) { case _nativeFunctionObject: // Since eval is a native function, we only have to check for it here if eval { eval = self == self.runtime.eval // If eval is true, then it IS a direct eval } // Enter a scope, name from the native object... rt := self.runtime if rt.scope != nil && !eval { rt.enterFunctionScope(rt.scope.lexical, this) rt.scope.frame = _frame{ native: true, nativeFile: fn.file, nativeLine: fn.line, callee: fn.name, file: nil, } defer func() { rt.leaveScope() }() } return fn.call(FunctionCall{ runtime: self.runtime, eval: eval, This: this, ArgumentList: argumentList, Otto: self.runtime.otto, }) case _bindFunctionObject: // TODO Passthrough site, do not enter a scope argumentList = append(fn.argumentList, argumentList...) return fn.target.call(fn.this, argumentList, false, frame) case _nodeFunctionObject: rt := self.runtime stash := rt.enterFunctionScope(fn.stash, this) rt.scope.frame = _frame{ callee: fn.node.name, file: fn.node.file, } defer func() { rt.leaveScope() }() callValue := rt.cmpl_call_nodeFunction(self, stash, fn.node, this, argumentList) if value, valid := callValue.value.(_result); valid { return value.value } return callValue } panic(self.runtime.panicTypeError("%v is not a function", toValue_object(self))) } func (self *_object) construct(argumentList []Value) Value { switch fn := self.value.(type) { case _nativeFunctionObject: if fn.call == nil { panic(self.runtime.panicTypeError("%v is not a function", toValue_object(self))) } if fn.construct == nil { panic(self.runtime.panicTypeError("%v is not a constructor", toValue_object(self))) } return fn.construct(self, argumentList) case _bindFunctionObject: return fn.construct(argumentList) case _nodeFunctionObject: return defaultConstruct(self, argumentList) } panic(self.runtime.panicTypeError("%v is not a function", toValue_object(self))) } // 15.3.5.3 func (self *_object) hasInstance(of Value) bool { if !self.isCall() { // We should not have a hasInstance method panic(self.runtime.panicTypeError()) } if !of.IsObject() { return false } prototype := self.get("prototype") if !prototype.IsObject() { panic(self.runtime.panicTypeError()) } prototypeObject := prototype._object() value := of._object().prototype for value != nil { if value == prototypeObject { return true } value = value.prototype } return false } // ============ // // FunctionCall // // ============ // // FunctionCall is an encapsulation of a JavaScript function call. type FunctionCall struct { runtime *_runtime _thisObject *_object eval bool // This call is a direct call to eval This Value ArgumentList []Value Otto *Otto } // Argument will return the value of the argument at the given index. // // If no such argument exists, undefined is returned. func (self FunctionCall) Argument(index int) Value { return valueOfArrayIndex(self.ArgumentList, index) } func (self FunctionCall) getArgument(index int) (Value, bool) { return getValueOfArrayIndex(self.ArgumentList, index) } func (self FunctionCall) slice(index int) []Value { if index < len(self.ArgumentList) { return self.ArgumentList[index:] } return []Value{} } func (self *FunctionCall) thisObject() *_object { if self._thisObject == nil { this := self.This.resolve() // FIXME Is this right? self._thisObject = self.runtime.toObject(this) } return self._thisObject } func (self *FunctionCall) thisClassObject(class string) *_object { thisObject := self.thisObject() if thisObject.class != class { panic(self.runtime.panicTypeError()) } return self._thisObject } func (self FunctionCall) toObject(value Value) *_object { return self.runtime.toObject(value) } // CallerLocation will return file location information (file:line:pos) where this function is being called. func (self FunctionCall) CallerLocation() string { // see error.go for location() return self.runtime.scope.outer.frame.location() }