Browse Source

Implemented Symbol (#128)

Dmitry Panov 5 years ago
parent
commit
37522a24db
27 changed files with 1528 additions and 404 deletions
  1. 58 5
      array.go
  2. 7 4
      array_sparse.go
  3. 106 9
      builtin_array.go
  4. 1 0
      builtin_function.go
  5. 71 14
      builtin_object.go
  6. 366 7
      builtin_regexp.go
  7. 86 126
      builtin_string.go
  8. 57 0
      builtin_string_test.go
  9. 139 0
      builtin_symbol.go
  10. 2 1
      compiler_expr.go
  11. 31 0
      date_test.go
  12. 12 8
      func.go
  13. 189 22
      object.go
  14. 13 9
      object_args.go
  15. 12 5
      object_gomap.go
  16. 48 17
      object_gomap_reflect.go
  17. 31 29
      object_goreflect.go
  18. 3 3
      object_goslice.go
  19. 3 3
      object_goslice_reflect.go
  20. 13 1
      object_lazy.go
  21. 8 9
      regexp.go
  22. 100 55
      runtime.go
  23. 21 0
      runtime_test.go
  24. 3 2
      string.go
  25. 50 24
      tc39_test.go
  26. 78 1
      value.go
  27. 20 50
      vm.go

+ 58 - 5
array.go

@@ -6,6 +6,55 @@ import (
 	"strconv"
 	"strconv"
 )
 )
 
 
