1471 lines
33 KiB
Go
1471 lines
33 KiB
Go
package goja
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
"fmt"
|
|
"go/ast"
|
|
"math"
|
|
"math/rand"
|
|
"reflect"
|
|
"strconv"
|
|
|
|
js_ast "github.com/dop251/goja/ast"
|
|
"github.com/dop251/goja/parser"
|
|
)
|
|
|
|
const (
|
|
sqrt1_2 float64 = math.Sqrt2 / 2
|
|
)
|
|
|
|
var (
|
|
typeCallable = reflect.TypeOf(Callable(nil))
|
|
typeValue = reflect.TypeOf((*Value)(nil)).Elem()
|
|
)
|
|
|
|
type global struct {
|
|
Object *Object
|
|
Array *Object
|
|
Function *Object
|
|
String *Object
|
|
Number *Object
|
|
Boolean *Object
|
|
RegExp *Object
|
|
Date *Object
|
|
|
|
ArrayBuffer *Object
|
|
|
|
Error *Object
|
|
TypeError *Object
|
|
ReferenceError *Object
|
|
SyntaxError *Object
|
|
RangeError *Object
|
|
EvalError *Object
|
|
URIError *Object
|
|
|
|
GoError *Object
|
|
|
|
ObjectPrototype *Object
|
|
ArrayPrototype *Object
|
|
NumberPrototype *Object
|
|
StringPrototype *Object
|
|
BooleanPrototype *Object
|
|
FunctionPrototype *Object
|
|
RegExpPrototype *Object
|
|
DatePrototype *Object
|
|
|
|
ArrayBufferPrototype *Object
|
|
|
|
ErrorPrototype *Object
|
|
TypeErrorPrototype *Object
|
|
SyntaxErrorPrototype *Object
|
|
RangeErrorPrototype *Object
|
|
ReferenceErrorPrototype *Object
|
|
EvalErrorPrototype *Object
|
|
URIErrorPrototype *Object
|
|
|
|
GoErrorPrototype *Object
|
|
|
|
Eval *Object
|
|
|
|
thrower *Object
|
|
throwerProperty Value
|
|
}
|
|
|
|
type Flag int
|
|
|
|
const (
|
|
FLAG_NOT_SET Flag = iota
|
|
FLAG_FALSE
|
|
FLAG_TRUE
|
|
)
|
|
|
|
func (f Flag) Bool() bool {
|
|
return f == FLAG_TRUE
|
|
}
|
|
|
|
func ToFlag(b bool) Flag {
|
|
if b {
|
|
return FLAG_TRUE
|
|
}
|
|
return FLAG_FALSE
|
|
}
|
|
|
|
type RandSource func() float64
|
|
|
|
type Runtime struct {
|
|
global global
|
|
globalObject *Object
|
|
stringSingleton *stringObject
|
|
rand RandSource
|
|
|
|
typeInfoCache map[reflect.Type]*reflectTypeInfo
|
|
fieldNameMapper FieldNameMapper
|
|
|
|
vm *vm
|
|
}
|
|
|
|
type stackFrame struct {
|
|
prg *Program
|
|
funcName string
|
|
pc int
|
|
}
|
|
|
|
func (f *stackFrame) position() Position {
|
|
return f.prg.src.Position(f.prg.sourceOffset(f.pc))
|
|
}
|
|
|
|
func (f *stackFrame) write(b *bytes.Buffer) {
|
|
if f.prg != nil {
|
|
if n := f.prg.funcName; n != "" {
|
|
b.WriteString(n)
|
|
b.WriteString(" (")
|
|
}
|
|
if n := f.prg.src.name; n != "" {
|
|
b.WriteString(n)
|
|
} else {
|
|
b.WriteString("<eval>")
|
|
}
|
|
b.WriteByte(':')
|
|
b.WriteString(f.position().String())
|
|
b.WriteByte('(')
|
|
b.WriteString(strconv.Itoa(f.pc))
|
|
b.WriteByte(')')
|
|
if f.prg.funcName != "" {
|
|
b.WriteByte(')')
|
|
}
|
|
} else {
|
|
if f.funcName != "" {
|
|
b.WriteString(f.funcName)
|
|
b.WriteString(" (")
|
|
}
|
|
b.WriteString("native")
|
|
if f.funcName != "" {
|
|
b.WriteByte(')')
|
|
}
|
|
}
|
|
}
|
|
|
|
type Exception struct {
|
|
val Value
|
|
stack []stackFrame
|
|
}
|
|
|
|
type InterruptedError struct {
|
|
Exception
|
|
iface interface{}
|
|
}
|
|
|
|
func (e *InterruptedError) Value() interface{} {
|
|
return e.iface
|
|
}
|
|
|
|
func (e *InterruptedError) String() string {
|
|
if e == nil {
|
|
return "<nil>"
|
|
}
|
|
var b bytes.Buffer
|
|
if e.iface != nil {
|
|
b.WriteString(fmt.Sprint(e.iface))
|
|
b.WriteByte('\n')
|
|
}
|
|
e.writeFullStack(&b)
|
|
return b.String()
|
|
}
|
|
|
|
func (e *InterruptedError) Error() string {
|
|
if e == nil || e.iface == nil {
|
|
return "<nil>"
|
|
}
|
|
var b bytes.Buffer
|
|
b.WriteString(fmt.Sprint(e.iface))
|
|
e.writeShortStack(&b)
|
|
return b.String()
|
|
}
|
|
|
|
func (e *Exception) writeFullStack(b *bytes.Buffer) {
|
|
for _, frame := range e.stack {
|
|
b.WriteString("\tat ")
|
|
frame.write(b)
|
|
b.WriteByte('\n')
|
|
}
|
|
}
|
|
|
|
func (e *Exception) writeShortStack(b *bytes.Buffer) {
|
|
if len(e.stack) > 0 && (e.stack[0].prg != nil || e.stack[0].funcName != "") {
|
|
b.WriteString(" at ")
|
|
e.stack[0].write(b)
|
|
}
|
|
}
|
|
|
|
func (e *Exception) String() string {
|
|
if e == nil {
|
|
return "<nil>"
|
|
}
|
|
var b bytes.Buffer
|
|
if e.val != nil {
|
|
b.WriteString(e.val.String())
|
|
b.WriteByte('\n')
|
|
}
|
|
e.writeFullStack(&b)
|
|
return b.String()
|
|
}
|
|
|
|
func (e *Exception) Error() string {
|
|
if e == nil || e.val == nil {
|
|
return "<nil>"
|
|
}
|
|
var b bytes.Buffer
|
|
b.WriteString(e.val.String())
|
|
e.writeShortStack(&b)
|
|
return b.String()
|
|
}
|
|
|
|
func (e *Exception) Value() Value {
|
|
return e.val
|
|
}
|
|
|
|
func (r *Runtime) addToGlobal(name string, value Value) {
|
|
r.globalObject.self._putProp(name, value, true, false, true)
|
|
}
|
|
|
|
func (r *Runtime) init() {
|
|
r.rand = rand.Float64
|
|
r.global.ObjectPrototype = r.newBaseObject(nil, classObject).val
|
|
r.globalObject = r.NewObject()
|
|
|
|
r.vm = &vm{
|
|
r: r,
|
|
}
|
|
r.vm.init()
|
|
|
|
r.global.FunctionPrototype = r.newNativeFunc(nil, nil, "Empty", nil, 0)
|
|
r.initObject()
|
|
r.initFunction()
|
|
r.initArray()
|
|
r.initString()
|
|
r.initNumber()
|
|
r.initRegExp()
|
|
r.initDate()
|
|
r.initBoolean()
|
|
|
|
r.initErrors()
|
|
|
|
r.global.Eval = r.newNativeFunc(r.builtin_eval, nil, "eval", nil, 1)
|
|
r.addToGlobal("eval", r.global.Eval)
|
|
|
|
r.initGlobalObject()
|
|
|
|
r.initMath()
|
|
r.initJSON()
|
|
|
|
//r.initTypedArrays()
|
|
|
|
r.global.thrower = r.newNativeFunc(r.builtin_thrower, nil, "thrower", nil, 0)
|
|
r.global.throwerProperty = &valueProperty{
|
|
getterFunc: r.global.thrower,
|
|
setterFunc: r.global.thrower,
|
|
accessor: true,
|
|
}
|
|
}
|
|
|
|
func (r *Runtime) typeErrorResult(throw bool, args ...interface{}) {
|
|
if throw {
|
|
panic(r.NewTypeError(args...))
|
|
}
|
|
}
|
|
|
|
func (r *Runtime) newError(typ *Object, format string, args ...interface{}) Value {
|
|
msg := fmt.Sprintf(format, args...)
|
|
return r.builtin_new(typ, []Value{newStringValue(msg)})
|
|
}
|
|
|
|
func (r *Runtime) throwReferenceError(name string) {
|
|
panic(r.newError(r.global.ReferenceError, "%s is not defined", name))
|
|
}
|
|
|
|
func (r *Runtime) newSyntaxError(msg string, offset int) Value {
|
|
return r.builtin_new((r.global.SyntaxError), []Value{newStringValue(msg)})
|
|
}
|
|
|
|
func (r *Runtime) newArray(prototype *Object) (a *arrayObject) {
|
|
v := &Object{runtime: r}
|
|
|
|
a = &arrayObject{}
|
|
a.class = classArray
|
|
a.val = v
|
|
a.extensible = true
|
|
v.self = a
|
|
a.prototype = prototype
|
|
a.init()
|
|
return
|
|
}
|
|
|
|
func (r *Runtime) newArrayObject() *arrayObject {
|
|
return r.newArray(r.global.ArrayPrototype)
|
|
}
|
|
|
|
func (r *Runtime) newArrayValues(values []Value) *Object {
|
|
v := &Object{runtime: r}
|
|
|
|
a := &arrayObject{}
|
|
a.class = classArray
|
|
a.val = v
|
|
a.extensible = true
|
|
v.self = a
|
|
a.prototype = r.global.ArrayPrototype
|
|
a.init()
|
|
a.values = values
|
|
a.length = int64(len(values))
|
|
a.objCount = a.length
|
|
return v
|
|
}
|
|
|
|
func (r *Runtime) newArrayLength(l int64) *Object {
|
|
a := r.newArrayValues(nil)
|
|
a.self.putStr("length", intToValue(l), true)
|
|
return a
|
|
}
|
|
|
|
func (r *Runtime) newBaseObject(proto *Object, class string) (o *baseObject) {
|
|
v := &Object{runtime: r}
|
|
|
|
o = &baseObject{}
|
|
o.class = class
|
|
o.val = v
|
|
o.extensible = true
|
|
v.self = o
|
|
o.prototype = proto
|
|
o.init()
|
|
return
|
|
}
|
|
|
|
func (r *Runtime) NewObject() (v *Object) {
|
|
return r.newBaseObject(r.global.ObjectPrototype, classObject).val
|
|
}
|
|
|
|
// CreateObject creates an object with given prototype. Equivalent of Object.create(proto).
|
|
func (r *Runtime) CreateObject(proto *Object) *Object {
|
|
return r.newBaseObject(proto, classObject).val
|
|
}
|
|
|
|
func (r *Runtime) NewTypeError(args ...interface{}) *Object {
|
|
msg := ""
|
|
if len(args) > 0 {
|
|
f, _ := args[0].(string)
|
|
msg = fmt.Sprintf(f, args[1:]...)
|
|
}
|
|
return r.builtin_new(r.global.TypeError, []Value{newStringValue(msg)})
|
|
}
|
|
|
|
func (r *Runtime) NewGoError(err error) *Object {
|
|
e := r.newError(r.global.GoError, err.Error()).(*Object)
|
|
e.Set("value", err)
|
|
return e
|
|
}
|
|
|
|
func (r *Runtime) newFunc(name string, len int, strict bool) (f *funcObject) {
|
|
v := &Object{runtime: r}
|
|
|
|
f = &funcObject{}
|
|
f.class = classFunction
|
|
f.val = v
|
|
f.extensible = true
|
|
v.self = f
|
|
f.prototype = r.global.FunctionPrototype
|
|
f.init(name, len)
|
|
if strict {
|
|
f._put("caller", r.global.throwerProperty)
|
|
f._put("arguments", r.global.throwerProperty)
|
|
}
|
|
return
|
|
}
|
|
|
|
func (r *Runtime) newNativeFuncObj(v *Object, call func(FunctionCall) Value, construct func(args []Value) *Object, name string, proto *Object, length int) *nativeFuncObject {
|
|
f := &nativeFuncObject{
|
|
baseFuncObject: baseFuncObject{
|
|
baseObject: baseObject{
|
|
class: classFunction,
|
|
val: v,
|
|
extensible: true,
|
|
prototype: r.global.FunctionPrototype,
|
|
},
|
|
},
|
|
f: call,
|
|
construct: construct,
|
|
}
|
|
v.self = f
|
|
f.init(name, length)
|
|
if proto != nil {
|
|
f._putProp("prototype", proto, false, false, false)
|
|
}
|
|
return f
|
|
}
|
|
|
|
func (r *Runtime) newNativeConstructor(call func(ConstructorCall) *Object, name string, length int) *Object {
|
|
v := &Object{runtime: r}
|
|
|
|
f := &nativeFuncObject{
|
|
baseFuncObject: baseFuncObject{
|
|
baseObject: baseObject{
|
|
class: classFunction,
|
|
val: v,
|
|
extensible: true,
|
|
prototype: r.global.FunctionPrototype,
|
|
},
|
|
},
|
|
}
|
|
|
|
f.f = func(c FunctionCall) Value {
|
|
return f.defaultConstruct(call, c.Arguments)
|
|
}
|
|
|
|
f.construct = func(args []Value) *Object {
|
|
return f.defaultConstruct(call, args)
|
|
}
|
|
|
|
v.self = f
|
|
f.init(name, length)
|
|
|
|
proto := r.NewObject()
|
|
proto.self._putProp("constructor", v, true, false, true)
|
|
f._putProp("prototype", proto, true, false, false)
|
|
|
|
return v
|
|
}
|
|
|
|
func (r *Runtime) newNativeFunc(call func(FunctionCall) Value, construct func(args []Value) *Object, name string, proto *Object, length int) *Object {
|
|
v := &Object{runtime: r}
|
|
|
|
f := &nativeFuncObject{
|
|
baseFuncObject: baseFuncObject{
|
|
baseObject: baseObject{
|
|
class: classFunction,
|
|
val: v,
|
|
extensible: true,
|
|
prototype: r.global.FunctionPrototype,
|
|
},
|
|
},
|
|
f: call,
|
|
construct: construct,
|
|
}
|
|
v.self = f
|
|
f.init(name, length)
|
|
if proto != nil {
|
|
f._putProp("prototype", proto, false, false, false)
|
|
proto.self._putProp("constructor", v, true, false, true)
|
|
}
|
|
return v
|
|
}
|
|
|
|
func (r *Runtime) newNativeFuncConstructObj(v *Object, construct func(args []Value, proto *Object) *Object, name string, proto *Object, length int) *nativeFuncObject {
|
|
f := &nativeFuncObject{
|
|
baseFuncObject: baseFuncObject{
|
|
baseObject: baseObject{
|
|
class: classFunction,
|
|
val: v,
|
|
extensible: true,
|
|
prototype: r.global.FunctionPrototype,
|
|
},
|
|
},
|
|
f: r.constructWrap(construct, proto),
|
|
construct: func(args []Value) *Object {
|
|
return construct(args, proto)
|
|
},
|
|
}
|
|
|
|
f.init(name, length)
|
|
if proto != nil {
|
|
f._putProp("prototype", proto, false, false, false)
|
|
}
|
|
return f
|
|
}
|
|
|
|
func (r *Runtime) newNativeFuncConstruct(construct func(args []Value, proto *Object) *Object, name string, prototype *Object, length int) *Object {
|
|
return r.newNativeFuncConstructProto(construct, name, prototype, r.global.FunctionPrototype, length)
|
|
}
|
|
|
|
func (r *Runtime) newNativeFuncConstructProto(construct func(args []Value, proto *Object) *Object, name string, prototype, proto *Object, length int) *Object {
|
|
v := &Object{runtime: r}
|
|
|
|
f := &nativeFuncObject{}
|
|
f.class = classFunction
|
|
f.val = v
|
|
f.extensible = true
|
|
v.self = f
|
|
f.prototype = proto
|
|
f.f = r.constructWrap(construct, prototype)
|
|
f.construct = func(args []Value) *Object {
|
|
return construct(args, prototype)
|
|
}
|
|
f.init(name, length)
|
|
if prototype != nil {
|
|
f._putProp("prototype", prototype, false, false, false)
|
|
prototype.self._putProp("constructor", v, true, false, true)
|
|
}
|
|
return v
|
|
}
|
|
|
|
func (r *Runtime) newPrimitiveObject(value Value, proto *Object, class string) *Object {
|
|
v := &Object{runtime: r}
|
|
|
|
o := &primitiveValueObject{}
|
|
o.class = class
|
|
o.val = v
|
|
o.extensible = true
|
|
v.self = o
|
|
o.prototype = proto
|
|
o.pValue = value
|
|
o.init()
|
|
return v
|
|
}
|
|
|
|
func (r *Runtime) builtin_Number(call FunctionCall) Value {
|
|
if len(call.Arguments) > 0 {
|
|
return call.Arguments[0].ToNumber()
|
|
} else {
|
|
return intToValue(0)
|
|
}
|
|
}
|
|
|
|
func (r *Runtime) builtin_newNumber(args []Value) *Object {
|
|
var v Value
|
|
if len(args) > 0 {
|
|
v = args[0].ToNumber()
|
|
} else {
|
|
v = intToValue(0)
|
|
}
|
|
return r.newPrimitiveObject(v, r.global.NumberPrototype, classNumber)
|
|
}
|
|
|
|
func (r *Runtime) builtin_Boolean(call FunctionCall) Value {
|
|
if len(call.Arguments) > 0 {
|
|
if call.Arguments[0].ToBoolean() {
|
|
return valueTrue
|
|
} else {
|
|
return valueFalse
|
|
}
|
|
} else {
|
|
return valueFalse
|
|
}
|
|
}
|
|
|
|
func (r *Runtime) builtin_newBoolean(args []Value) *Object {
|
|
var v Value
|
|
if len(args) > 0 {
|
|
if args[0].ToBoolean() {
|
|
v = valueTrue
|
|
} else {
|
|
v = valueFalse
|
|
}
|
|
} else {
|
|
v = valueFalse
|
|
}
|
|
return r.newPrimitiveObject(v, r.global.BooleanPrototype, classBoolean)
|
|
}
|
|
|
|
func (r *Runtime) error_toString(call FunctionCall) Value {
|
|
obj := call.This.ToObject(r).self
|
|
msg := obj.getStr("message")
|
|
name := obj.getStr("name")
|
|
var nameStr, msgStr string
|
|
if name != nil && name != _undefined {
|
|
nameStr = name.String()
|
|
}
|
|
if msg != nil && msg != _undefined {
|
|
msgStr = msg.String()
|
|
}
|
|
if nameStr != "" && msgStr != "" {
|
|
return newStringValue(fmt.Sprintf("%s: %s", name.String(), msgStr))
|
|
} else {
|
|
if nameStr != "" {
|
|
return name.ToString()
|
|
} else {
|
|
return msg.ToString()
|
|
}
|
|
}
|
|
}
|
|
|
|
func (r *Runtime) builtin_Error(args []Value, proto *Object) *Object {
|
|
obj := r.newBaseObject(proto, classError)
|
|
if len(args) > 0 && args[0] != _undefined {
|
|
obj._putProp("message", args[0], true, false, true)
|
|
}
|
|
return obj.val
|
|
}
|
|
|
|
func (r *Runtime) builtin_new(construct *Object, args []Value) *Object {
|
|
repeat:
|
|
switch f := construct.self.(type) {
|
|
case *nativeFuncObject:
|
|
if f.construct != nil {
|
|
return f.construct(args)
|
|
} else {
|
|
panic("Not a constructor")
|
|
}
|
|
case *boundFuncObject:
|
|
if f.construct != nil {
|
|
return f.construct(args)
|
|
} else {
|
|
panic("Not a constructor")
|
|
}
|
|
case *funcObject:
|
|
// TODO: implement
|
|
panic("Not implemented")
|
|
case *lazyObject:
|
|
construct.self = f.create(construct)
|
|
goto repeat
|
|
default:
|
|
panic("Not a constructor")
|
|
}
|
|
}
|
|
|
|
func (r *Runtime) throw(e Value) {
|
|
panic(e)
|
|
}
|
|
|
|
func (r *Runtime) builtin_thrower(call FunctionCall) Value {
|
|
r.typeErrorResult(true, "'caller', 'callee', and 'arguments' properties may not be accessed on strict mode functions or the arguments objects for calls to them")
|
|
return nil
|
|
}
|
|
|
|
func (r *Runtime) eval(src string, direct, strict bool, this Value) Value {
|
|
|
|
p, err := r.compile("<eval>", src, strict, true)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
vm := r.vm
|
|
|
|
vm.pushCtx()
|
|
vm.prg = p
|
|
vm.pc = 0
|
|
if !direct {
|
|
vm.stash = nil
|
|
}
|
|
vm.sb = vm.sp
|
|
vm.push(this)
|
|
if strict {
|
|
vm.push(valueTrue)
|
|
} else {
|
|
vm.push(valueFalse)
|
|
}
|
|
vm.run()
|
|
vm.popCtx()
|
|
vm.halt = false
|
|
retval := vm.stack[vm.sp-1]
|
|
vm.sp -= 2
|
|
return retval
|
|
}
|
|
|
|
func (r *Runtime) builtin_eval(call FunctionCall) Value {
|
|
if len(call.Arguments) == 0 {
|
|
return _undefined
|
|
}
|
|
if str, ok := call.Arguments[0].assertString(); ok {
|
|
return r.eval(str.String(), false, false, r.globalObject)
|
|
}
|
|
return call.Arguments[0]
|
|
}
|
|
|
|
func (r *Runtime) constructWrap(construct func(args []Value, proto *Object) *Object, proto *Object) func(call FunctionCall) Value {
|
|
return func(call FunctionCall) Value {
|
|
return construct(call.Arguments, proto)
|
|
}
|
|
}
|
|
|
|
func (r *Runtime) toCallable(v Value) func(FunctionCall) Value {
|
|
if call, ok := r.toObject(v).self.assertCallable(); ok {
|
|
return call
|
|
}
|
|
r.typeErrorResult(true, "Value is not callable: %s", v.ToString())
|
|
return nil
|
|
}
|
|
|
|
func (r *Runtime) checkObjectCoercible(v Value) {
|
|
switch v.(type) {
|
|
case valueUndefined, valueNull:
|
|
r.typeErrorResult(true, "Value is not object coercible")
|
|
}
|
|
}
|
|
|
|
func toUInt32(v Value) uint32 {
|
|
v = v.ToNumber()
|
|
if i, ok := v.assertInt(); ok {
|
|
return uint32(i)
|
|
}
|
|
|
|
if f, ok := v.assertFloat(); ok {
|
|
if !math.IsNaN(f) && !math.IsInf(f, 0) {
|
|
return uint32(int64(f))
|
|
}
|
|
}
|
|
return 0
|
|
}
|
|
|
|
func toUInt16(v Value) uint16 {
|
|
v = v.ToNumber()
|
|
if i, ok := v.assertInt(); ok {
|
|
return uint16(i)
|
|
}
|
|
|
|
if f, ok := v.assertFloat(); ok {
|
|
if !math.IsNaN(f) && !math.IsInf(f, 0) {
|
|
return uint16(int64(f))
|
|
}
|
|
}
|
|
return 0
|
|
}
|
|
|
|
func toLength(v Value) int64 {
|
|
if v == nil {
|
|
return 0
|
|
}
|
|
i := v.ToInteger()
|
|
if i < 0 {
|
|
return 0
|
|
}
|
|
if i >= maxInt {
|
|
return maxInt - 1
|
|
}
|
|
return i
|
|
}
|
|
|
|
func toInt32(v Value) int32 {
|
|
v = v.ToNumber()
|
|
if i, ok := v.assertInt(); ok {
|
|
return int32(i)
|
|
}
|
|
|
|
if f, ok := v.assertFloat(); ok {
|
|
if !math.IsNaN(f) && !math.IsInf(f, 0) {
|
|
return int32(int64(f))
|
|
}
|
|
}
|
|
return 0
|
|
}
|
|
|
|
func (r *Runtime) toBoolean(b bool) Value {
|
|
if b {
|
|
return valueTrue
|
|
} else {
|
|
return valueFalse
|
|
}
|
|
}
|
|
|
|
// New creates an instance of a Javascript runtime that can be used to run code. Multiple instances may be created and
|
|
// used simultaneously, however it is not possible to pass JS values across runtimes.
|
|
func New() *Runtime {
|
|
r := &Runtime{}
|
|
r.init()
|
|
return r
|
|
}
|
|
|
|
// Compile creates an internal representation of the JavaScript code that can be later run using the Runtime.RunProgram()
|
|
// method. This representation is not linked to a runtime in any way and can be run in multiple runtimes (possibly
|
|
// at the same time).
|
|
func Compile(name, src string, strict bool) (*Program, error) {
|
|
return compile(name, src, strict, false)
|
|
}
|
|
|
|
// CompileAST creates an internal representation of the JavaScript code that can be later run using the Runtime.RunProgram()
|
|
// method. This representation is not linked to a runtime in any way and can be run in multiple runtimes (possibly
|
|
// at the same time).
|
|
func CompileAST(prg *js_ast.Program, strict bool) (*Program, error) {
|
|
return compileAST(prg, strict, false)
|
|
}
|
|
|
|
// MustCompile is like Compile but panics if the code cannot be compiled.
|
|
// It simplifies safe initialization of global variables holding compiled JavaScript code.
|
|
func MustCompile(name, src string, strict bool) *Program {
|
|
prg, err := Compile(name, src, strict)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
return prg
|
|
}
|
|
|
|
func compile(name, src string, strict, eval bool) (p *Program, err error) {
|
|
prg, err1 := parser.ParseFile(nil, name, src, 0)
|
|
if err1 != nil {
|
|
switch err1 := err1.(type) {
|
|
case parser.ErrorList:
|
|
if len(err1) > 0 && err1[0].Message == "Invalid left-hand side in assignment" {
|
|
err = &CompilerReferenceError{
|
|
CompilerError: CompilerError{
|
|
Message: err1.Error(),
|
|
},
|
|
}
|
|
return
|
|
}
|
|
}
|
|
// FIXME offset
|
|
err = &CompilerSyntaxError{
|
|
CompilerError: CompilerError{
|
|
Message: err1.Error(),
|
|
},
|
|
}
|
|
return
|
|
}
|
|
|
|
p, err = compileAST(prg, strict, eval)
|
|
|
|
return
|
|
}
|
|
|
|
func compileAST(prg *js_ast.Program, strict, eval bool) (p *Program, err error) {
|
|
c := newCompiler()
|
|
c.scope.strict = strict
|
|
c.scope.eval = eval
|
|
|
|
defer func() {
|
|
if x := recover(); x != nil {
|
|
p = nil
|
|
switch x1 := x.(type) {
|
|
case *CompilerSyntaxError:
|
|
err = x1
|
|
default:
|
|
panic(x)
|
|
}
|
|
}
|
|
}()
|
|
|
|
c.compile(prg)
|
|
p = c.p
|
|
return
|
|
}
|
|
|
|
func (r *Runtime) compile(name, src string, strict, eval bool) (p *Program, err error) {
|
|
p, err = compile(name, src, strict, eval)
|
|
if err != nil {
|
|
switch x1 := err.(type) {
|
|
case *CompilerSyntaxError:
|
|
err = &Exception{
|
|
val: r.builtin_new(r.global.SyntaxError, []Value{newStringValue(x1.Error())}),
|
|
}
|
|
case *CompilerReferenceError:
|
|
err = &Exception{
|
|
val: r.newError(r.global.ReferenceError, x1.Message),
|
|
} // TODO proper message
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
// RunString executes the given string in the global context.
|
|
func (r *Runtime) RunString(str string) (Value, error) {
|
|
return r.RunScript("", str)
|
|
}
|
|
|
|
// RunScript executes the given string in the global context.
|
|
func (r *Runtime) RunScript(name, src string) (Value, error) {
|
|
p, err := Compile(name, src, false)
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return r.RunProgram(p)
|
|
}
|
|
|
|
// RunProgram executes a pre-compiled (see Compile()) code in the global context.
|
|
func (r *Runtime) RunProgram(p *Program) (result Value, err error) {
|
|
defer func() {
|
|
if x := recover(); x != nil {
|
|
if intr, ok := x.(*InterruptedError); ok {
|
|
err = intr
|
|
} else {
|
|
panic(x)
|
|
}
|
|
}
|
|
}()
|
|
recursive := false
|
|
if len(r.vm.callStack) > 0 {
|
|
recursive = true
|
|
r.vm.pushCtx()
|
|
}
|
|
r.vm.prg = p
|
|
r.vm.pc = 0
|
|
ex := r.vm.runTry()
|
|
if ex == nil {
|
|
result = r.vm.pop()
|
|
} else {
|
|
err = ex
|
|
}
|
|
if recursive {
|
|
r.vm.popCtx()
|
|
r.vm.halt = false
|
|
r.vm.clearStack()
|
|
} else {
|
|
r.vm.stack = nil
|
|
}
|
|
return
|
|
}
|
|
|
|
// Interrupt a running JavaScript. The corresponding Go call will return an *InterruptedError containing v.
|
|
// Note, it only works while in JavaScript code, it does not interrupt native Go functions (which includes all built-ins).
|
|
func (r *Runtime) Interrupt(v interface{}) {
|
|
r.vm.Interrupt(v)
|
|
}
|
|
|
|
/*
|
|
ToValue converts a Go value into JavaScript value.
|
|
|
|
Primitive types (ints and uints, floats, string, bool) are converted to the corresponding JavaScript primitives.
|
|
|
|
func(FunctionCall) Value is treated as a native JavaScript function.
|
|
|
|
map[string]interface{} is converted into a host object that largely behaves like a JavaScript Object.
|
|
|
|
[]interface{} is converted into a host object that behaves largely like a JavaScript Array, however it's not extensible
|
|
because extending it can change the pointer so it becomes detached from the original.
|
|
|
|
*[]interface{} same as above, but the array becomes extensible.
|
|
|
|
A function is wrapped within a native JavaScript function. When called the arguments are automatically converted to
|
|
the appropriate Go types. If conversion is not possible, a TypeError is thrown.
|
|
|
|
A slice type is converted into a generic reflect based host object that behaves similar to an unexpandable Array.
|
|
|
|
Any other type is converted to a generic reflect based host object. Depending on the underlying type it behaves similar
|
|
to a Number, String, Boolean or Object.
|
|
|
|
Note that the underlying type is not lost, calling Export() returns the original Go value. This applies to all
|
|
reflect based types.
|
|
*/
|
|
func (r *Runtime) ToValue(i interface{}) Value {
|
|
switch i := i.(type) {
|
|
case nil:
|
|
return _null
|
|
case Value:
|
|
// TODO: prevent importing Objects from a different runtime
|
|
return i
|
|
case string:
|
|
return newStringValue(i)
|
|
case bool:
|
|
if i {
|
|
return valueTrue
|
|
} else {
|
|
return valueFalse
|
|
}
|
|
case func(FunctionCall) Value:
|
|
return r.newNativeFunc(i, nil, "", nil, 0)
|
|
case func(ConstructorCall) *Object:
|
|
return r.newNativeConstructor(i, "", 0)
|
|
case int:
|
|
return intToValue(int64(i))
|
|
case int8:
|
|
return intToValue(int64(i))
|
|
case int16:
|
|
return intToValue(int64(i))
|
|
case int32:
|
|
return intToValue(int64(i))
|
|
case int64:
|
|
return intToValue(i)
|
|
case uint:
|
|
if int64(i) <= math.MaxInt64 {
|
|
return intToValue(int64(i))
|
|
} else {
|
|
return floatToValue(float64(i))
|
|
}
|
|
case uint8:
|
|
return intToValue(int64(i))
|
|
case uint16:
|
|
return intToValue(int64(i))
|
|
case uint32:
|
|
return intToValue(int64(i))
|
|
case uint64:
|
|
if i <= math.MaxInt64 {
|
|
return intToValue(int64(i))
|
|
}
|
|
return floatToValue(float64(i))
|
|
case float32:
|
|
return floatToValue(float64(i))
|
|
case float64:
|
|
return floatToValue(i)
|
|
case map[string]interface{}:
|
|
obj := &Object{runtime: r}
|
|
m := &objectGoMapSimple{
|
|
baseObject: baseObject{
|
|
val: obj,
|
|
extensible: true,
|
|
},
|
|
data: i,
|
|
}
|
|
obj.self = m
|
|
m.init()
|
|
return obj
|
|
case []interface{}:
|
|
obj := &Object{runtime: r}
|
|
a := &objectGoSlice{
|
|
baseObject: baseObject{
|
|
val: obj,
|
|
},
|
|
data: &i,
|
|
}
|
|
obj.self = a
|
|
a.init()
|
|
return obj
|
|
case *[]interface{}:
|
|
obj := &Object{runtime: r}
|
|
a := &objectGoSlice{
|
|
baseObject: baseObject{
|
|
val: obj,
|
|
},
|
|
data: i,
|
|
sliceExtensible: true,
|
|
}
|
|
obj.self = a
|
|
a.init()
|
|
return obj
|
|
}
|
|
|
|
origValue := reflect.ValueOf(i)
|
|
value := origValue
|
|
for value.Kind() == reflect.Ptr {
|
|
value = reflect.Indirect(value)
|
|
}
|
|
|
|
if !value.IsValid() {
|
|
return _null
|
|
}
|
|
|
|
switch value.Kind() {
|
|
case reflect.Map:
|
|
if value.Type().NumMethod() == 0 {
|
|
switch value.Type().Key().Kind() {
|
|
case reflect.String, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
|
|
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
|
|
reflect.Float64, reflect.Float32:
|
|
|
|
obj := &Object{runtime: r}
|
|
m := &objectGoMapReflect{
|
|
objectGoReflect: objectGoReflect{
|
|
baseObject: baseObject{
|
|
val: obj,
|
|
extensible: true,
|
|
},
|
|
origValue: origValue,
|
|
value: value,
|
|
},
|
|
}
|
|
m.init()
|
|
obj.self = m
|
|
return obj
|
|
}
|
|
}
|
|
case reflect.Slice:
|
|
obj := &Object{runtime: r}
|
|
a := &objectGoSliceReflect{
|
|
objectGoReflect: objectGoReflect{
|
|
baseObject: baseObject{
|
|
val: obj,
|
|
},
|
|
origValue: origValue,
|
|
value: value,
|
|
},
|
|
}
|
|
a.init()
|
|
obj.self = a
|
|
return obj
|
|
case reflect.Func:
|
|
return r.newNativeFunc(r.wrapReflectFunc(value), nil, "", nil, value.Type().NumIn())
|
|
}
|
|
|
|
obj := &Object{runtime: r}
|
|
o := &objectGoReflect{
|
|
baseObject: baseObject{
|
|
val: obj,
|
|
},
|
|
origValue: origValue,
|
|
value: value,
|
|
}
|
|
obj.self = o
|
|
o.init()
|
|
return obj
|
|
}
|
|
|
|
func (r *Runtime) wrapReflectFunc(value reflect.Value) func(FunctionCall) Value {
|
|
return func(call FunctionCall) Value {
|
|
typ := value.Type()
|
|
nargs := typ.NumIn()
|
|
var in []reflect.Value
|
|
|
|
if l := len(call.Arguments); l < nargs {
|
|
// fill missing arguments with zero values
|
|
n := nargs
|
|
if typ.IsVariadic() {
|
|
n--
|
|
}
|
|
in = make([]reflect.Value, n)
|
|
for i := l; i < n; i++ {
|
|
in[i] = reflect.Zero(typ.In(i))
|
|
}
|
|
} else {
|
|
if l > nargs && !typ.IsVariadic() {
|
|
l = nargs
|
|
}
|
|
in = make([]reflect.Value, l)
|
|
}
|
|
|
|
callSlice := false
|
|
for i, a := range call.Arguments {
|
|
var t reflect.Type
|
|
|
|
n := i
|
|
if n >= nargs-1 && typ.IsVariadic() {
|
|
if n > nargs-1 {
|
|
n = nargs - 1
|
|
}
|
|
|
|
t = typ.In(n).Elem()
|
|
} else if n > nargs-1 { // ignore extra arguments
|
|
break
|
|
} else {
|
|
t = typ.In(n)
|
|
}
|
|
|
|
// 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(call.Arguments) == nargs && i == nargs-1 {
|
|
if v, err := r.toReflectValue(a, typ.In(n)); err == nil {
|
|
in[i] = v
|
|
callSlice = true
|
|
break
|
|
}
|
|
}
|
|
var err error
|
|
in[i], err = r.toReflectValue(a, t)
|
|
if err != nil {
|
|
panic(r.newError(r.global.TypeError, "Could not convert function call parameter %v to %v", a, t))
|
|
}
|
|
}
|
|
|
|
var out []reflect.Value
|
|
if callSlice {
|
|
out = value.CallSlice(in)
|
|
} else {
|
|
out = value.Call(in)
|
|
}
|
|
|
|
if len(out) == 0 {
|
|
return _undefined
|
|
}
|
|
|
|
if last := out[len(out)-1]; last.Type().Name() == "error" {
|
|
if !last.IsNil() {
|
|
err := last.Interface()
|
|
if _, ok := err.(*Exception); ok {
|
|
panic(err)
|
|
}
|
|
panic(r.NewGoError(last.Interface().(error)))
|
|
}
|
|
out = out[:len(out)-1]
|
|
}
|
|
|
|
switch len(out) {
|
|
case 0:
|
|
return _undefined
|
|
case 1:
|
|
return r.ToValue(out[0].Interface())
|
|
default:
|
|
s := make([]interface{}, len(out))
|
|
for i, v := range out {
|
|
s[i] = v.Interface()
|
|
}
|
|
|
|
return r.ToValue(s)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (r *Runtime) toReflectValue(v Value, typ reflect.Type) (reflect.Value, error) {
|
|
switch typ.Kind() {
|
|
case reflect.String:
|
|
return reflect.ValueOf(v.String()).Convert(typ), nil
|
|
case reflect.Bool:
|
|
return reflect.ValueOf(v.ToBoolean()).Convert(typ), nil
|
|
case reflect.Int:
|
|
i, _ := toInt(v)
|
|
return reflect.ValueOf(int(i)).Convert(typ), nil
|
|
case reflect.Int64:
|
|
i, _ := toInt(v)
|
|
return reflect.ValueOf(i).Convert(typ), nil
|
|
case reflect.Int32:
|
|
i, _ := toInt(v)
|
|
return reflect.ValueOf(int32(i)).Convert(typ), nil
|
|
case reflect.Int16:
|
|
i, _ := toInt(v)
|
|
return reflect.ValueOf(int16(i)).Convert(typ), nil
|
|
case reflect.Int8:
|
|
i, _ := toInt(v)
|
|
return reflect.ValueOf(int8(i)).Convert(typ), nil
|
|
case reflect.Uint:
|
|
i, _ := toInt(v)
|
|
return reflect.ValueOf(uint(i)).Convert(typ), nil
|
|
case reflect.Uint64:
|
|
i, _ := toInt(v)
|
|
return reflect.ValueOf(uint64(i)).Convert(typ), nil
|
|
case reflect.Uint32:
|
|
i, _ := toInt(v)
|
|
return reflect.ValueOf(uint32(i)).Convert(typ), nil
|
|
case reflect.Uint16:
|
|
i, _ := toInt(v)
|
|
return reflect.ValueOf(uint16(i)).Convert(typ), nil
|
|
case reflect.Uint8:
|
|
i, _ := toInt(v)
|
|
return reflect.ValueOf(uint8(i)).Convert(typ), nil
|
|
}
|
|
|
|
if typ == typeCallable {
|
|
if fn, ok := AssertFunction(v); ok {
|
|
return reflect.ValueOf(fn), nil
|
|
}
|
|
}
|
|
|
|
if typ.Implements(typeValue) {
|
|
return reflect.ValueOf(v), nil
|
|
}
|
|
|
|
et := v.ExportType()
|
|
if et == nil {
|
|
return reflect.Zero(typ), nil
|
|
}
|
|
if et.AssignableTo(typ) {
|
|
return reflect.ValueOf(v.Export()), nil
|
|
} else if et.ConvertibleTo(typ) {
|
|
return reflect.ValueOf(v.Export()).Convert(typ), nil
|
|
}
|
|
|
|
switch typ.Kind() {
|
|
case reflect.Slice:
|
|
if o, ok := v.(*Object); ok {
|
|
if o.self.className() == classArray {
|
|
l := int(toLength(o.self.getStr("length")))
|
|
s := reflect.MakeSlice(typ, l, l)
|
|
elemTyp := typ.Elem()
|
|
for i := 0; i < l; i++ {
|
|
item := o.self.get(intToValue(int64(i)))
|
|
itemval, err := r.toReflectValue(item, elemTyp)
|
|
if err != nil {
|
|
return reflect.Value{}, fmt.Errorf("Could not convert array element %v to %v at %d", v, typ, i)
|
|
}
|
|
s.Index(i).Set(itemval)
|
|
}
|
|
return s, nil
|
|
}
|
|
}
|
|
case reflect.Map:
|
|
if o, ok := v.(*Object); ok {
|
|
m := reflect.MakeMap(typ)
|
|
keyTyp := typ.Key()
|
|
elemTyp := typ.Elem()
|
|
needConvertKeys := !reflect.ValueOf("").Type().AssignableTo(keyTyp)
|
|
for item, f := o.self.enumerate(false, false)(); f != nil; item, f = f() {
|
|
var kv reflect.Value
|
|
var err error
|
|
if needConvertKeys {
|
|
kv, err = r.toReflectValue(newStringValue(item.name), keyTyp)
|
|
if err != nil {
|
|
return reflect.Value{}, fmt.Errorf("Could not convert map key %s to %v", item.name, typ)
|
|
}
|
|
} else {
|
|
kv = reflect.ValueOf(item.name)
|
|
}
|
|
|
|
ival := item.value
|
|
if ival == nil {
|
|
ival = o.self.getStr(item.name)
|
|
}
|
|
if ival != nil {
|
|
vv, err := r.toReflectValue(ival, elemTyp)
|
|
if err != nil {
|
|
return reflect.Value{}, fmt.Errorf("Could not convert map value %v to %v at key %s", ival, typ, item.name)
|
|
}
|
|
m.SetMapIndex(kv, vv)
|
|
} else {
|
|
m.SetMapIndex(kv, reflect.Zero(elemTyp))
|
|
}
|
|
}
|
|
return m, nil
|
|
}
|
|
case reflect.Struct:
|
|
if o, ok := v.(*Object); ok {
|
|
s := reflect.New(typ).Elem()
|
|
for i := 0; i < typ.NumField(); i++ {
|
|
field := typ.Field(i)
|
|
if ast.IsExported(field.Name) {
|
|
v := o.self.getStr(field.Name)
|
|
if v != nil {
|
|
vv, err := r.toReflectValue(v, field.Type)
|
|
if err != nil {
|
|
return reflect.Value{}, fmt.Errorf("Could not convert struct value %v to %v for field %s", v, field.Type, field.Name)
|
|
|
|
}
|
|
s.Field(i).Set(vv)
|
|
}
|
|
}
|
|
}
|
|
return s, nil
|
|
}
|
|
case reflect.Func:
|
|
if fn, ok := AssertFunction(v); ok {
|
|
return reflect.MakeFunc(typ, r.wrapJSFunc(fn, typ)), nil
|
|
}
|
|
}
|
|
|
|
return reflect.Value{}, fmt.Errorf("Could not convert %v to %v", v, typ)
|
|
}
|
|
|
|
func (r *Runtime) wrapJSFunc(fn Callable, typ reflect.Type) func(args []reflect.Value) (results []reflect.Value) {
|
|
return func(args []reflect.Value) (results []reflect.Value) {
|
|
jsArgs := make([]Value, len(args))
|
|
for i, arg := range args {
|
|
jsArgs[i] = r.ToValue(arg.Interface())
|
|
}
|
|
|
|
results = make([]reflect.Value, typ.NumOut())
|
|
res, err := fn(_undefined, jsArgs...)
|
|
if err == nil {
|
|
if typ.NumOut() > 0 {
|
|
results[0], err = r.toReflectValue(res, typ.Out(0))
|
|
}
|
|
}
|
|
|
|
if err != nil {
|
|
if typ.NumOut() == 2 && typ.Out(1).Name() == "error" {
|
|
results[1] = reflect.ValueOf(err).Convert(typ.Out(1))
|
|
} else {
|
|
panic(err)
|
|
}
|
|
}
|
|
|
|
for i, v := range results {
|
|
if !v.IsValid() {
|
|
results[i] = reflect.Zero(typ.Out(i))
|
|
}
|
|
}
|
|
|
|
return
|
|
}
|
|
}
|
|
|
|
// ExportTo converts a JavaScript value into the specified Go value. The second parameter must be a non-nil pointer.
|
|
// Returns error if conversion is not possible.
|
|
func (r *Runtime) ExportTo(v Value, target interface{}) error {
|
|
tval := reflect.ValueOf(target)
|
|
if tval.Kind() != reflect.Ptr || tval.IsNil() {
|
|
return errors.New("target must be a non-nil pointer")
|
|
}
|
|
vv, err := r.toReflectValue(v, tval.Elem().Type())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
tval.Elem().Set(vv)
|
|
return nil
|
|
}
|
|
|
|
// GlobalObject returns the global object.
|
|
func (r *Runtime) GlobalObject() *Object {
|
|
return r.globalObject
|
|
}
|
|
|
|
// Set the specified value as a property of the global object.
|
|
// The value is first converted using ToValue()
|
|
func (r *Runtime) Set(name string, value interface{}) {
|
|
r.globalObject.self.putStr(name, r.ToValue(value), false)
|
|
}
|
|
|
|
// Get the specified property of the global object.
|
|
func (r *Runtime) Get(name string) Value {
|
|
return r.globalObject.self.getStr(name)
|
|
}
|
|
|
|
// SetRandSource sets random source for this Runtime. If not called, the default math/rand is used.
|
|
func (r *Runtime) SetRandSource(source RandSource) {
|
|
r.rand = source
|
|
}
|
|
|
|
// Callable represents a JavaScript function that can be called from Go.
|
|
type Callable func(this Value, args ...Value) (Value, error)
|
|
|
|
// AssertFunction checks if the Value is a function and returns a Callable.
|
|
func AssertFunction(v Value) (Callable, bool) {
|
|
if obj, ok := v.(*Object); ok {
|
|
if f, ok := obj.self.assertCallable(); ok {
|
|
return func(this Value, args ...Value) (ret Value, err error) {
|
|
defer func() {
|
|
if x := recover(); x != nil {
|
|
if ex, ok := x.(*InterruptedError); ok {
|
|
err = ex
|
|
} else {
|
|
panic(x)
|
|
}
|
|
}
|
|
}()
|
|
ex := obj.runtime.vm.try(func() {
|
|
ret = f(FunctionCall{
|
|
This: this,
|
|
Arguments: args,
|
|
})
|
|
})
|
|
if ex != nil {
|
|
err = ex
|
|
}
|
|
obj.runtime.vm.clearStack()
|
|
return
|
|
}, true
|
|
}
|
|
}
|
|
return nil, false
|
|
}
|
|
|
|
// IsUndefined returns true if the supplied Value is undefined. Note, it checks against the real undefined, not
|
|
// against the global object's 'undefined' property.
|
|
func IsUndefined(v Value) bool {
|
|
return v == _undefined
|
|
}
|
|
|
|
// IsNull returns true if the supplied Value is null.
|
|
func IsNull(v Value) bool {
|
|
return v == _null
|
|
}
|
|
|
|
// Undefined returns JS undefined value. Note if global 'undefined' property is changed this still returns the original value.
|
|
func Undefined() Value {
|
|
return _undefined
|
|
}
|
|
|
|
// Null returns JS null value.
|
|
func Null() Value {
|
|
return _null
|
|
}
|
|
|
|
func tryFunc(f func()) (err error) {
|
|
defer func() {
|
|
if x := recover(); x != nil {
|
|
switch x := x.(type) {
|
|
case *Exception:
|
|
err = x
|
|
case *InterruptedError:
|
|
err = x
|
|
case Value:
|
|
err = &Exception{
|
|
val: x,
|
|
}
|
|
default:
|
|
panic(x)
|
|
}
|
|
}
|
|
}()
|
|
|
|
f()
|
|
|
|
return nil
|
|
}
|