From 44400d2d2272117c8bf04499e7a78f4d5aa23765 Mon Sep 17 00:00:00 2001 From: Dmitry Panov Date: Sat, 5 Dec 2020 16:24:14 +0000 Subject: [PATCH 1/7] Do not create an object if a native constructor is called without 'new'. Closes #228. --- README.md | 30 ++--------------------------- func.go | 3 ++- object.go | 1 + runtime.go | 50 +++++++++++++++++++++++++++++++++++++++++++++---- runtime_test.go | 39 ++++++++++++++++++++++++++++++++++++++ 5 files changed, 90 insertions(+), 33 deletions(-) diff --git a/README.md b/README.md index 6a492be9..a2668a03 100644 --- a/README.md +++ b/README.md @@ -212,34 +212,8 @@ There are two standard mappers: [TagFieldNameMapper](https://godoc.org/github.co Native Constructors ------------------- -In order to implement a constructor function in Go: -```go -func MyObject(call goja.ConstructorCall) *Object { - // call.This contains the newly created object as per http://www.ecma-international.org/ecma-262/5.1/index.html#sec-13.2.2 - // call.Arguments contain arguments passed to the function - - call.This.Set("method", method) - - //... - - // If return value is a non-nil *Object, it will be used instead of call.This - // This way it is possible to return a Go struct or a map converted - // into goja.Value using runtime.ToValue(), however in this case - // instanceof will not work as expected. - return nil -} - -runtime.Set("MyObject", MyObject) - -``` - -Then it can be used in JS as follows: - -```js -var o = new MyObject(arg); -var o1 = MyObject(arg); // same thing -o instanceof MyObject && o1 instanceof MyObject; // true -``` +In order to implement a constructor function in Go use `func (goja.ConstructorCall) *goja.Object`. +See [Runtime.ToValue()](https://godoc.org/github.com/dop251/goja#Runtime.ToValue) documentation for more details. Regular Expressions ------------------- diff --git a/func.go b/func.go index 7f4931e6..7b3a8999 100644 --- a/func.go +++ b/func.go @@ -215,7 +215,7 @@ func (f *baseFuncObject) hasInstance(v Value) bool { return false } -func (f *nativeFuncObject) defaultConstruct(ccall func(ConstructorCall) *Object, args []Value) *Object { +func (f *nativeFuncObject) defaultConstruct(ccall func(ConstructorCall) *Object, args []Value, newTarget *Object) *Object { proto := f.getStr("prototype", nil) var protoObj *Object if p, ok := proto.(*Object); ok { @@ -227,6 +227,7 @@ func (f *nativeFuncObject) defaultConstruct(ccall func(ConstructorCall) *Object, ret := ccall(ConstructorCall{ This: obj, Arguments: args, + NewTarget: newTarget, }) if ret != nil { diff --git a/object.go b/object.go index 4cefb551..944b2819 100644 --- a/object.go +++ b/object.go @@ -295,6 +295,7 @@ type FunctionCall struct { type ConstructorCall struct { This *Object Arguments []Value + NewTarget *Object } func (f FunctionCall) Argument(idx int) Value { diff --git a/runtime.go b/runtime.go index f33c7c96..fa88cb84 100644 --- a/runtime.go +++ b/runtime.go @@ -520,11 +520,22 @@ func (r *Runtime) newNativeConstructor(call func(ConstructorCall) *Object, name } f.f = func(c FunctionCall) Value { - return f.defaultConstruct(call, c.Arguments) + thisObj, _ := c.This.(*Object) + if thisObj != nil { + res := call(ConstructorCall{ + This: thisObj, + Arguments: c.Arguments, + }) + if res == nil { + return _undefined + } + return res + } + return f.defaultConstruct(call, c.Arguments, nil) } - f.construct = func(args []Value, proto *Object) *Object { - return f.defaultConstruct(call, args) + f.construct = func(args []Value, newTarget *Object) *Object { + return f.defaultConstruct(call, args, newTarget) } v.self = f @@ -1270,7 +1281,38 @@ Nil is converted to null. Functions func(FunctionCall) Value is treated as a native JavaScript function. This increases performance because there are no -automatic argument and return value type conversions (which involves reflect). +automatic argument and return value type conversions (which involves reflect). Attempting to use +the function as a constructor will result in a TypeError. + +func(ConstructorCall) *Object is treated as a native constructor, allowing to use it with the new +operator: + + func MyObject(call goja.ConstructorCall) *goja.Object { + // call.This contains the newly created object as per http://www.ecma-international.org/ecma-262/5.1/index.html#sec-13.2.2 + // call.Arguments contain arguments passed to the function + + call.This.Set("method", method) + + //... + + // If return value is a non-nil *Object, it will be used instead of call.This + // This way it is possible to return a Go struct or a map converted + // into goja.Value using runtime.ToValue(), however in this case + // instanceof will not work as expected. + return nil + } + + runtime.Set("MyObject", MyObject) + +Then it can be used in JS as follows: + + var o = new MyObject(arg); + var o1 = MyObject(arg); // same thing + o instanceof MyObject && o1 instanceof MyObject; // true + +When a native constructor is called directory (without the new operator) its behavior depends on +this value: if it's an Object, it is passed through, otherwise a new one is created exactly as +if it was called with the new operator. In either case call.NewTarget will be nil. Any other Go function is wrapped so that the arguments are automatically converted into the required Go types and the return value is converted to a JavaScript value (using this method). If conversion is not possible, a TypeError is diff --git a/runtime_test.go b/runtime_test.go index ac24b3c7..80822b71 100644 --- a/runtime_test.go +++ b/runtime_test.go @@ -1713,6 +1713,45 @@ func TestNativeCtorNewTarget(t *testing.T) { testScript1(SCRIPT, valueTrue, t) } +func TestNativeCtorNonNewCall(t *testing.T) { + vm := New() + vm.Set(`Animal`, func(call ConstructorCall) *Object { + obj := call.This + obj.Set(`name`, call.Argument(0).String()) + obj.Set(`eat`, func(call FunctionCall) Value { + self := call.This.(*Object) + return vm.ToValue(fmt.Sprintf("%s eat", self.Get(`name`))) + }) + return nil + }) + v, err := vm.RunString(` + + function __extends(d, b){ + function __() { + this.constructor = d; + } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + } + + var Cat = (function (_super) { + __extends(Cat, _super); + function Cat() { + return _super.call(this, "cat") || this; + } + return Cat; + }(Animal)); + + var cat = new Cat(); + cat instanceof Cat && cat.eat() === "cat eat"; + `) + if err != nil { + t.Fatal(err) + } + if v != valueTrue { + t.Fatal(v) + } +} + /* func TestArrayConcatSparse(t *testing.T) { function foo(a,b,c) From ea06c24ab383e319513cacbc469b001470c32034 Mon Sep 17 00:00:00 2001 From: Dmitry Panov Date: Sat, 5 Dec 2020 16:28:32 +0000 Subject: [PATCH 2/7] Fixed documentation --- runtime.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime.go b/runtime.go index fa88cb84..058eff9f 100644 --- a/runtime.go +++ b/runtime.go @@ -1310,7 +1310,7 @@ Then it can be used in JS as follows: var o1 = MyObject(arg); // same thing o instanceof MyObject && o1 instanceof MyObject; // true -When a native constructor is called directory (without the new operator) its behavior depends on +When a native constructor is called directly (without the new operator) its behavior depends on this value: if it's an Object, it is passed through, otherwise a new one is created exactly as if it was called with the new operator. In either case call.NewTarget will be nil. From 6060b0671c09a09fbe8a33e29dfdbae33f85a640 Mon Sep 17 00:00:00 2001 From: Dmitry Panov Date: Mon, 7 Dec 2020 17:24:45 +0000 Subject: [PATCH 3/7] Exposed Symbol --- builtin_array.go | 16 +++--- builtin_date.go | 2 +- builtin_function.go | 2 +- builtin_json.go | 2 +- builtin_map.go | 8 +-- builtin_math.go | 2 +- builtin_object.go | 2 +- builtin_proxy.go | 6 +-- builtin_regexp.go | 10 ++-- builtin_set.go | 8 +-- builtin_string.go | 20 ++++---- builtin_symbol.go | 66 ++++++++++++------------ builtin_typedarrays.go | 14 +++--- builtin_weakmap.go | 2 +- builtin_weakset.go | 2 +- map_test.go | 4 +- object.go | 58 ++++++++++----------- object_lazy.go | 18 +++---- proxy.go | 24 ++++----- runtime.go | 12 ++--- runtime_test.go | 49 ++++++++++++++++++ value.go | 111 +++++++++++++++++++++++++++++++++-------- vm.go | 4 +- 23 files changed, 280 insertions(+), 162 deletions(-) diff --git a/builtin_array.go b/builtin_array.go index 64e49d76..27a896a1 100644 --- a/builtin_array.go +++ b/builtin_array.go @@ -38,7 +38,7 @@ func arraySpeciesCreate(obj *Object, size int64) *Object { if isArray(obj) { v := obj.self.getStr("constructor", nil) if constructObj, ok := v.(*Object); ok { - v = constructObj.self.getSym(symSpecies, nil) + v = constructObj.self.getSym(SymSpecies, nil) if v == _null { v = nil } @@ -256,7 +256,7 @@ func (r *Runtime) arrayproto_toLocaleString(call FunctionCall) Value { } func isConcatSpreadable(obj *Object) bool { - spreadable := obj.self.getSym(symIsConcatSpreadable, nil) + spreadable := obj.self.getSym(SymIsConcatSpreadable, nil) if spreadable != nil && spreadable != _undefined { return spreadable.ToBoolean() } @@ -1045,7 +1045,7 @@ func (r *Runtime) checkStdArray(v Value) *arrayObject { func (r *Runtime) checkStdArrayIter(v Value) *arrayObject { if arr := r.checkStdArray(v); arr != nil && - arr.getSym(symIterator, nil) == r.global.arrayValues { + arr.getSym(SymIterator, nil) == r.global.arrayValues { return arr } @@ -1084,7 +1084,7 @@ func (r *Runtime) array_from(call FunctionCall) Value { } } var arr *Object - if usingIterator := toMethod(r.getV(items, symIterator)); usingIterator != nil { + if usingIterator := toMethod(r.getV(items, SymIterator)); usingIterator != nil { if ctor != nil { arr = ctor([]Value{}, nil) } else { @@ -1228,7 +1228,7 @@ func (r *Runtime) createArrayProto(val *Object) objectImpl { r.global.arrayValues = valuesFunc o._putProp("values", valuesFunc, true, false, true) - o._putSym(symIterator, valueProp(valuesFunc, true, false, true)) + o._putSym(SymIterator, valueProp(valuesFunc, true, false, true)) bl := r.newBaseObject(nil, classObject) bl.setOwnStr("copyWithin", valueTrue, true) @@ -1239,7 +1239,7 @@ func (r *Runtime) createArrayProto(val *Object) objectImpl { bl.setOwnStr("includes", valueTrue, true) bl.setOwnStr("keys", valueTrue, true) bl.setOwnStr("values", valueTrue, true) - o._putSym(symUnscopables, valueProp(bl.val, false, false, true)) + o._putSym(SymUnscopables, valueProp(bl.val, false, false, true)) return o } @@ -1249,7 +1249,7 @@ func (r *Runtime) createArray(val *Object) objectImpl { o._putProp("from", r.newNativeFunc(r.array_from, nil, "from", nil, 1), true, false, true) o._putProp("isArray", r.newNativeFunc(r.array_isArray, nil, "isArray", nil, 1), true, false, true) o._putProp("of", r.newNativeFunc(r.array_of, nil, "of", nil, 0), true, false, true) - o._putSym(symSpecies, &valueProperty{ + o._putSym(SymSpecies, &valueProperty{ getterFunc: r.newNativeFunc(r.returnThis, nil, "get [Symbol.species]", nil, 0), accessor: true, configurable: true, @@ -1262,7 +1262,7 @@ func (r *Runtime) createArrayIterProto(val *Object) objectImpl { o := newBaseObjectObj(val, r.global.IteratorPrototype, classObject) o._putProp("next", r.newNativeFunc(r.arrayIterProto_next, nil, "next", nil, 0), true, false, true) - o._putSym(symToStringTag, valueProp(asciiString(classArrayIterator), false, false, true)) + o._putSym(SymToStringTag, valueProp(asciiString(classArrayIterator), false, false, true)) return o } diff --git a/builtin_date.go b/builtin_date.go index ab24235d..793b6b28 100644 --- a/builtin_date.go +++ b/builtin_date.go @@ -991,7 +991,7 @@ func (r *Runtime) createDateProto(val *Object) objectImpl { o._putProp("toISOString", r.newNativeFunc(r.dateproto_toISOString, nil, "toISOString", nil, 0), true, false, true) o._putProp("toJSON", r.newNativeFunc(r.dateproto_toJSON, nil, "toJSON", nil, 1), true, false, true) - o._putSym(symToPrimitive, valueProp(r.newNativeFunc(r.dateproto_toPrimitive, nil, "[Symbol.toPrimitive]", nil, 1), false, false, true)) + o._putSym(SymToPrimitive, valueProp(r.newNativeFunc(r.dateproto_toPrimitive, nil, "[Symbol.toPrimitive]", nil, 1), false, false, true)) return o } diff --git a/builtin_function.go b/builtin_function.go index c8a91f8b..e595675f 100644 --- a/builtin_function.go +++ b/builtin_function.go @@ -189,7 +189,7 @@ func (r *Runtime) initFunction() { o._putProp("bind", r.newNativeFunc(r.functionproto_bind, nil, "bind", nil, 1), true, false, true) o._putProp("call", r.newNativeFunc(r.functionproto_call, nil, "call", nil, 1), true, false, true) o._putProp("toString", r.newNativeFunc(r.functionproto_toString, nil, "toString", nil, 0), true, false, true) - o._putSym(symHasInstance, valueProp(r.newNativeFunc(r.functionproto_hasInstance, nil, "[Symbol.hasInstance]", nil, 1), false, false, false)) + o._putSym(SymHasInstance, valueProp(r.newNativeFunc(r.functionproto_hasInstance, nil, "[Symbol.hasInstance]", nil, 1), false, false, false)) r.global.Function = r.newNativeFuncConstruct(r.builtin_Function, "Function", r.global.FunctionPrototype, 1) r.addToGlobal("Function", r.global.Function) diff --git a/builtin_json.go b/builtin_json.go index 2d2e9e74..172d3329 100644 --- a/builtin_json.go +++ b/builtin_json.go @@ -502,7 +502,7 @@ func (r *Runtime) initJSON() { JSON := r.newBaseObject(r.global.ObjectPrototype, "JSON") JSON._putProp("parse", r.newNativeFunc(r.builtinJSON_parse, nil, "parse", nil, 2), true, false, true) JSON._putProp("stringify", r.newNativeFunc(r.builtinJSON_stringify, nil, "stringify", nil, 3), true, false, true) - JSON._putSym(symToStringTag, valueProp(asciiString(classJSON), false, false, true)) + JSON._putSym(SymToStringTag, valueProp(asciiString(classJSON), false, false, true)) r.addToGlobal("JSON", JSON.val) } diff --git a/builtin_map.go b/builtin_map.go index 20dc414d..097dbff6 100644 --- a/builtin_map.go +++ b/builtin_map.go @@ -235,15 +235,15 @@ func (r *Runtime) createMapProto(val *Object) objectImpl { entriesFunc := r.newNativeFunc(r.mapProto_entries, nil, "entries", nil, 0) o._putProp("entries", entriesFunc, true, false, true) - o._putSym(symIterator, valueProp(entriesFunc, true, false, true)) - o._putSym(symToStringTag, valueProp(asciiString(classMap), false, false, true)) + o._putSym(SymIterator, valueProp(entriesFunc, true, false, true)) + o._putSym(SymToStringTag, valueProp(asciiString(classMap), false, false, true)) return o } func (r *Runtime) createMap(val *Object) objectImpl { o := r.newNativeConstructOnly(val, r.builtin_newMap, r.global.MapPrototype, "Map", 0) - o._putSym(symSpecies, &valueProperty{ + o._putSym(SymSpecies, &valueProperty{ getterFunc: r.newNativeFunc(r.returnThis, nil, "get [Symbol.species]", nil, 0), accessor: true, configurable: true, @@ -256,7 +256,7 @@ func (r *Runtime) createMapIterProto(val *Object) objectImpl { o := newBaseObjectObj(val, r.global.IteratorPrototype, classObject) o._putProp("next", r.newNativeFunc(r.mapIterProto_next, nil, "next", nil, 0), true, false, true) - o._putSym(symToStringTag, valueProp(asciiString(classMapIterator), false, false, true)) + o._putSym(SymToStringTag, valueProp(asciiString(classMapIterator), false, false, true)) return o } diff --git a/builtin_math.go b/builtin_math.go index 90866c44..11439c0f 100644 --- a/builtin_math.go +++ b/builtin_math.go @@ -282,7 +282,7 @@ func (r *Runtime) createMath(val *Object) objectImpl { m._putProp("PI", valueFloat(math.Pi), false, false, false) m._putProp("SQRT1_2", valueFloat(sqrt1_2), false, false, false) m._putProp("SQRT2", valueFloat(math.Sqrt2), false, false, false) - m._putSym(symToStringTag, valueProp(asciiString(classMath), false, false, true)) + m._putSym(SymToStringTag, valueProp(asciiString(classMath), false, false, true)) m._putProp("abs", r.newNativeFunc(r.math_abs, nil, "abs", nil, 1), true, false, true) m._putProp("acos", r.newNativeFunc(r.math_acos, nil, "acos", nil, 1), true, false, true) diff --git a/builtin_object.go b/builtin_object.go index 1ca89ac1..75efb46a 100644 --- a/builtin_object.go +++ b/builtin_object.go @@ -410,7 +410,7 @@ func (r *Runtime) objectproto_toString(call FunctionCall) Value { } else { clsName = obj.self.className() } - if tag := obj.self.getSym(symToStringTag, nil); tag != nil { + if tag := obj.self.getSym(SymToStringTag, nil); tag != nil { if str, ok := tag.(valueString); ok { clsName = str.String() } diff --git a/builtin_proxy.go b/builtin_proxy.go index a4236ac7..268f507e 100644 --- a/builtin_proxy.go +++ b/builtin_proxy.go @@ -73,7 +73,7 @@ func (r *Runtime) proxyproto_nativehandler_getOwnPropertyDescriptor(native func( if len(call.Arguments) >= 2 { if t, ok := call.Argument(0).(*Object); ok { switch p := call.Argument(1).(type) { - case *valueSymbol: + case *Symbol: return _undefined default: desc := native(t, p.String()) @@ -109,7 +109,7 @@ func (r *Runtime) proxyproto_nativehandler_gen_obj_string_bool(name proxyTrap, n if len(call.Arguments) >= 2 { if t, ok := call.Argument(0).(*Object); ok { switch p := call.Argument(1).(type) { - case *valueSymbol: + case *Symbol: return valueFalse default: o := native(t, p.String()) @@ -129,7 +129,7 @@ func (r *Runtime) proxyproto_nativehandler_get(native func(*Object, string, *Obj if t, ok := call.Argument(0).(*Object); ok { if r, ok := call.Argument(2).(*Object); ok { switch p := call.Argument(1).(type) { - case *valueSymbol: + case *Symbol: return _undefined default: return native(t, p.String(), r) diff --git a/builtin_regexp.go b/builtin_regexp.go index b79c99d0..014dfab9 100644 --- a/builtin_regexp.go +++ b/builtin_regexp.go @@ -1144,15 +1144,15 @@ func (r *Runtime) initRegExp() { accessor: true, }, false) - o._putSym(symMatch, valueProp(r.newNativeFunc(r.regexpproto_stdMatcher, nil, "[Symbol.match]", nil, 1), true, false, true)) - o._putSym(symSearch, valueProp(r.newNativeFunc(r.regexpproto_stdSearch, nil, "[Symbol.search]", nil, 1), true, false, true)) - o._putSym(symSplit, valueProp(r.newNativeFunc(r.regexpproto_stdSplitter, nil, "[Symbol.split]", nil, 2), true, false, true)) - o._putSym(symReplace, valueProp(r.newNativeFunc(r.regexpproto_stdReplacer, nil, "[Symbol.replace]", nil, 2), true, false, true)) + o._putSym(SymMatch, valueProp(r.newNativeFunc(r.regexpproto_stdMatcher, nil, "[Symbol.match]", nil, 1), true, false, true)) + o._putSym(SymSearch, valueProp(r.newNativeFunc(r.regexpproto_stdSearch, nil, "[Symbol.search]", nil, 1), true, false, true)) + o._putSym(SymSplit, valueProp(r.newNativeFunc(r.regexpproto_stdSplitter, nil, "[Symbol.split]", nil, 2), true, false, true)) + o._putSym(SymReplace, valueProp(r.newNativeFunc(r.regexpproto_stdReplacer, nil, "[Symbol.replace]", nil, 2), true, false, true)) o.guard("exec", "global", "multiline", "ignoreCase", "unicode", "sticky") r.global.RegExp = r.newNativeFunc(r.builtin_RegExp, r.builtin_newRegExp, "RegExp", r.global.RegExpPrototype, 2) rx := r.global.RegExp.self - rx._putSym(symSpecies, &valueProperty{ + rx._putSym(SymSpecies, &valueProperty{ getterFunc: r.newNativeFunc(r.returnThis, nil, "get [Symbol.species]", nil, 0), accessor: true, configurable: true, diff --git a/builtin_set.go b/builtin_set.go index b3818b25..4a265405 100644 --- a/builtin_set.go +++ b/builtin_set.go @@ -210,15 +210,15 @@ func (r *Runtime) createSetProto(val *Object) objectImpl { o._putProp("values", valuesFunc, true, false, true) o._putProp("keys", valuesFunc, true, false, true) o._putProp("entries", r.newNativeFunc(r.setProto_entries, nil, "entries", nil, 0), true, false, true) - o._putSym(symIterator, valueProp(valuesFunc, true, false, true)) - o._putSym(symToStringTag, valueProp(asciiString(classSet), false, false, true)) + o._putSym(SymIterator, valueProp(valuesFunc, true, false, true)) + o._putSym(SymToStringTag, valueProp(asciiString(classSet), false, false, true)) return o } func (r *Runtime) createSet(val *Object) objectImpl { o := r.newNativeConstructOnly(val, r.builtin_newSet, r.global.SetPrototype, "Set", 0) - o._putSym(symSpecies, &valueProperty{ + o._putSym(SymSpecies, &valueProperty{ getterFunc: r.newNativeFunc(r.returnThis, nil, "get [Symbol.species]", nil, 0), accessor: true, configurable: true, @@ -231,7 +231,7 @@ func (r *Runtime) createSetIterProto(val *Object) objectImpl { o := newBaseObjectObj(val, r.global.IteratorPrototype, classObject) o._putProp("next", r.newNativeFunc(r.setIterProto_next, nil, "next", nil, 0), true, false, true) - o._putSym(symToStringTag, valueProp(asciiString(classSetIterator), false, false, true)) + o._putSym(SymToStringTag, valueProp(asciiString(classSetIterator), false, false, true)) return o } diff --git a/builtin_string.go b/builtin_string.go index b4484b3c..0ce00321 100644 --- a/builtin_string.go +++ b/builtin_string.go @@ -26,8 +26,8 @@ func toString(arg Value) valueString { if s, ok := arg.(valueString); ok { return s } - if s, ok := arg.(*valueSymbol); ok { - return s.desc + if s, ok := arg.(*Symbol); ok { + return s.descriptiveString() } return arg.toString() } @@ -344,7 +344,7 @@ func (r *Runtime) stringproto_match(call FunctionCall) Value { r.checkObjectCoercible(call.This) regexp := call.Argument(0) if regexp != _undefined && regexp != _null { - if matcher := toMethod(r.getV(regexp, symMatch)); matcher != nil { + if matcher := toMethod(r.getV(regexp, SymMatch)); matcher != nil { return matcher(FunctionCall{ This: regexp, Arguments: []Value{call.This}, @@ -361,7 +361,7 @@ func (r *Runtime) stringproto_match(call FunctionCall) Value { rx = r.newRegExp(regexp, nil, r.global.RegExpPrototype).self.(*regexpObject) } - if matcher, ok := r.toObject(rx.getSym(symMatch, nil)).self.assertCallable(); ok { + if matcher, ok := r.toObject(rx.getSym(SymMatch, nil)).self.assertCallable(); ok { return matcher(FunctionCall{ This: rx.val, Arguments: []Value{call.This.toString()}, @@ -624,7 +624,7 @@ func (r *Runtime) stringproto_replace(call FunctionCall) Value { searchValue := call.Argument(0) replaceValue := call.Argument(1) if searchValue != _undefined && searchValue != _null { - if replacer := toMethod(r.getV(searchValue, symReplace)); replacer != nil { + if replacer := toMethod(r.getV(searchValue, SymReplace)); replacer != nil { return replacer(FunctionCall{ This: searchValue, Arguments: []Value{call.This, replaceValue}, @@ -648,7 +648,7 @@ func (r *Runtime) stringproto_search(call FunctionCall) Value { r.checkObjectCoercible(call.This) regexp := call.Argument(0) if regexp != _undefined && regexp != _null { - if searcher := toMethod(r.getV(regexp, symSearch)); searcher != nil { + if searcher := toMethod(r.getV(regexp, SymSearch)); searcher != nil { return searcher(FunctionCall{ This: regexp, Arguments: []Value{call.This}, @@ -665,7 +665,7 @@ func (r *Runtime) stringproto_search(call FunctionCall) Value { rx = r.newRegExp(regexp, nil, r.global.RegExpPrototype).self.(*regexpObject) } - if searcher, ok := r.toObject(rx.getSym(symSearch, nil)).self.assertCallable(); ok { + if searcher, ok := r.toObject(rx.getSym(SymSearch, nil)).self.assertCallable(); ok { return searcher(FunctionCall{ This: rx.val, Arguments: []Value{call.This.toString()}, @@ -721,7 +721,7 @@ func (r *Runtime) stringproto_split(call FunctionCall) Value { separatorValue := call.Argument(0) limitValue := call.Argument(1) if separatorValue != _undefined && separatorValue != _null { - if splitter := toMethod(r.getV(separatorValue, symSplit)); splitter != nil { + if splitter := toMethod(r.getV(separatorValue, SymSplit)); splitter != nil { return splitter(FunctionCall{ This: separatorValue, Arguments: []Value{call.This, limitValue}, @@ -902,7 +902,7 @@ func (r *Runtime) createStringIterProto(val *Object) objectImpl { o := newBaseObjectObj(val, r.global.IteratorPrototype, classObject) o._putProp("next", r.newNativeFunc(r.stringIterProto_next, nil, "next", nil, 0), true, false, true) - o._putSym(symToStringTag, valueProp(asciiString(classStringIterator), false, false, true)) + o._putSym(SymToStringTag, valueProp(asciiString(classStringIterator), false, false, true)) return o } @@ -942,7 +942,7 @@ func (r *Runtime) initString() { o._putProp("trimStart", r.newNativeFunc(r.stringproto_trimStart, nil, "trimStart", nil, 0), true, false, true) o._putProp("valueOf", r.newNativeFunc(r.stringproto_valueOf, nil, "valueOf", nil, 0), true, false, true) - o._putSym(symIterator, valueProp(r.newNativeFunc(r.stringproto_iterator, nil, "[Symbol.iterator]", nil, 0), true, false, true)) + o._putSym(SymIterator, valueProp(r.newNativeFunc(r.stringproto_iterator, nil, "[Symbol.iterator]", nil, 0), true, false, true)) // Annex B o._putProp("substr", r.newNativeFunc(r.stringproto_substr, nil, "substr", nil, 2), true, false, true) diff --git a/builtin_symbol.go b/builtin_symbol.go index 1e90a824..4dd5d479 100644 --- a/builtin_symbol.go +++ b/builtin_symbol.go @@ -3,17 +3,17 @@ package goja import "github.com/dop251/goja/unistring" var ( - symHasInstance = newSymbol(asciiString("Symbol.hasInstance")) - symIsConcatSpreadable = newSymbol(asciiString("Symbol.isConcatSpreadable")) - symIterator = newSymbol(asciiString("Symbol.iterator")) - symMatch = newSymbol(asciiString("Symbol.match")) - symReplace = newSymbol(asciiString("Symbol.replace")) - symSearch = newSymbol(asciiString("Symbol.search")) - symSpecies = newSymbol(asciiString("Symbol.species")) - symSplit = newSymbol(asciiString("Symbol.split")) - symToPrimitive = newSymbol(asciiString("Symbol.toPrimitive")) - symToStringTag = newSymbol(asciiString("Symbol.toStringTag")) - symUnscopables = newSymbol(asciiString("Symbol.unscopables")) + SymHasInstance = newSymbol(asciiString("Symbol.hasInstance")) + SymIsConcatSpreadable = newSymbol(asciiString("Symbol.isConcatSpreadable")) + SymIterator = newSymbol(asciiString("Symbol.iterator")) + SymMatch = newSymbol(asciiString("Symbol.match")) + SymReplace = newSymbol(asciiString("Symbol.replace")) + SymSearch = newSymbol(asciiString("Symbol.search")) + SymSpecies = newSymbol(asciiString("Symbol.species")) + SymSplit = newSymbol(asciiString("Symbol.split")) + SymToPrimitive = newSymbol(asciiString("Symbol.toPrimitive")) + SymToStringTag = newSymbol(asciiString("Symbol.toStringTag")) + SymUnscopables = newSymbol(asciiString("Symbol.unscopables")) ) func (r *Runtime) builtin_symbol(call FunctionCall) Value { @@ -27,11 +27,11 @@ func (r *Runtime) builtin_symbol(call FunctionCall) Value { } func (r *Runtime) symbolproto_tostring(call FunctionCall) Value { - sym, ok := call.This.(*valueSymbol) + sym, ok := call.This.(*Symbol) if !ok { if obj, ok := call.This.(*Object); ok { if v, ok := obj.self.(*primitiveValueObject); ok { - if sym1, ok := v.pValue.(*valueSymbol); ok { + if sym1, ok := v.pValue.(*Symbol); ok { sym = sym1 } } @@ -40,18 +40,18 @@ func (r *Runtime) symbolproto_tostring(call FunctionCall) Value { if sym == nil { panic(r.NewTypeError("Method Symbol.prototype.toString is called on incompatible receiver")) } - return sym.desc + return sym.descriptiveString() } func (r *Runtime) symbolproto_valueOf(call FunctionCall) Value { - _, ok := call.This.(*valueSymbol) + _, ok := call.This.(*Symbol) if ok { return call.This } if obj, ok := call.This.(*Object); ok { if v, ok := obj.self.(*primitiveValueObject); ok { - if sym, ok := v.pValue.(*valueSymbol); ok { + if sym, ok := v.pValue.(*Symbol); ok { return sym } } @@ -67,7 +67,7 @@ func (r *Runtime) symbol_for(call FunctionCall) Value { return v } if r.symbolRegistry == nil { - r.symbolRegistry = make(map[unistring.String]*valueSymbol) + r.symbolRegistry = make(map[unistring.String]*Symbol) } v := newSymbol(key) r.symbolRegistry[keyStr] = v @@ -76,7 +76,7 @@ func (r *Runtime) symbol_for(call FunctionCall) Value { func (r *Runtime) symbol_keyfor(call FunctionCall) Value { arg := call.Argument(0) - sym, ok := arg.(*valueSymbol) + sym, ok := arg.(*Symbol) if !ok { panic(r.NewTypeError("%s is not a symbol", arg.String())) } @@ -100,8 +100,8 @@ func (r *Runtime) createSymbolProto(val *Object) objectImpl { o._putProp("constructor", r.global.Symbol, true, false, true) o._putProp("toString", r.newNativeFunc(r.symbolproto_tostring, nil, "toString", nil, 0), true, false, true) o._putProp("valueOf", r.newNativeFunc(r.symbolproto_valueOf, nil, "valueOf", nil, 0), true, false, true) - o._putSym(symToPrimitive, valueProp(r.newNativeFunc(r.symbolproto_valueOf, nil, "[Symbol.toPrimitive]", nil, 1), false, false, true)) - o._putSym(symToStringTag, valueProp(newStringValue("Symbol"), false, false, true)) + o._putSym(SymToPrimitive, valueProp(r.newNativeFunc(r.symbolproto_valueOf, nil, "[Symbol.toPrimitive]", nil, 1), false, false, true)) + o._putSym(SymToStringTag, valueProp(newStringValue("Symbol"), false, false, true)) return o } @@ -112,21 +112,21 @@ func (r *Runtime) createSymbol(val *Object) objectImpl { o._putProp("for", r.newNativeFunc(r.symbol_for, nil, "for", nil, 1), true, false, true) o._putProp("keyFor", r.newNativeFunc(r.symbol_keyfor, nil, "keyFor", nil, 1), true, false, true) - for _, s := range []*valueSymbol{ - symHasInstance, - symIsConcatSpreadable, - symIterator, - symMatch, - symReplace, - symSearch, - symSpecies, - symSplit, - symToPrimitive, - symToStringTag, - symUnscopables, + for _, s := range []*Symbol{ + SymHasInstance, + SymIsConcatSpreadable, + SymIterator, + SymMatch, + SymReplace, + SymSearch, + SymSpecies, + SymSplit, + SymToPrimitive, + SymToStringTag, + SymUnscopables, } { n := s.desc.(asciiString) - n = n[len("Symbol(Symbol.") : len(n)-1] + n = n[len("Symbol."):] o._putProp(unistring.String(n), s, false, false, false) } diff --git a/builtin_typedarrays.go b/builtin_typedarrays.go index 32bfafbf..05fc37f0 100644 --- a/builtin_typedarrays.go +++ b/builtin_typedarrays.go @@ -1090,7 +1090,7 @@ func (r *Runtime) typedArrayFrom(ctor, items *Object, mapFn, thisValue Value) *O thisValue = _undefined } } - usingIter := toMethod(items.self.getSym(symIterator, nil)) + usingIter := toMethod(items.self.getSym(SymIterator, nil)) if usingIter != nil { iter := r.getIterator(items, usingIter) var values []Value @@ -1251,14 +1251,14 @@ func (r *Runtime) createArrayBufferProto(val *Object) objectImpl { b._put("byteLength", byteLengthProp) b._putProp("constructor", r.global.ArrayBuffer, true, false, true) b._putProp("slice", r.newNativeFunc(r.arrayBufferProto_slice, nil, "slice", nil, 2), true, false, true) - b._putSym(symToStringTag, valueProp(asciiString("ArrayBuffer"), false, false, true)) + b._putSym(SymToStringTag, valueProp(asciiString("ArrayBuffer"), false, false, true)) return b } func (r *Runtime) createArrayBuffer(val *Object) objectImpl { o := r.newNativeConstructOnly(val, r.builtin_newArrayBuffer, r.global.ArrayBufferPrototype, "ArrayBuffer", 1) o._putProp("isView", r.newNativeFunc(r.arrayBuffer_isView, nil, "isView", nil, 1), true, false, true) - o._putSym(symSpecies, &valueProperty{ + o._putSym(SymSpecies, &valueProperty{ getterFunc: r.newNativeFunc(r.returnThis, nil, "get [Symbol.species]", nil, 0), accessor: true, configurable: true, @@ -1300,7 +1300,7 @@ func (r *Runtime) createDataViewProto(val *Object) objectImpl { b._putProp("setUint8", r.newNativeFunc(r.dataViewProto_setUint8, nil, "setUint8", nil, 2), true, false, true) b._putProp("setUint16", r.newNativeFunc(r.dataViewProto_setUint16, nil, "setUint16", nil, 2), true, false, true) b._putProp("setUint32", r.newNativeFunc(r.dataViewProto_setUint32, nil, "setUint32", nil, 2), true, false, true) - b._putSym(symToStringTag, valueProp(asciiString("DataView"), false, false, true)) + b._putSym(SymToStringTag, valueProp(asciiString("DataView"), false, false, true)) return b } @@ -1359,8 +1359,8 @@ func (r *Runtime) createTypedArrayProto(val *Object) objectImpl { b._putProp("toString", r.global.arrayToString, true, false, true) valuesFunc := r.newNativeFunc(r.typedArrayProto_values, nil, "values", nil, 0) b._putProp("values", valuesFunc, true, false, true) - b._putSym(symIterator, valueProp(valuesFunc, true, false, true)) - b._putSym(symToStringTag, &valueProperty{ + b._putSym(SymIterator, valueProp(valuesFunc, true, false, true)) + b._putSym(SymToStringTag, &valueProperty{ getterFunc: r.newNativeFunc(r.typedArrayProto_toStringTag, nil, "get [Symbol.toStringTag]", nil, 0), accessor: true, configurable: true, @@ -1373,7 +1373,7 @@ func (r *Runtime) createTypedArray(val *Object) objectImpl { o := r.newNativeConstructOnly(val, r.newTypedArray, r.global.TypedArrayPrototype, "TypedArray", 0) o._putProp("from", r.newNativeFunc(r.typedArray_from, nil, "from", nil, 1), true, false, true) o._putProp("of", r.newNativeFunc(r.typedArray_of, nil, "of", nil, 0), true, false, true) - o._putSym(symSpecies, &valueProperty{ + o._putSym(SymSpecies, &valueProperty{ getterFunc: r.newNativeFunc(r.returnThis, nil, "get [Symbol.species]", nil, 0), accessor: true, configurable: true, diff --git a/builtin_weakmap.go b/builtin_weakmap.go index bd614eec..66008576 100644 --- a/builtin_weakmap.go +++ b/builtin_weakmap.go @@ -183,7 +183,7 @@ func (r *Runtime) createWeakMapProto(val *Object) objectImpl { o._putProp("has", r.newNativeFunc(r.weakMapProto_has, nil, "has", nil, 1), true, false, true) o._putProp("get", r.newNativeFunc(r.weakMapProto_get, nil, "get", nil, 1), true, false, true) - o._putSym(symToStringTag, valueProp(asciiString(classWeakMap), false, false, true)) + o._putSym(SymToStringTag, valueProp(asciiString(classWeakMap), false, false, true)) return o } diff --git a/builtin_weakset.go b/builtin_weakset.go index fc61d405..bed6dbde 100644 --- a/builtin_weakset.go +++ b/builtin_weakset.go @@ -139,7 +139,7 @@ func (r *Runtime) createWeakSetProto(val *Object) objectImpl { o._putProp("delete", r.newNativeFunc(r.weakSetProto_delete, nil, "delete", nil, 1), true, false, true) o._putProp("has", r.newNativeFunc(r.weakSetProto_has, nil, "has", nil, 1), true, false, true) - o._putSym(symToStringTag, valueProp(asciiString(classWeakSet), false, false, true)) + o._putSym(SymToStringTag, valueProp(asciiString(classWeakSet), false, false, true)) return o } diff --git a/map_test.go b/map_test.go index 98afab7f..7d41e767 100644 --- a/map_test.go +++ b/map_test.go @@ -23,8 +23,8 @@ func TestMapHash(t *testing.T) { testMapHashVal(asciiString("Test"), asciiString("Test"), true, t) testMapHashVal(newStringValue("Тест"), newStringValue("Тест"), true, t) testMapHashVal(floatToValue(1.2345), floatToValue(1.2345), true, t) - testMapHashVal(symIterator, symToStringTag, false, t) - testMapHashVal(symIterator, symIterator, true, t) + testMapHashVal(SymIterator, SymToStringTag, false, t) + testMapHashVal(SymIterator, SymIterator, true, t) // The following tests introduce indeterministic behaviour //testMapHashVal(asciiString("Test"), asciiString("Test1"), false, t) diff --git a/object.go b/object.go index 944b2819..c2cc0a7b 100644 --- a/object.go +++ b/object.go @@ -202,35 +202,35 @@ type objectImpl interface { className() string getStr(p unistring.String, receiver Value) Value getIdx(p valueInt, receiver Value) Value - getSym(p *valueSymbol, receiver Value) Value + getSym(p *Symbol, receiver Value) Value getOwnPropStr(unistring.String) Value getOwnPropIdx(valueInt) Value - getOwnPropSym(*valueSymbol) Value + getOwnPropSym(*Symbol) Value setOwnStr(p unistring.String, v Value, throw bool) bool setOwnIdx(p valueInt, v Value, throw bool) bool - setOwnSym(p *valueSymbol, v Value, throw bool) bool + setOwnSym(p *Symbol, v Value, throw bool) bool setForeignStr(p unistring.String, v, receiver Value, throw bool) (res bool, handled bool) setForeignIdx(p valueInt, v, receiver Value, throw bool) (res bool, handled bool) - setForeignSym(p *valueSymbol, v, receiver Value, throw bool) (res bool, handled bool) + setForeignSym(p *Symbol, v, receiver Value, throw bool) (res bool, handled bool) hasPropertyStr(unistring.String) bool hasPropertyIdx(idx valueInt) bool - hasPropertySym(s *valueSymbol) bool + hasPropertySym(s *Symbol) bool hasOwnPropertyStr(unistring.String) bool hasOwnPropertyIdx(valueInt) bool - hasOwnPropertySym(s *valueSymbol) bool + hasOwnPropertySym(s *Symbol) bool defineOwnPropertyStr(name unistring.String, desc PropertyDescriptor, throw bool) bool defineOwnPropertyIdx(name valueInt, desc PropertyDescriptor, throw bool) bool - defineOwnPropertySym(name *valueSymbol, desc PropertyDescriptor, throw bool) bool + defineOwnPropertySym(name *Symbol, desc PropertyDescriptor, throw bool) bool deleteStr(name unistring.String, throw bool) bool deleteIdx(idx valueInt, throw bool) bool - deleteSym(s *valueSymbol, throw bool) bool + deleteSym(s *Symbol, throw bool) bool toPrimitiveNumber() Value toPrimitiveString() Value @@ -252,7 +252,7 @@ type objectImpl interface { ownPropertyKeys(all bool, accum []Value) []Value _putProp(name unistring.String, value Value, writable, enumerable, configurable bool) Value - _putSym(s *valueSymbol, prop Value) + _putSym(s *Symbol, prop Value) } type baseObject struct { @@ -334,7 +334,7 @@ func (o *baseObject) hasPropertyIdx(idx valueInt) bool { return o.val.self.hasPropertyStr(idx.string()) } -func (o *baseObject) hasPropertySym(s *valueSymbol) bool { +func (o *baseObject) hasPropertySym(s *Symbol) bool { if o.hasOwnPropertySym(s) { return true } @@ -380,7 +380,7 @@ func (o *baseObject) getIdx(idx valueInt, receiver Value) Value { return o.val.self.getStr(idx.string(), receiver) } -func (o *baseObject) getSym(s *valueSymbol, receiver Value) Value { +func (o *baseObject) getSym(s *Symbol, receiver Value) Value { return o.getWithOwnProp(o.getOwnPropSym(s), s, receiver) } @@ -407,7 +407,7 @@ func (o *baseObject) getOwnPropIdx(idx valueInt) Value { return o.val.self.getOwnPropStr(idx.string()) } -func (o *baseObject) getOwnPropSym(s *valueSymbol) Value { +func (o *baseObject) getOwnPropSym(s *Symbol) Value { if o.symValues != nil { return o.symValues.get(s) } @@ -454,7 +454,7 @@ func (o *baseObject) deleteIdx(idx valueInt, throw bool) bool { return o.val.self.deleteStr(idx.string(), throw) } -func (o *baseObject) deleteSym(s *valueSymbol, throw bool) bool { +func (o *baseObject) deleteSym(s *Symbol, throw bool) bool { if o.symValues != nil { if val := o.symValues.get(s); val != nil { if !o.checkDelete(s.desc.string(), val, throw) { @@ -532,7 +532,7 @@ func (o *baseObject) setOwnIdx(idx valueInt, val Value, throw bool) bool { return o.val.self.setOwnStr(idx.string(), val, throw) } -func (o *baseObject) setOwnSym(name *valueSymbol, val Value, throw bool) bool { +func (o *baseObject) setOwnSym(name *Symbol, val Value, throw bool) bool { var ownDesc Value if o.symValues != nil { ownDesc = o.symValues.get(name) @@ -623,7 +623,7 @@ func (o *baseObject) setForeignIdx(name valueInt, val, receiver Value, throw boo return o.val.self.setForeignStr(name.string(), val, receiver, throw) } -func (o *baseObject) setForeignSym(name *valueSymbol, val, receiver Value, throw bool) (bool, bool) { +func (o *baseObject) setForeignSym(name *Symbol, val, receiver Value, throw bool) (bool, bool) { var prop Value if o.symValues != nil { prop = o.symValues.get(name) @@ -650,7 +650,7 @@ func (o *baseObject) setForeignSym(name *valueSymbol, val, receiver Value, throw return false, false } -func (o *baseObject) hasOwnPropertySym(s *valueSymbol) bool { +func (o *baseObject) hasOwnPropertySym(s *Symbol) bool { if o.symValues != nil { return o.symValues.has(s) } @@ -785,7 +785,7 @@ func (o *baseObject) defineOwnPropertyIdx(idx valueInt, desc PropertyDescriptor, return o.val.self.defineOwnPropertyStr(idx.string(), desc, throw) } -func (o *baseObject) defineOwnPropertySym(s *valueSymbol, descr PropertyDescriptor, throw bool) bool { +func (o *baseObject) defineOwnPropertySym(s *Symbol, descr PropertyDescriptor, throw bool) bool { var existingVal Value if o.symValues != nil { existingVal = o.symValues.get(s) @@ -826,7 +826,7 @@ func (o *baseObject) _putProp(name unistring.String, value Value, writable, enum return prop } -func (o *baseObject) _putSym(s *valueSymbol, prop Value) { +func (o *baseObject) _putSym(s *Symbol, prop Value) { if o.symValues == nil { o.symValues = newOrderedMap(nil) } @@ -887,7 +887,7 @@ func (o *baseObject) toPrimitive() Value { } func (o *Object) tryExoticToPrimitive(hint Value) Value { - exoticToPrimitive := toMethod(o.self.getSym(symToPrimitive, nil)) + exoticToPrimitive := toMethod(o.self.getSym(SymToPrimitive, nil)) if exoticToPrimitive != nil { ret := exoticToPrimitive(FunctionCall{ This: o, @@ -1202,7 +1202,7 @@ func toMethod(v Value) func(FunctionCall) Value { } func instanceOfOperator(o Value, c *Object) bool { - if instOfHandler := toMethod(c.self.getSym(symHasInstance, c)); instOfHandler != nil { + if instOfHandler := toMethod(c.self.getSym(SymHasInstance, c)); instOfHandler != nil { return instOfHandler(FunctionCall{ This: c, Arguments: []Value{o}, @@ -1216,7 +1216,7 @@ func (o *Object) get(p Value, receiver Value) Value { switch p := p.(type) { case valueInt: return o.self.getIdx(p, receiver) - case *valueSymbol: + case *Symbol: return o.self.getSym(p, receiver) default: return o.self.getStr(p.string(), receiver) @@ -1227,7 +1227,7 @@ func (o *Object) getOwnProp(p Value) Value { switch p := p.(type) { case valueInt: return o.self.getOwnPropIdx(p) - case *valueSymbol: + case *Symbol: return o.self.getOwnPropSym(p) default: return o.self.getOwnPropStr(p.string()) @@ -1238,7 +1238,7 @@ func (o *Object) hasOwnProperty(p Value) bool { switch p := p.(type) { case valueInt: return o.self.hasOwnPropertyIdx(p) - case *valueSymbol: + case *Symbol: return o.self.hasOwnPropertySym(p) default: return o.self.hasOwnPropertyStr(p.string()) @@ -1249,7 +1249,7 @@ func (o *Object) hasProperty(p Value) bool { switch p := p.(type) { case valueInt: return o.self.hasPropertyIdx(p) - case *valueSymbol: + case *Symbol: return o.self.hasPropertySym(p) default: return o.self.hasPropertyStr(p.string()) @@ -1297,7 +1297,7 @@ func (o *Object) set(name Value, val, receiver Value, throw bool) bool { switch name := name.(type) { case valueInt: return o.setIdx(name, val, receiver, throw) - case *valueSymbol: + case *Symbol: return o.setSym(name, val, receiver, throw) default: return o.setStr(name.string(), val, receiver, throw) @@ -1308,7 +1308,7 @@ func (o *Object) setOwn(name Value, val Value, throw bool) bool { switch name := name.(type) { case valueInt: return o.self.setOwnIdx(name, val, throw) - case *valueSymbol: + case *Symbol: return o.self.setOwnSym(name, val, throw) default: return o.self.setOwnStr(name.string(), val, throw) @@ -1352,7 +1352,7 @@ func (o *Object) setIdx(name valueInt, val, receiver Value, throw bool) bool { return true } -func (o *Object) setSym(name *valueSymbol, val, receiver Value, throw bool) bool { +func (o *Object) setSym(name *Symbol, val, receiver Value, throw bool) bool { if receiver == o { return o.self.setOwnSym(name, val, throw) } else { @@ -1393,7 +1393,7 @@ func (o *Object) delete(n Value, throw bool) bool { switch n := n.(type) { case valueInt: return o.self.deleteIdx(n, throw) - case *valueSymbol: + case *Symbol: return o.self.deleteSym(n, throw) default: return o.self.deleteStr(n.string(), throw) @@ -1404,7 +1404,7 @@ func (o *Object) defineOwnProperty(n Value, desc PropertyDescriptor, throw bool) switch n := n.(type) { case valueInt: return o.self.defineOwnPropertyIdx(n, desc, throw) - case *valueSymbol: + case *Symbol: return o.self.defineOwnPropertySym(n, desc, throw) default: return o.self.defineOwnPropertyStr(n.string(), desc, throw) diff --git a/object_lazy.go b/object_lazy.go index 33766726..c57f2439 100644 --- a/object_lazy.go +++ b/object_lazy.go @@ -23,7 +23,7 @@ func (o *lazyObject) getIdx(p valueInt, receiver Value) Value { return obj.getIdx(p, receiver) } -func (o *lazyObject) getSym(p *valueSymbol, receiver Value) Value { +func (o *lazyObject) getSym(p *Symbol, receiver Value) Value { obj := o.create(o.val) o.val.self = obj return obj.getSym(p, receiver) @@ -35,7 +35,7 @@ func (o *lazyObject) getOwnPropIdx(idx valueInt) Value { return obj.getOwnPropIdx(idx) } -func (o *lazyObject) getOwnPropSym(s *valueSymbol) Value { +func (o *lazyObject) getOwnPropSym(s *Symbol) Value { obj := o.create(o.val) o.val.self = obj return obj.getOwnPropSym(s) @@ -47,7 +47,7 @@ func (o *lazyObject) hasPropertyIdx(idx valueInt) bool { return obj.hasPropertyIdx(idx) } -func (o *lazyObject) hasPropertySym(s *valueSymbol) bool { +func (o *lazyObject) hasPropertySym(s *Symbol) bool { obj := o.create(o.val) o.val.self = obj return obj.hasPropertySym(s) @@ -59,7 +59,7 @@ func (o *lazyObject) hasOwnPropertyIdx(idx valueInt) bool { return obj.hasOwnPropertyIdx(idx) } -func (o *lazyObject) hasOwnPropertySym(s *valueSymbol) bool { +func (o *lazyObject) hasOwnPropertySym(s *Symbol) bool { obj := o.create(o.val) o.val.self = obj return obj.hasOwnPropertySym(s) @@ -77,7 +77,7 @@ func (o *lazyObject) defineOwnPropertyIdx(name valueInt, desc PropertyDescriptor return obj.defineOwnPropertyIdx(name, desc, throw) } -func (o *lazyObject) defineOwnPropertySym(name *valueSymbol, desc PropertyDescriptor, throw bool) bool { +func (o *lazyObject) defineOwnPropertySym(name *Symbol, desc PropertyDescriptor, throw bool) bool { obj := o.create(o.val) o.val.self = obj return obj.defineOwnPropertySym(name, desc, throw) @@ -89,7 +89,7 @@ func (o *lazyObject) deleteIdx(idx valueInt, throw bool) bool { return obj.deleteIdx(idx, throw) } -func (o *lazyObject) deleteSym(s *valueSymbol, throw bool) bool { +func (o *lazyObject) deleteSym(s *Symbol, throw bool) bool { obj := o.create(o.val) o.val.self = obj return obj.deleteSym(s, throw) @@ -119,7 +119,7 @@ func (o *lazyObject) setOwnIdx(p valueInt, v Value, throw bool) bool { return obj.setOwnIdx(p, v, throw) } -func (o *lazyObject) setOwnSym(p *valueSymbol, v Value, throw bool) bool { +func (o *lazyObject) setOwnSym(p *Symbol, v Value, throw bool) bool { obj := o.create(o.val) o.val.self = obj return obj.setOwnSym(p, v, throw) @@ -137,7 +137,7 @@ func (o *lazyObject) setForeignIdx(p valueInt, v, receiver Value, throw bool) (b return obj.setForeignIdx(p, v, receiver, throw) } -func (o *lazyObject) setForeignSym(p *valueSymbol, v, receiver Value, throw bool) (bool, bool) { +func (o *lazyObject) setForeignSym(p *Symbol, v, receiver Value, throw bool) (bool, bool) { obj := o.create(o.val) o.val.self = obj return obj.setForeignSym(p, v, receiver, throw) @@ -159,7 +159,7 @@ func (o *lazyObject) _putProp(unistring.String, Value, bool, bool, bool) Value { panic("cannot use _putProp() in lazy object") } -func (o *lazyObject) _putSym(*valueSymbol, Value) { +func (o *lazyObject) _putSym(*Symbol, Value) { panic("cannot use _putSym() in lazy object") } diff --git a/proxy.go b/proxy.go index 3f61e746..b2df7239 100644 --- a/proxy.go +++ b/proxy.go @@ -248,7 +248,7 @@ func (p *proxyObject) defineOwnPropertyIdx(idx valueInt, descr PropertyDescripto return p.target.self.defineOwnPropertyIdx(idx, descr, throw) } -func (p *proxyObject) defineOwnPropertySym(s *valueSymbol, descr PropertyDescriptor, throw bool) bool { +func (p *proxyObject) defineOwnPropertySym(s *Symbol, descr PropertyDescriptor, throw bool) bool { if v, ok := p.proxyDefineOwnProperty(s, descr, throw); ok { return v } @@ -291,7 +291,7 @@ func (p *proxyObject) hasPropertyIdx(idx valueInt) bool { return p.target.self.hasPropertyIdx(idx) } -func (p *proxyObject) hasPropertySym(s *valueSymbol) bool { +func (p *proxyObject) hasPropertySym(s *Symbol) bool { if b, ok := p.proxyHas(s); ok { return b } @@ -307,7 +307,7 @@ func (p *proxyObject) hasOwnPropertyIdx(idx valueInt) bool { return p.getOwnPropIdx(idx) != nil } -func (p *proxyObject) hasOwnPropertySym(s *valueSymbol) bool { +func (p *proxyObject) hasOwnPropertySym(s *Symbol) bool { return p.getOwnPropSym(s) != nil } @@ -381,7 +381,7 @@ func (p *proxyObject) getOwnPropIdx(idx valueInt) Value { return p.target.self.getOwnPropIdx(idx) } -func (p *proxyObject) getOwnPropSym(s *valueSymbol) Value { +func (p *proxyObject) getOwnPropSym(s *Symbol) Value { if v, ok := p.proxyGetOwnPropertyDescriptor(s); ok { return v } @@ -403,7 +403,7 @@ func (p *proxyObject) getIdx(idx valueInt, receiver Value) Value { return p.target.self.getIdx(idx, receiver) } -func (p *proxyObject) getSym(s *valueSymbol, receiver Value) Value { +func (p *proxyObject) getSym(s *Symbol, receiver Value) Value { if v, ok := p.proxyGet(s, receiver); ok { return v } @@ -472,7 +472,7 @@ func (p *proxyObject) setOwnIdx(idx valueInt, v Value, throw bool) bool { return p.target.setIdx(idx, v, p.val, throw) } -func (p *proxyObject) setOwnSym(s *valueSymbol, v Value, throw bool) bool { +func (p *proxyObject) setOwnSym(s *Symbol, v Value, throw bool) bool { if res, ok := p.proxySet(s, v, p.val, throw); ok { return res } @@ -493,7 +493,7 @@ func (p *proxyObject) setForeignIdx(idx valueInt, v, receiver Value, throw bool) return p.target.setIdx(idx, v, receiver, throw), true } -func (p *proxyObject) setForeignSym(s *valueSymbol, v, receiver Value, throw bool) (bool, bool) { +func (p *proxyObject) setForeignSym(s *Symbol, v, receiver Value, throw bool) (bool, bool) { if res, ok := p.proxySet(s, v, receiver, throw); ok { return res, true } @@ -532,7 +532,7 @@ func (p *proxyObject) deleteIdx(idx valueInt, throw bool) bool { return p.target.self.deleteIdx(idx, throw) } -func (p *proxyObject) deleteSym(s *valueSymbol, throw bool) bool { +func (p *proxyObject) deleteSym(s *Symbol, throw bool) bool { if ret, ok := p.proxyDelete(s); ok { return ret } @@ -557,7 +557,7 @@ func (p *proxyObject) proxyOwnKeys() ([]Value, bool) { for k := int64(0); k < l; k++ { item := keys.self.getIdx(valueInt(k), nil) if _, ok := item.(valueString); !ok { - if _, ok := item.(*valueSymbol); !ok { + if _, ok := item.(*Symbol); !ok { panic(p.val.runtime.NewTypeError("%s is not a valid property name", item.String())) } } @@ -719,13 +719,13 @@ func (p *proxyObject) filterKeys(vals []Value, all, symbols bool) []Value { for i, val := range vals { var prop Value if symbols { - if s, ok := val.(*valueSymbol); ok { + if s, ok := val.(*Symbol); ok { prop = p.getOwnPropSym(s) } else { continue } } else { - if _, ok := val.(*valueSymbol); !ok { + if _, ok := val.(*Symbol); !ok { prop = p.getOwnPropStr(val.string()) } else { continue @@ -746,7 +746,7 @@ func (p *proxyObject) filterKeys(vals []Value, all, symbols bool) []Value { } else { k := 0 for i, val := range vals { - if _, ok := val.(*valueSymbol); ok != symbols { + if _, ok := val.(*Symbol); ok != symbols { continue } if k != i { diff --git a/runtime.go b/runtime.go index 058eff9f..0a6d1e45 100644 --- a/runtime.go +++ b/runtime.go @@ -162,7 +162,7 @@ type Runtime struct { now Now _collator *collate.Collator - symbolRegistry map[unistring.String]*valueSymbol + symbolRegistry map[unistring.String]*Symbol typeInfoCache map[reflect.Type]*reflectTypeInfo fieldNameMapper FieldNameMapper @@ -332,7 +332,7 @@ func (r *Runtime) addToGlobal(name string, value Value) { func (r *Runtime) createIterProto(val *Object) objectImpl { o := newBaseObjectObj(val, r.global.ObjectPrototype, classObject) - o._putSym(symIterator, valueProp(r.newNativeFunc(r.returnThis, nil, "[Symbol.iterator]", nil, 0), true, false, true)) + o._putSym(SymIterator, valueProp(r.newNativeFunc(r.returnThis, nil, "[Symbol.iterator]", nil, 0), true, false, true)) return o } @@ -2124,7 +2124,7 @@ func (r *Runtime) toNumber(v Value) Value { func (r *Runtime) speciesConstructor(o, defaultConstructor *Object) func(args []Value, newTarget *Object) *Object { c := o.self.getStr("constructor", nil) if c != nil && c != _undefined { - c = r.toObject(c).self.getSym(symSpecies, nil) + c = r.toObject(c).self.getSym(SymSpecies, nil) } if c == nil || c == _undefined || c == _null { c = defaultConstructor @@ -2135,7 +2135,7 @@ func (r *Runtime) speciesConstructor(o, defaultConstructor *Object) func(args [] func (r *Runtime) speciesConstructorObj(o, defaultConstructor *Object) *Object { c := o.self.getStr("constructor", nil) if c != nil && c != _undefined { - c = r.toObject(c).self.getSym(symSpecies, nil) + c = r.toObject(c).self.getSym(SymSpecies, nil) } if c == nil || c == _undefined || c == _null { return defaultConstructor @@ -2172,7 +2172,7 @@ func (r *Runtime) getV(v Value, p Value) Value { func (r *Runtime) getIterator(obj Value, method func(FunctionCall) Value) *Object { if method == nil { - method = toMethod(r.getV(obj, symIterator)) + method = toMethod(r.getV(obj, SymIterator)) if method == nil { panic(r.NewTypeError("object is not iterable")) } @@ -2310,7 +2310,7 @@ func isArray(object *Object) bool { func isRegexp(v Value) bool { if o, ok := v.(*Object); ok { - matcher := o.self.getSym(symMatch, nil) + matcher := o.self.getSym(SymMatch, nil) if matcher != nil && matcher != _undefined { return matcher.ToBoolean() } diff --git a/runtime_test.go b/runtime_test.go index 80822b71..e1ea23cd 100644 --- a/runtime_test.go +++ b/runtime_test.go @@ -1752,6 +1752,55 @@ func TestNativeCtorNonNewCall(t *testing.T) { } } +func ExampleNewSymbol() { + sym1 := NewSymbol("66") + sym2 := NewSymbol("66") + fmt.Printf("%s %s %v", sym1, sym2, sym1.Equals(sym2)) + // Output: 66 66 false +} + +func ExampleObject_SetSymbol() { + type IterResult struct { + Done bool + Value Value + } + + vm := New() + vm.SetFieldNameMapper(UncapFieldNameMapper()) // to use IterResult + + o := vm.NewObject() + o.SetSymbol(SymIterator, func() *Object { + count := 0 + iter := vm.NewObject() + iter.Set("next", func() IterResult { + if count < 10 { + count++ + return IterResult{ + Value: vm.ToValue(count), + } + } + return IterResult{ + Done: true, + } + }) + return iter + }) + vm.Set("o", o) + + res, err := vm.RunString(` + var acc = ""; + for (var v of o) { + acc += v + " "; + } + acc; + `) + if err != nil { + panic(err) + } + fmt.Println(res) + // Output: 1 2 3 4 5 6 7 8 9 10 +} + /* func TestArrayConcatSparse(t *testing.T) { function foo(a,b,c) diff --git a/value.go b/value.go index 140f508b..8095a607 100644 --- a/value.go +++ b/value.go @@ -88,7 +88,12 @@ type valueNull struct{} type valueUndefined struct { valueNull } -type valueSymbol struct { + +// *Symbol is a Value containing ECMAScript Symbol primitive. Symbols must only be created +// using NewSymbol(). Zero values and copying of values (i.e. *s1 = *s2) are not permitted. +// Well-known Symbols can be accessed using Sym* package variables (SymIterator, etc...) +// Symbols can be shared by multiple Runtimes. +type Symbol struct { h uintptr desc valueString } @@ -736,6 +741,12 @@ func (o *Object) Get(name string) Value { return o.self.getStr(unistring.NewFromString(name), nil) } +// GetSymbol returns the value of a symbol property. Use one of the Sym* values for well-known +// symbols (such as SymIterator, SymToStringTag, etc...). +func (o *Object) GetSymbol(sym *Symbol) Value { + return o.self.getSym(sym, nil) +} + func (o *Object) Keys() (keys []string) { names := o.self.ownKeys(false, nil) keys = make([]string, 0, len(names)) @@ -746,6 +757,15 @@ func (o *Object) Keys() (keys []string) { return } +func (o *Object) Symbols() []*Symbol { + symbols := o.self.ownSymbols(false, nil) + ret := make([]*Symbol, len(symbols)) + for i, sym := range symbols { + ret[i], _ = sym.(*Symbol) + } + return ret +} + // DefineDataProperty is a Go equivalent of Object.defineProperty(o, name, {value: value, writable: writable, // configurable: configurable, enumerable: enumerable}) func (o *Object) DefineDataProperty(name string, value Value, writable, configurable, enumerable Flag) error { @@ -772,18 +792,56 @@ func (o *Object) DefineAccessorProperty(name string, getter, setter Value, confi }) } +// DefineDataPropertySymbol is a Go equivalent of Object.defineProperty(o, name, {value: value, writable: writable, +// configurable: configurable, enumerable: enumerable}) +func (o *Object) DefineDataPropertySymbol(name *Symbol, value Value, writable, configurable, enumerable Flag) error { + return tryFunc(func() { + o.self.defineOwnPropertySym(name, PropertyDescriptor{ + Value: value, + Writable: writable, + Configurable: configurable, + Enumerable: enumerable, + }, true) + }) +} + +// DefineAccessorPropertySymbol is a Go equivalent of Object.defineProperty(o, name, {get: getter, set: setter, +// configurable: configurable, enumerable: enumerable}) +func (o *Object) DefineAccessorPropertySymbol(name *Symbol, getter, setter Value, configurable, enumerable Flag) error { + return tryFunc(func() { + o.self.defineOwnPropertySym(name, PropertyDescriptor{ + Getter: getter, + Setter: setter, + Configurable: configurable, + Enumerable: enumerable, + }, true) + }) +} + func (o *Object) Set(name string, value interface{}) error { return tryFunc(func() { o.self.setOwnStr(unistring.NewFromString(name), o.runtime.ToValue(value), true) }) } +func (o *Object) SetSymbol(name *Symbol, value interface{}) error { + return tryFunc(func() { + o.self.setOwnSym(name, o.runtime.ToValue(value), true) + }) +} + func (o *Object) Delete(name string) error { return tryFunc(func() { o.self.deleteStr(unistring.NewFromString(name), true) }) } +func (o *Object) DeleteSymbol(name *Symbol) error { + return tryFunc(func() { + o.self.deleteSym(name, true) + }) +} + // MarshalJSON returns JSON representation of the Object. It is equivalent to JSON.stringify(o). // Note, this implements json.Marshaler so that json.Marshal() can be used without the need to Export(). func (o *Object) MarshalJSON() ([]byte, error) { @@ -890,70 +948,70 @@ func (o valueUnresolved) hash(*maphash.Hash) uint64 { return 0 } -func (s *valueSymbol) ToInteger() int64 { +func (s *Symbol) ToInteger() int64 { panic(typeError("Cannot convert a Symbol value to a number")) } -func (s *valueSymbol) toString() valueString { +func (s *Symbol) toString() valueString { panic(typeError("Cannot convert a Symbol value to a string")) } -func (s *valueSymbol) ToString() Value { +func (s *Symbol) ToString() Value { return s } -func (s *valueSymbol) String() string { +func (s *Symbol) String() string { return s.desc.String() } -func (s *valueSymbol) string() unistring.String { +func (s *Symbol) string() unistring.String { return s.desc.string() } -func (s *valueSymbol) ToFloat() float64 { +func (s *Symbol) ToFloat() float64 { panic(typeError("Cannot convert a Symbol value to a number")) } -func (s *valueSymbol) ToNumber() Value { +func (s *Symbol) ToNumber() Value { panic(typeError("Cannot convert a Symbol value to a number")) } -func (s *valueSymbol) ToBoolean() bool { +func (s *Symbol) ToBoolean() bool { return true } -func (s *valueSymbol) ToObject(r *Runtime) *Object { +func (s *Symbol) ToObject(r *Runtime) *Object { return s.baseObject(r) } -func (s *valueSymbol) SameAs(other Value) bool { - if s1, ok := other.(*valueSymbol); ok { +func (s *Symbol) SameAs(other Value) bool { + if s1, ok := other.(*Symbol); ok { return s == s1 } return false } -func (s *valueSymbol) Equals(o Value) bool { +func (s *Symbol) Equals(o Value) bool { return s.SameAs(o) } -func (s *valueSymbol) StrictEquals(o Value) bool { +func (s *Symbol) StrictEquals(o Value) bool { return s.SameAs(o) } -func (s *valueSymbol) Export() interface{} { +func (s *Symbol) Export() interface{} { return s.String() } -func (s *valueSymbol) ExportType() reflect.Type { +func (s *Symbol) ExportType() reflect.Type { return reflectTypeString } -func (s *valueSymbol) baseObject(r *Runtime) *Object { +func (s *Symbol) baseObject(r *Runtime) *Object { return r.newPrimitiveObject(s, r.global.SymbolPrototype, "Symbol") } -func (s *valueSymbol) hash(*maphash.Hash) uint64 { +func (s *Symbol) hash(*maphash.Hash) uint64 { return uint64(s.h) } @@ -964,9 +1022,9 @@ func exportValue(v Value, ctx *objectExportCtx) interface{} { return v.Export() } -func newSymbol(s valueString) *valueSymbol { - r := &valueSymbol{ - desc: asciiString("Symbol(").concat(s).concat(asciiString(")")), +func newSymbol(s valueString) *Symbol { + r := &Symbol{ + desc: s, } // This may need to be reconsidered in the future. // Depending on changes in Go's allocation policy and/or introduction of a compacting GC @@ -976,6 +1034,17 @@ func newSymbol(s valueString) *valueSymbol { return r } +func NewSymbol(s string) *Symbol { + return newSymbol(newStringValue(s)) +} + +func (s *Symbol) descriptiveString() valueString { + if s.desc == nil { + return stringEmpty + } + return asciiString("Symbol(").concat(s.desc).concat(asciiString(")")) +} + func init() { for i := 0; i < 256; i++ { intCache[i] = valueInt(i - 128) diff --git a/vm.go b/vm.go index 26ed4179..713b4c49 100644 --- a/vm.go +++ b/vm.go @@ -205,7 +205,7 @@ func (s *valueStack) expand(idx int) { func stashObjHas(obj *Object, name unistring.String) bool { if obj.self.hasPropertyStr(name) { - if unscopables, ok := obj.self.getSym(symUnscopables, nil).(*Object); ok { + if unscopables, ok := obj.self.getSym(SymUnscopables, nil).(*Object); ok { if b := unscopables.self.getStr(name, nil); b != nil { return !b.ToBoolean() } @@ -2356,7 +2356,7 @@ func (_typeof) exec(vm *vm) { r = stringString case valueInt, valueFloat: r = stringNumber - case *valueSymbol: + case *Symbol: r = stringSymbol default: panic(fmt.Errorf("Unknown type: %T", v)) From be0895b77e07af79b41efdc974867383cf69d4c2 Mon Sep 17 00:00:00 2001 From: Dmitry Panov Date: Sat, 12 Dec 2020 16:20:34 +0000 Subject: [PATCH 4/7] Added Runtime.NewArray() --- runtime.go | 8 ++++++++ runtime_test.go | 18 ++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/runtime.go b/runtime.go index 0a6d1e45..a29b6d4f 100644 --- a/runtime.go +++ b/runtime.go @@ -452,6 +452,14 @@ func (r *Runtime) CreateObject(proto *Object) *Object { return r.newBaseObject(proto, classObject).val } +func (r *Runtime) NewArray(items ...interface{}) *Object { + values := make([]Value, len(items)) + for i, item := range items { + values[i] = r.ToValue(item) + } + return r.newArrayValues(values) +} + func (r *Runtime) NewTypeError(args ...interface{}) *Object { msg := "" if len(args) > 0 { diff --git a/runtime_test.go b/runtime_test.go index e1ea23cd..90caf848 100644 --- a/runtime_test.go +++ b/runtime_test.go @@ -1801,6 +1801,24 @@ func ExampleObject_SetSymbol() { // Output: 1 2 3 4 5 6 7 8 9 10 } +func ExampleRuntime_NewArray() { + vm := New() + array := vm.NewArray(1, 2, true) + vm.Set("array", array) + res, err := vm.RunString(` + var acc = ""; + for (var v of array) { + acc += v + " "; + } + acc; + `) + if err != nil { + panic(err) + } + fmt.Println(res) + // Output: 1 2 true +} + /* func TestArrayConcatSparse(t *testing.T) { function foo(a,b,c) From 4ac8fd96696c49000e1f5a012eb4e6848dce3c64 Mon Sep 17 00:00:00 2001 From: Dmitry Panov Date: Thu, 17 Dec 2020 14:06:58 +0000 Subject: [PATCH 5/7] Detect source map URL when the source is wrapped in "(function() { ... })". See #235. --- parser/parser_test.go | 17 +++++++++++++++++ parser/statement.go | 26 ++++++++++++++++++++++---- 2 files changed, 39 insertions(+), 4 deletions(-) diff --git a/parser/parser_test.go b/parser/parser_test.go index cd2303fd..61ab2306 100644 --- a/parser/parser_test.go +++ b/parser/parser_test.go @@ -1014,3 +1014,20 @@ func TestPosition(t *testing.T) { is(node.(*ast.FunctionLiteral).Source, "function(){ return abc; }") }) } + +func TestExtractSourceMapLine(t *testing.T) { + tt(t, func() { + is(extractSourceMapLine(""), "") + is(extractSourceMapLine("\n"), "") + is(extractSourceMapLine(" "), "") + is(extractSourceMapLine("1\n2\n3\n4\n"), "") + + src := `"use strict"; +var x = {}; +//# sourceMappingURL=delme.js.map` + modSrc := `(function(exports, require, module) {` + src + ` +})` + is(extractSourceMapLine(modSrc), "//# sourceMappingURL=delme.js.map") + is(extractSourceMapLine(modSrc+"\n\n\n\n"), "//# sourceMappingURL=delme.js.map") + }) +} diff --git a/parser/statement.go b/parser/statement.go index 45581cec..4fe3d053 100644 --- a/parser/statement.go +++ b/parser/statement.go @@ -586,11 +586,29 @@ func (self *_parser) parseProgram() *ast.Program { } } +func extractSourceMapLine(str string) string { + for { + p := strings.LastIndexByte(str, '\n') + line := str[p+1:] + if line != "" && line != "})" { + if strings.HasPrefix(line, "//# sourceMappingURL=") { + return line + } + break + } + if p >= 0 { + str = str[:p] + } else { + break + } + } + return "" +} + func (self *_parser) parseSourceMap() *sourcemap.Consumer { - lastLine := self.str[strings.LastIndexByte(self.str, '\n')+1:] - if strings.HasPrefix(lastLine, "//# sourceMappingURL") { - urlIndex := strings.Index(lastLine, "=") - urlStr := lastLine[urlIndex+1:] + if smLine := extractSourceMapLine(self.str); smLine != "" { + urlIndex := strings.Index(smLine, "=") + urlStr := smLine[urlIndex+1:] var data []byte if strings.HasPrefix(urlStr, "data:application/json") { From 10e5c759925729f7ea8427b09310c7ce0e647a12 Mon Sep 17 00:00:00 2001 From: Dmitry Panov Date: Sat, 19 Dec 2020 13:31:16 +0000 Subject: [PATCH 6/7] Introduced options for parser. Added options to set a custom source map loader or to disable source map support. See #235. --- parser/parser.go | 43 ++++++++++++++++++++++++++++++++++------ parser/parser_test.go | 41 ++++++++++++++++++++++++++++++++++++++ parser/statement.go | 44 ++++++++++++++++++++++++++++++----------- parser/testutil_test.go | 24 ++++++++++++++++++++-- runtime.go | 34 ++++++++++++++++++++++++------- runtime_test.go | 29 +++++++++++++++++++++++++++ 6 files changed, 188 insertions(+), 27 deletions(-) diff --git a/parser/parser.go b/parser/parser.go index a5784d33..bf596c37 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -52,6 +52,32 @@ const ( IgnoreRegExpErrors Mode = 1 << iota // Ignore RegExp compatibility errors (allow backtracking) ) +type options struct { + disableSourceMaps bool + sourceMapLoader func(path string) ([]byte, error) +} + +// Option represents one of the options for the parser to use in the Parse methods. Currently supported are: +// WithDisableSourceMaps and WithSourceMapLoader. +type Option func(*options) + +// WithDisableSourceMaps is an option to disable source maps support. May save a bit of time when source maps +// are not in use. +func WithDisableSourceMaps(opts *options) { + opts.disableSourceMaps = true +} + +// WithSourceMapLoader is an option to set a custom source map loader. The loader will be given a path or a +// URL from the sourceMappingURL. If sourceMappingURL is not absolute it is resolved relatively to the name +// of the file being parsed. Any error returned by the loader will fail the parsing. +// Note that setting this to nil does not disable source map support, there is a default loader which reads +// from the filesystem. Use WithDisableSourceMaps to disable source map support. +func WithSourceMapLoader(loader func(path string) ([]byte, error)) Option { + return func(opts *options) { + opts.sourceMapLoader = loader + } +} + type _parser struct { str string length int @@ -79,18 +105,23 @@ type _parser struct { } mode Mode + opts options file *file.File } -func _newParser(filename, src string, base int) *_parser { - return &_parser{ +func _newParser(filename, src string, base int, opts ...Option) *_parser { + p := &_parser{ chr: ' ', // This is set so we can start scanning by skipping whitespace str: src, length: len(src), base: base, file: file.NewFile(filename, src, base), } + for _, opt := range opts { + opt(&p.opts) + } + return p } func newParser(filename, src string) *_parser { @@ -133,7 +164,7 @@ func ReadSource(filename string, src interface{}) ([]byte, error) { // // Parse some JavaScript, yielding a *ast.Program and/or an ErrorList // program, err := parser.ParseFile(nil, "", `if (abc > 1) {}`, 0) // -func ParseFile(fileSet *file.FileSet, filename string, src interface{}, mode Mode) (*ast.Program, error) { +func ParseFile(fileSet *file.FileSet, filename string, src interface{}, mode Mode, options ...Option) (*ast.Program, error) { str, err := ReadSource(filename, src) if err != nil { return nil, err @@ -146,7 +177,7 @@ func ParseFile(fileSet *file.FileSet, filename string, src interface{}, mode Mod base = fileSet.AddFile(filename, str) } - parser := _newParser(filename, str, base) + parser := _newParser(filename, str, base, options...) parser.mode = mode return parser.parse() } @@ -157,11 +188,11 @@ func ParseFile(fileSet *file.FileSet, filename string, src interface{}, mode Mod // // The parameter list, if any, should be a comma-separated list of identifiers. // -func ParseFunction(parameterList, body string) (*ast.FunctionLiteral, error) { +func ParseFunction(parameterList, body string, options ...Option) (*ast.FunctionLiteral, error) { src := "(function(" + parameterList + ") {\n" + body + "\n})" - parser := _newParser("", src, 1) + parser := _newParser("", src, 1, options...) program, err := parser.parse() if err != nil { return nil, err diff --git a/parser/parser_test.go b/parser/parser_test.go index 61ab2306..ea7ae0ed 100644 --- a/parser/parser_test.go +++ b/parser/parser_test.go @@ -1031,3 +1031,44 @@ var x = {}; is(extractSourceMapLine(modSrc+"\n\n\n\n"), "//# sourceMappingURL=delme.js.map") }) } + +func TestSourceMapOptions(t *testing.T) { + tt(t, func() { + count := 0 + requestedPath := "" + loader := func(p string) ([]byte, error) { + count++ + requestedPath = p + return nil, nil + } + src := `"use strict"; +var x = {}; +//# sourceMappingURL=delme.js.map` + _, err := ParseFile(nil, "delme.js", src, 0, WithSourceMapLoader(loader)) + is(err, nil) + is(count, 1) + is(requestedPath, "delme.js.map") + + count = 0 + _, err = ParseFile(nil, "", src, 0, WithSourceMapLoader(loader)) + is(err, nil) + is(count, 1) + is(requestedPath, "delme.js.map") + + count = 0 + _, err = ParseFile(nil, "delme.js", src, 0, WithDisableSourceMaps) + is(err, nil) + is(count, 0) + + _, err = ParseFile(nil, "/home/user/src/delme.js", src, 0, WithSourceMapLoader(loader)) + is(err, nil) + is(count, 1) + is(requestedPath, "/home/user/src/delme.js.map") + + count = 0 + _, err = ParseFile(nil, "https://site.com/delme.js", src, 0, WithSourceMapLoader(loader)) + is(err, nil) + is(count, 1) + is(requestedPath, "https://site.com/delme.js.map") + }) +} diff --git a/parser/statement.go b/parser/statement.go index 4fe3d053..3cdff179 100644 --- a/parser/statement.go +++ b/parser/statement.go @@ -2,13 +2,14 @@ package parser import ( "encoding/base64" + "fmt" "github.com/dop251/goja/ast" "github.com/dop251/goja/file" "github.com/dop251/goja/token" "github.com/go-sourcemap/sourcemap" "io/ioutil" "net/url" - "os" + "path" "strings" ) @@ -606,38 +607,57 @@ func extractSourceMapLine(str string) string { } func (self *_parser) parseSourceMap() *sourcemap.Consumer { + if self.opts.disableSourceMaps { + return nil + } if smLine := extractSourceMapLine(self.str); smLine != "" { urlIndex := strings.Index(smLine, "=") urlStr := smLine[urlIndex+1:] var data []byte + var err error if strings.HasPrefix(urlStr, "data:application/json") { b64Index := strings.Index(urlStr, ",") b64 := urlStr[b64Index+1:] - if d, err := base64.StdEncoding.DecodeString(b64); err == nil { - data = d - } + data, err = base64.StdEncoding.DecodeString(b64) } else { - if smUrl, err := url.Parse(urlStr); err == nil { - if smUrl.Scheme == "" || smUrl.Scheme == "file" { - if f, err := os.Open(smUrl.Path); err == nil { - if d, err := ioutil.ReadAll(f); err == nil { - data = d - } + var smUrl *url.URL + if smUrl, err = url.Parse(urlStr); err == nil { + p := smUrl.Path + if !strings.HasPrefix(p, "/") { + baseName := self.file.Name() + baseUrl, err1 := url.Parse(baseName) + if err1 == nil && baseUrl.Scheme != "" { + baseUrl.Path = path.Join(path.Dir(baseUrl.Path), p) + p = baseUrl.String() + } else { + p = path.Join(path.Dir(baseName), p) } + } + if self.opts.sourceMapLoader != nil { + data, err = self.opts.sourceMapLoader(p) } else { - // Not implemented - compile error? - return nil + if smUrl.Scheme == "" || smUrl.Scheme == "file" { + data, err = ioutil.ReadFile(p) + } else { + err = fmt.Errorf("unsupported source map URL scheme: %s", smUrl.Scheme) + } } } } + if err != nil { + self.error(file.Idx(0), "Could not load source map: %v", err) + return nil + } if data == nil { return nil } if sm, err := sourcemap.Parse(self.file.Name(), data); err == nil { return sm + } else { + self.error(file.Idx(0), "Could not parse source map: %v", err) } } return nil diff --git a/parser/testutil_test.go b/parser/testutil_test.go index ac2ebae5..5dc9704f 100644 --- a/parser/testutil_test.go +++ b/parser/testutil_test.go @@ -12,8 +12,28 @@ import ( func tt(t *testing.T, f func()) { defer func() { if x := recover(); x != nil { - _, file, line, _ := runtime.Caller(4) - t.Errorf("Error at %s:%d: %v", filepath.Base(file), line, x) + pcs := make([]uintptr, 16) + pcs = pcs[:runtime.Callers(1, pcs)] + frames := runtime.CallersFrames(pcs) + var file string + var line int + for { + frame, more := frames.Next() + // The line number here must match the line where f() is called (see below) + if frame.Line == 40 && filepath.Base(frame.File) == "testutil_test.go" { + break + } + + if !more { + break + } + file, line = frame.File, frame.Line + } + if line > 0 { + t.Errorf("Error at %s:%d: %v", filepath.Base(file), line, x) + } else { + t.Errorf("Error at : %v", x) + } } }() diff --git a/runtime.go b/runtime.go index a29b6d4f..ba0d4be2 100644 --- a/runtime.go +++ b/runtime.go @@ -161,6 +161,7 @@ type Runtime struct { rand RandSource now Now _collator *collate.Collator + parserOptions []parser.Option symbolRegistry map[unistring.String]*Symbol @@ -1111,8 +1112,17 @@ func MustCompile(name, src string, strict bool) *Program { return prg } -func compile(name, src string, strict, eval bool) (p *Program, err error) { - prg, err1 := parser.ParseFile(nil, name, src, 0) +// Parse takes a source string and produces a parsed AST. Use this function if you want to pass options +// to the parser, e.g.: +// +// p, err := Parse("test.js", "var a = true", parser.WithDisableSourceMaps) +// if err != nil { /* ... */ } +// prg, err := CompileAST(p, true) +// // ... +// +// Otherwise use Compile which combines both steps. +func Parse(name, src string, options ...parser.Option) (prg *js_ast.Program, err error) { + prg, err1 := parser.ParseFile(nil, name, src, 0, options...) if err1 != nil { switch err1 := err1.(type) { case parser.ErrorList: @@ -1131,12 +1141,17 @@ func compile(name, src string, strict, eval bool) (p *Program, err error) { Message: err1.Error(), }, } - return } + return +} - p, err = compileAST(prg, strict, eval) +func compile(name, src string, strict, eval bool, parserOptions ...parser.Option) (p *Program, err error) { + prg, err := Parse(name, src, parserOptions...) + if err != nil { + return + } - return + return compileAST(prg, strict, eval) } func compileAST(prg *js_ast.Program, strict, eval bool) (p *Program, err error) { @@ -1162,7 +1177,7 @@ func compileAST(prg *js_ast.Program, strict, eval bool) (p *Program, err error) } func (r *Runtime) compile(name, src string, strict, eval bool) (p *Program, err error) { - p, err = compile(name, src, strict, eval) + p, err = compile(name, src, strict, eval, r.parserOptions...) if err != nil { switch x1 := err.(type) { case *CompilerSyntaxError: @@ -1185,7 +1200,7 @@ func (r *Runtime) RunString(str string) (Value, error) { // RunScript executes the given string in the global context. func (r *Runtime) RunScript(name, src string) (Value, error) { - p, err := Compile(name, src, false) + p, err := r.compile(name, src, false, false) if err != nil { return nil, err @@ -1984,6 +1999,11 @@ func (r *Runtime) SetTimeSource(now Now) { r.now = now } +// SetParserOptions sets parser options to be used by RunString, RunScript and eval() within the code. +func (r *Runtime) SetParserOptions(opts ...parser.Option) { + r.parserOptions = opts +} + // New is an equivalent of the 'new' operator allowing to call it directly from Go. func (r *Runtime) New(construct Value, args ...Value) (o *Object, err error) { err = tryFunc(func() { diff --git a/runtime_test.go b/runtime_test.go index 90caf848..611115ca 100644 --- a/runtime_test.go +++ b/runtime_test.go @@ -9,6 +9,8 @@ import ( "strconv" "testing" "time" + + "github.com/dop251/goja/parser" ) func TestGlobalObjectProto(t *testing.T) { @@ -1819,6 +1821,33 @@ func ExampleRuntime_NewArray() { // Output: 1 2 true } +func ExampleRuntime_SetParserOptions() { + vm := New() + vm.SetParserOptions(parser.WithDisableSourceMaps) + + res, err := vm.RunString(` + "I did not hang!"; +//# sourceMappingURL=/dev/zero`) + + if err != nil { + panic(err) + } + fmt.Println(res.String()) + // Output: I did not hang! +} + +func TestRuntime_SetParserOptions_Eval(t *testing.T) { + vm := New() + vm.SetParserOptions(parser.WithDisableSourceMaps) + + _, err := vm.RunString(` + eval("//# sourceMappingURL=/dev/zero"); + `) + if err != nil { + t.Fatal(err) + } +} + /* func TestArrayConcatSparse(t *testing.T) { function foo(a,b,c) From 6b6d5e2b5d800b95120ecc370025f00a64e9ea1e Mon Sep 17 00:00:00 2001 From: Dmitry Panov Date: Mon, 21 Dec 2020 18:39:57 +0000 Subject: [PATCH 7/7] Use source name provided by source map. See #235. --- ast/node.go | 3 - compiler.go | 6 +- file/file.go | 118 ++++++++++++++++++++++----- srcfile_test.go => file/file_test.go | 12 +-- parser/parser.go | 38 +-------- parser/statement.go | 7 +- runtime.go | 19 +++-- srcfile.go | 92 --------------------- vm.go | 2 +- 9 files changed, 124 insertions(+), 173 deletions(-) rename srcfile_test.go => file/file_test.go (62%) delete mode 100644 srcfile.go diff --git a/ast/node.go b/ast/node.go index a0d28559..9472e4ac 100644 --- a/ast/node.go +++ b/ast/node.go @@ -13,7 +13,6 @@ import ( "github.com/dop251/goja/file" "github.com/dop251/goja/token" "github.com/dop251/goja/unistring" - "github.com/go-sourcemap/sourcemap" ) // All nodes implement the Node interface. @@ -399,8 +398,6 @@ type Program struct { DeclarationList []Declaration File *file.File - - SourceMap *sourcemap.Consumer } // ==== // diff --git a/compiler.go b/compiler.go index 9375ef4d..3843a74e 100644 --- a/compiler.go +++ b/compiler.go @@ -21,7 +21,7 @@ const ( type CompilerError struct { Message string - File *SrcFile + File *file.File Offset int } @@ -43,7 +43,7 @@ type Program struct { values []Value funcName unistring.String - src *SrcFile + src *file.File srcMap []srcMapItem } @@ -250,7 +250,7 @@ func (c *compiler) markBlockStart() { } func (c *compiler) compile(in *ast.Program) { - c.p.src = NewSrcFile(in.File.Name(), in.File.Source(), in.SourceMap) + c.p.src = in.File if len(in.Body) > 0 { if !c.scope.strict { diff --git a/file/file.go b/file/file.go index 76524ac3..78ae1ad9 100644 --- a/file/file.go +++ b/file/file.go @@ -4,7 +4,11 @@ package file import ( "fmt" - "strings" + "path" + "sort" + "sync" + + "github.com/go-sourcemap/sourcemap" ) // Idx is a compact encoding of a source position within a file set. @@ -16,7 +20,6 @@ type Idx int // including the filename, line, and column location. type Position struct { Filename string // The filename where the error occurred, if any - Offset int // The src offset Line int // The line number, starting at 1 Column int // The column number, starting at 1 (The character count) @@ -35,7 +38,7 @@ func (self *Position) isValid() bool { // file An invalid position with filename // - An invalid position without filename // -func (self *Position) String() string { +func (self Position) String() string { str := self.Filename if self.isValid() { if str != "" { @@ -89,29 +92,23 @@ func (self *FileSet) File(idx Idx) *File { } // Position converts an Idx in the FileSet into a Position. -func (self *FileSet) Position(idx Idx) *Position { - position := &Position{} +func (self *FileSet) Position(idx Idx) Position { for _, file := range self.files { if idx <= Idx(file.base+len(file.src)) { - offset := int(idx) - file.base - src := file.src[:offset] - position.Filename = file.name - position.Offset = offset - position.Line = 1 + strings.Count(src, "\n") - if index := strings.LastIndex(src, "\n"); index >= 0 { - position.Column = offset - index - } else { - position.Column = 1 + len(src) - } + return file.Position(int(idx) - file.base) } } - return position + return Position{} } type File struct { - name string - src string - base int // This will always be 1 or greater + mu sync.Mutex + name string + src string + base int // This will always be 1 or greater + sourceMap *sourcemap.Consumer + lineOffsets []int + lastScannedOffset int } func NewFile(filename, src string, base int) *File { @@ -133,3 +130,86 @@ func (fl *File) Source() string { func (fl *File) Base() int { return fl.base } + +func (fl *File) SetSourceMap(m *sourcemap.Consumer) { + fl.sourceMap = m +} + +func (fl *File) Position(offset int) Position { + var line int + var lineOffsets []int + fl.mu.Lock() + if offset > fl.lastScannedOffset { + line = fl.scanTo(offset) + lineOffsets = fl.lineOffsets + fl.mu.Unlock() + } else { + lineOffsets = fl.lineOffsets + fl.mu.Unlock() + line = sort.Search(len(lineOffsets), func(x int) bool { return lineOffsets[x] > offset }) - 1 + } + + var lineStart int + if line >= 0 { + lineStart = lineOffsets[line] + } + + row := line + 2 + col := offset - lineStart + 1 + + if fl.sourceMap != nil { + if source, _, row, col, ok := fl.sourceMap.Source(row, col); ok { + if !path.IsAbs(source) { + source = path.Join(path.Dir(fl.name), source) + } + return Position{ + Filename: source, + Line: row, + Column: col, + } + } + } + + return Position{ + Filename: fl.name, + Line: row, + Column: col, + } +} + +func findNextLineStart(s string) int { + for pos, ch := range s { + switch ch { + case '\r': + if pos < len(s)-1 && s[pos+1] == '\n' { + return pos + 2 + } + return pos + 1 + case '\n': + return pos + 1 + case '\u2028', '\u2029': + return pos + 3 + } + } + return -1 +} + +func (fl *File) scanTo(offset int) int { + o := fl.lastScannedOffset + for o < offset { + p := findNextLineStart(fl.src[o:]) + if p == -1 { + fl.lastScannedOffset = len(fl.src) + return len(fl.lineOffsets) - 1 + } + o = o + p + fl.lineOffsets = append(fl.lineOffsets, o) + } + fl.lastScannedOffset = o + + if o == offset { + return len(fl.lineOffsets) - 1 + } + + return len(fl.lineOffsets) - 2 +} diff --git a/srcfile_test.go b/file/file_test.go similarity index 62% rename from srcfile_test.go rename to file/file_test.go index 235384f9..840b9715 100644 --- a/srcfile_test.go +++ b/file/file_test.go @@ -1,4 +1,4 @@ -package goja +package file import "testing" @@ -6,7 +6,7 @@ func TestPosition(t *testing.T) { const SRC = `line1 line2 line3` - f := NewSrcFile("", SRC, nil) + f := NewFile("", SRC, 0) tests := []struct { offset int @@ -27,17 +27,17 @@ line3` } for i, test := range tests { - if p := f.Position(test.offset); p.Line != test.line || p.Col != test.col { - t.Fatalf("%d. Line: %d, col: %d", i, p.Line, p.Col) + if p := f.Position(test.offset); p.Line != test.line || p.Column != test.col { + t.Fatalf("%d. Line: %d, col: %d", i, p.Line, p.Column) } } } -func TestSrcFileConcurrency(t *testing.T) { +func TestFileConcurrency(t *testing.T) { const SRC = `line1 line2 line3` - f := NewSrcFile("", SRC, nil) + f := NewFile("", SRC, 0) go func() { f.Position(12) }() diff --git a/parser/parser.go b/parser/parser.go index bf596c37..b1bc74bb 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -264,42 +264,6 @@ func (self *_parser) expect(value token.Token) file.Idx { return idx } -func lineCount(str string) (int, int) { - line, last := 0, -1 - pair := false - for index, chr := range str { - switch chr { - case '\r': - line += 1 - last = index - pair = true - continue - case '\n': - if !pair { - line += 1 - } - last = index - case '\u2028', '\u2029': - line += 1 - last = index + 2 - } - pair = false - } - return line, last -} - func (self *_parser) position(idx file.Idx) file.Position { - position := file.Position{} - offset := int(idx) - self.base - str := self.str[:offset] - position.Filename = self.file.Name() - line, last := lineCount(str) - position.Line = 1 + line - if last >= 0 { - position.Column = offset - last - } else { - position.Column = 1 + len(str) - } - - return position + return self.file.Position(int(idx) - self.base) } diff --git a/parser/statement.go b/parser/statement.go index 3cdff179..84ce7fd9 100644 --- a/parser/statement.go +++ b/parser/statement.go @@ -579,12 +579,13 @@ func (self *_parser) parseSourceElements() []ast.Statement { func (self *_parser) parseProgram() *ast.Program { self.openScope() defer self.closeScope() - return &ast.Program{ + prg := &ast.Program{ Body: self.parseSourceElements(), DeclarationList: self.scope.declarationList, File: self.file, - SourceMap: self.parseSourceMap(), } + self.file.SetSourceMap(self.parseSourceMap()) + return prg } func extractSourceMapLine(str string) string { @@ -624,7 +625,7 @@ func (self *_parser) parseSourceMap() *sourcemap.Consumer { var smUrl *url.URL if smUrl, err = url.Parse(urlStr); err == nil { p := smUrl.Path - if !strings.HasPrefix(p, "/") { + if !path.IsAbs(p) { baseName := self.file.Name() baseUrl, err1 := url.Parse(baseName) if err1 == nil && baseUrl.Scheme != "" { diff --git a/runtime.go b/runtime.go index ba0d4be2..1b498e37 100644 --- a/runtime.go +++ b/runtime.go @@ -4,6 +4,7 @@ import ( "bytes" "errors" "fmt" + "github.com/dop251/goja/file" "go/ast" "hash/maphash" "math" @@ -193,7 +194,7 @@ func (f *StackFrame) SrcName() string { if f.prg == nil { return "" } - return f.prg.src.name + return f.prg.src.Name() } func (f *StackFrame) FuncName() string { @@ -206,12 +207,9 @@ func (f *StackFrame) FuncName() string { return f.funcName.String() } -func (f *StackFrame) Position() Position { +func (f *StackFrame) Position() file.Position { if f.prg == nil || f.prg.src == nil { - return Position{ - 0, - 0, - } + return file.Position{} } return f.prg.src.Position(f.prg.sourceOffset(f.pc)) } @@ -222,13 +220,16 @@ func (f *StackFrame) Write(b *bytes.Buffer) { b.WriteString(n.String()) b.WriteString(" (") } - if n := f.prg.src.name; n != "" { - b.WriteString(n) + p := f.Position() + if p.Filename != "" { + b.WriteString(p.Filename) } else { b.WriteString("") } b.WriteByte(':') - b.WriteString(f.Position().String()) + b.WriteString(strconv.Itoa(p.Line)) + b.WriteByte(':') + b.WriteString(strconv.Itoa(p.Column)) b.WriteByte('(') b.WriteString(strconv.Itoa(f.pc)) b.WriteByte(')') diff --git a/srcfile.go b/srcfile.go deleted file mode 100644 index 8031b863..00000000 --- a/srcfile.go +++ /dev/null @@ -1,92 +0,0 @@ -package goja - -import ( - "fmt" - "github.com/go-sourcemap/sourcemap" - "sort" - "strings" - "sync" -) - -type Position struct { - Line, Col int -} - -type SrcFile struct { - name string - src string - - lineOffsets []int - lineOffsetsLock sync.Mutex - lastScannedOffset int - sourceMap *sourcemap.Consumer -} - -func NewSrcFile(name, src string, sourceMap *sourcemap.Consumer) *SrcFile { - return &SrcFile{ - name: name, - src: src, - sourceMap: sourceMap, - } -} - -func (f *SrcFile) Position(offset int) Position { - var line int - var lineOffsets []int - f.lineOffsetsLock.Lock() - if offset > f.lastScannedOffset { - line = f.scanTo(offset) - lineOffsets = f.lineOffsets - f.lineOffsetsLock.Unlock() - } else { - lineOffsets = f.lineOffsets - f.lineOffsetsLock.Unlock() - line = sort.Search(len(lineOffsets), func(x int) bool { return lineOffsets[x] > offset }) - 1 - } - - var lineStart int - if line >= 0 { - lineStart = lineOffsets[line] - } - - row := line + 2 - col := offset - lineStart + 1 - - if f.sourceMap != nil { - if _, _, row, col, ok := f.sourceMap.Source(row, col); ok { - return Position{ - Line: row, - Col: col, - } - } - } - - return Position{ - Line: row, - Col: col, - } -} - -func (f *SrcFile) scanTo(offset int) int { - o := f.lastScannedOffset - for o < offset { - p := strings.Index(f.src[o:], "\n") - if p == -1 { - f.lastScannedOffset = len(f.src) - return len(f.lineOffsets) - 1 - } - o = o + p + 1 - f.lineOffsets = append(f.lineOffsets, o) - } - f.lastScannedOffset = o - - if o == offset { - return len(f.lineOffsets) - 1 - } - - return len(f.lineOffsets) - 2 -} - -func (p Position) String() string { - return fmt.Sprintf("%d:%d", p.Line, p.Col) -} diff --git a/vm.go b/vm.go index 713b4c49..d232ab60 100644 --- a/vm.go +++ b/vm.go @@ -1946,7 +1946,7 @@ func (n *newFunc) exec(vm *vm) { obj := vm.r.newFunc(n.name, int(n.length), n.strict) obj.prg = n.prg obj.stash = vm.stash - obj.src = n.prg.src.src[n.srcStart:n.srcEnd] + obj.src = n.prg.src.Source()[n.srcStart:n.srcEnd] vm.push(obj.val) vm.pc++ }