Browse Source

Support for malformed UTF-16 strings and property keys. Missing String methods. (#146)

Dmitry Panov 5 years ago
parent
commit
5df89c3d31
48 changed files with 2040 additions and 886 deletions
  1. 19 17
      array.go
  2. 16 14
      array_sparse.go
  3. 5 4
      ast/node.go
  4. 3 3
      builtin_array.go
  5. 9 7
      builtin_global.go
  6. 6 4
      builtin_json.go
  7. 6 7
      builtin_object.go
  8. 11 7
      builtin_proxy.go
  9. 15 11
      builtin_regexp.go
  10. 414 30
      builtin_string.go
  11. 22 0
      builtin_string_test.go
  12. 29 26
      builtin_symbol.go
  13. 3 1
      builtin_typedarrays.go
  14. 17 15
      compiler.go
  15. 14 12
      compiler_expr.go
  16. 34 24
      compiler_stmt.go
  17. 20 16
      func.go
  18. 5 3
      map.go
  19. 86 73
      object.go
  20. 8 6
      object_args.go
  21. 23 19
      object_gomap.go
  22. 24 19
      object_gomap_reflect.go
  23. 29 0
      object_gomap_reflect_test.go
  24. 29 0
      object_gomap_test.go
  25. 27 22
      object_goreflect.go
  26. 17 0
      object_goreflect_test.go
  27. 12 21
      object_goslice.go
  28. 15 13
      object_goslice_reflect.go
  29. 16 12
      object_lazy.go
  30. 12 0
      object_test.go
  31. 20 27
      parser/expression.go
  32. 213 92
      parser/lexer.go
  33. 6 4
      parser/lexer_test.go
  34. 6 4
      parser/parser.go
  35. 49 42
      parser/parser_test.go
  36. 3 2
      parser/scope.go
  37. 31 22
      proxy.go
  38. 4 4
      regexp.go
  39. 50 28
      runtime.go
  40. 44 2
      runtime_test.go
  41. 133 46
      string.go
  42. 25 19
      string_ascii.go
  43. 130 41
      string_unicode.go
  44. 81 37
      tc39_test.go
  45. 13 11
      typedarrays.go
  46. 122 0
      unistring/string.go
  47. 89 48
      value.go
  48. 75 71
      vm.go

+ 19 - 17
array.go

@@ -5,6 +5,8 @@ import (
 	"math/bits"
 	"reflect"
 	"strconv"
+
+	"github.com/dop251/goja/unistring"
 )
 
 type arrayIterObject struct {
@@ -158,7 +160,7 @@ func (a *arrayObject) getIdx(idx valueInt, receiver Value) Value {
 	return prop
 }
 
-func (a *arrayObject) getOwnPropStr(name string) Value {
+func (a *arrayObject) getOwnPropStr(name unistring.String) Value {
 	if i := strToIdx(name); i != math.MaxUint32 {
 		if i < uint32(len(a.values)) {
 			return a.values[i]
@@ -178,7 +180,7 @@ func (a *arrayObject) getOwnPropIdx(idx valueInt) Value {
 		return nil
 	}
 
-	return a.baseObject.getOwnPropStr(idx.String())
+	return a.baseObject.getOwnPropStr(idx.string())
 }
 
 func (a *arrayObject) sortLen() int64 {
@@ -197,7 +199,7 @@ func (a *arrayObject) swap(i, j int64) {
 	a.values[i], a.values[j] = a.values[j], a.values[i]
 }
 
-func (a *arrayObject) getStr(name string, receiver Value) Value {
+func (a *arrayObject) getStr(name unistring.String, receiver Value) Value {
 	return a.getStrWithOwnProp(a.getOwnPropStr(name), name, receiver)
 }
 
@@ -210,7 +212,7 @@ func (a *arrayObject) setOwnIdx(idx valueInt, val Value, throw bool) bool {
 	if i := toIdx(idx); i != math.MaxUint32 {
 		return a._setOwnIdx(i, val, throw)
 	} else {
-		return a.baseObject.setOwnStr(idx.String(), val, throw)
+		return a.baseObject.setOwnStr(idx.string(), val, throw)
 	}
 }
 
@@ -259,7 +261,7 @@ func (a *arrayObject) _setOwnIdx(idx uint32, val Value, throw bool) bool {
 	return true
 }
 
-func (a *arrayObject) setOwnStr(name string, val Value, throw bool) bool {
+func (a *arrayObject) setOwnStr(name unistring.String, val Value, throw bool) bool {
 	if idx := strToIdx(name); idx != math.MaxUint32 {
 		return a._setOwnIdx(idx, val, throw)
 	} else {
@@ -275,7 +277,7 @@ func (a *arrayObject) setForeignIdx(idx valueInt, val, receiver Value, throw boo
 	return a._setForeignIdx(idx, a.getOwnPropIdx(idx), val, receiver, throw)
 }
 
-func (a *arrayObject) setForeignStr(name string, val, receiver Value, throw bool) (bool, bool) {
+func (a *arrayObject) setForeignStr(name unistring.String, val, receiver Value, throw bool) (bool, bool) {
 	return a._setForeignStr(name, a.getOwnPropStr(name), val, receiver, throw)
 }
 
@@ -286,7 +288,7 @@ type arrayPropIter struct {
 
 func (i *arrayPropIter) next() (propIterItem, iterNextFunc) {
 	for i.idx < len(i.a.values) {
-		name := strconv.Itoa(i.idx)
+		name := unistring.String(strconv.Itoa(i.idx))
 		prop := i.a.values[i.idx]
 		i.idx++
 		if prop != nil {
@@ -318,7 +320,7 @@ func (a *arrayObject) ownKeys(all bool, accum []Value) []Value {
 	return a.baseObject.ownKeys(all, accum)
 }
 
-func (a *arrayObject) hasOwnPropertyStr(name string) bool {
+func (a *arrayObject) hasOwnPropertyStr(name unistring.String) bool {
 	if idx := strToIdx(name); idx != math.MaxUint32 {
 		return idx < uint32(len(a.values)) && a.values[idx] != nil
 	} else {
@@ -330,7 +332,7 @@ func (a *arrayObject) hasOwnPropertyIdx(idx valueInt) bool {
 	if idx := toIdx(idx); idx != math.MaxUint32 {
 		return idx < uint32(len(a.values)) && a.values[idx] != nil
 	}
-	return a.baseObject.hasOwnPropertyStr(idx.String())
+	return a.baseObject.hasOwnPropertyStr(idx.string())
 }
 
 func (a *arrayObject) expand(idx uint32) bool {
@@ -420,7 +422,7 @@ func (a *arrayObject) _defineIdxProperty(idx uint32, desc PropertyDescriptor, th
 	if idx < uint32(len(a.values)) {
 		existing = a.values[idx]
 	}
-	prop, ok := a.baseObject._defineOwnProperty(strconv.FormatUint(uint64(idx), 10), existing, desc, throw)
+	prop, ok := a.baseObject._defineOwnProperty(unistring.String(strconv.FormatUint(uint64(idx), 10)), existing, desc, throw)
 	if ok {
 		if idx >= a.length {
 			if !a.setLengthInt(int64(idx)+1, throw) {
@@ -440,7 +442,7 @@ func (a *arrayObject) _defineIdxProperty(idx uint32, desc PropertyDescriptor, th
 	return ok
 }
 
-func (a *arrayObject) defineOwnPropertyStr(name string, descr PropertyDescriptor, throw bool) bool {
+func (a *arrayObject) defineOwnPropertyStr(name unistring.String, descr PropertyDescriptor, throw bool) bool {
 	if idx := strToIdx(name); idx != math.MaxUint32 {
 		return a._defineIdxProperty(idx, descr, throw)
 	}
@@ -454,7 +456,7 @@ func (a *arrayObject) defineOwnPropertyIdx(idx valueInt, descr PropertyDescripto
 	if idx := toIdx(idx); idx != math.MaxUint32 {
 		return a._defineIdxProperty(idx, descr, throw)
 	}
-	return a.baseObject.defineOwnPropertyStr(idx.String(), descr, throw)
+	return a.baseObject.defineOwnPropertyStr(idx.string(), descr, throw)
 }
 
 func (a *arrayObject) _deleteIdxProp(idx uint32, throw bool) bool {
@@ -474,7 +476,7 @@ func (a *arrayObject) _deleteIdxProp(idx uint32, throw bool) bool {
 	return true
 }
 
-func (a *arrayObject) deleteStr(name string, throw bool) bool {
+func (a *arrayObject) deleteStr(name unistring.String, throw bool) bool {
 	if idx := strToIdx(name); idx != math.MaxUint32 {
 		return a._deleteIdxProp(idx, throw)
 	}
@@ -485,7 +487,7 @@ func (a *arrayObject) deleteIdx(idx valueInt, throw bool) bool {
 	if idx := toIdx(idx); idx != math.MaxUint32 {
 		return a._deleteIdxProp(idx, throw)
 	}
-	return a.baseObject.deleteStr(idx.String(), throw)
+	return a.baseObject.deleteStr(idx.string(), throw)
 }
 
 func (a *arrayObject) export() interface{} {
@@ -518,7 +520,7 @@ func toIdx(v valueInt) uint32 {
 	return math.MaxUint32
 }
 
-func strToIdx64(s string) int64 {
+func strToIdx64(s unistring.String) int64 {
 	if s == "" {
 		return -1
 	}
@@ -567,7 +569,7 @@ func strToIdx64(s string) int64 {
 	return n1
 }
 
-func strToIdx(s string) uint32 {
+func strToIdx(s unistring.String) uint32 {
 	if s == "" {
 		return math.MaxUint32
 	}
@@ -617,7 +619,7 @@ func strToIdx(s string) uint32 {
 	return n1
 }
 
-func strToGoIdx(s string) int {
+func strToGoIdx(s unistring.String) int {
 	if bits.UintSize == 64 {
 		return int(strToIdx64(s))
 	}

+ 16 - 14
array_sparse.go

@@ -6,6 +6,8 @@ import (
 	"reflect"
 	"sort"
 	"strconv"
+
+	"github.com/dop251/goja/unistring"
 )
 
 type sparseArrayItem struct {
@@ -109,7 +111,7 @@ func (a *sparseArrayObject) _getIdx(idx uint32) Value {
 	return nil
 }
 
-func (a *sparseArrayObject) getStr(name string, receiver Value) Value {
+func (a *sparseArrayObject) getStr(name unistring.String, receiver Value) Value {
 	return a.getStrWithOwnProp(a.getOwnPropStr(name), name, receiver)
 }
 
@@ -137,7 +139,7 @@ func (a *sparseArrayObject) getLengthProp() Value {
 	return &a.lengthProp
 }
 
-func (a *sparseArrayObject) getOwnPropStr(name string) Value {
+func (a *sparseArrayObject) getOwnPropStr(name unistring.String) Value {
 	if idx := strToIdx(name); idx != math.MaxUint32 {
 		return a._getIdx(idx)
 	}
@@ -151,7 +153,7 @@ func (a *sparseArrayObject) getOwnPropIdx(idx valueInt) Value {
 	if idx := toIdx(idx); idx != math.MaxUint32 {
 		return a._getIdx(idx)
 	}
-	return a.baseObject.getOwnPropStr(idx.String())
+	return a.baseObject.getOwnPropStr(idx.string())
 }
 
 func (a *sparseArrayObject) add(idx uint32, val Value) {
@@ -218,7 +220,7 @@ func (a *sparseArrayObject) _setOwnIdx(idx uint32, val Value, throw bool) bool {
 	return true
 }
 
-func (a *sparseArrayObject) setOwnStr(name string, val Value, throw bool) bool {
+func (a *sparseArrayObject) setOwnStr(name unistring.String, val Value, throw bool) bool {
 	if idx := strToIdx(name); idx != math.MaxUint32 {
 		return a._setOwnIdx(idx, val, throw)
 	} else {
@@ -235,10 +237,10 @@ func (a *sparseArrayObject) setOwnIdx(idx valueInt, val Value, throw bool) bool
 		return a._setOwnIdx(idx, val, throw)
 	}
 
-	return a.baseObject.setOwnStr(idx.String(), val, throw)
+	return a.baseObject.setOwnStr(idx.string(), val, throw)
 }
 
-func (a *sparseArrayObject) setForeignStr(name string, val, receiver Value, throw bool) (bool, bool) {
+func (a *sparseArrayObject) setForeignStr(name unistring.String, val, receiver Value, throw bool) (bool, bool) {
 	return a._setForeignStr(name, a.getOwnPropStr(name), val, receiver, throw)
 }
 
@@ -253,7 +255,7 @@ type sparseArrayPropIter struct {
 
 func (i *sparseArrayPropIter) next() (propIterItem, iterNextFunc) {
 	for i.idx < len(i.a.items) {
-		name := strconv.Itoa(int(i.a.items[i.idx].idx))
+		name := unistring.String(strconv.Itoa(int(i.a.items[i.idx].idx)))
 		prop := i.a.items[i.idx].value
 		i.idx++
 		if prop != nil {
@@ -299,7 +301,7 @@ func (a *sparseArrayObject) setValues(values []Value, objCount int) {
 	}
 }
 
-func (a *sparseArrayObject) hasOwnPropertyStr(name string) bool {
+func (a *sparseArrayObject) hasOwnPropertyStr(name unistring.String) bool {
 	if idx := strToIdx(name); idx != math.MaxUint32 {
 		i := a.findIdx(idx)
 		return i < len(a.items) && a.items[i].idx == idx
@@ -314,7 +316,7 @@ func (a *sparseArrayObject) hasOwnPropertyIdx(idx valueInt) bool {
 		return i < len(a.items) && a.items[i].idx == idx
 	}
 
-	return a.baseObject.hasOwnPropertyStr(idx.String())
+	return a.baseObject.hasOwnPropertyStr(idx.string())
 }
 
 func (a *sparseArrayObject) expand(idx uint32) bool {
@@ -345,7 +347,7 @@ func (a *sparseArrayObject) _defineIdxProperty(idx uint32, desc PropertyDescript
 	if i < len(a.items) && a.items[i].idx == idx {
 		existing = a.items[i].value
 	}
-	prop, ok := a.baseObject._defineOwnProperty(strconv.FormatUint(uint64(idx), 10), existing, desc, throw)
+	prop, ok := a.baseObject._defineOwnProperty(unistring.String(strconv.FormatUint(uint64(idx), 10)), existing, desc, throw)
 	if ok {
 		if idx >= a.length {
 			if !a.setLengthInt(int64(idx)+1, throw) {
@@ -376,7 +378,7 @@ func (a *sparseArrayObject) _defineIdxProperty(idx uint32, desc PropertyDescript
 	return ok
 }
 
-func (a *sparseArrayObject) defineOwnPropertyStr(name string, descr PropertyDescriptor, throw bool) bool {
+func (a *sparseArrayObject) defineOwnPropertyStr(name unistring.String, descr PropertyDescriptor, throw bool) bool {
 	if idx := strToIdx(name); idx != math.MaxUint32 {
 		return a._defineIdxProperty(idx, descr, throw)
 	}
@@ -390,7 +392,7 @@ func (a *sparseArrayObject) defineOwnPropertyIdx(idx valueInt, descr PropertyDes
 	if idx := toIdx(idx); idx != math.MaxUint32 {
 		return a._defineIdxProperty(idx, descr, throw)
 	}
-	return a.baseObject.defineOwnPropertyStr(idx.String(), descr, throw)
+	return a.baseObject.defineOwnPropertyStr(idx.string(), descr, throw)
 }
 
 func (a *sparseArrayObject) _deleteIdxProp(idx uint32, throw bool) bool {
@@ -410,7 +412,7 @@ func (a *sparseArrayObject) _deleteIdxProp(idx uint32, throw bool) bool {
 	return true
 }
 
-func (a *sparseArrayObject) deleteStr(name string, throw bool) bool {
+func (a *sparseArrayObject) deleteStr(name unistring.String, throw bool) bool {
 	if idx := strToIdx(name); idx != math.MaxUint32 {
 		return a._deleteIdxProp(idx, throw)
 	}
@@ -421,7 +423,7 @@ func (a *sparseArrayObject) deleteIdx(idx valueInt, throw bool) bool {
 	if idx := toIdx(idx); idx != math.MaxUint32 {
 		return a._deleteIdxProp(idx, throw)
 	}
-	return a.baseObject.deleteStr(idx.String(), throw)
+	return a.baseObject.deleteStr(idx.string(), throw)
 }
 
 func (a *sparseArrayObject) sortLen() int64 {

+ 5 - 4
ast/node.go

@@ -12,6 +12,7 @@ package ast
 import (
 	"github.com/dop251/goja/file"
 	"github.com/dop251/goja/token"
+	"github.com/dop251/goja/unistring"
 	"github.com/go-sourcemap/sourcemap"
 )
 
@@ -98,7 +99,7 @@ type (
 	}
 
 	Identifier struct {
-		Name string
+		Name unistring.String
 		Idx  file.Idx
 	}
 
@@ -134,7 +135,7 @@ type (
 	}
 
 	Property struct {
-		Key   string
+		Key   unistring.String
 		Kind  string
 		Value Expression
 	}
@@ -153,7 +154,7 @@ type (
 	StringLiteral struct {
 		Idx     file.Idx
 		Literal string
-		Value   string
+		Value   unistring.String
 	}
 
 	ThisExpression struct {
@@ -168,7 +169,7 @@ type (
 	}
 
 	VariableExpression struct {
-		Name        string
+		Name        unistring.String
 		Idx         file.Idx
 		Initializer Expression
 	}

+ 3 - 3
builtin_array.go

@@ -1289,7 +1289,7 @@ type arraySortCtx struct {
 	compare func(FunctionCall) Value
 }
 
-func (ctx *arraySortCtx) sortCompare(x, y Value) int {
+func (a *arraySortCtx) sortCompare(x, y Value) int {
 	if x == nil && y == nil {
 		return 0
 	}
@@ -1314,8 +1314,8 @@ func (ctx *arraySortCtx) sortCompare(x, y Value) int {
 		return -1
 	}
 
-	if ctx.compare != nil {
-		f := ctx.compare(FunctionCall{
+	if a.compare != nil {
+		f := a.compare(FunctionCall{
 			This:      _undefined,
 			Arguments: []Value{x, y},
 		}).ToFloat()

+ 9 - 7
builtin_global.go

@@ -2,12 +2,12 @@ package goja
 
 import (
 	"errors"
+	"github.com/dop251/goja/unistring"
 	"io"
 	"math"
 	"regexp"
 	"strconv"
 	"strings"
-	"unicode/utf16"
 	"unicode/utf8"
 )
 
@@ -95,7 +95,7 @@ func (r *Runtime) _encode(uriString valueString, unescaped *[256]bool) valueStri
 	reader = uriString.reader(0)
 	for {
 		rn, _, err := reader.ReadRune()
-		if err != nil {
+		if err == io.EOF {
 			break
 		}
 
@@ -189,7 +189,7 @@ func (r *Runtime) _decode(sv valueString, reservedSet *[256]bool) valueString {
 		us = append(us, rn)
 		t = t[size:]
 	}
-	return unicodeString(utf16.Encode(us))
+	return unicodeStringFromRunes(us)
 }
 
 func ishex(c byte) bool {
@@ -240,7 +240,7 @@ func (r *Runtime) builtin_escape(call FunctionCall) Value {
 	s := call.Argument(0).toString()
 	var sb strings.Builder
 	l := s.length()
-	for i := int64(0); i < l; i++ {
+	for i := 0; i < l; i++ {
 		r := uint16(s.charAt(i))
 		if r >= 'A' && r <= 'Z' || r >= 'a' && r <= 'z' || r >= '0' && r <= '9' ||
 			r == '@' || r == '*' || r == '_' || r == '+' || r == '-' || r == '.' || r == '/' {
@@ -267,11 +267,12 @@ func (r *Runtime) builtin_unescape(call FunctionCall) Value {
 	var asciiBuf []byte
 	var unicodeBuf []uint16
 	if unicode {
-		unicodeBuf = make([]uint16, 0, l)
+		unicodeBuf = make([]uint16, 1, l+1)
+		unicodeBuf[0] = unistring.BOM
 	} else {
 		asciiBuf = make([]byte, 0, l)
 	}
-	for i := int64(0); i < l; {
+	for i := 0; i < l; {
 		r := s.charAt(i)
 		if r == '%' {
 			if i <= l-6 && s.charAt(i+1) == 'u' {
@@ -303,7 +304,8 @@ func (r *Runtime) builtin_unescape(call FunctionCall) Value {
 		}
 	out:
 		if r >= utf8.RuneSelf && !unicode {
-			unicodeBuf = make([]uint16, 0, l)
+			unicodeBuf = make([]uint16, 1, l+1)
+			unicodeBuf[0] = unistring.BOM
 			for _, b := range asciiBuf {
 				unicodeBuf = append(unicodeBuf, uint16(b))
 			}

+ 6 - 4
builtin_json.go

@@ -7,9 +7,11 @@ import (
 	"io"
 	"math"
 	"strings"
+
+	"github.com/dop251/goja/unistring"
 )
 
-var hex = "0123456789abcdef"
+const hex = "0123456789abcdef"
 
 func (r *Runtime) builtinJSON_parse(call FunctionCall) Value {
 	d := json.NewDecoder(bytes.NewBufferString(call.Argument(0).String()))
@@ -85,7 +87,7 @@ func (r *Runtime) builtinJSON_decodeObject(d *json.Decoder) (*Object, error) {
 			return nil, err
 		}
 
-		object.self._putProp(key, value, true, true, true)
+		object.self._putProp(unistring.NewFromString(key), value, true, true, true)
 	}
 	return object, nil
 }
@@ -150,9 +152,9 @@ func (r *Runtime) builtinJSON_reviveWalk(reviver func(FunctionCall) Value, holde
 			for _, itemName := range object.self.ownKeys(false, nil) {
 				value := r.builtinJSON_reviveWalk(reviver, object, name)
 				if value == _undefined {
-					object.self.deleteStr(itemName.String(), false)
+					object.self.deleteStr(itemName.string(), false)
 				} else {
-					object.self.setOwnStr(itemName.String(), value, false)
+					object.self.setOwnStr(itemName.string(), value, false)
 				}
 			}
 		}

+ 6 - 7
builtin_object.go

@@ -82,7 +82,7 @@ func (r *Runtime) object_getOwnPropertyNames(call FunctionCall) Value {
 
 func (r *Runtime) object_getOwnPropertySymbols(call FunctionCall) Value {
 	obj := call.Argument(0).ToObject(r)
-	return r.newArrayValues(obj.self.ownSymbols())
+	return r.newArrayValues(obj.self.ownSymbols(true, nil))
 }
 
 func (r *Runtime) toValueProp(v Value) *valueProperty {
@@ -179,21 +179,20 @@ func (r *Runtime) toPropertyDescriptor(v Value) (ret PropertyDescriptor) {
 
 func (r *Runtime) _defineProperties(o *Object, p Value) {
 	type propItem struct {
-		name string
+		name Value
 		prop PropertyDescriptor
 	}
 	props := p.ToObject(r)
-	names := props.self.ownKeys(false, nil)
+	names := props.self.ownPropertyKeys(false, nil)
 	list := make([]propItem, 0, len(names))
 	for _, itemName := range names {
-		itemNameStr := itemName.String()
 		list = append(list, propItem{
-			name: itemNameStr,
-			prop: r.toPropertyDescriptor(props.self.getStr(itemNameStr, nil)),
+			name: itemName,
+			prop: r.toPropertyDescriptor(props.get(itemName, nil)),
 		})
 	}
 	for _, prop := range list {
-		o.self.defineOwnPropertyStr(prop.name, prop.prop, true)
+		o.defineOwnProperty(prop.name, prop.prop, true)
 	}
 }
 

+ 11 - 7
builtin_proxy.go

@@ -1,6 +1,10 @@
 package goja
 
-import "fmt"
+import (
+	"fmt"
+
+	"github.com/dop251/goja/unistring"
+)
 
 func (r *Runtime) newNativeProxyHandler(nativeHandler *ProxyTrapConfig) *Object {
 	handler := r.NewObject()
@@ -22,14 +26,14 @@ func (r *Runtime) newNativeProxyHandler(nativeHandler *ProxyTrapConfig) *Object
 
 func (r *Runtime) proxyproto_nativehandler_gen_obj_obj(name proxyTrap, native func(*Object) *Object, handler *Object) {
 	if native != nil {
-		handler.self._putProp(string(name), r.newNativeFunc(func(call FunctionCall) Value {
+		handler.self._putProp(unistring.String(name), r.newNativeFunc(func(call FunctionCall) Value {
 			if len(call.Arguments) >= 1 {
 				if t, ok := call.Argument(0).(*Object); ok {
 					return native(t)
 				}
 			}
 			panic(r.NewTypeError("%s needs to be called with target as Object", name))
-		}, nil, fmt.Sprintf("[native %s]", name), nil, 1), true, true, true)
+		}, nil, unistring.String(fmt.Sprintf("[native %s]", name)), nil, 1), true, true, true)
 	}
 }
 
@@ -51,7 +55,7 @@ func (r *Runtime) proxyproto_nativehandler_setPrototypeOf(native func(*Object, *
 
 func (r *Runtime) proxyproto_nativehandler_gen_obj_bool(name proxyTrap, native func(*Object) bool, handler *Object) {
 	if native != nil {
-		handler.self._putProp(string(name), r.newNativeFunc(func(call FunctionCall) Value {
+		handler.self._putProp(unistring.String(name), r.newNativeFunc(func(call FunctionCall) Value {
 			if len(call.Arguments) >= 1 {
 				if t, ok := call.Argument(0).(*Object); ok {
 					s := native(t)
@@ -59,7 +63,7 @@ func (r *Runtime) proxyproto_nativehandler_gen_obj_bool(name proxyTrap, native f
 				}
 			}
 			panic(r.NewTypeError("%s needs to be called with target as Object", name))
-		}, nil, fmt.Sprintf("[native %s]", name), nil, 1), true, true, true)
+		}, nil, unistring.String(fmt.Sprintf("[native %s]", name)), nil, 1), true, true, true)
 	}
 }
 
@@ -98,7 +102,7 @@ func (r *Runtime) proxyproto_nativehandler_defineProperty(native func(*Object, s
 
 func (r *Runtime) proxyproto_nativehandler_gen_obj_string_bool(name proxyTrap, native func(*Object, string) bool, handler *Object) {
 	if native != nil {
-		handler.self._putProp(string(name), r.newNativeFunc(func(call FunctionCall) Value {
+		handler.self._putProp(unistring.String(name), r.newNativeFunc(func(call FunctionCall) Value {
 			if len(call.Arguments) >= 2 {
 				if t, ok := call.Argument(0).(*Object); ok {
 					if p, ok := call.Argument(1).(valueString); ok {
@@ -108,7 +112,7 @@ func (r *Runtime) proxyproto_nativehandler_gen_obj_string_bool(name proxyTrap, n
 				}
 			}
 			panic(r.NewTypeError("%s needs to be called with target as Object and property as string", name))
-		}, nil, fmt.Sprintf("[native %s]", name), nil, 2), true, true, true)
+		}, nil, unistring.String(fmt.Sprintf("[native %s]", name)), nil, 2), true, true, true)
 	}
 }
 

+ 15 - 11
builtin_regexp.go

@@ -389,7 +389,7 @@ func (r *Runtime) regexpproto_stdMatcher(call FunctionCall) Value {
 			} else {
 				previousLastIndex = thisIndex
 			}
-			a = append(a, s.substring(int64(result[0]), int64(result[1])))
+			a = append(a, s.substring(result[0], result[1]))
 		}
 		if len(a) == 0 {
 			return _null
@@ -447,11 +447,11 @@ func (r *Runtime) regexpproto_stdSplitterGeneric(splitter *Object, s valueString
 	} else {
 		lim = toLength(limit)
 	}
-	size := s.length()
-	p := int64(0)
 	if lim == 0 {
 		return r.newArrayValues(a)
 	}
+	size := s.length()
+	p := 0
 	execFn := toMethod(splitter.ToObject(r).self.getStr("exec", nil)) // must be non-nil
 
 	if size == 0 {
@@ -463,21 +463,25 @@ func (r *Runtime) regexpproto_stdSplitterGeneric(splitter *Object, s valueString
 
 	q := p
 	for q < size {
-		splitter.self.setOwnStr("lastIndex", intToValue(q), true)
+		splitter.self.setOwnStr("lastIndex", intToValue(int64(q)), true)
 		z := r.regExpExec(execFn, splitter, s)
 		if z == _null {
 			q++
 		} else {
 			z := r.toObject(z)
 			e := toLength(splitter.self.getStr("lastIndex", nil))
-			if e == p {
+			if e == int64(p) {
 				q++
 			} else {
 				a = append(a, s.substring(p, q))
 				if int64(len(a)) == lim {
 					return r.newArrayValues(a)
 				}
-				p = e
+				if e > int64(size) {
+					p = size
+				} else {
+					p = int(e)
+				}
 				numberOfCaptures := max(toLength(z.self.getStr("length", nil))-1, 0)
 				for i := int64(1); i <= numberOfCaptures; i++ {
 					a = append(a, z.self.getIdx(valueInt(i), nil))
@@ -529,13 +533,13 @@ func (r *Runtime) regexpproto_stdSplitter(call FunctionCall) Value {
 	for _, match := range result {
 		if match[0] == match[1] {
 			// FIXME Ugh, this is a hack
-			if match[0] == 0 || int64(match[0]) == targetLength {
+			if match[0] == 0 || match[0] == targetLength {
 				continue
 			}
 		}
 
 		if lastIndex != match[0] {
-			valueArray = append(valueArray, s.substring(int64(lastIndex), int64(match[0])))
+			valueArray = append(valueArray, s.substring(lastIndex, match[0]))
 			found++
 		} else if lastIndex == match[0] {
 			if lastIndex != -1 {
@@ -554,7 +558,7 @@ func (r *Runtime) regexpproto_stdSplitter(call FunctionCall) Value {
 			offset := index * 2
 			var value Value
 			if match[offset] != -1 {
-				value = s.substring(int64(match[offset]), int64(match[offset+1]))
+				value = s.substring(match[offset], match[offset+1])
 			} else {
 				value = _undefined
 			}
@@ -567,8 +571,8 @@ func (r *Runtime) regexpproto_stdSplitter(call FunctionCall) Value {
 	}
 
 	if found != limit {
-		if int64(lastIndex) != targetLength {
-			valueArray = append(valueArray, s.substring(int64(lastIndex), targetLength))
+		if lastIndex != targetLength {
+			valueArray = append(valueArray, s.substring(lastIndex, targetLength))
 		} else {
 			valueArray = append(valueArray, stringEmpty)
 		}

+ 414 - 30
builtin_string.go

@@ -2,13 +2,16 @@ package goja
 
 import (
 	"bytes"
+	"github.com/dop251/goja/unistring"
+	"math"
+	"strings"
+	"unicode/utf16"
+	"unicode/utf8"
+
 	"github.com/dop251/goja/parser"
 	"golang.org/x/text/collate"
 	"golang.org/x/text/language"
 	"golang.org/x/text/unicode/norm"
-	"math"
-	"strings"
-	"unicode/utf8"
 )
 
 func (r *Runtime) collator() *collate.Collator {
@@ -25,7 +28,7 @@ func toString(arg Value) valueString {
 		return s
 	}
 	if s, ok := arg.(*valueSymbol); ok {
-		return newStringValue(s.descString())
+		return s.desc
 	}
 	return arg.toString()
 }
@@ -57,7 +60,7 @@ func (r *Runtime) _newString(s valueString, proto *Object) *Object {
 func (r *Runtime) builtin_newString(args []Value, proto *Object) *Object {
 	var s valueString
 	if len(args) > 0 {
-		s = toString(args[0])
+		s = args[0].toString()
 	} else {
 		s = stringEmpty
 	}
@@ -99,19 +102,26 @@ func (r *Runtime) stringproto_valueOf(call FunctionCall) Value {
 	return r.stringproto_toStringValueOf(call.This, "valueOf")
 }
 
+func (r *Runtime) stringproto_iterator(call FunctionCall) Value {
+	r.checkObjectCoercible(call.This)
+	return r.createStringIterator(call.This.toString())
+}
+
 func (r *Runtime) string_fromcharcode(call FunctionCall) Value {
 	b := make([]byte, len(call.Arguments))
 	for i, arg := range call.Arguments {
 		chr := toUint16(arg)
 		if chr >= utf8.RuneSelf {
-			bb := make([]uint16, len(call.Arguments))
+			bb := make([]uint16, len(call.Arguments)+1)
+			bb[0] = unistring.BOM
+			bb1 := bb[1:]
 			for j := 0; j < i; j++ {
-				bb[j] = uint16(b[j])
+				bb1[j] = uint16(b[j])
 			}
-			bb[i] = chr
+			bb1[i] = chr
 			i++
 			for j, arg := range call.Arguments[i:] {
-				bb[i+j] = toUint16(arg)
+				bb1[i+j] = toUint16(arg)
 			}
 			return unicodeString(bb)
 		}
@@ -121,24 +131,107 @@ func (r *Runtime) string_fromcharcode(call FunctionCall) Value {
 	return asciiString(b)
 }
 
+func (r *Runtime) string_fromcodepoint(call FunctionCall) Value {
+	var b []byte
+	var sb unicodeStringBuilder
+	unicode := false
+	for i, arg := range call.Arguments {
+		num := arg.ToNumber()
+		var c rune
+		if numInt, ok := num.(valueInt); ok {
+			if numInt < 0 || numInt > utf8.MaxRune {
+				panic(r.newError(r.global.RangeError, "Invalid code point %d", numInt))
+			}
+			c = rune(numInt)
+		} else {
+			panic(r.newError(r.global.RangeError, "Invalid code point %s", num))
+		}
+		if c >= utf8.RuneSelf {
+			if !unicode {
+				unicode = true
+				sb.Grow(len(call.Arguments))
+				sb.writeASCII(b[:i])
+				b = nil
+			}
+		}
+		if unicode {
+			sb.writeRune(c)
+		} else {
+			if b == nil {
+				b = make([]byte, 0, len(call.Arguments))
+			}
+			b = append(b, byte(c))
+		}
+	}
+	if !unicode {
+		return asciiString(b)
+	}
+	return sb.string()
+}
+
+func (r *Runtime) string_raw(call FunctionCall) Value {
+	cooked := call.Argument(0).ToObject(r)
+	raw := nilSafe(cooked.self.getStr("raw", nil)).ToObject(r)
+	literalSegments := toLength(raw.self.getStr("length", nil))
+	if literalSegments <= 0 {
+		return stringEmpty
+	}
+	var stringElements unicodeStringBuilder
+	nextIndex := int64(0)
+	numberOfSubstitutions := int64(len(call.Arguments) - 1)
+	for {
+		nextSeg := nilSafe(raw.self.getIdx(valueInt(nextIndex), nil)).toString()
+		stringElements.writeString(nextSeg)
+		if nextIndex+1 == literalSegments {
+			return stringElements.string()
+		}
+		if nextIndex < numberOfSubstitutions {
+			stringElements.writeString(nilSafe(call.Arguments[nextIndex+1]).toString())
+		}
+		nextIndex++
+	}
+}
+
 func (r *Runtime) stringproto_charAt(call FunctionCall) Value {
 	r.checkObjectCoercible(call.This)
 	s := call.This.toString()
 	pos := call.Argument(0).ToInteger()
-	if pos < 0 || pos >= s.length() {
+	if pos < 0 || pos >= int64(s.length()) {
 		return stringEmpty
 	}
-	return newStringValue(string(s.charAt(pos)))
+	return newStringValue(string(s.charAt(toInt(pos))))
 }
 
 func (r *Runtime) stringproto_charCodeAt(call FunctionCall) Value {
 	r.checkObjectCoercible(call.This)
 	s := call.This.toString()
 	pos := call.Argument(0).ToInteger()
-	if pos < 0 || pos >= s.length() {
+	if pos < 0 || pos >= int64(s.length()) {
 		return _NaN
 	}
-	return intToValue(int64(s.charAt(pos) & 0xFFFF))
+	return intToValue(int64(s.charAt(toInt(pos)) & 0xFFFF))
+}
+
+func (r *Runtime) stringproto_codePointAt(call FunctionCall) Value {
+	r.checkObjectCoercible(call.This)
+	s := call.This.toString()
+	p := call.Argument(0).ToInteger()
+	size := s.length()
+	if p < 0 || p >= int64(size) {
+		return _undefined
+	}
+	pos := toInt(p)
+	first := s.charAt(pos)
+	if isUTF16FirstSurrogate(first) {
+		pos++
+		if pos < size {
+			second := s.charAt(pos)
+			if isUTF16SecondSurrogate(second) {
+				return intToValue(int64(utf16.DecodeRune(first, second)))
+			}
+		}
+	}
+	return intToValue(int64(first & 0xFFFF))
 }
 
 func (r *Runtime) stringproto_concat(call FunctionCall) Value {
@@ -163,8 +256,9 @@ func (r *Runtime) stringproto_concat(call FunctionCall) Value {
 		}
 		return asciiString(buf.String())
 	} else {
-		buf := make([]uint16, totalLen)
-		pos := int64(0)
+		buf := make([]uint16, totalLen+1)
+		buf[0] = unistring.BOM
+		pos := 1
 		for _, s := range strs {
 			switch s := s.(type) {
 			case asciiString:
@@ -173,7 +267,7 @@ func (r *Runtime) stringproto_concat(call FunctionCall) Value {
 					pos++
 				}
 			case unicodeString:
-				copy(buf[pos:], s)
+				copy(buf[pos:], s[1:])
 				pos += s.length()
 			}
 		}
@@ -181,6 +275,56 @@ func (r *Runtime) stringproto_concat(call FunctionCall) Value {
 	}
 }
 
+func (r *Runtime) stringproto_endsWith(call FunctionCall) Value {
+	r.checkObjectCoercible(call.This)
+	s := call.This.toString()
+	searchString := call.Argument(0)
+	if isRegexp(searchString) {
+		panic(r.NewTypeError("First argument to String.prototype.endsWith must not be a regular expression"))
+	}
+	searchStr := searchString.toString()
+	l := int64(s.length())
+	var pos int64
+	if posArg := call.Argument(1); posArg != _undefined {
+		pos = posArg.ToInteger()
+	} else {
+		pos = l
+	}
+	end := toInt(min(max(pos, 0), l))
+	searchLength := searchStr.length()
+	start := end - searchLength
+	if start < 0 {
+		return valueFalse
+	}
+	for i := 0; i < searchLength; i++ {
+		if s.charAt(start+i) != searchStr.charAt(i) {
+			return valueFalse
+		}
+	}
+	return valueTrue
+}
+
+func (r *Runtime) stringproto_includes(call FunctionCall) Value {
+	r.checkObjectCoercible(call.This)
+	s := call.This.toString()
+	searchString := call.Argument(0)
+	if isRegexp(searchString) {
+		panic(r.NewTypeError("First argument to String.prototype.includes must not be a regular expression"))
+	}
+	searchStr := searchString.toString()
+	var pos int64
+	if posArg := call.Argument(1); posArg != _undefined {
+		pos = posArg.ToInteger()
+	} else {
+		pos = 0
+	}
+	start := toInt(min(max(pos, 0), int64(s.length())))
+	if s.index(searchStr, start) != -1 {
+		return valueTrue
+	}
+	return valueFalse
+}
+
 func (r *Runtime) stringproto_indexOf(call FunctionCall) Value {
 	r.checkObjectCoercible(call.This)
 	value := call.This.toString()
@@ -190,13 +334,13 @@ func (r *Runtime) stringproto_indexOf(call FunctionCall) Value {
 	if pos < 0 {
 		pos = 0
 	} else {
-		l := value.length()
+		l := int64(value.length())
 		if pos > l {
 			pos = l
 		}
 	}
 
-	return intToValue(value.index(target, pos))
+	return intToValue(int64(value.index(target, toInt(pos))))
 }
 
 func (r *Runtime) stringproto_lastIndexOf(call FunctionCall) Value {
@@ -207,20 +351,20 @@ func (r *Runtime) stringproto_lastIndexOf(call FunctionCall) Value {
 
 	var pos int64
 	if f, ok := numPos.(valueFloat); ok && math.IsNaN(float64(f)) {
-		pos = value.length()
+		pos = int64(value.length())
 	} else {
 		pos = numPos.ToInteger()
 		if pos < 0 {
 			pos = 0
 		} else {
-			l := value.length()
+			l := int64(value.length())
 			if pos > l {
 				pos = l
 			}
 		}
 	}
 
-	return intToValue(value.lastIndex(target, pos))
+	return intToValue(int64(value.lastIndex(target, toInt(pos))))
 }
 
 func (r *Runtime) stringproto_localeCompare(call FunctionCall) Value {
@@ -261,6 +405,173 @@ func (r *Runtime) stringproto_match(call FunctionCall) Value {
 	panic(r.NewTypeError("RegExp matcher is not a function"))
 }
 
+func (r *Runtime) stringproto_normalize(call FunctionCall) Value {
+	r.checkObjectCoercible(call.This)
+	s := call.This.toString()
+	var form string
+	if formArg := call.Argument(0); formArg != _undefined {
+		form = formArg.toString().String()
+	} else {
+		form = "NFC"
+	}
+	var f norm.Form
+	switch form {
+	case "NFC":
+		f = norm.NFC
+	case "NFD":
+		f = norm.NFD
+	case "NFKC":
+		f = norm.NFKC
+	case "NFKD":
+		f = norm.NFKD
+	default:
+		panic(r.newError(r.global.RangeError, "The normalization form should be one of NFC, NFD, NFKC, NFKD"))
+	}
+
+	if s, ok := s.(unicodeString); ok {
+		ss := s.String()
+		return newStringValue(f.String(ss))
+	}
+
+	return s
+}
+
+func (r *Runtime) stringproto_padEnd(call FunctionCall) Value {
+	r.checkObjectCoercible(call.This)
+	s := call.This.toString()
+	maxLength := toLength(call.Argument(0))
+	stringLength := int64(s.length())
+	if maxLength <= stringLength {
+		return s
+	}
+	var filler valueString
+	var fillerASCII bool
+	if fillString := call.Argument(1); fillString != _undefined {
+		filler = fillString.toString()
+		if filler.length() == 0 {
+			return s
+		}
+		_, fillerASCII = filler.(asciiString)
+	} else {
+		filler = asciiString(" ")
+		fillerASCII = true
+	}
+	remaining := toInt(maxLength - stringLength)
+	_, stringASCII := s.(asciiString)
+	if fillerASCII && stringASCII {
+		fl := filler.length()
+		var sb strings.Builder
+		sb.Grow(toInt(maxLength))
+		sb.WriteString(s.String())
+		fs := filler.String()
+		for remaining >= fl {
+			sb.WriteString(fs)
+			remaining -= fl
+		}
+		if remaining > 0 {
+			sb.WriteString(fs[:remaining])
+		}
+		return asciiString(sb.String())
+	}
+	var sb unicodeStringBuilder
+	sb.Grow(toInt(maxLength))
+	sb.writeString(s)
+	fl := filler.length()
+	for remaining >= fl {
+		sb.writeString(filler)
+		remaining -= fl
+	}
+	if remaining > 0 {
+		sb.writeString(filler.substring(0, remaining))
+	}
+
+	return sb.string()
+}
+
+func (r *Runtime) stringproto_padStart(call FunctionCall) Value {
+	r.checkObjectCoercible(call.This)
+	s := call.This.toString()
+	maxLength := toLength(call.Argument(0))
+	stringLength := int64(s.length())
+	if maxLength <= stringLength {
+		return s
+	}
+	var filler valueString
+	var fillerASCII bool
+	if fillString := call.Argument(1); fillString != _undefined {
+		filler = fillString.toString()
+		if filler.length() == 0 {
+			return s
+		}
+		_, fillerASCII = filler.(asciiString)
+	} else {
+		filler = asciiString(" ")
+		fillerASCII = true
+	}
+	remaining := toInt(maxLength - stringLength)
+	_, stringASCII := s.(asciiString)
+	if fillerASCII && stringASCII {
+		fl := filler.length()
+		var sb strings.Builder
+		sb.Grow(toInt(maxLength))
+		fs := filler.String()
+		for remaining >= fl {
+			sb.WriteString(fs)
+			remaining -= fl
+		}
+		if remaining > 0 {
+			sb.WriteString(fs[:remaining])
+		}
+		sb.WriteString(s.String())
+		return asciiString(sb.String())
+	}
+	var sb unicodeStringBuilder
+	sb.Grow(toInt(maxLength))
+	fl := filler.length()
+	for remaining >= fl {
+		sb.writeString(filler)
+		remaining -= fl
+	}
+	if remaining > 0 {
+		sb.writeString(filler.substring(0, remaining))
+	}
+	sb.writeString(s)
+
+	return sb.string()
+}
+
+func (r *Runtime) stringproto_repeat(call FunctionCall) Value {
+	r.checkObjectCoercible(call.This)
+	s := call.This.toString()
+	n := call.Argument(0).ToNumber()
+	if n == _positiveInf {
+		panic(r.newError(r.global.RangeError, "Invalid count value"))
+	}
+	numInt := n.ToInteger()
+	if numInt < 0 {
+		panic(r.newError(r.global.RangeError, "Invalid count value"))
+	}
+	if numInt == 0 || s.length() == 0 {
+		return stringEmpty
+	}
+	num := toInt(numInt)
+	if s, ok := s.(asciiString); ok {
+		var sb strings.Builder
+		sb.Grow(len(s) * num)
+		for i := 0; i < num; i++ {
+			sb.WriteString(string(s))
+		}
+		return asciiString(sb.String())
+	}
+
+	var sb unicodeStringBuilder
+	sb.Grow(s.length() * num)
+	for i := 0; i < num; i++ {
+		sb.writeString(s)
+	}
+	return sb.string()
+}
+
 func (r *Runtime) stringproto_replace(call FunctionCall) Value {
 	r.checkObjectCoercible(call.This)
 	searchValue := call.Argument(0)
@@ -448,7 +759,7 @@ func (r *Runtime) stringproto_slice(call FunctionCall) Value {
 	r.checkObjectCoercible(call.This)
 	s := call.This.toString()
 
-	l := s.length()
+	l := int64(s.length())
 	start := call.Argument(0).ToInteger()
 	var end int64
 	if arg1 := call.Argument(1); arg1 != _undefined {
@@ -480,7 +791,7 @@ func (r *Runtime) stringproto_slice(call FunctionCall) Value {
 	}
 
 	if end > start {
-		return s.substring(start, end)
+		return s.substring(int(start), int(end))
 	}
 	return stringEmpty
 }
@@ -539,11 +850,37 @@ func (r *Runtime) stringproto_split(call FunctionCall) Value {
 	return r.newArrayValues(valueArray)
 }
 
+func (r *Runtime) stringproto_startsWith(call FunctionCall) Value {
+	r.checkObjectCoercible(call.This)
+	s := call.This.toString()
+	searchString := call.Argument(0)
+	if isRegexp(searchString) {
+		panic(r.NewTypeError("First argument to String.prototype.startsWith must not be a regular expression"))
+	}
+	searchStr := searchString.toString()
+	l := int64(s.length())
+	var pos int64
+	if posArg := call.Argument(1); posArg != _undefined {
+		pos = posArg.ToInteger()
+	}
+	start := toInt(min(max(pos, 0), l))
+	searchLength := searchStr.length()
+	if int64(searchLength+start) > l {
+		return valueFalse
+	}
+	for i := 0; i < searchLength; i++ {
+		if s.charAt(start+i) != searchStr.charAt(i) {
+			return valueFalse
+		}
+	}
+	return valueTrue
+}
+
 func (r *Runtime) stringproto_substring(call FunctionCall) Value {
 	r.checkObjectCoercible(call.This)
 	s := call.This.toString()
 
-	l := s.length()
+	l := int64(s.length())
 	intStart := call.Argument(0).ToInteger()
 	var intEnd int64
 	if end := call.Argument(1); end != _undefined {
@@ -567,7 +904,7 @@ func (r *Runtime) stringproto_substring(call FunctionCall) Value {
 		intStart, intEnd = intEnd, intStart
 	}
 
-	return s.substring(intStart, intEnd)
+	return s.substring(int(intStart), int(intEnd))
 }
 
 func (r *Runtime) stringproto_toLowerCase(call FunctionCall) Value {
@@ -591,7 +928,22 @@ func (r *Runtime) stringproto_trim(call FunctionCall) Value {
 	return newStringValue(strings.Trim(s.String(), parser.WhitespaceChars))
 }
 
+func (r *Runtime) stringproto_trimEnd(call FunctionCall) Value {
+	r.checkObjectCoercible(call.This)
+	s := call.This.toString()
+
+	return newStringValue(strings.TrimRight(s.String(), parser.WhitespaceChars))
+}
+
+func (r *Runtime) stringproto_trimStart(call FunctionCall) Value {
+	r.checkObjectCoercible(call.This)
+	s := call.This.toString()
+
+	return newStringValue(strings.TrimLeft(s.String(), parser.WhitespaceChars))
+}
+
 func (r *Runtime) stringproto_substr(call FunctionCall) Value {
+	r.checkObjectCoercible(call.This)
 	s := call.This.toString()
 	start := call.Argument(0).ToInteger()
 	var length int64
@@ -611,32 +963,62 @@ func (r *Runtime) stringproto_substr(call FunctionCall) Value {
 		return stringEmpty
 	}
 
-	return s.substring(start, start+length)
+	return s.substring(int(start), int(start+length))
+}
+
+func (r *Runtime) stringIterProto_next(call FunctionCall) Value {
+	thisObj := r.toObject(call.This)
+	if iter, ok := thisObj.self.(*stringIterObject); ok {
+		return iter.next()
+	}
+	panic(r.NewTypeError("Method String Iterator.prototype.next called on incompatible receiver %s", thisObj.String()))
+}
+
+func (r *Runtime) createStringIterProto(val *Object) objectImpl {
+	o := newBaseObjectObj(val, r.global.IteratorPrototype, classObject)
+
+	o._putProp("next", r.newNativeFunc(r.stringIterProto_next, nil, "next", nil, 0), true, false, true)
+	o._putSym(symToStringTag, valueProp(asciiString(classStringIterator), false, false, true))
+
+	return o
 }
 
 func (r *Runtime) initString() {
+	r.global.StringIteratorPrototype = r.newLazyObject(r.createStringIterProto)
 	r.global.StringPrototype = r.builtin_newString([]Value{stringEmpty}, r.global.ObjectPrototype)
 
 	o := r.global.StringPrototype.self
-	o._putProp("toString", r.newNativeFunc(r.stringproto_toString, nil, "toString", nil, 0), true, false, true)
-	o._putProp("valueOf", r.newNativeFunc(r.stringproto_valueOf, nil, "valueOf", nil, 0), true, false, true)
 	o._putProp("charAt", r.newNativeFunc(r.stringproto_charAt, nil, "charAt", nil, 1), true, false, true)
 	o._putProp("charCodeAt", r.newNativeFunc(r.stringproto_charCodeAt, nil, "charCodeAt", nil, 1), true, false, true)
+	o._putProp("codePointAt", r.newNativeFunc(r.stringproto_codePointAt, nil, "codePointAt", nil, 1), true, false, true)
 	o._putProp("concat", r.newNativeFunc(r.stringproto_concat, nil, "concat", nil, 1), true, false, true)
+	o._putProp("endsWith", r.newNativeFunc(r.stringproto_endsWith, nil, "endsWith", nil, 1), true, false, true)
+	o._putProp("includes", r.newNativeFunc(r.stringproto_includes, nil, "includes", nil, 1), true, false, true)
 	o._putProp("indexOf", r.newNativeFunc(r.stringproto_indexOf, nil, "indexOf", nil, 1), true, false, true)
 	o._putProp("lastIndexOf", r.newNativeFunc(r.stringproto_lastIndexOf, nil, "lastIndexOf", nil, 1), true, false, true)
 	o._putProp("localeCompare", r.newNativeFunc(r.stringproto_localeCompare, nil, "localeCompare", nil, 1), true, false, true)
 	o._putProp("match", r.newNativeFunc(r.stringproto_match, nil, "match", nil, 1), true, false, true)
+	o._putProp("normalize", r.newNativeFunc(r.stringproto_normalize, nil, "normalize", nil, 0), true, false, true)
+	o._putProp("padEnd", r.newNativeFunc(r.stringproto_padEnd, nil, "padEnd", nil, 1), true, false, true)
+	o._putProp("padStart", r.newNativeFunc(r.stringproto_padStart, nil, "padStart", nil, 1), true, false, true)
+	o._putProp("repeat", r.newNativeFunc(r.stringproto_repeat, nil, "repeat", nil, 1), true, false, true)
 	o._putProp("replace", r.newNativeFunc(r.stringproto_replace, nil, "replace", nil, 2), true, false, true)
 	o._putProp("search", r.newNativeFunc(r.stringproto_search, nil, "search", nil, 1), true, false, true)
 	o._putProp("slice", r.newNativeFunc(r.stringproto_slice, nil, "slice", nil, 2), true, false, true)
 	o._putProp("split", r.newNativeFunc(r.stringproto_split, nil, "split", nil, 2), true, false, true)
+	o._putProp("startsWith", r.newNativeFunc(r.stringproto_startsWith, nil, "startsWith", nil, 1), true, false, true)
 	o._putProp("substring", r.newNativeFunc(r.stringproto_substring, nil, "substring", nil, 2), true, false, true)
-	o._putProp("toLowerCase", r.newNativeFunc(r.stringproto_toLowerCase, nil, "toLowerCase", nil, 0), true, false, true)
 	o._putProp("toLocaleLowerCase", r.newNativeFunc(r.stringproto_toLowerCase, nil, "toLocaleLowerCase", nil, 0), true, false, true)
-	o._putProp("toUpperCase", r.newNativeFunc(r.stringproto_toUpperCase, nil, "toUpperCase", nil, 0), true, false, true)
 	o._putProp("toLocaleUpperCase", r.newNativeFunc(r.stringproto_toUpperCase, nil, "toLocaleUpperCase", nil, 0), true, false, true)
+	o._putProp("toLowerCase", r.newNativeFunc(r.stringproto_toLowerCase, nil, "toLowerCase", nil, 0), true, false, true)
+	o._putProp("toString", r.newNativeFunc(r.stringproto_toString, nil, "toString", nil, 0), true, false, true)
+	o._putProp("toUpperCase", r.newNativeFunc(r.stringproto_toUpperCase, nil, "toUpperCase", nil, 0), true, false, true)
 	o._putProp("trim", r.newNativeFunc(r.stringproto_trim, nil, "trim", nil, 0), true, false, true)
+	o._putProp("trimEnd", r.newNativeFunc(r.stringproto_trimEnd, nil, "trimEnd", nil, 0), true, false, true)
+	o._putProp("trimStart", r.newNativeFunc(r.stringproto_trimStart, nil, "trimStart", nil, 0), true, false, true)
+	o._putProp("valueOf", r.newNativeFunc(r.stringproto_valueOf, nil, "valueOf", nil, 0), true, false, true)
+
+	o._putSym(symIterator, valueProp(r.newNativeFunc(r.stringproto_iterator, nil, "[Symbol.iterator]", nil, 0), true, false, true))
 
 	// Annex B
 	o._putProp("substr", r.newNativeFunc(r.stringproto_substr, nil, "substr", nil, 2), true, false, true)
@@ -644,6 +1026,8 @@ func (r *Runtime) initString() {
 	r.global.String = r.newNativeFunc(r.builtin_String, r.builtin_newString, "String", r.global.StringPrototype, 1)
 	o = r.global.String.self
 	o._putProp("fromCharCode", r.newNativeFunc(r.string_fromcharcode, nil, "fromCharCode", nil, 1), true, false, true)
+	o._putProp("fromCodePoint", r.newNativeFunc(r.string_fromcodepoint, nil, "fromCodePoint", nil, 1), true, false, true)
+	o._putProp("raw", r.newNativeFunc(r.string_raw, nil, "raw", nil, 1), true, false, true)
 
 	r.addToGlobal("String", r.global.String)
 

+ 22 - 0
builtin_string_test.go

@@ -145,3 +145,25 @@ res.length === 3 && res[0] === "a" && res[1] === "b" && res[2] === "c";
 `
 	testScript1(SCRIPT, valueTrue, t)
 }
+
+func TestStringIterSurrPair(t *testing.T) {
+	const SCRIPT = `
+var lo = '\uD834';
+var hi = '\uDF06';
+var pair = lo + hi;
+var string = 'a' + pair + 'b' + lo + pair + hi + lo;
+var iterator = string[Symbol.iterator]();
+var result;
+
+result = iterator.next();
+if (result.value !== 'a') {
+	throw new Error("at 0: " + result.value);
+}
+result = iterator.next();
+if (result.value !== pair) {
+	throw new Error("at 1: " + result.value);
+}
+
+`
+	testScript1(SCRIPT, _undefined, t)
+}

+ 29 - 26
builtin_symbol.go

@@ -1,27 +1,29 @@
 package goja
 
+import "github.com/dop251/goja/unistring"
+
 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"}
+	symHasInstance        = newSymbol(asciiString("Symbol.hasInstance"))
+	symIsConcatSpreadable = newSymbol(asciiString("Symbol.isConcatSpreadable"))
+	symIterator           = newSymbol(asciiString("Symbol.iterator"))
+	symMatch              = newSymbol(asciiString("Symbol.match"))
+	symReplace            = newSymbol(asciiString("Symbol.replace"))
+	symSearch             = newSymbol(asciiString("Symbol.search"))
+	symSpecies            = newSymbol(asciiString("Symbol.species"))
+	symSplit              = newSymbol(asciiString("Symbol.split"))
+	symToPrimitive        = newSymbol(asciiString("Symbol.toPrimitive"))
+	symToStringTag        = newSymbol(asciiString("Symbol.toStringTag"))
+	symUnscopables        = newSymbol(asciiString("Symbol.unscopables"))
 )
 
 func (r *Runtime) builtin_symbol(call FunctionCall) Value {
-	desc := ""
+	var desc valueString
 	if arg := call.Argument(0); !IsUndefined(arg) {
-		desc = arg.toString().String()
-	}
-	return &valueSymbol{
-		desc: desc,
+		desc = arg.toString()
+	} else {
+		desc = stringEmpty
 	}
+	return newSymbol(desc)
 }
 
 func (r *Runtime) symbolproto_tostring(call FunctionCall) Value {
@@ -38,7 +40,7 @@ func (r *Runtime) symbolproto_tostring(call FunctionCall) Value {
 	if sym == nil {
 		panic(r.NewTypeError("Method Symbol.prototype.toString is called on incompatible receiver"))
 	}
-	return newStringValue(sym.descString())
+	return sym.desc
 }
 
 func (r *Runtime) symbolproto_valueOf(call FunctionCall) Value {
@@ -59,17 +61,16 @@ func (r *Runtime) symbolproto_valueOf(call FunctionCall) Value {
 }
 
 func (r *Runtime) symbol_for(call FunctionCall) Value {
-	key := call.Argument(0).toString().String()
-	if v := r.symbolRegistry[key]; v != nil {
+	key := call.Argument(0).toString()
+	keyStr := key.string()
+	if v := r.symbolRegistry[keyStr]; v != nil {
 		return v
 	}
 	if r.symbolRegistry == nil {
-		r.symbolRegistry = make(map[string]*valueSymbol)
-	}
-	v := &valueSymbol{
-		desc: key,
+		r.symbolRegistry = make(map[unistring.String]*valueSymbol)
 	}
-	r.symbolRegistry[key] = v
+	v := newSymbol(key)
+	r.symbolRegistry[keyStr] = v
 	return v
 }
 
@@ -81,7 +82,7 @@ func (r *Runtime) symbol_keyfor(call FunctionCall) Value {
 	}
 	for key, s := range r.symbolRegistry {
 		if s == sym {
-			return r.ToValue(key)
+			return stringValueFromRaw(key)
 		}
 	}
 	return _undefined
@@ -124,7 +125,9 @@ func (r *Runtime) createSymbol(val *Object) objectImpl {
 		symToStringTag,
 		symUnscopables,
 	} {
-		o._putProp(s.desc[len("Symbol."):], s, false, false, false)
+		n := s.desc.(asciiString)
+		n = n[len("Symbol(Symbol.") : len(n)-1]
+		o._putProp(unistring.String(n), s, false, false, false)
 	}
 
 	return o

+ 3 - 1
builtin_typedarrays.go

@@ -5,6 +5,8 @@ import (
 	"sort"
 	"strings"
 	"unsafe"
+
+	"github.com/dop251/goja/unistring"
 )
 
 type typedArraySortCtx struct {
@@ -1377,7 +1379,7 @@ func (r *Runtime) addPrototype(ctor *Object, proto *Object) *baseObject {
 	return p
 }
 
-func (r *Runtime) typedArrayCreator(ctor func(args []Value, newTarget *Object) *Object, name string, bytesPerElement int) func(val *Object) objectImpl {
+func (r *Runtime) typedArrayCreator(ctor func(args []Value, newTarget *Object) *Object, name unistring.String, bytesPerElement int) func(val *Object) objectImpl {
 	return func(val *Object) objectImpl {
 		o := r.newNativeConstructOnly(val, ctor, nil, name, 3)
 		o.prototype = r.global.TypedArray

+ 17 - 15
compiler.go

@@ -2,10 +2,12 @@ package goja
 
 import (
 	"fmt"
-	"github.com/dop251/goja/ast"
-	"github.com/dop251/goja/file"
 	"sort"
 	"strconv"
+
+	"github.com/dop251/goja/ast"
+	"github.com/dop251/goja/file"
+	"github.com/dop251/goja/unistring"
 )
 
 const (
@@ -39,7 +41,7 @@ type Program struct {
 	code   []instruction
 	values []Value
 
-	funcName string
+	funcName unistring.String
 	src      *SrcFile
 	srcMap   []srcMapItem
 }
@@ -56,7 +58,7 @@ type compiler struct {
 }
 
 type scope struct {
-	names      map[string]uint32
+	names      map[unistring.String]uint32
 	outer      *scope
 	strict     bool
 	eval       bool
@@ -66,13 +68,13 @@ type scope struct {
 	argsNeeded bool
 	thisNeeded bool
 
-	namesMap    map[string]string
+	namesMap    map[unistring.String]unistring.String
 	lastFreeTmp int
 }
 
 type block struct {
 	typ        int
-	label      string
+	label      unistring.String
 	needResult bool
 	cont       int
 	breaks     []int
@@ -111,9 +113,9 @@ func (c *compiler) newScope() {
 	}
 	c.scope = &scope{
 		outer:    c.scope,
-		names:    make(map[string]uint32),
+		names:    make(map[unistring.String]uint32),
 		strict:   strict,
-		namesMap: make(map[string]string),
+		namesMap: make(map[unistring.String]unistring.String),
 	}
 }
 
@@ -176,7 +178,7 @@ func (s *scope) isFunction() bool {
 	return s.outer.isFunction()
 }
 
-func (s *scope) lookupName(name string) (idx uint32, found, noDynamics bool) {
+func (s *scope) lookupName(name unistring.String) (idx uint32, found, noDynamics bool) {
 	var level uint32 = 0
 	noDynamics = true
 	for curScope := s; curScope != nil; curScope = curScope.outer {
@@ -186,7 +188,7 @@ func (s *scope) lookupName(name string) (idx uint32, found, noDynamics bool) {
 		if curScope.dynamic {
 			noDynamics = false
 		} else {
-			var mapped string
+			var mapped unistring.String
 			if m, exists := curScope.namesMap[name]; exists {
 				mapped = m
 			} else {
@@ -210,7 +212,7 @@ func (s *scope) lookupName(name string) (idx uint32, found, noDynamics bool) {
 	return
 }
 
-func (s *scope) bindName(name string) (uint32, bool) {
+func (s *scope) bindName(name unistring.String) (uint32, bool) {
 	if s.lexical {
 		return s.outer.bindName(name)
 	}
@@ -223,7 +225,7 @@ func (s *scope) bindName(name string) (uint32, bool) {
 	return idx, true
 }
 
-func (s *scope) bindNameShadow(name string) (uint32, bool) {
+func (s *scope) bindNameShadow(name unistring.String) (uint32, bool) {
 	if s.lexical {
 		return s.outer.bindName(name)
 	}
@@ -234,7 +236,7 @@ func (s *scope) bindNameShadow(name string) (uint32, bool) {
 		unique = false
 		// shadow the var
 		delete(s.names, name)
-		n := strconv.Itoa(int(idx))
+		n := unistring.String(strconv.Itoa(int(idx)))
 		s.names[n] = idx
 	}
 	idx := uint32(len(s.names))
@@ -446,14 +448,14 @@ func (c *compiler) isStrictStatement(s ast.Statement) bool {
 	return false
 }
 
-func (c *compiler) checkIdentifierName(name string, offset int) {
+func (c *compiler) checkIdentifierName(name unistring.String, offset int) {
 	switch name {
 	case "implements", "interface", "let", "package", "private", "protected", "public", "static", "yield":
 		c.throwSyntaxError(offset, "Unexpected strict mode reserved word")
 	}
 }
 
-func (c *compiler) checkIdentifierLName(name string, offset int) {
+func (c *compiler) checkIdentifierLName(name unistring.String, offset int) {
 	switch name {
 	case "eval", "arguments":
 		c.throwSyntaxError(offset, "Assignment to eval or arguments is not allowed in strict mode")

+ 14 - 12
compiler_expr.go

@@ -2,10 +2,12 @@ package goja
 
 import (
 	"fmt"
+	"regexp"
+
 	"github.com/dop251/goja/ast"
 	"github.com/dop251/goja/file"
 	"github.com/dop251/goja/token"
-	"regexp"
+	"github.com/dop251/goja/unistring"
 )
 
 var (
@@ -60,18 +62,18 @@ type compiledAssignExpr struct {
 
 type deleteGlobalExpr struct {
 	baseCompiledExpr
-	name string
+	name unistring.String
 }
 
 type deleteVarExpr struct {
 	baseCompiledExpr
-	name string
+	name unistring.String
 }
 
 type deletePropExpr struct {
 	baseCompiledExpr
 	left compiledExpr
-	name string
+	name unistring.String
 }
 
 type deleteElemExpr struct {
@@ -91,7 +93,7 @@ type baseCompiledExpr struct {
 
 type compiledIdentifierExpr struct {
 	baseCompiledExpr
-	name string
+	name unistring.String
 }
 
 type compiledFunctionLiteral struct {
@@ -154,7 +156,7 @@ type compiledBinaryExpr struct {
 
 type compiledVariableExpr struct {
 	baseCompiledExpr
-	name        string
+	name        unistring.String
 	initializer compiledExpr
 	expr        *ast.VariableExpression
 }
@@ -320,7 +322,7 @@ func (e *compiledIdentifierExpr) emitGetterOrRef() {
 	}
 }
 
-func (c *compiler) emitVarSetter1(name string, offset int, emitRight func(isRef bool)) {
+func (c *compiler) emitVarSetter1(name unistring.String, offset int, emitRight func(isRef bool)) {
 	if c.scope.strict {
 		c.checkIdentifierLName(name, offset)
 	}
@@ -353,7 +355,7 @@ func (c *compiler) emitVarSetter1(name string, offset int, emitRight func(isRef
 	}
 }
 
-func (c *compiler) emitVarSetter(name string, offset int, valueExpr compiledExpr) {
+func (c *compiler) emitVarSetter(name unistring.String, offset int, valueExpr compiledExpr) {
 	c.emitVarSetter1(name, offset, func(bool) {
 		c.emitExpr(valueExpr, true)
 	})
@@ -432,7 +434,7 @@ func (e *compiledIdentifierExpr) deleteExpr() compiledExpr {
 type compiledDotExpr struct {
 	baseCompiledExpr
 	left compiledExpr
-	name string
+	name unistring.String
 }
 
 func (e *compiledDotExpr) emitGetter(putOnStack bool) {
@@ -854,7 +856,7 @@ func (e *compiledFunctionLiteral) emitGetter(putOnStack bool) {
 	e.c.popScope()
 	e.c.p = savedPrg
 	e.c.blockStart = savedBlockStart
-	name := ""
+	var name unistring.String
 	if e.expr.Name != nil {
 		name = e.expr.Name.Name
 	}
@@ -1444,7 +1446,7 @@ func (c *compiler) compileRegexpLiteral(v *ast.RegExpLiteral) compiledExpr {
 }
 
 func (e *compiledCallExpr) emitGetter(putOnStack bool) {
-	var calleeName string
+	var calleeName unistring.String
 	switch callee := e.callee.(type) {
 	case *compiledDotExpr:
 		callee.left.emitGetter(true)
@@ -1549,7 +1551,7 @@ func (c *compiler) compileNumberLiteral(v *ast.NumberLiteral) compiledExpr {
 
 func (c *compiler) compileStringLiteral(v *ast.StringLiteral) compiledExpr {
 	r := &compiledLiteral{
-		val: newStringValue(v.Value),
+		val: stringValueFromRaw(v.Value),
 	}
 	r.init(c, v.Idx0())
 	return r

+ 34 - 24
compiler_stmt.go

@@ -2,10 +2,12 @@ package goja
 
 import (
 	"fmt"
+	"strconv"
+
 	"github.com/dop251/goja/ast"
 	"github.com/dop251/goja/file"
 	"github.com/dop251/goja/token"
-	"strconv"
+	"github.com/dop251/goja/unistring"
 )
 
 func (c *compiler) compileStatement(v ast.Statement, needResult bool) {
@@ -126,7 +128,7 @@ func (c *compiler) compileTryStatement(v *ast.TryStatement) {
 					// remap
 					newIdx, exists := m[idx]
 					if !exists {
-						exname := " __tmp" + strconv.Itoa(c.scope.lastFreeTmp)
+						exname := unistring.String(" __tmp" + strconv.Itoa(c.scope.lastFreeTmp))
 						c.scope.lastFreeTmp++
 						newIdx, _ = c.scope.bindName(exname)
 						m[idx] = newIdx
@@ -207,7 +209,7 @@ func (c *compiler) compileDoWhileStatement(v *ast.DoWhileStatement, needResult b
 	c.compileLabeledDoWhileStatement(v, needResult, "")
 }
 
-func (c *compiler) compileLabeledDoWhileStatement(v *ast.DoWhileStatement, needResult bool, label string) {
+func (c *compiler) compileLabeledDoWhileStatement(v *ast.DoWhileStatement, needResult bool, label unistring.String) {
 	c.block = &block{
 		typ:        blockLoop,
 		outer:      c.block,
@@ -234,7 +236,7 @@ func (c *compiler) compileForStatement(v *ast.ForStatement, needResult bool) {
 	c.compileLabeledForStatement(v, needResult, "")
 }
 
-func (c *compiler) compileLabeledForStatement(v *ast.ForStatement, needResult bool, label string) {
+func (c *compiler) compileLabeledForStatement(v *ast.ForStatement, needResult bool, label unistring.String) {
 	c.block = &block{
 		typ:        blockLoop,
 		outer:      c.block,
@@ -306,7 +308,7 @@ func (c *compiler) compileForInStatement(v *ast.ForInStatement, needResult bool)
 	c.compileLabeledForInStatement(v, needResult, "")
 }
 
-func (c *compiler) compileLabeledForInStatement(v *ast.ForInStatement, needResult bool, label string) {
+func (c *compiler) compileLabeledForInStatement(v *ast.ForInStatement, needResult bool, label unistring.String) {
 	c.block = &block{
 		typ:        blockLoop,
 		outer:      c.block,
@@ -341,7 +343,7 @@ func (c *compiler) compileForOfStatement(v *ast.ForOfStatement, needResult bool)
 	c.compileLabeledForOfStatement(v, needResult, "")
 }
 
-func (c *compiler) compileLabeledForOfStatement(v *ast.ForOfStatement, needResult bool, label string) {
+func (c *compiler) compileLabeledForOfStatement(v *ast.ForOfStatement, needResult bool, label unistring.String) {
 	c.block = &block{
 		typ:        blockLoop,
 		outer:      c.block,
@@ -377,7 +379,7 @@ func (c *compiler) compileWhileStatement(v *ast.WhileStatement, needResult bool)
 	c.compileLabeledWhileStatement(v, needResult, "")
 }
 
-func (c *compiler) compileLabeledWhileStatement(v *ast.WhileStatement, needResult bool, label string) {
+func (c *compiler) compileLabeledWhileStatement(v *ast.WhileStatement, needResult bool, label unistring.String) {
 	c.block = &block{
 		typ:        blockLoop,
 		outer:      c.block,
@@ -517,6 +519,10 @@ func (c *compiler) compileBreak(label *ast.Identifier, idx file.Idx) {
 				break
 			}
 		}
+		if block == nil {
+			c.throwSyntaxError(int(idx)-1, "Undefined label '%s'", label.Name)
+			return
+		}
 	} else {
 		// find the nearest loop or switch
 	L:
@@ -531,17 +537,17 @@ func (c *compiler) compileBreak(label *ast.Identifier, idx file.Idx) {
 				break L
 			}
 		}
+		if block == nil {
+			c.throwSyntaxError(int(idx)-1, "Could not find block")
+			return
+		}
 	}
 
-	if block != nil {
-		if len(c.p.code) == c.blockStart && block.needResult {
-			c.emit(loadUndef)
-		}
-		block.breaks = append(block.breaks, len(c.p.code))
-		c.emit(nil)
-	} else {
-		c.throwSyntaxError(int(idx)-1, "Undefined label '%s'", label.Name)
+	if len(c.p.code) == c.blockStart && block.needResult {
+		c.emit(loadUndef)
 	}
+	block.breaks = append(block.breaks, len(c.p.code))
+	c.emit(nil)
 }
 
 func (c *compiler) compileContinue(label *ast.Identifier, idx file.Idx) {
@@ -555,6 +561,10 @@ func (c *compiler) compileContinue(label *ast.Identifier, idx file.Idx) {
 				break
 			}
 		}
+		if block == nil {
+			c.throwSyntaxError(int(idx)-1, "Undefined label '%s'", label.Name)
+			return
+		}
 	} else {
 		// find the nearest loop
 		for b := c.block; b != nil; b = b.outer {
@@ -565,17 +575,17 @@ func (c *compiler) compileContinue(label *ast.Identifier, idx file.Idx) {
 				break
 			}
 		}
+		if block == nil {
+			c.throwSyntaxError(int(idx)-1, "Could not find block")
+			return
+		}
 	}
 
-	if block != nil {
-		if len(c.p.code) == c.blockStart && block.needResult {
-			c.emit(loadUndef)
-		}
-		block.conts = append(block.conts, len(c.p.code))
-		c.emit(nil)
-	} else {
-		c.throwSyntaxError(int(idx)-1, "Undefined label '%s'", label.Name)
+	if len(c.p.code) == c.blockStart && block.needResult {
+		c.emit(loadUndef)
 	}
+	block.conts = append(block.conts, len(c.p.code))
+	c.emit(nil)
 }
 
 func (c *compiler) compileIfStatement(v *ast.IfStatement, needResult bool) {
@@ -720,7 +730,7 @@ func (c *compiler) compileStatements(list []ast.Statement, needResult bool) {
 	}
 }
 
-func (c *compiler) compileGenericLabeledStatement(v ast.Statement, needResult bool, label string) {
+func (c *compiler) compileGenericLabeledStatement(v ast.Statement, needResult bool, label unistring.String) {
 	c.block = &block{
 		typ:        blockBranch,
 		outer:      c.block,

+ 20 - 16
func.go

@@ -1,6 +1,10 @@
 package goja
 
-import "reflect"
+import (
+	"reflect"
+
+	"github.com/dop251/goja/unistring"
+)
 
 type baseFuncObject struct {
 	baseObject
@@ -36,20 +40,20 @@ func (f *nativeFuncObject) exportType() reflect.Type {
 	return reflect.TypeOf(f.f)
 }
 
-func (f *funcObject) _addProto(n string) Value {
+func (f *funcObject) _addProto(n unistring.String) Value {
 	if n == "prototype" {
-		if _, exists := f.values["prototype"]; !exists {
+		if _, exists := f.values[n]; !exists {
 			return f.addPrototype()
 		}
 	}
 	return nil
 }
 
-func (f *funcObject) getStr(p string, receiver Value) Value {
+func (f *funcObject) getStr(p unistring.String, receiver Value) Value {
 	return f.getStrWithOwnProp(f.getOwnPropStr(p), p, receiver)
 }
 
-func (f *funcObject) getOwnPropStr(name string) Value {
+func (f *funcObject) getOwnPropStr(name unistring.String) Value {
 	if v := f._addProto(name); v != nil {
 		return v
 	}
@@ -57,16 +61,16 @@ func (f *funcObject) getOwnPropStr(name string) Value {
 	return f.baseObject.getOwnPropStr(name)
 }
 
-func (f *funcObject) setOwnStr(name string, val Value, throw bool) bool {
+func (f *funcObject) setOwnStr(name unistring.String, val Value, throw bool) bool {
 	f._addProto(name)
 	return f.baseObject.setOwnStr(name, val, throw)
 }
 
-func (f *funcObject) setForeignStr(name string, val, receiver Value, throw bool) (bool, bool) {
+func (f *funcObject) setForeignStr(name unistring.String, val, receiver Value, throw bool) (bool, bool) {
 	return f._setForeignStr(name, f.getOwnPropStr(name), val, receiver, throw)
 }
 
-func (f *funcObject) deleteStr(name string, throw bool) bool {
+func (f *funcObject) deleteStr(name unistring.String, throw bool) bool {
 	f._addProto(name)
 	return f.baseObject.deleteStr(name, throw)
 }
@@ -77,7 +81,7 @@ func (f *funcObject) addPrototype() Value {
 	return f._putProp("prototype", proto, true, false, false)
 }
 
-func (f *funcObject) hasOwnPropertyStr(name string) bool {
+func (f *funcObject) hasOwnPropertyStr(name unistring.String) bool {
 	if r := f.baseObject.hasOwnPropertyStr(name); r {
 		return true
 	}
@@ -176,12 +180,12 @@ func (f *funcObject) assertConstructor() func(args []Value, newTarget *Object) *
 	return f.construct
 }
 
-func (f *baseFuncObject) init(name string, length int) {
+func (f *baseFuncObject) init(name unistring.String, length int) {
 	f.baseObject.init()
 
 	if name != "" {
 		f.nameProp.configurable = true
-		f.nameProp.value = newStringValue(name)
+		f.nameProp.value = stringValueFromRaw(name)
 		f._put("name", &f.nameProp)
 	}
 
@@ -242,11 +246,11 @@ func (f *nativeFuncObject) assertConstructor() func(args []Value, newTarget *Obj
 	return f.construct
 }
 
-func (f *boundFuncObject) getStr(p string, receiver Value) Value {
+func (f *boundFuncObject) getStr(p unistring.String, receiver Value) Value {
 	return f.getStrWithOwnProp(f.getOwnPropStr(p), p, receiver)
 }
 
-func (f *boundFuncObject) getOwnPropStr(name string) Value {
+func (f *boundFuncObject) getOwnPropStr(name unistring.String) Value {
 	if name == "caller" || name == "arguments" {
 		return f.val.runtime.global.throwerProperty
 	}
@@ -254,21 +258,21 @@ func (f *boundFuncObject) getOwnPropStr(name string) Value {
 	return f.nativeFuncObject.getOwnPropStr(name)
 }
 
-func (f *boundFuncObject) deleteStr(name string, throw bool) bool {
+func (f *boundFuncObject) deleteStr(name unistring.String, throw bool) bool {
 	if name == "caller" || name == "arguments" {
 		return true
 	}
 	return f.nativeFuncObject.deleteStr(name, throw)
 }
 
-func (f *boundFuncObject) setOwnStr(name string, val Value, throw bool) bool {
+func (f *boundFuncObject) setOwnStr(name unistring.String, val Value, throw bool) bool {
 	if name == "caller" || name == "arguments" {
 		panic(f.val.runtime.NewTypeError("'caller' and 'arguments' are restricted function properties and cannot be accessed in this context."))
 	}
 	return f.nativeFuncObject.setOwnStr(name, val, throw)
 }
 
-func (f *boundFuncObject) setForeignStr(name string, val, receiver Value, throw bool) (bool, bool) {
+func (f *boundFuncObject) setForeignStr(name unistring.String, val, receiver Value, throw bool) (bool, bool) {
 	return f._setForeignStr(name, f.getOwnPropStr(name), val, receiver, throw)
 }
 

+ 5 - 3
map.go

@@ -1,6 +1,8 @@
 package goja
 
-import "hash"
+import (
+	"hash/maphash"
+)
 
 type mapEntry struct {
 	key, value Value
@@ -10,7 +12,7 @@ type mapEntry struct {
 }
 
 type orderedMap struct {
-	hash                hash.Hash64
+	hash                *maphash.Hash
 	hashTable           map[uint64]*mapEntry
 	iterFirst, iterLast *mapEntry
 	size                int
@@ -138,7 +140,7 @@ func (iter *orderedMapIter) close() {
 	iter.cur = nil
 }
 
-func newOrderedMap(h hash.Hash64) *orderedMap {
+func newOrderedMap(h *maphash.Hash) *orderedMap {
 	return &orderedMap{
 		hash:      h,
 		hashTable: make(map[uint64]*mapEntry),

+ 86 - 73
object.go

@@ -7,6 +7,8 @@ import (
 	"runtime"
 	"sort"
 	"unsafe"
+
+	"github.com/dop251/goja/unistring"
 )
 
 const (
@@ -24,9 +26,10 @@ const (
 	classRegExp   = "RegExp"
 	classDate     = "Date"
 
-	classArrayIterator = "Array Iterator"
-	classMapIterator   = "Map Iterator"
-	classSetIterator   = "Set Iterator"
+	classArrayIterator  = "Array Iterator"
+	classMapIterator    = "Map Iterator"
+	classSetIterator    = "Set Iterator"
+	classStringIterator = "String Iterator"
 )
 
 type weakCollection interface {
@@ -171,35 +174,35 @@ func (p *PropertyDescriptor) complete() {
 type objectImpl interface {
 	sortable
 	className() string
-	getStr(p string, receiver Value) Value
+	getStr(p unistring.String, receiver Value) Value
 	getIdx(p valueInt, receiver Value) Value
 	getSym(p *valueSymbol, receiver Value) Value
 
-	getOwnPropStr(string) Value
+	getOwnPropStr(unistring.String) Value
 	getOwnPropIdx(valueInt) Value
 	getOwnPropSym(*valueSymbol) Value
 
-	setOwnStr(p string, v Value, throw bool) bool
+	setOwnStr(p unistring.String, v Value, throw bool) bool
 	setOwnIdx(p valueInt, v Value, throw bool) bool
 	setOwnSym(p *valueSymbol, v Value, throw bool) bool
 
-	setForeignStr(p string, v, receiver Value, throw bool) (res bool, handled bool)
+	setForeignStr(p unistring.String, v, receiver Value, throw bool) (res bool, handled bool)
 	setForeignIdx(p valueInt, v, receiver Value, throw bool) (res bool, handled bool)
 	setForeignSym(p *valueSymbol, v, receiver Value, throw bool) (res bool, handled bool)
 
-	hasPropertyStr(string) bool
+	hasPropertyStr(unistring.String) bool
 	hasPropertyIdx(idx valueInt) bool
 	hasPropertySym(s *valueSymbol) bool
 
-	hasOwnPropertyStr(string) bool
+	hasOwnPropertyStr(unistring.String) bool
 	hasOwnPropertyIdx(valueInt) bool
 	hasOwnPropertySym(s *valueSymbol) bool
 
-	defineOwnPropertyStr(name string, desc PropertyDescriptor, throw bool) bool
+	defineOwnPropertyStr(name unistring.String, desc PropertyDescriptor, throw bool) bool
 	defineOwnPropertyIdx(name valueInt, desc PropertyDescriptor, throw bool) bool
 	defineOwnPropertySym(name *valueSymbol, desc PropertyDescriptor, throw bool) bool
 
-	deleteStr(name string, throw bool) bool
+	deleteStr(name unistring.String, throw bool) bool
 	deleteIdx(idx valueInt, throw bool) bool
 	deleteSym(s *valueSymbol, throw bool) bool
 
@@ -219,10 +222,10 @@ type objectImpl interface {
 	exportType() reflect.Type
 	equal(objectImpl) bool
 	ownKeys(all bool, accum []Value) []Value
-	ownSymbols() []Value
+	ownSymbols(all bool, accum []Value) []Value
 	ownPropertyKeys(all bool, accum []Value) []Value
 
-	_putProp(name string, value Value, writable, enumerable, configurable bool) Value
+	_putProp(name unistring.String, value Value, writable, enumerable, configurable bool) Value
 	_putSym(s *valueSymbol, prop Value)
 }
 
@@ -232,8 +235,8 @@ type baseObject struct {
 	prototype  *Object
 	extensible bool
 
-	values    map[string]Value
-	propNames []string
+	values    map[unistring.String]Value
+	propNames []unistring.String
 
 	lastSortedPropLen, idxPropCount int
 
@@ -278,14 +281,14 @@ func (f ConstructorCall) Argument(idx int) Value {
 }
 
 func (o *baseObject) init() {
-	o.values = make(map[string]Value)
+	o.values = make(map[unistring.String]Value)
 }
 
 func (o *baseObject) className() string {
 	return o.class
 }
 
-func (o *baseObject) hasPropertyStr(name string) bool {
+func (o *baseObject) hasPropertyStr(name unistring.String) bool {
 	if o.val.self.hasOwnPropertyStr(name) {
 		return true
 	}
@@ -296,7 +299,7 @@ func (o *baseObject) hasPropertyStr(name string) bool {
 }
 
 func (o *baseObject) hasPropertyIdx(idx valueInt) bool {
-	return o.val.self.hasPropertyStr(idx.String())
+	return o.val.self.hasPropertyStr(idx.string())
 }
 
 func (o *baseObject) hasPropertySym(s *valueSymbol) bool {
@@ -325,7 +328,7 @@ func (o *baseObject) getWithOwnProp(prop, p, receiver Value) Value {
 	return prop
 }
 
-func (o *baseObject) getStrWithOwnProp(prop Value, name string, receiver Value) Value {
+func (o *baseObject) getStrWithOwnProp(prop Value, name unistring.String, receiver Value) Value {
 	if prop == nil && o.prototype != nil {
 		if receiver == nil {
 			return o.prototype.self.getStr(name, o.val)
@@ -342,14 +345,14 @@ func (o *baseObject) getStrWithOwnProp(prop Value, name string, receiver Value)
 }
 
 func (o *baseObject) getIdx(idx valueInt, receiver Value) Value {
-	return o.val.self.getStr(idx.String(), receiver)
+	return o.val.self.getStr(idx.string(), receiver)
 }
 
 func (o *baseObject) getSym(s *valueSymbol, receiver Value) Value {
 	return o.getWithOwnProp(o.getOwnPropSym(s), s, receiver)
 }
 
-func (o *baseObject) getStr(name string, receiver Value) Value {
+func (o *baseObject) getStr(name unistring.String, receiver Value) Value {
 	prop := o.values[name]
 	if prop == nil {
 		if o.prototype != nil {
@@ -369,7 +372,7 @@ func (o *baseObject) getStr(name string, receiver Value) Value {
 }
 
 func (o *baseObject) getOwnPropIdx(idx valueInt) Value {
-	return o.val.self.getOwnPropStr(idx.String())
+	return o.val.self.getOwnPropStr(idx.string())
 }
 
 func (o *baseObject) getOwnPropSym(s *valueSymbol) Value {
@@ -379,11 +382,11 @@ func (o *baseObject) getOwnPropSym(s *valueSymbol) Value {
 	return nil
 }
 
-func (o *baseObject) getOwnPropStr(name string) Value {
+func (o *baseObject) getOwnPropStr(name unistring.String) Value {
 	return o.values[name]
 }
 
-func (o *baseObject) checkDeleteProp(name string, prop *valueProperty, throw bool) bool {
+func (o *baseObject) checkDeleteProp(name unistring.String, prop *valueProperty, throw bool) bool {
 	if !prop.configurable {
 		o.val.runtime.typeErrorResult(throw, "Cannot delete property '%s' of %s", name, o.val.toString())
 		return false
@@ -391,14 +394,14 @@ func (o *baseObject) checkDeleteProp(name string, prop *valueProperty, throw boo
 	return true
 }
 
-func (o *baseObject) checkDelete(name string, val Value, throw bool) bool {
+func (o *baseObject) checkDelete(name unistring.String, val Value, throw bool) bool {
 	if val, ok := val.(*valueProperty); ok {
 		return o.checkDeleteProp(name, val, throw)
 	}
 	return true
 }
 
-func (o *baseObject) _delete(name string) {
+func (o *baseObject) _delete(name unistring.String) {
 	delete(o.values, name)
 	for i, n := range o.propNames {
 		if n == name {
@@ -416,13 +419,13 @@ func (o *baseObject) _delete(name string) {
 }
 
 func (o *baseObject) deleteIdx(idx valueInt, throw bool) bool {
-	return o.val.self.deleteStr(idx.String(), throw)
+	return o.val.self.deleteStr(idx.string(), throw)
 }
 
 func (o *baseObject) deleteSym(s *valueSymbol, throw bool) bool {
 	if o.symValues != nil {
 		if val := o.symValues.get(s); val != nil {
-			if !o.checkDelete(s.String(), val, throw) {
+			if !o.checkDelete(s.desc.string(), val, throw) {
 				return false
 			}
 			o.symValues.remove(s)
@@ -431,7 +434,7 @@ func (o *baseObject) deleteSym(s *valueSymbol, throw bool) bool {
 	return true
 }
 
-func (o *baseObject) deleteStr(name string, throw bool) bool {
+func (o *baseObject) deleteStr(name unistring.String, throw bool) bool {
 	if val, exists := o.values[name]; exists {
 		if !o.checkDelete(name, val, throw) {
 			return false
@@ -461,7 +464,7 @@ func (o *baseObject) setProto(proto *Object, throw bool) bool {
 	return true
 }
 
-func (o *baseObject) setOwnStr(name string, val Value, throw bool) bool {
+func (o *baseObject) setOwnStr(name unistring.String, val Value, throw bool) bool {
 	ownDesc := o.values[name]
 	if ownDesc == nil {
 		if proto := o.prototype; proto != nil {
@@ -494,7 +497,7 @@ func (o *baseObject) setOwnStr(name string, val Value, throw bool) bool {
 }
 
 func (o *baseObject) setOwnIdx(idx valueInt, val Value, throw bool) bool {
-	return o.val.self.setOwnStr(idx.String(), val, throw)
+	return o.val.self.setOwnStr(idx.string(), val, throw)
 }
 
 func (o *baseObject) setOwnSym(name *valueSymbol, val Value, throw bool) bool {
@@ -534,7 +537,7 @@ func (o *baseObject) setOwnSym(name *valueSymbol, val Value, throw bool) bool {
 	return true
 }
 
-func (o *baseObject) _setForeignStr(name string, prop, val, receiver Value, throw bool) (bool, bool) {
+func (o *baseObject) _setForeignStr(name unistring.String, prop, val, receiver Value, throw bool) (bool, bool) {
 	if prop != nil {
 		if prop, ok := prop.(*valueProperty); ok {
 			if !prop.isWritable() {
@@ -580,12 +583,12 @@ func (o *baseObject) _setForeignIdx(idx valueInt, prop, val, receiver Value, thr
 	return false, false
 }
 
-func (o *baseObject) setForeignStr(name string, val, receiver Value, throw bool) (bool, bool) {
+func (o *baseObject) setForeignStr(name unistring.String, val, receiver Value, throw bool) (bool, bool) {
 	return o._setForeignStr(name, o.values[name], val, receiver, throw)
 }
 
 func (o *baseObject) setForeignIdx(name valueInt, val, receiver Value, throw bool) (bool, bool) {
-	return o.val.self.setForeignStr(name.String(), val, receiver, throw)
+	return o.val.self.setForeignStr(name.string(), val, receiver, throw)
 }
 
 func (o *baseObject) setForeignSym(name *valueSymbol, val, receiver Value, throw bool) (bool, bool) {
@@ -622,16 +625,16 @@ func (o *baseObject) hasOwnPropertySym(s *valueSymbol) bool {
 	return false
 }
 
-func (o *baseObject) hasOwnPropertyStr(name string) bool {
+func (o *baseObject) hasOwnPropertyStr(name unistring.String) bool {
 	_, exists := o.values[name]
 	return exists
 }
 
 func (o *baseObject) hasOwnPropertyIdx(idx valueInt) bool {
-	return o.val.self.hasOwnPropertyStr(idx.String())
+	return o.val.self.hasOwnPropertyStr(idx.string())
 }
 
-func (o *baseObject) _defineOwnProperty(name string, existingValue Value, descr PropertyDescriptor, throw bool) (val Value, ok bool) {
+func (o *baseObject) _defineOwnProperty(name unistring.String, existingValue Value, descr PropertyDescriptor, throw bool) (val Value, ok bool) {
 
 	getterObj, _ := descr.Getter.(*Object)
 	setterObj, _ := descr.Setter.(*Object)
@@ -734,7 +737,7 @@ Reject:
 
 }
 
-func (o *baseObject) defineOwnPropertyStr(name string, descr PropertyDescriptor, throw bool) bool {
+func (o *baseObject) defineOwnPropertyStr(name unistring.String, descr PropertyDescriptor, throw bool) bool {
 	existingVal := o.values[name]
 	if v, ok := o._defineOwnProperty(name, existingVal, descr, throw); ok {
 		o.values[name] = v
@@ -747,7 +750,7 @@ func (o *baseObject) defineOwnPropertyStr(name string, descr PropertyDescriptor,
 }
 
 func (o *baseObject) defineOwnPropertyIdx(idx valueInt, desc PropertyDescriptor, throw bool) bool {
-	return o.val.self.defineOwnPropertyStr(idx.String(), desc, throw)
+	return o.val.self.defineOwnPropertyStr(idx.string(), desc, throw)
 }
 
 func (o *baseObject) defineOwnPropertySym(s *valueSymbol, descr PropertyDescriptor, throw bool) bool {
@@ -755,7 +758,7 @@ func (o *baseObject) defineOwnPropertySym(s *valueSymbol, descr PropertyDescript
 	if o.symValues != nil {
 		existingVal = o.symValues.get(s)
 	}
-	if v, ok := o._defineOwnProperty(s.String(), existingVal, descr, throw); ok {
+	if v, ok := o._defineOwnProperty(s.desc.string(), existingVal, descr, throw); ok {
 		if o.symValues == nil {
 			o.symValues = newOrderedMap(&o.val.runtime.hash)
 		}
@@ -765,7 +768,7 @@ func (o *baseObject) defineOwnPropertySym(s *valueSymbol, descr PropertyDescript
 	return false
 }
 
-func (o *baseObject) _put(name string, v Value) {
+func (o *baseObject) _put(name unistring.String, v Value) {
 	if _, exists := o.values[name]; !exists {
 		o.propNames = append(o.propNames, name)
 	}
@@ -785,7 +788,7 @@ func valueProp(value Value, writable, enumerable, configurable bool) Value {
 	}
 }
 
-func (o *baseObject) _putProp(name string, value Value, writable, enumerable, configurable bool) Value {
+func (o *baseObject) _putProp(name unistring.String, value Value, writable, enumerable, configurable bool) Value {
 	prop := valueProp(value, writable, enumerable, configurable)
 	o._put(name, prop)
 	return prop
@@ -810,7 +813,7 @@ func (o *baseObject) tryExoticToPrimitive(hint string) Value {
 }
 
 func (o *baseObject) tryPrimitive(methodName string) Value {
-	if method, ok := o.val.self.getStr(methodName, nil).(*Object); ok {
+	if method, ok := o.val.self.getStr(unistring.String(methodName), nil).(*Object); ok {
 		if call, ok := method.self.assertCallable(); ok {
 			v := call(FunctionCall{
 				This: o.val,
@@ -905,7 +908,7 @@ func (o *baseObject) export() interface{} {
 	m := make(map[string]interface{})
 	for _, itemName := range o.ownKeys(false, nil) {
 		itemNameStr := itemName.String()
-		v := o.val.self.getStr(itemNameStr, nil)
+		v := o.val.self.getStr(itemName.string(), nil)
 		if v != nil {
 			m[itemNameStr] = v.Export()
 		} else {
@@ -929,21 +932,21 @@ const (
 )
 
 type propIterItem struct {
-	name       string
+	name       unistring.String
 	value      Value // set only when enumerable == _ENUM_UNKNOWN
 	enumerable enumerableFlag
 }
 
 type objectPropIter struct {
 	o         *baseObject
-	propNames []string
+	propNames []unistring.String
 	idx       int
 }
 
 type propFilterIter struct {
 	wrapped iterNextFunc
 	all     bool
-	seen    map[string]bool
+	seen    map[unistring.String]bool
 }
 
 func (i *propFilterIter) next() (propIterItem, iterNextFunc) {
@@ -989,7 +992,7 @@ func (i *objectPropIter) next() (propIterItem, iterNextFunc) {
 func (o *baseObject) enumerate() iterNextFunc {
 	return (&propFilterIter{
 		wrapped: o.val.self.enumerateUnfiltered(),
-		seen:    make(map[string]bool),
+		seen:    make(map[unistring.String]bool),
 	}).next
 }
 
@@ -997,7 +1000,7 @@ func (o *baseObject) ownIter() iterNextFunc {
 	if len(o.propNames) > o.lastSortedPropLen {
 		o.fixPropOrder()
 	}
-	propNames := make([]string, len(o.propNames))
+	propNames := make([]unistring.String, len(o.propNames))
 	copy(propNames, o.propNames)
 	return (&objectPropIter{
 		o:         o,
@@ -1069,7 +1072,7 @@ func (o *baseObject) ownKeys(all bool, keys []Value) []Value {
 	}
 	if all {
 		for _, k := range o.propNames {
-			keys = append(keys, newStringValue(k))
+			keys = append(keys, stringValueFromRaw(k))
 		}
 	} else {
 		for _, k := range o.propNames {
@@ -1077,34 +1080,44 @@ func (o *baseObject) ownKeys(all bool, keys []Value) []Value {
 			if prop, ok := prop.(*valueProperty); ok && !prop.enumerable {
 				continue
 			}
-			keys = append(keys, newStringValue(k))
+			keys = append(keys, stringValueFromRaw(k))
 		}
 	}
 	return keys
 }
 
-func (o *baseObject) ownSymbols() (res []Value) {
+func (o *baseObject) ownSymbols(all bool, accum []Value) []Value {
 	if o.symValues != nil {
 		iter := o.symValues.newIter()
-		for {
-			entry := iter.next()
-			if entry == nil {
-				break
+		if all {
+			for {
+				entry := iter.next()
+				if entry == nil {
+					break
+				}
+				accum = append(accum, entry.key)
+			}
+		} else {
+			for {
+				entry := iter.next()
+				if entry == nil {
+					break
+				}
+				if prop, ok := entry.value.(*valueProperty); ok {
+					if !prop.enumerable {
+						continue
+					}
+				}
+				accum = append(accum, entry.key)
 			}
-			res = append(res, entry.key)
 		}
 	}
 
-	return
+	return accum
 }
 
 func (o *baseObject) ownPropertyKeys(all bool, accum []Value) []Value {
-	accum = o.val.self.ownKeys(all, accum)
-	if all {
-		accum = append(accum, o.ownSymbols()...)
-	}
-
-	return accum
+	return o.ownSymbols(all, o.val.self.ownKeys(all, accum))
 }
 
 func (o *baseObject) hasInstance(Value) bool {
@@ -1141,7 +1154,7 @@ func (o *Object) get(p Value, receiver Value) Value {
 	case *valueSymbol:
 		return o.self.getSym(p, receiver)
 	default:
-		return o.self.getStr(p.String(), receiver)
+		return o.self.getStr(p.string(), receiver)
 	}
 }
 
@@ -1152,7 +1165,7 @@ func (o *Object) getOwnProp(p Value) Value {
 	case *valueSymbol:
 		return o.self.getOwnPropSym(p)
 	default:
-		return o.self.getOwnPropStr(p.String())
+		return o.self.getOwnPropStr(p.string())
 	}
 }
 
@@ -1163,7 +1176,7 @@ func (o *Object) hasOwnProperty(p Value) bool {
 	case *valueSymbol:
 		return o.self.hasOwnPropertySym(p)
 	default:
-		return o.self.hasOwnPropertyStr(p.String())
+		return o.self.hasOwnPropertyStr(p.string())
 	}
 }
 
@@ -1174,11 +1187,11 @@ func (o *Object) hasProperty(p Value) bool {
 	case *valueSymbol:
 		return o.self.hasPropertySym(p)
 	default:
-		return o.self.hasPropertyStr(p.String())
+		return o.self.hasPropertyStr(p.string())
 	}
 }
 
-func (o *Object) setStr(name string, val, receiver Value, throw bool) bool {
+func (o *Object) setStr(name unistring.String, val, receiver Value, throw bool) bool {
 	if receiver == o {
 		return o.self.setOwnStr(name, val, throw)
 	} else {
@@ -1222,7 +1235,7 @@ func (o *Object) set(name Value, val, receiver Value, throw bool) bool {
 	case *valueSymbol:
 		return o.setSym(name, val, receiver, throw)
 	default:
-		return o.setStr(name.String(), val, receiver, throw)
+		return o.setStr(name.string(), val, receiver, throw)
 	}
 }
 
@@ -1233,7 +1246,7 @@ func (o *Object) setOwn(name Value, val Value, throw bool) bool {
 	case *valueSymbol:
 		return o.self.setOwnSym(name, val, throw)
 	default:
-		return o.self.setOwnStr(name.String(), val, throw)
+		return o.self.setOwnStr(name.string(), val, throw)
 	}
 }
 
@@ -1318,7 +1331,7 @@ func (o *Object) delete(n Value, throw bool) bool {
 	case *valueSymbol:
 		return o.self.deleteSym(n, throw)
 	default:
-		return o.self.deleteStr(n.String(), throw)
+		return o.self.deleteStr(n.string(), throw)
 	}
 }
 
@@ -1329,7 +1342,7 @@ func (o *Object) defineOwnProperty(n Value, desc PropertyDescriptor, throw bool)
 	case *valueSymbol:
 		return o.self.defineOwnPropertySym(n, desc, throw)
 	default:
-		return o.self.defineOwnPropertyStr(n.String(), desc, throw)
+		return o.self.defineOwnPropertyStr(n.string(), desc, throw)
 	}
 }
 

+ 8 - 6
object_args.go

@@ -1,5 +1,7 @@
 package goja
 
+import "github.com/dop251/goja/unistring"
+
 type argumentsObject struct {
 	baseObject
 	length int
@@ -10,11 +12,11 @@ type mappedProperty struct {
 	v *Value
 }
 
-func (a *argumentsObject) getStr(name string, receiver Value) Value {
+func (a *argumentsObject) getStr(name unistring.String, receiver Value) Value {
 	return a.getStrWithOwnProp(a.getOwnPropStr(name), name, receiver)
 }
 
-func (a *argumentsObject) getOwnPropStr(name string) Value {
+func (a *argumentsObject) getOwnPropStr(name unistring.String) Value {
 	if mapped, ok := a.values[name].(*mappedProperty); ok {
 		return *mapped.v
 	}
@@ -27,7 +29,7 @@ func (a *argumentsObject) init() {
 	a._putProp("length", intToValue(int64(a.length)), true, false, true)
 }
 
-func (a *argumentsObject) setOwnStr(name string, val Value, throw bool) bool {
+func (a *argumentsObject) setOwnStr(name unistring.String, val Value, throw bool) bool {
 	if prop, ok := a.values[name].(*mappedProperty); ok {
 		if !prop.writable {
 			a.val.runtime.typeErrorResult(throw, "Property is not writable: %s", name)
@@ -39,7 +41,7 @@ func (a *argumentsObject) setOwnStr(name string, val Value, throw bool) bool {
 	return a.baseObject.setOwnStr(name, val, throw)
 }
 
-func (a *argumentsObject) setForeignStr(name string, val, receiver Value, throw bool) (bool, bool) {
+func (a *argumentsObject) setForeignStr(name unistring.String, val, receiver Value, throw bool) (bool, bool) {
 	return a._setForeignStr(name, a.getOwnPropStr(name), val, receiver, throw)
 }
 
@@ -55,7 +57,7 @@ func (a *argumentsObject) setForeignStr(name string, val, receiver Value, throw
 	a.baseObject.putStr(name, val, throw)
 }*/
 
-func (a *argumentsObject) deleteStr(name string, throw bool) bool {
+func (a *argumentsObject) deleteStr(name unistring.String, throw bool) bool {
 	if prop, ok := a.values[name].(*mappedProperty); ok {
 		if !a.checkDeleteProp(name, &prop.valueProperty, throw) {
 			return false
@@ -89,7 +91,7 @@ func (a *argumentsObject) enumerateUnfiltered() iterNextFunc {
 	}).next)
 }
 
-func (a *argumentsObject) defineOwnPropertyStr(name string, descr PropertyDescriptor, throw bool) bool {
+func (a *argumentsObject) defineOwnPropertyStr(name unistring.String, descr PropertyDescriptor, throw bool) bool {
 	if mapped, ok := a.values[name].(*mappedProperty); ok {
 		existing := &valueProperty{
 			configurable: mapped.configurable,

+ 23 - 19
object_gomap.go

@@ -2,6 +2,8 @@ package goja
 
 import (
 	"reflect"
+
+	"github.com/dop251/goja/unistring"
 )
 
 type objectGoMapSimple struct {
@@ -24,23 +26,24 @@ func (o *objectGoMapSimple) _getStr(name string) Value {
 	return o.val.runtime.ToValue(v)
 }
 
-func (o *objectGoMapSimple) getStr(name string, receiver Value) Value {
-	if v := o._getStr(name); v != nil {
+func (o *objectGoMapSimple) getStr(name unistring.String, receiver Value) Value {
+	if v := o._getStr(name.String()); v != nil {
 		return v
 	}
 	return o.baseObject.getStr(name, receiver)
 }
 
-func (o *objectGoMapSimple) getOwnPropStr(name string) Value {
-	if v := o._getStr(name); v != nil {
+func (o *objectGoMapSimple) getOwnPropStr(name unistring.String) Value {
+	if v := o._getStr(name.String()); v != nil {
 		return v
 	}
 	return nil
 }
 
-func (o *objectGoMapSimple) setOwnStr(name string, val Value, throw bool) bool {
-	if _, exists := o.data[name]; exists {
-		o.data[name] = val.Export()
+func (o *objectGoMapSimple) setOwnStr(name unistring.String, val Value, throw bool) bool {
+	n := name.String()
+	if _, exists := o.data[n]; exists {
+		o.data[n] = val.Export()
 		return true
 	}
 	if proto := o.prototype; proto != nil {
@@ -54,7 +57,7 @@ func (o *objectGoMapSimple) setOwnStr(name string, val Value, throw bool) bool {
 		o.val.runtime.typeErrorResult(throw, "Cannot add property %s, object is not extensible", name)
 		return false
 	} else {
-		o.data[name] = val.Export()
+		o.data[n] = val.Export()
 	}
 	return true
 }
@@ -66,8 +69,8 @@ func trueValIfPresent(present bool) Value {
 	return nil
 }
 
-func (o *objectGoMapSimple) setForeignStr(name string, val, receiver Value, throw bool) (bool, bool) {
-	return o._setForeignStr(name, trueValIfPresent(o._hasStr(name)), val, receiver, throw)
+func (o *objectGoMapSimple) setForeignStr(name unistring.String, val, receiver Value, throw bool) (bool, bool) {
+	return o._setForeignStr(name, trueValIfPresent(o._hasStr(name.String())), val, receiver, throw)
 }
 
 func (o *objectGoMapSimple) _hasStr(name string) bool {
@@ -75,21 +78,22 @@ func (o *objectGoMapSimple) _hasStr(name string) bool {
 	return exists
 }
 
-func (o *objectGoMapSimple) hasOwnPropertyStr(name string) bool {
-	return o._hasStr(name)
+func (o *objectGoMapSimple) hasOwnPropertyStr(name unistring.String) bool {
+	return o._hasStr(name.String())
 }
 
-func (o *objectGoMapSimple) defineOwnPropertyStr(name string, descr PropertyDescriptor, throw bool) bool {
+func (o *objectGoMapSimple) defineOwnPropertyStr(name unistring.String, descr PropertyDescriptor, throw bool) bool {
 	if !o.val.runtime.checkHostObjectPropertyDescr(name, descr, throw) {
 		return false
 	}
 
-	if o.extensible || o._hasStr(name) {
-		o.data[name] = descr.Value.Export()
+	n := name.String()
+	if o.extensible || o._hasStr(n) {
+		o.data[n] = descr.Value.Export()
 		return true
 	}
 
-	o.val.runtime.typeErrorResult(throw, "Cannot define property %s, object is not extensible", name)
+	o.val.runtime.typeErrorResult(throw, "Cannot define property %s, object is not extensible", n)
 	return false
 }
 
@@ -111,8 +115,8 @@ func (o *objectGoMapSimple) assertCallable() (call func(FunctionCall) Value, ok
 }
 */
 
-func (o *objectGoMapSimple) deleteStr(name string, _ bool) bool {
-	delete(o.data, name)
+func (o *objectGoMapSimple) deleteStr(name unistring.String, _ bool) bool {
+	delete(o.data, name.String())
 	return true
 }
 
@@ -127,7 +131,7 @@ func (i *gomapPropIter) next() (propIterItem, iterNextFunc) {
 		name := i.propNames[i.idx]
 		i.idx++
 		if _, exists := i.o.data[name]; exists {
-			return propIterItem{name: name, enumerable: _ENUM_TRUE}, i.next
+			return propIterItem{name: unistring.NewFromString(name), enumerable: _ENUM_TRUE}, i.next
 		}
 	}
 

+ 24 - 19
object_gomap_reflect.go

@@ -1,6 +1,10 @@
 package goja
 
-import "reflect"
+import (
+	"reflect"
+
+	"github.com/dop251/goja/unistring"
+)
 
 type objectGoMapReflect struct {
 	objectGoReflect
@@ -54,8 +58,8 @@ func (o *objectGoMapReflect) _getStr(name string) Value {
 	return nil
 }
 
-func (o *objectGoMapReflect) getStr(name string, receiver Value) Value {
-	if v := o._getStr(name); v != nil {
+func (o *objectGoMapReflect) getStr(name unistring.String, receiver Value) Value {
+	if v := o._getStr(name.String()); v != nil {
 		return v
 	}
 	return o.objectGoReflect.getStr(name, receiver)
@@ -68,8 +72,8 @@ func (o *objectGoMapReflect) getIdx(idx valueInt, receiver Value) Value {
 	return o.objectGoReflect.getIdx(idx, receiver)
 }
 
-func (o *objectGoMapReflect) getOwnPropStr(name string) Value {
-	if v := o._getStr(name); v != nil {
+func (o *objectGoMapReflect) getOwnPropStr(name unistring.String) Value {
+	if v := o._getStr(name.String()); v != nil {
 		return &valueProperty{
 			value:      v,
 			writable:   true,
@@ -87,7 +91,7 @@ func (o *objectGoMapReflect) getOwnPropIdx(idx valueInt) Value {
 			enumerable: true,
 		}
 	}
-	return o.objectGoReflect.getOwnPropStr(idx.String())
+	return o.objectGoReflect.getOwnPropStr(idx.string())
 }
 
 func (o *objectGoMapReflect) toValue(val Value, throw bool) (reflect.Value, bool) {
@@ -117,8 +121,9 @@ func (o *objectGoMapReflect) _put(key reflect.Value, val Value, throw bool) bool
 	return false
 }
 
-func (o *objectGoMapReflect) setOwnStr(name string, val Value, throw bool) bool {
-	key := o.strToKey(name, false)
+func (o *objectGoMapReflect) setOwnStr(name unistring.String, val Value, throw bool) bool {
+	n := name.String()
+	key := o.strToKey(n, false)
 	if !key.IsValid() || !o.value.MapIndex(key).IsValid() {
 		if proto := o.prototype; proto != nil {
 			// we know it's foreign because prototype loops are not allowed
@@ -128,11 +133,11 @@ func (o *objectGoMapReflect) setOwnStr(name string, val Value, throw bool) bool
 		}
 		// new property
 		if !o.extensible {
-			o.val.runtime.typeErrorResult(throw, "Cannot add property %s, object is not extensible", name)
+			o.val.runtime.typeErrorResult(throw, "Cannot add property %s, object is not extensible", n)
 			return false
 		} else {
 			if throw && !key.IsValid() {
-				o.strToKey(name, true)
+				o.strToKey(n, true)
 				return false
 			}
 		}
@@ -165,7 +170,7 @@ func (o *objectGoMapReflect) setOwnIdx(idx valueInt, val Value, throw bool) bool
 	return true
 }
 
-func (o *objectGoMapReflect) setForeignStr(name string, val, receiver Value, throw bool) (bool, bool) {
+func (o *objectGoMapReflect) setForeignStr(name unistring.String, val, receiver Value, throw bool) (bool, bool) {
 	return o._setForeignStr(name, trueValIfPresent(o.hasOwnPropertyStr(name)), val, receiver, throw)
 }
 
@@ -173,24 +178,24 @@ func (o *objectGoMapReflect) setForeignIdx(idx valueInt, val, receiver Value, th
 	return o._setForeignIdx(idx, trueValIfPresent(o.hasOwnPropertyIdx(idx)), val, receiver, throw)
 }
 
-func (o *objectGoMapReflect) defineOwnPropertyStr(name string, descr PropertyDescriptor, throw bool) bool {
+func (o *objectGoMapReflect) defineOwnPropertyStr(name unistring.String, descr PropertyDescriptor, throw bool) bool {
 	if !o.val.runtime.checkHostObjectPropertyDescr(name, descr, throw) {
 		return false
 	}
 
-	return o._put(o.strToKey(name, throw), descr.Value, throw)
+	return o._put(o.strToKey(name.String(), throw), descr.Value, throw)
 }
 
 func (o *objectGoMapReflect) defineOwnPropertyIdx(idx valueInt, descr PropertyDescriptor, throw bool) bool {
-	if !o.val.runtime.checkHostObjectPropertyDescr(idx.String(), descr, throw) {
+	if !o.val.runtime.checkHostObjectPropertyDescr(idx.string(), descr, throw) {
 		return false
 	}
 
 	return o._put(o.toKey(idx, throw), descr.Value, throw)
 }
 
-func (o *objectGoMapReflect) hasOwnPropertyStr(name string) bool {
-	key := o.strToKey(name, false)
+func (o *objectGoMapReflect) hasOwnPropertyStr(name unistring.String) bool {
+	key := o.strToKey(name.String(), false)
 	if key.IsValid() && o.value.MapIndex(key).IsValid() {
 		return true
 	}
@@ -205,8 +210,8 @@ func (o *objectGoMapReflect) hasOwnPropertyIdx(idx valueInt) bool {
 	return false
 }
 
-func (o *objectGoMapReflect) deleteStr(name string, throw bool) bool {
-	key := o.strToKey(name, throw)
+func (o *objectGoMapReflect) deleteStr(name unistring.String, throw bool) bool {
+	key := o.strToKey(name.String(), throw)
 	if !key.IsValid() {
 		return false
 	}
@@ -235,7 +240,7 @@ func (i *gomapReflectPropIter) next() (propIterItem, iterNextFunc) {
 		v := i.o.value.MapIndex(key)
 		i.idx++
 		if v.IsValid() {
-			return propIterItem{name: key.String(), enumerable: _ENUM_TRUE}, i.next
+			return propIterItem{name: unistring.NewFromString(key.String()), enumerable: _ENUM_TRUE}, i.next
 		}
 	}
 

+ 29 - 0
object_gomap_reflect_test.go

@@ -249,3 +249,32 @@ func TestGoMapReflectProtoProp(t *testing.T) {
 		t.Fatal(err)
 	}
 }
+
+func TestGoMapReflectUnicode(t *testing.T) {
+	const SCRIPT = `
+	Object.setPrototypeOf(m, s);
+	if (m.Тест !== "passed") {
+		throw new Error("m.Тест: " + m.Тест);
+	}
+	m["é"];
+	`
+	type S struct {
+		Тест string
+	}
+	vm := New()
+	m := map[string]int{
+		"é": 42,
+	}
+	s := S{
+		Тест: "passed",
+	}
+	vm.Set("m", m)
+	vm.Set("s", &s)
+	res, err := vm.RunString(SCRIPT)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if res == nil || !res.StrictEquals(valueInt(42)) {
+		t.Fatalf("Unexpected value: %v", res)
+	}
+}

+ 29 - 0
object_gomap_test.go

@@ -306,3 +306,32 @@ func TestGoMapProtoPropChain(t *testing.T) {
 		t.Fatal(err)
 	}
 }
+
+func TestGoMapUnicode(t *testing.T) {
+	const SCRIPT = `
+	Object.setPrototypeOf(m, s);
+	if (m.Тест !== "passed") {
+		throw new Error("m.Тест: " + m.Тест);
+	}
+	m["é"];
+	`
+	type S struct {
+		Тест string
+	}
+	vm := New()
+	m := map[string]interface{}{
+		"é": 42,
+	}
+	s := S{
+		Тест: "passed",
+	}
+	vm.Set("m", m)
+	vm.Set("s", &s)
+	res, err := vm.RunString(SCRIPT)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if res == nil || !res.StrictEquals(valueInt(42)) {
+		t.Fatalf("Unexpected value: %v", res)
+	}
+}

+ 27 - 22
object_goreflect.go

@@ -4,6 +4,8 @@ import (
 	"fmt"
 	"go/ast"
 	"reflect"
+
+	"github.com/dop251/goja/unistring"
 )
 
 // JsonEncodable allows custom JSON encoding by JSON.stringify()
@@ -83,8 +85,8 @@ func (o *objectGoReflect) valueOfFunc(FunctionCall) Value {
 	return o.toPrimitive()
 }
 
-func (o *objectGoReflect) getStr(name string, receiver Value) Value {
-	if v := o._get(name); v != nil {
+func (o *objectGoReflect) getStr(name unistring.String, receiver Value) Value {
+	if v := o._get(name.String()); v != nil {
 		return v
 	}
 	return o.baseObject.getStr(name, receiver)
@@ -128,9 +130,10 @@ func (o *objectGoReflect) _get(name string) Value {
 	return nil
 }
 
-func (o *objectGoReflect) getOwnPropStr(name string) Value {
+func (o *objectGoReflect) getOwnPropStr(name unistring.String) Value {
+	n := name.String()
 	if o.value.Kind() == reflect.Struct {
-		if v := o._getField(name); v.IsValid() {
+		if v := o._getField(n); v.IsValid() {
 			return &valueProperty{
 				value:      o.val.runtime.ToValue(o.getAddr(v).Interface()),
 				writable:   v.CanSet(),
@@ -139,7 +142,7 @@ func (o *objectGoReflect) getOwnPropStr(name string) Value {
 		}
 	}
 
-	if v := o._getMethod(name); v.IsValid() {
+	if v := o._getMethod(n); v.IsValid() {
 		return &valueProperty{
 			value:      o.val.runtime.ToValue(v.Interface()),
 			enumerable: true,
@@ -149,8 +152,8 @@ func (o *objectGoReflect) getOwnPropStr(name string) Value {
 	return nil
 }
 
-func (o *objectGoReflect) setOwnStr(name string, val Value, throw bool) bool {
-	has, ok := o._put(name, val, throw)
+func (o *objectGoReflect) setOwnStr(name unistring.String, val Value, throw bool) bool {
+	has, ok := o._put(name.String(), val, throw)
 	if !has {
 		if res, ok := o._setForeignStr(name, nil, val, o.val, throw); !ok {
 			o.val.runtime.typeErrorResult(throw, "Cannot assign to property %s of a host object", name)
@@ -162,8 +165,8 @@ func (o *objectGoReflect) setOwnStr(name string, val Value, throw bool) bool {
 	return ok
 }
 
-func (o *objectGoReflect) setForeignStr(name string, val, receiver Value, throw bool) (bool, bool) {
-	return o._setForeignStr(name, trueValIfPresent(o._has(name)), val, receiver, throw)
+func (o *objectGoReflect) setForeignStr(name unistring.String, val, receiver Value, throw bool) (bool, bool) {
+	return o._setForeignStr(name, trueValIfPresent(o._has(name.String())), val, receiver, throw)
 }
 
 func (o *objectGoReflect) _put(name string, val Value, throw bool) (has, ok bool) {
@@ -185,14 +188,14 @@ func (o *objectGoReflect) _put(name string, val Value, throw bool) (has, ok bool
 	return false, false
 }
 
-func (o *objectGoReflect) _putProp(name string, value Value, writable, enumerable, configurable bool) Value {
-	if _, ok := o._put(name, value, false); ok {
+func (o *objectGoReflect) _putProp(name unistring.String, value Value, writable, enumerable, configurable bool) Value {
+	if _, ok := o._put(name.String(), value, false); ok {
 		return value
 	}
 	return o.baseObject._putProp(name, value, writable, enumerable, configurable)
 }
 
-func (r *Runtime) checkHostObjectPropertyDescr(name string, descr PropertyDescriptor, throw bool) bool {
+func (r *Runtime) checkHostObjectPropertyDescr(name unistring.String, descr PropertyDescriptor, throw bool) bool {
 	if descr.Getter != nil || descr.Setter != nil {
 		r.typeErrorResult(throw, "Host objects do not support accessor properties")
 		return false
@@ -208,10 +211,11 @@ func (r *Runtime) checkHostObjectPropertyDescr(name string, descr PropertyDescri
 	return true
 }
 
-func (o *objectGoReflect) defineOwnPropertyStr(name string, descr PropertyDescriptor, throw bool) bool {
+func (o *objectGoReflect) defineOwnPropertyStr(name unistring.String, descr PropertyDescriptor, throw bool) bool {
 	if o.val.runtime.checkHostObjectPropertyDescr(name, descr, throw) {
-		if has, ok := o._put(name, descr.Value, throw); !has {
-			o.val.runtime.typeErrorResult(throw, "Cannot define property '%s' on a host object", name)
+		n := name.String()
+		if has, ok := o._put(n, descr.Value, throw); !has {
+			o.val.runtime.typeErrorResult(throw, "Cannot define property '%s' on a host object", n)
 			return false
 		} else {
 			return ok
@@ -232,8 +236,8 @@ func (o *objectGoReflect) _has(name string) bool {
 	return false
 }
 
-func (o *objectGoReflect) hasOwnPropertyStr(name string) bool {
-	return o._has(name)
+func (o *objectGoReflect) hasOwnPropertyStr(name unistring.String) bool {
+	return o._has(name.String())
 }
 
 func (o *objectGoReflect) _toNumber() Value {
@@ -293,9 +297,10 @@ func (o *objectGoReflect) toPrimitive() Value {
 	return o.toPrimitiveString()
 }
 
-func (o *objectGoReflect) deleteStr(name string, throw bool) bool {
-	if o._has(name) {
-		o.val.runtime.typeErrorResult(throw, "Cannot delete property %s from a Go type")
+func (o *objectGoReflect) deleteStr(name unistring.String, throw bool) bool {
+	n := name.String()
+	if o._has(n) {
+		o.val.runtime.typeErrorResult(throw, "Cannot delete property %s from a Go type", n)
 		return false
 	}
 	return o.baseObject.deleteStr(name, throw)
@@ -311,7 +316,7 @@ func (i *goreflectPropIter) nextField() (propIterItem, iterNextFunc) {
 	if i.idx < len(names) {
 		name := names[i.idx]
 		i.idx++
-		return propIterItem{name: name, enumerable: _ENUM_TRUE}, i.nextField
+		return propIterItem{name: unistring.NewFromString(name), enumerable: _ENUM_TRUE}, i.nextField
 	}
 
 	i.idx = 0
@@ -323,7 +328,7 @@ func (i *goreflectPropIter) nextMethod() (propIterItem, iterNextFunc) {
 	if i.idx < len(names) {
 		name := names[i.idx]
 		i.idx++
-		return propIterItem{name: name, enumerable: _ENUM_TRUE}, i.nextMethod
+		return propIterItem{name: unistring.NewFromString(name), enumerable: _ENUM_TRUE}, i.nextMethod
 	}
 
 	return propIterItem{}, nil

+ 17 - 0
object_goreflect_test.go

@@ -1047,3 +1047,20 @@ func TestGoObj__Proto__(t *testing.T) {
 		t.Fatal(err)
 	}
 }
+
+func TestGoReflectUnicodeProps(t *testing.T) {
+	type S struct {
+		Тест string
+	}
+	vm := New()
+	var s S
+	vm.Set("s", &s)
+	_, err := vm.RunString(`
+	if (!s.hasOwnProperty("Тест")) {
+		throw new Error("hasOwnProperty");
+	}
+	`)
+	if err != nil {
+		t.Fatal(err)
+	}
+}

+ 12 - 21
object_goslice.go

@@ -1,10 +1,10 @@
 package goja
 
 import (
-	"math"
-	"math/bits"
 	"reflect"
 	"strconv"
+
+	"github.com/dop251/goja/unistring"
 )
 
 type objectGoSlice struct {
@@ -28,7 +28,7 @@ func (o *objectGoSlice) updateLen() {
 	o.lengthProp.value = intToValue(int64(len(*o.data)))
 }
 
-func (o *objectGoSlice) getStr(name string, receiver Value) Value {
+func (o *objectGoSlice) getStr(name unistring.String, receiver Value) Value {
 	var ownProp Value
 	if idx := strToGoIdx(name); idx >= 0 && idx < len(*o.data) {
 		v := (*o.data)[idx]
@@ -54,7 +54,7 @@ func (o *objectGoSlice) getIdx(idx valueInt, receiver Value) Value {
 	return nil
 }
 
-func (o *objectGoSlice) getOwnPropStr(name string) Value {
+func (o *objectGoSlice) getOwnPropStr(name unistring.String) Value {
 	if idx := strToGoIdx(name); idx >= 0 {
 		if idx < len(*o.data) {
 			v := o.val.runtime.ToValue((*o.data)[idx])
@@ -134,15 +134,6 @@ func (o *objectGoSlice) putIdx(idx int, v Value, throw bool) {
 	(*o.data)[idx] = v.Export()
 }
 
-func toInt(i int64) int {
-	if bits.UintSize == 32 {
-		if i >= math.MaxInt32 || i < math.MinInt32 {
-			panic(rangeError("Integer value overflows 32-bit int"))
-		}
-	}
-	return int(i)
-}
-
 func (o *objectGoSlice) putLength(v Value, throw bool) bool {
 	newLen := toInt(toLength(v))
 	curLen := len(*o.data)
@@ -171,7 +162,7 @@ func (o *objectGoSlice) setOwnIdx(idx valueInt, val Value, throw bool) bool {
 		}
 		o.putIdx(i, val, throw)
 	} else {
-		name := idx.String()
+		name := idx.string()
 		if res, ok := o._setForeignStr(name, nil, val, o.val, throw); !ok {
 			o.val.runtime.typeErrorResult(throw, "Can't set property '%s' on Go slice", name)
 			return false
@@ -182,7 +173,7 @@ func (o *objectGoSlice) setOwnIdx(idx valueInt, val Value, throw bool) bool {
 	return true
 }
 
-func (o *objectGoSlice) setOwnStr(name string, val Value, throw bool) bool {
+func (o *objectGoSlice) setOwnStr(name unistring.String, val Value, throw bool) bool {
 	if idx := strToGoIdx(name); idx >= 0 {
 		if idx >= len(*o.data) {
 			if res, ok := o._setForeignStr(name, nil, val, o.val, throw); ok {
@@ -208,7 +199,7 @@ func (o *objectGoSlice) setForeignIdx(idx valueInt, val, receiver Value, throw b
 	return o._setForeignIdx(idx, trueValIfPresent(o.hasOwnPropertyIdx(idx)), val, receiver, throw)
 }
 
-func (o *objectGoSlice) setForeignStr(name string, val, receiver Value, throw bool) (bool, bool) {
+func (o *objectGoSlice) setForeignStr(name unistring.String, val, receiver Value, throw bool) (bool, bool) {
 	return o._setForeignStr(name, trueValIfPresent(o.hasOwnPropertyStr(name)), val, receiver, throw)
 }
 
@@ -219,7 +210,7 @@ func (o *objectGoSlice) hasOwnPropertyIdx(idx valueInt) bool {
 	return false
 }
 
-func (o *objectGoSlice) hasOwnPropertyStr(name string) bool {
+func (o *objectGoSlice) hasOwnPropertyStr(name unistring.String) bool {
 	if idx := strToIdx64(name); idx >= 0 {
 		return idx < int64(len(*o.data))
 	}
@@ -228,7 +219,7 @@ func (o *objectGoSlice) hasOwnPropertyStr(name string) bool {
 
 func (o *objectGoSlice) defineOwnPropertyIdx(idx valueInt, descr PropertyDescriptor, throw bool) bool {
 	if i := toInt(int64(idx)); i >= 0 {
-		if !o.val.runtime.checkHostObjectPropertyDescr(idx.String(), descr, throw) {
+		if !o.val.runtime.checkHostObjectPropertyDescr(idx.string(), descr, throw) {
 			return false
 		}
 		val := descr.Value
@@ -242,7 +233,7 @@ func (o *objectGoSlice) defineOwnPropertyIdx(idx valueInt, descr PropertyDescrip
 	return false
 }
 
-func (o *objectGoSlice) defineOwnPropertyStr(name string, descr PropertyDescriptor, throw bool) bool {
+func (o *objectGoSlice) defineOwnPropertyStr(name unistring.String, descr PropertyDescriptor, throw bool) bool {
 	if idx := strToGoIdx(name); idx >= 0 {
 		if !o.val.runtime.checkHostObjectPropertyDescr(name, descr, throw) {
 			return false
@@ -275,7 +266,7 @@ func (o *objectGoSlice) toPrimitive() Value {
 	return o.toPrimitiveString()
 }
 
-func (o *objectGoSlice) deleteStr(name string, throw bool) bool {
+func (o *objectGoSlice) deleteStr(name unistring.String, throw bool) bool {
 	if idx := strToIdx64(name); idx >= 0 {
 		if idx < int64(len(*o.data)) {
 			o.val.runtime.typeErrorResult(throw, "Can't delete from Go slice")
@@ -306,7 +297,7 @@ func (i *goslicePropIter) next() (propIterItem, iterNextFunc) {
 	if i.idx < i.limit && i.idx < len(*i.o.data) {
 		name := strconv.Itoa(i.idx)
 		i.idx++
-		return propIterItem{name: name, enumerable: _ENUM_TRUE}, i.next
+		return propIterItem{name: unistring.String(name), enumerable: _ENUM_TRUE}, i.next
 	}
 
 	return propIterItem{}, nil

+ 15 - 13
object_goslice_reflect.go

@@ -3,6 +3,8 @@ package goja
 import (
 	"reflect"
 	"strconv"
+
+	"github.com/dop251/goja/unistring"
 )
 
 type objectGoSliceReflect struct {
@@ -32,7 +34,7 @@ func (o *objectGoSliceReflect) _hasIdx(idx valueInt) bool {
 	return false
 }
 
-func (o *objectGoSliceReflect) _hasStr(name string) bool {
+func (o *objectGoSliceReflect) _hasStr(name unistring.String) bool {
 	if idx := strToIdx64(name); idx >= 0 && idx < int64(o.value.Len()) {
 		return true
 	}
@@ -51,10 +53,10 @@ func (o *objectGoSliceReflect) getIdx(idx valueInt, receiver Value) Value {
 	if idx := toInt(int64(idx)); idx >= 0 && idx < o.value.Len() {
 		return o._getIdx(idx)
 	}
-	return o.objectGoReflect.getStr(idx.String(), receiver)
+	return o.objectGoReflect.getStr(idx.string(), receiver)
 }
 
-func (o *objectGoSliceReflect) getStr(name string, receiver Value) Value {
+func (o *objectGoSliceReflect) getStr(name unistring.String, receiver Value) Value {
 	var ownProp Value
 	if idx := strToGoIdx(name); idx >= 0 && idx < o.value.Len() {
 		ownProp = o._getIdx(idx)
@@ -66,7 +68,7 @@ func (o *objectGoSliceReflect) getStr(name string, receiver Value) Value {
 	return o.getStrWithOwnProp(ownProp, name, receiver)
 }
 
-func (o *objectGoSliceReflect) getOwnPropStr(name string) Value {
+func (o *objectGoSliceReflect) getOwnPropStr(name unistring.String) Value {
 	if idx := strToGoIdx(name); idx >= 0 {
 		if idx < o.value.Len() {
 			return &valueProperty{
@@ -180,7 +182,7 @@ func (o *objectGoSliceReflect) setOwnIdx(idx valueInt, val Value, throw bool) bo
 		}
 		o.putIdx(i, val, throw)
 	} else {
-		name := idx.String()
+		name := idx.string()
 		if res, ok := o._setForeignStr(name, nil, val, o.val, throw); !ok {
 			o.val.runtime.typeErrorResult(throw, "Can't set property '%s' on Go slice", name)
 			return false
@@ -191,7 +193,7 @@ func (o *objectGoSliceReflect) setOwnIdx(idx valueInt, val Value, throw bool) bo
 	return true
 }
 
-func (o *objectGoSliceReflect) setOwnStr(name string, val Value, throw bool) bool {
+func (o *objectGoSliceReflect) setOwnStr(name unistring.String, val Value, throw bool) bool {
 	if idx := strToGoIdx(name); idx >= 0 {
 		if idx >= o.value.Len() {
 			if res, ok := o._setForeignStr(name, nil, val, o.val, throw); ok {
@@ -217,7 +219,7 @@ func (o *objectGoSliceReflect) setForeignIdx(idx valueInt, val, receiver Value,
 	return o._setForeignIdx(idx, trueValIfPresent(o._hasIdx(idx)), val, receiver, throw)
 }
 
-func (o *objectGoSliceReflect) setForeignStr(name string, val, receiver Value, throw bool) (bool, bool) {
+func (o *objectGoSliceReflect) setForeignStr(name unistring.String, val, receiver Value, throw bool) (bool, bool) {
 	return o._setForeignStr(name, trueValIfPresent(o._hasStr(name)), val, receiver, throw)
 }
 
@@ -225,16 +227,16 @@ func (o *objectGoSliceReflect) hasOwnPropertyIdx(idx valueInt) bool {
 	return o._hasIdx(idx)
 }
 
-func (o *objectGoSliceReflect) hasOwnPropertyStr(name string) bool {
+func (o *objectGoSliceReflect) hasOwnPropertyStr(name unistring.String) bool {
 	if o._hasStr(name) {
 		return true
 	}
-	return o.objectGoReflect._has(name)
+	return o.objectGoReflect._has(name.String())
 }
 
 func (o *objectGoSliceReflect) defineOwnPropertyIdx(idx valueInt, descr PropertyDescriptor, throw bool) bool {
 	if i := toInt(int64(idx)); i >= 0 {
-		if !o.val.runtime.checkHostObjectPropertyDescr(idx.String(), descr, throw) {
+		if !o.val.runtime.checkHostObjectPropertyDescr(idx.string(), descr, throw) {
 			return false
 		}
 		val := descr.Value
@@ -248,7 +250,7 @@ func (o *objectGoSliceReflect) defineOwnPropertyIdx(idx valueInt, descr Property
 	return false
 }
 
-func (o *objectGoSliceReflect) defineOwnPropertyStr(name string, descr PropertyDescriptor, throw bool) bool {
+func (o *objectGoSliceReflect) defineOwnPropertyStr(name unistring.String, descr PropertyDescriptor, throw bool) bool {
 	if idx := strToGoIdx(name); idx >= 0 {
 		if !o.val.runtime.checkHostObjectPropertyDescr(name, descr, throw) {
 			return false
@@ -278,7 +280,7 @@ func (o *objectGoSliceReflect) toPrimitive() Value {
 	return o.toPrimitiveString()
 }
 
-func (o *objectGoSliceReflect) deleteStr(name string, throw bool) bool {
+func (o *objectGoSliceReflect) deleteStr(name unistring.String, throw bool) bool {
 	if idx := strToIdx64(name); idx >= 0 {
 		if idx < int64(o.value.Len()) {
 			o.val.runtime.typeErrorResult(throw, "Can't delete from Go slice")
@@ -310,7 +312,7 @@ func (i *gosliceReflectPropIter) next() (propIterItem, iterNextFunc) {
 	if i.idx < i.limit && i.idx < i.o.value.Len() {
 		name := strconv.Itoa(i.idx)
 		i.idx++
-		return propIterItem{name: name, enumerable: _ENUM_TRUE}, i.next
+		return propIterItem{name: unistring.String(name), enumerable: _ENUM_TRUE}, i.next
 	}
 
 	return i.o.objectGoReflect.enumerateUnfiltered()()

+ 16 - 12
object_lazy.go

@@ -1,6 +1,10 @@
 package goja
 
-import "reflect"
+import (
+	"reflect"
+
+	"github.com/dop251/goja/unistring"
+)
 
 type lazyObject struct {
 	val    *Object
@@ -61,7 +65,7 @@ func (o *lazyObject) hasOwnPropertySym(s *valueSymbol) bool {
 	return obj.hasOwnPropertySym(s)
 }
 
-func (o *lazyObject) defineOwnPropertyStr(name string, desc PropertyDescriptor, throw bool) bool {
+func (o *lazyObject) defineOwnPropertyStr(name unistring.String, desc PropertyDescriptor, throw bool) bool {
 	obj := o.create(o.val)
 	o.val.self = obj
 	return obj.defineOwnPropertyStr(name, desc, throw)
@@ -91,19 +95,19 @@ func (o *lazyObject) deleteSym(s *valueSymbol, throw bool) bool {
 	return obj.deleteSym(s, throw)
 }
 
-func (o *lazyObject) getStr(name string, receiver Value) Value {
+func (o *lazyObject) getStr(name unistring.String, receiver Value) Value {
 	obj := o.create(o.val)
 	o.val.self = obj
 	return obj.getStr(name, receiver)
 }
 
-func (o *lazyObject) getOwnPropStr(name string) Value {
+func (o *lazyObject) getOwnPropStr(name unistring.String) Value {
 	obj := o.create(o.val)
 	o.val.self = obj
 	return obj.getOwnPropStr(name)
 }
 
-func (o *lazyObject) setOwnStr(p string, v Value, throw bool) bool {
+func (o *lazyObject) setOwnStr(p unistring.String, v Value, throw bool) bool {
 	obj := o.create(o.val)
 	o.val.self = obj
 	return obj.setOwnStr(p, v, throw)
@@ -121,7 +125,7 @@ func (o *lazyObject) setOwnSym(p *valueSymbol, v Value, throw bool) bool {
 	return obj.setOwnSym(p, v, throw)
 }
 
-func (o *lazyObject) setForeignStr(p string, v, receiver Value, throw bool) (bool, bool) {
+func (o *lazyObject) setForeignStr(p unistring.String, v, receiver Value, throw bool) (bool, bool) {
 	obj := o.create(o.val)
 	o.val.self = obj
 	return obj.setForeignStr(p, v, receiver, throw)
@@ -139,19 +143,19 @@ func (o *lazyObject) setForeignSym(p *valueSymbol, v, receiver Value, throw bool
 	return obj.setForeignSym(p, v, receiver, throw)
 }
 
-func (o *lazyObject) hasPropertyStr(name string) bool {
+func (o *lazyObject) hasPropertyStr(name unistring.String) bool {
 	obj := o.create(o.val)
 	o.val.self = obj
 	return obj.hasPropertyStr(name)
 }
 
-func (o *lazyObject) hasOwnPropertyStr(name string) bool {
+func (o *lazyObject) hasOwnPropertyStr(name unistring.String) bool {
 	obj := o.create(o.val)
 	o.val.self = obj
 	return obj.hasOwnPropertyStr(name)
 }
 
-func (o *lazyObject) _putProp(string, Value, bool, bool, bool) Value {
+func (o *lazyObject) _putProp(unistring.String, Value, bool, bool, bool) Value {
 	panic("cannot use _putProp() in lazy object")
 }
 
@@ -189,7 +193,7 @@ func (o *lazyObject) assertConstructor() func(args []Value, newTarget *Object) *
 	return obj.assertConstructor()
 }
 
-func (o *lazyObject) deleteStr(name string, throw bool) bool {
+func (o *lazyObject) deleteStr(name unistring.String, throw bool) bool {
 	obj := o.create(o.val)
 	o.val.self = obj
 	return obj.deleteStr(name, throw)
@@ -255,10 +259,10 @@ func (o *lazyObject) ownKeys(all bool, accum []Value) []Value {
 	return obj.ownKeys(all, accum)
 }
 
-func (o *lazyObject) ownSymbols() []Value {
+func (o *lazyObject) ownSymbols(all bool, accum []Value) []Value {
 	obj := o.create(o.val)
 	o.val.self = obj
-	return obj.ownSymbols()
+	return obj.ownSymbols(all, accum)
 }
 
 func (o *lazyObject) ownPropertyKeys(all bool, accum []Value) []Value {

+ 12 - 0
object_test.go

@@ -97,6 +97,18 @@ func TestPropertyOrder(t *testing.T) {
 	testScript1(SCRIPT, _undefined, t)
 }
 
+func TestDefinePropertiesSymbol(t *testing.T) {
+	const SCRIPT = `
+	var desc = {};
+	desc[Symbol.toStringTag] = {value: "Test"};
+	var o = {};
+	Object.defineProperties(o, desc);
+	o[Symbol.toStringTag] === "Test";
+	`
+
+	testScript1(SCRIPT, valueTrue, t)
+}
+
 func BenchmarkPut(b *testing.B) {
 	v := &Object{}
 

+ 20 - 27
parser/expression.go

@@ -4,10 +4,11 @@ import (
 	"github.com/dop251/goja/ast"
 	"github.com/dop251/goja/file"
 	"github.com/dop251/goja/token"
+	"github.com/dop251/goja/unistring"
 )
 
 func (self *_parser) parseIdentifier() *ast.Identifier {
-	literal := self.literal
+	literal := self.parsedLiteral
 	idx := self.idx
 	self.next()
 	return &ast.Identifier{
@@ -17,7 +18,7 @@ func (self *_parser) parseIdentifier() *ast.Identifier {
 }
 
 func (self *_parser) parsePrimaryExpression() ast.Expression {
-	literal := self.literal
+	literal, parsedLiteral := self.literal, self.parsedLiteral
 	idx := self.idx
 	switch self.token {
 	case token.IDENTIFIER:
@@ -31,7 +32,7 @@ func (self *_parser) parsePrimaryExpression() ast.Expression {
 			}
 		}
 		return &ast.Identifier{
-			Name: literal,
+			Name: parsedLiteral,
 			Idx:  idx,
 		}
 	case token.NULL:
@@ -43,7 +44,7 @@ func (self *_parser) parsePrimaryExpression() ast.Expression {
 	case token.BOOLEAN:
 		self.next()
 		value := false
-		switch literal {
+		switch parsedLiteral {
 		case "true":
 			value = true
 		case "false":
@@ -58,14 +59,10 @@ func (self *_parser) parsePrimaryExpression() ast.Expression {
 		}
 	case token.STRING:
 		self.next()
-		value, err := parseStringLiteral(literal[1 : len(literal)-1])
-		if err != nil {
-			self.error(idx, err.Error())
-		}
 		return &ast.StringLiteral{
 			Idx:     idx,
 			Literal: literal,
-			Value:   value,
+			Value:   parsedLiteral,
 		}
 	case token.NUMBER:
 		self.next()
@@ -112,7 +109,7 @@ func (self *_parser) parseRegExpLiteral() *ast.RegExpLiteral {
 	}
 	idx := self.idxOf(offset)
 
-	pattern, err := self.scanString(offset)
+	pattern, _, err := self.scanString(offset, false)
 	endOffset := self.chrOffset
 
 	if err == nil {
@@ -151,11 +148,11 @@ func (self *_parser) parseVariableDeclaration(declarationList *[]*ast.VariableEx
 		return &ast.BadExpression{From: idx, To: self.idx}
 	}
 
-	literal := self.literal
+	name := self.parsedLiteral
 	idx := self.idx
 	self.next()
 	node := &ast.VariableExpression{
-		Name: literal,
+		Name: name,
 		Idx:  idx,
 	}
 
@@ -192,31 +189,27 @@ func (self *_parser) parseVariableDeclarationList(var_ file.Idx) []ast.Expressio
 	return list
 }
 
-func (self *_parser) parseObjectPropertyKey() (string, string) {
-	idx, tkn, literal := self.idx, self.token, self.literal
-	value := ""
+func (self *_parser) parseObjectPropertyKey() (string, unistring.String) {
+	idx, tkn, literal, parsedLiteral := self.idx, self.token, self.literal, self.parsedLiteral
+	var value unistring.String
 	self.next()
 	switch tkn {
 	case token.IDENTIFIER:
-		value = literal
+		value = parsedLiteral
 	case token.NUMBER:
 		var err error
 		_, err = parseNumberLiteral(literal)
 		if err != nil {
 			self.error(idx, err.Error())
 		} else {
-			value = literal
+			value = unistring.String(literal)
 		}
 	case token.STRING:
-		var err error
-		value, err = parseStringLiteral(literal[1 : len(literal)-1])
-		if err != nil {
-			self.error(idx, err.Error())
-		}
+		value = parsedLiteral
 	default:
 		// null, false, class, etc.
-		if matchIdentifier.MatchString(literal) {
-			value = literal
+		if isId(tkn) {
+			value = unistring.String(literal)
 		}
 	}
 	return literal, value
@@ -339,10 +332,10 @@ func (self *_parser) parseCallExpression(left ast.Expression) ast.Expression {
 func (self *_parser) parseDotMember(left ast.Expression) ast.Expression {
 	period := self.expect(token.PERIOD)
 
-	literal := self.literal
+	literal := self.parsedLiteral
 	idx := self.idx
 
-	if !matchIdentifier.MatchString(literal) {
+	if self.token != token.IDENTIFIER && !isId(self.token) {
 		self.expect(token.IDENTIFIER)
 		self.nextStatement()
 		return &ast.BadExpression{From: period, To: self.idx}
@@ -382,7 +375,7 @@ func (self *_parser) parseNewExpression() ast.Expression {
 			}
 			return &ast.MetaProperty{
 				Meta: &ast.Identifier{
-					Name: token.NEW.String(),
+					Name: unistring.String(token.NEW.String()),
 					Idx:  idx,
 				},
 				Property: prop,

+ 213 - 92
parser/lexer.go

@@ -1,27 +1,19 @@
 package parser
 
 import (
-	"bytes"
 	"errors"
 	"fmt"
-	"regexp"
 	"strconv"
 	"strings"
 	"unicode"
+	"unicode/utf16"
 	"unicode/utf8"
 
 	"github.com/dop251/goja/file"
 	"github.com/dop251/goja/token"
-	"unicode/utf16"
+	"github.com/dop251/goja/unistring"
 )
 
-type _chr struct {
-	value rune
-	width int
-}
-
-var matchIdentifier = regexp.MustCompile(`^[$_\p{L}][$_\p{L}\d}]*$`)
-
 func isDecimalDigit(chr rune) bool {
 	return '0' <= chr && chr <= '9'
 }
@@ -55,45 +47,65 @@ func isIdentifierPart(chr rune) bool {
 		chr >= utf8.RuneSelf && (unicode.IsLetter(chr) || unicode.IsDigit(chr))
 }
 
-func (self *_parser) scanIdentifier() (string, error) {
+func (self *_parser) scanIdentifier() (string, unistring.String, bool, error) {
 	offset := self.chrOffset
-	parse := false
+	hasEscape := false
+	isUnicode := false
+	length := 0
 	for isIdentifierPart(self.chr) {
-		if self.chr == '\\' {
+		r := self.chr
+		length++
+		if r == '\\' {
+			hasEscape = true
 			distance := self.chrOffset - offset
 			self.read()
 			if self.chr != 'u' {
-				return "", fmt.Errorf("Invalid identifier escape character: %c (%s)", self.chr, string(self.chr))
+				return "", "", false, fmt.Errorf("Invalid identifier escape character: %c (%s)", self.chr, string(self.chr))
 			}
-			parse = true
 			var value rune
 			for j := 0; j < 4; j++ {
 				self.read()
 				decimal, ok := hex2decimal(byte(self.chr))
 				if !ok {
-					return "", fmt.Errorf("Invalid identifier escape character: %c (%s)", self.chr, string(self.chr))
+					return "", "", false, fmt.Errorf("Invalid identifier escape character: %c (%s)", self.chr, string(self.chr))
 				}
 				value = value<<4 | decimal
 			}
 			if value == '\\' {
-				return "", fmt.Errorf("Invalid identifier escape value: %c (%s)", value, string(value))
+				return "", "", false, fmt.Errorf("Invalid identifier escape value: %c (%s)", value, string(value))
 			} else if distance == 0 {
 				if !isIdentifierStart(value) {
-					return "", fmt.Errorf("Invalid identifier escape value: %c (%s)", value, string(value))
+					return "", "", false, fmt.Errorf("Invalid identifier escape value: %c (%s)", value, string(value))
 				}
 			} else if distance > 0 {
 				if !isIdentifierPart(value) {
-					return "", fmt.Errorf("Invalid identifier escape value: %c (%s)", value, string(value))
+					return "", "", false, fmt.Errorf("Invalid identifier escape value: %c (%s)", value, string(value))
 				}
 			}
+			r = value
+		}
+		if r >= utf8.RuneSelf {
+			isUnicode = true
+			if r > 0xFFFF {
+				length++
+			}
 		}
 		self.read()
 	}
-	literal := string(self.str[offset:self.chrOffset])
-	if parse {
-		return parseStringLiteral(literal)
+
+	literal := self.str[offset:self.chrOffset]
+	var parsed unistring.String
+	if hasEscape || isUnicode {
+		var err error
+		parsed, err = parseStringLiteral1(literal, length, isUnicode)
+		if err != nil {
+			return "", "", false, err
+		}
+	} else {
+		parsed = unistring.String(literal)
 	}
-	return literal, nil
+
+	return literal, parsed, hasEscape, nil
 }
 
 // 7.2
@@ -118,7 +130,52 @@ func isLineTerminator(chr rune) bool {
 	return false
 }
 
-func (self *_parser) scan() (tkn token.Token, literal string, idx file.Idx) {
+func isId(tkn token.Token) bool {
+	switch tkn {
+	case token.KEYWORD,
+		token.BOOLEAN,
+		token.NULL,
+		token.THIS,
+		token.IF,
+		token.IN,
+		token.OF,
+		token.DO,
+
+		token.VAR,
+		token.FOR,
+		token.NEW,
+		token.TRY,
+
+		token.ELSE,
+		token.CASE,
+		token.VOID,
+		token.WITH,
+
+		token.WHILE,
+		token.BREAK,
+		token.CATCH,
+		token.THROW,
+
+		token.RETURN,
+		token.TYPEOF,
+		token.DELETE,
+		token.SWITCH,
+
+		token.DEFAULT,
+		token.FINALLY,
+
+		token.FUNCTION,
+		token.CONTINUE,
+		token.DEBUGGER,
+
+		token.INSTANCEOF:
+
+		return true
+	}
+	return false
+}
+
+func (self *_parser) scan() (tkn token.Token, literal string, parsedLiteral unistring.String, idx file.Idx) {
 
 	self.implicitSemicolon = false
 
@@ -131,30 +188,43 @@ func (self *_parser) scan() (tkn token.Token, literal string, idx file.Idx) {
 		switch chr := self.chr; {
 		case isIdentifierStart(chr):
 			var err error
-			literal, err = self.scanIdentifier()
+			var hasEscape bool
+			literal, parsedLiteral, hasEscape, err = self.scanIdentifier()
 			if err != nil {
 				tkn = token.ILLEGAL
 				break
 			}
-			if len(literal) > 1 {
+			if len(parsedLiteral) > 1 {
 				// Keywords are longer than 1 character, avoid lookup otherwise
 				var strict bool
-				tkn, strict = token.IsKeyword(literal)
+				tkn, strict = token.IsKeyword(string(parsedLiteral))
 
 				switch tkn {
 
 				case 0: // Not a keyword
-					if literal == "true" || literal == "false" {
+					if parsedLiteral == "true" || parsedLiteral == "false" {
+						if hasEscape {
+							tkn = token.ILLEGAL
+							return
+						}
 						self.insertSemicolon = true
 						tkn = token.BOOLEAN
 						return
-					} else if literal == "null" {
+					} else if parsedLiteral == "null" {
+						if hasEscape {
+							tkn = token.ILLEGAL
+							return
+						}
 						self.insertSemicolon = true
 						tkn = token.NULL
 						return
 					}
 
 				case token.KEYWORD:
+					if hasEscape {
+						tkn = token.ILLEGAL
+						return
+					}
 					tkn = token.KEYWORD
 					if strict {
 						// TODO If strict and in strict mode, then this is not a break
@@ -169,10 +239,17 @@ func (self *_parser) scan() (tkn token.Token, literal string, idx file.Idx) {
 					token.RETURN,
 					token.CONTINUE,
 					token.DEBUGGER:
+					if hasEscape {
+						tkn = token.ILLEGAL
+						return
+					}
 					self.insertSemicolon = true
 					return
 
 				default:
+					if hasEscape {
+						tkn = token.ILLEGAL
+					}
 					return
 
 				}
@@ -286,7 +363,7 @@ func (self *_parser) scan() (tkn token.Token, literal string, idx file.Idx) {
 				insertSemicolon = true
 				tkn = token.STRING
 				var err error
-				literal, err = self.scanString(self.chrOffset - 1)
+				literal, parsedLiteral, err = self.scanString(self.chrOffset-1, true)
 				if err != nil {
 					tkn = token.ILLEGAL
 				}
@@ -360,14 +437,6 @@ func (self *_parser) switch6(tkn0, tkn1 token.Token, chr2 rune, tkn2, tkn3 token
 	return tkn0
 }
 
-func (self *_parser) chrAt(index int) _chr {
-	value, width := utf8.DecodeRuneInString(self.str[index:])
-	return _chr{
-		value: value,
-		width: width,
-	}
-}
-
 func (self *_parser) _peek() rune {
 	if self.offset+1 < self.length {
 		return rune(self.str[self.offset+1])
@@ -475,19 +544,30 @@ func (self *_parser) scanMantissa(base int) {
 	}
 }
 
-func (self *_parser) scanEscape(quote rune) {
+func (self *_parser) scanEscape(quote rune) (int, bool) {
 
 	var length, base uint32
-	switch self.chr {
-	//case '0', '1', '2', '3', '4', '5', '6', '7':
-	//    Octal:
-	//    length, base, limit = 3, 8, 255
-	case 'a', 'b', 'f', 'n', 'r', 't', 'v', '\\', '"', '\'', '0':
+	chr := self.chr
+	switch chr {
+	case '0', '1', '2', '3', '4', '5', '6', '7':
+		//    Octal:
+		length, base = 3, 8
+	case 'a', 'b', 'f', 'n', 'r', 't', 'v', '\\', '"', '\'':
 		self.read()
-		return
-	case '\r', '\n', '\u2028', '\u2029':
-		self.scanNewline()
-		return
+		return 1, false
+	case '\r':
+		self.read()
+		if self.chr == '\n' {
+			self.read()
+			return 2, false
+		}
+		return 1, false
+	case '\n':
+		self.read()
+		return 1, false
+	case '\u2028', '\u2029':
+		self.read()
+		return 1, true
 	case 'x':
 		self.read()
 		length, base = 2, 16
@@ -496,24 +576,34 @@ func (self *_parser) scanEscape(quote rune) {
 		length, base = 4, 16
 	default:
 		self.read() // Always make progress
-		return
 	}
 
-	var value uint32
-	for ; length > 0 && self.chr != quote && self.chr >= 0; length-- {
-		digit := uint32(digitValue(self.chr))
-		if digit >= base {
-			break
+	if length > 0 {
+		var value uint32
+		for ; length > 0 && self.chr != quote && self.chr >= 0; length-- {
+			digit := uint32(digitValue(self.chr))
+			if digit >= base {
+				break
+			}
+			value = value*base + digit
+			self.read()
 		}
-		value = value*base + digit
-		self.read()
+		chr = rune(value)
+	}
+	if chr >= utf8.RuneSelf {
+		if chr > 0xFFFF {
+			return 2, true
+		}
+		return 1, true
 	}
+	return 1, false
 }
 
-func (self *_parser) scanString(offset int) (string, error) {
+func (self *_parser) scanString(offset int, parse bool) (literal string, parsed unistring.String, err error) {
 	// " ' /
 	quote := rune(self.str[offset])
-
+	length := 0
+	isUnicode := false
 	for self.chr != quote {
 		chr := self.chr
 		if chr == '\n' || chr == '\r' || chr == '\u2028' || chr == '\u2029' || chr < 0 {
@@ -521,14 +611,19 @@ func (self *_parser) scanString(offset int) (string, error) {
 		}
 		self.read()
 		if chr == '\\' {
-			if quote == '/' {
-				if self.chr == '\n' || self.chr == '\r' || self.chr == '\u2028' || self.chr == '\u2029' || self.chr < 0 {
+			if self.chr == '\n' || self.chr == '\r' || self.chr == '\u2028' || self.chr == '\u2029' || self.chr < 0 {
+				if quote == '/' {
 					goto newline
 				}
-				self.read()
+				self.scanNewline()
 			} else {
-				self.scanEscape(quote)
+				l, u := self.scanEscape(quote)
+				length += l
+				if u {
+					isUnicode = true
+				}
 			}
+			continue
 		} else if chr == '[' && quote == '/' {
 			// Allow a slash (/) in a bracket character class ([...])
 			// TODO Fix this, this is hacky...
@@ -536,21 +631,31 @@ func (self *_parser) scanString(offset int) (string, error) {
 		} else if chr == ']' && quote == -1 {
 			quote = '/'
 		}
+		if chr >= utf8.RuneSelf {
+			isUnicode = true
+			if chr > 0xFFFF {
+				length++
+			}
+		}
+		length++
 	}
 
 	// " ' /
 	self.read()
-
-	return string(self.str[offset:self.chrOffset]), nil
+	literal = self.str[offset:self.chrOffset]
+	if parse {
+		parsed, err = parseStringLiteral1(literal[1:len(literal)-1], length, isUnicode)
+	}
+	return
 
 newline:
 	self.scanNewline()
-	err := "String not terminated"
+	errStr := "String not terminated"
 	if quote == '/' {
-		err = "Invalid regular expression: missing /"
-		self.error(self.idxOf(offset), err)
+		errStr = "Invalid regular expression: missing /"
+		self.error(self.idxOf(offset), errStr)
 	}
-	return "", errors.New(err)
+	return "", "", errors.New(errStr)
 }
 
 func (self *_parser) scanNewline() {
@@ -617,21 +722,16 @@ error:
 	return nil, errors.New("Illegal numeric literal")
 }
 
-func parseStringLiteral(literal string) (string, error) {
-	// Best case scenario...
-	if literal == "" {
-		return "", nil
-	}
-
-	// Slightly less-best case scenario...
-	if !strings.ContainsRune(literal, '\\') {
-		return literal, nil
+func parseStringLiteral1(literal string, length int, unicode bool) (unistring.String, error) {
+	var sb strings.Builder
+	var chars []uint16
+	if unicode {
+		chars = make([]uint16, 1, length+1)
+		chars[0] = unistring.BOM
+	} else {
+		sb.Grow(length)
 	}
-
 	str := literal
-	buffer := bytes.NewBuffer(make([]byte, 0, 3*len(literal)/2))
-	var surrogate rune
-S:
 	for len(str) > 0 {
 		switch chr := str[0]; {
 		// We do not explicitly handle the case of the quote
@@ -639,11 +739,20 @@ S:
 		// This assumes we're already passed a partially well-formed literal
 		case chr >= utf8.RuneSelf:
 			chr, size := utf8.DecodeRuneInString(str)
-			buffer.WriteRune(chr)
+			if chr <= 0xFFFF {
+				chars = append(chars, uint16(chr))
+			} else {
+				first, second := utf16.EncodeRune(chr)
+				chars = append(chars, uint16(first), uint16(second))
+			}
 			str = str[size:]
 			continue
 		case chr != '\\':
-			buffer.WriteByte(chr)
+			if unicode {
+				chars = append(chars, uint16(chr))
+			} else {
+				sb.WriteByte(chr)
+			}
 			str = str[1:]
 			continue
 		}
@@ -736,20 +845,32 @@ S:
 			default:
 				value = rune(chr)
 			}
-			if surrogate != 0 {
-				value = utf16.DecodeRune(surrogate, value)
-				surrogate = 0
+		}
+		if unicode {
+			if value <= 0xFFFF {
+				chars = append(chars, uint16(value))
 			} else {
-				if utf16.IsSurrogate(value) {
-					surrogate = value
-					continue S
-				}
+				first, second := utf16.EncodeRune(value)
+				chars = append(chars, uint16(first), uint16(second))
+			}
+		} else {
+			if value >= utf8.RuneSelf {
+				return "", fmt.Errorf("Unexpected unicode character")
 			}
+			sb.WriteByte(byte(value))
 		}
-		buffer.WriteRune(value)
 	}
 
-	return buffer.String(), nil
+	if unicode {
+		if len(chars) != length+1 {
+			panic(fmt.Errorf("unexpected unicode length while parsing '%s'", literal))
+		}
+		return unistring.FromUtf16(chars), nil
+	}
+	if sb.Len() != length {
+		panic(fmt.Errorf("unexpected length while parsing '%s'", literal))
+	}
+	return unistring.String(sb.String()), nil
 }
 
 func (self *_parser) scanNumericLiteral(decimalPoint bool) (token.Token, string) {

+ 6 - 4
parser/lexer_test.go

@@ -5,6 +5,7 @@ import (
 
 	"github.com/dop251/goja/file"
 	"github.com/dop251/goja/token"
+	"github.com/dop251/goja/unistring"
 )
 
 func TestLexer(t *testing.T) {
@@ -17,13 +18,13 @@ func TestLexer(t *testing.T) {
 		test := func(src string, test ...interface{}) {
 			parser := setup(src)
 			for len(test) > 0 {
-				tkn, literal, idx := parser.scan()
+				tkn, literal, _, idx := parser.scan()
 				if len(test) > 0 {
 					is(tkn, test[0].(token.Token))
 					test = test[1:]
 				}
 				if len(test) > 0 {
-					is(literal, test[0].(string))
+					is(literal, unistring.String(test[0].(string)))
 					test = test[1:]
 				}
 				if len(test) > 0 {
@@ -184,7 +185,7 @@ Second line \
 
 		test(`var \u0024 = 1`,
 			token.VAR, "var", 1,
-			token.IDENTIFIER, "$", 5,
+			token.IDENTIFIER, "\\u0024", 5,
 			token.ASSIGN, "", 12,
 			token.NUMBER, "1", 14,
 			token.EOF, "", 15,
@@ -368,7 +369,8 @@ Second line \
 		)
 
 		test(`"\x0G"`,
-			token.STRING, "\"\\x0G\"", 1,
+			token.ILLEGAL, "\"\\x0G\"", 1,
+			//token.STRING, "\"\\x0G\"", 1,
 			token.EOF, "", 7,
 		)
 

+ 6 - 4
parser/parser.go

@@ -42,6 +42,7 @@ import (
 	"github.com/dop251/goja/ast"
 	"github.com/dop251/goja/file"
 	"github.com/dop251/goja/token"
+	"github.com/dop251/goja/unistring"
 )
 
 // A Mode value is a set of flags (or 0). They control optional parser functionality.
@@ -60,9 +61,10 @@ type _parser struct {
 	chrOffset int  // The offset of current character
 	offset    int  // The offset after current character (may be greater than 1)
 
-	idx     file.Idx    // The index of token
-	token   token.Token // The token
-	literal string      // The literal of the token, if any
+	idx           file.Idx    // The index of token
+	token         token.Token // The token
+	literal       string      // The literal of the token, if any
+	parsedLiteral unistring.String
 
 	scope             *_scope
 	insertSemicolon   bool // If we see a newline, then insert an implicit semicolon
@@ -188,7 +190,7 @@ func (self *_parser) parse() (*ast.Program, error) {
 }
 
 func (self *_parser) next() {
-	self.token, self.literal, self.idx = self.scan()
+	self.token, self.literal, self.parsedLiteral, self.idx = self.scan()
 }
 
 func (self *_parser) optionalSemicolon() {

+ 49 - 42
parser/parser_test.go

@@ -8,6 +8,7 @@ import (
 
 	"github.com/dop251/goja/ast"
 	"github.com/dop251/goja/file"
+	"github.com/dop251/goja/unistring"
 )
 
 func firstErr(err error) error {
@@ -86,9 +87,9 @@ func TestParserErr(t *testing.T) {
 			return program, parser
 		}
 
-		program, parser := test("", nil)
+		test("", nil)
 
-		program, parser = test(`
+		program, parser := test(`
         var abc;
         break; do {
         } while(true);
@@ -513,7 +514,7 @@ func TestParser(t *testing.T) {
             abc()
         `, nil)
 
-		program := test("", nil)
+		test("", nil)
 
 		test("//", nil)
 
@@ -531,7 +532,7 @@ func TestParser(t *testing.T) {
 
 		test("new +", "(anonymous): Line 1:5 Unexpected token +")
 
-		program = test(";", nil)
+		program := test(";", nil)
 		is(len(program.Body), 1)
 		is(program.Body[0].(*ast.EmptyStatement).Semicolon, file.Idx(1))
 
@@ -874,71 +875,77 @@ func TestParser(t *testing.T) {
 
 func Test_parseStringLiteral(t *testing.T) {
 	tt(t, func() {
-		test := func(have, want string) {
-			have, err := parseStringLiteral(have)
+		test := func(have string, want unistring.String) {
+			parser := newParser("", have)
+			parser.read()
+			parser.read()
+			_, res, err := parser.scanString(0, true)
 			is(err, nil)
-			is(have, want)
+			is(res, want)
 		}
 
-		test("", "")
+		test(`""`, "")
+		test(`/=/`, "=")
 
-		test("1(\\\\d+)", "1(\\d+)")
+		test("'1(\\\\d+)'", "1(\\d+)")
 
-		test("\\u2029", "\u2029")
+		test("'\\u2029'", "\u2029")
 
-		test("abc\\uFFFFabc", "abc\uFFFFabc")
+		test("'abc\\uFFFFabc'", "abc\uFFFFabc")
 
-		test("[First line \\\nSecond line \\\n Third line\\\n.     ]",
+		test("'[First line \\\nSecond line \\\n Third line\\\n.     ]'",
 			"[First line Second line  Third line.     ]")
 
-		test("\\u007a\\x79\\u000a\\x78", "zy\nx")
+		test("'\\u007a\\x79\\u000a\\x78'", "zy\nx")
 
 		// S7.8.4_A4.2_T3
-		test("\\a", "a")
-		test("\u0410", "\u0410")
+		test("'\\a'", "a")
+		test("'\u0410'", "\u0410")
 
 		// S7.8.4_A5.1_T1
-		test("\\0", "\u0000")
+		test("'\\0'", "\u0000")
 
 		// S8.4_A5
-		test("\u0000", "\u0000")
+		test("'\u0000'", "\u0000")
 
 		// 15.5.4.20
-		test("'abc'\\\n'def'", "'abc''def'")
+		test("\"'abc'\\\n'def'\"", "'abc''def'")
 
 		// 15.5.4.20-4-1
-		test("'abc'\\\r\n'def'", "'abc''def'")
+		test("\"'abc'\\\r\n'def'\"", "'abc''def'")
 
 		// Octal
-		test("\\0", "\000")
-		test("\\00", "\000")
-		test("\\000", "\000")
-		test("\\09", "\0009")
-		test("\\009", "\0009")
-		test("\\0009", "\0009")
-		test("\\1", "\001")
-		test("\\01", "\001")
-		test("\\001", "\001")
-		test("\\0011", "\0011")
-		test("\\1abc", "\001abc")
-
-		test("\\\u4e16", "\u4e16")
+		test("'\\0'", "\000")
+		test("'\\00'", "\000")
+		test("'\\000'", "\000")
+		test("'\\09'", "\0009")
+		test("'\\009'", "\0009")
+		test("'\\0009'", "\0009")
+		test("'\\1'", "\001")
+		test("'\\01'", "\001")
+		test("'\\001'", "\001")
+		test("'\\0011'", "\0011")
+		test("'\\1abc'", "\001abc")
+
+		test("'\\\u4e16'", "\u4e16")
 
 		// err
-		test = func(have, want string) {
-			have, err := parseStringLiteral(have)
+		test = func(have string, want unistring.String) {
+			parser := newParser("", have)
+			parser.read()
+			parser.read()
+			_, res, err := parser.scanString(0, true)
 			is(err.Error(), want)
-			is(have, "")
+			is(res, "")
 		}
 
-		test(`\u`, `invalid escape: \u: len("") != 4`)
-		test(`\u0`, `invalid escape: \u: len("0") != 4`)
-		test(`\u00`, `invalid escape: \u: len("00") != 4`)
-		test(`\u000`, `invalid escape: \u: len("000") != 4`)
+		test(`"\u"`, `invalid escape: \u: len("") != 4`)
+		test(`"\u0"`, `invalid escape: \u: len("0") != 4`)
+		test(`"\u00"`, `invalid escape: \u: len("00") != 4`)
+		test(`"\u000"`, `invalid escape: \u: len("000") != 4`)
 
-		test(`\x`, `invalid escape: \x: len("") != 2`)
-		test(`\x0`, `invalid escape: \x: len("0") != 2`)
-		test(`\x0`, `invalid escape: \x: len("0") != 2`)
+		test(`"\x"`, `invalid escape: \x: len("") != 2`)
+		test(`"\x0"`, `invalid escape: \x: len("0") != 2`)
 	})
 }
 

+ 3 - 2
parser/scope.go

@@ -2,6 +2,7 @@ package parser
 
 import (
 	"github.com/dop251/goja/ast"
+	"github.com/dop251/goja/unistring"
 )
 
 type _scope struct {
@@ -12,7 +13,7 @@ type _scope struct {
 	inFunction      bool
 	declarationList []ast.Declaration
 
-	labels []string
+	labels []unistring.String
 }
 
 func (self *_parser) openScope() {
@@ -30,7 +31,7 @@ func (self *_scope) declare(declaration ast.Declaration) {
 	self.declarationList = append(self.declarationList, declaration)
 }
 
-func (self *_scope) hasLabel(name string) bool {
+func (self *_scope) hasLabel(name unistring.String) bool {
 	for _, label := range self.labels {
 		if label == name {
 			return true

+ 31 - 22
proxy.go

@@ -1,6 +1,10 @@
 package goja
 
-import "reflect"
+import (
+	"reflect"
+
+	"github.com/dop251/goja/unistring"
+)
 
 // Proxy is a Go wrapper around ECMAScript Proxy. Calling Runtime.ToValue() on it
 // returns the underlying Proxy. Calling Export() on an ECMAScript Proxy returns a wrapper.
@@ -24,7 +28,7 @@ func (i *proxyPropIter) next() (propIterItem, iterNextFunc) {
 		name := i.names[i.idx]
 		i.idx++
 		if prop := i.p.val.getOwnProp(name); prop != nil {
-			return propIterItem{name: name.String(), value: prop}, i.next
+			return propIterItem{name: name.string(), value: prop}, i.next
 		}
 	}
 	if proto := i.p.proto(); proto != nil {
@@ -118,7 +122,7 @@ func (p *proxyObject) proxyCall(trap proxyTrap, args ...Value) (Value, bool) {
 		panic(r.NewTypeError("Proxy already revoked"))
 	}
 
-	if m := toMethod(r.getVStr(p.handler, trap.String())); m != nil {
+	if m := toMethod(r.getVStr(p.handler, unistring.String(trap.String()))); m != nil {
 		return m(FunctionCall{
 			This:      p.handler,
 			Arguments: args,
@@ -230,8 +234,8 @@ func (p *proxyObject) proxyDefineOwnProperty(name Value, descr PropertyDescripto
 	return false, false
 }
 
-func (p *proxyObject) defineOwnPropertyStr(name string, descr PropertyDescriptor, throw bool) bool {
-	if v, ok := p.proxyDefineOwnProperty(newStringValue(name), descr, throw); ok {
+func (p *proxyObject) defineOwnPropertyStr(name unistring.String, descr PropertyDescriptor, throw bool) bool {
+	if v, ok := p.proxyDefineOwnProperty(stringValueFromRaw(name), descr, throw); ok {
 		return v
 	}
 	return p.target.self.defineOwnPropertyStr(name, descr, throw)
@@ -271,8 +275,8 @@ func (p *proxyObject) proxyHas(name Value) (bool, bool) {
 	return false, false
 }
 
-func (p *proxyObject) hasPropertyStr(name string) bool {
-	if b, ok := p.proxyHas(newStringValue(name)); ok {
+func (p *proxyObject) hasPropertyStr(name unistring.String) bool {
+	if b, ok := p.proxyHas(stringValueFromRaw(name)); ok {
 		return b
 	}
 
@@ -295,7 +299,7 @@ func (p *proxyObject) hasPropertySym(s *valueSymbol) bool {
 	return p.target.self.hasPropertySym(s)
 }
 
-func (p *proxyObject) hasOwnPropertyStr(name string) bool {
+func (p *proxyObject) hasOwnPropertyStr(name unistring.String) bool {
 	return p.getOwnPropStr(name) != nil
 }
 
@@ -361,8 +365,8 @@ func (p *proxyObject) proxyGetOwnPropertyDescriptor(name Value) (Value, bool) {
 	return nil, false
 }
 
-func (p *proxyObject) getOwnPropStr(name string) Value {
-	if v, ok := p.proxyGetOwnPropertyDescriptor(newStringValue(name)); ok {
+func (p *proxyObject) getOwnPropStr(name unistring.String) Value {
+	if v, ok := p.proxyGetOwnPropertyDescriptor(stringValueFromRaw(name)); ok {
 		return v
 	}
 
@@ -385,8 +389,8 @@ func (p *proxyObject) getOwnPropSym(s *valueSymbol) Value {
 	return p.target.self.getOwnPropSym(s)
 }
 
-func (p *proxyObject) getStr(name string, receiver Value) Value {
-	if v, ok := p.proxyGet(newStringValue(name), receiver); ok {
+func (p *proxyObject) getStr(name unistring.String, receiver Value) Value {
+	if v, ok := p.proxyGet(stringValueFromRaw(name), receiver); ok {
 		return v
 	}
 	return p.target.self.getStr(name, receiver)
@@ -451,8 +455,8 @@ func (p *proxyObject) proxySet(name, value, receiver Value, throw bool) (bool, b
 	return false, false
 }
 
-func (p *proxyObject) setOwnStr(name string, v Value, throw bool) bool {
-	if res, ok := p.proxySet(newStringValue(name), v, p.val, throw); ok {
+func (p *proxyObject) setOwnStr(name unistring.String, v Value, throw bool) bool {
+	if res, ok := p.proxySet(stringValueFromRaw(name), v, p.val, throw); ok {
 		return res
 	}
 	return p.target.setStr(name, v, p.val, throw)
@@ -472,8 +476,8 @@ func (p *proxyObject) setOwnSym(s *valueSymbol, v Value, throw bool) bool {
 	return p.target.setSym(s, v, p.val, throw)
 }
 
-func (p *proxyObject) setForeignStr(name string, v, receiver Value, throw bool) (bool, bool) {
-	if res, ok := p.proxySet(newStringValue(name), v, receiver, throw); ok {
+func (p *proxyObject) setForeignStr(name unistring.String, v, receiver Value, throw bool) (bool, bool) {
+	if res, ok := p.proxySet(stringValueFromRaw(name), v, receiver, throw); ok {
 		return res, true
 	}
 	return p.target.setStr(name, v, receiver, throw), true
@@ -509,8 +513,8 @@ func (p *proxyObject) proxyDelete(n Value) (bool, bool) {
 	return false, false
 }
 
-func (p *proxyObject) deleteStr(name string, throw bool) bool {
-	if ret, ok := p.proxyDelete(newStringValue(name)); ok {
+func (p *proxyObject) deleteStr(name unistring.String, throw bool) bool {
+	if ret, ok := p.proxyDelete(stringValueFromRaw(name)); ok {
 		return ret
 	}
 
@@ -719,7 +723,7 @@ func (p *proxyObject) filterKeys(vals []Value, all, symbols bool) []Value {
 				}
 			} else {
 				if _, ok := val.(*valueSymbol); !ok {
-					prop = p.getOwnPropStr(val.String())
+					prop = p.getOwnPropStr(val.string())
 				} else {
 					continue
 				}
@@ -760,12 +764,17 @@ func (p *proxyObject) ownKeys(all bool, _ []Value) []Value { // we can assume ac
 	return p.target.self.ownKeys(all, nil)
 }
 
-func (p *proxyObject) ownSymbols() []Value {
+func (p *proxyObject) ownSymbols(all bool, accum []Value) []Value {
 	if vals, ok := p.proxyOwnKeys(); ok {
-		return p.filterKeys(vals, true, true)
+		res := p.filterKeys(vals, true, true)
+		if accum == nil {
+			return res
+		}
+		accum = append(accum, res...)
+		return accum
 	}
 
-	return p.target.self.ownSymbols()
+	return p.target.self.ownSymbols(all, accum)
 }
 
 func (p *proxyObject) className() string {

+ 4 - 4
regexp.go

@@ -35,7 +35,7 @@ func (r *regexp2Wrapper) FindSubmatchIndex(s valueString, start int) (result []i
 	case asciiString:
 		match, err = wrapped.FindStringMatch(string(s)[start:])
 	case unicodeString:
-		match, err = wrapped.FindRunesMatch(utf16.Decode(s[start:]))
+		match, err = wrapped.FindRunesMatch(utf16.Decode(s[start+1:]))
 	default:
 		panic(fmt.Errorf("Unknown string type: %T", s))
 	}
@@ -208,7 +208,7 @@ func (r *regexp2Wrapper) MatchString(s valueString) bool {
 		matched, _ := wrapped.MatchString(string(s))
 		return matched
 	case unicodeString:
-		matched, _ := wrapped.MatchRunes(utf16.Decode(s))
+		matched, _ := wrapped.MatchRunes(utf16.Decode(s[1:]))
 		return matched
 	default:
 		panic(fmt.Errorf("Unknown string type: %T", s))
@@ -287,7 +287,7 @@ func (r *regexpObject) execResultToArray(target valueString, result []int) Value
 	for index := 0; index < captureCount; index++ {
 		offset := index << 1
 		if result[offset] >= lowerBound {
-			valueArray[index] = target.substring(int64(result[offset]), int64(result[offset+1]))
+			valueArray[index] = target.substring(result[offset], result[offset+1])
 			lowerBound = result[offset]
 		} else {
 			valueArray[index] = _undefined
@@ -311,7 +311,7 @@ func (r *regexpObject) execRegexp(target valueString) (match bool, result []int)
 	if !r.global && !r.sticky {
 		index = 0
 	}
-	if index >= 0 && index <= target.length() {
+	if index >= 0 && index <= int64(target.length()) {
 		result = r.pattern.FindSubmatchIndex(target, int(index))
 	}
 	if result == nil || r.sticky && result[0] != 0 {

+ 50 - 28
runtime.go

@@ -10,6 +10,7 @@ import (
 	"math/bits"
 	"math/rand"
 	"reflect"
+	"runtime"
 	"strconv"
 	"time"
 
@@ -17,7 +18,7 @@ import (
 
 	js_ast "github.com/dop251/goja/ast"
 	"github.com/dop251/goja/parser"
-	"runtime"
+	"github.com/dop251/goja/unistring"
 )
 
 const (
@@ -88,7 +89,6 @@ type global struct {
 	RegExpPrototype   *Object
 	DatePrototype     *Object
 	SymbolPrototype   *Object
-	ArrayIterator     *Object
 
 	ArrayBufferPrototype *Object
 	DataViewPrototype    *Object
@@ -98,10 +98,11 @@ type global struct {
 	MapPrototype         *Object
 	SetPrototype         *Object
 
-	IteratorPrototype      *Object
-	ArrayIteratorPrototype *Object
-	MapIteratorPrototype   *Object
-	SetIteratorPrototype   *Object
+	IteratorPrototype       *Object
+	ArrayIteratorPrototype  *Object
+	MapIteratorPrototype    *Object
+	SetIteratorPrototype    *Object
+	StringIteratorPrototype *Object
 
 	ErrorPrototype          *Object
 	TypeErrorPrototype      *Object
@@ -158,7 +159,7 @@ type Runtime struct {
 	now             Now
 	_collator       *collate.Collator
 
-	symbolRegistry map[string]*valueSymbol
+	symbolRegistry map[unistring.String]*valueSymbol
 
 	typeInfoCache   map[reflect.Type]*reflectTypeInfo
 	fieldNameMapper FieldNameMapper
@@ -169,7 +170,7 @@ type Runtime struct {
 
 type StackFrame struct {
 	prg      *Program
-	funcName string
+	funcName unistring.String
 	pc       int
 }
 
@@ -187,7 +188,7 @@ func (f *StackFrame) FuncName() string {
 	if f.funcName == "" {
 		return "<anonymous>"
 	}
-	return f.funcName
+	return f.funcName.String()
 }
 
 func (f *StackFrame) Position() Position {
@@ -203,7 +204,7 @@ func (f *StackFrame) Position() Position {
 func (f *StackFrame) Write(b *bytes.Buffer) {
 	if f.prg != nil {
 		if n := f.prg.funcName; n != "" {
-			b.WriteString(n)
+			b.WriteString(n.String())
 			b.WriteString(" (")
 		}
 		if n := f.prg.src.name; n != "" {
@@ -221,7 +222,7 @@ func (f *StackFrame) Write(b *bytes.Buffer) {
 		}
 	} else {
 		if f.funcName != "" {
-			b.WriteString(f.funcName)
+			b.WriteString(f.funcName.String())
 			b.WriteString(" (")
 		}
 		b.WriteString("native")
@@ -311,7 +312,7 @@ func (e *Exception) Value() Value {
 }
 
 func (r *Runtime) addToGlobal(name string, value Value) {
-	r.globalObject.self._putProp(name, value, true, false, true)
+	r.globalObject.self._putProp(unistring.String(name), value, true, false, true)
 }
 
 func (r *Runtime) createIterProto(val *Object) objectImpl {
@@ -382,7 +383,7 @@ func (r *Runtime) newError(typ *Object, format string, args ...interface{}) Valu
 	return r.builtin_new(typ, []Value{newStringValue(msg)})
 }
 
-func (r *Runtime) throwReferenceError(name string) {
+func (r *Runtime) throwReferenceError(name unistring.String) {
 	panic(r.newError(r.global.ReferenceError, "%s is not defined", name))
 }
 
@@ -431,7 +432,7 @@ func (r *Runtime) NewGoError(err error) *Object {
 	return e
 }
 
-func (r *Runtime) newFunc(name string, len int, strict bool) (f *funcObject) {
+func (r *Runtime) newFunc(name unistring.String, len int, strict bool) (f *funcObject) {
 	v := &Object{runtime: r}
 
 	f = &funcObject{}
@@ -448,7 +449,7 @@ func (r *Runtime) newFunc(name string, len int, strict bool) (f *funcObject) {
 	return
 }
 
-func (r *Runtime) newNativeFuncObj(v *Object, call func(FunctionCall) Value, construct func(args []Value, proto *Object) *Object, name string, proto *Object, length int) *nativeFuncObject {
+func (r *Runtime) newNativeFuncObj(v *Object, call func(FunctionCall) Value, construct func(args []Value, proto *Object) *Object, name unistring.String, proto *Object, length int) *nativeFuncObject {
 	f := &nativeFuncObject{
 		baseFuncObject: baseFuncObject{
 			baseObject: baseObject{
@@ -469,7 +470,7 @@ func (r *Runtime) newNativeFuncObj(v *Object, call func(FunctionCall) Value, con
 	return f
 }
 
-func (r *Runtime) newNativeConstructor(call func(ConstructorCall) *Object, name string, length int) *Object {
+func (r *Runtime) newNativeConstructor(call func(ConstructorCall) *Object, name unistring.String, length int) *Object {
 	v := &Object{runtime: r}
 
 	f := &nativeFuncObject{
@@ -501,7 +502,7 @@ func (r *Runtime) newNativeConstructor(call func(ConstructorCall) *Object, name
 	return v
 }
 
-func (r *Runtime) newNativeConstructOnly(v *Object, ctor func(args []Value, newTarget *Object) *Object, defaultProto *Object, name string, length int) *nativeFuncObject {
+func (r *Runtime) newNativeConstructOnly(v *Object, ctor func(args []Value, newTarget *Object) *Object, defaultProto *Object, name unistring.String, length int) *nativeFuncObject {
 	if v == nil {
 		v = &Object{runtime: r}
 	}
@@ -534,7 +535,7 @@ func (r *Runtime) newNativeConstructOnly(v *Object, ctor func(args []Value, newT
 	return f
 }
 
-func (r *Runtime) newNativeFunc(call func(FunctionCall) Value, construct func(args []Value, proto *Object) *Object, name string, proto *Object, length int) *Object {
+func (r *Runtime) newNativeFunc(call func(FunctionCall) Value, construct func(args []Value, proto *Object) *Object, name unistring.String, proto *Object, length int) *Object {
 	v := &Object{runtime: r}
 
 	f := &nativeFuncObject{
@@ -558,7 +559,7 @@ func (r *Runtime) newNativeFunc(call func(FunctionCall) Value, construct func(ar
 	return v
 }
 
-func (r *Runtime) newNativeFuncConstructObj(v *Object, construct func(args []Value, proto *Object) *Object, name string, proto *Object, length int) *nativeFuncObject {
+func (r *Runtime) newNativeFuncConstructObj(v *Object, construct func(args []Value, proto *Object) *Object, name unistring.String, proto *Object, length int) *nativeFuncObject {
 	f := &nativeFuncObject{
 		baseFuncObject: baseFuncObject{
 			baseObject: baseObject{
@@ -579,11 +580,11 @@ func (r *Runtime) newNativeFuncConstructObj(v *Object, construct func(args []Val
 	return f
 }
 
-func (r *Runtime) newNativeFuncConstruct(construct func(args []Value, proto *Object) *Object, name string, prototype *Object, length int) *Object {
+func (r *Runtime) newNativeFuncConstruct(construct func(args []Value, proto *Object) *Object, name unistring.String, prototype *Object, length int) *Object {
 	return r.newNativeFuncConstructProto(construct, name, prototype, r.global.FunctionPrototype, length)
 }
 
-func (r *Runtime) newNativeFuncConstructProto(construct func(args []Value, proto *Object) *Object, name string, prototype, proto *Object, length int) *Object {
+func (r *Runtime) newNativeFuncConstructProto(construct func(args []Value, proto *Object) *Object, name unistring.String, prototype, proto *Object, length int) *Object {
 	v := &Object{runtime: r}
 
 	f := &nativeFuncObject{}
@@ -929,6 +930,15 @@ func toLength(v Value) int64 {
 	return i
 }
 
+func toInt(i int64) int {
+	if bits.UintSize == 32 {
+		if i > math.MaxInt32 || i < math.MinInt32 {
+			panic(rangeError("Integer value overflows 32-bit int"))
+		}
+	}
+	return int(i)
+}
+
 func (r *Runtime) toIndex(v Value) int {
 	intIdx := v.ToInteger()
 	if intIdx >= 0 && intIdx < maxInt {
@@ -1178,10 +1188,10 @@ func (r *Runtime) ToValue(i interface{}) Value {
 			return valueFalse
 		}
 	case func(FunctionCall) Value:
-		name := runtime.FuncForPC(reflect.ValueOf(i).Pointer()).Name()
+		name := unistring.NewFromString(runtime.FuncForPC(reflect.ValueOf(i).Pointer()).Name())
 		return r.newNativeFunc(i, nil, name, nil, 0)
 	case func(ConstructorCall) *Object:
-		name := runtime.FuncForPC(reflect.ValueOf(i).Pointer()).Name()
+		name := unistring.NewFromString(runtime.FuncForPC(reflect.ValueOf(i).Pointer()).Name())
 		return r.newNativeConstructor(i, name, 0)
 	case int:
 		return intToValue(int64(i))
@@ -1309,7 +1319,7 @@ func (r *Runtime) ToValue(i interface{}) Value {
 		obj.self = a
 		return obj
 	case reflect.Func:
-		name := runtime.FuncForPC(reflect.ValueOf(i).Pointer()).Name()
+		name := unistring.NewFromString(runtime.FuncForPC(reflect.ValueOf(i).Pointer()).Name())
 		return r.newNativeFunc(r.wrapReflectFunc(value), nil, name, nil, value.Type().NumIn())
 	}
 
@@ -1559,7 +1569,7 @@ func (r *Runtime) toReflectValue(v Value, typ reflect.Type) (reflect.Value, erro
 					if field.Anonymous {
 						v = o
 					} else {
-						v = o.self.getStr(name, nil)
+						v = o.self.getStr(unistring.NewFromString(name), nil)
 					}
 
 					if v != nil {
@@ -1650,12 +1660,12 @@ func (r *Runtime) GlobalObject() *Object {
 // Set the specified value as a property of the global object.
 // The value is first converted using ToValue()
 func (r *Runtime) Set(name string, value interface{}) {
-	r.globalObject.self.setOwnStr(name, r.ToValue(value), false)
+	r.globalObject.self.setOwnStr(unistring.NewFromString(name), r.ToValue(value), false)
 }
 
 // Get the specified property of the global object.
 func (r *Runtime) Get(name string) Value {
-	return r.globalObject.self.getStr(name, nil)
+	return r.globalObject.self.getStr(unistring.NewFromString(name), nil)
 }
 
 // SetRandSource sets random source for this Runtime. If not called, the default math/rand is used.
@@ -1846,7 +1856,7 @@ func toPropertyKey(key Value) Value {
 	return key.ToPrimitiveString()
 }
 
-func (r *Runtime) getVStr(v Value, p string) Value {
+func (r *Runtime) getVStr(v Value, p unistring.String) Value {
 	o := v.ToObject(r)
 	return o.self.getStr(p, v)
 }
@@ -1933,3 +1943,15 @@ func isArray(object *Object) bool {
 		return false
 	}
 }
+
+func isRegexp(v Value) bool {
+	if o, ok := v.(*Object); ok {
+		matcher := o.self.getSym(symMatch, nil)
+		if matcher != nil && matcher != _undefined {
+			return matcher.ToBoolean()
+		}
+		_, reg := o.self.(*regexpObject)
+		return reg
+	}
+	return false
+}

+ 44 - 2
runtime_test.go

@@ -5,6 +5,7 @@ import (
 	"fmt"
 	"reflect"
 	"runtime"
+	"strconv"
 	"testing"
 	"time"
 )
@@ -1146,7 +1147,7 @@ func TestInterruptInWrappedFunction(t *testing.T) {
 		rt.Interrupt(errors.New("hi"))
 	}()
 
-	v, err = fn(nil)
+	_, err = fn(nil)
 	if err == nil {
 		t.Fatal("expected error")
 	}
@@ -1173,7 +1174,7 @@ func TestRunLoopPreempt(t *testing.T) {
 		vm.Interrupt(errors.New("hi"))
 	}()
 
-	v, err = fn(nil)
+	_, err = fn(nil)
 	if err == nil {
 		t.Fatal("expected error")
 	}
@@ -1523,3 +1524,44 @@ func BenchmarkMainLoop(b *testing.B) {
 		vm.RunProgram(prg)
 	}
 }
+
+func BenchmarkStringMapGet(b *testing.B) {
+	m := make(map[string]Value)
+	for i := 0; i < 100; i++ {
+		m[strconv.Itoa(i)] = intToValue(int64(i))
+	}
+	b.ResetTimer()
+	for i := 0; i < b.N; i++ {
+		if m["50"] == nil {
+			b.Fatal()
+		}
+	}
+}
+
+func BenchmarkValueStringMapGet(b *testing.B) {
+	m := make(map[valueString]Value)
+	for i := 0; i < 100; i++ {
+		m[asciiString(strconv.Itoa(i))] = intToValue(int64(i))
+	}
+	b.ResetTimer()
+	var key valueString = asciiString("50")
+	for i := 0; i < b.N; i++ {
+		if m[key] == nil {
+			b.Fatal()
+		}
+	}
+}
+
+func BenchmarkAsciiStringMapGet(b *testing.B) {
+	m := make(map[asciiString]Value)
+	for i := 0; i < 100; i++ {
+		m[asciiString(strconv.Itoa(i))] = intToValue(int64(i))
+	}
+	b.ResetTimer()
+	var key = asciiString("50")
+	for i := 0; i < b.N; i++ {
+		if m[key] == nil {
+			b.Fatal()
+		}
+	}
+}

+ 133 - 46
string.go

@@ -3,8 +3,11 @@ package goja
 import (
 	"io"
 	"strconv"
+	"strings"
 	"unicode/utf16"
 	"unicode/utf8"
+
+	"github.com/dop251/goja/unistring"
 )
 
 const (
@@ -46,37 +49,121 @@ var (
 
 type valueString interface {
 	Value
-	charAt(int64) rune
-	length() int64
+	charAt(int) rune
+	length() int
 	concat(valueString) valueString
-	substring(start, end int64) valueString
+	substring(start, end int) valueString
 	compareTo(valueString) int
 	reader(start int) io.RuneReader
-	index(valueString, int64) int64
-	lastIndex(valueString, int64) int64
+	index(valueString, int) int
+	lastIndex(valueString, int) int
 	toLower() valueString
 	toUpper() valueString
 	toTrimmedUTF8() string
 }
 
+type stringIterObject struct {
+	baseObject
+	reader io.RuneReader
+}
+
+func isUTF16FirstSurrogate(r rune) bool {
+	return r >= 0xD800 && r <= 0xDBFF
+}
+
+func isUTF16SecondSurrogate(r rune) bool {
+	return r >= 0xDC00 && r <= 0xDFFF
+}
+
+func (si *stringIterObject) next() Value {
+	if si.reader == nil {
+		return si.val.runtime.createIterResultObject(_undefined, true)
+	}
+	r, _, err := si.reader.ReadRune()
+	if err == io.EOF {
+		si.reader = nil
+		return si.val.runtime.createIterResultObject(_undefined, true)
+	}
+	return si.val.runtime.createIterResultObject(stringFromRune(r), false)
+}
+
+func stringFromRune(r rune) valueString {
+	if r < utf8.RuneSelf {
+		var sb strings.Builder
+		sb.Grow(1)
+		sb.WriteByte(byte(r))
+		return asciiString(sb.String())
+	}
+	var sb unicodeStringBuilder
+	if r <= 0xFFFF {
+		sb.Grow(1)
+	} else {
+		sb.Grow(2)
+	}
+	sb.writeRune(r)
+	return sb.string()
+}
+
+func (r *Runtime) createStringIterator(s valueString) Value {
+	o := &Object{runtime: r}
+
+	si := &stringIterObject{
+		reader: s.reader(0),
+	}
+	si.class = classStringIterator
+	si.val = o
+	si.extensible = true
+	o.self = si
+	si.prototype = r.global.StringIteratorPrototype
+	si.init()
+
+	return o
+}
+
 type stringObject struct {
 	baseObject
 	value      valueString
-	length     int64
+	length     int
 	lengthProp valueProperty
 }
 
-func newUnicodeString(s string) valueString {
-	return unicodeString(utf16.Encode([]rune(s)))
-}
-
 func newStringValue(s string) valueString {
+	utf16Size := 0
+	ascii := true
 	for _, chr := range s {
+		utf16Size++
 		if chr >= utf8.RuneSelf {
-			return newUnicodeString(s)
+			ascii = false
+			if chr > 0xFFFF {
+				utf16Size++
+			}
 		}
 	}
-	return asciiString(s)
+	if ascii {
+		return asciiString(s)
+	}
+	buf := make([]uint16, utf16Size+1)
+	buf[0] = unistring.BOM
+	c := 1
+	for _, chr := range s {
+		if chr <= 0xFFFF {
+			buf[c] = uint16(chr)
+		} else {
+			first, second := utf16.EncodeRune(chr)
+			buf[c] = uint16(first)
+			c++
+			buf[c] = uint16(second)
+		}
+		c++
+	}
+	return unicodeString(buf)
+}
+
+func stringValueFromRaw(raw unistring.String) valueString {
+	if b := raw.AsUtf16(); b != nil {
+		return unicodeString(b)
+	}
+	return asciiString(raw)
 }
 
 func (s *stringObject) init() {
@@ -88,12 +175,12 @@ func (s *stringObject) setLength() {
 	if s.value != nil {
 		s.length = s.value.length()
 	}
-	s.lengthProp.value = intToValue(s.length)
+	s.lengthProp.value = intToValue(int64(s.length))
 	s._put("length", &s.lengthProp)
 }
 
-func (s *stringObject) getStr(name string, receiver Value) Value {
-	if i := strToIdx64(name); i >= 0 && i < s.length {
+func (s *stringObject) getStr(name unistring.String, receiver Value) Value {
+	if i := strToGoIdx(name); i >= 0 && i < s.length {
 		return s._getIdx(i)
 	}
 	return s.baseObject.getStr(name, receiver)
@@ -102,16 +189,16 @@ func (s *stringObject) getStr(name string, receiver Value) Value {
 func (s *stringObject) getIdx(idx valueInt, receiver Value) Value {
 	i := int64(idx)
 	if i >= 0 {
-		if i < s.length {
-			return s._getIdx(i)
+		if i < int64(s.length) {
+			return s._getIdx(int(i))
 		}
 		return nil
 	}
-	return s.baseObject.getStr(idx.String(), receiver)
+	return s.baseObject.getStr(idx.string(), receiver)
 }
 
-func (s *stringObject) getOwnPropStr(name string) Value {
-	if i := strToIdx64(name); i >= 0 && i < s.length {
+func (s *stringObject) getOwnPropStr(name unistring.String) Value {
+	if i := strToGoIdx(name); i >= 0 && i < s.length {
 		val := s._getIdx(i)
 		return &valueProperty{
 			value:      val,
@@ -125,8 +212,8 @@ func (s *stringObject) getOwnPropStr(name string) Value {
 func (s *stringObject) getOwnPropIdx(idx valueInt) Value {
 	i := int64(idx)
 	if i >= 0 {
-		if i < s.length {
-			val := s._getIdx(i)
+		if i < int64(s.length) {
+			val := s._getIdx(int(i))
 			return &valueProperty{
 				value:      val,
 				enumerable: true,
@@ -135,15 +222,15 @@ func (s *stringObject) getOwnPropIdx(idx valueInt) Value {
 		return nil
 	}
 
-	return s.baseObject.getOwnPropStr(idx.String())
+	return s.baseObject.getOwnPropStr(idx.string())
 }
 
-func (s *stringObject) _getIdx(idx int64) Value {
+func (s *stringObject) _getIdx(idx int) Value {
 	return s.value.substring(idx, idx+1)
 }
 
-func (s *stringObject) setOwnStr(name string, val Value, throw bool) bool {
-	if i := strToIdx64(name); i >= 0 && i < s.length {
+func (s *stringObject) setOwnStr(name unistring.String, val Value, throw bool) bool {
+	if i := strToGoIdx(name); i >= 0 && i < s.length {
 		s.val.runtime.typeErrorResult(throw, "Cannot assign to read only property '%d' of a String", i)
 		return false
 	}
@@ -153,15 +240,15 @@ func (s *stringObject) setOwnStr(name string, val Value, throw bool) bool {
 
 func (s *stringObject) setOwnIdx(idx valueInt, val Value, throw bool) bool {
 	i := int64(idx)
-	if i >= 0 && i < s.length {
+	if i >= 0 && i < int64(s.length) {
 		s.val.runtime.typeErrorResult(throw, "Cannot assign to read only property '%d' of a String", i)
 		return false
 	}
 
-	return s.baseObject.setOwnStr(idx.String(), val, throw)
+	return s.baseObject.setOwnStr(idx.string(), val, throw)
 }
 
-func (s *stringObject) setForeignStr(name string, val, receiver Value, throw bool) (bool, bool) {
+func (s *stringObject) setForeignStr(name unistring.String, val, receiver Value, throw bool) (bool, bool) {
 	return s._setForeignStr(name, s.getOwnPropStr(name), val, receiver, throw)
 }
 
@@ -169,8 +256,8 @@ func (s *stringObject) setForeignIdx(idx valueInt, val, receiver Value, throw bo
 	return s._setForeignIdx(idx, s.getOwnPropIdx(idx), val, receiver, throw)
 }
 
-func (s *stringObject) defineOwnPropertyStr(name string, descr PropertyDescriptor, throw bool) bool {
-	if i := strToIdx64(name); i >= 0 && i < s.length {
+func (s *stringObject) defineOwnPropertyStr(name unistring.String, descr PropertyDescriptor, throw bool) bool {
+	if i := strToGoIdx(name); i >= 0 && i < s.length {
 		s.val.runtime.typeErrorResult(throw, "Cannot redefine property: %d", i)
 		return false
 	}
@@ -180,25 +267,25 @@ func (s *stringObject) defineOwnPropertyStr(name string, descr PropertyDescripto
 
 func (s *stringObject) defineOwnPropertyIdx(idx valueInt, descr PropertyDescriptor, throw bool) bool {
 	i := int64(idx)
-	if i >= 0 && i < s.length {
+	if i >= 0 && i < int64(s.length) {
 		s.val.runtime.typeErrorResult(throw, "Cannot redefine property: %d", i)
 		return false
 	}
 
-	return s.baseObject.defineOwnPropertyStr(idx.String(), descr, throw)
+	return s.baseObject.defineOwnPropertyStr(idx.string(), descr, throw)
 }
 
 type stringPropIter struct {
 	str         valueString // separate, because obj can be the singleton
 	obj         *stringObject
-	idx, length int64
+	idx, length int
 }
 
 func (i *stringPropIter) next() (propIterItem, iterNextFunc) {
 	if i.idx < i.length {
-		name := strconv.FormatInt(i.idx, 10)
+		name := strconv.Itoa(i.idx)
 		i.idx++
-		return propIterItem{name: name, enumerable: _ENUM_TRUE}, i.next
+		return propIterItem{name: unistring.String(name), enumerable: _ENUM_TRUE}, i.next
 	}
 
 	return i.obj.baseObject.enumerateUnfiltered()()
@@ -213,15 +300,15 @@ func (s *stringObject) enumerateUnfiltered() iterNextFunc {
 }
 
 func (s *stringObject) ownKeys(all bool, accum []Value) []Value {
-	for i := int64(0); i < s.length; i++ {
-		accum = append(accum, asciiString(strconv.FormatInt(i, 10)))
+	for i := 0; i < s.length; i++ {
+		accum = append(accum, asciiString(strconv.Itoa(i)))
 	}
 
 	return s.baseObject.ownKeys(all, accum)
 }
 
-func (s *stringObject) deleteStr(name string, throw bool) bool {
-	if i := strToIdx64(name); i >= 0 && i < s.length {
+func (s *stringObject) deleteStr(name unistring.String, throw bool) bool {
+	if i := strToGoIdx(name); i >= 0 && i < s.length {
 		s.val.runtime.typeErrorResult(throw, "Cannot delete property '%d' of a String", i)
 		return false
 	}
@@ -231,16 +318,16 @@ func (s *stringObject) deleteStr(name string, throw bool) bool {
 
 func (s *stringObject) deleteIdx(idx valueInt, throw bool) bool {
 	i := int64(idx)
-	if i >= 0 && i < s.length {
+	if i >= 0 && i < int64(s.length) {
 		s.val.runtime.typeErrorResult(throw, "Cannot delete property '%d' of a String", i)
 		return false
 	}
 
-	return s.baseObject.deleteStr(idx.String(), throw)
+	return s.baseObject.deleteStr(idx.string(), throw)
 }
 
-func (s *stringObject) hasOwnPropertyStr(name string) bool {
-	if i := strToIdx64(name); i >= 0 && i < s.length {
+func (s *stringObject) hasOwnPropertyStr(name unistring.String) bool {
+	if i := strToGoIdx(name); i >= 0 && i < s.length {
 		return true
 	}
 	return s.baseObject.hasOwnPropertyStr(name)
@@ -248,8 +335,8 @@ func (s *stringObject) hasOwnPropertyStr(name string) bool {
 
 func (s *stringObject) hasOwnPropertyIdx(idx valueInt) bool {
 	i := int64(idx)
-	if i >= 0 && i < s.length {
+	if i >= 0 && i < int64(s.length) {
 		return true
 	}
-	return s.baseObject.hasOwnPropertyStr(idx.String())
+	return s.baseObject.hasOwnPropertyStr(idx.string())
 }

+ 25 - 19
string_ascii.go

@@ -2,12 +2,14 @@ package goja
 
 import (
 	"fmt"
-	"hash"
+	"hash/maphash"
 	"io"
 	"math"
 	"reflect"
 	"strconv"
 	"strings"
+
+	"github.com/dop251/goja/unistring"
 )
 
 type asciiString string
@@ -218,19 +220,19 @@ func (s asciiString) baseObject(r *Runtime) *Object {
 	return ss.val
 }
 
-func (s asciiString) hash(hash hash.Hash64) uint64 {
-	_, _ = hash.Write([]byte(s))
+func (s asciiString) hash(hash *maphash.Hash) uint64 {
+	_, _ = hash.WriteString(string(s))
 	h := hash.Sum64()
 	hash.Reset()
 	return h
 }
 
-func (s asciiString) charAt(idx int64) rune {
+func (s asciiString) charAt(idx int) rune {
 	return rune(s[idx])
 }
 
-func (s asciiString) length() int64 {
-	return int64(len(s))
+func (s asciiString) length() int {
+	return len(s)
 }
 
 func (s asciiString) concat(other valueString) valueString {
@@ -240,21 +242,21 @@ func (s asciiString) concat(other valueString) valueString {
 		copy(b, s)
 		copy(b[len(s):], other)
 		return asciiString(b)
-		//return asciiString(string(s) + string(other))
 	case unicodeString:
 		b := make([]uint16, len(s)+len(other))
+		b[0] = unistring.BOM
 		for i := 0; i < len(s); i++ {
-			b[i] = uint16(s[i])
+			b[i+1] = uint16(s[i])
 		}
-		copy(b[len(s):], other)
+		copy(b[len(s)+1:], other[1:])
 		return unicodeString(b)
 	default:
-		panic(fmt.Errorf("Unknown string type: %T", other))
+		panic(fmt.Errorf("unknown string type: %T", other))
 	}
 }
 
-func (s asciiString) substring(start, end int64) valueString {
-	return asciiString(s[start:end])
+func (s asciiString) substring(start, end int) valueString {
+	return s[start:end]
 }
 
 func (s asciiString) compareTo(other valueString) int {
@@ -264,13 +266,13 @@ func (s asciiString) compareTo(other valueString) int {
 	case unicodeString:
 		return strings.Compare(string(s), other.String())
 	default:
-		panic(fmt.Errorf("Unknown string type: %T", other))
+		panic(fmt.Errorf("unknown string type: %T", other))
 	}
 }
 
-func (s asciiString) index(substr valueString, start int64) int64 {
+func (s asciiString) index(substr valueString, start int) int {
 	if substr, ok := substr.(asciiString); ok {
-		p := int64(strings.Index(string(s[start:]), string(substr)))
+		p := strings.Index(string(s[start:]), string(substr))
 		if p >= 0 {
 			return p + start
 		}
@@ -278,16 +280,16 @@ func (s asciiString) index(substr valueString, start int64) int64 {
 	return -1
 }
 
-func (s asciiString) lastIndex(substr valueString, pos int64) int64 {
+func (s asciiString) lastIndex(substr valueString, pos int) int {
 	if substr, ok := substr.(asciiString); ok {
-		end := pos + int64(len(substr))
+		end := pos + len(substr)
 		var ss string
-		if end > int64(len(s)) {
+		if end > len(s) {
 			ss = string(s)
 		} else {
 			ss = string(s[:end])
 		}
-		return int64(strings.LastIndex(ss, string(substr)))
+		return strings.LastIndex(ss, string(substr))
 	}
 	return -1
 }
@@ -304,6 +306,10 @@ func (s asciiString) toTrimmedUTF8() string {
 	return strings.TrimSpace(string(s))
 }
 
+func (s asciiString) string() unistring.String {
+	return unistring.String(s)
+}
+
 func (s asciiString) Export() interface{} {
 	return string(s)
 }

+ 130 - 41
string_unicode.go

@@ -3,17 +3,18 @@ package goja
 import (
 	"errors"
 	"fmt"
-	"github.com/dop251/goja/parser"
-	"golang.org/x/text/cases"
-	"golang.org/x/text/language"
-	"hash"
+	"hash/maphash"
 	"io"
 	"math"
 	"reflect"
 	"strings"
 	"unicode/utf16"
 	"unicode/utf8"
-	"unsafe"
+
+	"github.com/dop251/goja/parser"
+	"github.com/dop251/goja/unistring"
+	"golang.org/x/text/cases"
+	"golang.org/x/text/language"
 )
 
 type unicodeString []uint16
@@ -27,6 +28,11 @@ type runeReaderReplace struct {
 	wrapped io.RuneReader
 }
 
+type unicodeStringBuilder struct {
+	buf     []uint16
+	unicode bool
+}
+
 var (
 	InvalidRuneError = errors.New("Invalid rune")
 )
@@ -43,33 +49,101 @@ func (rr runeReaderReplace) ReadRune() (r rune, size int, err error) {
 func (rr *unicodeRuneReader) ReadRune() (r rune, size int, err error) {
 	if rr.pos < len(rr.s) {
 		r = rune(rr.s[rr.pos])
-		if r != utf8.RuneError {
-			if utf16.IsSurrogate(r) {
-				if rr.pos+1 < len(rr.s) {
-					r1 := utf16.DecodeRune(r, rune(rr.s[rr.pos+1]))
+		size++
+		rr.pos++
+		if isUTF16FirstSurrogate(r) {
+			if rr.pos < len(rr.s) {
+				second := rune(rr.s[rr.pos])
+				if isUTF16SecondSurrogate(second) {
+					r = utf16.DecodeRune(r, second)
 					size++
 					rr.pos++
-					if r1 == utf8.RuneError {
-						err = InvalidRuneError
-					} else {
-						r = r1
-					}
 				} else {
 					err = InvalidRuneError
 				}
+			} else {
+				err = InvalidRuneError
 			}
+		} else if isUTF16SecondSurrogate(r) {
+			err = InvalidRuneError
 		}
-		size++
-		rr.pos++
 	} else {
 		err = io.EOF
 	}
 	return
 }
 
+func (b *unicodeStringBuilder) grow(n int) {
+	if cap(b.buf)-len(b.buf) < n {
+		buf := make([]uint16, len(b.buf), 2*cap(b.buf)+n)
+		copy(buf, b.buf)
+		b.buf = buf
+	}
+}
+
+func (b *unicodeStringBuilder) Grow(n int) {
+	b.grow(n + 1)
+}
+
+func (b *unicodeStringBuilder) ensureStarted(initialSize int) {
+	b.grow(len(b.buf) + initialSize + 1)
+	if len(b.buf) == 0 {
+		b.buf = append(b.buf, unistring.BOM)
+	}
+}
+
+func (b *unicodeStringBuilder) writeString(s valueString) {
+	b.ensureStarted(int(s.length()))
+	switch s := s.(type) {
+	case unicodeString:
+		b.buf = append(b.buf, s[1:]...)
+		b.unicode = true
+	case asciiString:
+		for i := 0; i < len(s); i++ {
+			b.buf = append(b.buf, uint16(s[i]))
+		}
+	default:
+		panic(fmt.Errorf("unsupported string type: %T", s))
+	}
+}
+
+func (b *unicodeStringBuilder) string() valueString {
+	if b.unicode {
+		return unicodeString(b.buf)
+	}
+	if len(b.buf) == 0 {
+		return stringEmpty
+	}
+	buf := make([]byte, 0, len(b.buf)-1)
+	for _, c := range b.buf[1:] {
+		buf = append(buf, byte(c))
+	}
+	return asciiString(buf)
+}
+
+func (b *unicodeStringBuilder) writeRune(r rune) {
+	if r <= 0xFFFF {
+		b.ensureStarted(1)
+		b.buf = append(b.buf, uint16(r))
+		b.unicode = r >= utf8.RuneSelf
+	} else {
+		b.ensureStarted(2)
+		first, second := utf16.EncodeRune(r)
+		b.buf = append(b.buf, uint16(first), uint16(second))
+		b.unicode = true
+	}
+}
+
+func (b *unicodeStringBuilder) writeASCII(bytes []byte) {
+	b.ensureStarted(len(bytes))
+	for _, c := range bytes {
+		b.buf = append(b.buf, uint16(c))
+	}
+}
+
 func (s unicodeString) reader(start int) io.RuneReader {
 	return &unicodeRuneReader{
-		s: s[start:],
+		s: s[start+1:],
 	}
 }
 
@@ -150,18 +224,21 @@ func (s unicodeString) baseObject(r *Runtime) *Object {
 	return ss.val
 }
 
-func (s unicodeString) charAt(idx int64) rune {
-	return rune(s[idx])
+func (s unicodeString) charAt(idx int) rune {
+	return rune(s[idx+1])
 }
 
-func (s unicodeString) length() int64 {
-	return int64(len(s))
+func (s unicodeString) length() int {
+	return len(s) - 1
 }
 
 func (s unicodeString) concat(other valueString) valueString {
 	switch other := other.(type) {
 	case unicodeString:
-		return unicodeString(append(s, other...))
+		b := make(unicodeString, len(s)+len(other)-1)
+		copy(b, s)
+		copy(b[len(s):], other[1:])
+		return b
 	case asciiString:
 		b := make([]uint16, len(s)+len(other))
 		copy(b, s)
@@ -175,11 +252,14 @@ func (s unicodeString) concat(other valueString) valueString {
 	}
 }
 
-func (s unicodeString) substring(start, end int64) valueString {
-	ss := s[start:end]
+func (s unicodeString) substring(start, end int) valueString {
+	ss := s[start+1 : end+1]
 	for _, c := range ss {
 		if c >= utf8.RuneSelf {
-			return unicodeString(ss)
+			b := make(unicodeString, end-start+1)
+			b[0] = unistring.BOM
+			copy(b[1:], ss)
+			return b
 		}
 	}
 	as := make([]byte, end-start)
@@ -190,32 +270,32 @@ func (s unicodeString) substring(start, end int64) valueString {
 }
 
 func (s unicodeString) String() string {
-	return string(utf16.Decode(s))
+	return string(utf16.Decode(s[1:]))
 }
 
 func (s unicodeString) compareTo(other valueString) int {
 	return strings.Compare(s.String(), other.String())
 }
 
-func (s unicodeString) index(substr valueString, start int64) int64 {
+func (s unicodeString) index(substr valueString, start int) int {
 	var ss []uint16
 	switch substr := substr.(type) {
 	case unicodeString:
-		ss = substr
+		ss = substr[1:]
 	case asciiString:
 		ss = make([]uint16, len(substr))
 		for i := 0; i < len(substr); i++ {
 			ss[i] = uint16(substr[i])
 		}
 	default:
-		panic(fmt.Errorf("Unknown string type: %T", substr))
+		panic(fmt.Errorf("unknown string type: %T", substr))
 	}
-
+	s1 := s[1:]
 	// TODO: optimise
-	end := int64(len(s) - len(ss))
+	end := len(s1) - len(ss)
 	for start <= end {
-		for i := int64(0); i < int64(len(ss)); i++ {
-			if s[start+i] != ss[i] {
+		for i := 0; i < len(ss); i++ {
+			if s1[start+i] != ss[i] {
 				goto nomatch
 			}
 		}
@@ -227,11 +307,11 @@ func (s unicodeString) index(substr valueString, start int64) int64 {
 	return -1
 }
 
-func (s unicodeString) lastIndex(substr valueString, start int64) int64 {
+func (s unicodeString) lastIndex(substr valueString, start int) int {
 	var ss []uint16
 	switch substr := substr.(type) {
 	case unicodeString:
-		ss = substr
+		ss = substr[1:]
 	case asciiString:
 		ss = make([]uint16, len(substr))
 		for i := 0; i < len(substr); i++ {
@@ -241,13 +321,14 @@ func (s unicodeString) lastIndex(substr valueString, start int64) int64 {
 		panic(fmt.Errorf("Unknown string type: %T", substr))
 	}
 
-	if maxStart := int64(len(s) - len(ss)); start > maxStart {
+	s1 := s[1:]
+	if maxStart := len(s1) - len(ss); start > maxStart {
 		start = maxStart
 	}
 	// TODO: optimise
 	for start >= 0 {
-		for i := int64(0); i < int64(len(ss)); i++ {
-			if s[start+i] != ss[i] {
+		for i := 0; i < len(ss); i++ {
+			if s1[start+i] != ss[i] {
 				goto nomatch
 			}
 		}
@@ -259,6 +340,10 @@ func (s unicodeString) lastIndex(substr valueString, start int64) int64 {
 	return -1
 }
 
+func unicodeStringFromRunes(r []rune) unicodeString {
+	return unistring.NewFromRunes(r).AsUtf16()
+}
+
 func (s unicodeString) toLower() valueString {
 	caser := cases.Lower(language.Und)
 	r := []rune(caser.String(s.String()))
@@ -279,7 +364,7 @@ func (s unicodeString) toLower() valueString {
 	if ascii {
 		return asciiString(r)
 	}
-	return unicodeString(utf16.Encode(r))
+	return unicodeStringFromRunes(r)
 }
 
 func (s unicodeString) toUpper() valueString {
@@ -295,9 +380,13 @@ func (s unicodeString) ExportType() reflect.Type {
 	return reflectTypeString
 }
 
-func (s unicodeString) hash(hash hash.Hash64) uint64 {
-	_, _ = hash.Write(*(*[]byte)(unsafe.Pointer(&s)))
+func (s unicodeString) hash(hash *maphash.Hash) uint64 {
+	_, _ = hash.WriteString(string(unistring.FromUtf16(s)))
 	h := hash.Sum64()
 	hash.Reset()
 	return h
 }
+
+func (s unicodeString) string() unistring.String {
+	return unistring.FromUtf16(s)
+}

+ 81 - 37
tc39_test.go

@@ -7,9 +7,11 @@ import (
 	"io/ioutil"
 	"os"
 	"path"
+	"sort"
 	"strings"
 	"sync"
 	"testing"
+	"time"
 )
 
 const (
@@ -19,7 +21,7 @@ const (
 var (
 	invalidFormatError = errors.New("Invalid file format")
 
-	ignorableTestError = &valueSymbol{}
+	ignorableTestError = newSymbol(stringEmpty)
 
 	sabStub = MustCompile("sabStub.js", `
 		Object.defineProperty(this, "SharedArrayBuffer", {
@@ -32,18 +34,11 @@ var (
 
 var (
 	skipList = map[string]bool{
-		"test/language/literals/regexp/S7.8.5_A1.1_T2.js":             true, // UTF-16
-		"test/language/literals/regexp/S7.8.5_A1.4_T2.js":             true, // UTF-16
-		"test/language/literals/regexp/S7.8.5_A2.1_T2.js":             true, // UTF-16
-		"test/language/literals/regexp/S7.8.5_A2.4_T2.js":             true, // UTF-16
 		"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}
 
-		// utf-16
-		"test/built-ins/Array/prototype/concat/Array.prototype.concat_spreadable-string-wrapper.js": true,
-
 		// class
 		"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,
@@ -63,12 +58,16 @@ var (
 		"test/language/statements/class/subclass/builtin-objects/TypedArray/regular-subclassing.js":       true,
 		"test/language/statements/class/subclass/builtin-objects/DataView/super-must-be-called.js":        true,
 		"test/language/statements/class/subclass/builtin-objects/DataView/regular-subclassing.js":         true,
+		"test/language/statements/class/subclass/builtin-objects/String/super-must-be-called.js":          true,
+		"test/language/statements/class/subclass/builtin-objects/String/regular-subclassing.js":           true,
+		"test/language/statements/class/subclass/builtin-objects/String/length.js":                        true,
 
 		// full unicode regexp flag
 		"test/built-ins/RegExp/prototype/Symbol.match/u-advance-after-empty.js":               true,
 		"test/built-ins/RegExp/prototype/Symbol.match/get-unicode-error.js":                   true,
 		"test/built-ins/RegExp/prototype/Symbol.match/builtin-success-u-return-val-groups.js": true,
 		"test/built-ins/RegExp/prototype/Symbol.match/builtin-infer-unicode.js":               true,
+		"test/built-ins/RegExp/unicode_identity_escape.js":                                    true,
 
 		// object literals
 		"test/built-ins/Array/from/source-object-iterator-1.js":                   true,
@@ -86,6 +85,12 @@ var (
 
 		// arrow-function
 		"test/built-ins/Object/prototype/toString/proxy-function.js": true,
+
+		// template strings
+		"test/built-ins/String/raw/zero-literal-segments.js":                             true,
+		"test/built-ins/String/raw/template-substitutions-are-appended-on-same-index.js": true,
+		"test/built-ins/String/raw/special-characters.js":                                true,
+		"test/built-ins/String/raw/return-the-string-value-from-template.js":             true,
 	}
 
 	featuresBlackList = []string{
@@ -101,9 +106,7 @@ var (
 		"12.9.4",
 		"19.1",
 		"19.4",
-		"21.1.3.14",
-		"21.1.3.15",
-		"21.1.3.17",
+		"21.1",
 		"21.2.5.6",
 		"22.1.2.1",
 		"22.1.2.3",
@@ -126,6 +129,7 @@ var (
 	esIdPrefixWhiteList = []string{
 		"sec-array.prototype.includes",
 		"sec-%typedarray%",
+		"sec-string.prototype",
 	}
 )
 
@@ -134,11 +138,21 @@ type tc39Test struct {
 	f    func(t *testing.T)
 }
 
+type tc39BenchmarkItem struct {
+	name     string
+	duration time.Duration
+}
+
+type tc39BenchmarkData []tc39BenchmarkItem
+
 type tc39TestCtx struct {
 	base         string
 	t            *testing.T
 	prgCache     map[string]*Program
 	prgCacheLock sync.Mutex
+	enableBench  bool
+	benchmark    tc39BenchmarkData
+	benchLock    sync.Mutex
 	testQueue    []tc39Test
 }
 
@@ -334,6 +348,11 @@ func (ctx *tc39TestCtx) runTC39File(name string, t testing.TB) {
 		}
 	}
 
+	var startTime time.Time
+	if ctx.enableBench {
+		startTime = time.Now()
+	}
+
 	hasRaw := meta.hasFlag("raw")
 
 	if hasRaw || !meta.hasFlag("onlyStrict") {
@@ -348,6 +367,15 @@ func (ctx *tc39TestCtx) runTC39File(name string, t testing.TB) {
 		ctx.runTC39Test(name, "'use strict';\n"+src, meta, t)
 	}
 
+	if ctx.enableBench {
+		ctx.benchLock.Lock()
+		ctx.benchmark = append(ctx.benchmark, tc39BenchmarkItem{
+			name:     name,
+			duration: time.Since(startTime),
+		})
+		ctx.benchLock.Unlock()
+	}
+
 }
 
 func (ctx *tc39TestCtx) init() {
@@ -459,32 +487,48 @@ func TestTC39(t *testing.T) {
 
 	ctx := &tc39TestCtx{
 		base: tc39BASE,
-		t:    t,
 	}
 	ctx.init()
-
-	//ctx.runTC39File("test/language/types/number/8.5.1.js", t)
-	//ctx.runTC39Tests("test/language")
-	ctx.runTC39Tests("test/language/expressions")
-	ctx.runTC39Tests("test/language/arguments-object")
-	ctx.runTC39Tests("test/language/asi")
-	ctx.runTC39Tests("test/language/directive-prologue")
-	ctx.runTC39Tests("test/language/function-code")
-	ctx.runTC39Tests("test/language/eval-code")
-	ctx.runTC39Tests("test/language/global-code")
-	ctx.runTC39Tests("test/language/identifier-resolution")
-	ctx.runTC39Tests("test/language/identifiers")
-	//ctx.runTC39Tests("test/language/literals") // octal sequences in strict mode
-	ctx.runTC39Tests("test/language/punctuators")
-	ctx.runTC39Tests("test/language/reserved-words")
-	ctx.runTC39Tests("test/language/source-text")
-	ctx.runTC39Tests("test/language/statements")
-	ctx.runTC39Tests("test/language/types")
-	ctx.runTC39Tests("test/language/white-space")
-	ctx.runTC39Tests("test/built-ins")
-	ctx.runTC39Tests("test/annexB/built-ins/String/prototype/substr")
-	ctx.runTC39Tests("test/annexB/built-ins/escape")
-	ctx.runTC39Tests("test/annexB/built-ins/unescape")
-
-	ctx.flush()
+	//ctx.enableBench = true
+
+	t.Run("tc39", func(t *testing.T) {
+		ctx.t = t
+		//ctx.runTC39File("test/language/types/number/8.5.1.js", t)
+		//ctx.runTC39Tests("test/language")
+		ctx.runTC39Tests("test/language/expressions")
+		ctx.runTC39Tests("test/language/arguments-object")
+		ctx.runTC39Tests("test/language/asi")
+		ctx.runTC39Tests("test/language/directive-prologue")
+		ctx.runTC39Tests("test/language/function-code")
+		ctx.runTC39Tests("test/language/eval-code")
+		ctx.runTC39Tests("test/language/global-code")
+		ctx.runTC39Tests("test/language/identifier-resolution")
+		ctx.runTC39Tests("test/language/identifiers")
+		//ctx.runTC39Tests("test/language/literals") // octal sequences in strict mode
+		ctx.runTC39Tests("test/language/punctuators")
+		ctx.runTC39Tests("test/language/reserved-words")
+		ctx.runTC39Tests("test/language/source-text")
+		ctx.runTC39Tests("test/language/statements")
+		ctx.runTC39Tests("test/language/types")
+		ctx.runTC39Tests("test/language/white-space")
+		ctx.runTC39Tests("test/built-ins")
+		ctx.runTC39Tests("test/annexB/built-ins/String/prototype/substr")
+		ctx.runTC39Tests("test/annexB/built-ins/escape")
+		ctx.runTC39Tests("test/annexB/built-ins/unescape")
+
+		ctx.flush()
+	})
+
+	if ctx.enableBench {
+		sort.Slice(ctx.benchmark, func(i, j int) bool {
+			return ctx.benchmark[i].duration > ctx.benchmark[j].duration
+		})
+		bench := ctx.benchmark
+		if len(bench) > 50 {
+			bench = bench[:50]
+		}
+		for _, item := range bench {
+			fmt.Printf("%s\t%d\n", item.name, item.duration/time.Millisecond)
+		}
+	}
 }

+ 13 - 11
typedarrays.go

@@ -6,6 +6,8 @@ import (
 	"reflect"
 	"strconv"
 	"unsafe"
+
+	"github.com/dop251/goja/unistring"
 )
 
 type byteOrder bool
@@ -458,15 +460,15 @@ func (a *typedArrayObject) _getIdx(idx int) Value {
 	return nil
 }
 
-func strToTAIdx(s string) (int, bool) {
-	i, err := strconv.ParseInt(s, 10, bits.UintSize)
+func strToTAIdx(s unistring.String) (int, bool) {
+	i, err := strconv.ParseInt(string(s), 10, bits.UintSize)
 	if err != nil {
 		return 0, false
 	}
 	return int(i), true
 }
 
-func (a *typedArrayObject) getOwnPropStr(name string) Value {
+func (a *typedArrayObject) getOwnPropStr(name unistring.String) Value {
 	if idx, ok := strToTAIdx(name); ok {
 		v := a._getIdx(idx)
 		if v != nil {
@@ -493,7 +495,7 @@ func (a *typedArrayObject) getOwnPropIdx(idx valueInt) Value {
 	return nil
 }
 
-func (a *typedArrayObject) getStr(name string, receiver Value) Value {
+func (a *typedArrayObject) getStr(name unistring.String, receiver Value) Value {
 	if idx, ok := strToTAIdx(name); ok {
 		prop := a._getIdx(idx)
 		if prop == nil {
@@ -538,7 +540,7 @@ func (a *typedArrayObject) _hasIdx(idx int) bool {
 	return idx >= 0 && idx < a.length
 }
 
-func (a *typedArrayObject) setOwnStr(p string, v Value, throw bool) bool {
+func (a *typedArrayObject) setOwnStr(p unistring.String, v Value, throw bool) bool {
 	if idx, ok := strToTAIdx(p); ok {
 		return a._putIdx(idx, v, throw)
 	}
@@ -549,7 +551,7 @@ func (a *typedArrayObject) setOwnIdx(p valueInt, v Value, throw bool) bool {
 	return a._putIdx(toInt(int64(p)), v, throw)
 }
 
-func (a *typedArrayObject) setForeignStr(p string, v, receiver Value, throw bool) (res bool, handled bool) {
+func (a *typedArrayObject) setForeignStr(p unistring.String, v, receiver Value, throw bool) (res bool, handled bool) {
 	return a._setForeignStr(p, a.getOwnPropStr(p), v, receiver, throw)
 }
 
@@ -557,7 +559,7 @@ func (a *typedArrayObject) setForeignIdx(p valueInt, v, receiver Value, throw bo
 	return a._setForeignIdx(p, trueValIfPresent(a.hasOwnPropertyIdx(p)), v, receiver, throw)
 }
 
-func (a *typedArrayObject) hasOwnPropertyStr(name string) bool {
+func (a *typedArrayObject) hasOwnPropertyStr(name unistring.String) bool {
 	if idx, ok := strToTAIdx(name); ok {
 		a.viewedArrayBuf.ensureNotDetached()
 		return idx < a.length
@@ -571,14 +573,14 @@ func (a *typedArrayObject) hasOwnPropertyIdx(idx valueInt) bool {
 }
 
 func (a *typedArrayObject) _defineIdxProperty(idx int, desc PropertyDescriptor, throw bool) bool {
-	prop, ok := a._defineOwnProperty(strconv.Itoa(idx), a.getOwnPropIdx(valueInt(idx)), desc, throw)
+	prop, ok := a._defineOwnProperty(unistring.String(strconv.Itoa(idx)), a.getOwnPropIdx(valueInt(idx)), desc, throw)
 	if ok {
 		return a._putIdx(idx, prop, throw)
 	}
 	return ok
 }
 
-func (a *typedArrayObject) defineOwnPropertyStr(name string, desc PropertyDescriptor, throw bool) bool {
+func (a *typedArrayObject) defineOwnPropertyStr(name unistring.String, desc PropertyDescriptor, throw bool) bool {
 	if idx, ok := strToTAIdx(name); ok {
 		return a._defineIdxProperty(idx, desc, throw)
 	}
@@ -589,7 +591,7 @@ func (a *typedArrayObject) defineOwnPropertyIdx(name valueInt, desc PropertyDesc
 	return a._defineIdxProperty(toInt(int64(name)), desc, throw)
 }
 
-func (a *typedArrayObject) deleteStr(name string, throw bool) bool {
+func (a *typedArrayObject) deleteStr(name unistring.String, throw bool) bool {
 	if idx, ok := strToTAIdx(name); ok {
 		if idx < a.length {
 			a.val.runtime.typeErrorResult(throw, "Cannot delete property '%d' of %s", idx, a.val.String())
@@ -627,7 +629,7 @@ func (i *typedArrayPropIter) next() (propIterItem, iterNextFunc) {
 		name := strconv.Itoa(i.idx)
 		prop := i.a._getIdx(i.idx)
 		i.idx++
-		return propIterItem{name: name, value: prop}, i.next
+		return propIterItem{name: unistring.String(name), value: prop}, i.next
 	}
 
 	return i.a.baseObject.enumerateUnfiltered()()

+ 122 - 0
unistring/string.go

@@ -0,0 +1,122 @@
+// Package unistring contains an implementation of a hybrid ASCII/UTF-16 string.
+// For ASCII strings the underlying representation is equivalent to a normal Go string.
+// For unicode strings the underlying representation is UTF-16 as []uint16 with 0th element set to 0xFEFF.
+// unicode.String allows representing malformed UTF-16 values (e.g. stand-alone parts of surrogate pairs)
+// which cannot be represented in UTF-8.
+// At the same time it is possible to use unicode.String as property keys just as efficiently as simple strings,
+// (the leading 0xFEFF ensures there is no clash with ASCII string), and it is possible to convert it
+// to valueString without extra allocations.
+package unistring
+
+import (
+	"reflect"
+	"unicode/utf16"
+	"unicode/utf8"
+	"unsafe"
+)
+
+const (
+	BOM = 0xFEFF
+)
+
+type String string
+
+func NewFromString(s string) String {
+	ascii := true
+	size := 0
+	for _, c := range s {
+		if c >= utf8.RuneSelf {
+			ascii = false
+			if c > 0xFFFF {
+				size++
+			}
+		}
+		size++
+	}
+	if ascii {
+		return String(s)
+	}
+	b := make([]uint16, size+1)
+	b[0] = BOM
+	i := 1
+	for _, c := range s {
+		if c <= 0xFFFF {
+			b[i] = uint16(c)
+		} else {
+			first, second := utf16.EncodeRune(c)
+			b[i] = uint16(first)
+			i++
+			b[i] = uint16(second)
+		}
+		i++
+	}
+	return FromUtf16(b)
+}
+
+func NewFromRunes(s []rune) String {
+	ascii := true
+	size := 0
+	for _, c := range s {
+		if c >= utf8.RuneSelf {
+			ascii = false
+			if c > 0xFFFF {
+				size++
+			}
+		}
+		size++
+	}
+	if ascii {
+		return String(s)
+	}
+	b := make([]uint16, size+1)
+	b[0] = BOM
+	i := 1
+	for _, c := range s {
+		if c <= 0xFFFF {
+			b[i] = uint16(c)
+		} else {
+			first, second := utf16.EncodeRune(c)
+			b[i] = uint16(first)
+			i++
+			b[i] = uint16(second)
+		}
+		i++
+	}
+	return FromUtf16(b)
+}
+
+func FromUtf16(b []uint16) String {
+	var str string
+	hdr := (*reflect.StringHeader)(unsafe.Pointer(&str))
+	hdr.Data = uintptr(unsafe.Pointer(&b[0]))
+	hdr.Len = len(b) * 2
+
+	return String(str)
+}
+
+func (s String) String() string {
+	if b := s.AsUtf16(); b != nil {
+		return string(utf16.Decode(b[1:]))
+	}
+
+	return string(s)
+}
+
+func (s String) AsUtf16() []uint16 {
+	if len(s) < 4 || len(s)&1 != 0 {
+		return nil
+	}
+	l := len(s) / 2
+	raw := string(s)
+	hdr := (*reflect.StringHeader)(unsafe.Pointer(&raw))
+	a := *(*[]uint16)(unsafe.Pointer(&reflect.SliceHeader{
+		Data: hdr.Data,
+		Len:  l,
+		Cap:  l,
+	}))
+	if a[0] == BOM {
+		return a
+	}
+
+	return nil
+}

+ 89 - 48
value.go

@@ -1,13 +1,14 @@
 package goja
 
 import (
-	"fmt"
-	"hash"
+	"hash/maphash"
 	"math"
 	"reflect"
 	"regexp"
 	"strconv"
 	"unsafe"
+
+	"github.com/dop251/goja/unistring"
 )
 
 var (
@@ -39,6 +40,7 @@ var intCache [256]Value
 type Value interface {
 	ToInteger() int64
 	toString() valueString
+	string() unistring.String
 	ToPrimitiveString() Value
 	String() string
 	ToFloat() float64
@@ -53,7 +55,7 @@ type Value interface {
 
 	baseObject(r *Runtime) *Object
 
-	hash(hash64 hash.Hash64) uint64
+	hash(hasher *maphash.Hash) uint64
 }
 
 type valueContainer interface {
@@ -71,12 +73,12 @@ type valueUndefined struct {
 	valueNull
 }
 type valueSymbol struct {
-	desc string
+	desc valueString
 }
 
 type valueUnresolved struct {
 	r   *Runtime
-	ref string
+	ref unistring.String
 }
 
 type memberUnresolved struct {
@@ -127,6 +129,10 @@ func (i valueInt) toString() valueString {
 	return asciiString(i.String())
 }
 
+func (i valueInt) string() unistring.String {
+	return unistring.String(i.String())
+}
+
 func (i valueInt) ToPrimitiveString() Value {
 	return i
 }
@@ -195,60 +201,64 @@ func (i valueInt) ExportType() reflect.Type {
 	return reflectTypeInt
 }
 
-func (i valueInt) hash(hash.Hash64) uint64 {
+func (i valueInt) hash(*maphash.Hash) uint64 {
 	return uint64(i)
 }
 
-func (o valueBool) ToInteger() int64 {
-	if o {
+func (b valueBool) ToInteger() int64 {
+	if b {
 		return 1
 	}
 	return 0
 }
 
-func (o valueBool) toString() valueString {
-	if o {
+func (b valueBool) toString() valueString {
+	if b {
 		return stringTrue
 	}
 	return stringFalse
 }
 
-func (o valueBool) ToPrimitiveString() Value {
-	return o
+func (b valueBool) ToPrimitiveString() Value {
+	return b
 }
 
-func (o valueBool) String() string {
-	if o {
+func (b valueBool) String() string {
+	if b {
 		return "true"
 	}
 	return "false"
 }
 
-func (o valueBool) ToFloat() float64 {
-	if o {
+func (b valueBool) string() unistring.String {
+	return unistring.String(b.String())
+}
+
+func (b valueBool) ToFloat() float64 {
+	if b {
 		return 1.0
 	}
 	return 0
 }
 
-func (o valueBool) ToBoolean() bool {
-	return bool(o)
+func (b valueBool) ToBoolean() bool {
+	return bool(b)
 }
 
-func (o valueBool) ToObject(r *Runtime) *Object {
-	return r.newPrimitiveObject(o, r.global.BooleanPrototype, "Boolean")
+func (b valueBool) ToObject(r *Runtime) *Object {
+	return r.newPrimitiveObject(b, r.global.BooleanPrototype, "Boolean")
 }
 
-func (o valueBool) ToNumber() Value {
-	if o {
+func (b valueBool) ToNumber() Value {
+	if b {
 		return valueInt(1)
 	}
 	return valueInt(0)
 }
 
-func (o valueBool) SameAs(other Value) bool {
+func (b valueBool) SameAs(other Value) bool {
 	if other, ok := other.(valueBool); ok {
-		return o == other
+		return b == other
 	}
 	return false
 }
@@ -266,26 +276,26 @@ func (b valueBool) Equals(other Value) bool {
 
 }
 
-func (o valueBool) StrictEquals(other Value) bool {
+func (b valueBool) StrictEquals(other Value) bool {
 	if other, ok := other.(valueBool); ok {
-		return o == other
+		return b == other
 	}
 	return false
 }
 
-func (o valueBool) baseObject(r *Runtime) *Object {
+func (b valueBool) baseObject(r *Runtime) *Object {
 	return r.global.BooleanPrototype
 }
 
-func (o valueBool) Export() interface{} {
-	return bool(o)
+func (b valueBool) Export() interface{} {
+	return bool(b)
 }
 
-func (o valueBool) ExportType() reflect.Type {
+func (b valueBool) ExportType() reflect.Type {
 	return reflectTypeBool
 }
 
-func (b valueBool) hash(hash.Hash64) uint64 {
+func (b valueBool) hash(*maphash.Hash) uint64 {
 	if b {
 		return uint64(uintptr(unsafe.Pointer(&valueTrue)))
 	}
@@ -300,6 +310,10 @@ func (n valueNull) toString() valueString {
 	return stringNull
 }
 
+func (n valueNull) string() unistring.String {
+	return stringNull.string()
+}
+
 func (n valueNull) ToPrimitiveString() Value {
 	return n
 }
@@ -320,6 +334,10 @@ func (u valueUndefined) String() string {
 	return "undefined"
 }
 
+func (u valueUndefined) string() unistring.String {
+	return "undefined"
+}
+
 func (u valueUndefined) ToNumber() Value {
 	return _NaN
 }
@@ -338,7 +356,7 @@ func (u valueUndefined) ToFloat() float64 {
 	return math.NaN()
 }
 
-func (u valueUndefined) hash(hash.Hash64) uint64 {
+func (u valueUndefined) hash(*maphash.Hash) uint64 {
 	return uint64(uintptr(unsafe.Pointer(&_undefined)))
 }
 
@@ -390,7 +408,7 @@ func (n valueNull) ExportType() reflect.Type {
 	return reflectTypeNil
 }
 
-func (n valueNull) hash(hash.Hash64) uint64 {
+func (n valueNull) hash(*maphash.Hash) uint64 {
 	return uint64(uintptr(unsafe.Pointer(&_null)))
 }
 
@@ -402,6 +420,10 @@ func (p *valueProperty) toString() valueString {
 	return stringEmpty
 }
 
+func (p *valueProperty) string() unistring.String {
+	return ""
+}
+
 func (p *valueProperty) ToPrimitiveString() Value {
 	return _undefined
 }
@@ -470,20 +492,20 @@ func (p *valueProperty) StrictEquals(Value) bool {
 	return false
 }
 
-func (n *valueProperty) baseObject(r *Runtime) *Object {
+func (p *valueProperty) baseObject(r *Runtime) *Object {
 	r.typeErrorResult(true, "BUG: baseObject() is called on valueProperty") // TODO error message
 	return nil
 }
 
-func (n *valueProperty) Export() interface{} {
+func (p *valueProperty) Export() interface{} {
 	panic("Cannot export valueProperty")
 }
 
-func (n *valueProperty) ExportType() reflect.Type {
+func (p *valueProperty) ExportType() reflect.Type {
 	panic("Cannot export valueProperty")
 }
 
-func (n *valueProperty) hash(hash.Hash64) uint64 {
+func (p *valueProperty) hash(*maphash.Hash) uint64 {
 	panic("valueProperty should never be used in maps or sets")
 }
 
@@ -503,6 +525,10 @@ func (f valueFloat) toString() valueString {
 	return asciiString(f.String())
 }
 
+func (f valueFloat) string() unistring.String {
+	return unistring.String(f.String())
+}
+
 func (f valueFloat) ToPrimitiveString() Value {
 	return f
 }
@@ -608,7 +634,7 @@ func (f valueFloat) ExportType() reflect.Type {
 	return reflectTypeFloat
 }
 
-func (f valueFloat) hash(hash.Hash64) uint64 {
+func (f valueFloat) hash(*maphash.Hash) uint64 {
 	if f == _negativeZero {
 		return 0
 	}
@@ -623,6 +649,10 @@ func (o *Object) toString() valueString {
 	return o.self.toPrimitiveString().toString()
 }
 
+func (o *Object) string() unistring.String {
+	return o.self.toPrimitiveString().string()
+}
+
 func (o *Object) ToPrimitiveString() Value {
 	return o.self.toPrimitiveString().ToPrimitiveString()
 }
@@ -688,12 +718,12 @@ func (o *Object) ExportType() reflect.Type {
 	return o.self.exportType()
 }
 
-func (o *Object) hash(hash.Hash64) uint64 {
+func (o *Object) hash(*maphash.Hash) uint64 {
 	return uint64(uintptr(unsafe.Pointer(o)))
 }
 
 func (o *Object) Get(name string) Value {
-	return o.self.getStr(name, nil)
+	return o.self.getStr(unistring.NewFromString(name), nil)
 }
 
 func (o *Object) Keys() (keys []string) {
@@ -710,7 +740,7 @@ func (o *Object) Keys() (keys []string) {
 // configurable: configurable, enumerable: enumerable})
 func (o *Object) DefineDataProperty(name string, value Value, writable, configurable, enumerable Flag) error {
 	return tryFunc(func() {
-		o.self.defineOwnPropertyStr(name, PropertyDescriptor{
+		o.self.defineOwnPropertyStr(unistring.NewFromString(name), PropertyDescriptor{
 			Value:        value,
 			Writable:     writable,
 			Configurable: configurable,
@@ -723,7 +753,7 @@ func (o *Object) DefineDataProperty(name string, value Value, writable, configur
 // configurable: configurable, enumerable: enumerable})
 func (o *Object) DefineAccessorProperty(name string, getter, setter Value, configurable, enumerable Flag) error {
 	return tryFunc(func() {
-		o.self.defineOwnPropertyStr(name, PropertyDescriptor{
+		o.self.defineOwnPropertyStr(unistring.NewFromString(name), PropertyDescriptor{
 			Getter:       getter,
 			Setter:       setter,
 			Configurable: configurable,
@@ -734,7 +764,7 @@ func (o *Object) DefineAccessorProperty(name string, getter, setter Value, confi
 
 func (o *Object) Set(name string, value interface{}) error {
 	return tryFunc(func() {
-		o.self.setOwnStr(name, o.runtime.ToValue(value), true)
+		o.self.setOwnStr(unistring.NewFromString(name), o.runtime.ToValue(value), true)
 	})
 }
 
@@ -774,6 +804,11 @@ func (o valueUnresolved) toString() valueString {
 	return nil
 }
 
+func (o valueUnresolved) string() unistring.String {
+	o.throw()
+	return ""
+}
+
 func (o valueUnresolved) ToPrimitiveString() Value {
 	o.throw()
 	return nil
@@ -834,7 +869,7 @@ func (o valueUnresolved) ExportType() reflect.Type {
 	return nil
 }
 
-func (o valueUnresolved) hash(hash.Hash64) uint64 {
+func (o valueUnresolved) hash(*maphash.Hash) uint64 {
 	o.throw()
 	return 0
 }
@@ -852,7 +887,11 @@ func (s *valueSymbol) ToPrimitiveString() Value {
 }
 
 func (s *valueSymbol) String() string {
-	return s.descString()
+	return s.desc.String()
+}
+
+func (s *valueSymbol) string() unistring.String {
+	return s.desc.string()
 }
 
 func (s *valueSymbol) ToFloat() float64 {
@@ -898,12 +937,14 @@ func (s *valueSymbol) baseObject(r *Runtime) *Object {
 	return r.newPrimitiveObject(s, r.global.SymbolPrototype, "Symbol")
 }
 
-func (s *valueSymbol) hash(hash.Hash64) uint64 {
+func (s *valueSymbol) hash(*maphash.Hash) uint64 {
 	return uint64(uintptr(unsafe.Pointer(s)))
 }
 
-func (s *valueSymbol) descString() string {
-	return fmt.Sprintf("Symbol(%s)", s.desc)
+func newSymbol(s valueString) *valueSymbol {
+	return &valueSymbol{
+		desc: asciiString("Symbol(").concat(s).concat(asciiString(")")),
+	}
 }
 
 func init() {

+ 75 - 71
vm.go

@@ -7,6 +7,8 @@ import (
 	"strconv"
 	"sync"
 	"sync/atomic"
+
+	"github.com/dop251/goja/unistring"
 )
 
 const (
@@ -18,7 +20,7 @@ type valueStack []Value
 type stash struct {
 	values    valueStack
 	extraArgs valueStack
-	names     map[string]uint32
+	names     map[unistring.String]uint32
 	obj       objectImpl
 
 	outer *stash
@@ -26,7 +28,7 @@ type stash struct {
 
 type context struct {
 	prg       *Program
-	funcName  string
+	funcName  unistring.String
 	stash     *stash
 	newTarget Value
 	pc, sb    int
@@ -42,12 +44,12 @@ type iterStackItem struct {
 type ref interface {
 	get() Value
 	set(Value)
-	refname() string
+	refname() unistring.String
 }
 
 type stashRef struct {
 	v *Value
-	n string
+	n unistring.String
 }
 
 func (r stashRef) get() Value {
@@ -58,13 +60,13 @@ func (r *stashRef) set(v Value) {
 	*r.v = v
 }
 
-func (r *stashRef) refname() string {
+func (r *stashRef) refname() unistring.String {
 	return r.n
 }
 
 type objRef struct {
 	base   objectImpl
-	name   string
+	name   unistring.String
 	strict bool
 }
 
@@ -76,13 +78,13 @@ func (r *objRef) set(v Value) {
 	r.base.setOwnStr(r.name, v, r.strict)
 }
 
-func (r *objRef) refname() string {
+func (r *objRef) refname() unistring.String {
 	return r.name
 }
 
 type unresolvedRef struct {
 	runtime *Runtime
-	name    string
+	name    unistring.String
 }
 
 func (r *unresolvedRef) get() Value {
@@ -94,14 +96,14 @@ func (r *unresolvedRef) set(Value) {
 	r.get()
 }
 
-func (r *unresolvedRef) refname() string {
+func (r *unresolvedRef) refname() unistring.String {
 	return r.name
 }
 
 type vm struct {
 	r            *Runtime
 	prg          *Program
-	funcName     string
+	funcName     unistring.String
 	pc           int
 	stack        valueStack
 	sp, sb, args int
@@ -201,7 +203,7 @@ func (s *valueStack) expand(idx int) {
 	}
 }
 
-func stashObjHas(obj objectImpl, name string) bool {
+func stashObjHas(obj objectImpl, name unistring.String) bool {
 	if obj.hasPropertyStr(name) {
 		if unscopables, ok := obj.getSym(symUnscopables, nil).(*Object); ok {
 			if b := unscopables.self.getStr(name, nil); b != nil {
@@ -213,7 +215,7 @@ func stashObjHas(obj objectImpl, name string) bool {
 	return false
 }
 
-func (s *stash) put(name string, v Value) bool {
+func (s *stash) put(name unistring.String, v Value) bool {
 	if s.obj != nil {
 		if stashObjHas(s.obj, name) {
 			s.obj.setOwnStr(name, v, false)
@@ -245,7 +247,7 @@ func (s *stash) getByIdx(idx uint32) Value {
 	return _undefined
 }
 
-func (s *stash) getByName(name string, _ *vm) (v Value, exists bool) {
+func (s *stash) getByName(name unistring.String, _ *vm) (v Value, exists bool) {
 	if s.obj != nil {
 		if stashObjHas(s.obj, name) {
 			return nilSafe(s.obj.getStr(name, nil)), true
@@ -259,9 +261,9 @@ func (s *stash) getByName(name string, _ *vm) (v Value, exists bool) {
 	//return valueUnresolved{r: vm.r, ref: name}, false
 }
 
-func (s *stash) createBinding(name string) {
+func (s *stash) createBinding(name unistring.String) {
 	if s.names == nil {
-		s.names = make(map[string]uint32)
+		s.names = make(map[unistring.String]uint32)
 	}
 	if _, exists := s.names[name]; !exists {
 		s.names[name] = uint32(len(s.names))
@@ -269,7 +271,7 @@ func (s *stash) createBinding(name string) {
 	}
 }
 
-func (s *stash) deleteBinding(name string) bool {
+func (s *stash) deleteBinding(name unistring.String) bool {
 	if s.obj != nil {
 		if stashObjHas(s.obj, name) {
 			return s.obj.deleteStr(name, false)
@@ -1034,11 +1036,11 @@ func (_deleteElemStrict) exec(vm *vm) {
 	vm.pc++
 }
 
-type deleteProp string
+type deleteProp unistring.String
 
 func (d deleteProp) exec(vm *vm) {
 	obj := vm.r.toObject(vm.stack[vm.sp-1])
-	if obj.self.deleteStr(string(d), false) {
+	if obj.self.deleteStr(unistring.String(d), false) {
 		vm.stack[vm.sp-1] = valueTrue
 	} else {
 		vm.stack[vm.sp-1] = valueFalse
@@ -1046,42 +1048,42 @@ func (d deleteProp) exec(vm *vm) {
 	vm.pc++
 }
 
-type deletePropStrict string
+type deletePropStrict unistring.String
 
 func (d deletePropStrict) exec(vm *vm) {
 	obj := vm.r.toObject(vm.stack[vm.sp-1])
-	obj.self.deleteStr(string(d), true)
+	obj.self.deleteStr(unistring.String(d), true)
 	vm.stack[vm.sp-1] = valueTrue
 	vm.pc++
 }
 
-type setProp string
+type setProp unistring.String
 
 func (p setProp) exec(vm *vm) {
 	val := vm.stack[vm.sp-1]
-	vm.stack[vm.sp-2].ToObject(vm.r).self.setOwnStr(string(p), val, false)
+	vm.stack[vm.sp-2].ToObject(vm.r).self.setOwnStr(unistring.String(p), val, false)
 	vm.stack[vm.sp-2] = val
 	vm.sp--
 	vm.pc++
 }
 
-type setPropStrict string
+type setPropStrict unistring.String
 
 func (p setPropStrict) exec(vm *vm) {
 	obj := vm.stack[vm.sp-2]
 	val := vm.stack[vm.sp-1]
 
 	obj1 := vm.r.toObject(obj)
-	obj1.self.setOwnStr(string(p), val, true)
+	obj1.self.setOwnStr(unistring.String(p), val, true)
 	vm.stack[vm.sp-2] = val
 	vm.sp--
 	vm.pc++
 }
 
-type setProp1 string
+type setProp1 unistring.String
 
 func (p setProp1) exec(vm *vm) {
-	vm.r.toObject(vm.stack[vm.sp-2]).self._putProp(string(p), vm.stack[vm.sp-1], true, true, true)
+	vm.r.toObject(vm.stack[vm.sp-2]).self._putProp(unistring.String(p), vm.stack[vm.sp-1], true, true, true)
 
 	vm.sp--
 	vm.pc++
@@ -1098,7 +1100,7 @@ func (_setProto) exec(vm *vm) {
 	vm.pc++
 }
 
-type setPropGetter string
+type setPropGetter unistring.String
 
 func (s setPropGetter) exec(vm *vm) {
 	obj := vm.r.toObject(vm.stack[vm.sp-2])
@@ -1110,13 +1112,13 @@ func (s setPropGetter) exec(vm *vm) {
 		Enumerable:   FLAG_TRUE,
 	}
 
-	obj.self.defineOwnPropertyStr(string(s), descr, false)
+	obj.self.defineOwnPropertyStr(unistring.String(s), descr, false)
 
 	vm.sp--
 	vm.pc++
 }
 
-type setPropSetter string
+type setPropSetter unistring.String
 
 func (s setPropSetter) exec(vm *vm) {
 	obj := vm.r.toObject(vm.stack[vm.sp-2])
@@ -1128,13 +1130,13 @@ func (s setPropSetter) exec(vm *vm) {
 		Enumerable:   FLAG_TRUE,
 	}
 
-	obj.self.defineOwnPropertyStr(string(s), descr, false)
+	obj.self.defineOwnPropertyStr(unistring.String(s), descr, false)
 
 	vm.sp--
 	vm.pc++
 }
 
-type getProp string
+type getProp unistring.String
 
 func (g getProp) exec(vm *vm) {
 	v := vm.stack[vm.sp-1]
@@ -1142,22 +1144,23 @@ func (g getProp) exec(vm *vm) {
 	if obj == nil {
 		panic(vm.r.NewTypeError("Cannot read property '%s' of undefined", g))
 	}
-	vm.stack[vm.sp-1] = nilSafe(obj.self.getStr(string(g), v))
+	vm.stack[vm.sp-1] = nilSafe(obj.self.getStr(unistring.String(g), v))
 
 	vm.pc++
 }
 
-type getPropCallee string
+type getPropCallee unistring.String
 
 func (g getPropCallee) exec(vm *vm) {
 	v := vm.stack[vm.sp-1]
 	obj := v.baseObject(vm.r)
+	n := unistring.String(g)
 	if obj == nil {
-		panic(vm.r.NewTypeError("Cannot read property '%s' of undefined or null", g))
+		panic(vm.r.NewTypeError("Cannot read property '%s' of undefined or null", n))
 	}
-	prop := obj.self.getStr(string(g), v)
+	prop := obj.self.getStr(n, v)
 	if prop == nil {
-		prop = memberUnresolved{valueUnresolved{r: vm.r, ref: string(g)}}
+		prop = memberUnresolved{valueUnresolved{r: vm.r, ref: n}}
 	}
 	vm.stack[vm.sp-1] = prop
 
@@ -1196,7 +1199,7 @@ func (_getElemCallee) exec(vm *vm) {
 
 	prop := obj.get(propName, v)
 	if prop == nil {
-		prop = memberUnresolved{valueUnresolved{r: vm.r, ref: propName.String()}}
+		prop = memberUnresolved{valueUnresolved{r: vm.r, ref: propName.string()}}
 	}
 	vm.stack[vm.sp-2] = prop
 
@@ -1306,7 +1309,7 @@ func (s setLocalP) exec(vm *vm) {
 }
 
 type setVar struct {
-	name string
+	name unistring.String
 	idx  uint32
 }
 
@@ -1334,10 +1337,10 @@ end:
 	vm.pc++
 }
 
-type resolveVar1 string
+type resolveVar1 unistring.String
 
 func (s resolveVar1) exec(vm *vm) {
-	name := string(s)
+	name := unistring.String(s)
 	var ref ref
 	for stash := vm.stash; stash != nil; stash = stash.outer {
 		if stash.obj != nil {
@@ -1368,10 +1371,10 @@ end:
 	vm.pc++
 }
 
-type deleteVar string
+type deleteVar unistring.String
 
 func (d deleteVar) exec(vm *vm) {
-	name := string(d)
+	name := unistring.String(d)
 	ret := true
 	for stash := vm.stash; stash != nil; stash = stash.outer {
 		if stash.obj != nil {
@@ -1400,10 +1403,10 @@ end:
 	vm.pc++
 }
 
-type deleteGlobal string
+type deleteGlobal unistring.String
 
 func (d deleteGlobal) exec(vm *vm) {
-	name := string(d)
+	name := unistring.String(d)
 	var ret bool
 	if vm.r.globalObject.self.hasPropertyStr(name) {
 		ret = vm.r.globalObject.self.deleteStr(name, false)
@@ -1418,10 +1421,10 @@ func (d deleteGlobal) exec(vm *vm) {
 	vm.pc++
 }
 
-type resolveVar1Strict string
+type resolveVar1Strict unistring.String
 
 func (s resolveVar1Strict) exec(vm *vm) {
-	name := string(s)
+	name := unistring.String(s)
 	var ref ref
 	for stash := vm.stash; stash != nil; stash = stash.outer {
 		if stash.obj != nil {
@@ -1454,7 +1457,7 @@ func (s resolveVar1Strict) exec(vm *vm) {
 
 	ref = &unresolvedRef{
 		runtime: vm.r,
-		name:    string(s),
+		name:    name,
 	}
 
 end:
@@ -1462,21 +1465,21 @@ end:
 	vm.pc++
 }
 
-type setGlobal string
+type setGlobal unistring.String
 
 func (s setGlobal) exec(vm *vm) {
 	v := vm.peek()
 
-	vm.r.globalObject.self.setOwnStr(string(s), v, false)
+	vm.r.globalObject.self.setOwnStr(unistring.String(s), v, false)
 	vm.pc++
 }
 
-type setGlobalStrict string
+type setGlobalStrict unistring.String
 
 func (s setGlobalStrict) exec(vm *vm) {
 	v := vm.peek()
 
-	name := string(s)
+	name := unistring.String(s)
 	o := vm.r.globalObject.self
 	if o.hasOwnPropertyStr(name) {
 		o.setOwnStr(name, v, true)
@@ -1501,14 +1504,14 @@ func (g getLocal) exec(vm *vm) {
 }
 
 type getVar struct {
-	name string
+	name unistring.String
 	idx  uint32
 	ref  bool
 }
 
 func (g getVar) exec(vm *vm) {
 	level := int(g.idx >> 24)
-	idx := uint32(g.idx & 0x00FFFFFF)
+	idx := g.idx & 0x00FFFFFF
 	stash := vm.stash
 	name := g.name
 	for i := 0; i < level; i++ {
@@ -1536,7 +1539,7 @@ end:
 }
 
 type resolveVar struct {
-	name   string
+	name   unistring.String
 	idx    uint32
 	strict bool
 }
@@ -1620,10 +1623,10 @@ func (_putValue) exec(vm *vm) {
 	vm.pc++
 }
 
-type getVar1 string
+type getVar1 unistring.String
 
 func (n getVar1) exec(vm *vm) {
-	name := string(n)
+	name := unistring.String(n)
 	var val Value
 	for stash := vm.stash; stash != nil; stash = stash.outer {
 		if v, exists := stash.getByName(name, vm); exists {
@@ -1641,10 +1644,10 @@ func (n getVar1) exec(vm *vm) {
 	vm.pc++
 }
 
-type getVar1Callee string
+type getVar1Callee unistring.String
 
 func (n getVar1Callee) exec(vm *vm) {
-	name := string(n)
+	name := unistring.String(n)
 	var val Value
 	for stash := vm.stash; stash != nil; stash = stash.outer {
 		if v, exists := stash.getByName(name, vm); exists {
@@ -1774,7 +1777,7 @@ func (vm *vm) _nativeCall(f *nativeFuncObject, n int) {
 	if f.f != nil {
 		vm.pushCtx()
 		vm.prg = nil
-		vm.funcName = f.nameProp.get(nil).String()
+		vm.funcName = f.nameProp.get(nil).string()
 		ret := f.f(FunctionCall{
 			Arguments: vm.stack[vm.sp-n : vm.sp],
 			This:      vm.stack[vm.sp-n-2],
@@ -1895,7 +1898,7 @@ func (_retStashless) exec(vm *vm) {
 
 type newFunc struct {
 	prg    *Program
-	name   string
+	name   unistring.String
 	length uint32
 	strict bool
 
@@ -1911,13 +1914,14 @@ func (n *newFunc) exec(vm *vm) {
 	vm.pc++
 }
 
-type bindName string
+type bindName unistring.String
 
 func (d bindName) exec(vm *vm) {
+	name := unistring.String(d)
 	if vm.stash != nil {
-		vm.stash.createBinding(string(d))
+		vm.stash.createBinding(name)
 	} else {
-		vm.r.globalObject.self._putProp(string(d), _undefined, true, true, false)
+		vm.r.globalObject.self._putProp(name, _undefined, true, true, false)
 	}
 	vm.pc++
 }
@@ -2239,11 +2243,11 @@ func (_retFinally) exec(vm *vm) {
 	vm.pc++
 }
 
-type enterCatch string
+type enterCatch unistring.String
 
 func (varName enterCatch) exec(vm *vm) {
-	vm.stash.names = map[string]uint32{
-		string(varName): 0,
+	vm.stash.names = map[unistring.String]uint32{
+		unistring.String(varName): 0,
 	}
 	vm.pc++
 }
@@ -2335,7 +2339,7 @@ func (formalArgs createArgs) exec(vm *vm) {
 		c = vm.args
 	}
 	for ; i < c; i++ {
-		args._put(strconv.Itoa(i), &mappedProperty{
+		args._put(unistring.String(strconv.Itoa(i)), &mappedProperty{
 			valueProperty: valueProperty{
 				writable:     true,
 				configurable: true,
@@ -2346,7 +2350,7 @@ func (formalArgs createArgs) exec(vm *vm) {
 	}
 
 	for _, v := range vm.stash.extraArgs {
-		args._put(strconv.Itoa(i), v)
+		args._put(unistring.String(strconv.Itoa(i)), v)
 		i++
 	}
 
@@ -2365,12 +2369,12 @@ func (formalArgs createArgsStrict) exec(vm *vm) {
 		c = vm.args
 	}
 	for _, v := range vm.stash.values[:c] {
-		args._put(strconv.Itoa(i), v)
+		args._put(unistring.String(strconv.Itoa(i)), v)
 		i++
 	}
 
 	for _, v := range vm.stash.extraArgs {
-		args._put(strconv.Itoa(i), v)
+		args._put(unistring.String(strconv.Itoa(i)), v)
 		i++
 	}
 
@@ -2426,7 +2430,7 @@ func (jmp enumNext) exec(vm *vm) {
 	l := len(vm.iterStack) - 1
 	item, n := vm.iterStack[l].f()
 	if n != nil {
-		vm.iterStack[l].val = newStringValue(item.name)
+		vm.iterStack[l].val = stringValueFromRaw(item.name)
 		vm.iterStack[l].f = n
 		vm.pc++
 	} else {