2015-02-16 13:28:33 +00:00
package otto
import (
"errors"
2016-02-11 14:16:52 +00:00
"fmt"
"math"
2015-02-16 13:28:33 +00:00
"reflect"
2015-03-20 12:22:01 +00:00
"sync"
2015-02-16 13:28:33 +00:00
"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 {
2015-03-20 12:22:01 +00:00
global _global
globalObject * _object
globalStash * _objectStash
scope * _scope
otto * Otto
eval * _object // The builtin eval, for determine indirect versus direct invocation
2016-02-11 14:16:52 +00:00
debugger func ( * Otto )
2016-02-19 11:06:58 +00:00
random func ( ) float64
2015-02-16 13:28:33 +00:00
labels [ ] string // FIXME
2015-03-20 12:22:01 +00:00
lck sync . Mutex
2015-02-16 13:28:33 +00:00
}
2015-03-20 12:22:01 +00:00
func ( self * _runtime ) enterScope ( scope * _scope ) {
scope . outer = self . scope
self . scope = scope
2015-02-16 13:28:33 +00:00
}
2015-03-20 12:22:01 +00:00
func ( self * _runtime ) leaveScope ( ) {
self . scope = self . scope . outer
2015-02-16 13:28:33 +00:00
}
2015-03-20 12:22:01 +00:00
// FIXME This is used in two places (cloning)
func ( self * _runtime ) enterGlobalScope ( ) {
self . enterScope ( newScope ( self . globalStash , self . globalStash , self . globalObject ) )
2015-02-16 13:28:33 +00:00
}
2015-03-20 12:22:01 +00:00
func ( self * _runtime ) enterFunctionScope ( outer _stash , this Value ) * _fnStash {
if outer == nil {
outer = self . globalStash
2015-02-16 13:28:33 +00:00
}
2015-03-20 12:22:01 +00:00
stash := self . newFunctionStash ( outer )
2015-02-16 13:28:33 +00:00
var thisObject * _object
2015-03-20 12:22:01 +00:00
switch this . kind {
2015-02-16 13:28:33 +00:00
case valueUndefined , valueNull :
2015-03-20 12:22:01 +00:00
thisObject = self . globalObject
2015-02-16 13:28:33 +00:00
default :
thisObject = self . toObject ( this )
}
2015-03-20 12:22:01 +00:00
self . enterScope ( newScope ( stash , stash , thisObject ) )
return stash
2015-02-16 13:28:33 +00:00
}
2015-03-20 12:22:01 +00:00
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 )
2015-02-16 13:28:33 +00:00
}
}
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
2015-03-20 12:22:01 +00:00
tryValue = toValue_object ( self . newError ( caught . name , caught . messageValue ( ) ) )
2015-02-16 13:28:33 +00:00
case Value :
exception = true
tryValue = caught
default :
panic ( caught )
}
}
} ( )
tryValue = inner ( )
return
}
// toObject
func ( self * _runtime ) toObject ( value Value ) * _object {
2015-03-20 12:22:01 +00:00
switch value . kind {
2015-02-16 13:28:33 +00:00
case valueEmpty , valueUndefined , valueNull :
2015-03-20 12:22:01 +00:00
panic ( self . panicTypeError ( ) )
2015-02-16 13:28:33 +00:00
case valueBoolean :
return self . newBoolean ( value )
case valueString :
return self . newString ( value )
case valueNumber :
return self . newNumber ( value )
case valueObject :
return value . _object ( )
}
2015-03-20 12:22:01 +00:00
panic ( self . panicTypeError ( ) )
2015-02-16 13:28:33 +00:00
}
func ( self * _runtime ) objectCoerce ( value Value ) ( * _object , error ) {
2015-03-20 12:22:01 +00:00
switch value . kind {
2015-02-16 13:28:33 +00:00
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
}
2015-03-20 12:22:01 +00:00
panic ( self . panicTypeError ( ) )
2015-02-16 13:28:33 +00:00
}
2015-03-20 12:22:01 +00:00
func checkObjectCoercible ( rt * _runtime , value Value ) {
2015-02-16 13:28:33 +00:00
isObject , mustCoerce := testObjectCoercible ( value )
if ! isObject && ! mustCoerce {
2015-03-20 12:22:01 +00:00
panic ( rt . panicTypeError ( ) )
2015-02-16 13:28:33 +00:00
}
}
// testObjectCoercible
func testObjectCoercible ( value Value ) ( isObject bool , mustCoerce bool ) {
2015-03-20 12:22:01 +00:00
switch value . kind {
2015-02-16 13:28:33 +00:00
case valueReference , valueEmpty , valueNull , valueUndefined :
return false , false
case valueNumber , valueString , valueBoolean :
isObject = false
mustCoerce = true
case valueObject :
isObject = true
mustCoerce = false
}
return
}
2015-03-20 12:22:01 +00:00
func ( self * _runtime ) safeToValue ( value interface { } ) ( Value , error ) {
result := Value { }
2015-02-16 13:28:33 +00:00
err := catchPanic ( func ( ) {
result = self . toValue ( value )
} )
return result , err
}
2016-02-11 14:16:52 +00:00
// 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
}
2015-02-16 13:28:33 +00:00
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.
2015-03-20 12:22:01 +00:00
// FIXME We should really figure out what can come here.
// This catch-all is ugly.
2015-02-16 13:28:33 +00:00
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 :
2015-03-20 12:22:01 +00:00
// TODO Maybe cache this?
2015-02-16 13:28:33 +00:00
return toValue_object ( self . newNativeFunction ( "" , func ( call FunctionCall ) Value {
2016-02-11 14:16:52 +00:00
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 ( )
2015-03-20 12:22:01 +00:00
for i , value := range call . ArgumentList {
2016-02-11 14:16:52 +00:00
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 )
2015-02-16 13:28:33 +00:00
}
2016-02-11 14:16:52 +00:00
var out [ ] reflect . Value
if callSlice {
out = value . CallSlice ( in )
} else {
out = value . Call ( in )
}
l := len ( out )
switch l {
case 0 :
2015-03-20 12:22:01 +00:00
return Value { }
2016-02-11 14:16:52 +00:00
case 1 :
return self . toValue ( out [ 0 ] . Interface ( ) )
2015-02-16 13:28:33 +00:00
}
2015-03-20 12:22:01 +00:00
2016-02-11 14:16:52 +00:00
// 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 )
2015-02-16 13:28:33 +00:00
} ) )
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 )
2015-03-20 12:22:01 +00:00
self . prototype = runtime . global . ArrayPrototype
2015-02-16 13:28:33 +00:00
return self
}
func ( runtime * _runtime ) newGoArray ( value reflect . Value ) * _object {
self := runtime . newGoArrayObject ( value )
2015-03-20 12:22:01 +00:00
self . prototype = runtime . global . ArrayPrototype
2015-02-16 13:28:33 +00:00
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
}
2016-02-11 14:16:52 +00:00
func ( self * _runtime ) cmpl_runOrEval ( src interface { } , eval bool ) ( Value , error ) {
2015-03-20 12:22:01 +00:00
result := Value { }
2015-02-16 13:28:33 +00:00
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 ( ) {
2016-02-11 14:16:52 +00:00
result = self . cmpl_evaluate_nodeProgram ( cmpl_program , eval )
2015-02-16 13:28:33 +00:00
} )
2015-03-20 12:22:01 +00:00
switch result . kind {
2015-02-16 13:28:33 +00:00
case valueEmpty :
2015-03-20 12:22:01 +00:00
result = Value { }
2015-02-16 13:28:33 +00:00
case valueReference :
2015-03-20 12:22:01 +00:00
result = result . resolve ( )
2015-02-16 13:28:33 +00:00
}
return result , err
}
2016-02-11 14:16:52 +00:00
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 )
}
2015-02-16 13:28:33 +00:00
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" {
2015-03-20 12:22:01 +00:00
panic ( self . panicReferenceError ( err . Message ) )
2015-02-16 13:28:33 +00:00
}
2015-03-20 12:22:01 +00:00
panic ( self . panicSyntaxError ( err . Message ) )
2015-02-16 13:28:33 +00:00
}
}
2015-03-20 12:22:01 +00:00
panic ( self . panicSyntaxError ( err . Error ( ) ) )
2015-02-16 13:28:33 +00:00
}
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
}