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"
 )
 
+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 {
 	baseObject
 	values         []Value
@@ -129,6 +178,9 @@ func toIdx(v Value) (idx int64) {
 	if idxVal, ok1 := v.(valueInt); ok1 {
 		idx = int64(idxVal)
 	} else {
+		if _, ok := v.(*valueSymbol); ok {
+			return -1
+		}
 		if i, err := strconv.ParseInt(v.String(), 10, 64); err == nil {
 			idx = i
 		}
@@ -155,9 +207,10 @@ func (a *arrayObject) getProp(n Value) Value {
 	if idx := toIdx(n); idx >= 0 {
 		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)
 }
@@ -177,7 +230,7 @@ func (a *arrayObject) getPropStr(name string) Value {
 	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 >= 0 && i < int64(len(a.values)) {
 			return a.values[i]
@@ -186,7 +239,7 @@ func (a *arrayObject) getOwnProp(name string) Value {
 	if name == "length" {
 		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) {

+ 7 - 4
array_sparse.go

@@ -120,9 +120,12 @@ func (a *sparseArrayObject) getProp(n Value) Value {
 		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)
 }
 
@@ -131,7 +134,7 @@ func (a *sparseArrayObject) getLengthProp() Value {
 	return &a.lengthProp
 }
 
-func (a *sparseArrayObject) getOwnProp(name string) Value {
+func (a *sparseArrayObject) getOwnPropStr(name string) Value {
 	if idx := strToIdx(name); idx >= 0 {
 		i := a.findIdx(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" {
 		return a.getLengthProp()
 	}
-	return a.baseObject.getOwnProp(name)
+	return a.baseObject.getOwnPropStr(name)
 }
 
 func (a *sparseArrayObject) getPropStr(name string) Value {

+ 106 - 9
builtin_array.go

@@ -6,11 +6,48 @@ import (
 	"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 {
 	l := len(args)
 	if l == 1 {
 		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 {
 			al := int64(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"))
 			}
 		}
-		return r.newArrayValues([]Value{args[0]})
+		return setArrayValues(r.newArray(proto), []Value{args[0]}).val
 	} else {
 		argsCopy := make([]Value, l)
 		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)
 }
 
-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"))
 	if l == 0 {
 		obj.self.putStr("length", intToValue(0), true)
@@ -72,11 +109,11 @@ func (r *Runtime) arrayproto_pop(call FunctionCall) Value {
 			}
 			if val == nil {
 				// optimisation bail-out
-				return r.arrayproto_pop_generic(obj, call)
+				return r.arrayproto_pop_generic(obj)
 			}
 			if _, ok := val.(*valueProperty); ok {
 				// optimisation bail-out
-				return r.arrayproto_pop_generic(obj, call)
+				return r.arrayproto_pop_generic(obj)
 			}
 			//a._setLengthInt(l, false)
 			a.values[l] = nil
@@ -86,7 +123,7 @@ func (r *Runtime) arrayproto_pop(call FunctionCall) Value {
 		}
 		return _undefined
 	} 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) {
 	descr := propertyDescr{
 		Writable:     FLAG_TRUE,
@@ -200,7 +245,7 @@ func (r *Runtime) arrayproto_concat_append(a *Object, item Value) {
 
 	aLength := toLength(a.self.getStr("length"))
 	if obj, ok := item.(*Object); ok {
-		if isArray(obj) {
+		if isConcatSpreadable(obj) {
 			length := toLength(obj.self.getStr("length"))
 			for i := int64(0); i < length; 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)
 }
 
+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 {
-	a := r.newArrayValues(nil)
+	obj := call.This.ToObject(r)
+	a := arraySpeciesCreate(obj, 0)
 	r.arrayproto_concat_append(a, call.This.ToObject(r))
 	for _, item := range call.Arguments {
 		r.arrayproto_concat_append(a, item)
@@ -758,6 +824,10 @@ func (r *Runtime) arrayproto_shift(call FunctionCall) Value {
 	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 {
 	if o, ok := call.Argument(0).(*Object); ok {
 		if isArray(o) {
@@ -767,6 +837,14 @@ func (r *Runtime) array_isArray(call FunctionCall) Value {
 	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 {
 	o := &arrayObject{
 		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("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)
+	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
 }
@@ -807,10 +888,26 @@ func (r *Runtime) createArrayProto(val *Object) objectImpl {
 func (r *Runtime) createArray(val *Object) objectImpl {
 	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.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
 }
 
 func (r *Runtime) initArray() {
+	r.global.ArrayIteratorPrototype = r.newLazyObject(r.createArrayIterProto)
 	//r.global.ArrayPrototype = r.newArray(r.global.ObjectPrototype).val
 	//o := r.global.ArrayPrototype.self
 	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)
 	v.self = &boundFuncObject{
 		nativeFuncObject: *ff,
+		wrapped:          obj,
 	}
 
 	//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 r.NewObject()
+	return r.newBaseObject(proto, classObject).val
 }
 
 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 {
 	obj := call.Argument(0).ToObject(r)
-	propName := call.Argument(1).String()
+	propName := call.Argument(1)
 	desc := obj.self.getOwnProp(propName)
 	if desc == nil {
 		return _undefined
@@ -83,6 +83,11 @@ func (r *Runtime) object_getOwnPropertyNames(call FunctionCall) Value {
 	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) {
 	if o, ok := v.(*Object); ok {
 		descr := o.self
@@ -188,7 +193,7 @@ func (r *Runtime) object_seal(call FunctionCall) Value {
 			Configurable: FLAG_FALSE,
 		}
 		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.configurable {
 					continue
@@ -200,6 +205,18 @@ func (r *Runtime) object_seal(call FunctionCall) Value {
 				//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()
 		return obj
 	}
@@ -215,7 +232,7 @@ func (r *Runtime) object_freeze(call FunctionCall) Value {
 			Configurable: FLAG_FALSE,
 		}
 		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 {
 				prop.configurable = false
 				if prop.value != nil {
@@ -226,6 +243,18 @@ func (r *Runtime) object_freeze(call FunctionCall) Value {
 				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()
 		return obj
 	} else {
@@ -252,7 +281,17 @@ func (r *Runtime) object_isSealed(call FunctionCall) Value {
 			return valueFalse
 		}
 		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.configurable {
 					return valueFalse
@@ -275,7 +314,17 @@ func (r *Runtime) object_isFrozen(call FunctionCall) Value {
 			return valueFalse
 		}
 		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.configurable || prop.value != nil && prop.writable {
 					return valueFalse
@@ -321,9 +370,9 @@ func (r *Runtime) object_keys(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)
-	if o.self.hasOwnPropertyStr(p) {
+	if o.self.hasOwnProperty(p) {
 		return valueTrue
 	} else {
 		return valueFalse
@@ -347,9 +396,9 @@ func (r *Runtime) objectproto_isPrototypeOf(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)
-	pv := o.self.getOwnProp(p.String())
+	pv := o.self.getOwnProp(p)
 	if pv == nil {
 		return valueFalse
 	}
@@ -367,11 +416,18 @@ func (r *Runtime) objectproto_toString(call FunctionCall) Value {
 		return stringObjectNull
 	case valueUndefined:
 		return stringObjectUndefined
-	case *Object:
-		return newStringValue(fmt.Sprintf("[object %s]", o.self.className()))
 	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("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("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("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)

+ 366 - 7
builtin_regexp.go

@@ -5,6 +5,7 @@ import (
 	"github.com/dlclark/regexp2"
 	"github.com/dop251/goja/parser"
 	"regexp"
+	"strings"
 )
 
 func (r *Runtime) newRegexpObject(proto *Object) *regexpObject {
@@ -20,7 +21,7 @@ func (r *Runtime) newRegexpObject(proto *Object) *regexpObject {
 	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.pattern = pattern
@@ -28,11 +29,12 @@ func (r *Runtime) newRegExpp(pattern regexpPattern, patternStr valueString, glob
 	o.global = global
 	o.ignoreCase = ignoreCase
 	o.multiline = multiline
+	o.sticky = sticky
 
 	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 != "" {
 		invalidFlags := func() {
@@ -58,6 +60,12 @@ func compileRegexp(patternStr, flags string) (p regexpPattern, global, ignoreCas
 					return
 				}
 				ignoreCase = true
+			case 'y':
+				if sticky {
+					invalidFlags()
+					return
+				}
+				sticky = true
 			default:
 				invalidFlags()
 				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 {
-	pattern, global, ignoreCase, multiline, err := compileRegexp(patternStr.String(), flags)
+	pattern, global, ignoreCase, multiline, sticky, err := compileRegexp(patternStr.String(), flags)
 	if err != nil {
 		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 {
@@ -175,7 +183,7 @@ func (r *Runtime) regexpproto_test(call FunctionCall) Value {
 
 func (r *Runtime) regexpproto_toString(call FunctionCall) Value {
 	if this, ok := r.toObject(call.This).self.(*regexpObject); ok {
-		var g, i, m string
+		var g, i, m, y string
 		if this.global {
 			g = "g"
 		}
@@ -185,7 +193,10 @@ func (r *Runtime) regexpproto_toString(call FunctionCall) Value {
 		if this.multiline {
 			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 {
 		r.typeErrorResult(true, "Method RegExp.prototype.toString called on incompatible receiver %s", call.This)
 		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() {
 	r.global.RegExpPrototype = r.NewObject()
 	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("toString", r.newNativeFunc(r.regexpproto_toString, nil, "toString", nil, 0), true, false, true)
 	o.putStr("source", &valueProperty{
@@ -266,7 +605,27 @@ func (r *Runtime) initRegExp() {
 		getterFunc:   r.newNativeFunc(r.regexpproto_getIgnoreCase, nil, "get ignoreCase", nil, 0),
 		accessor:     true,
 	}, 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)
+	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)
 }

+ 86 - 126
builtin_string.go

@@ -20,15 +20,21 @@ func (r *Runtime) collator() *collate.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 {
 	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 {
-		return newStringValue("")
+		return stringEmpty
 	}
 }
 
@@ -51,7 +57,7 @@ func (r *Runtime) _newString(s valueString) *Object {
 func (r *Runtime) builtin_newString(args []Value) *Object {
 	var s valueString
 	if len(args) > 0 {
-		s = args[0].ToString()
+		s = toString(args[0])
 	} else {
 		s = stringEmpty
 	}
@@ -226,8 +232,16 @@ func (r *Runtime) stringproto_localeCompare(call FunctionCall) Value {
 
 func (r *Runtime) stringproto_match(call FunctionCall) Value {
 	r.checkObjectCoercible(call.This)
-	s := call.This.ToString()
 	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
 	if regexp, ok := regexp.(*Object); ok {
 		rx, _ = regexp.self.(*regexpObject)
@@ -237,34 +251,29 @@ func (r *Runtime) stringproto_match(call FunctionCall) Value {
 		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 {
+	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()
 	var str string
 	var isASCII bool
@@ -274,8 +283,6 @@ func (r *Runtime) stringproto_replace(call FunctionCall) Value {
 	} else {
 		str = s.String()
 	}
-	searchValue := call.Argument(0)
-	replaceValue := call.Argument(1)
 
 	var found [][]int
 
@@ -408,8 +415,16 @@ func (r *Runtime) stringproto_replace(call FunctionCall) Value {
 
 func (r *Runtime) stringproto_search(call FunctionCall) Value {
 	r.checkObjectCoercible(call.This)
-	s := call.This.ToString()
 	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
 	if regexp, ok := regexp.(*Object); ok {
 		rx, _ = regexp.self.(*regexpObject)
@@ -419,11 +434,14 @@ func (r *Runtime) stringproto_search(call FunctionCall) Value {
 		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 {
@@ -469,10 +487,18 @@ func (r *Runtime) stringproto_slice(call FunctionCall) Value {
 
 func (r *Runtime) stringproto_split(call FunctionCall) Value {
 	r.checkObjectCoercible(call.This)
-	s := call.This.ToString()
-
 	separatorValue := call.Argument(0)
 	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
 	if limitValue != _undefined {
 		limit = int(toUInt32(limitValue))
@@ -486,97 +512,31 @@ func (r *Runtime) stringproto_split(call FunctionCall) Value {
 		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 {

+ 57 - 0
builtin_string_test.go

@@ -88,3 +88,60 @@ assert.sameValue('A—', String.fromCharCode(65, 0x2014));
 
 	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) {
 	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 {
 			e.c.throwSyntaxError(e.offset, err.Error())
 		}
@@ -1398,6 +1398,7 @@ func (e *compiledRegexpLiteral) emitGetter(putOnStack bool) {
 			global:     global,
 			ignoreCase: ignoreCase,
 			multiline:  multiline,
+			sticky:     sticky,
 		})
 	}
 }

+ 31 - 0
date_test.go

@@ -10,6 +10,9 @@ function $ERROR(message) {
 	throw new Error(message);
 }
 
+function Test262Error() {
+}
+
 function assert(mustBeTrue, message) {
     if (mustBeTrue === true) {
         return;
@@ -47,6 +50,34 @@ assert.sameValue = function (actual, expected, 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 {
 	nativeFuncObject
+	wrapped *Object
 }
 
 func (f *nativeFuncObject) export() interface{} {
@@ -52,10 +53,6 @@ func (f *funcObject) addPrototype() Value {
 	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 {
 	if r := f.baseObject.hasOwnProperty(n); r {
 		return true
@@ -205,10 +202,6 @@ func (f *nativeFuncObject) assertCallable() (func(FunctionCall) Value, bool) {
 	return nil, false
 }
 
-func (f *boundFuncObject) getProp(n Value) Value {
-	return f.getPropStr(n.String())
-}
-
 func (f *boundFuncObject) getPropStr(name string) Value {
 	if name == "caller" || name == "arguments" {
 		//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 {
+	if s, ok := n.(*valueSymbol); ok {
+		return f.deleteSym(s, 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) {
+	if s, ok := n.(*valueSymbol); ok {
+		f.putSym(s, val, throw)
+		return
+	}
 	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
 
-import "reflect"
+import (
+	"fmt"
+	"reflect"
+)
 
 const (
 	classObject   = "Object"
@@ -12,6 +15,8 @@ const (
 	classError    = "Error"
 	classRegExp   = "RegExp"
 	classDate     = "Date"
+
+	classArrayIterator = "Array Iterator"
 )
 
 type Object struct {
@@ -36,7 +41,8 @@ type objectImpl interface {
 	getProp(Value) Value
 	getPropStr(string) Value
 	getStr(string) Value
-	getOwnProp(string) Value
+	getOwnProp(Value) Value
+	getOwnPropStr(string) Value
 	put(Value, Value, bool)
 	putStr(string, Value, bool)
 	hasProperty(Value) bool
@@ -60,6 +66,7 @@ type objectImpl interface {
 	export() interface{}
 	exportType() reflect.Type
 	equal(objectImpl) bool
+	getOwnSymbols() []Value
 }
 
 type baseObject struct {
@@ -70,6 +77,8 @@ type baseObject struct {
 
 	values    map[string]Value
 	propNames []string
+
+	symValues map[*valueSymbol]Value
 }
 
 type primitiveValueObject struct {
@@ -118,7 +127,7 @@ func (o *baseObject) className() string {
 }
 
 func (o *baseObject) getPropStr(name string) Value {
-	if val := o.getOwnProp(name); val != nil {
+	if val := o.getOwnPropStr(name); val != nil {
 		return val
 	}
 	if o.prototype != nil {
@@ -127,7 +136,20 @@ func (o *baseObject) getPropStr(name string) Value {
 	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 {
+	if s, ok := n.(*valueSymbol); ok {
+		return o.getPropSym(s)
+	}
 	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 {
-	p := o.getOwnProp(name)
+	p := o.getOwnPropStr(name)
 
 	if p == nil && o.prototype != nil {
 		p = o.prototype.self.getPropStr(name)
@@ -162,7 +184,19 @@ func (o *baseObject) getStr(name string) Value {
 	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 {
+	if s, ok := n.(*valueSymbol); ok {
+		return o.getSym(s)
+	}
 	return o.getStr(n.String())
 }
 
@@ -198,20 +232,36 @@ func (o *baseObject) deleteStr(name string, throw bool) bool {
 			return false
 		}
 		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
 }
 
 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)
 }
 
 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]
 	if v == nil && name == __proto__ {
 		return o.prototype
@@ -219,6 +269,14 @@ func (o *baseObject) getOwnProp(name string) Value {
 	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) {
 	if v, exists := o.values[name]; exists {
 		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)
 }
 
+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 {
+	if s, ok := n.(*valueSymbol); ok {
+		_, exists := o.symValues[s]
+		return exists
+	}
 	v := o.values[n.String()]
 	return v != nil
 }
@@ -390,6 +495,17 @@ Reject:
 }
 
 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()
 	existingVal := o.values[name]
 	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
 }
 
-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 {
-		o._put(name, 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 {
 	if method, ok := o.getStr(methodName).(*Object); ok {
 		if call, ok := method.self.assertCallable(); ok {
@@ -441,6 +570,10 @@ func (o *baseObject) tryPrimitive(methodName string) Value {
 }
 
 func (o *baseObject) toPrimitiveNumber() Value {
+	if v := o.tryExoticToPrimitive("number"); v != nil {
+		return v
+	}
+
 	if v := o.tryPrimitive("valueOf"); v != nil {
 		return v
 	}
@@ -454,6 +587,10 @@ func (o *baseObject) toPrimitiveNumber() Value {
 }
 
 func (o *baseObject) toPrimitiveString() Value {
+	if v := o.tryExoticToPrimitive("string"); v != nil {
+		return v
+	}
+
 	if v := o.tryPrimitive("toString"); v != nil {
 		return v
 	}
@@ -614,12 +751,42 @@ func (o *baseObject) enumerate(all, recursive bool) iterNextFunc {
 	}).next
 }
 
-func (o *baseObject) equal(other objectImpl) bool {
+func (o *baseObject) equal(objectImpl) bool {
 	// Rely on parent reference comparison
 	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) {
+	if s, ok := n.(*valueSymbol); ok {
+		a.putSym(s, val, throw)
+		return
+	}
 	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 {
+	if s, ok := n.(*valueSymbol); ok {
+		return a.deleteSym(s, throw)
+	}
 	return a.deleteStr(n.String(), throw)
 }
 
-type argumentsPropIter1 struct {
-	a         *argumentsObject
-	idx       int
-	recursive bool
-}
-
 type argumentsPropIter struct {
 	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 {
+	if _, ok := n.(*valueSymbol); ok {
+		return a.baseObject.defineOwnProperty(n, descr, throw)
+	}
 	name := n.String()
 	if mapped, ok := a.values[name].(*mappedProperty); ok {
 		existing := &valueProperty{
@@ -130,17 +134,17 @@ func (a *argumentsObject) defineOwnProperty(n Value, descr propertyDescr, 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 {
 		return *mapped.v
 	}
 
-	return a.baseObject.getOwnProp(name)
+	return a.baseObject.getOwnPropStr(name)
 }
 
 func (a *argumentsObject) export() interface{} {
 	arr := make([]interface{}, a.length)
-	for i, _ := range arr {
+	for i := range arr {
 		v := a.get(intToValue(int64(i)))
 		if v != nil {
 			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)
 }
 
-func (o *objectGoMapSimple) getOwnProp(name string) Value {
+func (o *objectGoMapSimple) getOwnPropStr(name string) Value {
 	if v := o._getStr(name); v != nil {
 		return v
 	}
-	return o.baseObject.getOwnProp(name)
+	return o.baseObject.getOwnPropStr(name)
 }
 
 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)
 }
 
@@ -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 {
-	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
 	}
 	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 {
+	if _, ok := name.(*valueSymbol); ok {
+		return true
+	}
+
 	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 {
 	propNames := make([]string, len(o.data))
 	i := 0
-	for key, _ := range o.data {
+	for key := range o.data {
 		propNames[i] = key
 		i++
 	}

+ 48 - 17
object_gomap_reflect.go

@@ -14,24 +14,32 @@ func (o *objectGoMapReflect) init() {
 	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)
 	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
 }
 
-func (o *objectGoMapReflect) strToKey(name string) reflect.Value {
+func (o *objectGoMapReflect) strToKey(name string, throw bool) reflect.Value {
 	if o.keyType.Kind() == reflect.String {
 		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 {
-	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())
 	}
 
@@ -39,7 +47,11 @@ func (o *objectGoMapReflect) _get(n Value) 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())
 	}
 
@@ -68,7 +80,7 @@ func (o *objectGoMapReflect) getPropStr(name string) Value {
 	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 {
 		return &valueProperty{
 			value:      v,
@@ -76,7 +88,7 @@ func (o *objectGoMapReflect) getOwnProp(name string) Value {
 			enumerable: true,
 		}
 	}
-	return o.objectGoReflect.getOwnProp(name)
+	return o.objectGoReflect.getOwnPropStr(name)
 }
 
 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) {
-	k := o.toKey(key)
+	k := o.toKey(key, throw)
 	v, ok := o.toValue(val, throw)
 	if !ok {
 		return
@@ -99,7 +111,10 @@ func (o *objectGoMapReflect) put(key, 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)
 	if !ok {
 		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 {
-	name := n.String()
-	if !o.val.runtime.checkHostObjectPropertyDescr(name, descr, throw) {
+	if !o.val.runtime.checkHostObjectPropertyDescr(n, descr, throw) {
 		return false
 	}
 
@@ -123,11 +137,20 @@ func (o *objectGoMapReflect) defineOwnProperty(n Value, descr propertyDescr, thr
 }
 
 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 {
-	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 {
@@ -145,12 +168,20 @@ func (o *objectGoMapReflect) hasPropertyStr(name string) 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
 }
 
 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
 }
 

+ 31 - 29
object_goreflect.go

@@ -127,22 +127,14 @@ func (o *objectGoReflect) getStr(name string) Value {
 	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 {
-	if v := o.getOwnProp(name); v != nil {
+	if v := o.getOwnPropStr(name); v != nil {
 		return v
 	}
 	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 v := o._getField(name); v.IsValid() {
 			return &valueProperty{
@@ -164,6 +156,10 @@ func (o *objectGoReflect) getOwnProp(name string) Value {
 }
 
 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)
 }
 
@@ -199,40 +195,46 @@ func (o *objectGoReflect) _putProp(name string, value Value, writable, enumerabl
 	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 {
 		r.typeErrorResult(throw, "Host objects do not support accessor properties")
 		return 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
 	}
 	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 true
 }
 
 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)
 }
 
-func (o *objectGoSlice) getOwnProp(name string) Value {
+func (o *objectGoSlice) getOwnPropStr(name string) Value {
 	if v := o._getStr(name); v != nil {
 		return &valueProperty{
 			value:      v,
@@ -82,7 +82,7 @@ func (o *objectGoSlice) getOwnProp(name string) Value {
 			enumerable: true,
 		}
 	}
-	return o.baseObject.getOwnProp(name)
+	return o.baseObject.getOwnPropStr(name)
 }
 
 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 {
 	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
 		}
 		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)
 }
 
-func (o *objectGoSliceReflect) getOwnProp(name string) Value {
+func (o *objectGoSliceReflect) getOwnPropStr(name string) Value {
 	if v := o._getStr(name); v != nil {
 		return v
 	}
-	return o.objectGoReflect.getOwnProp(name)
+	return o.objectGoReflect.getOwnPropStr(name)
 }
 
 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 {
-	if !o.val.runtime.checkHostObjectPropertyDescr(name.String(), descr, throw) {
+	if !o.val.runtime.checkHostObjectPropertyDescr(name, descr, throw) {
 		return false
 	}
 	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)
 }
 
-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)
 	o.val.self = obj
 	return obj.getOwnProp(name)
@@ -181,6 +187,12 @@ func (o *lazyObject) equal(other objectImpl) bool {
 	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 {
 	obj := o.create(o.val)
 	o.val.self = obj

+ 8 - 9
regexp.go

@@ -24,7 +24,7 @@ type regexpObject struct {
 	pattern regexpPattern
 	source  valueString
 
-	global, multiline, ignoreCase bool
+	global, multiline, ignoreCase, sticky bool
 }
 
 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
-	if !r.global {
+	if !r.global && !r.sticky {
 		index = 0
 	}
 	if index >= 0 && index <= target.length() {
 		result = r.pattern.FindSubmatchIndex(target, int(index))
 	}
-	if result == nil {
+	if result == nil || r.sticky && result[0] != 0 {
 		r.putStr("lastIndex", intToValue(0), true)
 		return
 	}
 	match = true
-	startIndex := index
-	endIndex := int(lastIndex) + result[1]
 	// We do this shift here because the .FindStringSubmatchIndex above
 	// 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
 }
@@ -352,6 +350,7 @@ func (r *regexpObject) clone() *Object {
 	r1.global = r.global
 	r1.ignoreCase = r.ignoreCase
 	r1.multiline = r.multiline
+	r1.sticky = r.sticky
 	return r1.val
 }
 

+ 100 - 55
runtime.go

@@ -27,6 +27,14 @@ var (
 	typeTime     = reflect.TypeOf(time.Time{})
 )
 
+type iterationKind int
+
+const (
+	iterationKindKey iterationKind = iota
+	iterationKindValue
+	iterationKindKeyValue
+)
+
 type global struct {
 	Object   *Object
 	Array    *Object
@@ -36,6 +44,7 @@ type global struct {
 	Boolean  *Object
 	RegExp   *Object
 	Date     *Object
+	Symbol   *Object
 
 	ArrayBuffer *Object
 
@@ -57,9 +66,14 @@ type global struct {
 	FunctionPrototype *Object
 	RegExpPrototype   *Object
 	DatePrototype     *Object
+	SymbolPrototype   *Object
+	ArrayIterator     *Object
 
 	ArrayBufferPrototype *Object
 
+	IteratorPrototype      *Object
+	ArrayIteratorPrototype *Object
+
 	ErrorPrototype          *Object
 	TypeErrorPrototype      *Object
 	SyntaxErrorPrototype    *Object
@@ -74,6 +88,8 @@ type global struct {
 
 	thrower         *Object
 	throwerProperty Value
+
+	regexpProtoExec Value
 }
 
 type Flag int
@@ -107,6 +123,8 @@ type Runtime struct {
 	now             Now
 	_collator       *collate.Collator
 
+	symbolRegistry map[string]*valueSymbol
+
 	typeInfoCache   map[reflect.Type]*reflectTypeInfo
 	fieldNameMapper FieldNameMapper
 
@@ -237,6 +255,13 @@ func (r *Runtime) addToGlobal(name string, value Value) {
 	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() {
 	r.rand = rand.Float64
 	r.now = time.Now
@@ -249,6 +274,8 @@ func (r *Runtime) init() {
 	r.vm.init()
 
 	r.global.FunctionPrototype = r.newNativeFunc(nil, nil, "Empty", nil, 0)
+	r.global.IteratorPrototype = r.newLazyObject(r.createIterProto)
+
 	r.initObject()
 	r.initFunction()
 	r.initArray()
@@ -269,6 +296,7 @@ func (r *Runtime) init() {
 	r.initJSON()
 
 	//r.initTypedArrays()
+	r.initSymbol()
 
 	r.global.thrower = r.newNativeFunc(r.builtin_thrower, nil, "thrower", nil, 0)
 	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)})
 }
 
-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.val = v
+	o.val = obj
 	o.extensible = true
-	v.self = o
+	obj.self = o
 	o.prototype = proto
 	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) {
@@ -603,37 +595,44 @@ func (r *Runtime) builtin_Error(args []Value, proto *Object) *Object {
 	return obj.val
 }
 
-func (r *Runtime) builtin_new(construct *Object, args []Value) *Object {
+func getConstructor(construct *Object) func(args []Value) *Object {
 repeat:
 	switch f := construct.self.(type) {
 	case *nativeFuncObject:
 		if f.construct != nil {
-			return f.construct(args)
+			return f.construct
 		} else {
-			panic("Not a constructor")
+			panic(construct.runtime.NewTypeError("Not a constructor"))
 		}
 	case *boundFuncObject:
 		if f.construct != nil {
-			return f.construct(args)
+			return f.construct
 		} else {
-			panic("Not a constructor")
+			panic(construct.runtime.NewTypeError("Not a constructor"))
 		}
 	case *funcObject:
-		// TODO: implement
-		panic("Not implemented")
+		return f.construct
 	case *lazyObject:
 		construct.self = f.create(construct)
 		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")
 	}
+	return f(args)
 }
 
 func (r *Runtime) throw(e Value) {
 	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")
 	return nil
 }
@@ -1572,3 +1571,49 @@ func tryFunc(f func()) (err error) {
 
 	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)
 }
 
+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) {
 function foo(a,b,c)

+ 3 - 2
string.go

@@ -20,6 +20,7 @@ var (
 	stringFunction     valueString = asciiString("function")
 	stringBoolean      valueString = asciiString("boolean")
 	stringString       valueString = asciiString("string")
+	stringSymbol       valueString = asciiString("symbol")
 	stringNumber       valueString = asciiString("number")
 	stringNaN          valueString = asciiString("NaN")
 	stringInfinity                 = asciiString("Infinity")
@@ -120,7 +121,7 @@ func (s *stringObject) getProp(n Value) Value {
 	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 {
 		val := s.getIdx(i)
 		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 {

+ 50 - 24
tc39_test.go

@@ -2,6 +2,7 @@ package goja
 
 import (
 	"errors"
+	"fmt"
 	"gopkg.in/yaml.v2"
 	"io/ioutil"
 	"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-9.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 {
@@ -131,6 +152,11 @@ func parseTC39File(name string) (*tc39Meta, string, error) {
 }
 
 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()
 	err, early := ctx.runTC39Script(name, src, meta.Includes, vm)
 

+ 78 - 1
value.go

@@ -1,6 +1,7 @@
 package goja
 
 import (
+	"fmt"
 	"math"
 	"reflect"
 	"regexp"
@@ -53,6 +54,8 @@ type Value interface {
 	baseObject(r *Runtime) *Object
 }
 
+type typeError string
+
 type valueInt int64
 type valueFloat float64
 type valueBool bool
@@ -60,6 +63,9 @@ type valueNull struct{}
 type valueUndefined struct {
 	valueNull
 }
+type valueSymbol struct {
+	desc string
+}
 
 type valueUnresolved struct {
 	r   *Runtime
@@ -498,7 +504,7 @@ func (f valueFloat) ToString() valueString {
 	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 {
 	value := float64(f)
@@ -854,6 +860,77 @@ func (o valueUnresolved) ExportType() reflect.Type {
 	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() {
 	for i := 0; i < 256; i++ {
 		intCache[i] = valueInt(i - 128)

+ 20 - 50
vm.go

@@ -366,6 +366,10 @@ func (vm *vm) try(f func()) (ex *Exception) {
 				panic(x1)
 			case *Exception:
 				ex = x1
+			case typeError:
+				ex = &Exception{
+					val: vm.r.NewTypeError(string(x1)),
+				}
 			default:
 				/*
 					if vm.prg != nil {
@@ -448,19 +452,7 @@ func (vm *vm) popCtx() {
 	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 {
 		return obj
 	}
@@ -469,11 +461,9 @@ func (r *Runtime) toCallee(v Value) *Object {
 		unresolved.throw()
 		panic("Unreachable")
 	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{}
@@ -1263,11 +1253,11 @@ type newRegexp struct {
 	pattern regexpPattern
 	src     valueString
 
-	global, ignoreCase, multiline bool
+	global, ignoreCase, multiline, sticky bool
 }
 
 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++
 }
 
@@ -1725,7 +1715,7 @@ func (numargs call) exec(vm *vm) {
 	// arg<numargs-1>
 	n := int(numargs)
 	v := vm.stack[vm.sp-n-1] // callee
-	obj := vm.r.toCallee(v)
+	obj := vm.toCallee(v)
 repeat:
 	switch f := obj.self.(type) {
 	case *funcObject:
@@ -2136,7 +2126,7 @@ func (_op_instanceof) exec(vm *vm) {
 	left := vm.stack[vm.sp-2]
 	right := vm.r.toObject(vm.stack[vm.sp-1])
 
-	if right.self.hasInstance(left) {
+	if instanceOfOperator(left, right) {
 		vm.stack[vm.sp-2] = valueTrue
 	} else {
 		vm.stack[vm.sp-2] = valueFalse
@@ -2238,37 +2228,15 @@ func (_throw) exec(vm *vm) {
 type _new uint32
 
 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 {
-		vm.r.typeErrorResult(true, "Not a constructor")
+		panic(vm.r.NewTypeError("Not a constructor"))
 	}
+	vm.pc++
 }
 
 type _typeof struct{}
@@ -2299,6 +2267,8 @@ func (_typeof) exec(vm *vm) {
 		r = stringString
 	case valueInt, valueFloat:
 		r = stringNumber
+	case *valueSymbol:
+		r = stringSymbol
 	default:
 		panic(fmt.Errorf("Unknown type: %T", v))
 	}