mirror of
https://github.com/robertkrimen/otto
synced 2025-10-12 20:27:30 +08:00
parent
329e5afb2c
commit
5fe23327c9
276
builtin_json.go
Normal file
276
builtin_json.go
Normal file
|
@ -0,0 +1,276 @@
|
|||
package otto
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"math"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type _builtinJSON_parseContext struct {
|
||||
call FunctionCall
|
||||
reviver Value
|
||||
}
|
||||
|
||||
func builtinJSON_parse(call FunctionCall) Value {
|
||||
ctx := _builtinJSON_parseContext{
|
||||
call: call,
|
||||
}
|
||||
revive := false
|
||||
if reviver := call.Argument(1); reviver.isCallable() {
|
||||
revive = true
|
||||
ctx.reviver = reviver
|
||||
}
|
||||
|
||||
var root interface{}
|
||||
err := json.Unmarshal([]byte(toString(call.Argument(0))), &root)
|
||||
if err != nil {
|
||||
panic(newSyntaxError(err.Error()))
|
||||
}
|
||||
value, exists := builtinJSON_parseWalk(ctx, root)
|
||||
if !exists {
|
||||
value = UndefinedValue()
|
||||
}
|
||||
if revive {
|
||||
root := ctx.call.runtime.newObject()
|
||||
root.put("", value, false)
|
||||
return builtinJSON_reviveWalk(ctx, root, "")
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
func builtinJSON_reviveWalk(ctx _builtinJSON_parseContext, holder *_object, name string) Value {
|
||||
value := holder.get(name)
|
||||
if object := value._object(); object != nil {
|
||||
if isArray(object) {
|
||||
length := int64(objectLength(object))
|
||||
for index := int64(0); index < length; index += 1 {
|
||||
name := arrayIndexToString(index)
|
||||
value := builtinJSON_reviveWalk(ctx, object, name)
|
||||
if value.IsUndefined() {
|
||||
object.delete(name, false)
|
||||
} else {
|
||||
object.defineProperty(name, value, 0111, false)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
object.enumerate(false, func(name string) bool {
|
||||
value := builtinJSON_reviveWalk(ctx, object, name)
|
||||
if value.IsUndefined() {
|
||||
object.delete(name, false)
|
||||
} else {
|
||||
object.defineProperty(name, value, 0111, false)
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
}
|
||||
return ctx.reviver.call(toValue_object(holder), name, value)
|
||||
}
|
||||
|
||||
func builtinJSON_parseWalk(ctx _builtinJSON_parseContext, rawValue interface{}) (Value, bool) {
|
||||
switch value := rawValue.(type) {
|
||||
case nil:
|
||||
return NullValue(), true
|
||||
case bool:
|
||||
return toValue_bool(value), true
|
||||
case string:
|
||||
return toValue_string(value), true
|
||||
case float64:
|
||||
return toValue_float64(value), true
|
||||
case []interface{}:
|
||||
arrayValue := make([]Value, len(value))
|
||||
for index, rawValue := range value {
|
||||
if value, exists := builtinJSON_parseWalk(ctx, rawValue); exists {
|
||||
arrayValue[index] = value
|
||||
}
|
||||
}
|
||||
return toValue_object(ctx.call.runtime.newArrayOf(arrayValue)), true
|
||||
case map[string]interface{}:
|
||||
object := ctx.call.runtime.newObject()
|
||||
for name, rawValue := range value {
|
||||
if value, exists := builtinJSON_parseWalk(ctx, rawValue); exists {
|
||||
object.put(name, value, false)
|
||||
}
|
||||
}
|
||||
return toValue_object(object), true
|
||||
}
|
||||
return Value{}, false
|
||||
}
|
||||
|
||||
type _builtinJSON_stringifyContext struct {
|
||||
call FunctionCall
|
||||
stack []*_object
|
||||
propertyList []string
|
||||
replacerFunction *Value
|
||||
gap string
|
||||
}
|
||||
|
||||
func builtinJSON_stringify(call FunctionCall) Value {
|
||||
ctx := _builtinJSON_stringifyContext{
|
||||
call: call,
|
||||
stack: []*_object{nil},
|
||||
}
|
||||
replacer := call.Argument(1)._object()
|
||||
if replacer != nil {
|
||||
if isArray(replacer) {
|
||||
length := objectLength(replacer)
|
||||
seen := map[string]bool{}
|
||||
propertyList := make([]string, length)
|
||||
length = 0
|
||||
for index, _ := range propertyList {
|
||||
value := replacer.get(arrayIndexToString(int64(index)))
|
||||
switch value._valueType {
|
||||
case valueObject:
|
||||
switch value.value.(*_object).class {
|
||||
case "String":
|
||||
case "Number":
|
||||
default:
|
||||
continue
|
||||
}
|
||||
case valueString:
|
||||
case valueNumber:
|
||||
default:
|
||||
continue
|
||||
}
|
||||
name := toString(value)
|
||||
if seen[name] {
|
||||
continue
|
||||
}
|
||||
seen[name] = true
|
||||
length += 1
|
||||
propertyList[index] = name
|
||||
}
|
||||
ctx.propertyList = propertyList[0:length]
|
||||
} else if replacer.class == "Function" {
|
||||
value := toValue_object(replacer)
|
||||
ctx.replacerFunction = &value
|
||||
}
|
||||
}
|
||||
if spaceValue, exists := call.getArgument(2); exists {
|
||||
if spaceValue._valueType == valueObject {
|
||||
switch spaceValue.value.(*_object).class {
|
||||
case "String":
|
||||
spaceValue = toValue_string(toString(spaceValue))
|
||||
case "Number":
|
||||
spaceValue = toNumber(spaceValue)
|
||||
}
|
||||
}
|
||||
switch spaceValue._valueType {
|
||||
case valueString:
|
||||
value := toString(spaceValue)
|
||||
if len(value) > 10 {
|
||||
ctx.gap = value[0:10]
|
||||
} else {
|
||||
ctx.gap = value
|
||||
}
|
||||
case valueNumber:
|
||||
value := toInteger(spaceValue).value
|
||||
if value > 10 {
|
||||
value = 10
|
||||
} else if value < 0 {
|
||||
value = 0
|
||||
}
|
||||
ctx.gap = strings.Repeat(" ", int(value))
|
||||
}
|
||||
}
|
||||
holder := call.runtime.newObject()
|
||||
holder.put("", call.Argument(0), false)
|
||||
value, exists := builtinJSON_stringifyWalk(ctx, "", holder)
|
||||
if !exists {
|
||||
return UndefinedValue()
|
||||
}
|
||||
valueJSON, err := json.Marshal(value)
|
||||
if err != nil {
|
||||
panic(newTypeError(err.Error()))
|
||||
}
|
||||
if ctx.gap != "" {
|
||||
valueJSON1 := bytes.Buffer{}
|
||||
json.Indent(&valueJSON1, valueJSON, "", ctx.gap)
|
||||
valueJSON = valueJSON1.Bytes()
|
||||
}
|
||||
return toValue_string(string(valueJSON))
|
||||
}
|
||||
|
||||
func builtinJSON_stringifyWalk(ctx _builtinJSON_stringifyContext, key string, holder *_object) (interface{}, bool) {
|
||||
value := holder.get(key)
|
||||
|
||||
if value.IsObject() {
|
||||
if toJSON := value._object().get("toJSON"); toJSON.IsFunction() {
|
||||
value = toJSON.call(value, key)
|
||||
}
|
||||
}
|
||||
|
||||
if ctx.replacerFunction != nil {
|
||||
value = (*ctx.replacerFunction).call(toValue_object(holder), key, value)
|
||||
}
|
||||
|
||||
if value._valueType == valueObject {
|
||||
switch value.value.(*_object).class {
|
||||
case "Boolean":
|
||||
value = value._object().value.(Value)
|
||||
case "String":
|
||||
value = toValue_string(toString(value))
|
||||
case "Number":
|
||||
value = toNumber(value)
|
||||
}
|
||||
}
|
||||
|
||||
switch value._valueType {
|
||||
case valueBoolean:
|
||||
return toBoolean(value), true
|
||||
case valueString:
|
||||
return toString(value), true
|
||||
case valueNumber:
|
||||
value := toFloat(value)
|
||||
if math.IsNaN(value) || math.IsInf(value, 0) {
|
||||
return nil, true
|
||||
}
|
||||
return value, true
|
||||
case valueNull:
|
||||
return nil, true
|
||||
case valueObject:
|
||||
holder := value._object()
|
||||
if value := value._object(); nil != value {
|
||||
for _, object := range ctx.stack {
|
||||
if holder == object {
|
||||
panic(newTypeError("Converting circular structure to JSON"))
|
||||
}
|
||||
}
|
||||
ctx.stack = append(ctx.stack, value)
|
||||
defer func() { ctx.stack = ctx.stack[:len(ctx.stack)-1] }()
|
||||
}
|
||||
if isArray(holder) {
|
||||
length := holder.get("length").value.(uint32)
|
||||
array := make([]interface{}, length)
|
||||
for index, _ := range array {
|
||||
name := arrayIndexToString(int64(index))
|
||||
value, _ := builtinJSON_stringifyWalk(ctx, name, holder)
|
||||
array[index] = value
|
||||
}
|
||||
return array, true
|
||||
} else if holder.class != "Function" {
|
||||
object := map[string]interface{}{}
|
||||
if ctx.propertyList != nil {
|
||||
for _, name := range ctx.propertyList {
|
||||
value, exists := builtinJSON_stringifyWalk(ctx, name, holder)
|
||||
if exists {
|
||||
object[name] = value
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Go maps are without order, so this doesn't conform to the ECMA ordering
|
||||
// standard, but oh well...
|
||||
holder.enumerate(false, func(name string) bool {
|
||||
value, exists := builtinJSON_stringifyWalk(ctx, name, holder)
|
||||
if exists {
|
||||
object[name] = value
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
return object, true
|
||||
}
|
||||
}
|
||||
return nil, false
|
||||
}
|
1
clone.go
1
clone.go
|
@ -43,6 +43,7 @@ func (runtime *_runtime) clone() *_runtime {
|
|||
clone.object(runtime.Global.ReferenceError),
|
||||
clone.object(runtime.Global.SyntaxError),
|
||||
clone.object(runtime.Global.URIError),
|
||||
clone.object(runtime.Global.JSON),
|
||||
|
||||
clone.object(runtime.Global.ObjectPrototype),
|
||||
clone.object(runtime.Global.FunctionPrototype),
|
||||
|
|
|
@ -62,7 +62,7 @@ func TestGlobal(t *testing.T) {
|
|||
|
||||
test(`
|
||||
Object.getOwnPropertyNames(Function('return this')()).sort();
|
||||
`, "Array,Boolean,Date,Error,EvalError,Function,Infinity,Math,NaN,Number,Object,RangeError,ReferenceError,RegExp,String,SyntaxError,TypeError,URIError,console,decodeURI,decodeURIComponent,encodeURI,encodeURIComponent,escape,eval,isFinite,isNaN,parseFloat,parseInt,undefined,unescape")
|
||||
`, "Array,Boolean,Date,Error,EvalError,Function,Infinity,JSON,Math,NaN,Number,Object,RangeError,ReferenceError,RegExp,String,SyntaxError,TypeError,URIError,console,decodeURI,decodeURIComponent,encodeURI,encodeURIComponent,escape,eval,isFinite,isNaN,parseFloat,parseInt,undefined,unescape")
|
||||
|
||||
// __defineGetter__,__defineSetter__,__lookupGetter__,__lookupSetter__,constructor,hasOwnProperty,isPrototypeOf,propertyIsEnumerable,toLocaleString,toString,valueOf
|
||||
test(`
|
||||
|
|
16
inline
16
inline
|
@ -582,6 +582,21 @@ sub newContext {
|
|||
});
|
||||
} qw/Eval Type Range Reference Syntax URI/),
|
||||
|
||||
# JSON
|
||||
$self->block(sub {
|
||||
my $class = "JSON";
|
||||
return
|
||||
".$class =",
|
||||
$self->globalObject(
|
||||
$class,
|
||||
$self->functionDeclare(
|
||||
$class,
|
||||
"parse", 2,
|
||||
"stringify", 3,
|
||||
),
|
||||
),
|
||||
}),
|
||||
|
||||
# Global
|
||||
$self->block(sub {
|
||||
my $class = "Global";
|
||||
|
@ -618,6 +633,7 @@ sub newContext {
|
|||
"ReferenceError",
|
||||
"SyntaxError",
|
||||
"URIError",
|
||||
"JSON",
|
||||
),
|
||||
$self->property("undefined", $self->undefinedValue(), "0"),
|
||||
$self->property("NaN", $self->numberValue("math.NaN()"), "0"),
|
||||
|
|
81
inline.go
81
inline.go
|
@ -5464,6 +5464,79 @@ func _newContext(runtime *_runtime) {
|
|||
},
|
||||
}
|
||||
}
|
||||
{
|
||||
parse_function := &_object{
|
||||
runtime: runtime,
|
||||
class: "Function",
|
||||
objectClass: _classObject,
|
||||
prototype: runtime.Global.FunctionPrototype,
|
||||
extensible: true,
|
||||
property: map[string]_property{
|
||||
"length": _property{
|
||||
mode: 0,
|
||||
value: Value{
|
||||
_valueType: valueNumber,
|
||||
value: 2,
|
||||
},
|
||||
},
|
||||
},
|
||||
propertyOrder: []string{
|
||||
"length",
|
||||
},
|
||||
value: _functionObject{
|
||||
call: _nativeCallFunction(builtinJSON_parse),
|
||||
},
|
||||
}
|
||||
stringify_function := &_object{
|
||||
runtime: runtime,
|
||||
class: "Function",
|
||||
objectClass: _classObject,
|
||||
prototype: runtime.Global.FunctionPrototype,
|
||||
extensible: true,
|
||||
property: map[string]_property{
|
||||
"length": _property{
|
||||
mode: 0,
|
||||
value: Value{
|
||||
_valueType: valueNumber,
|
||||
value: 3,
|
||||
},
|
||||
},
|
||||
},
|
||||
propertyOrder: []string{
|
||||
"length",
|
||||
},
|
||||
value: _functionObject{
|
||||
call: _nativeCallFunction(builtinJSON_stringify),
|
||||
},
|
||||
}
|
||||
runtime.Global.JSON = &_object{
|
||||
runtime: runtime,
|
||||
class: "JSON",
|
||||
objectClass: _classObject,
|
||||
prototype: runtime.Global.ObjectPrototype,
|
||||
extensible: true,
|
||||
property: map[string]_property{
|
||||
"parse": _property{
|
||||
mode: 0101,
|
||||
value: Value{
|
||||
_valueType: valueObject,
|
||||
value: parse_function,
|
||||
},
|
||||
},
|
||||
"stringify": _property{
|
||||
mode: 0101,
|
||||
value: Value{
|
||||
_valueType: valueObject,
|
||||
value: stringify_function,
|
||||
},
|
||||
},
|
||||
},
|
||||
propertyOrder: []string{
|
||||
"parse",
|
||||
"stringify",
|
||||
},
|
||||
}
|
||||
}
|
||||
{
|
||||
eval_function := &_object{
|
||||
runtime: runtime,
|
||||
|
@ -5897,6 +5970,13 @@ func _newContext(runtime *_runtime) {
|
|||
value: runtime.Global.URIError,
|
||||
},
|
||||
},
|
||||
"JSON": _property{
|
||||
mode: 0101,
|
||||
value: Value{
|
||||
_valueType: valueObject,
|
||||
value: runtime.Global.JSON,
|
||||
},
|
||||
},
|
||||
"undefined": _property{
|
||||
mode: 0,
|
||||
value: Value{
|
||||
|
@ -5946,6 +6026,7 @@ func _newContext(runtime *_runtime) {
|
|||
"ReferenceError",
|
||||
"SyntaxError",
|
||||
"URIError",
|
||||
"JSON",
|
||||
"undefined",
|
||||
"NaN",
|
||||
"Infinity",
|
||||
|
|
185
json_test.go
Normal file
185
json_test.go
Normal file
|
@ -0,0 +1,185 @@
|
|||
package otto
|
||||
|
||||
import (
|
||||
. "./terst"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func BenchmarkJSON_parse(b *testing.B) {
|
||||
otto := New()
|
||||
for i := 0; i < b.N; i++ {
|
||||
otto.Run(`JSON.parse("1")`)
|
||||
otto.Run(`JSON.parse("[1,2,3]")`)
|
||||
otto.Run(`JSON.parse('{"a":{"x":100,"y":110},"b":[10,20,30],"c":"zazazaza"}')`)
|
||||
otto.Run(`JSON.parse("[1,2,3]", function(k, v) { return undefined })`)
|
||||
}
|
||||
}
|
||||
|
||||
func TestJSON_parse(t *testing.T) {
|
||||
Terst(t)
|
||||
|
||||
test := runTest()
|
||||
|
||||
test(`
|
||||
JSON.parse("1");
|
||||
`, "1")
|
||||
|
||||
test(`
|
||||
JSON.parse("null");
|
||||
`, "null")
|
||||
|
||||
test(`
|
||||
var abc = JSON.parse('"a\uFFFFbc"');
|
||||
[ abc[0], abc[2], abc[3], abc.length ];
|
||||
`, "a,b,c,4")
|
||||
|
||||
test(`
|
||||
JSON.parse("[1, 2, 3]");
|
||||
`, "1,2,3")
|
||||
|
||||
test(`
|
||||
JSON.parse('{ "abc": 1, "def":2 }').abc;
|
||||
`, "1")
|
||||
|
||||
test(`
|
||||
JSON.parse('{ "abc": { "x": 100, "y": 110 }, "def": [ 10, 20 ,30 ], "ghi": "zazazaza" }').def;
|
||||
`, "10,20,30")
|
||||
|
||||
test(`raise:
|
||||
JSON.parse("12\t\r\n 34");
|
||||
`, "SyntaxError: invalid character '3' after top-level value")
|
||||
|
||||
test(`
|
||||
JSON.parse("[1, 2, 3]", function() { return undefined });
|
||||
`, "undefined")
|
||||
|
||||
test(`raise:
|
||||
JSON.parse("");
|
||||
`, "SyntaxError: unexpected end of JSON input")
|
||||
|
||||
test(`raise:
|
||||
JSON.parse("[1, 2, 3");
|
||||
`, "SyntaxError: unexpected end of JSON input")
|
||||
|
||||
test(`raise:
|
||||
JSON.parse("[1, 2, ; abc=10");
|
||||
`, "SyntaxError: invalid character ';' looking for beginning of value")
|
||||
|
||||
test(`raise:
|
||||
JSON.parse("[1, 2, function(){}]");
|
||||
`, "SyntaxError: invalid character 'u' in literal false (expecting 'a')")
|
||||
}
|
||||
|
||||
func TestJSON_stringify(t *testing.T) {
|
||||
Terst(t)
|
||||
|
||||
test := runTest()
|
||||
|
||||
defer mockTimeLocal(time.UTC)()
|
||||
|
||||
test(`
|
||||
JSON.stringify(function(){});
|
||||
`, "undefined")
|
||||
|
||||
test(`
|
||||
JSON.stringify(new Boolean(false));
|
||||
`, "false")
|
||||
|
||||
test(`
|
||||
JSON.stringify({a1: {b1: [1,2,3,4], b2: {c1: 1, c2: 2}}, a2: 'a2'}, null, -5);
|
||||
`, `{"a1":{"b1":[1,2,3,4],"b2":{"c1":1,"c2":2}},"a2":"a2"}`)
|
||||
|
||||
test(`
|
||||
JSON.stringify(undefined);
|
||||
`, "undefined")
|
||||
|
||||
test(`
|
||||
JSON.stringify(1);
|
||||
`, "1")
|
||||
|
||||
test(`
|
||||
JSON.stringify("abc def");
|
||||
`, "\"abc def\"")
|
||||
|
||||
test(`
|
||||
JSON.stringify(3.14159);
|
||||
`, "3.14159")
|
||||
|
||||
test(`
|
||||
JSON.stringify([]);
|
||||
`, "[]")
|
||||
|
||||
test(`
|
||||
JSON.stringify([1, 2, 3]);
|
||||
`, "[1,2,3]")
|
||||
|
||||
test(`
|
||||
JSON.stringify([true, false, null]);
|
||||
`, "[true,false,null]")
|
||||
|
||||
test(`
|
||||
JSON.stringify({
|
||||
abc: { x: 100, y: 110 },
|
||||
def: [ 10, 20, 30 ],
|
||||
ghi: "zazazaza"
|
||||
});
|
||||
`, `{"abc":{"x":100,"y":110},"def":[10,20,30],"ghi":"zazazaza"}`)
|
||||
|
||||
test(`
|
||||
JSON.stringify([
|
||||
'e',
|
||||
{pluribus: 'unum'}
|
||||
], null, '\t');
|
||||
`, "[\n\t\"e\",\n\t{\n\t\t\"pluribus\": \"unum\"\n\t}\n]")
|
||||
|
||||
test(`
|
||||
JSON.stringify(new Date(0));
|
||||
`, `"1970-01-01T00:00:00.000Z"`)
|
||||
|
||||
test(`
|
||||
JSON.stringify([ new Date(0) ], function(key, value){
|
||||
return this[key] instanceof Date ? 'Date(' + this[key] + ')' : value
|
||||
});
|
||||
`, `["Date(Thu, 01 Jan 1970 00:00:00 UTC)"]`)
|
||||
|
||||
test(`
|
||||
JSON.stringify({
|
||||
abc: 1,
|
||||
def: 2,
|
||||
ghi: 3
|
||||
}, ['abc','def']);
|
||||
`, `{"abc":1,"def":2}`)
|
||||
|
||||
test(`raise:
|
||||
var abc = {
|
||||
def: null
|
||||
};
|
||||
abc.def = abc;
|
||||
JSON.stringify(abc)
|
||||
`, "TypeError: Converting circular structure to JSON")
|
||||
|
||||
test(`raise:
|
||||
var abc= [ null ];
|
||||
abc[0] = abc;
|
||||
JSON.stringify(abc);
|
||||
`, "TypeError: Converting circular structure to JSON")
|
||||
|
||||
test(`raise:
|
||||
var abc = {
|
||||
def: {}
|
||||
};
|
||||
abc.def.ghi = abc;
|
||||
JSON.stringify(abc)
|
||||
`, "TypeError: Converting circular structure to JSON")
|
||||
|
||||
test(`
|
||||
var ghi = { "pi": 3.14159 };
|
||||
var abc = {
|
||||
def: {}
|
||||
};
|
||||
abc.ghi = ghi;
|
||||
abc.def.ghi = ghi;
|
||||
JSON.stringify(abc);
|
||||
`, `{"def":{"ghi":{"pi":3.14159}},"ghi":{"pi":3.14159}}`)
|
||||
}
|
15
otto_.go
15
otto_.go
|
@ -55,14 +55,19 @@ func arrayIndexToString(index int64) string {
|
|||
return strconv.FormatInt(index, 10)
|
||||
}
|
||||
|
||||
func valueOfArrayIndex(list []Value, index int) Value {
|
||||
if index >= 0 && index < len(list) {
|
||||
value := list[index]
|
||||
func valueOfArrayIndex(array []Value, index int) Value {
|
||||
value, _ := getValueOfArrayIndex(array, index)
|
||||
return value
|
||||
}
|
||||
|
||||
func getValueOfArrayIndex(array []Value, index int) (Value, bool) {
|
||||
if index >= 0 && index < len(array) {
|
||||
value := array[index]
|
||||
if !value.isEmpty() {
|
||||
return value
|
||||
return value, true
|
||||
}
|
||||
}
|
||||
return UndefinedValue()
|
||||
return UndefinedValue(), false
|
||||
}
|
||||
|
||||
// A range index can be anything from 0 up to length. It is NOT safe to use as an index
|
||||
|
|
|
@ -22,6 +22,7 @@ type _global struct {
|
|||
ReferenceError *_object
|
||||
SyntaxError *_object
|
||||
URIError *_object
|
||||
JSON *_object
|
||||
|
||||
ObjectPrototype *_object // Object.prototype
|
||||
FunctionPrototype *_object // Function.prototype
|
||||
|
|
|
@ -276,6 +276,10 @@ func (self FunctionCall) Argument(index int) Value {
|
|||
return valueOfArrayIndex(self.ArgumentList, index)
|
||||
}
|
||||
|
||||
func (self FunctionCall) getArgument(index int) (Value, bool) {
|
||||
return getValueOfArrayIndex(self.ArgumentList, index)
|
||||
}
|
||||
|
||||
func (self FunctionCall) slice(index int) []Value {
|
||||
if index < len(self.ArgumentList) {
|
||||
return self.ArgumentList[index:]
|
||||
|
|
Loading…
Reference in New Issue
Block a user