2024-02-24 21:00:04 +00:00
package otto
import (
"encoding"
"encoding/json"
"errors"
"fmt"
"math"
"path"
"reflect"
2024-04-04 14:46:14 +00:00
goruntime "runtime"
2024-02-24 21:00:04 +00:00
"strconv"
"strings"
"sync"
"github.com/robertkrimen/otto/ast"
"github.com/robertkrimen/otto/parser"
)
2024-04-04 14:46:14 +00:00
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
2024-02-24 21:00:04 +00:00
}
2024-04-04 14:46:14 +00:00
type runtime struct {
global global
globalObject * object
globalStash * objectStash
scope * scope
2024-02-24 21:00:04 +00:00
otto * Otto
2024-04-04 14:46:14 +00:00
eval * object // The builtin eval, for determine indirect versus direct invocation
2024-02-24 21:00:04 +00:00
debugger func ( * Otto )
random func ( ) float64
stackLimit int
traceLimit int
labels [ ] string // FIXME
lck sync . Mutex
}
2024-04-04 14:46:14 +00:00
func ( rt * runtime ) enterScope ( scop * scope ) {
scop . outer = rt . scope
if rt . scope != nil {
if rt . stackLimit != 0 && rt . scope . depth + 1 >= rt . stackLimit {
panic ( rt . panicRangeError ( "Maximum call stack size exceeded" ) )
2024-02-24 21:00:04 +00:00
}
2024-04-04 14:46:14 +00:00
scop . depth = rt . scope . depth + 1
2024-02-24 21:00:04 +00:00
}
2024-04-04 14:46:14 +00:00
rt . scope = scop
2024-02-24 21:00:04 +00:00
}
2024-04-04 14:46:14 +00:00
func ( rt * runtime ) leaveScope ( ) {
rt . scope = rt . scope . outer
2024-02-24 21:00:04 +00:00
}
2024-04-04 14:46:14 +00:00
// FIXME This is used in two places (cloning).
func ( rt * runtime ) enterGlobalScope ( ) {
rt . enterScope ( newScope ( rt . globalStash , rt . globalStash , rt . globalObject ) )
2024-02-24 21:00:04 +00:00
}
2024-04-04 14:46:14 +00:00
func ( rt * runtime ) enterFunctionScope ( outer stasher , this Value ) * fnStash {
2024-02-24 21:00:04 +00:00
if outer == nil {
2024-04-04 14:46:14 +00:00
outer = rt . globalStash
2024-02-24 21:00:04 +00:00
}
2024-04-04 14:46:14 +00:00
stash := rt . newFunctionStash ( outer )
var thisObject * object
2024-02-24 21:00:04 +00:00
switch this . kind {
case valueUndefined , valueNull :
2024-04-04 14:46:14 +00:00
thisObject = rt . globalObject
2024-02-24 21:00:04 +00:00
default :
2024-04-04 14:46:14 +00:00
thisObject = rt . toObject ( this )
2024-02-24 21:00:04 +00:00
}
2024-04-04 14:46:14 +00:00
rt . enterScope ( newScope ( stash , stash , thisObject ) )
2024-02-24 21:00:04 +00:00
return stash
}
2024-04-04 14:46:14 +00:00
func ( rt * runtime ) putValue ( reference referencer , value Value ) {
2024-02-24 21:00:04 +00:00
name := reference . putValue ( value )
if name != "" {
// Why? -- If reference.base == nil
// strict = false
2024-04-04 14:46:14 +00:00
rt . globalObject . defineProperty ( name , value , 0 o111 , false )
2024-02-24 21:00:04 +00:00
}
}
2024-04-04 14:46:14 +00:00
func ( rt * runtime ) tryCatchEvaluate ( inner func ( ) Value ) ( tryValue Value , isException bool ) { //nolint: nonamedreturns
2024-02-24 21:00:04 +00:00
// 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 {
2024-04-04 14:46:14 +00:00
if excep , ok := caught . ( * exception ) ; ok {
caught = excep . eject ( )
2024-02-24 21:00:04 +00:00
}
switch caught := caught . ( type ) {
2024-04-04 14:46:14 +00:00
case ottoError :
isException = true
tryValue = objectValue ( rt . newErrorObjectError ( caught ) )
2024-02-24 21:00:04 +00:00
case Value :
2024-04-04 14:46:14 +00:00
isException = true
2024-02-24 21:00:04 +00:00
tryValue = caught
default :
2024-04-04 14:46:14 +00:00
isException = true
2024-02-24 21:00:04 +00:00
tryValue = toValue ( caught )
}
}
} ( )
return inner ( ) , false
}
2024-04-04 14:46:14 +00:00
func ( rt * runtime ) toObject ( value Value ) * object {
2024-02-24 21:00:04 +00:00
switch value . kind {
case valueEmpty , valueUndefined , valueNull :
2024-04-04 14:46:14 +00:00
panic ( rt . panicTypeError ( "toObject unsupported kind %s" , value . kind ) )
2024-02-24 21:00:04 +00:00
case valueBoolean :
2024-04-04 14:46:14 +00:00
return rt . newBoolean ( value )
2024-02-24 21:00:04 +00:00
case valueString :
2024-04-04 14:46:14 +00:00
return rt . newString ( value )
2024-02-24 21:00:04 +00:00
case valueNumber :
2024-04-04 14:46:14 +00:00
return rt . newNumber ( value )
2024-02-24 21:00:04 +00:00
case valueObject :
2024-04-04 14:46:14 +00:00
return value . object ( )
default :
panic ( rt . panicTypeError ( "toObject unknown kind %s" , value . kind ) )
2024-02-24 21:00:04 +00:00
}
}
2024-04-04 14:46:14 +00:00
func ( rt * runtime ) objectCoerce ( value Value ) ( * object , error ) {
2024-02-24 21:00:04 +00:00
switch value . kind {
case valueUndefined :
return nil , errors . New ( "undefined" )
case valueNull :
return nil , errors . New ( "null" )
case valueBoolean :
2024-04-04 14:46:14 +00:00
return rt . newBoolean ( value ) , nil
2024-02-24 21:00:04 +00:00
case valueString :
2024-04-04 14:46:14 +00:00
return rt . newString ( value ) , nil
2024-02-24 21:00:04 +00:00
case valueNumber :
2024-04-04 14:46:14 +00:00
return rt . newNumber ( value ) , nil
2024-02-24 21:00:04 +00:00
case valueObject :
2024-04-04 14:46:14 +00:00
return value . object ( ) , nil
default :
panic ( rt . panicTypeError ( "objectCoerce unknown kind %s" , value . kind ) )
2024-02-24 21:00:04 +00:00
}
}
2024-04-04 14:46:14 +00:00
func checkObjectCoercible ( rt * runtime , value Value ) {
2024-02-24 21:00:04 +00:00
isObject , mustCoerce := testObjectCoercible ( value )
if ! isObject && ! mustCoerce {
2024-04-04 14:46:14 +00:00
panic ( rt . panicTypeError ( "checkObjectCoercible not object or mustCoerce" ) )
2024-02-24 21:00:04 +00:00
}
}
2024-04-04 14:46:14 +00:00
// testObjectCoercible.
func testObjectCoercible ( value Value ) ( isObject , mustCoerce bool ) { //nolint: nonamedreturns
2024-02-24 21:00:04 +00:00
switch value . kind {
case valueReference , valueEmpty , valueNull , valueUndefined :
return false , false
case valueNumber , valueString , valueBoolean :
return false , true
case valueObject :
return true , false
default :
2024-04-04 14:46:14 +00:00
panic ( fmt . Sprintf ( "testObjectCoercible unknown kind %s" , value . kind ) )
2024-02-24 21:00:04 +00:00
}
}
2024-04-04 14:46:14 +00:00
func ( rt * runtime ) safeToValue ( value interface { } ) ( Value , error ) {
2024-02-24 21:00:04 +00:00
result := Value { }
err := catchPanic ( func ( ) {
2024-04-04 14:46:14 +00:00
result = rt . toValue ( value )
2024-02-24 21:00:04 +00:00
} )
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.
2024-04-04 14:46:14 +00:00
func ( rt * runtime ) convertNumeric ( v Value , t reflect . Type ) reflect . Value {
2024-02-24 21:00:04 +00:00
val := reflect . ValueOf ( v . export ( ) )
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 ) {
2024-04-04 14:46:14 +00:00
panic ( rt . panicRangeError ( "converting float64 to float32 would overflow" ) )
2024-02-24 21:00:04 +00:00
}
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 {
2024-04-04 14:46:14 +00:00
panic ( rt . panicRangeError ( fmt . Sprintf ( "converting %v to %v would cause loss of precision" , val . Type ( ) , t ) ) )
2024-02-24 21:00:04 +00:00
}
// The float represents an integer
val = reflect . ValueOf ( i64 )
default :
2024-04-04 14:46:14 +00:00
panic ( rt . panicTypeError ( fmt . Sprintf ( "cannot convert %v to %v" , val . Type ( ) , t ) ) )
2024-02-24 21:00:04 +00:00
}
}
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 ) {
2024-04-04 14:46:14 +00:00
panic ( rt . panicRangeError ( fmt . Sprintf ( "converting %v to %v would overflow" , val . Type ( ) , t ) ) )
2024-02-24 21:00:04 +00:00
}
return val . Convert ( t )
case reflect . Uint , reflect . Uint8 , reflect . Uint16 , reflect . Uint32 , reflect . Uint64 :
if i64 < 0 {
2024-04-04 14:46:14 +00:00
panic ( rt . panicRangeError ( fmt . Sprintf ( "converting %v to %v would underflow" , val . Type ( ) , t ) ) )
2024-02-24 21:00:04 +00:00
}
if reflect . Zero ( t ) . OverflowUint ( uint64 ( i64 ) ) {
2024-04-04 14:46:14 +00:00
panic ( rt . panicRangeError ( fmt . Sprintf ( "converting %v to %v would overflow" , val . Type ( ) , t ) ) )
2024-02-24 21:00:04 +00:00
}
return val . Convert ( t )
case reflect . Float32 , reflect . Float64 :
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 ) ) {
2024-04-04 14:46:14 +00:00
panic ( rt . panicRangeError ( fmt . Sprintf ( "converting %v to %v would overflow" , val . Type ( ) , t ) ) )
2024-02-24 21:00:04 +00:00
}
return val . Convert ( t )
case reflect . Uint , reflect . Uint8 , reflect . Uint16 , reflect . Uint32 , reflect . Uint64 :
if reflect . Zero ( t ) . OverflowUint ( u64 ) {
2024-04-04 14:46:14 +00:00
panic ( rt . panicRangeError ( fmt . Sprintf ( "converting %v to %v would overflow" , val . Type ( ) , t ) ) )
2024-02-24 21:00:04 +00:00
}
return val . Convert ( t )
case reflect . Float32 , reflect . Float64 :
return val . Convert ( t )
}
}
2024-04-04 14:46:14 +00:00
panic ( rt . panicTypeError ( fmt . Sprintf ( "unsupported type %v -> %v for numeric conversion" , val . Type ( ) , t ) ) )
2024-02-24 21:00:04 +00:00
}
func fieldIndexByName ( t reflect . Type , name string ) [ ] int {
for t . Kind ( ) == reflect . Ptr {
t = t . Elem ( )
}
for i := 0 ; i < t . NumField ( ) ; i ++ {
f := t . Field ( i )
if ! validGoStructName ( f . Name ) {
continue
}
if f . Anonymous {
for t . Kind ( ) == reflect . Ptr {
t = t . Elem ( )
}
if f . Type . Kind ( ) == reflect . Struct {
if a := fieldIndexByName ( f . Type , name ) ; a != nil {
return append ( [ ] int { i } , a ... )
}
}
}
if a := strings . SplitN ( f . Tag . Get ( "json" ) , "," , 2 ) ; a [ 0 ] != "" {
if a [ 0 ] == "-" {
continue
}
if a [ 0 ] == name {
return [ ] int { i }
}
}
if f . Name == name {
return [ ] int { i }
}
}
return nil
}
2024-04-04 14:46:14 +00:00
var (
typeOfValue = reflect . TypeOf ( Value { } )
typeOfJSONRawMessage = reflect . TypeOf ( json . RawMessage { } )
)
2024-02-24 21:00:04 +00:00
// convertCallParameter 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.
2024-04-04 14:46:14 +00:00
func ( rt * runtime ) convertCallParameter ( v Value , t reflect . Type ) ( reflect . Value , error ) {
2024-02-24 21:00:04 +00:00
if t == typeOfValue {
return reflect . ValueOf ( v ) , nil
}
if t == typeOfJSONRawMessage {
if d , err := json . Marshal ( v . export ( ) ) ; err == nil {
return reflect . ValueOf ( d ) , nil
}
}
if v . kind == valueObject {
2024-04-04 14:46:14 +00:00
if gso , ok := v . object ( ) . value . ( * goStructObject ) ; ok {
2024-02-24 21:00:04 +00:00
if gso . value . Type ( ) . AssignableTo ( t ) {
// please see TestDynamicFunctionReturningInterface for why this exists
if t . Kind ( ) == reflect . Interface && gso . value . Type ( ) . ConvertibleTo ( t ) {
return gso . value . Convert ( t ) , nil
}
2024-04-04 14:46:14 +00:00
return gso . value , nil
2024-02-24 21:00:04 +00:00
}
}
2024-04-04 14:46:14 +00:00
if gao , ok := v . object ( ) . value . ( * goArrayObject ) ; ok {
2024-02-24 21:00:04 +00:00
if gao . value . Type ( ) . AssignableTo ( t ) {
// please see TestDynamicFunctionReturningInterface for why this exists
if t . Kind ( ) == reflect . Interface && gao . value . Type ( ) . ConvertibleTo ( t ) {
return gao . value . Convert ( t ) , nil
}
2024-04-04 14:46:14 +00:00
return gao . value , nil
2024-02-24 21:00:04 +00:00
}
}
}
tk := t . Kind ( )
if tk == reflect . Interface {
e := v . export ( )
if e == nil {
return reflect . Zero ( t ) , nil
}
iv := reflect . ValueOf ( e )
if iv . Type ( ) . AssignableTo ( t ) {
return iv , nil
}
}
if tk == reflect . Ptr {
switch v . kind {
case valueEmpty , valueNull , valueUndefined :
return reflect . Zero ( t ) , nil
default :
var vv reflect . Value
2024-04-04 14:46:14 +00:00
vv , err := rt . convertCallParameter ( v , t . Elem ( ) )
2024-02-24 21:00:04 +00:00
if err != nil {
2024-04-04 14:46:14 +00:00
return reflect . Zero ( t ) , fmt . Errorf ( "can't convert to %s: %w" , t , err )
2024-02-24 21:00:04 +00:00
}
if vv . CanAddr ( ) {
return vv . Addr ( ) , nil
}
pv := reflect . New ( vv . Type ( ) )
pv . Elem ( ) . Set ( vv )
return pv , nil
}
}
switch tk {
case reflect . Bool :
return reflect . ValueOf ( v . bool ( ) ) , nil
case reflect . String :
switch v . kind {
case valueString :
return reflect . ValueOf ( v . value ) , nil
case valueNumber :
return reflect . ValueOf ( fmt . Sprintf ( "%v" , v . value ) ) , nil
}
case reflect . Int , reflect . Int8 , reflect . Int16 , reflect . Int32 , reflect . Int64 , reflect . Uint , reflect . Uint8 , reflect . Uint16 , reflect . Uint32 , reflect . Uint64 , reflect . Float32 , reflect . Float64 :
2024-04-04 14:46:14 +00:00
if v . kind == valueNumber {
return rt . convertNumeric ( v , t ) , nil
2024-02-24 21:00:04 +00:00
}
case reflect . Slice :
2024-04-04 14:46:14 +00:00
if o := v . object ( ) ; o != nil {
2024-02-24 21:00:04 +00:00
if lv := o . get ( propertyLength ) ; lv . IsNumber ( ) {
l := lv . number ( ) . int64
s := reflect . MakeSlice ( t , int ( l ) , int ( l ) )
tt := t . Elem ( )
switch o . class {
2024-04-04 14:46:14 +00:00
case classArrayName :
2024-02-24 21:00:04 +00:00
for i := int64 ( 0 ) ; i < l ; i ++ {
p , ok := o . property [ strconv . FormatInt ( i , 10 ) ]
if ! ok {
continue
}
e , ok := p . value . ( Value )
if ! ok {
continue
}
2024-04-04 14:46:14 +00:00
ev , err := rt . convertCallParameter ( e , tt )
2024-02-24 21:00:04 +00:00
if err != nil {
2024-04-04 14:46:14 +00:00
return reflect . Zero ( t ) , fmt . Errorf ( "couldn't convert element %d of %s: %w" , i , t , err )
2024-02-24 21:00:04 +00:00
}
s . Index ( int ( i ) ) . Set ( ev )
}
2024-04-04 14:46:14 +00:00
case classGoArrayName , classGoSliceName :
2024-02-24 21:00:04 +00:00
var gslice bool
switch o . value . ( type ) {
2024-04-04 14:46:14 +00:00
case * goSliceObject :
2024-02-24 21:00:04 +00:00
gslice = true
2024-04-04 14:46:14 +00:00
case * goArrayObject :
2024-02-24 21:00:04 +00:00
gslice = false
}
for i := int64 ( 0 ) ; i < l ; i ++ {
2024-04-04 14:46:14 +00:00
var p * property
2024-02-24 21:00:04 +00:00
if gslice {
p = goSliceGetOwnProperty ( o , strconv . FormatInt ( i , 10 ) )
} else {
p = goArrayGetOwnProperty ( o , strconv . FormatInt ( i , 10 ) )
}
if p == nil {
continue
}
e , ok := p . value . ( Value )
if ! ok {
continue
}
2024-04-04 14:46:14 +00:00
ev , err := rt . convertCallParameter ( e , tt )
2024-02-24 21:00:04 +00:00
if err != nil {
2024-04-04 14:46:14 +00:00
return reflect . Zero ( t ) , fmt . Errorf ( "couldn't convert element %d of %s: %w" , i , t , err )
2024-02-24 21:00:04 +00:00
}
s . Index ( int ( i ) ) . Set ( ev )
}
}
return s , nil
}
}
case reflect . Map :
2024-04-04 14:46:14 +00:00
if o := v . object ( ) ; o != nil && t . Key ( ) . Kind ( ) == reflect . String {
2024-02-24 21:00:04 +00:00
m := reflect . MakeMap ( t )
var err error
o . enumerate ( false , func ( k string ) bool {
2024-04-04 14:46:14 +00:00
v , verr := rt . convertCallParameter ( o . get ( k ) , t . Elem ( ) )
2024-02-24 21:00:04 +00:00
if verr != nil {
2024-04-04 14:46:14 +00:00
err = fmt . Errorf ( "couldn't convert property %q of %s: %w" , k , t , verr )
2024-02-24 21:00:04 +00:00
return false
}
m . SetMapIndex ( reflect . ValueOf ( k ) , v )
return true
} )
if err != nil {
return reflect . Zero ( t ) , err
}
return m , nil
}
case reflect . Func :
if t . NumOut ( ) > 1 {
return reflect . Zero ( t ) , fmt . Errorf ( "converting JavaScript values to Go functions with more than one return value is currently not supported" )
}
2024-04-04 14:46:14 +00:00
if o := v . object ( ) ; o != nil && o . class == classFunctionName {
2024-02-24 21:00:04 +00:00
return reflect . MakeFunc ( t , func ( args [ ] reflect . Value ) [ ] reflect . Value {
l := make ( [ ] interface { } , len ( args ) )
for i , a := range args {
if a . CanInterface ( ) {
l [ i ] = a . Interface ( )
}
}
rv , err := v . Call ( nullValue , l ... )
if err != nil {
panic ( err )
}
if t . NumOut ( ) == 0 {
return nil
}
2024-04-04 14:46:14 +00:00
r , err := rt . convertCallParameter ( rv , t . Out ( 0 ) )
2024-02-24 21:00:04 +00:00
if err != nil {
2024-04-04 14:46:14 +00:00
panic ( rt . panicTypeError ( "convertCallParameter Func: %s" , err ) )
2024-02-24 21:00:04 +00:00
}
return [ ] reflect . Value { r }
} ) , nil
}
case reflect . Struct :
2024-04-04 14:46:14 +00:00
if o := v . object ( ) ; o != nil && o . class == classObjectName {
2024-02-24 21:00:04 +00:00
s := reflect . New ( t )
for _ , k := range o . propertyOrder {
idx := fieldIndexByName ( t , k )
if idx == nil {
return reflect . Zero ( t ) , fmt . Errorf ( "can't convert property %q of %s: field does not exist" , k , t )
}
ss := s
for _ , i := range idx {
if ss . Kind ( ) == reflect . Ptr {
if ss . IsNil ( ) {
if ! ss . CanSet ( ) {
return reflect . Zero ( t ) , fmt . Errorf ( "can't convert property %q of %s: %s is unexported" , k , t , ss . Type ( ) . Elem ( ) )
}
ss . Set ( reflect . New ( ss . Type ( ) . Elem ( ) ) )
}
ss = ss . Elem ( )
}
ss = ss . Field ( i )
}
2024-04-04 14:46:14 +00:00
v , err := rt . convertCallParameter ( o . get ( k ) , ss . Type ( ) )
2024-02-24 21:00:04 +00:00
if err != nil {
2024-04-04 14:46:14 +00:00
return reflect . Zero ( t ) , fmt . Errorf ( "couldn't convert property %q of %s: %w" , k , t , err )
2024-02-24 21:00:04 +00:00
}
ss . Set ( v )
}
return s . Elem ( ) , nil
}
}
if tk == reflect . String {
2024-04-04 14:46:14 +00:00
if o := v . object ( ) ; o != nil && o . hasProperty ( "toString" ) {
2024-02-24 21:00:04 +00:00
if fn := o . get ( "toString" ) ; fn . IsFunction ( ) {
sv , err := fn . Call ( v )
if err != nil {
2024-04-04 14:46:14 +00:00
return reflect . Zero ( t ) , fmt . Errorf ( "couldn't call toString: %w" , err )
2024-02-24 21:00:04 +00:00
}
2024-04-04 14:46:14 +00:00
r , err := rt . convertCallParameter ( sv , t )
2024-02-24 21:00:04 +00:00
if err != nil {
2024-04-04 14:46:14 +00:00
return reflect . Zero ( t ) , fmt . Errorf ( "couldn't convert toString result: %w" , err )
2024-02-24 21:00:04 +00:00
}
return r , nil
}
}
return reflect . ValueOf ( v . String ( ) ) , nil
}
if v . kind == valueString {
var s encoding . TextUnmarshaler
if reflect . PtrTo ( t ) . Implements ( reflect . TypeOf ( & s ) . Elem ( ) ) {
r := reflect . New ( t )
if err := r . Interface ( ) . ( encoding . TextUnmarshaler ) . UnmarshalText ( [ ] byte ( v . string ( ) ) ) ; err != nil {
2024-04-04 14:46:14 +00:00
return reflect . Zero ( t ) , fmt . Errorf ( "can't convert to %s as TextUnmarshaller: %w" , t . String ( ) , err )
2024-02-24 21:00:04 +00:00
}
return r . Elem ( ) , nil
}
}
s := "OTTO DOES NOT UNDERSTAND THIS TYPE"
switch v . kind {
case valueBoolean :
s = "boolean"
case valueNull :
s = "null"
case valueNumber :
s = "number"
case valueString :
s = "string"
case valueUndefined :
s = "undefined"
case valueObject :
s = v . Class ( )
}
return reflect . Zero ( t ) , fmt . Errorf ( "can't convert from %q to %q" , s , t )
}
2024-04-04 14:46:14 +00:00
func ( rt * runtime ) toValue ( value interface { } ) Value {
2024-02-24 21:00:04 +00:00
rv , ok := value . ( reflect . Value )
if ok {
value = rv . Interface ( )
}
switch value := value . ( type ) {
case Value :
return value
case func ( FunctionCall ) Value :
var name , file string
var line int
pc := reflect . ValueOf ( value ) . Pointer ( )
2024-04-04 14:46:14 +00:00
fn := goruntime . FuncForPC ( pc )
2024-02-24 21:00:04 +00:00
if fn != nil {
name = fn . Name ( )
file , line = fn . FileLine ( pc )
file = path . Base ( file )
}
2024-04-04 14:46:14 +00:00
return objectValue ( rt . newNativeFunction ( name , file , line , value ) )
case nativeFunction :
2024-02-24 21:00:04 +00:00
var name , file string
var line int
pc := reflect . ValueOf ( value ) . Pointer ( )
2024-04-04 14:46:14 +00:00
fn := goruntime . FuncForPC ( pc )
2024-02-24 21:00:04 +00:00
if fn != nil {
name = fn . Name ( )
file , line = fn . FileLine ( pc )
file = path . Base ( file )
}
2024-04-04 14:46:14 +00:00
return objectValue ( rt . newNativeFunction ( name , file , line , value ) )
case Object , * Object , object , * object :
2024-02-24 21:00:04 +00:00
// Nothing happens.
// FIXME We should really figure out what can come here.
// This catch-all is ugly.
default :
2024-04-04 14:46:14 +00:00
val := reflect . ValueOf ( value )
if ok && val . Kind ( ) == rv . Kind ( ) {
// Use passed in rv which may be writable.
val = rv
}
2024-02-24 21:00:04 +00:00
2024-04-04 14:46:14 +00:00
switch val . Kind ( ) {
case reflect . Ptr :
switch reflect . Indirect ( val ) . Kind ( ) {
2024-02-24 21:00:04 +00:00
case reflect . Struct :
2024-04-04 14:46:14 +00:00
return objectValue ( rt . newGoStructObject ( val ) )
2024-02-24 21:00:04 +00:00
case reflect . Array :
2024-04-04 14:46:14 +00:00
return objectValue ( rt . newGoArray ( val ) )
}
case reflect . Struct :
return objectValue ( rt . newGoStructObject ( val ) )
case reflect . Map :
return objectValue ( rt . newGoMapObject ( val ) )
case reflect . Slice :
return objectValue ( rt . newGoSlice ( val ) )
case reflect . Array :
return objectValue ( rt . newGoArray ( val ) )
case reflect . Func :
var name , file string
var line int
if v := reflect . ValueOf ( val ) ; v . Kind ( ) == reflect . Ptr {
pc := v . Pointer ( )
fn := goruntime . FuncForPC ( pc )
if fn != nil {
name = fn . Name ( )
file , line = fn . FileLine ( pc )
file = path . Base ( file )
2024-02-24 21:00:04 +00:00
}
2024-04-04 14:46:14 +00:00
}
2024-02-24 21:00:04 +00:00
2024-04-04 14:46:14 +00:00
typ := val . Type ( )
2024-02-24 21:00:04 +00:00
2024-04-04 14:46:14 +00:00
return objectValue ( rt . newNativeFunction ( name , file , line , func ( c FunctionCall ) Value {
nargs := typ . NumIn ( )
2024-02-24 21:00:04 +00:00
2024-04-04 14:46:14 +00:00
if len ( c . ArgumentList ) != nargs {
if typ . IsVariadic ( ) {
if len ( c . ArgumentList ) < nargs - 1 {
panic ( rt . panicRangeError ( fmt . Sprintf ( "expected at least %d arguments; got %d" , nargs - 1 , len ( c . ArgumentList ) ) ) )
2024-02-24 21:00:04 +00:00
}
2024-04-04 14:46:14 +00:00
} else {
panic ( rt . panicRangeError ( fmt . Sprintf ( "expected %d argument(s); got %d" , nargs , len ( c . ArgumentList ) ) ) )
2024-02-24 21:00:04 +00:00
}
2024-04-04 14:46:14 +00:00
}
2024-02-24 21:00:04 +00:00
2024-04-04 14:46:14 +00:00
in := make ( [ ] reflect . Value , len ( c . ArgumentList ) )
2024-02-24 21:00:04 +00:00
2024-04-04 14:46:14 +00:00
callSlice := false
2024-02-24 21:00:04 +00:00
2024-04-04 14:46:14 +00:00
for i , a := range c . ArgumentList {
var t reflect . Type
2024-02-24 21:00:04 +00:00
2024-04-04 14:46:14 +00:00
n := i
if n >= nargs - 1 && typ . IsVariadic ( ) {
if n > nargs - 1 {
n = nargs - 1
2024-02-24 21:00:04 +00:00
}
2024-04-04 14:46:14 +00:00
t = typ . In ( n ) . Elem ( )
} else {
t = typ . In ( n )
}
2024-02-24 21:00:04 +00:00
2024-04-04 14:46:14 +00:00
// if this is a variadic Go function, and the caller has supplied
// exactly the number of JavaScript arguments required, and this
// is the last JavaScript argument, try treating the it as the
// actual set of variadic Go arguments. if that succeeds, break
// out of the loop.
if typ . IsVariadic ( ) && len ( c . ArgumentList ) == nargs && i == nargs - 1 {
if v , err := rt . convertCallParameter ( a , typ . In ( n ) ) ; err == nil {
in [ i ] = v
callSlice = true
break
2024-02-24 21:00:04 +00:00
}
}
2024-04-04 14:46:14 +00:00
v , err := rt . convertCallParameter ( a , t )
if err != nil {
panic ( rt . panicTypeError ( err . Error ( ) ) )
2024-02-24 21:00:04 +00:00
}
2024-04-04 14:46:14 +00:00
in [ i ] = v
}
2024-02-24 21:00:04 +00:00
2024-04-04 14:46:14 +00:00
var out [ ] reflect . Value
if callSlice {
out = val . CallSlice ( in )
} else {
out = val . Call ( in )
}
switch len ( out ) {
case 0 :
return Value { }
case 1 :
return rt . toValue ( out [ 0 ] . Interface ( ) )
default :
s := make ( [ ] interface { } , len ( out ) )
for i , v := range out {
s [ i ] = rt . toValue ( v . Interface ( ) )
2024-02-24 21:00:04 +00:00
}
2024-04-04 14:46:14 +00:00
return rt . toValue ( s )
}
} ) )
2024-02-24 21:00:04 +00:00
}
}
return toValue ( value )
}
2024-04-04 14:46:14 +00:00
func ( rt * runtime ) newGoSlice ( value reflect . Value ) * object {
obj := rt . newGoSliceObject ( value )
obj . prototype = rt . global . ArrayPrototype
return obj
2024-02-24 21:00:04 +00:00
}
2024-04-04 14:46:14 +00:00
func ( rt * runtime ) newGoArray ( value reflect . Value ) * object {
obj := rt . newGoArrayObject ( value )
obj . prototype = rt . global . ArrayPrototype
return obj
2024-02-24 21:00:04 +00:00
}
2024-04-04 14:46:14 +00:00
func ( rt * runtime ) parse ( filename string , src , sm interface { } ) ( * ast . Program , error ) {
2024-02-24 21:00:04 +00:00
return parser . ParseFileWithSourceMap ( nil , filename , src , sm , 0 )
}
2024-04-04 14:46:14 +00:00
func ( rt * runtime ) cmplParse ( filename string , src , sm interface { } ) ( * nodeProgram , error ) {
2024-02-24 21:00:04 +00:00
program , err := parser . ParseFileWithSourceMap ( nil , filename , src , sm , 0 )
if err != nil {
return nil , err
}
2024-04-04 14:46:14 +00:00
return cmplParse ( program ) , nil
2024-02-24 21:00:04 +00:00
}
2024-04-04 14:46:14 +00:00
func ( rt * runtime ) parseSource ( src , sm interface { } ) ( * nodeProgram , * ast . Program , error ) {
2024-02-24 21:00:04 +00:00
switch src := src . ( type ) {
case * ast . Program :
return nil , src , nil
case * Script :
return src . program , nil , nil
}
2024-04-04 14:46:14 +00:00
program , err := rt . parse ( "" , src , sm )
2024-02-24 21:00:04 +00:00
return nil , program , err
}
2024-04-04 14:46:14 +00:00
func ( rt * runtime ) cmplRunOrEval ( src , sm interface { } , eval bool ) ( Value , error ) {
2024-02-24 21:00:04 +00:00
result := Value { }
2024-04-04 14:46:14 +00:00
node , program , err := rt . parseSource ( src , sm )
2024-02-24 21:00:04 +00:00
if err != nil {
return result , err
}
2024-04-04 14:46:14 +00:00
if node == nil {
node = cmplParse ( program )
2024-02-24 21:00:04 +00:00
}
err = catchPanic ( func ( ) {
2024-04-04 14:46:14 +00:00
result = rt . cmplEvaluateNodeProgram ( node , eval )
2024-02-24 21:00:04 +00:00
} )
switch result . kind {
case valueEmpty :
result = Value { }
case valueReference :
result = result . resolve ( )
}
return result , err
}
2024-04-04 14:46:14 +00:00
func ( rt * runtime ) cmplRun ( src , sm interface { } ) ( Value , error ) {
return rt . cmplRunOrEval ( src , sm , false )
2024-02-24 21:00:04 +00:00
}
2024-04-04 14:46:14 +00:00
func ( rt * runtime ) cmplEval ( src , sm interface { } ) ( Value , error ) {
return rt . cmplRunOrEval ( src , sm , true )
2024-02-24 21:00:04 +00:00
}
2024-04-04 14:46:14 +00:00
func ( rt * runtime ) parseThrow ( err error ) {
2024-02-24 21:00:04 +00:00
if err == nil {
return
}
2024-04-04 14:46:14 +00:00
var errl parser . ErrorList
if errors . Is ( err , & errl ) {
err := errl [ 0 ]
if err . Message == "invalid left-hand side in assignment" {
panic ( rt . panicReferenceError ( err . Message ) )
2024-02-24 21:00:04 +00:00
}
2024-04-04 14:46:14 +00:00
panic ( rt . panicSyntaxError ( err . Message ) )
2024-02-24 21:00:04 +00:00
}
2024-04-04 14:46:14 +00:00
panic ( rt . panicSyntaxError ( err . Error ( ) ) )
2024-02-24 21:00:04 +00:00
}
2024-04-04 14:46:14 +00:00
func ( rt * runtime ) cmplParseOrThrow ( src , sm interface { } ) * nodeProgram {
program , err := rt . cmplParse ( "" , src , sm )
rt . parseThrow ( err ) // Will panic/throw appropriately
2024-02-24 21:00:04 +00:00
return program
}