+type arrayIterObject struct {
+	baseObject
+	obj     *Object
+	nextIdx int64
+	kind    iterationKind
+}
+
+func (ai *arrayIterObject) next() Value {
+	if ai.obj == nil {
+		return ai.val.runtime.createIterResultObject(_undefined, true)
+	}
+	l := toLength(ai.obj.self.getStr("length"))
+	index := ai.nextIdx
+	if index >= l {
+		ai.obj = nil
+		return ai.val.runtime.createIterResultObject(_undefined, true)
+	}
+	ai.nextIdx++
+	if ai.kind == iterationKindKey {
+		return ai.val.runtime.createIterResultObject(intToValue(index), false)
+	}
+	elementKey := asciiString(strconv.FormatInt(index, 10))
+	elementValue := ai.obj.self.get(intToValue(index))
+	var result Value
+	if ai.kind == iterationKindValue {
+		result = elementValue
+	} else {
+		result = ai.val.runtime.newArrayValues([]Value{elementKey, elementValue})
+	}
+	return ai.val.runtime.createIterResultObject(result, false)
+}
+
+func (r *Runtime) createArrayIterator(iterObj *Object, kind iterationKind) Value {
+	o := &Object{runtime: r}
+
+	ai := &arrayIterObject{
+		obj:  iterObj,
+		kind: kind,
+	}
+	ai.class = classArrayIterator
+	ai.val = o
+	ai.extensible = true
+	o.self = ai
+	ai.prototype = r.global.ArrayIteratorPrototype
+	ai.init()
+
+	return o
+}
+
 type arrayObject struct {
 type arrayObject struct {
 	baseObject
 	baseObject
 	values         []Value
 	values         []Value
@@ -129,6 +178,9 @@ func toIdx(v Value) (idx int64) {
 	if idxVal, ok1 := v.(valueInt); ok1 {
 	if idxVal, ok1 := v.(valueInt); ok1 {
 		idx = int64(idxVal)
 		idx = int64(idxVal)
 	} else {
 	} else {
+		if _, ok := v.(*valueSymbol); ok {
+			return -1
+		}
 		if i, err := strconv.ParseInt(v.String(), 10, 64); err == nil {
 		if i, err := strconv.ParseInt(v.String(), 10, 64); err == nil {
 			idx = i
 			idx = i
 		}
 		}
@@ -155,9 +207,10 @@ func (a *arrayObject) getProp(n Value) Value {
 	if idx := toIdx(n); idx >= 0 {
 	if idx := toIdx(n); idx >= 0 {
 		return a.getIdx(idx, "", n)
 		return a.getIdx(idx, "", n)
 	}
 	}
-
-	if n.String() == "length" {
-		return a.getLengthProp()
+	if _, ok := n.(*valueSymbol); !ok {
+		if n.String() == "length" {
+			return a.getLengthProp()
+		}
 	}
 	}
 	return a.baseObject.getProp(n)
 	return a.baseObject.getProp(n)
 }
 }
@@ -177,7 +230,7 @@ func (a *arrayObject) getPropStr(name string) Value {
 	return a.baseObject.getPropStr(name)
 	return a.baseObject.getPropStr(name)
 }
 }
 
 
-func (a *arrayObject) getOwnProp(name string) Value {
+func (a *arrayObject) getOwnPropStr(name string) Value {
 	if i := strToIdx(name); i >= 0 {
 	if i := strToIdx(name); i >= 0 {
 		if i >= 0 && i < int64(len(a.values)) {
 		if i >= 0 && i < int64(len(a.values)) {
 			return a.values[i]
 			return a.values[i]
@@ -186,7 +239,7 @@ func (a *arrayObject) getOwnProp(name string) Value {
 	if name == "length" {
 	if name == "length" {
 		return a.getLengthProp()
 		return a.getLengthProp()
 	}
 	}
-	return a.baseObject.getOwnProp(name)
+	return a.baseObject.getOwnPropStr(name)
 }
 }
 
 
 func (a *arrayObject) putIdx(idx int64, val Value, throw bool, origNameStr string, origName Value) {
 func (a *arrayObject) putIdx(idx int64, val Value, throw bool, origNameStr string, origName Value) {

+ 7 - 4
array_sparse.go

@@ -120,9 +120,12 @@ func (a *sparseArrayObject) getProp(n Value) Value {
 		return a.getIdx(idx, "", n)
 		return a.getIdx(idx, "", n)
 	}
 	}
 
 
-	if n.String() == "length" {
-		return a.getLengthProp()
+	if _, ok := n.(*valueSymbol); !ok {
+		if n.String() == "length" {
+			return a.getLengthProp()
+		}
 	}
 	}
+
 	return a.baseObject.getProp(n)
 	return a.baseObject.getProp(n)
 }
 }
 
 
@@ -131,7 +134,7 @@ func (a *sparseArrayObject) getLengthProp() Value {
 	return &a.lengthProp
 	return &a.lengthProp
 }
 }
 
 
-func (a *sparseArrayObject) getOwnProp(name string) Value {
+func (a *sparseArrayObject) getOwnPropStr(name string) Value {
 	if idx := strToIdx(name); idx >= 0 {
 	if idx := strToIdx(name); idx >= 0 {
 		i := a.findIdx(idx)
 		i := a.findIdx(idx)
 		if i < len(a.items) && a.items[i].idx == idx {
 		if i < len(a.items) && a.items[i].idx == idx {
@@ -142,7 +145,7 @@ func (a *sparseArrayObject) getOwnProp(name string) Value {
 	if name == "length" {
 	if name == "length" {
 		return a.getLengthProp()
 		return a.getLengthProp()
 	}
 	}
-	return a.baseObject.getOwnProp(name)
+	return a.baseObject.getOwnPropStr(name)
 }
 }
 
 
 func (a *sparseArrayObject) getPropStr(name string) Value {
 func (a *sparseArrayObject) getPropStr(name string) Value {

+ 106 - 9
builtin_array.go

@@ -6,11 +6,48 @@ import (
 	"strings"
 	"strings"
 )
 )
 
 
+func (r *Runtime) newArray(prototype *Object) (a *arrayObject) {
+	v := &Object{runtime: r}
+
+	a = &arrayObject{}
+	a.class = classArray
+	a.val = v
+	a.extensible = true
+	v.self = a
+	a.prototype = prototype
+	a.init()
+	return
+}
+
+func (r *Runtime) newArrayObject() *arrayObject {
+	return r.newArray(r.global.ArrayPrototype)
+}
+
+func setArrayValues(a *arrayObject, values []Value) *arrayObject {
+	a.values = values
+	a.length = int64(len(values))
+	a.objCount = a.length
+	return a
+}
+
+func setArrayLength(a *arrayObject, l int64) *arrayObject {
+	a.putStr("length", intToValue(l), true)
+	return a
+}
+
+func (r *Runtime) newArrayValues(values []Value) *Object {
+	return setArrayValues(r.newArrayObject(), values).val
+}
+
+func (r *Runtime) newArrayLength(l int64) *Object {
+	return setArrayLength(r.newArrayObject(), l).val
+}
+
 func (r *Runtime) builtin_newArray(args []Value, proto *Object) *Object {
 func (r *Runtime) builtin_newArray(args []Value, proto *Object) *Object {
 	l := len(args)
 	l := len(args)
 	if l == 1 {
 	if l == 1 {
 		if al, ok := args[0].assertInt(); ok {
 		if al, ok := args[0].assertInt(); ok {
-			return r.newArrayLength(al)
+			return setArrayLength(r.newArray(proto), al).val
 		} else if f, ok := args[0].assertFloat(); ok {
 		} else if f, ok := args[0].assertFloat(); ok {
 			al := int64(f)
 			al := int64(f)
 			if float64(al) == f {
 			if float64(al) == f {
@@ -19,11 +56,11 @@ func (r *Runtime) builtin_newArray(args []Value, proto *Object) *Object {
 				panic(r.newError(r.global.RangeError, "Invalid array length"))
 				panic(r.newError(r.global.RangeError, "Invalid array length"))
 			}
 			}
 		}
 		}
-		return r.newArrayValues([]Value{args[0]})
+		return setArrayValues(r.newArray(proto), []Value{args[0]}).val
 	} else {
 	} else {
 		argsCopy := make([]Value, l)
 		argsCopy := make([]Value, l)
 		copy(argsCopy, args)
 		copy(argsCopy, args)
-		return r.newArrayValues(argsCopy)
+		return setArrayValues(r.newArray(proto), argsCopy).val
 	}
 	}
 }
 }
 
 
@@ -47,7 +84,7 @@ func (r *Runtime) arrayproto_push(call FunctionCall) Value {
 	return r.generic_push(obj, call)
 	return r.generic_push(obj, call)
 }
 }
 
 
-func (r *Runtime) arrayproto_pop_generic(obj *Object, call FunctionCall) Value {
+func (r *Runtime) arrayproto_pop_generic(obj *Object) Value {
 	l := toLength(obj.self.getStr("length"))
 	l := toLength(obj.self.getStr("length"))
 	if l == 0 {
 	if l == 0 {
 		obj.self.putStr("length", intToValue(0), true)
 		obj.self.putStr("length", intToValue(0), true)
@@ -72,11 +109,11 @@ func (r *Runtime) arrayproto_pop(call FunctionCall) Value {
 			}
 			}
 			if val == nil {
 			if val == nil {
 				// optimisation bail-out
 				// optimisation bail-out
-				return r.arrayproto_pop_generic(obj, call)
+				return r.arrayproto_pop_generic(obj)
 			}
 			}
 			if _, ok := val.(*valueProperty); ok {
 			if _, ok := val.(*valueProperty); ok {
 				// optimisation bail-out
 				// optimisation bail-out
-				return r.arrayproto_pop_generic(obj, call)
+				return r.arrayproto_pop_generic(obj)
 			}
 			}
 			//a._setLengthInt(l, false)
 			//a._setLengthInt(l, false)
 			a.values[l] = nil
 			a.values[l] = nil
@@ -86,7 +123,7 @@ func (r *Runtime) arrayproto_pop(call FunctionCall) Value {
 		}
 		}
 		return _undefined
 		return _undefined
 	} else {
 	} else {
-		return r.arrayproto_pop_generic(obj, call)
+		return r.arrayproto_pop_generic(obj)
 	}
 	}
 }
 }
 
 
@@ -191,6 +228,14 @@ func (r *Runtime) arrayproto_toLocaleString(call FunctionCall) Value {
 
 
 }
 }
 
 
+func isConcatSpreadable(obj *Object) bool {
+	spreadable := obj.self.get(symIsConcatSpreadable)
+	if spreadable != nil && spreadable != _undefined {
+		return spreadable.ToBoolean()
+	}
+	return isArray(obj)
+}
+
 func (r *Runtime) arrayproto_concat_append(a *Object, item Value) {
 func (r *Runtime) arrayproto_concat_append(a *Object, item Value) {
 	descr := propertyDescr{
 	descr := propertyDescr{
 		Writable:     FLAG_TRUE,
 		Writable:     FLAG_TRUE,
@@ -200,7 +245,7 @@ func (r *Runtime) arrayproto_concat_append(a *Object, item Value) {
 
 
 	aLength := toLength(a.self.getStr("length"))
 	aLength := toLength(a.self.getStr("length"))
 	if obj, ok := item.(*Object); ok {
 	if obj, ok := item.(*Object); ok {
-		if isArray(obj) {
+		if isConcatSpreadable(obj) {
 			length := toLength(obj.self.getStr("length"))
 			length := toLength(obj.self.getStr("length"))
 			for i := int64(0); i < length; i++ {
 			for i := int64(0); i < length; i++ {
 				v := obj.self.get(intToValue(i))
 				v := obj.self.get(intToValue(i))
@@ -220,8 +265,29 @@ func (r *Runtime) arrayproto_concat_append(a *Object, item Value) {
 	a.self.defineOwnProperty(intToValue(aLength), descr, false)
 	a.self.defineOwnProperty(intToValue(aLength), descr, false)
 }
 }
 
 
+func arraySpeciesCreate(obj *Object, size int) *Object {
+	if isArray(obj) {
+		v := obj.self.getStr("constructor")
+		if constructObj, ok := v.(*Object); ok {
+			species := constructObj.self.get(symSpecies)
+			if species != nil && !IsUndefined(species) && !IsNull(species) {
+				constructObj, _ = species.(*Object)
+				if constructObj != nil {
+					constructor := getConstructor(constructObj)
+					if constructor != nil {
+						return constructor([]Value{intToValue(int64(size))})
+					}
+				}
+				panic(obj.runtime.NewTypeError())
+			}
+		}
+	}
+	return obj.runtime.newArrayValues(nil)
+}
+
 func (r *Runtime) arrayproto_concat(call FunctionCall) Value {
 func (r *Runtime) arrayproto_concat(call FunctionCall) Value {
-	a := r.newArrayValues(nil)
+	obj := call.This.ToObject(r)
+	a := arraySpeciesCreate(obj, 0)
 	r.arrayproto_concat_append(a, call.This.ToObject(r))
 	r.arrayproto_concat_append(a, call.This.ToObject(r))
 	for _, item := range call.Arguments {
 	for _, item := range call.Arguments {
 		r.arrayproto_concat_append(a, item)
 		r.arrayproto_concat_append(a, item)
@@ -758,6 +824,10 @@ func (r *Runtime) arrayproto_shift(call FunctionCall) Value {
 	return first
 	return first
 }
 }
 
 
+func (r *Runtime) arrayproto_values(call FunctionCall) Value {
+	return r.createArrayIterator(call.This.ToObject(r), iterationKindValue)
+}
+
 func (r *Runtime) array_isArray(call FunctionCall) Value {
 func (r *Runtime) array_isArray(call FunctionCall) Value {
 	if o, ok := call.Argument(0).(*Object); ok {
 	if o, ok := call.Argument(0).(*Object); ok {
 		if isArray(o) {
 		if isArray(o) {
@@ -767,6 +837,14 @@ func (r *Runtime) array_isArray(call FunctionCall) Value {
 	return valueFalse
 	return valueFalse
 }
 }
 
 
+func (r *Runtime) arrayIterProto_next(call FunctionCall) Value {
+	thisObj := r.toObject(call.This)
+	if iter, ok := thisObj.self.(*arrayIterObject); ok {
+		return iter.next()
+	}
+	panic(r.NewTypeError("Method Array Iterator.prototype.next called on incompatible receiver %s", thisObj.String()))
+}
+
 func (r *Runtime) createArrayProto(val *Object) objectImpl {
 func (r *Runtime) createArrayProto(val *Object) objectImpl {
 	o := &arrayObject{
 	o := &arrayObject{
 		baseObject: baseObject{
 		baseObject: baseObject{
@@ -800,6 +878,9 @@ func (r *Runtime) createArrayProto(val *Object) objectImpl {
 	o._putProp("filter", r.newNativeFunc(r.arrayproto_filter, nil, "filter", nil, 1), true, false, true)
 	o._putProp("filter", r.newNativeFunc(r.arrayproto_filter, nil, "filter", nil, 1), true, false, true)
 	o._putProp("reduce", r.newNativeFunc(r.arrayproto_reduce, nil, "reduce", nil, 1), true, false, true)
 	o._putProp("reduce", r.newNativeFunc(r.arrayproto_reduce, nil, "reduce", nil, 1), true, false, true)
 	o._putProp("reduceRight", r.newNativeFunc(r.arrayproto_reduceRight, nil, "reduceRight", nil, 1), true, false, true)
 	o._putProp("reduceRight", r.newNativeFunc(r.arrayproto_reduceRight, nil, "reduceRight", nil, 1), true, false, true)
+	valuesFunc := r.newNativeFunc(r.arrayproto_values, nil, "values", nil, 0)
+	o._putProp("values", valuesFunc, true, false, true)
+	o.put(symIterator, valueProp(valuesFunc, false, false, true), true)
 
 
 	return o
 	return o
 }
 }
@@ -807,10 +888,26 @@ func (r *Runtime) createArrayProto(val *Object) objectImpl {
 func (r *Runtime) createArray(val *Object) objectImpl {
 func (r *Runtime) createArray(val *Object) objectImpl {
 	o := r.newNativeFuncConstructObj(val, r.builtin_newArray, "Array", r.global.ArrayPrototype, 1)
 	o := r.newNativeFuncConstructObj(val, r.builtin_newArray, "Array", r.global.ArrayPrototype, 1)
 	o._putProp("isArray", r.newNativeFunc(r.array_isArray, nil, "isArray", nil, 1), true, false, true)
 	o._putProp("isArray", r.newNativeFunc(r.array_isArray, nil, "isArray", nil, 1), true, false, true)
+	o.putSym(symSpecies, &valueProperty{
+		getterFunc:   r.newNativeFunc(r.returnThis, nil, "get [Symbol.species]", nil, 0),
+		accessor:     true,
+		configurable: true,
+	}, true)
+
+	return o
+}
+
+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.put(symToStringTag, valueProp(asciiString(classArrayIterator), false, false, true), true)
+
 	return o
 	return o
 }
 }
 
 
 func (r *Runtime) initArray() {
 func (r *Runtime) initArray() {
+	r.global.ArrayIteratorPrototype = r.newLazyObject(r.createArrayIterProto)
 	//r.global.ArrayPrototype = r.newArray(r.global.ObjectPrototype).val
 	//r.global.ArrayPrototype = r.newArray(r.global.ObjectPrototype).val
 	//o := r.global.ArrayPrototype.self
 	//o := r.global.ArrayPrototype.self
 	r.global.ArrayPrototype = r.newLazyObject(r.createArrayProto)
 	r.global.ArrayPrototype = r.newLazyObject(r.createArrayProto)

+ 1 - 0
builtin_function.go

@@ -143,6 +143,7 @@ repeat:
 	ff := r.newNativeFuncObj(v, r.boundCallable(fcall, call.Arguments), r.boundConstruct(construct, call.Arguments), "", nil, l)
 	ff := r.newNativeFuncObj(v, r.boundCallable(fcall, call.Arguments), r.boundConstruct(construct, call.Arguments), "", nil, l)
 	v.self = &boundFuncObject{
 	v.self = &boundFuncObject{
 		nativeFuncObject: *ff,
 		nativeFuncObject: *ff,
+		wrapped:          obj,
 	}
 	}
 
 
 	//ret := r.newNativeFunc(r.boundCallable(f, call.Arguments), nil, "", nil, l)
 	//ret := r.newNativeFunc(r.boundCallable(f, call.Arguments), nil, "", nil, l)

+ 71 - 14
builtin_object.go

@@ -11,7 +11,7 @@ func (r *Runtime) builtin_Object(args []Value, proto *Object) *Object {
 			return arg.ToObject(r)
 			return arg.ToObject(r)
 		}
 		}
 	}
 	}
-	return r.NewObject()
+	return r.newBaseObject(proto, classObject).val
 }
 }
 
 
 func (r *Runtime) object_getPrototypeOf(call FunctionCall) Value {
 func (r *Runtime) object_getPrototypeOf(call FunctionCall) Value {
@@ -25,7 +25,7 @@ func (r *Runtime) object_getPrototypeOf(call FunctionCall) Value {
 
 
 func (r *Runtime) object_getOwnPropertyDescriptor(call FunctionCall) Value {
 func (r *Runtime) object_getOwnPropertyDescriptor(call FunctionCall) Value {
 	obj := call.Argument(0).ToObject(r)
 	obj := call.Argument(0).ToObject(r)
-	propName := call.Argument(1).String()
+	propName := call.Argument(1)
 	desc := obj.self.getOwnProp(propName)
 	desc := obj.self.getOwnProp(propName)
 	if desc == nil {
 	if desc == nil {
 		return _undefined
 		return _undefined
@@ -83,6 +83,11 @@ func (r *Runtime) object_getOwnPropertyNames(call FunctionCall) Value {
 	return r.newArrayValues(values)
 	return r.newArrayValues(values)
 }
 }
 
 
+func (r *Runtime) object_getOwnPropertySymbols(call FunctionCall) Value {
+	obj := call.Argument(0).ToObject(r)
+	return r.newArrayValues(obj.self.getOwnSymbols())
+}
+
 func (r *Runtime) toPropertyDescr(v Value) (ret propertyDescr) {
 func (r *Runtime) toPropertyDescr(v Value) (ret propertyDescr) {
 	if o, ok := v.(*Object); ok {
 	if o, ok := v.(*Object); ok {
 		descr := o.self
 		descr := o.self
@@ -188,7 +193,7 @@ func (r *Runtime) object_seal(call FunctionCall) Value {
 			Configurable: FLAG_FALSE,
 			Configurable: FLAG_FALSE,
 		}
 		}
 		for item, f := obj.self.enumerate(true, false)(); f != nil; item, f = f() {
 		for item, f := obj.self.enumerate(true, false)(); f != nil; item, f = f() {
-			v := obj.self.getOwnProp(item.name)
+			v := obj.self.getOwnPropStr(item.name)
 			if prop, ok := v.(*valueProperty); ok {
 			if prop, ok := v.(*valueProperty); ok {
 				if !prop.configurable {
 				if !prop.configurable {
 					continue
 					continue
@@ -200,6 +205,18 @@ func (r *Runtime) object_seal(call FunctionCall) Value {
 				//obj.self._putProp(item.name, v, true, true, false)
 				//obj.self._putProp(item.name, v, true, true, false)
 			}
 			}
 		}
 		}
+		for _, sym := range obj.self.getOwnSymbols() {
+			v := obj.self.getOwnProp(sym)
+			if prop, ok := v.(*valueProperty); ok {
+				if !prop.configurable {
+					continue
+				}
+				prop.configurable = false
+			} else {
+				descr.Value = v
+				obj.self.defineOwnProperty(sym, descr, true)
+			}
+		}
 		obj.self.preventExtensions()
 		obj.self.preventExtensions()
 		return obj
 		return obj
 	}
 	}
@@ -215,7 +232,7 @@ func (r *Runtime) object_freeze(call FunctionCall) Value {
 			Configurable: FLAG_FALSE,
 			Configurable: FLAG_FALSE,
 		}
 		}
 		for item, f := obj.self.enumerate(true, false)(); f != nil; item, f = f() {
 		for item, f := obj.self.enumerate(true, false)(); f != nil; item, f = f() {
-			v := obj.self.getOwnProp(item.name)
+			v := obj.self.getOwnPropStr(item.name)
 			if prop, ok := v.(*valueProperty); ok {
 			if prop, ok := v.(*valueProperty); ok {
 				prop.configurable = false
 				prop.configurable = false
 				if prop.value != nil {
 				if prop.value != nil {
@@ -226,6 +243,18 @@ func (r *Runtime) object_freeze(call FunctionCall) Value {
 				obj.self.defineOwnProperty(newStringValue(item.name), descr, true)
 				obj.self.defineOwnProperty(newStringValue(item.name), descr, true)
 			}
 			}
 		}
 		}
+		for _, sym := range obj.self.getOwnSymbols() {
+			v := obj.self.getOwnProp(sym)
+			if prop, ok := v.(*valueProperty); ok {
+				prop.configurable = false
+				if prop.value != nil {
+					prop.writable = false
+				}
+			} else {
+				descr.Value = v
+				obj.self.defineOwnProperty(sym, descr, true)
+			}
+		}
 		obj.self.preventExtensions()
 		obj.self.preventExtensions()
 		return obj
 		return obj
 	} else {
 	} else {
@@ -252,7 +281,17 @@ func (r *Runtime) object_isSealed(call FunctionCall) Value {
 			return valueFalse
 			return valueFalse
 		}
 		}
 		for item, f := obj.self.enumerate(true, false)(); f != nil; item, f = f() {
 		for item, f := obj.self.enumerate(true, false)(); f != nil; item, f = f() {
-			prop := obj.self.getOwnProp(item.name)
+			prop := obj.self.getOwnPropStr(item.name)
+			if prop, ok := prop.(*valueProperty); ok {
+				if prop.configurable {
+					return valueFalse
+				}
+			} else {
+				return valueFalse
+			}
+		}
+		for _, sym := range obj.self.getOwnSymbols() {
+			prop := obj.self.getOwnProp(sym)
 			if prop, ok := prop.(*valueProperty); ok {
 			if prop, ok := prop.(*valueProperty); ok {
 				if prop.configurable {
 				if prop.configurable {
 					return valueFalse
 					return valueFalse
@@ -275,7 +314,17 @@ func (r *Runtime) object_isFrozen(call FunctionCall) Value {
 			return valueFalse
 			return valueFalse
 		}
 		}
 		for item, f := obj.self.enumerate(true, false)(); f != nil; item, f = f() {
 		for item, f := obj.self.enumerate(true, false)(); f != nil; item, f = f() {
-			prop := obj.self.getOwnProp(item.name)
+			prop := obj.self.getOwnPropStr(item.name)
+			if prop, ok := prop.(*valueProperty); ok {
+				if prop.configurable || prop.value != nil && prop.writable {
+					return valueFalse
+				}
+			} else {
+				return valueFalse
+			}
+		}
+		for _, sym := range obj.self.getOwnSymbols() {
+			prop := obj.self.getOwnProp(sym)
 			if prop, ok := prop.(*valueProperty); ok {
 			if prop, ok := prop.(*valueProperty); ok {
 				if prop.configurable || prop.value != nil && prop.writable {
 				if prop.configurable || prop.value != nil && prop.writable {
 					return valueFalse
 					return valueFalse
@@ -321,9 +370,9 @@ func (r *Runtime) object_keys(call FunctionCall) Value {
 }
 }
 
 
 func (r *Runtime) objectproto_hasOwnProperty(call FunctionCall) Value {
 func (r *Runtime) objectproto_hasOwnProperty(call FunctionCall) Value {
-	p := call.Argument(0).String()
+	p := call.Argument(0)
 	o := call.This.ToObject(r)
 	o := call.This.ToObject(r)
-	if o.self.hasOwnPropertyStr(p) {
+	if o.self.hasOwnProperty(p) {
 		return valueTrue
 		return valueTrue
 	} else {
 	} else {
 		return valueFalse
 		return valueFalse
@@ -347,9 +396,9 @@ func (r *Runtime) objectproto_isPrototypeOf(call FunctionCall) Value {
 }
 }
 
 
 func (r *Runtime) objectproto_propertyIsEnumerable(call FunctionCall) Value {
 func (r *Runtime) objectproto_propertyIsEnumerable(call FunctionCall) Value {
-	p := call.Argument(0).ToString()
+	p := call.Argument(0)
 	o := call.This.ToObject(r)
 	o := call.This.ToObject(r)
-	pv := o.self.getOwnProp(p.String())
+	pv := o.self.getOwnProp(p)
 	if pv == nil {
 	if pv == nil {
 		return valueFalse
 		return valueFalse
 	}
 	}
@@ -367,11 +416,18 @@ func (r *Runtime) objectproto_toString(call FunctionCall) Value {
 		return stringObjectNull
 		return stringObjectNull
 	case valueUndefined:
 	case valueUndefined:
 		return stringObjectUndefined
 		return stringObjectUndefined
-	case *Object:
-		return newStringValue(fmt.Sprintf("[object %s]", o.self.className()))
 	default:
 	default:
-		obj := call.This.ToObject(r)
-		return newStringValue(fmt.Sprintf("[object %s]", obj.self.className()))
+		obj := o.ToObject(r)
+		var clsName string
+		if tag := obj.self.get(symToStringTag); tag != nil {
+			if str, ok := tag.assertString(); ok {
+				clsName = str.String()
+			}
+		}
+		if clsName == "" {
+			clsName = obj.self.className()
+		}
+		return newStringValue(fmt.Sprintf("[object %s]", clsName))
 	}
 	}
 }
 }
 
 
@@ -399,6 +455,7 @@ func (r *Runtime) initObject() {
 	o._putProp("getOwnPropertyDescriptor", r.newNativeFunc(r.object_getOwnPropertyDescriptor, nil, "getOwnPropertyDescriptor", nil, 2), true, false, true)
 	o._putProp("getOwnPropertyDescriptor", r.newNativeFunc(r.object_getOwnPropertyDescriptor, nil, "getOwnPropertyDescriptor", nil, 2), true, false, true)
 	o._putProp("getPrototypeOf", r.newNativeFunc(r.object_getPrototypeOf, nil, "getPrototypeOf", nil, 1), true, false, true)
 	o._putProp("getPrototypeOf", r.newNativeFunc(r.object_getPrototypeOf, nil, "getPrototypeOf", nil, 1), true, false, true)
 	o._putProp("getOwnPropertyNames", r.newNativeFunc(r.object_getOwnPropertyNames, nil, "getOwnPropertyNames", nil, 1), true, false, true)
 	o._putProp("getOwnPropertyNames", r.newNativeFunc(r.object_getOwnPropertyNames, nil, "getOwnPropertyNames", nil, 1), true, false, true)
+	o._putProp("getOwnPropertySymbols", r.newNativeFunc(r.object_getOwnPropertySymbols, nil, "getOwnPropertySymbols", nil, 1), true, false, true)
 	o._putProp("create", r.newNativeFunc(r.object_create, nil, "create", nil, 2), true, false, true)
 	o._putProp("create", r.newNativeFunc(r.object_create, nil, "create", nil, 2), true, false, true)
 	o._putProp("seal", r.newNativeFunc(r.object_seal, nil, "seal", nil, 1), true, false, true)
 	o._putProp("seal", r.newNativeFunc(r.object_seal, nil, "seal", nil, 1), true, false, true)
 	o._putProp("freeze", r.newNativeFunc(r.object_freeze, nil, "freeze", nil, 1), true, false, true)
 	o._putProp("freeze", r.newNativeFunc(r.object_freeze, nil, "freeze", nil, 1), true, false, true)

+ 366 - 7
builtin_regexp.go

@@ -5,6 +5,7 @@ import (
 	"github.com/dlclark/regexp2"
 	"github.com/dlclark/regexp2"
 	"github.com/dop251/goja/parser"
 	"github.com/dop251/goja/parser"
 	"regexp"
 	"regexp"
+	"strings"
 )
 )
 
 
 func (r *Runtime) newRegexpObject(proto *Object) *regexpObject {
 func (r *Runtime) newRegexpObject(proto *Object) *regexpObject {
@@ -20,7 +21,7 @@ func (r *Runtime) newRegexpObject(proto *Object) *regexpObject {
 	return o
 	return o
 }
 }
 
 
-func (r *Runtime) newRegExpp(pattern regexpPattern, patternStr valueString, global, ignoreCase, multiline bool, proto *Object) *Object {
+func (r *Runtime) newRegExpp(pattern regexpPattern, patternStr valueString, global, ignoreCase, multiline, sticky bool, proto *Object) *Object {
 	o := r.newRegexpObject(proto)
 	o := r.newRegexpObject(proto)
 
 
 	o.pattern = pattern
 	o.pattern = pattern
@@ -28,11 +29,12 @@ func (r *Runtime) newRegExpp(pattern regexpPattern, patternStr valueString, glob
 	o.global = global
 	o.global = global
 	o.ignoreCase = ignoreCase
 	o.ignoreCase = ignoreCase
 	o.multiline = multiline
 	o.multiline = multiline
+	o.sticky = sticky
 
 
 	return o.val
 	return o.val
 }
 }
 
 
-func compileRegexp(patternStr, flags string) (p regexpPattern, global, ignoreCase, multiline bool, err error) {
+func compileRegexp(patternStr, flags string) (p regexpPattern, global, ignoreCase, multiline, sticky bool, err error) {
 
 
 	if flags != "" {
 	if flags != "" {
 		invalidFlags := func() {
 		invalidFlags := func() {
@@ -58,6 +60,12 @@ func compileRegexp(patternStr, flags string) (p regexpPattern, global, ignoreCas
 					return
 					return
 				}
 				}
 				ignoreCase = true
 				ignoreCase = true
+			case 'y':
+				if sticky {
+					invalidFlags()
+					return
+				}
+				sticky = true
 			default:
 			default:
 				invalidFlags()
 				invalidFlags()
 				return
 				return
@@ -104,11 +112,11 @@ func compileRegexp(patternStr, flags string) (p regexpPattern, global, ignoreCas
 }
 }
 
 
 func (r *Runtime) newRegExp(patternStr valueString, flags string, proto *Object) *Object {
 func (r *Runtime) newRegExp(patternStr valueString, flags string, proto *Object) *Object {
-	pattern, global, ignoreCase, multiline, err := compileRegexp(patternStr.String(), flags)
+	pattern, global, ignoreCase, multiline, sticky, err := compileRegexp(patternStr.String(), flags)
 	if err != nil {
 	if err != nil {
 		panic(r.newSyntaxError(err.Error(), -1))
 		panic(r.newSyntaxError(err.Error(), -1))
 	}
 	}
-	return r.newRegExpp(pattern, patternStr, global, ignoreCase, multiline, proto)
+	return r.newRegExpp(pattern, patternStr, global, ignoreCase, multiline, sticky, proto)
 }
 }
 
 
 func (r *Runtime) builtin_newRegExp(args []Value) *Object {
 func (r *Runtime) builtin_newRegExp(args []Value) *Object {
@@ -175,7 +183,7 @@ func (r *Runtime) regexpproto_test(call FunctionCall) Value {
 
 
 func (r *Runtime) regexpproto_toString(call FunctionCall) Value {
 func (r *Runtime) regexpproto_toString(call FunctionCall) Value {
 	if this, ok := r.toObject(call.This).self.(*regexpObject); ok {
 	if this, ok := r.toObject(call.This).self.(*regexpObject); ok {
-		var g, i, m string
+		var g, i, m, y string
 		if this.global {
 		if this.global {
 			g = "g"
 			g = "g"
 		}
 		}
@@ -185,7 +193,10 @@ func (r *Runtime) regexpproto_toString(call FunctionCall) Value {
 		if this.multiline {
 		if this.multiline {
 			m = "m"
 			m = "m"
 		}
 		}
-		return newStringValue(fmt.Sprintf("/%s/%s%s%s", this.source.String(), g, i, m))
+		if this.sticky {
+			y = "y"
+		}
+		return newStringValue(fmt.Sprintf("/%s/%s%s%s%s", this.source.String(), g, i, m, y))
 	} else {
 	} else {
 		r.typeErrorResult(true, "Method RegExp.prototype.toString called on incompatible receiver %s", call.This)
 		r.typeErrorResult(true, "Method RegExp.prototype.toString called on incompatible receiver %s", call.This)
 		return nil
 		return nil
@@ -240,10 +251,338 @@ func (r *Runtime) regexpproto_getIgnoreCase(call FunctionCall) Value {
 	}
 	}
 }
 }
 
 
+func (r *Runtime) regexpproto_getSticky(call FunctionCall) Value {
+	if this, ok := r.toObject(call.This).self.(*regexpObject); ok {
+		if this.sticky {
+			return valueTrue
+		} else {
+			return valueFalse
+		}
+	} else {
+		r.typeErrorResult(true, "Method RegExp.prototype.sticky getter called on incompatible receiver %s", call.This.ToString())
+		return nil
+	}
+}
+
+func (r *Runtime) regexpproto_getFlags(call FunctionCall) Value {
+	var global, ignoreCase, multiline, sticky bool
+
+	thisObj := r.toObject(call.This)
+	if this, ok := thisObj.self.(*regexpObject); ok {
+		global, ignoreCase, multiline, sticky = this.global, this.ignoreCase, this.multiline, this.sticky
+	} else {
+		if v := thisObj.self.getStr("global"); v != nil {
+			global = v.ToBoolean()
+		}
+		if v := thisObj.self.getStr("ignoreCase"); v != nil {
+			ignoreCase = v.ToBoolean()
+		}
+		if v := thisObj.self.getStr("multiline"); v != nil {
+			multiline = v.ToBoolean()
+		}
+		if v := thisObj.self.getStr("sticky"); v != nil {
+			sticky = v.ToBoolean()
+		}
+	}
+
+	var sb strings.Builder
+	if global {
+		sb.WriteByte('g')
+	}
+	if ignoreCase {
+		sb.WriteByte('i')
+	}
+	if multiline {
+		sb.WriteByte('m')
+	}
+	if sticky {
+		sb.WriteByte('y')
+	}
+
+	return asciiString(sb.String())
+}
+
+func (r *Runtime) regExpExec(execFn func(FunctionCall) Value, rxObj *Object, arg Value) Value {
+	res := execFn(FunctionCall{
+		This:      rxObj,
+		Arguments: []Value{arg},
+	})
+
+	if res != _null {
+		if _, ok := res.(*Object); !ok {
+			panic(r.NewTypeError("RegExp exec method returned something other than an Object or null"))
+		}
+	}
+
+	return res
+}
+
+func (r *Runtime) regexpproto_stdMatcherGeneric(rxObj *Object, arg Value) Value {
+	rx := rxObj.self
+	global := rx.getStr("global")
+	if global != nil && global.ToBoolean() {
+		rx.putStr("lastIndex", intToValue(0), true)
+		execFn, ok := r.toObject(rx.getStr("exec")).self.assertCallable()
+		if !ok {
+			panic(r.NewTypeError("exec is not a function"))
+		}
+		var a []Value
+		for {
+			res := r.regExpExec(execFn, rxObj, arg)
+			if res == _null {
+				break
+			}
+			matchStr := nilSafe(r.toObject(res).self.get(intToValue(0))).ToString()
+			a = append(a, matchStr)
+			if matchStr.length() == 0 {
+				thisIndex := rx.getStr("lastIndex").ToInteger()
+				rx.putStr("lastIndex", intToValue(thisIndex+1), true) // TODO fullUnicode
+			}
+		}
+		if len(a) == 0 {
+			return _null
+		}
+		return r.newArrayValues(a)
+	}
+
+	execFn, ok := r.toObject(rx.getStr("exec")).self.assertCallable()
+	if !ok {
+		panic(r.NewTypeError("exec is not a function"))
+	}
+
+	return r.regExpExec(execFn, rxObj, arg)
+}
+
+func (r *Runtime) checkStdRegexp(rxObj *Object) *regexpObject {
+	rx, ok := rxObj.self.(*regexpObject)
+	if !ok {
+		return nil
+	}
+	execFn := rx.getPropStr("exec")
+	if execFn != nil && execFn != r.global.regexpProtoExec {
+		return nil
+	}
+
+	return rx
+}
+
+func (r *Runtime) regexpproto_stdMatcher(call FunctionCall) Value {
+	thisObj := r.toObject(call.This)
+	s := call.Argument(0).ToString()
+	rx := r.checkStdRegexp(thisObj)
+	if rx == nil {
+		return r.regexpproto_stdMatcherGeneric(thisObj, s)
+	}
+	if rx.global {
+		rx.putStr("lastIndex", intToValue(0), true)
+		var a []Value
+		var previousLastIndex int64
+		for {
+			match, result := rx.execRegexp(s)
+			if !match {
+				break
+			}
+			thisIndex := rx.getStr("lastIndex").ToInteger()
+			if thisIndex == previousLastIndex {
+				previousLastIndex++
+				rx.putStr("lastIndex", intToValue(previousLastIndex), true)
+			} else {
+				previousLastIndex = thisIndex
+			}
+			a = append(a, s.substring(int64(result[0]), int64(result[1])))
+		}
+		if len(a) == 0 {
+			return _null
+		}
+		return r.newArrayValues(a)
+	} else {
+		return rx.exec(s)
+	}
+}
+
+func (r *Runtime) regexpproto_stdSearchGeneric(rxObj *Object, arg valueString) Value {
+	rx := rxObj.self
+	previousLastIndex := rx.getStr("lastIndex")
+	rx.putStr("lastIndex", intToValue(0), true)
+	execFn, ok := r.toObject(rx.getStr("exec")).self.assertCallable()
+	if !ok {
+		panic(r.NewTypeError("exec is not a function"))
+	}
+
+	result := r.regExpExec(execFn, rxObj, arg)
+	rx.putStr("lastIndex", previousLastIndex, true)
+
+	if result == _null {
+		return intToValue(-1)
+	}
+
+	return r.toObject(result).self.getStr("index")
+}
+
+func (r *Runtime) regexpproto_stdSearch(call FunctionCall) Value {
+	thisObj := r.toObject(call.This)
+	s := call.Argument(0).ToString()
+	rx := r.checkStdRegexp(thisObj)
+	if rx == nil {
+		return r.regexpproto_stdSearchGeneric(thisObj, s)
+	}
+
+	previousLastIndex := rx.getStr("lastIndex")
+	rx.putStr("lastIndex", intToValue(0), true)
+
+	match, result := rx.execRegexp(s)
+	rx.putStr("lastIndex", previousLastIndex, true)
+
+	if !match {
+		return intToValue(-1)
+	}
+	return intToValue(int64(result[0]))
+}
+
+func (r *Runtime) regexpproto_stdSplitterGeneric(splitter *Object, s valueString, limit Value) Value {
+	var a []Value
+	var lim int64
+	if limit == nil || limit == _undefined {
+		lim = maxInt - 1
+	} else {
+		lim = toLength(limit)
+	}
+	size := s.length()
+	p := int64(0)
+	if lim == 0 {
+		return r.newArrayValues(a)
+	}
+	execFn := toMethod(splitter.ToObject(r).self.getStr("exec")) // must be non-nil
+
+	if size == 0 {
+		if r.regExpExec(execFn, splitter, s) == _null {
+			a = append(a, s)
+		}
+		return r.newArrayValues(a)
+	}
+
+	q := p
+	for q < size {
+		splitter.self.putStr("lastIndex", intToValue(q), true)
+		z := r.regExpExec(execFn, splitter, s)
+		if z == _null {
+			q++
+		} else {
+			z := r.toObject(z)
+			e := toLength(splitter.self.getStr("lastIndex"))
+			if e == p {
+				q++
+			} else {
+				a = append(a, s.substring(p, q))
+				if int64(len(a)) == lim {
+					return r.newArrayValues(a)
+				}
+				p = e
+				numberOfCaptures := max(toLength(z.self.getStr("length"))-1, 0)
+				for i := int64(1); i <= numberOfCaptures; i++ {
+					a = append(a, z.self.get(intToValue(i)))
+					if int64(len(a)) == lim {
+						return r.newArrayValues(a)
+					}
+				}
+				q = p
+			}
+		}
+	}
+	a = append(a, s.substring(p, size))
+	return r.newArrayValues(a)
+}
+
+func (r *Runtime) regexpproto_stdSplitter(call FunctionCall) Value {
+	rxObj := r.toObject(call.This)
+	c := r.speciesConstructor(rxObj, r.global.RegExp)
+	flags := nilSafe(rxObj.self.getStr("flags")).ToString()
+
+	// Add 'y' flag if missing
+	if flagsStr := flags.String(); !strings.Contains(flagsStr, "y") {
+		flags = newStringValue(flagsStr + "y")
+	}
+	splitter := c([]Value{rxObj, flags})
+
+	s := call.Argument(0).ToString()
+	limitValue := call.Argument(1)
+	search := r.checkStdRegexp(splitter)
+	if search == nil {
+		return r.regexpproto_stdSplitterGeneric(splitter, s, limitValue)
+	}
+
+	limit := -1
+	if limitValue != _undefined {
+		limit = int(toUInt32(limitValue))
+	}
+
+	if limit == 0 {
+		return r.newArrayValues(nil)
+	}
+
+	targetLength := s.length()
+	var valueArray []Value
+	result := search.pattern.FindAllSubmatchIndex(s, -1)
+	lastIndex := 0
+	found := 0
+
+	for _, match := range result {
+		if match[0] == match[1] {
+			// FIXME Ugh, this is a hack
+			if match[0] == 0 || int64(match[0]) == targetLength {
+				continue
+			}
+		}
+
+		if lastIndex != match[0] {
+			valueArray = append(valueArray, s.substring(int64(lastIndex), int64(match[0])))
+			found++
+		} else if lastIndex == match[0] {
+			if lastIndex != -1 {
+				valueArray = append(valueArray, stringEmpty)
+				found++
+			}
+		}
+
+		lastIndex = match[1]
+		if found == limit {
+			goto RETURN
+		}
+
+		captureCount := len(match) / 2
+		for index := 1; index < captureCount; index++ {
+			offset := index * 2
+			var value Value
+			if match[offset] != -1 {
+				value = s.substring(int64(match[offset]), int64(match[offset+1]))
+			} else {
+				value = _undefined
+			}
+			valueArray = append(valueArray, value)
+			found++
+			if found == limit {
+				goto RETURN
+			}
+		}
+	}
+
+	if found != limit {
+		if int64(lastIndex) != targetLength {
+			valueArray = append(valueArray, s.substring(int64(lastIndex), targetLength))
+		} else {
+			valueArray = append(valueArray, stringEmpty)
+		}
+	}
+
+RETURN:
+	return r.newArrayValues(valueArray)
+}
+
 func (r *Runtime) initRegExp() {
 func (r *Runtime) initRegExp() {
 	r.global.RegExpPrototype = r.NewObject()
 	r.global.RegExpPrototype = r.NewObject()
 	o := r.global.RegExpPrototype.self
 	o := r.global.RegExpPrototype.self
-	o._putProp("exec", r.newNativeFunc(r.regexpproto_exec, nil, "exec", nil, 1), true, false, true)
+	r.global.regexpProtoExec = valueProp(r.newNativeFunc(r.regexpproto_exec, nil, "exec", nil, 1), true, false, true)
+	o.putStr("exec", r.global.regexpProtoExec, true)
 	o._putProp("test", r.newNativeFunc(r.regexpproto_test, nil, "test", nil, 1), true, false, true)
 	o._putProp("test", r.newNativeFunc(r.regexpproto_test, nil, "test", nil, 1), true, false, true)
 	o._putProp("toString", r.newNativeFunc(r.regexpproto_toString, nil, "toString", nil, 0), true, false, true)
 	o._putProp("toString", r.newNativeFunc(r.regexpproto_toString, nil, "toString", nil, 0), true, false, true)
 	o.putStr("source", &valueProperty{
 	o.putStr("source", &valueProperty{
@@ -266,7 +605,27 @@ func (r *Runtime) initRegExp() {
 		getterFunc:   r.newNativeFunc(r.regexpproto_getIgnoreCase, nil, "get ignoreCase", nil, 0),
 		getterFunc:   r.newNativeFunc(r.regexpproto_getIgnoreCase, nil, "get ignoreCase", nil, 0),
 		accessor:     true,
 		accessor:     true,
 	}, false)
 	}, false)
+	o.putStr("sticky", &valueProperty{
+		configurable: true,
+		getterFunc:   r.newNativeFunc(r.regexpproto_getSticky, nil, "get sticky", nil, 0),
+		accessor:     true,
+	}, false)
+	o.putStr("flags", &valueProperty{
+		configurable: true,
+		getterFunc:   r.newNativeFunc(r.regexpproto_getFlags, nil, "get flags", nil, 0),
+		accessor:     true,
+	}, false)
+
+	o.put(symMatch, valueProp(r.newNativeFunc(r.regexpproto_stdMatcher, nil, "[Symbol.match]", nil, 1), true, false, true), true)
+	o.put(symSearch, valueProp(r.newNativeFunc(r.regexpproto_stdSearch, nil, "[Symbol.search]", nil, 1), true, false, true), true)
+	o.put(symSplit, valueProp(r.newNativeFunc(r.regexpproto_stdSplitter, nil, "[Symbol.split]", nil, 2), true, false, true), true)
 
 
 	r.global.RegExp = r.newNativeFunc(r.builtin_RegExp, r.builtin_newRegExp, "RegExp", r.global.RegExpPrototype, 2)
 	r.global.RegExp = r.newNativeFunc(r.builtin_RegExp, r.builtin_newRegExp, "RegExp", r.global.RegExpPrototype, 2)
+	o = r.global.RegExp.self
+	o.put(symSpecies, &valueProperty{
+		getterFunc:   r.newNativeFunc(r.returnThis, nil, "get [Symbol.species]", nil, 0),
+		accessor:     true,
+		configurable: true,
+	}, true)
 	r.addToGlobal("RegExp", r.global.RegExp)
 	r.addToGlobal("RegExp", r.global.RegExp)
 }
 }

+ 86 - 126
builtin_string.go

@@ -20,15 +20,21 @@ func (r *Runtime) collator() *collate.Collator {
 	return collator
 	return collator
 }
 }
 
 
+func toString(arg Value) valueString {
+	if s, ok := arg.assertString(); ok {
+		return s
+	}
+	if s, ok := arg.(*valueSymbol); ok {
+		return newStringValue(s.descString())
+	}
+	return arg.ToString()
+}
+
 func (r *Runtime) builtin_String(call FunctionCall) Value {
 func (r *Runtime) builtin_String(call FunctionCall) Value {
 	if len(call.Arguments) > 0 {
 	if len(call.Arguments) > 0 {
-		arg := call.Arguments[0]
-		if _, ok := arg.assertString(); ok {
-			return arg
-		}
-		return arg.ToString()
+		return toString(call.Arguments[0])
 	} else {
 	} else {
-		return newStringValue("")
+		return stringEmpty
 	}
 	}
 }
 }
 
 
@@ -51,7 +57,7 @@ func (r *Runtime) _newString(s valueString) *Object {
 func (r *Runtime) builtin_newString(args []Value) *Object {
 func (r *Runtime) builtin_newString(args []Value) *Object {
 	var s valueString
 	var s valueString
 	if len(args) > 0 {
 	if len(args) > 0 {
-		s = args[0].ToString()
+		s = toString(args[0])
 	} else {
 	} else {
 		s = stringEmpty
 		s = stringEmpty
 	}
 	}
@@ -226,8 +232,16 @@ func (r *Runtime) stringproto_localeCompare(call FunctionCall) Value {
 
 
 func (r *Runtime) stringproto_match(call FunctionCall) Value {
 func (r *Runtime) stringproto_match(call FunctionCall) Value {
 	r.checkObjectCoercible(call.This)
 	r.checkObjectCoercible(call.This)
-	s := call.This.ToString()
 	regexp := call.Argument(0)
 	regexp := call.Argument(0)
+	if regexp != _undefined && regexp != _null {
+		if matcher := toMethod(regexp.ToObject(r).self.get(symMatch)); matcher != nil {
+			return matcher(FunctionCall{
+				This:      regexp,
+				Arguments: []Value{call.This},
+			})
+		}
+	}
+
 	var rx *regexpObject
 	var rx *regexpObject
 	if regexp, ok := regexp.(*Object); ok {
 	if regexp, ok := regexp.(*Object); ok {
 		rx, _ = regexp.self.(*regexpObject)
 		rx, _ = regexp.self.(*regexpObject)
@@ -237,34 +251,29 @@ func (r *Runtime) stringproto_match(call FunctionCall) Value {
 		rx = r.builtin_newRegExp([]Value{regexp}).self.(*regexpObject)
 		rx = r.builtin_newRegExp([]Value{regexp}).self.(*regexpObject)
 	}
 	}
 
 
-	if rx.global {
-		rx.putStr("lastIndex", intToValue(0), false)
-		var a []Value
-		var previousLastIndex int64
-		for {
-			match, result := rx.execRegexp(s)
-			if !match {
-				break
-			}
-			thisIndex := rx.getStr("lastIndex").ToInteger()
-			if thisIndex == previousLastIndex {
-				previousLastIndex++
-				rx.putStr("lastIndex", intToValue(previousLastIndex), false)
-			} else {
-				previousLastIndex = thisIndex
-			}
-			a = append(a, s.substring(int64(result[0]), int64(result[1])))
-		}
-		if len(a) == 0 {
-			return _null
-		}
-		return r.newArrayValues(a)
-	} else {
-		return rx.exec(s)
+	if matcher, ok := r.toObject(rx.getSym(symMatch)).self.assertCallable(); ok {
+		return matcher(FunctionCall{
+			This:      rx.val,
+			Arguments: []Value{call.This.ToString()},
+		})
 	}
 	}
+
+	panic(r.NewTypeError("RegExp matcher is not a function"))
 }
 }
 
 
 func (r *Runtime) stringproto_replace(call FunctionCall) Value {
 func (r *Runtime) stringproto_replace(call FunctionCall) Value {
+	r.checkObjectCoercible(call.This)
+	searchValue := call.Argument(0)
+	replaceValue := call.Argument(1)
+	if searchValue != _undefined && searchValue != _null {
+		if replacer := toMethod(searchValue.ToObject(r).self.get(symReplace)); replacer != nil {
+			return replacer(FunctionCall{
+				This:      searchValue,
+				Arguments: []Value{call.This, replaceValue},
+			})
+		}
+	}
+
 	s := call.This.ToString()
 	s := call.This.ToString()
 	var str string
 	var str string
 	var isASCII bool
 	var isASCII bool
@@ -274,8 +283,6 @@ func (r *Runtime) stringproto_replace(call FunctionCall) Value {
 	} else {
 	} else {
 		str = s.String()
 		str = s.String()
 	}
 	}
-	searchValue := call.Argument(0)
-	replaceValue := call.Argument(1)
 
 
 	var found [][]int
 	var found [][]int
 
 
@@ -408,8 +415,16 @@ func (r *Runtime) stringproto_replace(call FunctionCall) Value {
 
 
 func (r *Runtime) stringproto_search(call FunctionCall) Value {
 func (r *Runtime) stringproto_search(call FunctionCall) Value {
 	r.checkObjectCoercible(call.This)
 	r.checkObjectCoercible(call.This)
-	s := call.This.ToString()
 	regexp := call.Argument(0)
 	regexp := call.Argument(0)
+	if regexp != _undefined && regexp != _null {
+		if searcher := toMethod(regexp.ToObject(r).self.get(symSearch)); searcher != nil {
+			return searcher(FunctionCall{
+				This:      regexp,
+				Arguments: []Value{call.This},
+			})
+		}
+	}
+
 	var rx *regexpObject
 	var rx *regexpObject
 	if regexp, ok := regexp.(*Object); ok {
 	if regexp, ok := regexp.(*Object); ok {
 		rx, _ = regexp.self.(*regexpObject)
 		rx, _ = regexp.self.(*regexpObject)
@@ -419,11 +434,14 @@ func (r *Runtime) stringproto_search(call FunctionCall) Value {
 		rx = r.builtin_newRegExp([]Value{regexp}).self.(*regexpObject)
 		rx = r.builtin_newRegExp([]Value{regexp}).self.(*regexpObject)
 	}
 	}
 
 
-	match, result := rx.execRegexp(s)
-	if !match {
-		return intToValue(-1)
+	if searcher, ok := r.toObject(rx.getSym(symSearch)).self.assertCallable(); ok {
+		return searcher(FunctionCall{
+			This:      rx.val,
+			Arguments: []Value{call.This.ToString()},
+		})
 	}
 	}
-	return intToValue(int64(result[0]))
+
+	panic(r.NewTypeError("RegExp searcher is not a function"))
 }
 }
 
 
 func (r *Runtime) stringproto_slice(call FunctionCall) Value {
 func (r *Runtime) stringproto_slice(call FunctionCall) Value {
@@ -469,10 +487,18 @@ func (r *Runtime) stringproto_slice(call FunctionCall) Value {
 
 
 func (r *Runtime) stringproto_split(call FunctionCall) Value {
 func (r *Runtime) stringproto_split(call FunctionCall) Value {
 	r.checkObjectCoercible(call.This)
 	r.checkObjectCoercible(call.This)
-	s := call.This.ToString()
-
 	separatorValue := call.Argument(0)
 	separatorValue := call.Argument(0)
 	limitValue := call.Argument(1)
 	limitValue := call.Argument(1)
+	if separatorValue != _undefined && separatorValue != _null {
+		if splitter := toMethod(separatorValue.ToObject(r).self.get(symSplit)); splitter != nil {
+			return splitter(FunctionCall{
+				This:      separatorValue,
+				Arguments: []Value{call.This, limitValue},
+			})
+		}
+	}
+	s := call.This.ToString()
+
 	limit := -1
 	limit := -1
 	if limitValue != _undefined {
 	if limitValue != _undefined {
 		limit = int(toUInt32(limitValue))
 		limit = int(toUInt32(limitValue))
@@ -486,97 +512,31 @@ func (r *Runtime) stringproto_split(call FunctionCall) Value {
 		return r.newArrayValues([]Value{s})
 		return r.newArrayValues([]Value{s})
 	}
 	}
 
 
-	var search *regexpObject
-	if o, ok := separatorValue.(*Object); ok {
-		search, _ = o.self.(*regexpObject)
-	}
-
-	if search != nil {
-		targetLength := s.length()
-		valueArray := []Value{}
-		result := search.pattern.FindAllSubmatchIndex(s, -1)
-		lastIndex := 0
-		found := 0
-
-		for _, match := range result {
-			if match[0] == match[1] {
-				// FIXME Ugh, this is a hack
-				if match[0] == 0 || int64(match[0]) == targetLength {
-					continue
-				}
-			}
-
-			if lastIndex != match[0] {
-				valueArray = append(valueArray, s.substring(int64(lastIndex), int64(match[0])))
-				found++
-			} else if lastIndex == match[0] {
-				if lastIndex != -1 {
-					valueArray = append(valueArray, stringEmpty)
-					found++
-				}
-			}
-
-			lastIndex = match[1]
-			if found == limit {
-				goto RETURN
-			}
-
-			captureCount := len(match) / 2
-			for index := 1; index < captureCount; index++ {
-				offset := index * 2
-				var value Value
-				if match[offset] != -1 {
-					value = s.substring(int64(match[offset]), int64(match[offset+1]))
-				} else {
-					value = _undefined
-				}
-				valueArray = append(valueArray, value)
-				found++
-				if found == limit {
-					goto RETURN
-				}
-			}
-		}
-
-		if found != limit {
-			if int64(lastIndex) != targetLength {
-				valueArray = append(valueArray, s.substring(int64(lastIndex), targetLength))
-			} else {
-				valueArray = append(valueArray, stringEmpty)
-			}
-		}
-
-	RETURN:
-		return r.newArrayValues(valueArray)
-
-	} else {
-		separator := separatorValue.String()
+	separator := separatorValue.String()
 
 
-		excess := false
-		str := s.String()
-		if limit > len(str) {
-			limit = len(str)
-		}
-		splitLimit := limit
-		if limit > 0 {
-			splitLimit = limit + 1
-			excess = true
-		}
-
-		split := strings.SplitN(str, separator, splitLimit)
+	excess := false
+	str := s.String()
+	if limit > len(str) {
+		limit = len(str)
+	}
+	splitLimit := limit
+	if limit > 0 {
+		splitLimit = limit + 1
+		excess = true
+	}
 
 
-		if excess && len(split) > limit {
-			split = split[:limit]
-		}
+	split := strings.SplitN(str, separator, splitLimit)
 
 
-		valueArray := make([]Value, len(split))
-		for index, value := range split {
-			valueArray[index] = newStringValue(value)
-		}
+	if excess && len(split) > limit {
+		split = split[:limit]
+	}
 
 
-		return r.newArrayValues(valueArray)
+	valueArray := make([]Value, len(split))
+	for index, value := range split {
+		valueArray[index] = newStringValue(value)
 	}
 	}
 
 
+	return r.newArrayValues(valueArray)
 }
 }
 
 
 func (r *Runtime) stringproto_substring(call FunctionCall) Value {
 func (r *Runtime) stringproto_substring(call FunctionCall) Value {

+ 57 - 0
builtin_string_test.go

@@ -88,3 +88,60 @@ assert.sameValue('A—', String.fromCharCode(65, 0x2014));
 
 
 	testScript1(TESTLIB+SCRIPT, _undefined, t)
 	testScript1(TESTLIB+SCRIPT, _undefined, t)
 }
 }
+
+func TestStringMatchSym(t *testing.T) {
+	const SCRIPT = `
+function Prefix(p) {
+	this.p = p;
+}
+
+Prefix.prototype[Symbol.match] = function(s) {
+	return s.substring(0, this.p.length) === this.p;
+}
+
+var prefix1 = new Prefix("abc");
+var prefix2 = new Prefix("def");
+
+"abc123".match(prefix1) === true && "abc123".match(prefix2) === false &&
+"def123".match(prefix1) === false && "def123".match(prefix2) === true;
+`
+	testScript1(SCRIPT, valueTrue, t)
+}
+
+func TestGenericSplitter(t *testing.T) {
+	const SCRIPT = `
+function MyRegexp(pattern, flags) {
+	if (pattern instanceof MyRegexp) {
+		pattern = pattern.wrapped;
+	}
+	this.wrapped = new RegExp(pattern, flags);
+}
+
+MyRegexp.prototype.exec = function() {
+	return this.wrapped.exec.apply(this.wrapped, arguments);
+}
+
+Object.defineProperty(MyRegexp.prototype, "lastIndex", {
+	get: function() {
+		return this.wrapped.lastIndex;
+	},
+	set: function(v) {
+		this.wrapped.lastIndex = v;
+	}
+});
+
+Object.defineProperty(MyRegexp.prototype, "flags", {
+	get: function() {
+		return this.wrapped.flags;
+	}
+});
+
+MyRegexp[Symbol.species] = MyRegexp;
+MyRegexp.prototype[Symbol.split] = RegExp.prototype[Symbol.split];
+
+var r = new MyRegexp(/ /);
+var res = "a b c".split(r);
+res.length === 3 && res[0] === "a" && res[1] === "b" && res[2] === "c";
+`
+	testScript1(SCRIPT, valueTrue, t)
+}

+ 139 - 0
builtin_symbol.go

@@ -0,0 +1,139 @@
+package goja
+
+var (
+	symHasInstance        = &valueSymbol{desc: "Symbol.hasInstance"}
+	symIsConcatSpreadable = &valueSymbol{desc: "Symbol.isConcatSpreadable"}
+	symIterator           = &valueSymbol{desc: "Symbol.iterator"}
+	symMatch              = &valueSymbol{desc: "Symbol.match"}
+	symReplace            = &valueSymbol{desc: "Symbol.replace"}
+	symSearch             = &valueSymbol{desc: "Symbol.search"}
+	symSpecies            = &valueSymbol{desc: "Symbol.species"}
+	symSplit              = &valueSymbol{desc: "Symbol.split"}
+	symToPrimitive        = &valueSymbol{desc: "Symbol.toPrimitive"}
+	symToStringTag        = &valueSymbol{desc: "Symbol.toStringTag"}
+	symUnscopables        = &valueSymbol{desc: "Symbol.unscopables"}
+)
+
+func (r *Runtime) builtin_symbol(call FunctionCall) Value {
+	desc := ""
+	if arg := call.Argument(0); !IsUndefined(arg) {
+		desc = arg.ToString().String()
+	}
+	return &valueSymbol{
+		desc: desc,
+	}
+}
+
+func (r *Runtime) symbolproto_tostring(call FunctionCall) Value {
+	sym, ok := call.This.(*valueSymbol)
+	if !ok {
+		if obj, ok := call.This.(*Object); ok {
+			if v, ok := obj.self.(*primitiveValueObject); ok {
+				if sym1, ok := v.pValue.(*valueSymbol); ok {
+					sym = sym1
+				}
+			}
+		}
+	}
+	if sym == nil {
+		panic(r.NewTypeError("Method Symbol.prototype.toString is called on incompatible receiver"))
+	}
+	return newStringValue(sym.descString())
+}
+
+func (r *Runtime) symbolproto_valueOf(call FunctionCall) Value {
+	_, ok := call.This.(*valueSymbol)
+	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 {
+				return sym
+			}
+		}
+	}
+
+	panic(r.NewTypeError("Symbol.prototype.valueOf requires that 'this' be a Symbol"))
+}
+
+func (r *Runtime) symbol_for(call FunctionCall) Value {
+	key := call.Argument(0).ToString().String()
+	if v := r.symbolRegistry[key]; v != nil {
+		return v
+	}
+	if r.symbolRegistry == nil {
+		r.symbolRegistry = make(map[string]*valueSymbol)
+	}
+	v := &valueSymbol{
+		desc: key,
+	}
+	r.symbolRegistry[key] = v
+	return v
+}
+
+func (r *Runtime) symbol_keyfor(call FunctionCall) Value {
+	arg := call.Argument(0)
+	sym, ok := arg.(*valueSymbol)
+	if !ok {
+		panic(r.NewTypeError("%s is not a symbol", arg.String()))
+	}
+	for key, s := range r.symbolRegistry {
+		if s == sym {
+			return r.ToValue(key)
+		}
+	}
+	return _undefined
+}
+
+func (r *Runtime) createSymbolProto(val *Object) objectImpl {
+	o := &baseObject{
+		class:      classObject,
+		val:        val,
+		extensible: true,
+		prototype:  r.global.ObjectPrototype,
+	}
+	o.init()
+
+	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), true)
+	o.putSym(symToStringTag, valueProp(newStringValue("Symbol"), false, false, true), true)
+
+	return o
+}
+
+func (r *Runtime) createSymbol(val *Object) objectImpl {
+	o := r.newNativeFuncObj(val, r.builtin_symbol, nil, "Symbol", r.global.SymbolPrototype, 0)
+
+	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,
+	} {
+		o._putProp(s.desc[len("Symbol."):], s, false, false, false)
+	}
+
+	return o
+}
+
+func (r *Runtime) initSymbol() {
+	r.global.SymbolPrototype = r.newLazyObject(r.createSymbolProto)
+
+	r.global.Symbol = r.newLazyObject(r.createSymbol)
+	r.addToGlobal("Symbol", r.global.Symbol)
+
+}

+ 2 - 1
compiler_expr.go

@@ -1388,7 +1388,7 @@ func (c *compiler) compileArrayLiteral(v *ast.ArrayLiteral) compiledExpr {
 
 
 func (e *compiledRegexpLiteral) emitGetter(putOnStack bool) {
 func (e *compiledRegexpLiteral) emitGetter(putOnStack bool) {
 	if putOnStack {
 	if putOnStack {
-		pattern, global, ignoreCase, multiline, err := compileRegexp(e.expr.Pattern, e.expr.Flags)
+		pattern, global, ignoreCase, multiline, sticky, err := compileRegexp(e.expr.Pattern, e.expr.Flags)
 		if err != nil {
 		if err != nil {
 			e.c.throwSyntaxError(e.offset, err.Error())
 			e.c.throwSyntaxError(e.offset, err.Error())
 		}
 		}
@@ -1398,6 +1398,7 @@ func (e *compiledRegexpLiteral) emitGetter(putOnStack bool) {
 			global:     global,
 			global:     global,
 			ignoreCase: ignoreCase,
 			ignoreCase: ignoreCase,
 			multiline:  multiline,
 			multiline:  multiline,
+			sticky:     sticky,
 		})
 		})
 	}
 	}
 }
 }

+ 31 - 0
date_test.go

@@ -10,6 +10,9 @@ function $ERROR(message) {
 	throw new Error(message);
 	throw new Error(message);
 }
 }
 
 
+function Test262Error() {
+}
+
 function assert(mustBeTrue, message) {
 function assert(mustBeTrue, message) {
     if (mustBeTrue === true) {
     if (mustBeTrue === true) {
         return;
         return;
@@ -47,6 +50,34 @@ assert.sameValue = function (actual, expected, message) {
     $ERROR(message);
     $ERROR(message);
 };
 };
 
 
+assert.throws = function (expectedErrorConstructor, func, message) {
+  if (typeof func !== "function") {
+    $ERROR('assert.throws requires two arguments: the error constructor ' +
+      'and a function to run');
+    return;
+  }
+  if (message === undefined) {
+    message = '';
+  } else {
+    message += ' ';
+  }
+
+  try {
+    func();
+  } catch (thrown) {
+    if (typeof thrown !== 'object' || thrown === null) {
+      message += 'Thrown value was not an object!';
+      $ERROR(message);
+    } else if (thrown.constructor !== expectedErrorConstructor) {
+      message += 'Expected a ' + expectedErrorConstructor.name + ' but got a ' + thrown.constructor.name;
+      $ERROR(message);
+    }
+    return;
+  }
+
+  message += 'Expected a ' + expectedErrorConstructor.name + ' to be thrown but no exception was thrown at all';
+  $ERROR(message);
+};
 
 
 `
 `
 
 

+ 12 - 8
func.go

@@ -25,6 +25,7 @@ type nativeFuncObject struct {
 
 
 type boundFuncObject struct {
 type boundFuncObject struct {
 	nativeFuncObject
 	nativeFuncObject
+	wrapped *Object
 }
 }
 
 
 func (f *nativeFuncObject) export() interface{} {
 func (f *nativeFuncObject) export() interface{} {
@@ -52,10 +53,6 @@ func (f *funcObject) addPrototype() Value {
 	return f._putProp("prototype", proto, true, false, false)
 	return f._putProp("prototype", proto, true, false, false)
 }
 }
 
 
-func (f *funcObject) getProp(n Value) Value {
-	return f.getPropStr(n.String())
-}
-
 func (f *funcObject) hasOwnProperty(n Value) bool {
 func (f *funcObject) hasOwnProperty(n Value) bool {
 	if r := f.baseObject.hasOwnProperty(n); r {
 	if r := f.baseObject.hasOwnProperty(n); r {
 		return true
 		return true
@@ -205,10 +202,6 @@ func (f *nativeFuncObject) assertCallable() (func(FunctionCall) Value, bool) {
 	return nil, false
 	return nil, false
 }
 }
 
 
-func (f *boundFuncObject) getProp(n Value) Value {
-	return f.getPropStr(n.String())
-}
-
 func (f *boundFuncObject) getPropStr(name string) Value {
 func (f *boundFuncObject) getPropStr(name string) Value {
 	if name == "caller" || name == "arguments" {
 	if name == "caller" || name == "arguments" {
 		//f.runtime.typeErrorResult(true, "'caller' and 'arguments' are restricted function properties and cannot be accessed in this context.")
 		//f.runtime.typeErrorResult(true, "'caller' and 'arguments' are restricted function properties and cannot be accessed in this context.")
@@ -218,6 +211,9 @@ func (f *boundFuncObject) getPropStr(name string) Value {
 }
 }
 
 
 func (f *boundFuncObject) delete(n Value, throw bool) bool {
 func (f *boundFuncObject) delete(n Value, throw bool) bool {
+	if s, ok := n.(*valueSymbol); ok {
+		return f.deleteSym(s, throw)
+	}
 	return f.deleteStr(n.String(), throw)
 	return f.deleteStr(n.String(), throw)
 }
 }
 
 
@@ -236,5 +232,13 @@ func (f *boundFuncObject) putStr(name string, val Value, throw bool) {
 }
 }
 
 
 func (f *boundFuncObject) put(n Value, val Value, throw bool) {
 func (f *boundFuncObject) put(n Value, val Value, throw bool) {
+	if s, ok := n.(*valueSymbol); ok {
+		f.putSym(s, val, throw)
+		return
+	}
 	f.putStr(n.String(), val, throw)
 	f.putStr(n.String(), val, throw)
 }
 }
+
+func (f *boundFuncObject) hasInstance(v Value) bool {
+	return instanceOfOperator(v, f.wrapped)
+}

+ 189 - 22
object.go

@@ -1,6 +1,9 @@
 package goja
 package goja
 
 
-import "reflect"
+import (
+	"fmt"
+	"reflect"
+)
 
 
 const (
 const (
 	classObject   = "Object"
 	classObject   = "Object"
@@ -12,6 +15,8 @@ const (
 	classError    = "Error"
 	classError    = "Error"
 	classRegExp   = "RegExp"
 	classRegExp   = "RegExp"
 	classDate     = "Date"
 	classDate     = "Date"
+
+	classArrayIterator = "Array Iterator"
 )
 )
 
 
 type Object struct {
 type Object struct {
@@ -36,7 +41,8 @@ type objectImpl interface {
 	getProp(Value) Value
 	getProp(Value) Value
 	getPropStr(string) Value
 	getPropStr(string) Value
 	getStr(string) Value
 	getStr(string) Value
-	getOwnProp(string) Value
+	getOwnProp(Value) Value
+	getOwnPropStr(string) Value
 	put(Value, Value, bool)
 	put(Value, Value, bool)
 	putStr(string, Value, bool)
 	putStr(string, Value, bool)
 	hasProperty(Value) bool
 	hasProperty(Value) bool
@@ -60,6 +66,7 @@ type objectImpl interface {
 	export() interface{}
 	export() interface{}
 	exportType() reflect.Type
 	exportType() reflect.Type
 	equal(objectImpl) bool
 	equal(objectImpl) bool
+	getOwnSymbols() []Value
 }
 }
 
 
 type baseObject struct {
 type baseObject struct {
@@ -70,6 +77,8 @@ type baseObject struct {
 
 
 	values    map[string]Value
 	values    map[string]Value
 	propNames []string
 	propNames []string
+
+	symValues map[*valueSymbol]Value
 }
 }
 
 
 type primitiveValueObject struct {
 type primitiveValueObject struct {
@@ -118,7 +127,7 @@ func (o *baseObject) className() string {
 }
 }
 
 
 func (o *baseObject) getPropStr(name string) Value {
 func (o *baseObject) getPropStr(name string) Value {
-	if val := o.getOwnProp(name); val != nil {
+	if val := o.getOwnPropStr(name); val != nil {
 		return val
 		return val
 	}
 	}
 	if o.prototype != nil {
 	if o.prototype != nil {
@@ -127,7 +136,20 @@ func (o *baseObject) getPropStr(name string) Value {
 	return nil
 	return nil
 }
 }
 
 
+func (o *baseObject) getPropSym(s *valueSymbol) Value {
+	if val := o.symValues[s]; val != nil {
+		return val
+	}
+	if o.prototype != nil {
+		return o.prototype.self.getProp(s)
+	}
+	return nil
+}
+
 func (o *baseObject) getProp(n Value) Value {
 func (o *baseObject) getProp(n Value) Value {
+	if s, ok := n.(*valueSymbol); ok {
+		return o.getPropSym(s)
+	}
 	return o.val.self.getPropStr(n.String())
 	return o.val.self.getPropStr(n.String())
 }
 }
 
 
@@ -140,7 +162,7 @@ func (o *baseObject) hasPropertyStr(name string) bool {
 }
 }
 
 
 func (o *baseObject) _getStr(name string) Value {
 func (o *baseObject) _getStr(name string) Value {
-	p := o.getOwnProp(name)
+	p := o.getOwnPropStr(name)
 
 
 	if p == nil && o.prototype != nil {
 	if p == nil && o.prototype != nil {
 		p = o.prototype.self.getPropStr(name)
 		p = o.prototype.self.getPropStr(name)
@@ -162,7 +184,19 @@ func (o *baseObject) getStr(name string) Value {
 	return p
 	return p
 }
 }
 
 
+func (o *baseObject) getSym(s *valueSymbol) Value {
+	p := o.getPropSym(s)
+	if p, ok := p.(*valueProperty); ok {
+		return p.get(o.val)
+	}
+
+	return p
+}
+
 func (o *baseObject) get(n Value) Value {
 func (o *baseObject) get(n Value) Value {
+	if s, ok := n.(*valueSymbol); ok {
+		return o.getSym(s)
+	}
 	return o.getStr(n.String())
 	return o.getStr(n.String())
 }
 }
 
 
@@ -198,20 +232,36 @@ func (o *baseObject) deleteStr(name string, throw bool) bool {
 			return false
 			return false
 		}
 		}
 		o._delete(name)
 		o._delete(name)
-		return true
+	}
+	return true
+}
+
+func (o *baseObject) deleteSym(s *valueSymbol, throw bool) bool {
+	if val, exists := o.symValues[s]; exists {
+		if !o.checkDelete(s.String(), val, throw) {
+			return false
+		}
+		delete(o.symValues, s)
 	}
 	}
 	return true
 	return true
 }
 }
 
 
 func (o *baseObject) delete(n Value, throw bool) bool {
 func (o *baseObject) delete(n Value, throw bool) bool {
+	if s, ok := n.(*valueSymbol); ok {
+		return o.deleteSym(s, throw)
+	}
 	return o.deleteStr(n.String(), throw)
 	return o.deleteStr(n.String(), throw)
 }
 }
 
 
 func (o *baseObject) put(n Value, val Value, throw bool) {
 func (o *baseObject) put(n Value, val Value, throw bool) {
-	o.putStr(n.String(), val, throw)
+	if s, ok := n.(*valueSymbol); ok {
+		o.putSym(s, val, throw)
+	} else {
+		o.putStr(n.String(), val, throw)
+	}
 }
 }
 
 
-func (o *baseObject) getOwnProp(name string) Value {
+func (o *baseObject) getOwnPropStr(name string) Value {
 	v := o.values[name]
 	v := o.values[name]
 	if v == nil && name == __proto__ {
 	if v == nil && name == __proto__ {
 		return o.prototype
 		return o.prototype
@@ -219,6 +269,14 @@ func (o *baseObject) getOwnProp(name string) Value {
 	return v
 	return v
 }
 }
 
 
+func (o *baseObject) getOwnProp(name Value) Value {
+	if s, ok := name.(*valueSymbol); ok {
+		return o.symValues[s]
+	}
+
+	return o.val.self.getOwnPropStr(name.String())
+}
+
 func (o *baseObject) putStr(name string, val Value, throw bool) {
 func (o *baseObject) putStr(name string, val Value, throw bool) {
 	if v, exists := o.values[name]; exists {
 	if v, exists := o.values[name]; exists {
 		if prop, ok := v.(*valueProperty); ok {
 		if prop, ok := v.(*valueProperty); ok {
@@ -276,7 +334,54 @@ func (o *baseObject) putStr(name string, val Value, throw bool) {
 	o.propNames = append(o.propNames, name)
 	o.propNames = append(o.propNames, name)
 }
 }
 
 
+func (o *baseObject) putSym(s *valueSymbol, val Value, throw bool) {
+	if v, exists := o.symValues[s]; exists {
+		if prop, ok := v.(*valueProperty); ok {
+			if !prop.isWritable() {
+				o.val.runtime.typeErrorResult(throw, "Cannot assign to read only property '%s'", s.String())
+				return
+			}
+			prop.set(o.val, val)
+			return
+		}
+		o.symValues[s] = val
+		return
+	}
+
+	var pprop Value
+	if proto := o.prototype; proto != nil {
+		pprop = proto.self.getProp(s)
+	}
+
+	if pprop != nil {
+		if prop, ok := pprop.(*valueProperty); ok {
+			if !prop.isWritable() {
+				o.val.runtime.typeErrorResult(throw)
+				return
+			}
+			if prop.accessor {
+				prop.set(o.val, val)
+				return
+			}
+		}
+	} else {
+		if !o.extensible {
+			o.val.runtime.typeErrorResult(throw)
+			return
+		}
+	}
+
+	if o.symValues == nil {
+		o.symValues = make(map[*valueSymbol]Value, 1)
+	}
+	o.symValues[s] = val
+}
+
 func (o *baseObject) hasOwnProperty(n Value) bool {
 func (o *baseObject) hasOwnProperty(n Value) bool {
+	if s, ok := n.(*valueSymbol); ok {
+		_, exists := o.symValues[s]
+		return exists
+	}
 	v := o.values[n.String()]
 	v := o.values[n.String()]
 	return v != nil
 	return v != nil
 }
 }
@@ -390,6 +495,17 @@ Reject:
 }
 }
 
 
 func (o *baseObject) defineOwnProperty(n Value, descr propertyDescr, throw bool) bool {
 func (o *baseObject) defineOwnProperty(n Value, descr propertyDescr, throw bool) bool {
+	if s, ok := n.(*valueSymbol); ok {
+		existingVal := o.symValues[s]
+		if v, ok := o._defineOwnProperty(n, existingVal, descr, throw); ok {
+			if o.symValues == nil {
+				o.symValues = make(map[*valueSymbol]Value, 1)
+			}
+			o.symValues[s] = v
+			return true
+		}
+		return false
+	}
 	name := n.String()
 	name := n.String()
 	existingVal := o.values[name]
 	existingVal := o.values[name]
 	if v, ok := o._defineOwnProperty(n, existingVal, descr, throw); ok {
 	if v, ok := o._defineOwnProperty(n, existingVal, descr, throw); ok {
@@ -410,22 +526,35 @@ func (o *baseObject) _put(name string, v Value) {
 	o.values[name] = v
 	o.values[name] = v
 }
 }
 
 
-func (o *baseObject) _putProp(name string, value Value, writable, enumerable, configurable bool) Value {
+func valueProp(value Value, writable, enumerable, configurable bool) Value {
 	if writable && enumerable && configurable {
 	if writable && enumerable && configurable {
-		o._put(name, value)
 		return value
 		return value
-	} else {
-		p := &valueProperty{
-			value:        value,
-			writable:     writable,
-			enumerable:   enumerable,
-			configurable: configurable,
-		}
-		o._put(name, p)
-		return p
+	}
+	return &valueProperty{
+		value:        value,
+		writable:     writable,
+		enumerable:   enumerable,
+		configurable: configurable,
 	}
 	}
 }
 }
 
 
+func (o *baseObject) _putProp(name string, value Value, writable, enumerable, configurable bool) Value {
+	prop := valueProp(value, writable, enumerable, configurable)
+	o._put(name, prop)
+	return prop
+}
+
+func (o *baseObject) tryExoticToPrimitive(hint string) Value {
+	exoticToPrimitive := toMethod(o.getSym(symToPrimitive))
+	if exoticToPrimitive != nil {
+		return exoticToPrimitive(FunctionCall{
+			This:      o.val,
+			Arguments: []Value{newStringValue(hint)},
+		})
+	}
+	return nil
+}
+
 func (o *baseObject) tryPrimitive(methodName string) Value {
 func (o *baseObject) tryPrimitive(methodName string) Value {
 	if method, ok := o.getStr(methodName).(*Object); ok {
 	if method, ok := o.getStr(methodName).(*Object); ok {
 		if call, ok := method.self.assertCallable(); ok {
 		if call, ok := method.self.assertCallable(); ok {
@@ -441,6 +570,10 @@ func (o *baseObject) tryPrimitive(methodName string) Value {
 }
 }
 
 
 func (o *baseObject) toPrimitiveNumber() Value {
 func (o *baseObject) toPrimitiveNumber() Value {
+	if v := o.tryExoticToPrimitive("number"); v != nil {
+		return v
+	}
+
 	if v := o.tryPrimitive("valueOf"); v != nil {
 	if v := o.tryPrimitive("valueOf"); v != nil {
 		return v
 		return v
 	}
 	}
@@ -454,6 +587,10 @@ func (o *baseObject) toPrimitiveNumber() Value {
 }
 }
 
 
 func (o *baseObject) toPrimitiveString() Value {
 func (o *baseObject) toPrimitiveString() Value {
+	if v := o.tryExoticToPrimitive("string"); v != nil {
+		return v
+	}
+
 	if v := o.tryPrimitive("toString"); v != nil {
 	if v := o.tryPrimitive("toString"); v != nil {
 		return v
 		return v
 	}
 	}
@@ -614,12 +751,42 @@ func (o *baseObject) enumerate(all, recursive bool) iterNextFunc {
 	}).next
 	}).next
 }
 }
 
 
-func (o *baseObject) equal(other objectImpl) bool {
+func (o *baseObject) equal(objectImpl) bool {
 	// Rely on parent reference comparison
 	// Rely on parent reference comparison
 	return false
 	return false
 }
 }
 
 
-func (o *baseObject) hasInstance(v Value) bool {
-	o.val.runtime.typeErrorResult(true, "Expecting a function in instanceof check, but got %s", o.val.ToString())
-	panic("Unreachable")
+func (o *baseObject) getOwnSymbols() (res []Value) {
+	for s := range o.symValues {
+		res = append(res, s)
+	}
+
+	return
+}
+
+func (o *baseObject) hasInstance(Value) bool {
+	panic(o.val.runtime.NewTypeError("Expecting a function in instanceof check, but got %s", o.val.ToString()))
+}
+
+func toMethod(v Value) func(FunctionCall) Value {
+	if v == nil || IsUndefined(v) || IsNull(v) {
+		return nil
+	}
+	if obj, ok := v.(*Object); ok {
+		if call, ok := obj.self.assertCallable(); ok {
+			return call
+		}
+	}
+	panic(typeError(fmt.Sprintf("%s is not a method", v.String())))
+}
+
+func instanceOfOperator(o Value, c *Object) bool {
+	if instOfHandler := toMethod(c.self.get(symHasInstance)); instOfHandler != nil {
+		return instOfHandler(FunctionCall{
+			This:      c,
+			Arguments: []Value{o},
+		}).ToBoolean()
+	}
+
+	return c.self.hasInstance(o)
 }
 }

+ 13 - 9
object_args.go

@@ -27,6 +27,10 @@ func (a *argumentsObject) init() {
 }
 }
 
 
 func (a *argumentsObject) put(n Value, val Value, throw bool) {
 func (a *argumentsObject) put(n Value, val Value, throw bool) {
+	if s, ok := n.(*valueSymbol); ok {
+		a.putSym(s, val, throw)
+		return
+	}
 	a.putStr(n.String(), val, throw)
 	a.putStr(n.String(), val, throw)
 }
 }
 
 
@@ -55,15 +59,12 @@ func (a *argumentsObject) deleteStr(name string, throw bool) bool {
 }
 }
 
 
 func (a *argumentsObject) delete(n Value, throw bool) bool {
 func (a *argumentsObject) delete(n Value, throw bool) bool {
+	if s, ok := n.(*valueSymbol); ok {
+		return a.deleteSym(s, throw)
+	}
 	return a.deleteStr(n.String(), throw)
 	return a.deleteStr(n.String(), throw)
 }
 }
 
 
-type argumentsPropIter1 struct {
-	a         *argumentsObject
-	idx       int
-	recursive bool
-}
-
 type argumentsPropIter struct {
 type argumentsPropIter struct {
 	wrapped iterNextFunc
 	wrapped iterNextFunc
 }
 }
@@ -94,6 +95,9 @@ func (a *argumentsObject) enumerate(all, recursive bool) iterNextFunc {
 }
 }
 
 
 func (a *argumentsObject) defineOwnProperty(n Value, descr propertyDescr, throw bool) bool {
 func (a *argumentsObject) defineOwnProperty(n Value, descr propertyDescr, throw bool) bool {
+	if _, ok := n.(*valueSymbol); ok {
+		return a.baseObject.defineOwnProperty(n, descr, throw)
+	}
 	name := n.String()
 	name := n.String()
 	if mapped, ok := a.values[name].(*mappedProperty); ok {
 	if mapped, ok := a.values[name].(*mappedProperty); ok {
 		existing := &valueProperty{
 		existing := &valueProperty{
@@ -130,17 +134,17 @@ func (a *argumentsObject) defineOwnProperty(n Value, descr propertyDescr, throw
 	return a.baseObject.defineOwnProperty(n, descr, throw)
 	return a.baseObject.defineOwnProperty(n, descr, throw)
 }
 }
 
 
-func (a *argumentsObject) getOwnProp(name string) Value {
+func (a *argumentsObject) getOwnPropStr(name string) Value {
 	if mapped, ok := a.values[name].(*mappedProperty); ok {
 	if mapped, ok := a.values[name].(*mappedProperty); ok {
 		return *mapped.v
 		return *mapped.v
 	}
 	}
 
 
-	return a.baseObject.getOwnProp(name)
+	return a.baseObject.getOwnPropStr(name)
 }
 }
 
 
 func (a *argumentsObject) export() interface{} {
 func (a *argumentsObject) export() interface{} {
 	arr := make([]interface{}, a.length)
 	arr := make([]interface{}, a.length)
-	for i, _ := range arr {
+	for i := range arr {
 		v := a.get(intToValue(int64(i)))
 		v := a.get(intToValue(int64(i)))
 		if v != nil {
 		if v != nil {
 			arr[i] = v.Export()
 			arr[i] = v.Export()

+ 12 - 5
object_gomap.go

@@ -51,14 +51,18 @@ func (o *objectGoMapSimple) getStr(name string) Value {
 	return o.baseObject._getStr(name)
 	return o.baseObject._getStr(name)
 }
 }
 
 
-func (o *objectGoMapSimple) getOwnProp(name string) Value {
+func (o *objectGoMapSimple) getOwnPropStr(name string) Value {
 	if v := o._getStr(name); v != nil {
 	if v := o._getStr(name); v != nil {
 		return v
 		return v
 	}
 	}
-	return o.baseObject.getOwnProp(name)
+	return o.baseObject.getOwnPropStr(name)
 }
 }
 
 
 func (o *objectGoMapSimple) put(n Value, val Value, throw bool) {
 func (o *objectGoMapSimple) put(n Value, val Value, throw bool) {
+	if _, ok := n.(*valueSymbol); ok {
+		o.val.runtime.typeErrorResult(throw, "Cannot set Symbol properties on Go maps")
+		return
+	}
 	o.putStr(n.String(), val, throw)
 	o.putStr(n.String(), val, throw)
 }
 }
 
 
@@ -107,8 +111,7 @@ func (o *objectGoMapSimple) _putProp(name string, value Value, writable, enumera
 }
 }
 
 
 func (o *objectGoMapSimple) defineOwnProperty(name Value, descr propertyDescr, throw bool) bool {
 func (o *objectGoMapSimple) defineOwnProperty(name Value, descr propertyDescr, throw bool) bool {
-	if descr.Getter != nil || descr.Setter != nil {
-		o.val.runtime.typeErrorResult(throw, "Host objects do not support accessor properties")
+	if !o.val.runtime.checkHostObjectPropertyDescr(name, descr, throw) {
 		return false
 		return false
 	}
 	}
 	o.put(name, descr.Value, throw)
 	o.put(name, descr.Value, throw)
@@ -139,6 +142,10 @@ func (o *objectGoMapSimple) deleteStr(name string, throw bool) bool {
 }
 }
 
 
 func (o *objectGoMapSimple) delete(name Value, throw bool) bool {
 func (o *objectGoMapSimple) delete(name Value, throw bool) bool {
+	if _, ok := name.(*valueSymbol); ok {
+		return true
+	}
+
 	return o.deleteStr(name.String(), throw)
 	return o.deleteStr(name.String(), throw)
 }
 }
 
 
@@ -176,7 +183,7 @@ func (o *objectGoMapSimple) enumerate(all, recursive bool) iterNextFunc {
 func (o *objectGoMapSimple) _enumerate(recursive bool) iterNextFunc {
 func (o *objectGoMapSimple) _enumerate(recursive bool) iterNextFunc {
 	propNames := make([]string, len(o.data))
 	propNames := make([]string, len(o.data))
 	i := 0
 	i := 0
-	for key, _ := range o.data {
+	for key := range o.data {
 		propNames[i] = key
 		propNames[i] = key
 		i++
 		i++
 	}
 	}

+ 48 - 17
object_gomap_reflect.go

@@ -14,24 +14,32 @@ func (o *objectGoMapReflect) init() {
 	o.valueType = o.value.Type().Elem()
 	o.valueType = o.value.Type().Elem()
 }
 }
 
 
-func (o *objectGoMapReflect) toKey(n Value) reflect.Value {
+func (o *objectGoMapReflect) toKey(n Value, throw bool) reflect.Value {
+	if _, ok := n.(*valueSymbol); ok {
+		o.val.runtime.typeErrorResult(throw, "Cannot set Symbol properties on Go maps")
+		return reflect.Value{}
+	}
 	key, err := o.val.runtime.toReflectValue(n, o.keyType)
 	key, err := o.val.runtime.toReflectValue(n, o.keyType)
 	if err != nil {
 	if err != nil {
-		o.val.runtime.typeErrorResult(true, "map key conversion error: %v", err)
-		panic("unreachable")
+		o.val.runtime.typeErrorResult(throw, "map key conversion error: %v", err)
+		return reflect.Value{}
 	}
 	}
 	return key
 	return key
 }
 }
 
 
-func (o *objectGoMapReflect) strToKey(name string) reflect.Value {
+func (o *objectGoMapReflect) strToKey(name string, throw bool) reflect.Value {
 	if o.keyType.Kind() == reflect.String {
 	if o.keyType.Kind() == reflect.String {
 		return reflect.ValueOf(name).Convert(o.keyType)
 		return reflect.ValueOf(name).Convert(o.keyType)
 	}
 	}
-	return o.toKey(newStringValue(name))
+	return o.toKey(newStringValue(name), throw)
 }
 }
 
 
 func (o *objectGoMapReflect) _get(n Value) Value {
 func (o *objectGoMapReflect) _get(n Value) Value {
-	if v := o.value.MapIndex(o.toKey(n)); v.IsValid() {
+	key := o.toKey(n, false)
+	if !key.IsValid() {
+		return nil
+	}
+	if v := o.value.MapIndex(key); v.IsValid() {
 		return o.val.runtime.ToValue(v.Interface())
 		return o.val.runtime.ToValue(v.Interface())
 	}
 	}
 
 
@@ -39,7 +47,11 @@ func (o *objectGoMapReflect) _get(n Value) Value {
 }
 }
 
 
 func (o *objectGoMapReflect) _getStr(name string) Value {
 func (o *objectGoMapReflect) _getStr(name string) Value {
-	if v := o.value.MapIndex(o.strToKey(name)); v.IsValid() {
+	key := o.strToKey(name, false)
+	if !key.IsValid() {
+		return nil
+	}
+	if v := o.value.MapIndex(key); v.IsValid() {
 		return o.val.runtime.ToValue(v.Interface())
 		return o.val.runtime.ToValue(v.Interface())
 	}
 	}
 
 
@@ -68,7 +80,7 @@ func (o *objectGoMapReflect) getPropStr(name string) Value {
 	return o.getStr(name)
 	return o.getStr(name)
 }
 }
 
 
-func (o *objectGoMapReflect) getOwnProp(name string) Value {
+func (o *objectGoMapReflect) getOwnPropStr(name string) Value {
 	if v := o._getStr(name); v != nil {
 	if v := o._getStr(name); v != nil {
 		return &valueProperty{
 		return &valueProperty{
 			value:      v,
 			value:      v,
@@ -76,7 +88,7 @@ func (o *objectGoMapReflect) getOwnProp(name string) Value {
 			enumerable: true,
 			enumerable: true,
 		}
 		}
 	}
 	}
-	return o.objectGoReflect.getOwnProp(name)
+	return o.objectGoReflect.getOwnPropStr(name)
 }
 }
 
 
 func (o *objectGoMapReflect) toValue(val Value, throw bool) (reflect.Value, bool) {
 func (o *objectGoMapReflect) toValue(val Value, throw bool) (reflect.Value, bool) {
@@ -90,7 +102,7 @@ func (o *objectGoMapReflect) toValue(val Value, throw bool) (reflect.Value, bool
 }
 }
 
 
 func (o *objectGoMapReflect) put(key, val Value, throw bool) {
 func (o *objectGoMapReflect) put(key, val Value, throw bool) {
-	k := o.toKey(key)
+	k := o.toKey(key, throw)
 	v, ok := o.toValue(val, throw)
 	v, ok := o.toValue(val, throw)
 	if !ok {
 	if !ok {
 		return
 		return
@@ -99,7 +111,10 @@ func (o *objectGoMapReflect) put(key, val Value, throw bool) {
 }
 }
 
 
 func (o *objectGoMapReflect) putStr(name string, val Value, throw bool) {
 func (o *objectGoMapReflect) putStr(name string, val Value, throw bool) {
-	k := o.strToKey(name)
+	k := o.strToKey(name, throw)
+	if !k.IsValid() {
+		return
+	}
 	v, ok := o.toValue(val, throw)
 	v, ok := o.toValue(val, throw)
 	if !ok {
 	if !ok {
 		return
 		return
@@ -113,8 +128,7 @@ func (o *objectGoMapReflect) _putProp(name string, value Value, writable, enumer
 }
 }
 
 
 func (o *objectGoMapReflect) defineOwnProperty(n Value, descr propertyDescr, throw bool) bool {
 func (o *objectGoMapReflect) defineOwnProperty(n Value, descr propertyDescr, throw bool) bool {
-	name := n.String()
-	if !o.val.runtime.checkHostObjectPropertyDescr(name, descr, throw) {
+	if !o.val.runtime.checkHostObjectPropertyDescr(n, descr, throw) {
 		return false
 		return false
 	}
 	}
 
 
@@ -123,11 +137,20 @@ func (o *objectGoMapReflect) defineOwnProperty(n Value, descr propertyDescr, thr
 }
 }
 
 
 func (o *objectGoMapReflect) hasOwnPropertyStr(name string) bool {
 func (o *objectGoMapReflect) hasOwnPropertyStr(name string) bool {
-	return o.value.MapIndex(o.strToKey(name)).IsValid()
+	key := o.strToKey(name, false)
+	if !key.IsValid() {
+		return false
+	}
+	return o.value.MapIndex(key).IsValid()
 }
 }
 
 
 func (o *objectGoMapReflect) hasOwnProperty(n Value) bool {
 func (o *objectGoMapReflect) hasOwnProperty(n Value) bool {
-	return o.value.MapIndex(o.toKey(n)).IsValid()
+	key := o.toKey(n, false)
+	if !key.IsValid() {
+		return false
+	}
+
+	return o.value.MapIndex(key).IsValid()
 }
 }
 
 
 func (o *objectGoMapReflect) hasProperty(n Value) bool {
 func (o *objectGoMapReflect) hasProperty(n Value) bool {
@@ -145,12 +168,20 @@ func (o *objectGoMapReflect) hasPropertyStr(name string) bool {
 }
 }
 
 
 func (o *objectGoMapReflect) delete(n Value, throw bool) bool {
 func (o *objectGoMapReflect) delete(n Value, throw bool) bool {
-	o.value.SetMapIndex(o.toKey(n), reflect.Value{})
+	key := o.toKey(n, throw)
+	if !key.IsValid() {
+		return false
+	}
+	o.value.SetMapIndex(key, reflect.Value{})
 	return true
 	return true
 }
 }
 
 
 func (o *objectGoMapReflect) deleteStr(name string, throw bool) bool {
 func (o *objectGoMapReflect) deleteStr(name string, throw bool) bool {
-	o.value.SetMapIndex(o.strToKey(name), reflect.Value{})
+	key := o.strToKey(name, throw)
+	if !key.IsValid() {
+		return false
+	}
+	o.value.SetMapIndex(key, reflect.Value{})
 	return true
 	return true
 }
 }
 
 

+ 31 - 29
object_goreflect.go

@@ -127,22 +127,14 @@ func (o *objectGoReflect) getStr(name string) Value {
 	return o.baseObject._getStr(name)
 	return o.baseObject._getStr(name)
 }
 }
 
 
-func (o *objectGoReflect) getProp(n Value) Value {
-	name := n.String()
-	if p := o.getOwnProp(name); p != nil {
-		return p
-	}
-	return o.baseObject.getOwnProp(name)
-}
-
 func (o *objectGoReflect) getPropStr(name string) Value {
 func (o *objectGoReflect) getPropStr(name string) Value {
-	if v := o.getOwnProp(name); v != nil {
+	if v := o.getOwnPropStr(name); v != nil {
 		return v
 		return v
 	}
 	}
 	return o.baseObject.getPropStr(name)
 	return o.baseObject.getPropStr(name)
 }
 }
 
 
-func (o *objectGoReflect) getOwnProp(name string) Value {
+func (o *objectGoReflect) getOwnPropStr(name string) Value {
 	if o.value.Kind() == reflect.Struct {
 	if o.value.Kind() == reflect.Struct {
 		if v := o._getField(name); v.IsValid() {
 		if v := o._getField(name); v.IsValid() {
 			return &valueProperty{
 			return &valueProperty{
@@ -164,6 +156,10 @@ func (o *objectGoReflect) getOwnProp(name string) Value {
 }
 }
 
 
 func (o *objectGoReflect) put(n Value, val Value, throw bool) {
 func (o *objectGoReflect) put(n Value, val Value, throw bool) {
+	if _, ok := n.(*valueSymbol); ok {
+		o.val.runtime.typeErrorResult(throw, "Cannot assign to Symbol property %s of a host object", n.String())
+		return
+	}
 	o.putStr(n.String(), val, throw)
 	o.putStr(n.String(), val, throw)
 }
 }
 
 
@@ -199,40 +195,46 @@ func (o *objectGoReflect) _putProp(name string, value Value, writable, enumerabl
 	return o.baseObject._putProp(name, value, writable, enumerable, configurable)
 	return o.baseObject._putProp(name, value, writable, enumerable, configurable)
 }
 }
 
 
-func (r *Runtime) checkHostObjectPropertyDescr(name string, descr propertyDescr, throw bool) bool {
+func (r *Runtime) checkHostObjectPropertyDescr(n Value, descr propertyDescr, throw bool) bool {
+	if _, ok := n.(*valueSymbol); ok {
+		r.typeErrorResult(throw, "Host objects do not support symbol properties")
+		return false
+	}
 	if descr.Getter != nil || descr.Setter != nil {
 	if descr.Getter != nil || descr.Setter != nil {
 		r.typeErrorResult(throw, "Host objects do not support accessor properties")
 		r.typeErrorResult(throw, "Host objects do not support accessor properties")
 		return false
 		return false
 	}
 	}
 	if descr.Writable == FLAG_FALSE {
 	if descr.Writable == FLAG_FALSE {
-		r.typeErrorResult(throw, "Host object field %s cannot be made read-only", name)
+		r.typeErrorResult(throw, "Host object field %s cannot be made read-only", n.String())
 		return false
 		return false
 	}
 	}
 	if descr.Configurable == FLAG_TRUE {
 	if descr.Configurable == FLAG_TRUE {
-		r.typeErrorResult(throw, "Host object field %s cannot be made configurable", name)
+		r.typeErrorResult(throw, "Host object field %s cannot be made configurable", n.String())
 		return false
 		return false
 	}
 	}
 	return true
 	return true
 }
 }
 
 
 func (o *objectGoReflect) defineOwnProperty(n Value, descr propertyDescr, throw bool) bool {
 func (o *objectGoReflect) defineOwnProperty(n Value, descr propertyDescr, throw bool) bool {
-	if o.value.Kind() == reflect.Struct {
-		name := n.String()
-		if v := o._getField(name); v.IsValid() {
-			if !o.val.runtime.checkHostObjectPropertyDescr(name, descr, throw) {
-				return false
-			}
-			val := descr.Value
-			if val == nil {
-				val = _undefined
-			}
-			vv, err := o.val.runtime.toReflectValue(val, v.Type())
-			if err != nil {
-				o.val.runtime.typeErrorResult(throw, "Go struct conversion error: %v", err)
-				return false
+	if _, ok := n.(*valueSymbol); !ok {
+		if o.value.Kind() == reflect.Struct {
+			name := n.String()
+			if v := o._getField(name); v.IsValid() {
+				if !o.val.runtime.checkHostObjectPropertyDescr(n, descr, throw) {
+					return false
+				}
+				val := descr.Value
+				if val == nil {
+					val = _undefined
+				}
+				vv, err := o.val.runtime.toReflectValue(val, v.Type())
+				if err != nil {
+					o.val.runtime.typeErrorResult(throw, "Go struct conversion error: %v", err)
+					return false
+				}
+				v.Set(vv)
+				return true
 			}
 			}
-			v.Set(vv)
-			return true
 		}
 		}
 	}
 	}
 
 

+ 3 - 3
object_goslice.go

@@ -74,7 +74,7 @@ func (o *objectGoSlice) getPropStr(name string) Value {
 	return o.baseObject.getPropStr(name)
 	return o.baseObject.getPropStr(name)
 }
 }
 
 
-func (o *objectGoSlice) getOwnProp(name string) Value {
+func (o *objectGoSlice) getOwnPropStr(name string) Value {
 	if v := o._getStr(name); v != nil {
 	if v := o._getStr(name); v != nil {
 		return &valueProperty{
 		return &valueProperty{
 			value:      v,
 			value:      v,
@@ -82,7 +82,7 @@ func (o *objectGoSlice) getOwnProp(name string) Value {
 			enumerable: true,
 			enumerable: true,
 		}
 		}
 	}
 	}
-	return o.baseObject.getOwnProp(name)
+	return o.baseObject.getOwnPropStr(name)
 }
 }
 
 
 func (o *objectGoSlice) grow(size int64) {
 func (o *objectGoSlice) grow(size int64) {
@@ -189,7 +189,7 @@ func (o *objectGoSlice) _putProp(name string, value Value, writable, enumerable,
 
 
 func (o *objectGoSlice) defineOwnProperty(n Value, descr propertyDescr, throw bool) bool {
 func (o *objectGoSlice) defineOwnProperty(n Value, descr propertyDescr, throw bool) bool {
 	if idx := toIdx(n); idx >= 0 {
 	if idx := toIdx(n); idx >= 0 {
-		if !o.val.runtime.checkHostObjectPropertyDescr(n.String(), descr, throw) {
+		if !o.val.runtime.checkHostObjectPropertyDescr(n, descr, throw) {
 			return false
 			return false
 		}
 		}
 		val := descr.Value
 		val := descr.Value

+ 3 - 3
object_goslice_reflect.go

@@ -79,11 +79,11 @@ func (o *objectGoSliceReflect) getPropStr(name string) Value {
 	return o.objectGoReflect.getPropStr(name)
 	return o.objectGoReflect.getPropStr(name)
 }
 }
 
 
-func (o *objectGoSliceReflect) getOwnProp(name string) Value {
+func (o *objectGoSliceReflect) getOwnPropStr(name string) Value {
 	if v := o._getStr(name); v != nil {
 	if v := o._getStr(name); v != nil {
 		return v
 		return v
 	}
 	}
-	return o.objectGoReflect.getOwnProp(name)
+	return o.objectGoReflect.getOwnPropStr(name)
 }
 }
 
 
 func (o *objectGoSliceReflect) putIdx(idx int64, v Value, throw bool) {
 func (o *objectGoSliceReflect) putIdx(idx int64, v Value, throw bool) {
@@ -151,7 +151,7 @@ func (o *objectGoSliceReflect) _putProp(name string, value Value, writable, enum
 }
 }
 
 
 func (o *objectGoSliceReflect) defineOwnProperty(name Value, descr propertyDescr, throw bool) bool {
 func (o *objectGoSliceReflect) defineOwnProperty(name Value, descr propertyDescr, throw bool) bool {
-	if !o.val.runtime.checkHostObjectPropertyDescr(name.String(), descr, throw) {
+	if !o.val.runtime.checkHostObjectPropertyDescr(name, descr, throw) {
 		return false
 		return false
 	}
 	}
 	o.put(name, descr.Value, throw)
 	o.put(name, descr.Value, throw)

+ 13 - 1
object_lazy.go

@@ -37,7 +37,13 @@ func (o *lazyObject) getStr(name string) Value {
 	return obj.getStr(name)
 	return obj.getStr(name)
 }
 }
 
 
-func (o *lazyObject) getOwnProp(name string) Value {
+func (o *lazyObject) getOwnPropStr(name string) Value {
+	obj := o.create(o.val)
+	o.val.self = obj
+	return obj.getOwnPropStr(name)
+}
+
+func (o *lazyObject) getOwnProp(name Value) Value {
 	obj := o.create(o.val)
 	obj := o.create(o.val)
 	o.val.self = obj
 	o.val.self = obj
 	return obj.getOwnProp(name)
 	return obj.getOwnProp(name)
@@ -181,6 +187,12 @@ func (o *lazyObject) equal(other objectImpl) bool {
 	return obj.equal(other)
 	return obj.equal(other)
 }
 }
 
 
+func (o *lazyObject) getOwnSymbols() []Value {
+	obj := o.create(o.val)
+	o.val.self = obj
+	return obj.getOwnSymbols()
+}
+
 func (o *lazyObject) sortLen() int64 {
 func (o *lazyObject) sortLen() int64 {
 	obj := o.create(o.val)
 	obj := o.create(o.val)
 	o.val.self = obj
 	o.val.self = obj

+ 8 - 9
regexp.go

@@ -24,7 +24,7 @@ type regexpObject struct {
 	pattern regexpPattern
 	pattern regexpPattern
 	source  valueString
 	source  valueString
 
 
-	global, multiline, ignoreCase bool
+	global, multiline, ignoreCase, sticky bool
 }
 }
 
 
 func (r *regexp2Wrapper) FindSubmatchIndex(s valueString, start int) (result []int) {
 func (r *regexp2Wrapper) FindSubmatchIndex(s valueString, start int) (result []int) {
@@ -308,26 +308,24 @@ func (r *regexpObject) execRegexp(target valueString) (match bool, result []int)
 		}
 		}
 	}
 	}
 	index := lastIndex
 	index := lastIndex
-	if !r.global {
+	if !r.global && !r.sticky {
 		index = 0
 		index = 0
 	}
 	}
 	if index >= 0 && index <= target.length() {
 	if index >= 0 && index <= target.length() {
 		result = r.pattern.FindSubmatchIndex(target, int(index))
 		result = r.pattern.FindSubmatchIndex(target, int(index))
 	}
 	}
-	if result == nil {
+	if result == nil || r.sticky && result[0] != 0 {
 		r.putStr("lastIndex", intToValue(0), true)
 		r.putStr("lastIndex", intToValue(0), true)
 		return
 		return
 	}
 	}
 	match = true
 	match = true
-	startIndex := index
-	endIndex := int(lastIndex) + result[1]
 	// We do this shift here because the .FindStringSubmatchIndex above
 	// We do this shift here because the .FindStringSubmatchIndex above
 	// was done on a local subordinate slice of the string, not the whole string
 	// was done on a local subordinate slice of the string, not the whole string
-	for index, _ := range result {
-		result[index] += int(startIndex)
+	for i := range result {
+		result[i] += int(index)
 	}
 	}
-	if r.global {
-		r.putStr("lastIndex", intToValue(int64(endIndex)), true)
+	if r.global || r.sticky {
+		r.putStr("lastIndex", intToValue(int64(result[1])), true)
 	}
 	}
 	return
 	return
 }
 }
@@ -352,6 +350,7 @@ func (r *regexpObject) clone() *Object {
 	r1.global = r.global
 	r1.global = r.global
 	r1.ignoreCase = r.ignoreCase
 	r1.ignoreCase = r.ignoreCase
 	r1.multiline = r.multiline
 	r1.multiline = r.multiline
+	r1.sticky = r.sticky
 	return r1.val
 	return r1.val
 }
 }
 
 

+ 100 - 55
runtime.go

@@ -27,6 +27,14 @@ var (
 	typeTime     = reflect.TypeOf(time.Time{})
 	typeTime     = reflect.TypeOf(time.Time{})
 )
 )
 
 
+type iterationKind int
+
+const (
+	iterationKindKey iterationKind = iota
+	iterationKindValue
+	iterationKindKeyValue
+)
+
 type global struct {
 type global struct {
 	Object   *Object
 	Object   *Object
 	Array    *Object
 	Array    *Object
@@ -36,6 +44,7 @@ type global struct {
 	Boolean  *Object
 	Boolean  *Object
 	RegExp   *Object
 	RegExp   *Object
 	Date     *Object
 	Date     *Object
+	Symbol   *Object
 
 
 	ArrayBuffer *Object
 	ArrayBuffer *Object
 
 
@@ -57,9 +66,14 @@ type global struct {
 	FunctionPrototype *Object
 	FunctionPrototype *Object
 	RegExpPrototype   *Object
 	RegExpPrototype   *Object
 	DatePrototype     *Object
 	DatePrototype     *Object
+	SymbolPrototype   *Object
+	ArrayIterator     *Object
 
 
 	ArrayBufferPrototype *Object
 	ArrayBufferPrototype *Object
 
 
+	IteratorPrototype      *Object
+	ArrayIteratorPrototype *Object
+
 	ErrorPrototype          *Object
 	ErrorPrototype          *Object
 	TypeErrorPrototype      *Object
 	TypeErrorPrototype      *Object
 	SyntaxErrorPrototype    *Object
 	SyntaxErrorPrototype    *Object
@@ -74,6 +88,8 @@ type global struct {
 
 
 	thrower         *Object
 	thrower         *Object
 	throwerProperty Value
 	throwerProperty Value
+
+	regexpProtoExec Value
 }
 }
 
 
 type Flag int
 type Flag int
@@ -107,6 +123,8 @@ type Runtime struct {
 	now             Now
 	now             Now
 	_collator       *collate.Collator
 	_collator       *collate.Collator
 
 
+	symbolRegistry map[string]*valueSymbol
+
 	typeInfoCache   map[reflect.Type]*reflectTypeInfo
 	typeInfoCache   map[reflect.Type]*reflectTypeInfo
 	fieldNameMapper FieldNameMapper
 	fieldNameMapper FieldNameMapper
 
 
@@ -237,6 +255,13 @@ func (r *Runtime) addToGlobal(name string, value Value) {
 	r.globalObject.self._putProp(name, value, true, false, true)
 	r.globalObject.self._putProp(name, value, true, false, true)
 }
 }
 
 
+func (r *Runtime) createIterProto(val *Object) objectImpl {
+	o := newBaseObjectObj(val, r.global.ObjectPrototype, classObject)
+
+	o.put(symIterator, valueProp(r.newNativeFunc(r.returnThis, nil, "[Symbol.iterator]", nil, 0), true, false, true), true)
+	return o
+}
+
 func (r *Runtime) init() {
 func (r *Runtime) init() {
 	r.rand = rand.Float64
 	r.rand = rand.Float64
 	r.now = time.Now
 	r.now = time.Now
@@ -249,6 +274,8 @@ func (r *Runtime) init() {
 	r.vm.init()
 	r.vm.init()
 
 
 	r.global.FunctionPrototype = r.newNativeFunc(nil, nil, "Empty", nil, 0)
 	r.global.FunctionPrototype = r.newNativeFunc(nil, nil, "Empty", nil, 0)
+	r.global.IteratorPrototype = r.newLazyObject(r.createIterProto)
+
 	r.initObject()
 	r.initObject()
 	r.initFunction()
 	r.initFunction()
 	r.initArray()
 	r.initArray()
@@ -269,6 +296,7 @@ func (r *Runtime) init() {
 	r.initJSON()
 	r.initJSON()
 
 
 	//r.initTypedArrays()
 	//r.initTypedArrays()
+	r.initSymbol()
 
 
 	r.global.thrower = r.newNativeFunc(r.builtin_thrower, nil, "thrower", nil, 0)
 	r.global.thrower = r.newNativeFunc(r.builtin_thrower, nil, "thrower", nil, 0)
 	r.global.throwerProperty = &valueProperty{
 	r.global.throwerProperty = &valueProperty{
@@ -297,56 +325,20 @@ func (r *Runtime) newSyntaxError(msg string, offset int) Value {
 	return r.builtin_new((r.global.SyntaxError), []Value{newStringValue(msg)})
 	return r.builtin_new((r.global.SyntaxError), []Value{newStringValue(msg)})
 }
 }
 
 
-func (r *Runtime) newArray(prototype *Object) (a *arrayObject) {
-	v := &Object{runtime: r}
-
-	a = &arrayObject{}
-	a.class = classArray
-	a.val = v
-	a.extensible = true
-	v.self = a
-	a.prototype = prototype
-	a.init()
-	return
-}
-
-func (r *Runtime) newArrayObject() *arrayObject {
-	return r.newArray(r.global.ArrayPrototype)
-}
-
-func (r *Runtime) newArrayValues(values []Value) *Object {
-	v := &Object{runtime: r}
-
-	a := &arrayObject{}
-	a.class = classArray
-	a.val = v
-	a.extensible = true
-	v.self = a
-	a.prototype = r.global.ArrayPrototype
-	a.init()
-	a.values = values
-	a.length = int64(len(values))
-	a.objCount = a.length
-	return v
-}
-
-func (r *Runtime) newArrayLength(l int64) *Object {
-	a := r.newArrayValues(nil)
-	a.self.putStr("length", intToValue(l), true)
-	return a
-}
-
-func (r *Runtime) newBaseObject(proto *Object, class string) (o *baseObject) {
-	v := &Object{runtime: r}
-
-	o = &baseObject{}
+func newBaseObjectObj(obj, proto *Object, class string) *baseObject {
+	o := &baseObject{}
 	o.class = class
 	o.class = class
-	o.val = v
+	o.val = obj
 	o.extensible = true
 	o.extensible = true
-	v.self = o
+	obj.self = o
 	o.prototype = proto
 	o.prototype = proto
 	o.init()
 	o.init()
-	return
+	return o
+}
+
+func (r *Runtime) newBaseObject(proto *Object, class string) (o *baseObject) {
+	v := &Object{runtime: r}
+	return newBaseObjectObj(v, proto, class)
 }
 }
 
 
 func (r *Runtime) NewObject() (v *Object) {
 func (r *Runtime) NewObject() (v *Object) {
@@ -603,37 +595,44 @@ func (r *Runtime) builtin_Error(args []Value, proto *Object) *Object {
 	return obj.val
 	return obj.val
 }
 }
 
 
-func (r *Runtime) builtin_new(construct *Object, args []Value) *Object {
+func getConstructor(construct *Object) func(args []Value) *Object {
 repeat:
 repeat:
 	switch f := construct.self.(type) {
 	switch f := construct.self.(type) {
 	case *nativeFuncObject:
 	case *nativeFuncObject:
 		if f.construct != nil {
 		if f.construct != nil {
-			return f.construct(args)
+			return f.construct
 		} else {
 		} else {
-			panic("Not a constructor")
+			panic(construct.runtime.NewTypeError("Not a constructor"))
 		}
 		}
 	case *boundFuncObject:
 	case *boundFuncObject:
 		if f.construct != nil {
 		if f.construct != nil {
-			return f.construct(args)
+			return f.construct
 		} else {
 		} else {
-			panic("Not a constructor")
+			panic(construct.runtime.NewTypeError("Not a constructor"))
 		}
 		}
 	case *funcObject:
 	case *funcObject:
-		// TODO: implement
-		panic("Not implemented")
+		return f.construct
 	case *lazyObject:
 	case *lazyObject:
 		construct.self = f.create(construct)
 		construct.self = f.create(construct)
 		goto repeat
 		goto repeat
-	default:
+	}
+
+	return nil
+}
+
+func (r *Runtime) builtin_new(construct *Object, args []Value) *Object {
+	f := getConstructor(construct)
+	if f == nil {
 		panic("Not a constructor")
 		panic("Not a constructor")
 	}
 	}
+	return f(args)
 }
 }
 
 
 func (r *Runtime) throw(e Value) {
 func (r *Runtime) throw(e Value) {
 	panic(e)
 	panic(e)
 }
 }
 
 
-func (r *Runtime) builtin_thrower(call FunctionCall) Value {
+func (r *Runtime) builtin_thrower(FunctionCall) Value {
 	r.typeErrorResult(true, "'caller', 'callee', and 'arguments' properties may not be accessed on strict mode functions or the arguments objects for calls to them")
 	r.typeErrorResult(true, "'caller', 'callee', and 'arguments' properties may not be accessed on strict mode functions or the arguments objects for calls to them")
 	return nil
 	return nil
 }
 }
@@ -1572,3 +1571,49 @@ func tryFunc(f func()) (err error) {
 
 
 	return nil
 	return nil
 }
 }
+
+func (r *Runtime) toObject(v Value, args ...interface{}) *Object {
+	if obj, ok := v.(*Object); ok {
+		return obj
+	}
+	if len(args) > 0 {
+		panic(r.NewTypeError(args...))
+	} else {
+		var s string
+		if v == nil {
+			s = "undefined"
+		} else {
+			s = v.String()
+		}
+		panic(r.NewTypeError("Value is not an object: %s", s))
+	}
+}
+
+func (r *Runtime) speciesConstructor(o, defaultConstructor *Object) func(args []Value) *Object {
+	c := o.self.getStr("constructor")
+	if c != nil && c != _undefined {
+		c = r.toObject(c).self.get(symSpecies)
+	}
+	if c == nil || c == _undefined {
+		c = defaultConstructor
+	}
+	return getConstructor(r.toObject(c))
+}
+
+func (r *Runtime) returnThis(call FunctionCall) Value {
+	return call.This
+}
+
+func (r *Runtime) createIterResultObject(value Value, done bool) Value {
+	o := r.NewObject()
+	o.self.putStr("value", value, false)
+	o.self.putStr("done", r.toBoolean(done), false)
+	return o
+}
+
+func nilSafe(v Value) Value {
+	if v != nil {
+		return v
+	}
+	return _undefined
+}

+ 21 - 0
runtime_test.go

@@ -1327,6 +1327,27 @@ func TestProtoGetter(t *testing.T) {
 	testScript1(SCRIPT, valueTrue, t)
 	testScript1(SCRIPT, valueTrue, t)
 }
 }
 
 
+func TestSymbol1(t *testing.T) {
+	const SCRIPT = `
+		Symbol.toPrimitive[Symbol.toPrimitive]() === Symbol.toPrimitive;
+	`
+
+	testScript1(SCRIPT, valueTrue, t)
+}
+
+func TestFreezeSymbol(t *testing.T) {
+	const SCRIPT = `
+		var s = Symbol(1);
+		var o = {};
+		o[s] = 42;
+		Object.freeze(o);
+		o[s] = 43;
+		o[s] === 42 && Object.isFrozen(o);
+	`
+
+	testScript1(SCRIPT, valueTrue, t)
+}
+
 /*
 /*
 func TestArrayConcatSparse(t *testing.T) {
 func TestArrayConcatSparse(t *testing.T) {
 function foo(a,b,c)
 function foo(a,b,c)

+ 3 - 2
string.go

@@ -20,6 +20,7 @@ var (
 	stringFunction     valueString = asciiString("function")
 	stringFunction     valueString = asciiString("function")
 	stringBoolean      valueString = asciiString("boolean")
 	stringBoolean      valueString = asciiString("boolean")
 	stringString       valueString = asciiString("string")
 	stringString       valueString = asciiString("string")
+	stringSymbol       valueString = asciiString("symbol")
 	stringNumber       valueString = asciiString("number")
 	stringNumber       valueString = asciiString("number")
 	stringNaN          valueString = asciiString("NaN")
 	stringNaN          valueString = asciiString("NaN")
 	stringInfinity                 = asciiString("Infinity")
 	stringInfinity                 = asciiString("Infinity")
@@ -120,7 +121,7 @@ func (s *stringObject) getProp(n Value) Value {
 	return s.baseObject.getProp(n)
 	return s.baseObject.getProp(n)
 }
 }
 
 
-func (s *stringObject) getOwnProp(name string) Value {
+func (s *stringObject) getOwnPropStr(name string) Value {
 	if i := strToIdx(name); i >= 0 && i < s.length {
 	if i := strToIdx(name); i >= 0 && i < s.length {
 		val := s.getIdx(i)
 		val := s.getIdx(i)
 		return &valueProperty{
 		return &valueProperty{
@@ -129,7 +130,7 @@ func (s *stringObject) getOwnProp(name string) Value {
 		}
 		}
 	}
 	}
 
 
-	return s.baseObject.getOwnProp(name)
+	return s.baseObject.getOwnPropStr(name)
 }
 }
 
 
 func (s *stringObject) getIdx(idx int64) Value {
 func (s *stringObject) getIdx(idx int64) Value {

+ 50 - 24
tc39_test.go

@@ -2,6 +2,7 @@ package goja
 
 
 import (
 import (
 	"errors"
 	"errors"
+	"fmt"
 	"gopkg.in/yaml.v2"
 	"gopkg.in/yaml.v2"
 	"io/ioutil"
 	"io/ioutil"
 	"os"
 	"os"
@@ -29,33 +30,53 @@ var (
 		"test/built-ins/Date/prototype/toISOString/15.9.5.43-0-8.js":  true, // timezone
 		"test/built-ins/Date/prototype/toISOString/15.9.5.43-0-8.js":  true, // timezone
 		"test/built-ins/Date/prototype/toISOString/15.9.5.43-0-9.js":  true, // timezone
 		"test/built-ins/Date/prototype/toISOString/15.9.5.43-0-9.js":  true, // timezone
 		"test/built-ins/Date/prototype/toISOString/15.9.5.43-0-10.js": true, // timezone
 		"test/built-ins/Date/prototype/toISOString/15.9.5.43-0-10.js": true, // timezone
+		"test/annexB/built-ins/escape/escape-above-astral.js":         true, // \u{xxxxx}
+
+		"test/built-ins/Symbol/unscopables/cross-realm.js":                                                true,
+		"test/built-ins/Symbol/toStringTag/cross-realm.js":                                                true,
+		"test/built-ins/Symbol/toPrimitive/cross-realm.js":                                                true,
+		"test/built-ins/Symbol/split/cross-realm.js":                                                      true,
+		"test/built-ins/Symbol/species/cross-realm.js":                                                    true,
+		"test/built-ins/Symbol/search/cross-realm.js":                                                     true,
+		"test/built-ins/Symbol/replace/cross-realm.js":                                                    true,
+		"test/built-ins/Symbol/match/cross-realm.js":                                                      true,
+		"test/built-ins/Symbol/keyFor/cross-realm.js":                                                     true,
+		"test/built-ins/Symbol/iterator/cross-realm.js":                                                   true,
+		"test/built-ins/Symbol/isConcatSpreadable/cross-realm.js":                                         true,
+		"test/built-ins/Symbol/hasInstance/cross-realm.js":                                                true,
+		"test/built-ins/Symbol/for/cross-realm.js":                                                        true,
+		"test/built-ins/Set/symbol-as-entry.js":                                                           true,
+		"test/built-ins/Map/symbol-as-entry-key.js":                                                       true,
+		"test/language/statements/class/subclass/builtin-objects/Symbol/symbol-valid-as-extends-value.js": true,
+		"test/language/statements/class/subclass/builtin-objects/Symbol/new-symbol-with-super-throws.js":  true,
+
+		// Proxy
+		"test/built-ins/Object/prototype/toString/proxy-revoked.js":  true,
+		"test/built-ins/Object/prototype/toString/proxy-function.js": true,
+		"test/built-ins/Object/prototype/toString/proxy-array.js":    true,
+		"test/built-ins/JSON/stringify/value-proxy.js":               true,
 	}
 	}
 
 
-	es6WhiteList = map[string]bool{
-		"test/annexB/built-ins/escape/empty-string.js":      true,
-		"test/annexB/built-ins/escape/escape-above.js":      true,
-		"test/annexB/built-ins/escape/escape-below.js":      true,
-		"test/annexB/built-ins/escape/length.js":            true,
-		"test/annexB/built-ins/escape/name.js":              true,
-		"test/annexB/built-ins/escape/to-string-err.js":     true,
-		"test/annexB/built-ins/escape/to-string-observe.js": true,
-		"test/annexB/built-ins/escape/unmodified.js":        true,
-
-		"test/annexB/built-ins/unescape/empty-string.js":        true,
-		"test/annexB/built-ins/unescape/four.js":                true,
-		"test/annexB/built-ins/unescape/four-ignore-bad-u.js":   true,
-		"test/annexB/built-ins/unescape/four-ignore-end-str.js": true,
-		"test/annexB/built-ins/unescape/four-ignore-non-hex.js": true,
-		"test/annexB/built-ins/unescape/length.js":              true,
-		"test/annexB/built-ins/unescape/name.js":                true,
-		"test/annexB/built-ins/unescape/to-string-err.js":       true,
-		"test/annexB/built-ins/unescape/to-string-observe.js":   true,
-		"test/annexB/built-ins/unescape/two.js":                 true,
-		"test/annexB/built-ins/unescape/two-ignore-end-str.js":  true,
-		"test/annexB/built-ins/unescape/two-ignore-non-hex.js":  true,
+	es6WhiteList = map[string]bool{}
+
+	es6IdWhiteList = []string{
+		"12.9.3",
+		"12.9.4",
+		"19.1.2.8",
+		"19.1.2.5",
+		"19.1.3.6",
+		"19.4",
+		"21.1.3.14",
+		"21.1.3.15",
+		"21.1.3.17",
+		//"21.2.5.6",
+		"22.1.2.5",
+		//"22.1.3.1",
+		"22.1.3.29",
+		"25.1.2",
+		"B.2.1",
+		"B.2.2",
 	}
 	}
-
-	es6IdWhiteList = []string{}
 )
 )
 
 
 type tc39Test struct {
 type tc39Test struct {
@@ -131,6 +152,11 @@ func parseTC39File(name string) (*tc39Meta, string, error) {
 }
 }
 
 
 func (ctx *tc39TestCtx) runTC39Test(name, src string, meta *tc39Meta, t testing.TB) {
 func (ctx *tc39TestCtx) runTC39Test(name, src string, meta *tc39Meta, t testing.TB) {
+	defer func() {
+		if x := recover(); x != nil {
+			panic(fmt.Sprintf("panic while running %s: %v", name, x))
+		}
+	}()
 	vm := New()
 	vm := New()
 	err, early := ctx.runTC39Script(name, src, meta.Includes, vm)
 	err, early := ctx.runTC39Script(name, src, meta.Includes, vm)
 
 

+ 78 - 1
value.go

@@ -1,6 +1,7 @@
 package goja
 package goja
 
 
 import (
 import (
+	"fmt"
 	"math"
 	"math"
 	"reflect"
 	"reflect"
 	"regexp"
 	"regexp"
@@ -53,6 +54,8 @@ type Value interface {
 	baseObject(r *Runtime) *Object
 	baseObject(r *Runtime) *Object
 }
 }
 
 
+type typeError string
+
 type valueInt int64
 type valueInt int64
 type valueFloat float64
 type valueFloat float64
 type valueBool bool
 type valueBool bool
@@ -60,6 +63,9 @@ type valueNull struct{}
 type valueUndefined struct {
 type valueUndefined struct {
 	valueNull
 	valueNull
 }
 }
+type valueSymbol struct {
+	desc string
+}
 
 
 type valueUnresolved struct {
 type valueUnresolved struct {
 	r   *Runtime
 	r   *Runtime
@@ -498,7 +504,7 @@ func (f valueFloat) ToString() valueString {
 	return asciiString(f.String())
 	return asciiString(f.String())
 }
 }
 
 
-var matchLeading0Exponent = regexp.MustCompile(`([eE][\+\-])0+([1-9])`) // 1e-07 => 1e-7
+var matchLeading0Exponent = regexp.MustCompile(`([eE][+\-])0+([1-9])`) // 1e-07 => 1e-7
 
 
 func (f valueFloat) String() string {
 func (f valueFloat) String() string {
 	value := float64(f)
 	value := float64(f)
@@ -854,6 +860,77 @@ func (o valueUnresolved) ExportType() reflect.Type {
 	return nil
 	return nil
 }
 }
 
 
+func (s *valueSymbol) ToInteger() int64 {
+	panic(typeError("Cannot convert a Symbol value to a number"))
+}
+
+func (s *valueSymbol) ToString() valueString {
+	panic(typeError("Cannot convert a Symbol value to a string"))
+}
+
+func (s *valueSymbol) String() string {
+	return s.descString()
+}
+
+func (s *valueSymbol) ToFloat() float64 {
+	panic(typeError("Cannot convert a Symbol value to a number"))
+}
+
+func (s *valueSymbol) ToNumber() Value {
+	panic(typeError("Cannot convert a Symbol value to a number"))
+}
+
+func (s *valueSymbol) ToBoolean() bool {
+	return true
+}
+
+func (s *valueSymbol) ToObject(r *Runtime) *Object {
+	return s.baseObject(r)
+}
+
+func (s *valueSymbol) SameAs(other Value) bool {
+	if s1, ok := other.(*valueSymbol); ok {
+		return s == s1
+	}
+	return false
+}
+
+func (s *valueSymbol) Equals(o Value) bool {
+	return s.SameAs(o)
+}
+
+func (s *valueSymbol) StrictEquals(o Value) bool {
+	return s.SameAs(o)
+}
+
+func (s *valueSymbol) Export() interface{} {
+	return s.String()
+}
+
+func (s *valueSymbol) ExportType() reflect.Type {
+	return reflectTypeString
+}
+
+func (s *valueSymbol) assertInt() (int64, bool) {
+	return 0, false
+}
+
+func (s *valueSymbol) assertString() (valueString, bool) {
+	return nil, false
+}
+
+func (s *valueSymbol) assertFloat() (float64, bool) {
+	return 0, false
+}
+
+func (s *valueSymbol) baseObject(r *Runtime) *Object {
+	return r.newPrimitiveObject(s, r.global.SymbolPrototype, "Symbol")
+}
+
+func (s *valueSymbol) descString() string {
+	return fmt.Sprintf("Symbol(%s)", s.desc)
+}
+
 func init() {
 func init() {
 	for i := 0; i < 256; i++ {
 	for i := 0; i < 256; i++ {
 		intCache[i] = valueInt(i - 128)
 		intCache[i] = valueInt(i - 128)

+ 20 - 50
vm.go

@@ -366,6 +366,10 @@ func (vm *vm) try(f func()) (ex *Exception) {
 				panic(x1)
 				panic(x1)
 			case *Exception:
 			case *Exception:
 				ex = x1
 				ex = x1
+			case typeError:
+				ex = &Exception{
+					val: vm.r.NewTypeError(string(x1)),
+				}
 			default:
 			default:
 				/*
 				/*
 					if vm.prg != nil {
 					if vm.prg != nil {
@@ -448,19 +452,7 @@ func (vm *vm) popCtx() {
 	vm.callStack = vm.callStack[:l]
 	vm.callStack = vm.callStack[:l]
 }
 }
 
 
-func (r *Runtime) toObject(v Value, args ...interface{}) *Object {
-	//r.checkResolveable(v)
-	if obj, ok := v.(*Object); ok {
-		return obj
-	}
-	if len(args) > 0 {
-		panic(r.NewTypeError(args...))
-	} else {
-		panic(r.NewTypeError("Value is not an object: %s", v.String()))
-	}
-}
-
-func (r *Runtime) toCallee(v Value) *Object {
+func (vm *vm) toCallee(v Value) *Object {
 	if obj, ok := v.(*Object); ok {
 	if obj, ok := v.(*Object); ok {
 		return obj
 		return obj
 	}
 	}
@@ -469,11 +461,9 @@ func (r *Runtime) toCallee(v Value) *Object {
 		unresolved.throw()
 		unresolved.throw()
 		panic("Unreachable")
 		panic("Unreachable")
 	case memberUnresolved:
 	case memberUnresolved:
-		r.typeErrorResult(true, "Object has no member '%s'", unresolved.ref)
-		panic("Unreachable")
+		panic(vm.r.NewTypeError("Object has no member '%s'", unresolved.ref))
 	}
 	}
-	r.typeErrorResult(true, "Value is not an object: %s", v.ToString())
-	panic("Unreachable")
+	panic(vm.r.NewTypeError("Value is not an object: %s", v.ToString()))
 }
 }
 
 
 type _newStash struct{}
 type _newStash struct{}
@@ -1263,11 +1253,11 @@ type newRegexp struct {
 	pattern regexpPattern
 	pattern regexpPattern
 	src     valueString
 	src     valueString
 
 
-	global, ignoreCase, multiline bool
+	global, ignoreCase, multiline, sticky bool
 }
 }
 
 
 func (n *newRegexp) exec(vm *vm) {
 func (n *newRegexp) exec(vm *vm) {
-	vm.push(vm.r.newRegExpp(n.pattern, n.src, n.global, n.ignoreCase, n.multiline, vm.r.global.RegExpPrototype))
+	vm.push(vm.r.newRegExpp(n.pattern, n.src, n.global, n.ignoreCase, n.multiline, n.sticky, vm.r.global.RegExpPrototype))
 	vm.pc++
 	vm.pc++
 }
 }
 
 
@@ -1725,7 +1715,7 @@ func (numargs call) exec(vm *vm) {
 	// arg<numargs-1>
 	// arg<numargs-1>
 	n := int(numargs)
 	n := int(numargs)
 	v := vm.stack[vm.sp-n-1] // callee
 	v := vm.stack[vm.sp-n-1] // callee
-	obj := vm.r.toCallee(v)
+	obj := vm.toCallee(v)
 repeat:
 repeat:
 	switch f := obj.self.(type) {
 	switch f := obj.self.(type) {
 	case *funcObject:
 	case *funcObject:
@@ -2136,7 +2126,7 @@ func (_op_instanceof) exec(vm *vm) {
 	left := vm.stack[vm.sp-2]
 	left := vm.stack[vm.sp-2]
 	right := vm.r.toObject(vm.stack[vm.sp-1])
 	right := vm.r.toObject(vm.stack[vm.sp-1])
 
 
-	if right.self.hasInstance(left) {
+	if instanceOfOperator(left, right) {
 		vm.stack[vm.sp-2] = valueTrue
 		vm.stack[vm.sp-2] = valueTrue
 	} else {
 	} else {
 		vm.stack[vm.sp-2] = valueFalse
 		vm.stack[vm.sp-2] = valueFalse
@@ -2238,37 +2228,15 @@ func (_throw) exec(vm *vm) {
 type _new uint32
 type _new uint32
 
 
 func (n _new) exec(vm *vm) {
 func (n _new) exec(vm *vm) {
-	obj := vm.r.toObject(vm.stack[vm.sp-1-int(n)])
-repeat:
-	switch f := obj.self.(type) {
-	case *funcObject:
-		args := make([]Value, n)
-		copy(args, vm.stack[vm.sp-int(n):])
-		vm.sp -= int(n)
-		vm.stack[vm.sp-1] = f.construct(args)
-	case *nativeFuncObject:
-		vm._nativeNew(f, int(n))
-	case *boundFuncObject:
-		vm._nativeNew(&f.nativeFuncObject, int(n))
-	case *lazyObject:
-		obj.self = f.create(obj)
-		goto repeat
-	default:
-		vm.r.typeErrorResult(true, "Not a constructor")
-	}
-
-	vm.pc++
-}
-
-func (vm *vm) _nativeNew(f *nativeFuncObject, n int) {
-	if f.construct != nil {
-		args := make([]Value, n)
-		copy(args, vm.stack[vm.sp-n:])
-		vm.sp -= n
-		vm.stack[vm.sp-1] = f.construct(args)
+	sp := vm.sp - int(n)
+	obj := vm.r.toObject(vm.stack[sp-1])
+	if ctor := getConstructor(obj); ctor != nil {
+		vm.stack[sp-1] = ctor(vm.stack[sp:vm.sp])
+		vm.sp = sp
 	} else {
 	} else {
-		vm.r.typeErrorResult(true, "Not a constructor")
+		panic(vm.r.NewTypeError("Not a constructor"))
 	}
 	}
+	vm.pc++
 }
 }
 
 
 type _typeof struct{}
 type _typeof struct{}
@@ -2299,6 +2267,8 @@ func (_typeof) exec(vm *vm) {
 		r = stringString
 		r = stringString
 	case valueInt, valueFloat:
 	case valueInt, valueFloat:
 		r = stringNumber
 		r = stringNumber
+	case *valueSymbol:
+		r = stringSymbol
 	default:
 	default:
 		panic(fmt.Errorf("Unknown type: %T", v))
 		panic(fmt.Errorf("Unknown type: %T", v))
 	}
 	}