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"
 	"math/bits"
 	"reflect"
 	"reflect"
 	"strconv"
 	"strconv"
+
+	"github.com/dop251/goja/unistring"
 )
 )
 
 
 type arrayIterObject struct {
 type arrayIterObject struct {
@@ -158,7 +160,7 @@ func (a *arrayObject) getIdx(idx valueInt, receiver Value) Value {
 	return prop
 	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 := strToIdx(name); i != math.MaxUint32 {
 		if i < uint32(len(a.values)) {
 		if i < uint32(len(a.values)) {
 			return a.values[i]
 			return a.values[i]
@@ -178,7 +180,7 @@ func (a *arrayObject) getOwnPropIdx(idx valueInt) Value {
 		return nil
 		return nil
 	}
 	}
 
 
-	return a.baseObject.getOwnPropStr(idx.String())
+	return a.baseObject.getOwnPropStr(idx.string())
 }
 }
 
 
 func (a *arrayObject) sortLen() int64 {
 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]
 	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)
 	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 {
 	if i := toIdx(idx); i != math.MaxUint32 {
 		return a._setOwnIdx(i, val, throw)
 		return a._setOwnIdx(i, val, throw)
 	} else {
 	} 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
 	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 {
 	if idx := strToIdx(name); idx != math.MaxUint32 {
 		return a._setOwnIdx(idx, val, throw)
 		return a._setOwnIdx(idx, val, throw)
 	} else {
 	} 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)
 	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)
 	return a._setForeignStr(name, a.getOwnPropStr(name), val, receiver, throw)
 }
 }
 
 
