1
0
mirror of https://github.com/robertkrimen/otto synced 2025-10-19 19:55:30 +08:00
otto/runtime.go
Steven Hartland a30a6a7b53 Improve method call parameter processing
Add support for the final parameter to a variadic function to be a slice.

Add support for conversion of slice parameters.

Expand numeric conversion support to include all int and uint types as souce types. This allows the result of bitwise calculations (int32) to be passed in as parameters.
2015-02-19 11:45:35 +00:00

518 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
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_run(src interface{}) (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, false)
})
switch result.kind {
case valueEmpty:
result = Value{}
case valueReference:
result = result.resolve()
}
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(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
}