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/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/builtin_array.go b/builtin_array.go index e9fb0283..f5ab4427 100644 --- a/builtin_array.go +++ b/builtin_array.go @@ -39,7 +39,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 } @@ -260,7 +260,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() } @@ -1059,7 +1059,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 } @@ -1098,7 +1098,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 { @@ -1242,7 +1242,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) @@ -1253,7 +1253,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 } @@ -1263,7 +1263,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, @@ -1276,7 +1276,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 eb6194dd..f76f95cd 100644 --- a/builtin_date.go +++ b/builtin_date.go @@ -992,7 +992,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 c59fde6d..d7367976 100644 --- a/builtin_function.go +++ b/builtin_function.go @@ -195,7 +195,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 0ac48088..71ff5aab 100644 --- a/builtin_json.go +++ b/builtin_json.go @@ -505,7 +505,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 d6682b13..9fd93363 100644 --- a/builtin_map.go +++ b/builtin_map.go @@ -277,15 +277,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, @@ -298,7 +298,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 97ee7ee3..83a98245 100644 --- a/builtin_object.go +++ b/builtin_object.go @@ -461,7 +461,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() } @@ -585,8 +585,8 @@ func (r *Runtime) initObject() { o._putProp("setPrototypeOf", r.newNativeFunc(r.object_setPrototypeOf, nil, "setPrototypeOf", nil, 2), true, false, true) entriesFunc := r.newNativeFunc(r.object_entries, nil, "entries", nil, 1) - o._putSym(symIterator, valueProp(entriesFunc, true, false, true)) - o._putSym(symToStringTag, valueProp(asciiString(classObject), false, false, true)) + o._putSym(SymIterator, valueProp(entriesFunc, true, false, true)) + o._putSym(SymToStringTag, valueProp(asciiString(classObject), false, false, true)) o._putProp("values", r.newNativeFunc(r.object_values, nil, "values", nil, 1), true, false, true) o._putProp("entries", r.newNativeFunc(r.object_entries, nil, "entries", nil, 1), true, false, true) 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 cd3e56f2..9d34e937 100644 --- a/builtin_regexp.go +++ b/builtin_regexp.go @@ -1147,15 +1147,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 2c86db43..623ece68 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 a74bf1e1..43d4f275 100644 --- a/builtin_string.go +++ b/builtin_string.go @@ -27,8 +27,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() } @@ -345,7 +345,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{ ctx: r.vm.ctx, This: regexp, @@ -363,7 +363,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{ ctx: r.vm.ctx, This: rx.val, @@ -628,7 +628,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{ ctx: r.vm.ctx, This: searchValue, @@ -653,7 +653,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{ ctx: r.vm.ctx, This: regexp, @@ -671,7 +671,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{ ctx: r.vm.ctx, This: rx.val, @@ -728,7 +728,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{ ctx: r.vm.ctx, This: separatorValue, @@ -910,7 +910,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 } @@ -950,7 +950,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 04217ed1..5a10815c 100644 --- a/builtin_typedarrays.go +++ b/builtin_typedarrays.go @@ -1115,7 +1115,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 @@ -1280,14 +1280,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, @@ -1331,7 +1331,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 } @@ -1392,8 +1392,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, @@ -1406,7 +1406,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 b901b528..ed7f6ee4 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 0ed7fb1b..57f7239f 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/compiler.go b/compiler.go index 89195381..5ee6c14c 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 } @@ -267,7 +267,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/func.go b/func.go index fdfaa855..c31b4597 100644 --- a/func.go +++ b/func.go @@ -221,7 +221,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 { @@ -234,6 +234,7 @@ func (f *nativeFuncObject) defaultConstruct(ccall func(ConstructorCall) *Object, ctx: f.ctx, This: obj, Arguments: args, + NewTarget: newTarget, }) if ret != nil { 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 f8f350fc..66ed9d35 100644 --- a/object.go +++ b/object.go @@ -226,35 +226,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 @@ -276,7 +276,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) MemUsage(ctx *MemUsageContext) (uint64, error) } @@ -323,6 +323,7 @@ type ConstructorCall struct { ctx context.Context This *Object Arguments []Value + NewTarget *Object } func (f FunctionCall) Context() context.Context { @@ -369,7 +370,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 } @@ -415,7 +416,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) } @@ -442,7 +443,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) } @@ -489,7 +490,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) { @@ -567,7 +568,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) @@ -658,7 +659,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) @@ -685,7 +686,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) } @@ -820,7 +821,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) @@ -861,7 +862,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) } @@ -926,7 +927,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{ ctx: o.runtime.ctx, @@ -1242,7 +1243,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{ ctx: c.runtime.ctx, This: c, @@ -1257,7 +1258,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) @@ -1268,7 +1269,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()) @@ -1279,7 +1280,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()) @@ -1290,7 +1291,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()) @@ -1338,7 +1339,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) @@ -1349,7 +1350,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) @@ -1393,7 +1394,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 { @@ -1434,7 +1435,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) @@ -1445,7 +1446,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 c5e71f0a..c2080d2b 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/parser/parser.go b/parser/parser.go index a5784d33..b1bc74bb 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 @@ -233,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/parser_test.go b/parser/parser_test.go index cd2303fd..ea7ae0ed 100644 --- a/parser/parser_test.go +++ b/parser/parser_test.go @@ -1014,3 +1014,61 @@ 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") + }) +} + +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 45581cec..84ce7fd9 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" ) @@ -578,48 +579,86 @@ 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 { + 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 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 !path.IsAbs(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/proxy.go b/proxy.go index 509a6f77..240c829d 100644 --- a/proxy.go +++ b/proxy.go @@ -249,7 +249,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 } @@ -292,7 +292,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 } @@ -308,7 +308,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 } @@ -382,7 +382,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 } @@ -404,7 +404,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 } @@ -473,7 +473,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 } @@ -494,7 +494,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 } @@ -533,7 +533,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 } @@ -558,7 +558,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())) } } @@ -720,13 +720,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 @@ -747,7 +747,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 0e3824c0..0c8a02ae 100644 --- a/runtime.go +++ b/runtime.go @@ -5,6 +5,7 @@ import ( "context" "errors" "fmt" + "github.com/dop251/goja/file" "go/ast" "hash/maphash" "math" @@ -165,8 +166,9 @@ type Runtime struct { rand RandSource now Now _collator *collate.Collator + parserOptions []parser.Option - symbolRegistry map[unistring.String]*valueSymbol + symbolRegistry map[unistring.String]*Symbol typeInfoCache map[reflect.Type]*reflectTypeInfo fieldNameMapper FieldNameMapper @@ -210,7 +212,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 { @@ -223,12 +225,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)) } @@ -239,13 +238,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(')') @@ -365,7 +367,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 } @@ -515,6 +517,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 { @@ -589,11 +599,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 @@ -1176,8 +1197,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: @@ -1196,12 +1226,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) { @@ -1227,7 +1262,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: @@ -1250,7 +1285,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 @@ -1356,7 +1391,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 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. 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 @@ -2021,6 +2087,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() { @@ -2210,7 +2281,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 @@ -2221,7 +2292,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 @@ -2258,7 +2329,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")) } @@ -2397,7 +2468,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 103b2507..76474352 100644 --- a/runtime_test.go +++ b/runtime_test.go @@ -8,6 +8,8 @@ import ( "strconv" "testing" "time" + + "github.com/dop251/goja/parser" ) func TestGlobalObjectProto(t *testing.T) { @@ -1714,6 +1716,139 @@ 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 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 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 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) 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/value.go b/value.go index 5ae12a47..b0b90484 100644 --- a/value.go +++ b/value.go @@ -136,7 +136,12 @@ func UndefinedValue() Value { 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 } @@ -1151,6 +1156,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)) @@ -1161,6 +1172,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 { @@ -1187,18 +1207,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) { @@ -1357,111 +1415,111 @@ func (o valueUnresolved) assertString() (valueString, bool) { return nil, false } -func (s *valueSymbol) IsNumber() bool { +func (s *Symbol) IsNumber() bool { return false } -func (s *valueSymbol) IsObject() bool { +func (s *Symbol) IsObject() bool { return false } -func (s *valueSymbol) ToInteger() int64 { +func (s *Symbol) ToInteger() int64 { panic(typeError("Cannot convert a Symbol value to a number")) } -func (s *valueSymbol) ToInt() int { +func (s *Symbol) ToInt() int { panic(typeError("Cannot convert a Symbol value to a number")) } -func (s *valueSymbol) ToInt32() int32 { +func (s *Symbol) ToInt32() int32 { panic(typeError("Cannot convert a Symbol value to a number")) } -func (s *valueSymbol) ToUInt32() uint32 { +func (s *Symbol) ToUInt32() uint32 { panic(typeError("Cannot convert a Symbol value to a number")) } -func (s *valueSymbol) ToInt64() int64 { +func (s *Symbol) ToInt64() int64 { panic(typeError("Cannot convert a Symbol value to a number")) } -func (s *valueSymbol) assertFloat() (float64, bool) { +func (s *Symbol) assertFloat() (float64, bool) { return 0, false } -func (s *valueSymbol) assertInt() (int, bool) { +func (s *Symbol) assertInt() (int, bool) { return 0, false } -func (s *valueSymbol) assertInt32() (int32, bool) { +func (s *Symbol) assertInt32() (int32, bool) { return 0, false } -func (s *valueSymbol) assertUInt32() (uint32, bool) { +func (s *Symbol) assertUInt32() (uint32, bool) { return 0, false } -func (s *valueSymbol) assertInt64() (int64, bool) { +func (s *Symbol) assertInt64() (int64, bool) { return 0, false } -func (s *valueSymbol) assertString() (valueString, bool) { +func (s *Symbol) assertString() (valueString, bool) { return nil, false } -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) } -func (s *valueSymbol) MemUsage(ctx *MemUsageContext) (uint64, error) { +func (s *Symbol) MemUsage(ctx *MemUsageContext) (uint64, error) { return 0, nil } @@ -1472,9 +1530,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 @@ -1484,6 +1542,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 5f02350f..4e30cd2d 100644 --- a/vm.go +++ b/vm.go @@ -238,7 +238,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() } @@ -2062,7 +2062,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++ } @@ -2488,7 +2488,7 @@ func (_typeof) exec(vm *vm) { r = stringString case valueInt, valueFloat, valueNumber, valueInt32, valueInt64, valueUInt32: r = stringNumber - case *valueSymbol: + case *Symbol: r = stringSymbol default: panic(fmt.Errorf("Unknown type: %T", v))