@@ -286,7 +288,7 @@ type arrayPropIter struct {
 
 
 func (i *arrayPropIter) next() (propIterItem, iterNextFunc) {
 func (i *arrayPropIter) next() (propIterItem, iterNextFunc) {
 	for i.idx < len(i.a.values) {
 	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]
 		prop := i.a.values[i.idx]
 		i.idx++
 		i.idx++
 		if prop != nil {
 		if prop != nil {
@@ -318,7 +320,7 @@ func (a *arrayObject) ownKeys(all bool, accum []Value) []Value {
 	return a.baseObject.ownKeys(all, accum)
 	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 {
 	if idx := strToIdx(name); idx != math.MaxUint32 {
 		return idx < uint32(len(a.values)) && a.values[idx] != nil
 		return idx < uint32(len(a.values)) && a.values[idx] != nil
 	} else {
 	} else {
@@ -330,7 +332,7 @@ func (a *arrayObject) hasOwnPropertyIdx(idx valueInt) bool {
 	if idx := toIdx(idx); idx != math.MaxUint32 {
 	if idx := toIdx(idx); idx != math.MaxUint32 {
 		return idx < uint32(len(a.values)) && a.values[idx] != nil
 		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 {
 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)) {
 	if idx < uint32(len(a.values)) {
 		existing = a.values[idx]
 		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 ok {
 		if idx >= a.length {
 		if idx >= a.length {
 			if !a.setLengthInt(int64(idx)+1, throw) {
 			if !a.setLengthInt(int64(idx)+1, throw) {
@@ -440,7 +442,7 @@ func (a *arrayObject) _defineIdxProperty(idx uint32, desc PropertyDescriptor, th
 	return ok
 	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 {
 	if idx := strToIdx(name); idx != math.MaxUint32 {
 		return a._defineIdxProperty(idx, descr, throw)
 		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 {
 	if idx := toIdx(idx); idx != math.MaxUint32 {
 		return a._defineIdxProperty(idx, descr, throw)
 		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 {
 func (a *arrayObject) _deleteIdxProp(idx uint32, throw bool) bool {
@@ -474,7 +476,7 @@ func (a *arrayObject) _deleteIdxProp(idx uint32, throw bool) bool {
 	return true
 	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 {
 	if idx := strToIdx(name); idx != math.MaxUint32 {
 		return a._deleteIdxProp(idx, throw)
 		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 {
 	if idx := toIdx(idx); idx != math.MaxUint32 {
 		return a._deleteIdxProp(idx, throw)
 		return a._deleteIdxProp(idx, throw)
 	}
 	}
-	return a.baseObject.deleteStr(idx.String(), throw)
+	return a.baseObject.deleteStr(idx.string(), throw)
 }
 }
 
 
 func (a *arrayObject) export() interface{} {
 func (a *arrayObject) export() interface{} {
@@ -518,7 +520,7 @@ func toIdx(v valueInt) uint32 {
 	return math.MaxUint32
 	return math.MaxUint32
 }
 }
 
 
-func strToIdx64(s string) int64 {
+func strToIdx64(s unistring.String) int64 {
 	if s == "" {
 	if s == "" {
 		return -1
 		return -1
 	}
 	}
@@ -567,7 +569,7 @@ func strToIdx64(s string) int64 {
 	return n1
 	return n1
 }
 }
 
 
-func strToIdx(s string) uint32 {
+func strToIdx(s unistring.String) uint32 {
 	if s == "" {
 	if s == "" {
 		return math.MaxUint32
 		return math.MaxUint32
 	}
 	}
@@ -617,7 +619,7 @@ func strToIdx(s string) uint32 {
 	return n1
 	return n1
 }
 }
 
 
-func strToGoIdx(s string) int {
+func strToGoIdx(s unistring.String) int {
 	if bits.UintSize == 64 {
 	if bits.UintSize == 64 {
 		return int(strToIdx64(s))
 		return int(strToIdx64(s))
 	}
 	}

+ 16 - 14
array_sparse.go

@@ -6,6 +6,8 @@ import (
 	"reflect"
 	"reflect"
 	"sort"
 	"sort"
 	"strconv"
 	"strconv"
+
+	"github.com/dop251/goja/unistring"
 )
 )
 
 
 type sparseArrayItem struct {
 type sparseArrayItem struct {
@@ -109,7 +111,7 @@ func (a *sparseArrayObject) _getIdx(idx uint32) Value {
 	return nil
 	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)
 	return a.getStrWithOwnProp(a.getOwnPropStr(name), name, receiver)
 }
 }
 
 
@@ -137,7 +139,7 @@ func (a *sparseArrayObject) getLengthProp() Value {
 	return &a.lengthProp
 	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 {
 	if idx := strToIdx(name); idx != math.MaxUint32 {
 		return a._getIdx(idx)
 		return a._getIdx(idx)
 	}
 	}
@@ -151,7 +153,7 @@ func (a *sparseArrayObject) getOwnPropIdx(idx valueInt) Value {
 	if idx := toIdx(idx); idx != math.MaxUint32 {
 	if idx := toIdx(idx); idx != math.MaxUint32 {
 		return a._getIdx(idx)
 		return a._getIdx(idx)
 	}
 	}
-	return a.baseObject.getOwnPropStr(idx.String())
+	return a.baseObject.getOwnPropStr(idx.string())
 }
 }
 
 
 func (a *sparseArrayObject) add(idx uint32, val Value) {
 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
 	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 {
 	if idx := strToIdx(name); idx != math.MaxUint32 {
 		return a._setOwnIdx(idx, val, throw)
 		return a._setOwnIdx(idx, val, throw)
 	} else {
 	} else {
@@ -235,10 +237,10 @@ func (a *sparseArrayObject) setOwnIdx(idx valueInt, val Value, throw bool) bool
 		return a._setOwnIdx(idx, val, throw)
 		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)
 	return a._setForeignStr(name, a.getOwnPropStr(name), val, receiver, throw)
 }
 }
 
 
@@ -253,7 +255,7 @@ type sparseArrayPropIter struct {
 
 
 func (i *sparseArrayPropIter) next() (propIterItem, iterNextFunc) {
 func (i *sparseArrayPropIter) next() (propIterItem, iterNextFunc) {
 	for i.idx < len(i.a.items) {
 	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
 		prop := i.a.items[i.idx].value
 		i.idx++
 		i.idx++
 		if prop != nil {
 		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 {
 	if idx := strToIdx(name); idx != math.MaxUint32 {
 		i := a.findIdx(idx)
 		i := a.findIdx(idx)
 		return i < len(a.items) && a.items[i].idx == 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 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 {
 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 {
 	if i < len(a.items) && a.items[i].idx == idx {
 		existing = a.items[i].value
 		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 ok {
 		if idx >= a.length {
 		if idx >= a.length {
 			if !a.setLengthInt(int64(idx)+1, throw) {
 			if !a.setLengthInt(int64(idx)+1, throw) {
@@ -376,7 +378,7 @@ func (a *sparseArrayObject) _defineIdxProperty(idx uint32, desc PropertyDescript
 	return ok
 	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 {
 	if idx := strToIdx(name); idx != math.MaxUint32 {
 		return a._defineIdxProperty(idx, descr, throw)
 		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 {
 	if idx := toIdx(idx); idx != math.MaxUint32 {
 		return a._defineIdxProperty(idx, descr, throw)
 		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 {
 func (a *sparseArrayObject) _deleteIdxProp(idx uint32, throw bool) bool {
@@ -410,7 +412,7 @@ func (a *sparseArrayObject) _deleteIdxProp(idx uint32, throw bool) bool {
 	return true
 	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 {
 	if idx := strToIdx(name); idx != math.MaxUint32 {
 		return a._deleteIdxProp(idx, throw)
 		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 {
 	if idx := toIdx(idx); idx != math.MaxUint32 {
 		return a._deleteIdxProp(idx, throw)
 		return a._deleteIdxProp(idx, throw)
 	}
 	}
-	return a.baseObject.deleteStr(idx.String(), throw)
+	return a.baseObject.deleteStr(idx.string(), throw)
 }
 }
 
 
 func (a *sparseArrayObject) sortLen() int64 {
 func (a *sparseArrayObject) sortLen() int64 {

+ 5 - 4
ast/node.go

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

+ 3 - 3
builtin_array.go

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

+ 9 - 7
builtin_global.go

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

+ 6 - 4
builtin_json.go

@@ -7,9 +7,11 @@ import (
 	"io"
 	"io"
 	"math"
 	"math"
 	"strings"
 	"strings"
+
+	"github.com/dop251/goja/unistring"
 )
 )
 
 
-var hex = "0123456789abcdef"
+const hex = "0123456789abcdef"
 
 
 func (r *Runtime) builtinJSON_parse(call FunctionCall) Value {
 func (r *Runtime) builtinJSON_parse(call FunctionCall) Value {
 	d := json.NewDecoder(bytes.NewBufferString(call.Argument(0).String()))
 	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
 			return nil, err
 		}
 		}
 
 
-		object.self._putProp(key, value, true, true, true)
+		object.self._putProp(unistring.NewFromString(key), value, true, true, true)
 	}
 	}
 	return object, nil
 	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) {
 			for _, itemName := range object.self.ownKeys(false, nil) {
 				value := r.builtinJSON_reviveWalk(reviver, object, name)
 				value := r.builtinJSON_reviveWalk(reviver, object, name)
 				if value == _undefined {
 				if value == _undefined {
-					object.self.deleteStr(itemName.String(), false)
+					object.self.deleteStr(itemName.string(), false)
 				} else {
 				} 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 {
 func (r *Runtime) object_getOwnPropertySymbols(call FunctionCall) Value {
 	obj := call.Argument(0).ToObject(r)
 	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 {
 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) {
 func (r *Runtime) _defineProperties(o *Object, p Value) {
 	type propItem struct {
 	type propItem struct {
-		name string
+		name Value
 		prop PropertyDescriptor
 		prop PropertyDescriptor
 	}
 	}
 	props := p.ToObject(r)
 	props := p.ToObject(r)
-	names := props.self.ownKeys(false, nil)
+	names := props.self.ownPropertyKeys(false, nil)
 	list := make([]propItem, 0, len(names))
 	list := make([]propItem, 0, len(names))
 	for _, itemName := range names {
 	for _, itemName := range names {
-		itemNameStr := itemName.String()
 		list = append(list, propItem{
 		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 {
 	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
 package goja
 
 
-import "fmt"
+import (
+	"fmt"
+
+	"github.com/dop251/goja/unistring"
+)
 
 
 func (r *Runtime) newNativeProxyHandler(nativeHandler *ProxyTrapConfig) *Object {
 func (r *Runtime) newNativeProxyHandler(nativeHandler *ProxyTrapConfig) *Object {
 	handler := r.NewObject()
 	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) {
 func (r *Runtime) proxyproto_nativehandler_gen_obj_obj(name proxyTrap, native func(*Object) *Object, handler *Object) {
 	if native != nil {
 	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 len(call.Arguments) >= 1 {
 				if t, ok := call.Argument(0).(*Object); ok {
 				if t, ok := call.Argument(0).(*Object); ok {
 					return native(t)
 					return native(t)
 				}
 				}
 			}
 			}
 			panic(r.NewTypeError("%s needs to be called with target as Object", name))
 			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) {
 func (r *Runtime) proxyproto_nativehandler_gen_obj_bool(name proxyTrap, native func(*Object) bool, handler *Object) {
 	if native != nil {
 	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 len(call.Arguments) >= 1 {
 				if t, ok := call.Argument(0).(*Object); ok {
 				if t, ok := call.Argument(0).(*Object); ok {
 					s := native(t)
 					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))
 			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) {
 func (r *Runtime) proxyproto_nativehandler_gen_obj_string_bool(name proxyTrap, native func(*Object, string) bool, handler *Object) {
 	if native != nil {
 	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 len(call.Arguments) >= 2 {
 				if t, ok := call.Argument(0).(*Object); ok {
 				if t, ok := call.Argument(0).(*Object); ok {
 					if p, ok := call.Argument(1).(valueString); 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))
 			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 {
 			} else {
 				previousLastIndex = thisIndex
 				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 {
 		if len(a) == 0 {
 			return _null
 			return _null
@@ -447,11 +447,11 @@ func (r *Runtime) regexpproto_stdSplitterGeneric(splitter *Object, s valueString
 	} else {
 	} else {
 		lim = toLength(limit)
 		lim = toLength(limit)
 	}
 	}
-	size := s.length()
-	p := int64(0)
 	if lim == 0 {
 	if lim == 0 {
 		return r.newArrayValues(a)
 		return r.newArrayValues(a)
 	}
 	}
+	size := s.length()
+	p := 0
 	execFn := toMethod(splitter.ToObject(r).self.getStr("exec", nil)) // must be non-nil
 	execFn := toMethod(splitter.ToObject(r).self.getStr("exec", nil)) // must be non-nil
 
 
 	if size == 0 {
 	if size == 0 {
@@ -463,21 +463,25 @@ func (r *Runtime) regexpproto_stdSplitterGeneric(splitter *Object, s valueString
 
 
 	q := p
 	q := p
 	for q < size {
 	for q < size {
-		splitter.self.setOwnStr("lastIndex", intToValue(q), true)
+		splitter.self.setOwnStr("lastIndex", intToValue(int64(q)), true)
 		z := r.regExpExec(execFn, splitter, s)
 		z := r.regExpExec(execFn, splitter, s)
 		if z == _null {
 		if z == _null {
 			q++
 			q++
 		} else {
 		} else {
 			z := r.toObject(z)
 			z := r.toObject(z)
 			e := toLength(splitter.self.getStr("lastIndex", nil))
 			e := toLength(splitter.self.getStr("lastIndex", nil))
-			if e == p {
+			if e == int64(p) {
 				q++
 				q++
 			} else {
 			} else {
 				a = append(a, s.substring(p, q))
 				a = append(a, s.substring(p, q))
 				if int64(len(a)) == lim {
 				if int64(len(a)) == lim {
 					return r.newArrayValues(a)
 					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)
 				numberOfCaptures := max(toLength(z.self.getStr("length", nil))-1, 0)
 				for i := int64(1); i <= numberOfCaptures; i++ {
 				for i := int64(1); i <= numberOfCaptures; i++ {
 					a = append(a, z.self.getIdx(valueInt(i), nil))
 					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 {
 	for _, match := range result {
 		if match[0] == match[1] {
 		if match[0] == match[1] {
 			// FIXME Ugh, this is a hack
 			// FIXME Ugh, this is a hack
-			if match[0] == 0 || int64(match[0]) == targetLength {
+			if match[0] == 0 || match[0] == targetLength {
 				continue
 				continue
 			}
 			}
 		}
 		}
 
 
 		if lastIndex != match[0] {
 		if lastIndex != match[0] {
-			valueArray = append(valueArray, s.substring(int64(lastIndex), int64(match[0])))
+			valueArray = append(valueArray, s.substring(lastIndex, match[0]))
 			found++
 			found++
 		} else if lastIndex == match[0] {
 		} else if lastIndex == match[0] {
 			if lastIndex != -1 {
 			if lastIndex != -1 {
@@ -554,7 +558,7 @@ func (r *Runtime) regexpproto_stdSplitter(call FunctionCall) Value {
 			offset := index * 2
 			offset := index * 2
 			var value Value
 			var value Value
 			if match[offset] != -1 {
 			if match[offset] != -1 {
-				value = s.substring(int64(match[offset]), int64(match[offset+1]))
+				value = s.substring(match[offset], match[offset+1])
 			} else {
 			} else {
 				value = _undefined
 				value = _undefined
 			}
 			}
@@ -567,8 +571,8 @@ func (r *Runtime) regexpproto_stdSplitter(call FunctionCall) Value {
 	}
 	}
 
 
 	if found != limit {
 	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 {
 		} else {
 			valueArray = append(valueArray, stringEmpty)
 			valueArray = append(valueArray, stringEmpty)
 		}
 		}

+ 414 - 30
builtin_string.go

@@ -2,13 +2,16 @@ package goja
 
 
 import (
 import (
 	"bytes"
 	"bytes"
+	"github.com/dop251/goja/unistring"
+	"math"
+	"strings"
+	"unicode/utf16"
+	"unicode/utf8"
+
 	"github.com/dop251/goja/parser"
 	"github.com/dop251/goja/parser"
 	"golang.org/x/text/collate"
 	"golang.org/x/text/collate"
 	"golang.org/x/text/language"
 	"golang.org/x/text/language"
 	"golang.org/x/text/unicode/norm"
 	"golang.org/x/text/unicode/norm"
-	"math"
-	"strings"
-	"unicode/utf8"
 )
 )
 
 
 func (r *Runtime) collator() *collate.Collator {
 func (r *Runtime) collator() *collate.Collator {
@@ -25,7 +28,7 @@ func toString(arg Value) valueString {
 		return s
 		return s
 	}
 	}
 	if s, ok := arg.(*valueSymbol); ok {
 	if s, ok := arg.(*valueSymbol); ok {
-		return newStringValue(s.descString())
+		return s.desc
 	}
 	}
 	return arg.toString()
 	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 {
 func (r *Runtime) builtin_newString(args []Value, proto *Object) *Object {
 	var s valueString
 	var s valueString
 	if len(args) > 0 {
 	if len(args) > 0 {
-		s = toString(args[0])
+		s = args[0].toString()
 	} else {
 	} else {
 		s = stringEmpty
 		s = stringEmpty
 	}
 	}
@@ -99,19 +102,26 @@ func (r *Runtime) stringproto_valueOf(call FunctionCall) Value {
 	return r.stringproto_toStringValueOf(call.This, "valueOf")
 	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 {
 func (r *Runtime) string_fromcharcode(call FunctionCall) Value {
 	b := make([]byte, len(call.Arguments))
 	b := make([]byte, len(call.Arguments))
 	for i, arg := range call.Arguments {
 	for i, arg := range call.Arguments {
 		chr := toUint16(arg)
 		chr := toUint16(arg)
 		if chr >= utf8.RuneSelf {
 		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++ {
 			for j := 0; j < i; j++ {
-				bb[j] = uint16(b[j])
+				bb1[j] = uint16(b[j])
 			}
 			}
-			bb[i] = chr
+			bb1[i] = chr
 			i++
 			i++
 			for j, arg := range call.Arguments[i:] {
 			for j, arg := range call.Arguments[i:] {
-				bb[i+j] = toUint16(arg)
+				bb1[i+j] = toUint16(arg)
 			}
 			}
 			return unicodeString(bb)
 			return unicodeString(bb)
 		}
 		}
@@ -121,24 +131,107 @@ func (r *Runtime) string_fromcharcode(call FunctionCall) Value {
 	return asciiString(b)
 	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 {
 func (r *Runtime) stringproto_charAt(call FunctionCall) Value {
 	r.checkObjectCoercible(call.This)
 	r.checkObjectCoercible(call.This)
 	s := call.This.toString()
 	s := call.This.toString()
 	pos := call.Argument(0).ToInteger()
 	pos := call.Argument(0).ToInteger()
-	if pos < 0 || pos >= s.length() {
+	if pos < 0 || pos >= int64(s.length()) {
 		return stringEmpty
 		return stringEmpty
 	}
 	}
-	return newStringValue(string(s.charAt(pos)))
+	return newStringValue(string(s.charAt(toInt(pos))))
 }
 }
 
 
 func (r *Runtime) stringproto_charCodeAt(call FunctionCall) Value {
 func (r *Runtime) stringproto_charCodeAt(call FunctionCall) Value {
 	r.checkObjectCoercible(call.This)
 	r.checkObjectCoercible(call.This)
 	s := call.This.toString()
 	s := call.This.toString()
 	pos := call.Argument(0).ToInteger()
 	pos := call.Argument(0).ToInteger()
-	if pos < 0 || pos >= s.length() {
+	if pos < 0 || pos >= int64(s.length()) {
 		return _NaN
 		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 {
 func (r *Runtime) stringproto_concat(call FunctionCall) Value {
@@ -163,8 +256,9 @@ func (r *Runtime) stringproto_concat(call FunctionCall) Value {
 		}
 		}
 		return asciiString(buf.String())
 		return asciiString(buf.String())
 	} else {
 	} else {
-		buf := make([]uint16, totalLen)
-		pos := int64(0)
+		buf := make([]uint16, totalLen+1)
+		buf[0] = unistring.BOM
+		pos := 1
 		for _, s := range strs {
 		for _, s := range strs {
 			switch s := s.(type) {
 			switch s := s.(type) {
 			case asciiString:
 			case asciiString:
@@ -173,7 +267,7 @@ func (r *Runtime) stringproto_concat(call FunctionCall) Value {
 					pos++
 					pos++
 				}
 				}
 			case unicodeString:
 			case unicodeString:
-				copy(buf[pos:], s)
+				copy(buf[pos:], s[1:])
 				pos += s.length()
 				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 {
 func (r *Runtime) stringproto_indexOf(call FunctionCall) Value {
 	r.checkObjectCoercible(call.This)
 	r.checkObjectCoercible(call.This)
 	value := call.This.toString()
 	value := call.This.toString()
@@ -190,13 +334,13 @@ func (r *Runtime) stringproto_indexOf(call FunctionCall) Value {
 	if pos < 0 {
 	if pos < 0 {
 		pos = 0
 		pos = 0
 	} else {
 	} else {
-		l := value.length()
+		l := int64(value.length())
 		if pos > l {
 		if pos > l {
 			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 {
 func (r *Runtime) stringproto_lastIndexOf(call FunctionCall) Value {
@@ -207,20 +351,20 @@ func (r *Runtime) stringproto_lastIndexOf(call FunctionCall) Value {
 
 
 	var pos int64
 	var pos int64
 	if f, ok := numPos.(valueFloat); ok && math.IsNaN(float64(f)) {
 	if f, ok := numPos.(valueFloat); ok && math.IsNaN(float64(f)) {
-		pos = value.length()
+		pos = int64(value.length())
 	} else {
 	} else {
 		pos = numPos.ToInteger()
 		pos = numPos.ToInteger()
 		if pos < 0 {
 		if pos < 0 {
 			pos = 0
 			pos = 0
 		} else {
 		} else {
-			l := value.length()
+			l := int64(value.length())
 			if pos > l {
 			if pos > l {
 				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 {
 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"))
 	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 {
 func (r *Runtime) stringproto_replace(call FunctionCall) Value {
 	r.checkObjectCoercible(call.This)
 	r.checkObjectCoercible(call.This)
 	searchValue := call.Argument(0)
 	searchValue := call.Argument(0)
@@ -448,7 +759,7 @@ func (r *Runtime) stringproto_slice(call FunctionCall) Value {
 	r.checkObjectCoercible(call.This)
 	r.checkObjectCoercible(call.This)
 	s := call.This.toString()
 	s := call.This.toString()
 
 
-	l := s.length()
+	l := int64(s.length())
 	start := call.Argument(0).ToInteger()
 	start := call.Argument(0).ToInteger()
 	var end int64
 	var end int64
 	if arg1 := call.Argument(1); arg1 != _undefined {
 	if arg1 := call.Argument(1); arg1 != _undefined {
@@ -480,7 +791,7 @@ func (r *Runtime) stringproto_slice(call FunctionCall) Value {
 	}
 	}
 
 
 	if end > start {
 	if end > start {
-		return s.substring(start, end)
+		return s.substring(int(start), int(end))
 	}
 	}
 	return stringEmpty
 	return stringEmpty
 }
 }
@@ -539,11 +850,37 @@ func (r *Runtime) stringproto_split(call FunctionCall) Value {
 	return r.newArrayValues(valueArray)
 	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 {
 func (r *Runtime) stringproto_substring(call FunctionCall) Value {
 	r.checkObjectCoercible(call.This)
 	r.checkObjectCoercible(call.This)
 	s := call.This.toString()
 	s := call.This.toString()
 
 
-	l := s.length()
+	l := int64(s.length())
 	intStart := call.Argument(0).ToInteger()
 	intStart := call.Argument(0).ToInteger()
 	var intEnd int64
 	var intEnd int64
 	if end := call.Argument(1); end != _undefined {
 	if end := call.Argument(1); end != _undefined {
@@ -567,7 +904,7 @@ func (r *Runtime) stringproto_substring(call FunctionCall) Value {
 		intStart, intEnd = intEnd, intStart
 		intStart, intEnd = intEnd, intStart
 	}
 	}
 
 
-	return s.substring(intStart, intEnd)
+	return s.substring(int(intStart), int(intEnd))
 }
 }
 
 
 func (r *Runtime) stringproto_toLowerCase(call FunctionCall) Value {
 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))
 	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 {
 func (r *Runtime) stringproto_substr(call FunctionCall) Value {
+	r.checkObjectCoercible(call.This)
 	s := call.This.toString()
 	s := call.This.toString()
 	start := call.Argument(0).ToInteger()
 	start := call.Argument(0).ToInteger()
 	var length int64
 	var length int64
@@ -611,32 +963,62 @@ func (r *Runtime) stringproto_substr(call FunctionCall) Value {
 		return stringEmpty
 		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() {
 func (r *Runtime) initString() {
+	r.global.StringIteratorPrototype = r.newLazyObject(r.createStringIterProto)
 	r.global.StringPrototype = r.builtin_newString([]Value{stringEmpty}, r.global.ObjectPrototype)
 	r.global.StringPrototype = r.builtin_newString([]Value{stringEmpty}, r.global.ObjectPrototype)
 
 
 	o := r.global.StringPrototype.self
 	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("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("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("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("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("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("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("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("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("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("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("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("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("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("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("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
 	// Annex B
 	o._putProp("substr", r.newNativeFunc(r.stringproto_substr, nil, "substr", nil, 2), true, false, true)
 	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)
 	r.global.String = r.newNativeFunc(r.builtin_String, r.builtin_newString, "String", r.global.StringPrototype, 1)
 	o = r.global.String.self
 	o = r.global.String.self
 	o._putProp("fromCharCode", r.newNativeFunc(r.string_fromcharcode, nil, "fromCharCode", nil, 1), true, false, true)
 	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)
 	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)
 	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
 package goja
 
 
+import "github.com/dop251/goja/unistring"
+
 var (
 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 {
 func (r *Runtime) builtin_symbol(call FunctionCall) Value {
-	desc := ""
+	var desc valueString
 	if arg := call.Argument(0); !IsUndefined(arg) {
 	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 {
 func (r *Runtime) symbolproto_tostring(call FunctionCall) Value {
@@ -38,7 +40,7 @@ func (r *Runtime) symbolproto_tostring(call FunctionCall) Value {
 	if sym == nil {
 	if sym == nil {
 		panic(r.NewTypeError("Method Symbol.prototype.toString is called on incompatible receiver"))
 		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 {
 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 {
 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
 		return v
 	}
 	}
 	if r.symbolRegistry == nil {
 	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
 	return v
 }
 }
 
 
@@ -81,7 +82,7 @@ func (r *Runtime) symbol_keyfor(call FunctionCall) Value {
 	}
 	}
 	for key, s := range r.symbolRegistry {
 	for key, s := range r.symbolRegistry {
 		if s == sym {
 		if s == sym {
-			return r.ToValue(key)
+			return stringValueFromRaw(key)
 		}
 		}
 	}
 	}
 	return _undefined
 	return _undefined
@@ -124,7 +125,9 @@ func (r *Runtime) createSymbol(val *Object) objectImpl {
 		symToStringTag,
 		symToStringTag,
 		symUnscopables,
 		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
 	return o

+ 3 - 1
builtin_typedarrays.go

@@ -5,6 +5,8 @@ import (
 	"sort"
 	"sort"
 	"strings"
 	"strings"
 	"unsafe"
 	"unsafe"
+
+	"github.com/dop251/goja/unistring"
 )
 )
 
 
 type typedArraySortCtx struct {
 type typedArraySortCtx struct {
@@ -1377,7 +1379,7 @@ func (r *Runtime) addPrototype(ctor *Object, proto *Object) *baseObject {
 	return p
 	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 {
 	return func(val *Object) objectImpl {
 		o := r.newNativeConstructOnly(val, ctor, nil, name, 3)
 		o := r.newNativeConstructOnly(val, ctor, nil, name, 3)
 		o.prototype = r.global.TypedArray
 		o.prototype = r.global.TypedArray

+ 17 - 15
compiler.go

@@ -2,10 +2,12 @@ package goja
 
 
 import (
 import (
 	"fmt"
 	"fmt"
-	"github.com/dop251/goja/ast"
-	"github.com/dop251/goja/file"
 	"sort"
 	"sort"
 	"strconv"
 	"strconv"
+
+	"github.com/dop251/goja/ast"
+	"github.com/dop251/goja/file"
+	"github.com/dop251/goja/unistring"
 )
 )
 
 
 const (
 const (
@@ -39,7 +41,7 @@ type Program struct {
 	code   []instruction
 	code   []instruction
 	values []Value
 	values []Value
 
 
-	funcName string
+	funcName unistring.String
 	src      *SrcFile
 	src      *SrcFile
 	srcMap   []srcMapItem
 	srcMap   []srcMapItem
 }
 }
@@ -56,7 +58,7 @@ type compiler struct {
 }
 }
 
 
 type scope struct {
 type scope struct {
-	names      map[string]uint32
+	names      map[unistring.String]uint32
 	outer      *scope
 	outer      *scope
 	strict     bool
 	strict     bool
 	eval       bool
 	eval       bool
@@ -66,13 +68,13 @@ type scope struct {
 	argsNeeded bool
 	argsNeeded bool
 	thisNeeded bool
 	thisNeeded bool
 
 
-	namesMap    map[string]string
+	namesMap    map[unistring.String]unistring.String
 	lastFreeTmp int
 	lastFreeTmp int
 }
 }
 
 
 type block struct {
 type block struct {
 	typ        int
 	typ        int
-	label      string
+	label      unistring.String
 	needResult bool
 	needResult bool
 	cont       int
 	cont       int
 	breaks     []int
 	breaks     []int
@@ -111,9 +113,9 @@ func (c *compiler) newScope() {
 	}
 	}
 	c.scope = &scope{
 	c.scope = &scope{
 		outer:    c.scope,
 		outer:    c.scope,
-		names:    make(map[string]uint32),
+		names:    make(map[unistring.String]uint32),
 		strict:   strict,
 		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()
 	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
 	var level uint32 = 0
 	noDynamics = true
 	noDynamics = true
 	for curScope := s; curScope != nil; curScope = curScope.outer {
 	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 {
 		if curScope.dynamic {
 			noDynamics = false
 			noDynamics = false
 		} else {
 		} else {
-			var mapped string
+			var mapped unistring.String
 			if m, exists := curScope.namesMap[name]; exists {
 			if m, exists := curScope.namesMap[name]; exists {
 				mapped = m
 				mapped = m
 			} else {
 			} else {
@@ -210,7 +212,7 @@ func (s *scope) lookupName(name string) (idx uint32, found, noDynamics bool) {
 	return
 	return
 }
 }
 
 
-func (s *scope) bindName(name string) (uint32, bool) {
+func (s *scope) bindName(name unistring.String) (uint32, bool) {
 	if s.lexical {
 	if s.lexical {
 		return s.outer.bindName(name)
 		return s.outer.bindName(name)
 	}
 	}
@@ -223,7 +225,7 @@ func (s *scope) bindName(name string) (uint32, bool) {
 	return idx, true
 	return idx, true
 }
 }
 
 
-func (s *scope) bindNameShadow(name string) (uint32, bool) {
+func (s *scope) bindNameShadow(name unistring.String) (uint32, bool) {
 	if s.lexical {
 	if s.lexical {
 		return s.outer.bindName(name)
 		return s.outer.bindName(name)
 	}
 	}
@@ -234,7 +236,7 @@ func (s *scope) bindNameShadow(name string) (uint32, bool) {
 		unique = false
 		unique = false
 		// shadow the var
 		// shadow the var
 		delete(s.names, name)
 		delete(s.names, name)
-		n := strconv.Itoa(int(idx))
+		n := unistring.String(strconv.Itoa(int(idx)))
 		s.names[n] = idx
 		s.names[n] = idx
 	}
 	}
 	idx := uint32(len(s.names))
 	idx := uint32(len(s.names))
@@ -446,14 +448,14 @@ func (c *compiler) isStrictStatement(s ast.Statement) bool {
 	return false
 	return false
 }
 }
 
 
-func (c *compiler) checkIdentifierName(name string, offset int) {
+func (c *compiler) checkIdentifierName(name unistring.String, offset int) {
 	switch name {
 	switch name {
 	case "implements", "interface", "let", "package", "private", "protected", "public", "static", "yield":
 	case "implements", "interface", "let", "package", "private", "protected", "public", "static", "yield":
 		c.throwSyntaxError(offset, "Unexpected strict mode reserved word")
 		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 {
 	switch name {
 	case "eval", "arguments":
 	case "eval", "arguments":
 		c.throwSyntaxError(offset, "Assignment to eval or arguments is not allowed in strict mode")
 		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 (
 import (
 	"fmt"
 	"fmt"
+	"regexp"
+
 	"github.com/dop251/goja/ast"
 	"github.com/dop251/goja/ast"
 	"github.com/dop251/goja/file"
 	"github.com/dop251/goja/file"
 	"github.com/dop251/goja/token"
 	"github.com/dop251/goja/token"
-	"regexp"
+	"github.com/dop251/goja/unistring"
 )
 )
 
 
 var (
 var (
@@ -60,18 +62,18 @@ type compiledAssignExpr struct {
 
 
 type deleteGlobalExpr struct {
 type deleteGlobalExpr struct {
 	baseCompiledExpr
 	baseCompiledExpr
-	name string
+	name unistring.String
 }
 }
 
 
 type deleteVarExpr struct {
 type deleteVarExpr struct {
 	baseCompiledExpr
 	baseCompiledExpr
-	name string
+	name unistring.String
 }
 }
 
 
 type deletePropExpr struct {
 type deletePropExpr struct {
 	baseCompiledExpr
 	baseCompiledExpr
 	left compiledExpr
 	left compiledExpr
-	name string
+	name unistring.String
 }
 }
 
 
 type deleteElemExpr struct {
 type deleteElemExpr struct {
@@ -91,7 +93,7 @@ type baseCompiledExpr struct {
 
 
 type compiledIdentifierExpr struct {
 type compiledIdentifierExpr struct {
 	baseCompiledExpr
 	baseCompiledExpr
-	name string
+	name unistring.String
 }
 }
 
 
 type compiledFunctionLiteral struct {
 type compiledFunctionLiteral struct {
@@ -154,7 +156,7 @@ type compiledBinaryExpr struct {
 
 
 type compiledVariableExpr struct {
 type compiledVariableExpr struct {
 	baseCompiledExpr
 	baseCompiledExpr
-	name        string
+	name        unistring.String
 	initializer compiledExpr
 	initializer compiledExpr
 	expr        *ast.VariableExpression
 	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 {
 	if c.scope.strict {
 		c.checkIdentifierLName(name, offset)
 		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.emitVarSetter1(name, offset, func(bool) {
 		c.emitExpr(valueExpr, true)
 		c.emitExpr(valueExpr, true)
 	})
 	})
@@ -432,7 +434,7 @@ func (e *compiledIdentifierExpr) deleteExpr() compiledExpr {
 type compiledDotExpr struct {
 type compiledDotExpr struct {
 	baseCompiledExpr
 	baseCompiledExpr
 	left compiledExpr
 	left compiledExpr
-	name string
+	name unistring.String
 }
 }
 
 
 func (e *compiledDotExpr) emitGetter(putOnStack bool) {
 func (e *compiledDotExpr) emitGetter(putOnStack bool) {
@@ -854,7 +856,7 @@ func (e *compiledFunctionLiteral) emitGetter(putOnStack bool) {
 	e.c.popScope()
 	e.c.popScope()
 	e.c.p = savedPrg
 	e.c.p = savedPrg
 	e.c.blockStart = savedBlockStart
 	e.c.blockStart = savedBlockStart
-	name := ""
+	var name unistring.String
 	if e.expr.Name != nil {
 	if e.expr.Name != nil {
 		name = e.expr.Name.Name
 		name = e.expr.Name.Name
 	}
 	}
@@ -1444,7 +1446,7 @@ func (c *compiler) compileRegexpLiteral(v *ast.RegExpLiteral) compiledExpr {
 }
 }
 
 
 func (e *compiledCallExpr) emitGetter(putOnStack bool) {
 func (e *compiledCallExpr) emitGetter(putOnStack bool) {
-	var calleeName string
+	var calleeName unistring.String
 	switch callee := e.callee.(type) {
 	switch callee := e.callee.(type) {
 	case *compiledDotExpr:
 	case *compiledDotExpr:
 		callee.left.emitGetter(true)
 		callee.left.emitGetter(true)
@@ -1549,7 +1551,7 @@ func (c *compiler) compileNumberLiteral(v *ast.NumberLiteral) compiledExpr {
 
 
 func (c *compiler) compileStringLiteral(v *ast.StringLiteral) compiledExpr {
 func (c *compiler) compileStringLiteral(v *ast.StringLiteral) compiledExpr {
 	r := &compiledLiteral{
 	r := &compiledLiteral{
-		val: newStringValue(v.Value),
+		val: stringValueFromRaw(v.Value),
 	}
 	}
 	r.init(c, v.Idx0())
 	r.init(c, v.Idx0())
 	return r
 	return r

+ 34 - 24
compiler_stmt.go

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

+ 20 - 16
func.go

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

+ 5 - 3
map.go

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

+ 86 - 73
object.go

@@ -7,6 +7,8 @@ import (
 	"runtime"
 	"runtime"
 	"sort"
 	"sort"
 	"unsafe"
 	"unsafe"
+
+	"github.com/dop251/goja/unistring"
 )
 )
 
 
 const (
 const (
@@ -24,9 +26,10 @@ const (
 	classRegExp   = "RegExp"
 	classRegExp   = "RegExp"
 	classDate     = "Date"
 	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 {
 type weakCollection interface {
@@ -171,35 +174,35 @@ func (p *PropertyDescriptor) complete() {
 type objectImpl interface {
 type objectImpl interface {
 	sortable
 	sortable
 	className() string
 	className() string
-	getStr(p string, receiver Value) Value
+	getStr(p unistring.String, receiver Value) Value
 	getIdx(p valueInt, receiver Value) Value
 	getIdx(p valueInt, receiver Value) Value
 	getSym(p *valueSymbol, receiver Value) Value
 	getSym(p *valueSymbol, receiver Value) Value
 
 
-	getOwnPropStr(string) Value
+	getOwnPropStr(unistring.String) Value
 	getOwnPropIdx(valueInt) Value
 	getOwnPropIdx(valueInt) Value
 	getOwnPropSym(*valueSymbol) 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
 	setOwnIdx(p valueInt, v Value, throw bool) bool
 	setOwnSym(p *valueSymbol, 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)
 	setForeignIdx(p valueInt, v, receiver Value, throw bool) (res bool, handled bool)
 	setForeignSym(p *valueSymbol, v, receiver Value, throw bool) (res bool, handled bool)
 	setForeignSym(p *valueSymbol, v, receiver Value, throw bool) (res bool, handled bool)
 
 
-	hasPropertyStr(string) bool
+	hasPropertyStr(unistring.String) bool
 	hasPropertyIdx(idx valueInt) bool
 	hasPropertyIdx(idx valueInt) bool
 	hasPropertySym(s *valueSymbol) bool
 	hasPropertySym(s *valueSymbol) bool
 
 
-	hasOwnPropertyStr(string) bool
+	hasOwnPropertyStr(unistring.String) bool
 	hasOwnPropertyIdx(valueInt) bool
 	hasOwnPropertyIdx(valueInt) bool
 	hasOwnPropertySym(s *valueSymbol) 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
 	defineOwnPropertyIdx(name valueInt, desc PropertyDescriptor, throw bool) bool
 	defineOwnPropertySym(name *valueSymbol, 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
 	deleteIdx(idx valueInt, throw bool) bool
 	deleteSym(s *valueSymbol, throw bool) bool
 	deleteSym(s *valueSymbol, throw bool) bool
 
 
@@ -219,10 +222,10 @@ type objectImpl interface {
 	exportType() reflect.Type
 	exportType() reflect.Type
 	equal(objectImpl) bool
 	equal(objectImpl) bool
 	ownKeys(all bool, accum []Value) []Value
 	ownKeys(all bool, accum []Value) []Value
-	ownSymbols() []Value
+	ownSymbols(all bool, accum []Value) []Value
 	ownPropertyKeys(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)
 	_putSym(s *valueSymbol, prop Value)
 }
 }
 
 
@@ -232,8 +235,8 @@ type baseObject struct {
 	prototype  *Object
 	prototype  *Object
 	extensible bool
 	extensible bool
 
 
-	values    map[string]Value
-	propNames []string
+	values    map[unistring.String]Value
+	propNames []unistring.String
 
 
 	lastSortedPropLen, idxPropCount int
 	lastSortedPropLen, idxPropCount int
 
 
@@ -278,14 +281,14 @@ func (f ConstructorCall) Argument(idx int) Value {
 }
 }
 
 
 func (o *baseObject) init() {
 func (o *baseObject) init() {
-	o.values = make(map[string]Value)
+	o.values = make(map[unistring.String]Value)
 }
 }
 
 
 func (o *baseObject) className() string {
 func (o *baseObject) className() string {
 	return o.class
 	return o.class
 }
 }
 
 
-func (o *baseObject) hasPropertyStr(name string) bool {
+func (o *baseObject) hasPropertyStr(name unistring.String) bool {
 	if o.val.self.hasOwnPropertyStr(name) {
 	if o.val.self.hasOwnPropertyStr(name) {
 		return true
 		return true
 	}
 	}
@@ -296,7 +299,7 @@ func (o *baseObject) hasPropertyStr(name string) bool {
 }
 }
 
 
 func (o *baseObject) hasPropertyIdx(idx valueInt) 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 {
 func (o *baseObject) hasPropertySym(s *valueSymbol) bool {
@@ -325,7 +328,7 @@ func (o *baseObject) getWithOwnProp(prop, p, receiver Value) Value {
 	return prop
 	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 prop == nil && o.prototype != nil {
 		if receiver == nil {
 		if receiver == nil {
 			return o.prototype.self.getStr(name, o.val)
 			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 {
 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 {
 func (o *baseObject) getSym(s *valueSymbol, receiver Value) Value {
 	return o.getWithOwnProp(o.getOwnPropSym(s), s, receiver)
 	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]
 	prop := o.values[name]
 	if prop == nil {
 	if prop == nil {
 		if o.prototype != 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 {
 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 {
 func (o *baseObject) getOwnPropSym(s *valueSymbol) Value {
@@ -379,11 +382,11 @@ func (o *baseObject) getOwnPropSym(s *valueSymbol) Value {
 	return nil
 	return nil
 }
 }
 
 
-func (o *baseObject) getOwnPropStr(name string) Value {
+func (o *baseObject) getOwnPropStr(name unistring.String) Value {
 	return o.values[name]
 	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 {
 	if !prop.configurable {
 		o.val.runtime.typeErrorResult(throw, "Cannot delete property '%s' of %s", name, o.val.toString())
 		o.val.runtime.typeErrorResult(throw, "Cannot delete property '%s' of %s", name, o.val.toString())
 		return false
 		return false
@@ -391,14 +394,14 @@ func (o *baseObject) checkDeleteProp(name string, prop *valueProperty, throw boo
 	return true
 	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 {
 	if val, ok := val.(*valueProperty); ok {
 		return o.checkDeleteProp(name, val, throw)
 		return o.checkDeleteProp(name, val, throw)
 	}
 	}
 	return true
 	return true
 }
 }
 
 
-func (o *baseObject) _delete(name string) {
+func (o *baseObject) _delete(name unistring.String) {
 	delete(o.values, name)
 	delete(o.values, name)
 	for i, n := range o.propNames {
 	for i, n := range o.propNames {
 		if n == name {
 		if n == name {
@@ -416,13 +419,13 @@ func (o *baseObject) _delete(name string) {
 }
 }
 
 
 func (o *baseObject) deleteIdx(idx valueInt, throw bool) bool {
 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 {
 func (o *baseObject) deleteSym(s *valueSymbol, throw bool) bool {
 	if o.symValues != nil {
 	if o.symValues != nil {
 		if val := o.symValues.get(s); val != 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
 				return false
 			}
 			}
 			o.symValues.remove(s)
 			o.symValues.remove(s)
@@ -431,7 +434,7 @@ func (o *baseObject) deleteSym(s *valueSymbol, throw bool) bool {
 	return true
 	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 val, exists := o.values[name]; exists {
 		if !o.checkDelete(name, val, throw) {
 		if !o.checkDelete(name, val, throw) {
 			return false
 			return false
@@ -461,7 +464,7 @@ func (o *baseObject) setProto(proto *Object, throw bool) bool {
 	return true
 	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]
 	ownDesc := o.values[name]
 	if ownDesc == nil {
 	if ownDesc == nil {
 		if proto := o.prototype; proto != 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 {
 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 {
 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
 	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 != nil {
 		if prop, ok := prop.(*valueProperty); ok {
 		if prop, ok := prop.(*valueProperty); ok {
 			if !prop.isWritable() {
 			if !prop.isWritable() {
@@ -580,12 +583,12 @@ func (o *baseObject) _setForeignIdx(idx valueInt, prop, val, receiver Value, thr
 	return false, false
 	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)
 	return o._setForeignStr(name, o.values[name], val, receiver, throw)
 }
 }
 
 
 func (o *baseObject) setForeignIdx(name valueInt, val, receiver Value, throw bool) (bool, bool) {
 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) {
 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
 	return false
 }
 }
 
 
-func (o *baseObject) hasOwnPropertyStr(name string) bool {
+func (o *baseObject) hasOwnPropertyStr(name unistring.String) bool {
 	_, exists := o.values[name]
 	_, exists := o.values[name]
 	return exists
 	return exists
 }
 }
 
 
 func (o *baseObject) hasOwnPropertyIdx(idx valueInt) bool {
 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)
 	getterObj, _ := descr.Getter.(*Object)
 	setterObj, _ := descr.Setter.(*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]
 	existingVal := o.values[name]
 	if v, ok := o._defineOwnProperty(name, existingVal, descr, throw); ok {
 	if v, ok := o._defineOwnProperty(name, existingVal, descr, throw); ok {
 		o.values[name] = v
 		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 {
 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 {
 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 {
 	if o.symValues != nil {
 		existingVal = o.symValues.get(s)
 		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 {
 		if o.symValues == nil {
 			o.symValues = newOrderedMap(&o.val.runtime.hash)
 			o.symValues = newOrderedMap(&o.val.runtime.hash)
 		}
 		}
@@ -765,7 +768,7 @@ func (o *baseObject) defineOwnPropertySym(s *valueSymbol, descr PropertyDescript
 	return false
 	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 {
 	if _, exists := o.values[name]; !exists {
 		o.propNames = append(o.propNames, name)
 		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)
 	prop := valueProp(value, writable, enumerable, configurable)
 	o._put(name, prop)
 	o._put(name, prop)
 	return prop
 	return prop
@@ -810,7 +813,7 @@ func (o *baseObject) tryExoticToPrimitive(hint string) Value {
 }
 }
 
 
 func (o *baseObject) tryPrimitive(methodName 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 {
 		if call, ok := method.self.assertCallable(); ok {
 			v := call(FunctionCall{
 			v := call(FunctionCall{
 				This: o.val,
 				This: o.val,
@@ -905,7 +908,7 @@ func (o *baseObject) export() interface{} {
 	m := make(map[string]interface{})
 	m := make(map[string]interface{})
 	for _, itemName := range o.ownKeys(false, nil) {
 	for _, itemName := range o.ownKeys(false, nil) {
 		itemNameStr := itemName.String()
 		itemNameStr := itemName.String()
-		v := o.val.self.getStr(itemNameStr, nil)
+		v := o.val.self.getStr(itemName.string(), nil)
 		if v != nil {
 		if v != nil {
 			m[itemNameStr] = v.Export()
 			m[itemNameStr] = v.Export()
 		} else {
 		} else {
@@ -929,21 +932,21 @@ const (
 )
 )
 
 
 type propIterItem struct {
 type propIterItem struct {
-	name       string
+	name       unistring.String
 	value      Value // set only when enumerable == _ENUM_UNKNOWN
 	value      Value // set only when enumerable == _ENUM_UNKNOWN
 	enumerable enumerableFlag
 	enumerable enumerableFlag
 }
 }
 
 
 type objectPropIter struct {
 type objectPropIter struct {
 	o         *baseObject
 	o         *baseObject
-	propNames []string
+	propNames []unistring.String
 	idx       int
 	idx       int
 }
 }
 
 
 type propFilterIter struct {
 type propFilterIter struct {
 	wrapped iterNextFunc
 	wrapped iterNextFunc
 	all     bool
 	all     bool
-	seen    map[string]bool
+	seen    map[unistring.String]bool
 }
 }
 
 
 func (i *propFilterIter) next() (propIterItem, iterNextFunc) {
 func (i *propFilterIter) next() (propIterItem, iterNextFunc) {
@@ -989,7 +992,7 @@ func (i *objectPropIter) next() (propIterItem, iterNextFunc) {
 func (o *baseObject) enumerate() iterNextFunc {
 func (o *baseObject) enumerate() iterNextFunc {
 	return (&propFilterIter{
 	return (&propFilterIter{
 		wrapped: o.val.self.enumerateUnfiltered(),
 		wrapped: o.val.self.enumerateUnfiltered(),
-		seen:    make(map[string]bool),
+		seen:    make(map[unistring.String]bool),
 	}).next
 	}).next
 }
 }
 
 
@@ -997,7 +1000,7 @@ func (o *baseObject) ownIter() iterNextFunc {
 	if len(o.propNames) > o.lastSortedPropLen {
 	if len(o.propNames) > o.lastSortedPropLen {
 		o.fixPropOrder()
 		o.fixPropOrder()
 	}
 	}
-	propNames := make([]string, len(o.propNames))
+	propNames := make([]unistring.String, len(o.propNames))
 	copy(propNames, o.propNames)
 	copy(propNames, o.propNames)
 	return (&objectPropIter{
 	return (&objectPropIter{
 		o:         o,
 		o:         o,
@@ -1069,7 +1072,7 @@ func (o *baseObject) ownKeys(all bool, keys []Value) []Value {
 	}
 	}
 	if all {
 	if all {
 		for _, k := range o.propNames {
 		for _, k := range o.propNames {
-			keys = append(keys, newStringValue(k))
+			keys = append(keys, stringValueFromRaw(k))
 		}
 		}
 	} else {
 	} else {
 		for _, k := range o.propNames {
 		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 {
 			if prop, ok := prop.(*valueProperty); ok && !prop.enumerable {
 				continue
 				continue
 			}
 			}
-			keys = append(keys, newStringValue(k))
+			keys = append(keys, stringValueFromRaw(k))
 		}
 		}
 	}
 	}
 	return keys
 	return keys
 }
 }
 
 
-func (o *baseObject) ownSymbols() (res []Value) {
+func (o *baseObject) ownSymbols(all bool, accum []Value) []Value {
 	if o.symValues != nil {
 	if o.symValues != nil {
 		iter := o.symValues.newIter()
 		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 {
 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 {
 func (o *baseObject) hasInstance(Value) bool {
@@ -1141,7 +1154,7 @@ func (o *Object) get(p Value, receiver Value) Value {
 	case *valueSymbol:
 	case *valueSymbol:
 		return o.self.getSym(p, receiver)
 		return o.self.getSym(p, receiver)
 	default:
 	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:
 	case *valueSymbol:
 		return o.self.getOwnPropSym(p)
 		return o.self.getOwnPropSym(p)
 	default:
 	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:
 	case *valueSymbol:
 		return o.self.hasOwnPropertySym(p)
 		return o.self.hasOwnPropertySym(p)
 	default:
 	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:
 	case *valueSymbol:
 		return o.self.hasPropertySym(p)
 		return o.self.hasPropertySym(p)
 	default:
 	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 {
 	if receiver == o {
 		return o.self.setOwnStr(name, val, throw)
 		return o.self.setOwnStr(name, val, throw)
 	} else {
 	} else {
@@ -1222,7 +1235,7 @@ func (o *Object) set(name Value, val, receiver Value, throw bool) bool {
 	case *valueSymbol:
 	case *valueSymbol:
 		return o.setSym(name, val, receiver, throw)
 		return o.setSym(name, val, receiver, throw)
 	default:
 	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:
 	case *valueSymbol:
 		return o.self.setOwnSym(name, val, throw)
 		return o.self.setOwnSym(name, val, throw)
 	default:
 	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:
 	case *valueSymbol:
 		return o.self.deleteSym(n, throw)
 		return o.self.deleteSym(n, throw)
 	default:
 	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:
 	case *valueSymbol:
 		return o.self.defineOwnPropertySym(n, desc, throw)
 		return o.self.defineOwnPropertySym(n, desc, throw)
 	default:
 	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
 package goja
 
 
+import "github.com/dop251/goja/unistring"
+
 type argumentsObject struct {
 type argumentsObject struct {
 	baseObject
 	baseObject
 	length int
 	length int
@@ -10,11 +12,11 @@ type mappedProperty struct {
 	v *Value
 	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)
 	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 {
 	if mapped, ok := a.values[name].(*mappedProperty); ok {
 		return *mapped.v
 		return *mapped.v
 	}
 	}
@@ -27,7 +29,7 @@ func (a *argumentsObject) init() {
 	a._putProp("length", intToValue(int64(a.length)), true, false, true)
 	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, ok := a.values[name].(*mappedProperty); ok {
 		if !prop.writable {
 		if !prop.writable {
 			a.val.runtime.typeErrorResult(throw, "Property is not writable: %s", name)
 			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)
 	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)
 	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)
 	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 prop, ok := a.values[name].(*mappedProperty); ok {
 		if !a.checkDeleteProp(name, &prop.valueProperty, throw) {
 		if !a.checkDeleteProp(name, &prop.valueProperty, throw) {
 			return false
 			return false
@@ -89,7 +91,7 @@ func (a *argumentsObject) enumerateUnfiltered() iterNextFunc {
 	}).next)
 	}).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 {
 	if mapped, ok := a.values[name].(*mappedProperty); ok {
 		existing := &valueProperty{
 		existing := &valueProperty{
 			configurable: mapped.configurable,
 			configurable: mapped.configurable,

+ 23 - 19
object_gomap.go

@@ -2,6 +2,8 @@ package goja
 
 
 import (
 import (
 	"reflect"
 	"reflect"
+
+	"github.com/dop251/goja/unistring"
 )
 )
 
 
 type objectGoMapSimple struct {
 type objectGoMapSimple struct {
@@ -24,23 +26,24 @@ func (o *objectGoMapSimple) _getStr(name string) Value {
 	return o.val.runtime.ToValue(v)
 	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 v
 	}
 	}
 	return o.baseObject.getStr(name, receiver)
 	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 v
 	}
 	}
 	return nil
 	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
 		return true
 	}
 	}
 	if proto := o.prototype; proto != nil {
 	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)
 		o.val.runtime.typeErrorResult(throw, "Cannot add property %s, object is not extensible", name)
 		return false
 		return false
 	} else {
 	} else {
-		o.data[name] = val.Export()
+		o.data[n] = val.Export()
 	}
 	}
 	return true
 	return true
 }
 }
@@ -66,8 +69,8 @@ func trueValIfPresent(present bool) Value {
 	return nil
 	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 {
 func (o *objectGoMapSimple) _hasStr(name string) bool {
@@ -75,21 +78,22 @@ func (o *objectGoMapSimple) _hasStr(name string) bool {
 	return exists
 	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) {
 	if !o.val.runtime.checkHostObjectPropertyDescr(name, descr, throw) {
 		return false
 		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
 		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
 	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
 	return true
 }
 }
 
 
@@ -127,7 +131,7 @@ func (i *gomapPropIter) next() (propIterItem, iterNextFunc) {
 		name := i.propNames[i.idx]
 		name := i.propNames[i.idx]
 		i.idx++
 		i.idx++
 		if _, exists := i.o.data[name]; exists {
 		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
 package goja
 
 
-import "reflect"
+import (
+	"reflect"
+
+	"github.com/dop251/goja/unistring"
+)
 
 
 type objectGoMapReflect struct {
 type objectGoMapReflect struct {
 	objectGoReflect
 	objectGoReflect
@@ -54,8 +58,8 @@ func (o *objectGoMapReflect) _getStr(name string) Value {
 	return nil
 	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 v
 	}
 	}
 	return o.objectGoReflect.getStr(name, receiver)
 	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)
 	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{
 		return &valueProperty{
 			value:      v,
 			value:      v,
 			writable:   true,
 			writable:   true,
@@ -87,7 +91,7 @@ func (o *objectGoMapReflect) getOwnPropIdx(idx valueInt) Value {
 			enumerable: true,
 			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) {
 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
 	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 !key.IsValid() || !o.value.MapIndex(key).IsValid() {
 		if proto := o.prototype; proto != nil {
 		if proto := o.prototype; proto != nil {
 			// we know it's foreign because prototype loops are not allowed
 			// 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
 		// new property
 		if !o.extensible {
 		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
 			return false
 		} else {
 		} else {
 			if throw && !key.IsValid() {
 			if throw && !key.IsValid() {
-				o.strToKey(name, true)
+				o.strToKey(n, true)
 				return false
 				return false
 			}
 			}
 		}
 		}
@@ -165,7 +170,7 @@ func (o *objectGoMapReflect) setOwnIdx(idx valueInt, val Value, throw bool) bool
 	return true
 	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)
 	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)
 	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) {
 	if !o.val.runtime.checkHostObjectPropertyDescr(name, descr, throw) {
 		return false
 		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 {
 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 false
 	}
 	}
 
 
 	return o._put(o.toKey(idx, throw), descr.Value, throw)
 	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() {
 	if key.IsValid() && o.value.MapIndex(key).IsValid() {
 		return true
 		return true
 	}
 	}
@@ -205,8 +210,8 @@ func (o *objectGoMapReflect) hasOwnPropertyIdx(idx valueInt) bool {
 	return false
 	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() {
 	if !key.IsValid() {
 		return false
 		return false
 	}
 	}
@@ -235,7 +240,7 @@ func (i *gomapReflectPropIter) next() (propIterItem, iterNextFunc) {
 		v := i.o.value.MapIndex(key)
 		v := i.o.value.MapIndex(key)
 		i.idx++
 		i.idx++
 		if v.IsValid() {
 		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)
 		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)
 		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"
 	"fmt"
 	"go/ast"
 	"go/ast"
 	"reflect"
 	"reflect"
+
+	"github.com/dop251/goja/unistring"
 )
 )
 
 
 // JsonEncodable allows custom JSON encoding by JSON.stringify()
 // JsonEncodable allows custom JSON encoding by JSON.stringify()
@@ -83,8 +85,8 @@ func (o *objectGoReflect) valueOfFunc(FunctionCall) Value {
 	return o.toPrimitive()
 	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 v
 	}
 	}
 	return o.baseObject.getStr(name, receiver)
 	return o.baseObject.getStr(name, receiver)
@@ -128,9 +130,10 @@ func (o *objectGoReflect) _get(name string) Value {
 	return nil
 	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 o.value.Kind() == reflect.Struct {
-		if v := o._getField(name); v.IsValid() {
+		if v := o._getField(n); v.IsValid() {
 			return &valueProperty{
 			return &valueProperty{
 				value:      o.val.runtime.ToValue(o.getAddr(v).Interface()),
 				value:      o.val.runtime.ToValue(o.getAddr(v).Interface()),
 				writable:   v.CanSet(),
 				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{
 		return &valueProperty{
 			value:      o.val.runtime.ToValue(v.Interface()),
 			value:      o.val.runtime.ToValue(v.Interface()),
 			enumerable: true,
 			enumerable: true,
@@ -149,8 +152,8 @@ func (o *objectGoReflect) getOwnPropStr(name string) Value {
 	return nil
 	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 !has {
 		if res, ok := o._setForeignStr(name, nil, val, o.val, throw); !ok {
 		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)
 			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
 	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) {
 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
 	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 value
 	}
 	}
 	return o.baseObject._putProp(name, value, writable, enumerable, configurable)
 	return o.baseObject._putProp(name, value, writable, enumerable, configurable)
 }
 }
 
 
-func (r *Runtime) checkHostObjectPropertyDescr(name string, descr PropertyDescriptor, throw bool) bool {
+func (r *Runtime) checkHostObjectPropertyDescr(name unistring.String, descr PropertyDescriptor, throw bool) bool {
 	if descr.Getter != nil || descr.Setter != nil {
 	if descr.Getter != nil || descr.Setter != nil {
 		r.typeErrorResult(throw, "Host objects do not support accessor properties")
 		r.typeErrorResult(throw, "Host objects do not support accessor properties")
 		return false
 		return false
@@ -208,10 +211,11 @@ func (r *Runtime) checkHostObjectPropertyDescr(name string, descr PropertyDescri
 	return true
 	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 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
 			return false
 		} else {
 		} else {
 			return ok
 			return ok
@@ -232,8 +236,8 @@ func (o *objectGoReflect) _has(name string) bool {
 	return false
 	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 {
 func (o *objectGoReflect) _toNumber() Value {
@@ -293,9 +297,10 @@ func (o *objectGoReflect) toPrimitive() Value {
 	return o.toPrimitiveString()
 	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 false
 	}
 	}
 	return o.baseObject.deleteStr(name, throw)
 	return o.baseObject.deleteStr(name, throw)
@@ -311,7 +316,7 @@ func (i *goreflectPropIter) nextField() (propIterItem, iterNextFunc) {
 	if i.idx < len(names) {
 	if i.idx < len(names) {
 		name := names[i.idx]
 		name := names[i.idx]
 		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
 	i.idx = 0
@@ -323,7 +328,7 @@ func (i *goreflectPropIter) nextMethod() (propIterItem, iterNextFunc) {
 	if i.idx < len(names) {
 	if i.idx < len(names) {
 		name := names[i.idx]
 		name := names[i.idx]
 		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
 	return propIterItem{}, nil

+ 17 - 0
object_goreflect_test.go

@@ -1047,3 +1047,20 @@ func TestGoObj__Proto__(t *testing.T) {
 		t.Fatal(err)
 		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
 package goja
 
 
 import (
 import (
-	"math"
-	"math/bits"
 	"reflect"
 	"reflect"
 	"strconv"
 	"strconv"
+
+	"github.com/dop251/goja/unistring"
 )
 )
 
 
 type objectGoSlice struct {
 type objectGoSlice struct {
@@ -28,7 +28,7 @@ func (o *objectGoSlice) updateLen() {
 	o.lengthProp.value = intToValue(int64(len(*o.data)))
 	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
 	var ownProp Value
 	if idx := strToGoIdx(name); idx >= 0 && idx < len(*o.data) {
 	if idx := strToGoIdx(name); idx >= 0 && idx < len(*o.data) {
 		v := (*o.data)[idx]
 		v := (*o.data)[idx]
@@ -54,7 +54,7 @@ func (o *objectGoSlice) getIdx(idx valueInt, receiver Value) Value {
 	return nil
 	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 := strToGoIdx(name); idx >= 0 {
 		if idx < len(*o.data) {
 		if idx < len(*o.data) {
 			v := o.val.runtime.ToValue((*o.data)[idx])
 			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()
 	(*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 {
 func (o *objectGoSlice) putLength(v Value, throw bool) bool {
 	newLen := toInt(toLength(v))
 	newLen := toInt(toLength(v))
 	curLen := len(*o.data)
 	curLen := len(*o.data)
@@ -171,7 +162,7 @@ func (o *objectGoSlice) setOwnIdx(idx valueInt, val Value, throw bool) bool {
 		}
 		}
 		o.putIdx(i, val, throw)
 		o.putIdx(i, val, throw)
 	} else {
 	} else {
-		name := idx.String()
+		name := idx.string()
 		if res, ok := o._setForeignStr(name, nil, val, o.val, throw); !ok {
 		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)
 			o.val.runtime.typeErrorResult(throw, "Can't set property '%s' on Go slice", name)
 			return false
 			return false
@@ -182,7 +173,7 @@ func (o *objectGoSlice) setOwnIdx(idx valueInt, val Value, throw bool) bool {
 	return true
 	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 := strToGoIdx(name); idx >= 0 {
 		if idx >= len(*o.data) {
 		if idx >= len(*o.data) {
 			if res, ok := o._setForeignStr(name, nil, val, o.val, throw); ok {
 			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)
 	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)
 	return o._setForeignStr(name, trueValIfPresent(o.hasOwnPropertyStr(name)), val, receiver, throw)
 }
 }
 
 
@@ -219,7 +210,7 @@ func (o *objectGoSlice) hasOwnPropertyIdx(idx valueInt) bool {
 	return false
 	return false
 }
 }
 
 
-func (o *objectGoSlice) hasOwnPropertyStr(name string) bool {
+func (o *objectGoSlice) hasOwnPropertyStr(name unistring.String) bool {
 	if idx := strToIdx64(name); idx >= 0 {
 	if idx := strToIdx64(name); idx >= 0 {
 		return idx < int64(len(*o.data))
 		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 {
 func (o *objectGoSlice) defineOwnPropertyIdx(idx valueInt, descr PropertyDescriptor, throw bool) bool {
 	if i := toInt(int64(idx)); i >= 0 {
 	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
 			return false
 		}
 		}
 		val := descr.Value
 		val := descr.Value
@@ -242,7 +233,7 @@ func (o *objectGoSlice) defineOwnPropertyIdx(idx valueInt, descr PropertyDescrip
 	return false
 	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 idx := strToGoIdx(name); idx >= 0 {
 		if !o.val.runtime.checkHostObjectPropertyDescr(name, descr, throw) {
 		if !o.val.runtime.checkHostObjectPropertyDescr(name, descr, throw) {
 			return false
 			return false
@@ -275,7 +266,7 @@ func (o *objectGoSlice) toPrimitive() Value {
 	return o.toPrimitiveString()
 	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 := strToIdx64(name); idx >= 0 {
 		if idx < int64(len(*o.data)) {
 		if idx < int64(len(*o.data)) {
 			o.val.runtime.typeErrorResult(throw, "Can't delete from Go slice")
 			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) {
 	if i.idx < i.limit && i.idx < len(*i.o.data) {
 		name := strconv.Itoa(i.idx)
 		name := strconv.Itoa(i.idx)
 		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
 	return propIterItem{}, nil

+ 15 - 13
object_goslice_reflect.go

@@ -3,6 +3,8 @@ package goja
 import (
 import (
 	"reflect"
 	"reflect"
 	"strconv"
 	"strconv"
+
+	"github.com/dop251/goja/unistring"
 )
 )
 
 
 type objectGoSliceReflect struct {
 type objectGoSliceReflect struct {
@@ -32,7 +34,7 @@ func (o *objectGoSliceReflect) _hasIdx(idx valueInt) bool {
 	return false
 	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()) {
 	if idx := strToIdx64(name); idx >= 0 && idx < int64(o.value.Len()) {
 		return true
 		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() {
 	if idx := toInt(int64(idx)); idx >= 0 && idx < o.value.Len() {
 		return o._getIdx(idx)
 		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
 	var ownProp Value
 	if idx := strToGoIdx(name); idx >= 0 && idx < o.value.Len() {
 	if idx := strToGoIdx(name); idx >= 0 && idx < o.value.Len() {
 		ownProp = o._getIdx(idx)
 		ownProp = o._getIdx(idx)
@@ -66,7 +68,7 @@ func (o *objectGoSliceReflect) getStr(name string, receiver Value) Value {
 	return o.getStrWithOwnProp(ownProp, name, receiver)
 	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 := strToGoIdx(name); idx >= 0 {
 		if idx < o.value.Len() {
 		if idx < o.value.Len() {
 			return &valueProperty{
 			return &valueProperty{
@@ -180,7 +182,7 @@ func (o *objectGoSliceReflect) setOwnIdx(idx valueInt, val Value, throw bool) bo
 		}
 		}
 		o.putIdx(i, val, throw)
 		o.putIdx(i, val, throw)
 	} else {
 	} else {
-		name := idx.String()
+		name := idx.string()
 		if res, ok := o._setForeignStr(name, nil, val, o.val, throw); !ok {
 		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)
 			o.val.runtime.typeErrorResult(throw, "Can't set property '%s' on Go slice", name)
 			return false
 			return false
@@ -191,7 +193,7 @@ func (o *objectGoSliceReflect) setOwnIdx(idx valueInt, val Value, throw bool) bo
 	return true
 	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 := strToGoIdx(name); idx >= 0 {
 		if idx >= o.value.Len() {
 		if idx >= o.value.Len() {
 			if res, ok := o._setForeignStr(name, nil, val, o.val, throw); ok {
 			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)
 	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)
 	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)
 	return o._hasIdx(idx)
 }
 }
 
 
-func (o *objectGoSliceReflect) hasOwnPropertyStr(name string) bool {
+func (o *objectGoSliceReflect) hasOwnPropertyStr(name unistring.String) bool {
 	if o._hasStr(name) {
 	if o._hasStr(name) {
 		return true
 		return true
 	}
 	}
-	return o.objectGoReflect._has(name)
+	return o.objectGoReflect._has(name.String())
 }
 }
 
 
 func (o *objectGoSliceReflect) defineOwnPropertyIdx(idx valueInt, descr PropertyDescriptor, throw bool) bool {
 func (o *objectGoSliceReflect) defineOwnPropertyIdx(idx valueInt, descr PropertyDescriptor, throw bool) bool {
 	if i := toInt(int64(idx)); i >= 0 {
 	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
 			return false
 		}
 		}
 		val := descr.Value
 		val := descr.Value
@@ -248,7 +250,7 @@ func (o *objectGoSliceReflect) defineOwnPropertyIdx(idx valueInt, descr Property
 	return false
 	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 idx := strToGoIdx(name); idx >= 0 {
 		if !o.val.runtime.checkHostObjectPropertyDescr(name, descr, throw) {
 		if !o.val.runtime.checkHostObjectPropertyDescr(name, descr, throw) {
 			return false
 			return false
@@ -278,7 +280,7 @@ func (o *objectGoSliceReflect) toPrimitive() Value {
 	return o.toPrimitiveString()
 	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 := strToIdx64(name); idx >= 0 {
 		if idx < int64(o.value.Len()) {
 		if idx < int64(o.value.Len()) {
 			o.val.runtime.typeErrorResult(throw, "Can't delete from Go slice")
 			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() {
 	if i.idx < i.limit && i.idx < i.o.value.Len() {
 		name := strconv.Itoa(i.idx)
 		name := strconv.Itoa(i.idx)
 		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()()
 	return i.o.objectGoReflect.enumerateUnfiltered()()

+ 16 - 12
object_lazy.go

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

+ 20 - 27
parser/expression.go

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

+ 213 - 92
parser/lexer.go

@@ -1,27 +1,19 @@
 package parser
 package parser
 
 
 import (
 import (
-	"bytes"
 	"errors"
 	"errors"
 	"fmt"
 	"fmt"
-	"regexp"
 	"strconv"
 	"strconv"
 	"strings"
 	"strings"
 	"unicode"
 	"unicode"
+	"unicode/utf16"
 	"unicode/utf8"
 	"unicode/utf8"
 
 
 	"github.com/dop251/goja/file"
 	"github.com/dop251/goja/file"
 	"github.com/dop251/goja/token"
 	"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 {
 func isDecimalDigit(chr rune) bool {
 	return '0' <= chr && chr <= '9'
 	return '0' <= chr && chr <= '9'
 }
 }
@@ -55,45 +47,65 @@ func isIdentifierPart(chr rune) bool {
 		chr >= utf8.RuneSelf && (unicode.IsLetter(chr) || unicode.IsDigit(chr))
 		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
 	offset := self.chrOffset
-	parse := false
+	hasEscape := false
+	isUnicode := false
+	length := 0
 	for isIdentifierPart(self.chr) {
 	for isIdentifierPart(self.chr) {
-		if self.chr == '\\' {
+		r := self.chr
+		length++
+		if r == '\\' {
+			hasEscape = true
 			distance := self.chrOffset - offset
 			distance := self.chrOffset - offset
 			self.read()
 			self.read()
 			if self.chr != 'u' {
 			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
 			var value rune
 			for j := 0; j < 4; j++ {
 			for j := 0; j < 4; j++ {
 				self.read()
 				self.read()
 				decimal, ok := hex2decimal(byte(self.chr))
 				decimal, ok := hex2decimal(byte(self.chr))
 				if !ok {
 				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
 				value = value<<4 | decimal
 			}
 			}
 			if value == '\\' {
 			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 {
 			} else if distance == 0 {
 				if !isIdentifierStart(value) {
 				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 {
 			} else if distance > 0 {
 				if !isIdentifierPart(value) {
 				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()
 		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
 // 7.2
@@ -118,7 +130,52 @@ func isLineTerminator(chr rune) bool {
 	return false
 	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
 	self.implicitSemicolon = false
 
 
@@ -131,30 +188,43 @@ func (self *_parser) scan() (tkn token.Token, literal string, idx file.Idx) {
 		switch chr := self.chr; {
 		switch chr := self.chr; {
 		case isIdentifierStart(chr):
 		case isIdentifierStart(chr):
 			var err error
 			var err error
-			literal, err = self.scanIdentifier()
+			var hasEscape bool
+			literal, parsedLiteral, hasEscape, err = self.scanIdentifier()
 			if err != nil {
 			if err != nil {
 				tkn = token.ILLEGAL
 				tkn = token.ILLEGAL
 				break
 				break
 			}
 			}
-			if len(literal) > 1 {
+			if len(parsedLiteral) > 1 {
 				// Keywords are longer than 1 character, avoid lookup otherwise
 				// Keywords are longer than 1 character, avoid lookup otherwise
 				var strict bool
 				var strict bool
-				tkn, strict = token.IsKeyword(literal)
+				tkn, strict = token.IsKeyword(string(parsedLiteral))
 
 
 				switch tkn {
 				switch tkn {
 
 
 				case 0: // Not a keyword
 				case 0: // Not a keyword
-					if literal == "true" || literal == "false" {
+					if parsedLiteral == "true" || parsedLiteral == "false" {
+						if hasEscape {
+							tkn = token.ILLEGAL
+							return
+						}
 						self.insertSemicolon = true
 						self.insertSemicolon = true
 						tkn = token.BOOLEAN
 						tkn = token.BOOLEAN
 						return
 						return
-					} else if literal == "null" {
+					} else if parsedLiteral == "null" {
+						if hasEscape {
+							tkn = token.ILLEGAL
+							return
+						}
 						self.insertSemicolon = true
 						self.insertSemicolon = true
 						tkn = token.NULL
 						tkn = token.NULL
 						return
 						return
 					}
 					}
 
 
 				case token.KEYWORD:
 				case token.KEYWORD:
+					if hasEscape {
+						tkn = token.ILLEGAL
+						return
+					}
 					tkn = token.KEYWORD
 					tkn = token.KEYWORD
 					if strict {
 					if strict {
 						// TODO If strict and in strict mode, then this is not a break
 						// 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.RETURN,
 					token.CONTINUE,
 					token.CONTINUE,
 					token.DEBUGGER:
 					token.DEBUGGER:
+					if hasEscape {
+						tkn = token.ILLEGAL
+						return
+					}
 					self.insertSemicolon = true
 					self.insertSemicolon = true
 					return
 					return
 
 
 				default:
 				default:
+					if hasEscape {
+						tkn = token.ILLEGAL
+					}
 					return
 					return
 
 
 				}
 				}
@@ -286,7 +363,7 @@ func (self *_parser) scan() (tkn token.Token, literal string, idx file.Idx) {
 				insertSemicolon = true
 				insertSemicolon = true
 				tkn = token.STRING
 				tkn = token.STRING
 				var err error
 				var err error
-				literal, err = self.scanString(self.chrOffset - 1)
+				literal, parsedLiteral, err = self.scanString(self.chrOffset-1, true)
 				if err != nil {
 				if err != nil {
 					tkn = token.ILLEGAL
 					tkn = token.ILLEGAL
 				}
 				}
@@ -360,14 +437,6 @@ func (self *_parser) switch6(tkn0, tkn1 token.Token, chr2 rune, tkn2, tkn3 token
 	return tkn0
 	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 {
 func (self *_parser) _peek() rune {
 	if self.offset+1 < self.length {
 	if self.offset+1 < self.length {
 		return rune(self.str[self.offset+1])
 		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
 	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()
 		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':
 	case 'x':
 		self.read()
 		self.read()
 		length, base = 2, 16
 		length, base = 2, 16
@@ -496,24 +576,34 @@ func (self *_parser) scanEscape(quote rune) {
 		length, base = 4, 16
 		length, base = 4, 16
 	default:
 	default:
 		self.read() // Always make progress
 		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])
 	quote := rune(self.str[offset])
-
+	length := 0
+	isUnicode := false
 	for self.chr != quote {
 	for self.chr != quote {
 		chr := self.chr
 		chr := self.chr
 		if chr == '\n' || chr == '\r' || chr == '\u2028' || chr == '\u2029' || chr < 0 {
 		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()
 		self.read()
 		if chr == '\\' {
 		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
 					goto newline
 				}
 				}
-				self.read()
+				self.scanNewline()
 			} else {
 			} else {
-				self.scanEscape(quote)
+				l, u := self.scanEscape(quote)
+				length += l
+				if u {
+					isUnicode = true
+				}
 			}
 			}
+			continue
 		} else if chr == '[' && quote == '/' {
 		} else if chr == '[' && quote == '/' {
 			// Allow a slash (/) in a bracket character class ([...])
 			// Allow a slash (/) in a bracket character class ([...])
 			// TODO Fix this, this is hacky...
 			// TODO Fix this, this is hacky...
@@ -536,21 +631,31 @@ func (self *_parser) scanString(offset int) (string, error) {
 		} else if chr == ']' && quote == -1 {
 		} else if chr == ']' && quote == -1 {
 			quote = '/'
 			quote = '/'
 		}
 		}
+		if chr >= utf8.RuneSelf {
+			isUnicode = true
+			if chr > 0xFFFF {
+				length++
+			}
+		}
+		length++
 	}
 	}
 
 
 	// " ' /
 	// " ' /
 	self.read()
 	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:
 newline:
 	self.scanNewline()
 	self.scanNewline()
-	err := "String not terminated"
+	errStr := "String not terminated"
 	if quote == '/' {
 	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() {
 func (self *_parser) scanNewline() {
@@ -617,21 +722,16 @@ error:
 	return nil, errors.New("Illegal numeric literal")
 	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
 	str := literal
-	buffer := bytes.NewBuffer(make([]byte, 0, 3*len(literal)/2))
-	var surrogate rune
-S:
 	for len(str) > 0 {
 	for len(str) > 0 {
 		switch chr := str[0]; {
 		switch chr := str[0]; {
 		// We do not explicitly handle the case of the quote
 		// 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
 		// This assumes we're already passed a partially well-formed literal
 		case chr >= utf8.RuneSelf:
 		case chr >= utf8.RuneSelf:
 			chr, size := utf8.DecodeRuneInString(str)
 			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:]
 			str = str[size:]
 			continue
 			continue
 		case chr != '\\':
 		case chr != '\\':
-			buffer.WriteByte(chr)
+			if unicode {
+				chars = append(chars, uint16(chr))
+			} else {
+				sb.WriteByte(chr)
+			}
 			str = str[1:]
 			str = str[1:]
 			continue
 			continue
 		}
 		}
@@ -736,20 +845,32 @@ S:
 			default:
 			default:
 				value = rune(chr)
 				value = rune(chr)
 			}
 			}
-			if surrogate != 0 {
-				value = utf16.DecodeRune(surrogate, value)
-				surrogate = 0
+		}
+		if unicode {
+			if value <= 0xFFFF {
+				chars = append(chars, uint16(value))
 			} else {
 			} 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) {
 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/file"
 	"github.com/dop251/goja/token"
 	"github.com/dop251/goja/token"
+	"github.com/dop251/goja/unistring"
 )
 )
 
 
 func TestLexer(t *testing.T) {
 func TestLexer(t *testing.T) {
@@ -17,13 +18,13 @@ func TestLexer(t *testing.T) {
 		test := func(src string, test ...interface{}) {
 		test := func(src string, test ...interface{}) {
 			parser := setup(src)
 			parser := setup(src)
 			for len(test) > 0 {
 			for len(test) > 0 {
-				tkn, literal, idx := parser.scan()
+				tkn, literal, _, idx := parser.scan()
 				if len(test) > 0 {
 				if len(test) > 0 {
 					is(tkn, test[0].(token.Token))
 					is(tkn, test[0].(token.Token))
 					test = test[1:]
 					test = test[1:]
 				}
 				}
 				if len(test) > 0 {
 				if len(test) > 0 {
-					is(literal, test[0].(string))
+					is(literal, unistring.String(test[0].(string)))
 					test = test[1:]
 					test = test[1:]
 				}
 				}
 				if len(test) > 0 {
 				if len(test) > 0 {
@@ -184,7 +185,7 @@ Second line \
 
 
 		test(`var \u0024 = 1`,
 		test(`var \u0024 = 1`,
 			token.VAR, "var", 1,
 			token.VAR, "var", 1,
-			token.IDENTIFIER, "$", 5,
+			token.IDENTIFIER, "\\u0024", 5,
 			token.ASSIGN, "", 12,
 			token.ASSIGN, "", 12,
 			token.NUMBER, "1", 14,
 			token.NUMBER, "1", 14,
 			token.EOF, "", 15,
 			token.EOF, "", 15,
@@ -368,7 +369,8 @@ Second line \
 		)
 		)
 
 
 		test(`"\x0G"`,
 		test(`"\x0G"`,
-			token.STRING, "\"\\x0G\"", 1,
+			token.ILLEGAL, "\"\\x0G\"", 1,
+			//token.STRING, "\"\\x0G\"", 1,
 			token.EOF, "", 7,
 			token.EOF, "", 7,
 		)
 		)
 
 

+ 6 - 4
parser/parser.go

@@ -42,6 +42,7 @@ import (
 	"github.com/dop251/goja/ast"
 	"github.com/dop251/goja/ast"
 	"github.com/dop251/goja/file"
 	"github.com/dop251/goja/file"
 	"github.com/dop251/goja/token"
 	"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.
 // 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
 	chrOffset int  // The offset of current character
 	offset    int  // The offset after current character (may be greater than 1)
 	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
 	scope             *_scope
 	insertSemicolon   bool // If we see a newline, then insert an implicit semicolon
 	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() {
 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() {
 func (self *_parser) optionalSemicolon() {

+ 49 - 42
parser/parser_test.go

@@ -8,6 +8,7 @@ import (
 
 
 	"github.com/dop251/goja/ast"
 	"github.com/dop251/goja/ast"
 	"github.com/dop251/goja/file"
 	"github.com/dop251/goja/file"
+	"github.com/dop251/goja/unistring"
 )
 )
 
 
 func firstErr(err error) error {
 func firstErr(err error) error {
@@ -86,9 +87,9 @@ func TestParserErr(t *testing.T) {
 			return program, parser
 			return program, parser
 		}
 		}
 
 
-		program, parser := test("", nil)
+		test("", nil)
 
 
-		program, parser = test(`
+		program, parser := test(`
         var abc;
         var abc;
         break; do {
         break; do {
         } while(true);
         } while(true);
@@ -513,7 +514,7 @@ func TestParser(t *testing.T) {
             abc()
             abc()
         `, nil)
         `, nil)
 
 
-		program := test("", nil)
+		test("", nil)
 
 
 		test("//", nil)
 		test("//", nil)
 
 
@@ -531,7 +532,7 @@ func TestParser(t *testing.T) {
 
 
 		test("new +", "(anonymous): Line 1:5 Unexpected token +")
 		test("new +", "(anonymous): Line 1:5 Unexpected token +")
 
 
-		program = test(";", nil)
+		program := test(";", nil)
 		is(len(program.Body), 1)
 		is(len(program.Body), 1)
 		is(program.Body[0].(*ast.EmptyStatement).Semicolon, file.Idx(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) {
 func Test_parseStringLiteral(t *testing.T) {
 	tt(t, func() {
 	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(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.     ]")
 			"[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
 		// S7.8.4_A4.2_T3
-		test("\\a", "a")
-		test("\u0410", "\u0410")
+		test("'\\a'", "a")
+		test("'\u0410'", "\u0410")
 
 
 		// S7.8.4_A5.1_T1
 		// S7.8.4_A5.1_T1
-		test("\\0", "\u0000")
+		test("'\\0'", "\u0000")
 
 
 		// S8.4_A5
 		// S8.4_A5
-		test("\u0000", "\u0000")
+		test("'\u0000'", "\u0000")
 
 
 		// 15.5.4.20
 		// 15.5.4.20
-		test("'abc'\\\n'def'", "'abc''def'")
+		test("\"'abc'\\\n'def'\"", "'abc''def'")
 
 
 		// 15.5.4.20-4-1
 		// 15.5.4.20-4-1
-		test("'abc'\\\r\n'def'", "'abc''def'")
+		test("\"'abc'\\\r\n'def'\"", "'abc''def'")
 
 
 		// Octal
 		// 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
 		// 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(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 (
 import (
 	"github.com/dop251/goja/ast"
 	"github.com/dop251/goja/ast"
+	"github.com/dop251/goja/unistring"
 )
 )
 
 
 type _scope struct {
 type _scope struct {
@@ -12,7 +13,7 @@ type _scope struct {
 	inFunction      bool
 	inFunction      bool
 	declarationList []ast.Declaration
 	declarationList []ast.Declaration
 
 
-	labels []string
+	labels []unistring.String
 }
 }
 
 
 func (self *_parser) openScope() {
 func (self *_parser) openScope() {
@@ -30,7 +31,7 @@ func (self *_scope) declare(declaration ast.Declaration) {
 	self.declarationList = append(self.declarationList, 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 {
 	for _, label := range self.labels {
 		if label == name {
 		if label == name {
 			return true
 			return true

+ 31 - 22
proxy.go

@@ -1,6 +1,10 @@
 package goja
 package goja
 
 
-import "reflect"
+import (
+	"reflect"
+
+	"github.com/dop251/goja/unistring"
+)
 
 
 // Proxy is a Go wrapper around ECMAScript Proxy. Calling Runtime.ToValue() on it
 // 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.
 // 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]
 		name := i.names[i.idx]
 		i.idx++
 		i.idx++
 		if prop := i.p.val.getOwnProp(name); prop != nil {
 		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 {
 	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"))
 		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{
 		return m(FunctionCall{
 			This:      p.handler,
 			This:      p.handler,
 			Arguments: args,
 			Arguments: args,
@@ -230,8 +234,8 @@ func (p *proxyObject) proxyDefineOwnProperty(name Value, descr PropertyDescripto
 	return false, false
 	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 v
 	}
 	}
 	return p.target.self.defineOwnPropertyStr(name, descr, throw)
 	return p.target.self.defineOwnPropertyStr(name, descr, throw)
@@ -271,8 +275,8 @@ func (p *proxyObject) proxyHas(name Value) (bool, bool) {
 	return false, false
 	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
 		return b
 	}
 	}
 
 
@@ -295,7 +299,7 @@ func (p *proxyObject) hasPropertySym(s *valueSymbol) bool {
 	return p.target.self.hasPropertySym(s)
 	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
 	return p.getOwnPropStr(name) != nil
 }
 }
 
 
@@ -361,8 +365,8 @@ func (p *proxyObject) proxyGetOwnPropertyDescriptor(name Value) (Value, bool) {
 	return nil, false
 	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
 		return v
 	}
 	}
 
 
@@ -385,8 +389,8 @@ func (p *proxyObject) getOwnPropSym(s *valueSymbol) Value {
 	return p.target.self.getOwnPropSym(s)
 	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 v
 	}
 	}
 	return p.target.self.getStr(name, receiver)
 	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
 	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 res
 	}
 	}
 	return p.target.setStr(name, v, p.val, throw)
 	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)
 	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 res, true
 	}
 	}
 	return p.target.setStr(name, v, receiver, throw), 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
 	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
 		return ret
 	}
 	}
 
 
@@ -719,7 +723,7 @@ func (p *proxyObject) filterKeys(vals []Value, all, symbols bool) []Value {
 				}
 				}
 			} else {
 			} else {
 				if _, ok := val.(*valueSymbol); !ok {
 				if _, ok := val.(*valueSymbol); !ok {
-					prop = p.getOwnPropStr(val.String())
+					prop = p.getOwnPropStr(val.string())
 				} else {
 				} else {
 					continue
 					continue
 				}
 				}
@@ -760,12 +764,17 @@ func (p *proxyObject) ownKeys(all bool, _ []Value) []Value { // we can assume ac
 	return p.target.self.ownKeys(all, nil)
 	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 {
 	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 {
 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:
 	case asciiString:
 		match, err = wrapped.FindStringMatch(string(s)[start:])
 		match, err = wrapped.FindStringMatch(string(s)[start:])
 	case unicodeString:
 	case unicodeString:
-		match, err = wrapped.FindRunesMatch(utf16.Decode(s[start:]))
+		match, err = wrapped.FindRunesMatch(utf16.Decode(s[start+1:]))
 	default:
 	default:
 		panic(fmt.Errorf("Unknown string type: %T", s))
 		panic(fmt.Errorf("Unknown string type: %T", s))
 	}
 	}
@@ -208,7 +208,7 @@ func (r *regexp2Wrapper) MatchString(s valueString) bool {
 		matched, _ := wrapped.MatchString(string(s))
 		matched, _ := wrapped.MatchString(string(s))
 		return matched
 		return matched
 	case unicodeString:
 	case unicodeString:
-		matched, _ := wrapped.MatchRunes(utf16.Decode(s))
+		matched, _ := wrapped.MatchRunes(utf16.Decode(s[1:]))
 		return matched
 		return matched
 	default:
 	default:
 		panic(fmt.Errorf("Unknown string type: %T", s))
 		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++ {
 	for index := 0; index < captureCount; index++ {
 		offset := index << 1
 		offset := index << 1
 		if result[offset] >= lowerBound {
 		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]
 			lowerBound = result[offset]
 		} else {
 		} else {
 			valueArray[index] = _undefined
 			valueArray[index] = _undefined
@@ -311,7 +311,7 @@ func (r *regexpObject) execRegexp(target valueString) (match bool, result []int)
 	if !r.global && !r.sticky {
 	if !r.global && !r.sticky {
 		index = 0
 		index = 0
 	}
 	}
-	if index >= 0 && index <= target.length() {
+	if index >= 0 && index <= int64(target.length()) {
 		result = r.pattern.FindSubmatchIndex(target, int(index))
 		result = r.pattern.FindSubmatchIndex(target, int(index))
 	}
 	}
 	if result == nil || r.sticky && result[0] != 0 {
 	if result == nil || r.sticky && result[0] != 0 {

+ 50 - 28
runtime.go

@@ -10,6 +10,7 @@ import (
 	"math/bits"
 	"math/bits"
 	"math/rand"
 	"math/rand"
 	"reflect"
 	"reflect"
+	"runtime"
 	"strconv"
 	"strconv"
 	"time"
 	"time"
 
 
@@ -17,7 +18,7 @@ import (
 
 
 	js_ast "github.com/dop251/goja/ast"
 	js_ast "github.com/dop251/goja/ast"
 	"github.com/dop251/goja/parser"
 	"github.com/dop251/goja/parser"
-	"runtime"
+	"github.com/dop251/goja/unistring"
 )
 )
 
 
 const (
 const (
@@ -88,7 +89,6 @@ type global struct {
 	RegExpPrototype   *Object
 	RegExpPrototype   *Object
 	DatePrototype     *Object
 	DatePrototype     *Object
 	SymbolPrototype   *Object
 	SymbolPrototype   *Object
-	ArrayIterator     *Object
 
 
 	ArrayBufferPrototype *Object
 	ArrayBufferPrototype *Object
 	DataViewPrototype    *Object
 	DataViewPrototype    *Object
@@ -98,10 +98,11 @@ type global struct {
 	MapPrototype         *Object
 	MapPrototype         *Object
 	SetPrototype         *Object
 	SetPrototype         *Object
 
 
-	IteratorPrototype      *Object
-	ArrayIteratorPrototype *Object
-	MapIteratorPrototype   *Object
-	SetIteratorPrototype   *Object
+	IteratorPrototype       *Object
+	ArrayIteratorPrototype  *Object
+	MapIteratorPrototype    *Object
+	SetIteratorPrototype    *Object
+	StringIteratorPrototype *Object
 
 
 	ErrorPrototype          *Object
 	ErrorPrototype          *Object
 	TypeErrorPrototype      *Object
 	TypeErrorPrototype      *Object
@@ -158,7 +159,7 @@ type Runtime struct {
 	now             Now
 	now             Now
 	_collator       *collate.Collator
 	_collator       *collate.Collator
 
 
-	symbolRegistry map[string]*valueSymbol
+	symbolRegistry map[unistring.String]*valueSymbol
 
 
 	typeInfoCache   map[reflect.Type]*reflectTypeInfo
 	typeInfoCache   map[reflect.Type]*reflectTypeInfo
 	fieldNameMapper FieldNameMapper
 	fieldNameMapper FieldNameMapper
@@ -169,7 +170,7 @@ type Runtime struct {
 
 
 type StackFrame struct {
 type StackFrame struct {
 	prg      *Program
 	prg      *Program
-	funcName string
+	funcName unistring.String
 	pc       int
 	pc       int
 }
 }
 
 
@@ -187,7 +188,7 @@ func (f *StackFrame) FuncName() string {
 	if f.funcName == "" {
 	if f.funcName == "" {
 		return "<anonymous>"
 		return "<anonymous>"
 	}
 	}
-	return f.funcName
+	return f.funcName.String()
 }
 }
 
 
 func (f *StackFrame) Position() Position {
 func (f *StackFrame) Position() Position {
@@ -203,7 +204,7 @@ func (f *StackFrame) Position() Position {
 func (f *StackFrame) Write(b *bytes.Buffer) {
 func (f *StackFrame) Write(b *bytes.Buffer) {
 	if f.prg != nil {
 	if f.prg != nil {
 		if n := f.prg.funcName; n != "" {
 		if n := f.prg.funcName; n != "" {
-			b.WriteString(n)
+			b.WriteString(n.String())
 			b.WriteString(" (")
 			b.WriteString(" (")
 		}
 		}
 		if n := f.prg.src.name; n != "" {
 		if n := f.prg.src.name; n != "" {
@@ -221,7 +222,7 @@ func (f *StackFrame) Write(b *bytes.Buffer) {
 		}
 		}
 	} else {
 	} else {
 		if f.funcName != "" {
 		if f.funcName != "" {
-			b.WriteString(f.funcName)
+			b.WriteString(f.funcName.String())
 			b.WriteString(" (")
 			b.WriteString(" (")
 		}
 		}
 		b.WriteString("native")
 		b.WriteString("native")
@@ -311,7 +312,7 @@ func (e *Exception) Value() Value {
 }
 }
 
 
 func (r *Runtime) addToGlobal(name string, 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 {
 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)})
 	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))
 	panic(r.newError(r.global.ReferenceError, "%s is not defined", name))
 }
 }
 
 
@@ -431,7 +432,7 @@ func (r *Runtime) NewGoError(err error) *Object {
 	return e
 	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}
 	v := &Object{runtime: r}
 
 
 	f = &funcObject{}
 	f = &funcObject{}
@@ -448,7 +449,7 @@ func (r *Runtime) newFunc(name string, len int, strict bool) (f *funcObject) {
 	return
 	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{
 	f := &nativeFuncObject{
 		baseFuncObject: baseFuncObject{
 		baseFuncObject: baseFuncObject{
 			baseObject: baseObject{
 			baseObject: baseObject{
@@ -469,7 +470,7 @@ func (r *Runtime) newNativeFuncObj(v *Object, call func(FunctionCall) Value, con
 	return f
 	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}
 	v := &Object{runtime: r}
 
 
 	f := &nativeFuncObject{
 	f := &nativeFuncObject{
@@ -501,7 +502,7 @@ func (r *Runtime) newNativeConstructor(call func(ConstructorCall) *Object, name
 	return v
 	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 {
 	if v == nil {
 		v = &Object{runtime: r}
 		v = &Object{runtime: r}
 	}
 	}
@@ -534,7 +535,7 @@ func (r *Runtime) newNativeConstructOnly(v *Object, ctor func(args []Value, newT
 	return f
 	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}
 	v := &Object{runtime: r}
 
 
 	f := &nativeFuncObject{
 	f := &nativeFuncObject{
@@ -558,7 +559,7 @@ func (r *Runtime) newNativeFunc(call func(FunctionCall) Value, construct func(ar
 	return v
 	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{
 	f := &nativeFuncObject{
 		baseFuncObject: baseFuncObject{
 		baseFuncObject: baseFuncObject{
 			baseObject: baseObject{
 			baseObject: baseObject{
@@ -579,11 +580,11 @@ func (r *Runtime) newNativeFuncConstructObj(v *Object, construct func(args []Val
 	return f
 	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)
 	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}
 	v := &Object{runtime: r}
 
 
 	f := &nativeFuncObject{}
 	f := &nativeFuncObject{}
@@ -929,6 +930,15 @@ func toLength(v Value) int64 {
 	return i
 	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 {
 func (r *Runtime) toIndex(v Value) int {
 	intIdx := v.ToInteger()
 	intIdx := v.ToInteger()
 	if intIdx >= 0 && intIdx < maxInt {
 	if intIdx >= 0 && intIdx < maxInt {
@@ -1178,10 +1188,10 @@ func (r *Runtime) ToValue(i interface{}) Value {
 			return valueFalse
 			return valueFalse
 		}
 		}
 	case func(FunctionCall) Value:
 	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)
 		return r.newNativeFunc(i, nil, name, nil, 0)
 	case func(ConstructorCall) *Object:
 	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)
 		return r.newNativeConstructor(i, name, 0)
 	case int:
 	case int:
 		return intToValue(int64(i))
 		return intToValue(int64(i))
@@ -1309,7 +1319,7 @@ func (r *Runtime) ToValue(i interface{}) Value {
 		obj.self = a
 		obj.self = a
 		return obj
 		return obj
 	case reflect.Func:
 	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())
 		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 {
 					if field.Anonymous {
 						v = o
 						v = o
 					} else {
 					} else {
-						v = o.self.getStr(name, nil)
+						v = o.self.getStr(unistring.NewFromString(name), nil)
 					}
 					}
 
 
 					if v != nil {
 					if v != nil {
@@ -1650,12 +1660,12 @@ func (r *Runtime) GlobalObject() *Object {
 // Set the specified value as a property of the global object.
 // Set the specified value as a property of the global object.
 // The value is first converted using ToValue()
 // The value is first converted using ToValue()
 func (r *Runtime) Set(name string, value interface{}) {
 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.
 // Get the specified property of the global object.
 func (r *Runtime) Get(name string) Value {
 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.
 // 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()
 	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)
 	o := v.ToObject(r)
 	return o.self.getStr(p, v)
 	return o.self.getStr(p, v)
 }
 }
@@ -1933,3 +1943,15 @@ func isArray(object *Object) bool {
 		return false
 		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"
 	"fmt"
 	"reflect"
 	"reflect"
 	"runtime"
 	"runtime"
+	"strconv"
 	"testing"
 	"testing"
 	"time"
 	"time"
 )
 )
@@ -1146,7 +1147,7 @@ func TestInterruptInWrappedFunction(t *testing.T) {
 		rt.Interrupt(errors.New("hi"))
 		rt.Interrupt(errors.New("hi"))
 	}()
 	}()
 
 
-	v, err = fn(nil)
+	_, err = fn(nil)
 	if err == nil {
 	if err == nil {
 		t.Fatal("expected error")
 		t.Fatal("expected error")
 	}
 	}
@@ -1173,7 +1174,7 @@ func TestRunLoopPreempt(t *testing.T) {
 		vm.Interrupt(errors.New("hi"))
 		vm.Interrupt(errors.New("hi"))
 	}()
 	}()
 
 
-	v, err = fn(nil)
+	_, err = fn(nil)
 	if err == nil {
 	if err == nil {
 		t.Fatal("expected error")
 		t.Fatal("expected error")
 	}
 	}
@@ -1523,3 +1524,44 @@ func BenchmarkMainLoop(b *testing.B) {
 		vm.RunProgram(prg)
 		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 (
 import (
 	"io"
 	"io"
 	"strconv"
 	"strconv"
+	"strings"
 	"unicode/utf16"
 	"unicode/utf16"
 	"unicode/utf8"
 	"unicode/utf8"
+
+	"github.com/dop251/goja/unistring"
 )
 )
 
 
 const (
 const (
@@ -46,37 +49,121 @@ var (
 
 
 type valueString interface {
 type valueString interface {
 	Value
 	Value
-	charAt(int64) rune
-	length() int64
+	charAt(int) rune
+	length() int
 	concat(valueString) valueString
 	concat(valueString) valueString
-	substring(start, end int64) valueString
+	substring(start, end int) valueString
 	compareTo(valueString) int
 	compareTo(valueString) int
 	reader(start int) io.RuneReader
 	reader(start int) io.RuneReader
-	index(valueString, int64) int64
-	lastIndex(valueString, int64) int64
+	index(valueString, int) int
+	lastIndex(valueString, int) int
 	toLower() valueString
 	toLower() valueString
 	toUpper() valueString
 	toUpper() valueString
 	toTrimmedUTF8() string
 	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 {
 type stringObject struct {
 	baseObject
 	baseObject
 	value      valueString
 	value      valueString
-	length     int64
+	length     int
 	lengthProp valueProperty
 	lengthProp valueProperty
 }
 }
 
 
-func newUnicodeString(s string) valueString {
-	return unicodeString(utf16.Encode([]rune(s)))
-}
-
 func newStringValue(s string) valueString {
 func newStringValue(s string) valueString {
+	utf16Size := 0
+	ascii := true
 	for _, chr := range s {
 	for _, chr := range s {
+		utf16Size++
 		if chr >= utf8.RuneSelf {
 		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() {
 func (s *stringObject) init() {
@@ -88,12 +175,12 @@ func (s *stringObject) setLength() {
 	if s.value != nil {
 	if s.value != nil {
 		s.length = s.value.length()
 		s.length = s.value.length()
 	}
 	}
-	s.lengthProp.value = intToValue(s.length)
+	s.lengthProp.value = intToValue(int64(s.length))
 	s._put("length", &s.lengthProp)
 	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._getIdx(i)
 	}
 	}
 	return s.baseObject.getStr(name, receiver)
 	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 {
 func (s *stringObject) getIdx(idx valueInt, receiver Value) Value {
 	i := int64(idx)
 	i := int64(idx)
 	if i >= 0 {
 	if i >= 0 {
-		if i < s.length {
-			return s._getIdx(i)
+		if i < int64(s.length) {
+			return s._getIdx(int(i))
 		}
 		}
 		return nil
 		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)
 		val := s._getIdx(i)
 		return &valueProperty{
 		return &valueProperty{
 			value:      val,
 			value:      val,
@@ -125,8 +212,8 @@ func (s *stringObject) getOwnPropStr(name string) Value {
 func (s *stringObject) getOwnPropIdx(idx valueInt) Value {
 func (s *stringObject) getOwnPropIdx(idx valueInt) Value {
 	i := int64(idx)
 	i := int64(idx)
 	if i >= 0 {
 	if i >= 0 {
-		if i < s.length {
-			val := s._getIdx(i)
+		if i < int64(s.length) {
+			val := s._getIdx(int(i))
 			return &valueProperty{
 			return &valueProperty{
 				value:      val,
 				value:      val,
 				enumerable: true,
 				enumerable: true,
@@ -135,15 +222,15 @@ func (s *stringObject) getOwnPropIdx(idx valueInt) Value {
 		return nil
 		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)
 	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)
 		s.val.runtime.typeErrorResult(throw, "Cannot assign to read only property '%d' of a String", i)
 		return false
 		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 {
 func (s *stringObject) setOwnIdx(idx valueInt, val Value, throw bool) bool {
 	i := int64(idx)
 	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)
 		s.val.runtime.typeErrorResult(throw, "Cannot assign to read only property '%d' of a String", i)
 		return false
 		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)
 	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)
 	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)
 		s.val.runtime.typeErrorResult(throw, "Cannot redefine property: %d", i)
 		return false
 		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 {
 func (s *stringObject) defineOwnPropertyIdx(idx valueInt, descr PropertyDescriptor, throw bool) bool {
 	i := int64(idx)
 	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)
 		s.val.runtime.typeErrorResult(throw, "Cannot redefine property: %d", i)
 		return false
 		return false
 	}
 	}
 
 
-	return s.baseObject.defineOwnPropertyStr(idx.String(), descr, throw)
+	return s.baseObject.defineOwnPropertyStr(idx.string(), descr, throw)
 }
 }
 
 
 type stringPropIter struct {
 type stringPropIter struct {
 	str         valueString // separate, because obj can be the singleton
 	str         valueString // separate, because obj can be the singleton
 	obj         *stringObject
 	obj         *stringObject
-	idx, length int64
+	idx, length int
 }
 }
 
 
 func (i *stringPropIter) next() (propIterItem, iterNextFunc) {
 func (i *stringPropIter) next() (propIterItem, iterNextFunc) {
 	if i.idx < i.length {
 	if i.idx < i.length {
-		name := strconv.FormatInt(i.idx, 10)
+		name := strconv.Itoa(i.idx)
 		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()()
 	return i.obj.baseObject.enumerateUnfiltered()()
@@ -213,15 +300,15 @@ func (s *stringObject) enumerateUnfiltered() iterNextFunc {
 }
 }
 
 
 func (s *stringObject) ownKeys(all bool, accum []Value) []Value {
 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)
 	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)
 		s.val.runtime.typeErrorResult(throw, "Cannot delete property '%d' of a String", i)
 		return false
 		return false
 	}
 	}
@@ -231,16 +318,16 @@ func (s *stringObject) deleteStr(name string, throw bool) bool {
 
 
 func (s *stringObject) deleteIdx(idx valueInt, throw bool) bool {
 func (s *stringObject) deleteIdx(idx valueInt, throw bool) bool {
 	i := int64(idx)
 	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)
 		s.val.runtime.typeErrorResult(throw, "Cannot delete property '%d' of a String", i)
 		return false
 		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 true
 	}
 	}
 	return s.baseObject.hasOwnPropertyStr(name)
 	return s.baseObject.hasOwnPropertyStr(name)
@@ -248,8 +335,8 @@ func (s *stringObject) hasOwnPropertyStr(name string) bool {
 
 
 func (s *stringObject) hasOwnPropertyIdx(idx valueInt) bool {
 func (s *stringObject) hasOwnPropertyIdx(idx valueInt) bool {
 	i := int64(idx)
 	i := int64(idx)
-	if i >= 0 && i < s.length {
+	if i >= 0 && i < int64(s.length) {
 		return true
 		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 (
 import (
 	"fmt"
 	"fmt"
-	"hash"
+	"hash/maphash"
 	"io"
 	"io"
 	"math"
 	"math"
 	"reflect"
 	"reflect"
 	"strconv"
 	"strconv"
 	"strings"
 	"strings"
+
+	"github.com/dop251/goja/unistring"
 )
 )
 
 
 type asciiString string
 type asciiString string
@@ -218,19 +220,19 @@ func (s asciiString) baseObject(r *Runtime) *Object {
 	return ss.val
 	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()
 	h := hash.Sum64()
 	hash.Reset()
 	hash.Reset()
 	return h
 	return h
 }
 }
 
 
-func (s asciiString) charAt(idx int64) rune {
+func (s asciiString) charAt(idx int) rune {
 	return rune(s[idx])
 	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 {
 func (s asciiString) concat(other valueString) valueString {
@@ -240,21 +242,21 @@ func (s asciiString) concat(other valueString) valueString {
 		copy(b, s)
 		copy(b, s)
 		copy(b[len(s):], other)
 		copy(b[len(s):], other)
 		return asciiString(b)
 		return asciiString(b)
-		//return asciiString(string(s) + string(other))
 	case unicodeString:
 	case unicodeString:
 		b := make([]uint16, len(s)+len(other))
 		b := make([]uint16, len(s)+len(other))
+		b[0] = unistring.BOM
 		for i := 0; i < len(s); i++ {
 		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)
 		return unicodeString(b)
 	default:
 	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 {
 func (s asciiString) compareTo(other valueString) int {
@@ -264,13 +266,13 @@ func (s asciiString) compareTo(other valueString) int {
 	case unicodeString:
 	case unicodeString:
 		return strings.Compare(string(s), other.String())
 		return strings.Compare(string(s), other.String())
 	default:
 	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 {
 	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 {
 		if p >= 0 {
 			return p + start
 			return p + start
 		}
 		}
@@ -278,16 +280,16 @@ func (s asciiString) index(substr valueString, start int64) int64 {
 	return -1
 	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 {
 	if substr, ok := substr.(asciiString); ok {
-		end := pos + int64(len(substr))
+		end := pos + len(substr)
 		var ss string
 		var ss string
-		if end > int64(len(s)) {
+		if end > len(s) {
 			ss = string(s)
 			ss = string(s)
 		} else {
 		} else {
 			ss = string(s[:end])
 			ss = string(s[:end])
 		}
 		}
-		return int64(strings.LastIndex(ss, string(substr)))
+		return strings.LastIndex(ss, string(substr))
 	}
 	}
 	return -1
 	return -1
 }
 }
@@ -304,6 +306,10 @@ func (s asciiString) toTrimmedUTF8() string {
 	return strings.TrimSpace(string(s))
 	return strings.TrimSpace(string(s))
 }
 }
 
 
+func (s asciiString) string() unistring.String {
+	return unistring.String(s)
+}
+
 func (s asciiString) Export() interface{} {
 func (s asciiString) Export() interface{} {
 	return string(s)
 	return string(s)
 }
 }

+ 130 - 41
string_unicode.go

@@ -3,17 +3,18 @@ package goja
 import (
 import (
 	"errors"
 	"errors"
 	"fmt"
 	"fmt"
-	"github.com/dop251/goja/parser"
-	"golang.org/x/text/cases"
-	"golang.org/x/text/language"
-	"hash"
+	"hash/maphash"
 	"io"
 	"io"
 	"math"
 	"math"
 	"reflect"
 	"reflect"
 	"strings"
 	"strings"
 	"unicode/utf16"
 	"unicode/utf16"
 	"unicode/utf8"
 	"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
 type unicodeString []uint16
@@ -27,6 +28,11 @@ type runeReaderReplace struct {
 	wrapped io.RuneReader
 	wrapped io.RuneReader
 }
 }
 
 
+type unicodeStringBuilder struct {
+	buf     []uint16
+	unicode bool
+}
+
 var (
 var (
 	InvalidRuneError = errors.New("Invalid rune")
 	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) {
 func (rr *unicodeRuneReader) ReadRune() (r rune, size int, err error) {
 	if rr.pos < len(rr.s) {
 	if rr.pos < len(rr.s) {
 		r = rune(rr.s[rr.pos])
 		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++
 					size++
 					rr.pos++
 					rr.pos++
-					if r1 == utf8.RuneError {
-						err = InvalidRuneError
-					} else {
-						r = r1
-					}
 				} else {
 				} else {
 					err = InvalidRuneError
 					err = InvalidRuneError
 				}
 				}
+			} else {
+				err = InvalidRuneError
 			}
 			}
+		} else if isUTF16SecondSurrogate(r) {
+			err = InvalidRuneError
 		}
 		}
-		size++
-		rr.pos++
 	} else {
 	} else {
 		err = io.EOF
 		err = io.EOF
 	}
 	}
 	return
 	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 {
 func (s unicodeString) reader(start int) io.RuneReader {
 	return &unicodeRuneReader{
 	return &unicodeRuneReader{
-		s: s[start:],
+		s: s[start+1:],
 	}
 	}
 }
 }
 
 
@@ -150,18 +224,21 @@ func (s unicodeString) baseObject(r *Runtime) *Object {
 	return ss.val
 	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 {
 func (s unicodeString) concat(other valueString) valueString {
 	switch other := other.(type) {
 	switch other := other.(type) {
 	case unicodeString:
 	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:
 	case asciiString:
 		b := make([]uint16, len(s)+len(other))
 		b := make([]uint16, len(s)+len(other))
 		copy(b, s)
 		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 {
 	for _, c := range ss {
 		if c >= utf8.RuneSelf {
 		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)
 	as := make([]byte, end-start)
@@ -190,32 +270,32 @@ func (s unicodeString) substring(start, end int64) valueString {
 }
 }
 
 
 func (s unicodeString) String() string {
 func (s unicodeString) String() string {
-	return string(utf16.Decode(s))
+	return string(utf16.Decode(s[1:]))
 }
 }
 
 
 func (s unicodeString) compareTo(other valueString) int {
 func (s unicodeString) compareTo(other valueString) int {
 	return strings.Compare(s.String(), other.String())
 	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
 	var ss []uint16
 	switch substr := substr.(type) {
 	switch substr := substr.(type) {
 	case unicodeString:
 	case unicodeString:
-		ss = substr
+		ss = substr[1:]
 	case asciiString:
 	case asciiString:
 		ss = make([]uint16, len(substr))
 		ss = make([]uint16, len(substr))
 		for i := 0; i < len(substr); i++ {
 		for i := 0; i < len(substr); i++ {
 			ss[i] = uint16(substr[i])
 			ss[i] = uint16(substr[i])
 		}
 		}
 	default:
 	default:
-		panic(fmt.Errorf("Unknown string type: %T", substr))
+		panic(fmt.Errorf("unknown string type: %T", substr))
 	}
 	}
-
+	s1 := s[1:]
 	// TODO: optimise
 	// TODO: optimise
-	end := int64(len(s) - len(ss))
+	end := len(s1) - len(ss)
 	for start <= end {
 	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
 				goto nomatch
 			}
 			}
 		}
 		}
@@ -227,11 +307,11 @@ func (s unicodeString) index(substr valueString, start int64) int64 {
 	return -1
 	return -1
 }
 }
 
 
-func (s unicodeString) lastIndex(substr valueString, start int64) int64 {
+func (s unicodeString) lastIndex(substr valueString, start int) int {
 	var ss []uint16
 	var ss []uint16
 	switch substr := substr.(type) {
 	switch substr := substr.(type) {
 	case unicodeString:
 	case unicodeString:
-		ss = substr
+		ss = substr[1:]
 	case asciiString:
 	case asciiString:
 		ss = make([]uint16, len(substr))
 		ss = make([]uint16, len(substr))
 		for i := 0; i < len(substr); i++ {
 		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))
 		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
 		start = maxStart
 	}
 	}
 	// TODO: optimise
 	// TODO: optimise
 	for start >= 0 {
 	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
 				goto nomatch
 			}
 			}
 		}
 		}
@@ -259,6 +340,10 @@ func (s unicodeString) lastIndex(substr valueString, start int64) int64 {
 	return -1
 	return -1
 }
 }
 
 
+func unicodeStringFromRunes(r []rune) unicodeString {
+	return unistring.NewFromRunes(r).AsUtf16()
+}
+
 func (s unicodeString) toLower() valueString {
 func (s unicodeString) toLower() valueString {
 	caser := cases.Lower(language.Und)
 	caser := cases.Lower(language.Und)
 	r := []rune(caser.String(s.String()))
 	r := []rune(caser.String(s.String()))
@@ -279,7 +364,7 @@ func (s unicodeString) toLower() valueString {
 	if ascii {
 	if ascii {
 		return asciiString(r)
 		return asciiString(r)
 	}
 	}
-	return unicodeString(utf16.Encode(r))
+	return unicodeStringFromRunes(r)
 }
 }
 
 
 func (s unicodeString) toUpper() valueString {
 func (s unicodeString) toUpper() valueString {
@@ -295,9 +380,13 @@ func (s unicodeString) ExportType() reflect.Type {
 	return reflectTypeString
 	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()
 	h := hash.Sum64()
 	hash.Reset()
 	hash.Reset()
 	return h
 	return h
 }
 }
+
+func (s unicodeString) string() unistring.String {
+	return unistring.FromUtf16(s)
+}

+ 81 - 37
tc39_test.go

@@ -7,9 +7,11 @@ import (
 	"io/ioutil"
 	"io/ioutil"
 	"os"
 	"os"
 	"path"
 	"path"
+	"sort"
 	"strings"
 	"strings"
 	"sync"
 	"sync"
 	"testing"
 	"testing"
+	"time"
 )
 )
 
 
 const (
 const (
@@ -19,7 +21,7 @@ const (
 var (
 var (
 	invalidFormatError = errors.New("Invalid file format")
 	invalidFormatError = errors.New("Invalid file format")
 
 
-	ignorableTestError = &valueSymbol{}
+	ignorableTestError = newSymbol(stringEmpty)
 
 
 	sabStub = MustCompile("sabStub.js", `
 	sabStub = MustCompile("sabStub.js", `
 		Object.defineProperty(this, "SharedArrayBuffer", {
 		Object.defineProperty(this, "SharedArrayBuffer", {
@@ -32,18 +34,11 @@ var (
 
 
 var (
 var (
 	skipList = map[string]bool{
 	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-8.js":  true, // timezone
 		"test/built-ins/Date/prototype/toISOString/15.9.5.43-0-9.js":  true, // timezone
 		"test/built-ins/Date/prototype/toISOString/15.9.5.43-0-9.js":  true, // timezone
 		"test/built-ins/Date/prototype/toISOString/15.9.5.43-0-10.js": true, // timezone
 		"test/built-ins/Date/prototype/toISOString/15.9.5.43-0-10.js": true, // timezone
 		"test/annexB/built-ins/escape/escape-above-astral.js":         true, // \u{xxxxx}
 		"test/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
 		// class
 		"test/language/statements/class/subclass/builtin-objects/Symbol/symbol-valid-as-extends-value.js": true,
 		"test/language/statements/class/subclass/builtin-objects/Symbol/symbol-valid-as-extends-value.js": true,
 		"test/language/statements/class/subclass/builtin-objects/Symbol/new-symbol-with-super-throws.js":  true,
 		"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/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/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/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
 		// full unicode regexp flag
 		"test/built-ins/RegExp/prototype/Symbol.match/u-advance-after-empty.js":               true,
 		"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/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-success-u-return-val-groups.js": true,
 		"test/built-ins/RegExp/prototype/Symbol.match/builtin-infer-unicode.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
 		// object literals
 		"test/built-ins/Array/from/source-object-iterator-1.js":                   true,
 		"test/built-ins/Array/from/source-object-iterator-1.js":                   true,
@@ -86,6 +85,12 @@ var (
 
 
 		// arrow-function
 		// arrow-function
 		"test/built-ins/Object/prototype/toString/proxy-function.js": true,
 		"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{
 	featuresBlackList = []string{
@@ -101,9 +106,7 @@ var (
 		"12.9.4",
 		"12.9.4",
 		"19.1",
 		"19.1",
 		"19.4",
 		"19.4",
-		"21.1.3.14",
-		"21.1.3.15",
-		"21.1.3.17",
+		"21.1",
 		"21.2.5.6",
 		"21.2.5.6",
 		"22.1.2.1",
 		"22.1.2.1",
 		"22.1.2.3",
 		"22.1.2.3",
@@ -126,6 +129,7 @@ var (
 	esIdPrefixWhiteList = []string{
 	esIdPrefixWhiteList = []string{
 		"sec-array.prototype.includes",
 		"sec-array.prototype.includes",
 		"sec-%typedarray%",
 		"sec-%typedarray%",
+		"sec-string.prototype",
 	}
 	}
 )
 )
 
 
@@ -134,11 +138,21 @@ type tc39Test struct {
 	f    func(t *testing.T)
 	f    func(t *testing.T)
 }
 }
 
 
+type tc39BenchmarkItem struct {
+	name     string
+	duration time.Duration
+}
+
+type tc39BenchmarkData []tc39BenchmarkItem
+
 type tc39TestCtx struct {
 type tc39TestCtx struct {
 	base         string
 	base         string
 	t            *testing.T
 	t            *testing.T
 	prgCache     map[string]*Program
 	prgCache     map[string]*Program
 	prgCacheLock sync.Mutex
 	prgCacheLock sync.Mutex
+	enableBench  bool
+	benchmark    tc39BenchmarkData
+	benchLock    sync.Mutex
 	testQueue    []tc39Test
 	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")
 	hasRaw := meta.hasFlag("raw")
 
 
 	if hasRaw || !meta.hasFlag("onlyStrict") {
 	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)
 		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() {
 func (ctx *tc39TestCtx) init() {
@@ -459,32 +487,48 @@ func TestTC39(t *testing.T) {
 
 
 	ctx := &tc39TestCtx{
 	ctx := &tc39TestCtx{
 		base: tc39BASE,
 		base: tc39BASE,
-		t:    t,
 	}
 	}
 	ctx.init()
 	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"
 	"reflect"
 	"strconv"
 	"strconv"
 	"unsafe"
 	"unsafe"
+
+	"github.com/dop251/goja/unistring"
 )
 )
 
 
 type byteOrder bool
 type byteOrder bool
@@ -458,15 +460,15 @@ func (a *typedArrayObject) _getIdx(idx int) Value {
 	return nil
 	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 {
 	if err != nil {
 		return 0, false
 		return 0, false
 	}
 	}
 	return int(i), true
 	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 {
 	if idx, ok := strToTAIdx(name); ok {
 		v := a._getIdx(idx)
 		v := a._getIdx(idx)
 		if v != nil {
 		if v != nil {
@@ -493,7 +495,7 @@ func (a *typedArrayObject) getOwnPropIdx(idx valueInt) Value {
 	return nil
 	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 {
 	if idx, ok := strToTAIdx(name); ok {
 		prop := a._getIdx(idx)
 		prop := a._getIdx(idx)
 		if prop == nil {
 		if prop == nil {
@@ -538,7 +540,7 @@ func (a *typedArrayObject) _hasIdx(idx int) bool {
 	return idx >= 0 && idx < a.length
 	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 {
 	if idx, ok := strToTAIdx(p); ok {
 		return a._putIdx(idx, v, throw)
 		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)
 	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)
 	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)
 	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 {
 	if idx, ok := strToTAIdx(name); ok {
 		a.viewedArrayBuf.ensureNotDetached()
 		a.viewedArrayBuf.ensureNotDetached()
 		return idx < a.length
 		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 {
 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 {
 	if ok {
 		return a._putIdx(idx, prop, throw)
 		return a._putIdx(idx, prop, throw)
 	}
 	}
 	return ok
 	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 {
 	if idx, ok := strToTAIdx(name); ok {
 		return a._defineIdxProperty(idx, desc, throw)
 		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)
 	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, ok := strToTAIdx(name); ok {
 		if idx < a.length {
 		if idx < a.length {
 			a.val.runtime.typeErrorResult(throw, "Cannot delete property '%d' of %s", idx, a.val.String())
 			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)
 		name := strconv.Itoa(i.idx)
 		prop := i.a._getIdx(i.idx)
 		prop := i.a._getIdx(i.idx)
 		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()()
 	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
 package goja
 
 
 import (
 import (
-	"fmt"
-	"hash"
+	"hash/maphash"
 	"math"
 	"math"
 	"reflect"
 	"reflect"
 	"regexp"
 	"regexp"
 	"strconv"
 	"strconv"
 	"unsafe"
 	"unsafe"
+
+	"github.com/dop251/goja/unistring"
 )
 )
 
 
 var (
 var (
@@ -39,6 +40,7 @@ var intCache [256]Value
 type Value interface {
 type Value interface {
 	ToInteger() int64
 	ToInteger() int64
 	toString() valueString
 	toString() valueString
+	string() unistring.String
 	ToPrimitiveString() Value
 	ToPrimitiveString() Value
 	String() string
 	String() string
 	ToFloat() float64
 	ToFloat() float64
@@ -53,7 +55,7 @@ type Value interface {
 
 
 	baseObject(r *Runtime) *Object
 	baseObject(r *Runtime) *Object
 
 
-	hash(hash64 hash.Hash64) uint64
+	hash(hasher *maphash.Hash) uint64
 }
 }
 
 
 type valueContainer interface {
 type valueContainer interface {
@@ -71,12 +73,12 @@ type valueUndefined struct {
 	valueNull
 	valueNull
 }
 }
 type valueSymbol struct {
 type valueSymbol struct {
-	desc string
+	desc valueString
 }
 }
 
 
 type valueUnresolved struct {
 type valueUnresolved struct {
 	r   *Runtime
 	r   *Runtime
-	ref string
+	ref unistring.String
 }
 }
 
 
 type memberUnresolved struct {
 type memberUnresolved struct {
@@ -127,6 +129,10 @@ func (i valueInt) toString() valueString {
 	return asciiString(i.String())
 	return asciiString(i.String())
 }
 }
 
 
+func (i valueInt) string() unistring.String {
+	return unistring.String(i.String())
+}
+
 func (i valueInt) ToPrimitiveString() Value {
 func (i valueInt) ToPrimitiveString() Value {
 	return i
 	return i
 }
 }
@@ -195,60 +201,64 @@ func (i valueInt) ExportType() reflect.Type {
 	return reflectTypeInt
 	return reflectTypeInt
 }
 }
 
 
-func (i valueInt) hash(hash.Hash64) uint64 {
+func (i valueInt) hash(*maphash.Hash) uint64 {
 	return uint64(i)
 	return uint64(i)
 }
 }
 
 
-func (o valueBool) ToInteger() int64 {
-	if o {
+func (b valueBool) ToInteger() int64 {
+	if b {
 		return 1
 		return 1
 	}
 	}
 	return 0
 	return 0
 }
 }
 
 
-func (o valueBool) toString() valueString {
-	if o {
+func (b valueBool) toString() valueString {
+	if b {
 		return stringTrue
 		return stringTrue
 	}
 	}
 	return stringFalse
 	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 "true"
 	}
 	}
 	return "false"
 	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 1.0
 	}
 	}
 	return 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(1)
 	}
 	}
 	return valueInt(0)
 	return valueInt(0)
 }
 }
 
 
-func (o valueBool) SameAs(other Value) bool {
+func (b valueBool) SameAs(other Value) bool {
 	if other, ok := other.(valueBool); ok {
 	if other, ok := other.(valueBool); ok {
-		return o == other
+		return b == other
 	}
 	}
 	return false
 	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 {
 	if other, ok := other.(valueBool); ok {
-		return o == other
+		return b == other
 	}
 	}
 	return false
 	return false
 }
 }
 
 
-func (o valueBool) baseObject(r *Runtime) *Object {
+func (b valueBool) baseObject(r *Runtime) *Object {
 	return r.global.BooleanPrototype
 	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
 	return reflectTypeBool
 }
 }
 
 
-func (b valueBool) hash(hash.Hash64) uint64 {
+func (b valueBool) hash(*maphash.Hash) uint64 {
 	if b {
 	if b {
 		return uint64(uintptr(unsafe.Pointer(&valueTrue)))
 		return uint64(uintptr(unsafe.Pointer(&valueTrue)))
 	}
 	}
@@ -300,6 +310,10 @@ func (n valueNull) toString() valueString {
 	return stringNull
 	return stringNull
 }
 }
 
 
+func (n valueNull) string() unistring.String {
+	return stringNull.string()
+}
+
 func (n valueNull) ToPrimitiveString() Value {
 func (n valueNull) ToPrimitiveString() Value {
 	return n
 	return n
 }
 }
@@ -320,6 +334,10 @@ func (u valueUndefined) String() string {
 	return "undefined"
 	return "undefined"
 }
 }
 
 
+func (u valueUndefined) string() unistring.String {
+	return "undefined"
+}
+
 func (u valueUndefined) ToNumber() Value {
 func (u valueUndefined) ToNumber() Value {
 	return _NaN
 	return _NaN
 }
 }
@@ -338,7 +356,7 @@ func (u valueUndefined) ToFloat() float64 {
 	return math.NaN()
 	return math.NaN()
 }
 }
 
 
-func (u valueUndefined) hash(hash.Hash64) uint64 {
+func (u valueUndefined) hash(*maphash.Hash) uint64 {
 	return uint64(uintptr(unsafe.Pointer(&_undefined)))
 	return uint64(uintptr(unsafe.Pointer(&_undefined)))
 }
 }
 
 
@@ -390,7 +408,7 @@ func (n valueNull) ExportType() reflect.Type {
 	return reflectTypeNil
 	return reflectTypeNil
 }
 }
 
 
-func (n valueNull) hash(hash.Hash64) uint64 {
+func (n valueNull) hash(*maphash.Hash) uint64 {
 	return uint64(uintptr(unsafe.Pointer(&_null)))
 	return uint64(uintptr(unsafe.Pointer(&_null)))
 }
 }
 
 
@@ -402,6 +420,10 @@ func (p *valueProperty) toString() valueString {
 	return stringEmpty
 	return stringEmpty
 }
 }
 
 
+func (p *valueProperty) string() unistring.String {
+	return ""
+}
+
 func (p *valueProperty) ToPrimitiveString() Value {
 func (p *valueProperty) ToPrimitiveString() Value {
 	return _undefined
 	return _undefined
 }
 }
@@ -470,20 +492,20 @@ func (p *valueProperty) StrictEquals(Value) bool {
 	return false
 	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
 	r.typeErrorResult(true, "BUG: baseObject() is called on valueProperty") // TODO error message
 	return nil
 	return nil
 }
 }
 
 
-func (n *valueProperty) Export() interface{} {
+func (p *valueProperty) Export() interface{} {
 	panic("Cannot export valueProperty")
 	panic("Cannot export valueProperty")
 }
 }
 
 
-func (n *valueProperty) ExportType() reflect.Type {
+func (p *valueProperty) ExportType() reflect.Type {
 	panic("Cannot export valueProperty")
 	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")
 	panic("valueProperty should never be used in maps or sets")
 }
 }
 
 
@@ -503,6 +525,10 @@ func (f valueFloat) toString() valueString {
 	return asciiString(f.String())
 	return asciiString(f.String())
 }
 }
 
 
+func (f valueFloat) string() unistring.String {
+	return unistring.String(f.String())
+}
+
 func (f valueFloat) ToPrimitiveString() Value {
 func (f valueFloat) ToPrimitiveString() Value {
 	return f
 	return f
 }
 }
@@ -608,7 +634,7 @@ func (f valueFloat) ExportType() reflect.Type {
 	return reflectTypeFloat
 	return reflectTypeFloat
 }
 }
 
 
-func (f valueFloat) hash(hash.Hash64) uint64 {
+func (f valueFloat) hash(*maphash.Hash) uint64 {
 	if f == _negativeZero {
 	if f == _negativeZero {
 		return 0
 		return 0
 	}
 	}
@@ -623,6 +649,10 @@ func (o *Object) toString() valueString {
 	return o.self.toPrimitiveString().toString()
 	return o.self.toPrimitiveString().toString()
 }
 }
 
 
+func (o *Object) string() unistring.String {
+	return o.self.toPrimitiveString().string()
+}
+
 func (o *Object) ToPrimitiveString() Value {
 func (o *Object) ToPrimitiveString() Value {
 	return o.self.toPrimitiveString().ToPrimitiveString()
 	return o.self.toPrimitiveString().ToPrimitiveString()
 }
 }
@@ -688,12 +718,12 @@ func (o *Object) ExportType() reflect.Type {
 	return o.self.exportType()
 	return o.self.exportType()
 }
 }
 
 
-func (o *Object) hash(hash.Hash64) uint64 {
+func (o *Object) hash(*maphash.Hash) uint64 {
 	return uint64(uintptr(unsafe.Pointer(o)))
 	return uint64(uintptr(unsafe.Pointer(o)))
 }
 }
 
 
 func (o *Object) Get(name string) Value {
 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) {
 func (o *Object) Keys() (keys []string) {
@@ -710,7 +740,7 @@ func (o *Object) Keys() (keys []string) {
 // configurable: configurable, enumerable: enumerable})
 // configurable: configurable, enumerable: enumerable})
 func (o *Object) DefineDataProperty(name string, value Value, writable, configurable, enumerable Flag) error {
 func (o *Object) DefineDataProperty(name string, value Value, writable, configurable, enumerable Flag) error {
 	return tryFunc(func() {
 	return tryFunc(func() {
-		o.self.defineOwnPropertyStr(name, PropertyDescriptor{
+		o.self.defineOwnPropertyStr(unistring.NewFromString(name), PropertyDescriptor{
 			Value:        value,
 			Value:        value,
 			Writable:     writable,
 			Writable:     writable,
 			Configurable: configurable,
 			Configurable: configurable,
@@ -723,7 +753,7 @@ func (o *Object) DefineDataProperty(name string, value Value, writable, configur
 // configurable: configurable, enumerable: enumerable})
 // configurable: configurable, enumerable: enumerable})
 func (o *Object) DefineAccessorProperty(name string, getter, setter Value, configurable, enumerable Flag) error {
 func (o *Object) DefineAccessorProperty(name string, getter, setter Value, configurable, enumerable Flag) error {
 	return tryFunc(func() {
 	return tryFunc(func() {
-		o.self.defineOwnPropertyStr(name, PropertyDescriptor{
+		o.self.defineOwnPropertyStr(unistring.NewFromString(name), PropertyDescriptor{
 			Getter:       getter,
 			Getter:       getter,
 			Setter:       setter,
 			Setter:       setter,
 			Configurable: configurable,
 			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 {
 func (o *Object) Set(name string, value interface{}) error {
 	return tryFunc(func() {
 	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
 	return nil
 }
 }
 
 
+func (o valueUnresolved) string() unistring.String {
+	o.throw()
+	return ""
+}
+
 func (o valueUnresolved) ToPrimitiveString() Value {
 func (o valueUnresolved) ToPrimitiveString() Value {
 	o.throw()
 	o.throw()
 	return nil
 	return nil
@@ -834,7 +869,7 @@ func (o valueUnresolved) ExportType() reflect.Type {
 	return nil
 	return nil
 }
 }
 
 
-func (o valueUnresolved) hash(hash.Hash64) uint64 {
+func (o valueUnresolved) hash(*maphash.Hash) uint64 {
 	o.throw()
 	o.throw()
 	return 0
 	return 0
 }
 }
@@ -852,7 +887,11 @@ func (s *valueSymbol) ToPrimitiveString() Value {
 }
 }
 
 
 func (s *valueSymbol) String() string {
 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 {
 func (s *valueSymbol) ToFloat() float64 {
@@ -898,12 +937,14 @@ func (s *valueSymbol) baseObject(r *Runtime) *Object {
 	return r.newPrimitiveObject(s, r.global.SymbolPrototype, "Symbol")
 	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)))
 	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() {
 func init() {

+ 75 - 71
vm.go

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