Преглед изворни кода

Exposed String and StringBuilder. Closes #321.

Dmitry Panov пре 2 година
родитељ
комит
c933cf95e1
32 измењених фајлова са 601 додато и 426 уклоњено
  1. 5 5
      builtin_array.go
  2. 1 1
      builtin_date.go
  3. 9 9
      builtin_error.go
  4. 3 3
      builtin_function.go
  5. 21 21
      builtin_global.go
  6. 4 4
      builtin_json.go
  7. 1 1
      builtin_object.go
  8. 61 61
      builtin_regexp.go
  9. 60 60
      builtin_string.go
  10. 8 8
      builtin_string_test.go
  11. 1 1
      builtin_symbol.go
  12. 3 3
      builtin_typedarrays.go
  13. 1 1
      compiler.go
  14. 1 1
      compiler_expr.go
  15. 1 1
      destruct.go
  16. 4 4
      func.go
  17. 2 2
      object.go
  18. 1 1
      object_dynamic.go
  19. 1 1
      object_goreflect_test.go
  20. 1 1
      object_lazy.go
  21. 4 4
      object_test.go
  22. 2 2
      proxy.go
  23. 24 24
      regexp.go
  24. 14 13
      runtime.go
  25. 2 2
      runtime_test.go
  26. 88 54
      string.go
  27. 48 14
      string_ascii.go
  28. 52 33
      string_imported.go
  29. 28 0
      string_test.go
  30. 115 56
      string_unicode.go
  31. 19 19
      value.go
  32. 16 16
      vm.go

+ 5 - 5
builtin_array.go

@@ -178,7 +178,7 @@ func (r *Runtime) arrayproto_pop(call FunctionCall) Value {
 func (r *Runtime) arrayproto_join(call FunctionCall) Value {
 	o := call.This.ToObject(r)
 	l := int(toLength(o.self.getStr("length", nil)))
-	var sep valueString
+	var sep String
 	if s := call.Argument(0); s != _undefined {
 		sep = s.toString()
 	} else {
@@ -188,7 +188,7 @@ func (r *Runtime) arrayproto_join(call FunctionCall) Value {
 		return stringEmpty
 	}
 
-	var buf valueStringBuilder
+	var buf StringBuilder
 
 	element0 := o.self.getIdx(valueInt(0), nil)
 	if element0 != nil && element0 != _undefined && element0 != _null {
@@ -231,7 +231,7 @@ func (r *Runtime) arrayproto_toString(call FunctionCall) Value {
 	})
 }
 
-func (r *Runtime) writeItemLocaleString(item Value, buf *valueStringBuilder) {
+func (r *Runtime) writeItemLocaleString(item Value, buf *StringBuilder) {
 	if item != nil && item != _undefined && item != _null {
 		if f, ok := r.getVStr(item, "toLocaleString").(*Object); ok {
 			if c, ok := f.self.assertCallable(); ok {
@@ -248,7 +248,7 @@ func (r *Runtime) writeItemLocaleString(item Value, buf *valueStringBuilder) {
 
 func (r *Runtime) arrayproto_toLocaleString(call FunctionCall) Value {
 	array := call.This.ToObject(r)
-	var buf valueStringBuilder
+	var buf StringBuilder
 	if a := r.checkStdArrayObj(array); a != nil {
 		for i, item := range a.values {
 			if i > 0 {
@@ -1559,7 +1559,7 @@ func (a *arraySortCtx) sortCompare(x, y Value) int {
 		}
 		return 0
 	}
-	return x.toString().compareTo(y.toString())
+	return x.toString().CompareTo(y.toString())
 }
 
 // sort.Interface

+ 1 - 1
builtin_date.go

@@ -23,7 +23,7 @@ func (r *Runtime) makeDate(args []Value, utc bool) (t time.Time, valid bool) {
 		}
 		if !valid {
 			pv := toPrimitive(args[0])
-			if val, ok := pv.(valueString); ok {
+			if val, ok := pv.(String); ok {
 				return dateParse(val.String())
 			}
 			pv = pv.ToNumber()

+ 9 - 9
builtin_error.go

@@ -10,8 +10,8 @@ type errorObject struct {
 	stackPropAdded bool
 }
 
-func (e *errorObject) formatStack() valueString {
-	var b valueStringBuilder
+func (e *errorObject) formatStack() String {
+	var b StringBuilder
 	val := writeErrorString(&b, e.val)
 	if val != nil {
 		b.WriteString(val)
@@ -19,7 +19,7 @@ func (e *errorObject) formatStack() valueString {
 	b.WriteRune('\n')
 
 	for _, frame := range e.stack {
-		b.WriteASCII("\tat ")
+		b.writeASCII("\tat ")
 		frame.WriteToValueBuilder(&b)
 		b.WriteRune('\n')
 	}
@@ -141,8 +141,8 @@ func (r *Runtime) builtin_AggregateError(args []Value, proto *Object) *Object {
 	return obj.val
 }
 
-func writeErrorString(sb *valueStringBuilder, obj *Object) valueString {
-	var nameStr, msgStr valueString
+func writeErrorString(sb *StringBuilder, obj *Object) String {
+	var nameStr, msgStr String
 	name := obj.self.getStr("name", nil)
 	if name == nil || name == _undefined {
 		nameStr = asciiString("Error")
@@ -155,10 +155,10 @@ func writeErrorString(sb *valueStringBuilder, obj *Object) valueString {
 	} else {
 		msgStr = msg.toString()
 	}
-	if nameStr.length() == 0 {
+	if nameStr.Length() == 0 {
 		return msgStr
 	}
-	if msgStr.length() == 0 {
+	if msgStr.Length() == 0 {
 		return nameStr
 	}
 	sb.WriteString(nameStr)
@@ -168,7 +168,7 @@ func writeErrorString(sb *valueStringBuilder, obj *Object) valueString {
 }
 
 func (r *Runtime) error_toString(call FunctionCall) Value {
-	var sb valueStringBuilder
+	var sb StringBuilder
 	val := writeErrorString(&sb, r.toObject(call.This))
 	if val != nil {
 		return val
@@ -176,7 +176,7 @@ func (r *Runtime) error_toString(call FunctionCall) Value {
 	return sb.String()
 }
 
-func (r *Runtime) createErrorPrototype(name valueString) *Object {
+func (r *Runtime) createErrorPrototype(name String) *Object {
 	o := r.newBaseObject(r.global.ErrorPrototype, classObject)
 	o._putProp("message", stringEmpty, true, false, true)
 	o._putProp("name", name, true, false, true)

+ 3 - 3
builtin_function.go

@@ -5,7 +5,7 @@ import (
 )
 
 func (r *Runtime) functionCtor(args []Value, proto *Object, async, generator bool) *Object {
-	var sb valueStringBuilder
+	var sb StringBuilder
 	if async {
 		if generator {
 			sb.WriteString(asciiString("(async function* anonymous("))
@@ -194,8 +194,8 @@ func (r *Runtime) functionproto_bind(call FunctionCall) Value {
 lenNotInt:
 	name := obj.self.getStr("name", nil)
 	nameStr := stringBound_
-	if s, ok := name.(valueString); ok {
-		nameStr = nameStr.concat(s)
+	if s, ok := name.(String); ok {
+		nameStr = nameStr.Concat(s)
 	}
 
 	v := &Object{runtime: r}

+ 21 - 21
builtin_global.go

@@ -61,8 +61,8 @@ func (r *Runtime) builtin_isFinite(call FunctionCall) Value {
 	return valueTrue
 }
 
-func (r *Runtime) _encode(uriString valueString, unescaped *[256]bool) valueString {
-	reader := uriString.reader()
+func (r *Runtime) _encode(uriString String, unescaped *[256]bool) String {
+	reader := uriString.Reader()
 	utf8Buf := make([]byte, utf8.UTFMax)
 	needed := false
 	l := 0
@@ -92,7 +92,7 @@ func (r *Runtime) _encode(uriString valueString, unescaped *[256]bool) valueStri
 
 	buf := make([]byte, l)
 	i := 0
-	reader = uriString.reader()
+	reader = uriString.Reader()
 	for {
 		rn, _, err := reader.ReadRune()
 		if err == io.EOF {
@@ -120,7 +120,7 @@ func (r *Runtime) _encode(uriString valueString, unescaped *[256]bool) valueStri
 	return asciiString(buf)
 }
 
-func (r *Runtime) _decode(sv valueString, reservedSet *[256]bool) valueString {
+func (r *Runtime) _decode(sv String, reservedSet *[256]bool) String {
 	s := sv.String()
 	hexCount := 0
 	for i := 0; i < len(s); {
@@ -239,9 +239,9 @@ func (r *Runtime) builtin_encodeURIComponent(call FunctionCall) Value {
 func (r *Runtime) builtin_escape(call FunctionCall) Value {
 	s := call.Argument(0).toString()
 	var sb strings.Builder
-	l := s.length()
+	l := s.Length()
 	for i := 0; i < l; i++ {
-		r := uint16(s.charAt(i))
+		r := s.CharAt(i)
 		if r >= 'A' && r <= 'Z' || r >= 'a' && r <= 'z' || r >= '0' && r <= '9' ||
 			r == '@' || r == '*' || r == '_' || r == '+' || r == '-' || r == '.' || r == '/' {
 			sb.WriteByte(byte(r))
@@ -262,7 +262,7 @@ func (r *Runtime) builtin_escape(call FunctionCall) Value {
 
 func (r *Runtime) builtin_unescape(call FunctionCall) Value {
 	s := call.Argument(0).toString()
-	l := s.length()
+	l := s.Length()
 	var asciiBuf []byte
 	var unicodeBuf []uint16
 	_, u := devirtualizeString(s)
@@ -274,31 +274,31 @@ func (r *Runtime) builtin_unescape(call FunctionCall) Value {
 		asciiBuf = make([]byte, 0, l)
 	}
 	for i := 0; i < l; {
-		r := s.charAt(i)
+		r := s.CharAt(i)
 		if r == '%' {
-			if i <= l-6 && s.charAt(i+1) == 'u' {
-				c0 := s.charAt(i + 2)
-				c1 := s.charAt(i + 3)
-				c2 := s.charAt(i + 4)
-				c3 := s.charAt(i + 5)
+			if i <= l-6 && s.CharAt(i+1) == 'u' {
+				c0 := s.CharAt(i + 2)
+				c1 := s.CharAt(i + 3)
+				c2 := s.CharAt(i + 4)
+				c3 := s.CharAt(i + 5)
 				if c0 <= 0xff && ishex(byte(c0)) &&
 					c1 <= 0xff && ishex(byte(c1)) &&
 					c2 <= 0xff && ishex(byte(c2)) &&
 					c3 <= 0xff && ishex(byte(c3)) {
-					r = rune(unhex(byte(c0)))<<12 |
-						rune(unhex(byte(c1)))<<8 |
-						rune(unhex(byte(c2)))<<4 |
-						rune(unhex(byte(c3)))
+					r = uint16(unhex(byte(c0)))<<12 |
+						uint16(unhex(byte(c1)))<<8 |
+						uint16(unhex(byte(c2)))<<4 |
+						uint16(unhex(byte(c3)))
 					i += 5
 					goto out
 				}
 			}
 			if i <= l-3 {
-				c0 := s.charAt(i + 1)
-				c1 := s.charAt(i + 2)
+				c0 := s.CharAt(i + 1)
+				c1 := s.CharAt(i + 2)
 				if c0 <= 0xff && ishex(byte(c0)) &&
 					c1 <= 0xff && ishex(byte(c1)) {
-					r = rune(unhex(byte(c0))<<4 | unhex(byte(c1)))
+					r = uint16(unhex(byte(c0))<<4 | unhex(byte(c1)))
 					i += 2
 				}
 			}
@@ -314,7 +314,7 @@ func (r *Runtime) builtin_unescape(call FunctionCall) Value {
 			unicode = true
 		}
 		if unicode {
-			unicodeBuf = append(unicodeBuf, uint16(r))
+			unicodeBuf = append(unicodeBuf, r)
 		} else {
 			asciiBuf = append(asciiBuf, byte(r))
 		}

+ 4 - 4
builtin_json.go

@@ -196,7 +196,7 @@ func (r *Runtime) builtinJSON_stringify(call FunctionCall) Value {
 				var name string
 				value := replacer.self.getIdx(valueInt(int64(index)), nil)
 				switch v := value.(type) {
-				case valueFloat, valueInt, valueString:
+				case valueFloat, valueInt, String:
 					name = value.String()
 				case *Object:
 					switch v.self.className() {
@@ -249,7 +249,7 @@ func (r *Runtime) builtinJSON_stringify(call FunctionCall) Value {
 				ctx.gap = strings.Repeat(" ", int(num))
 			}
 		} else {
-			if s, ok := spaceValue.(valueString); ok {
+			if s, ok := spaceValue.(String); ok {
 				str := s.String()
 				if len(str) > 10 {
 					ctx.gap = str[:10]
@@ -345,7 +345,7 @@ func (ctx *_builtinJSON_stringifyContext) str(key Value, holder *Object) bool {
 		} else {
 			ctx.buf.WriteString("false")
 		}
-	case valueString:
+	case String:
 		ctx.quote(value1)
 	case valueInt:
 		ctx.buf.WriteString(value.String())
@@ -476,7 +476,7 @@ func (ctx *_builtinJSON_stringifyContext) jo(object *Object) {
 	ctx.buf.WriteByte('}')
 }
 
-func (ctx *_builtinJSON_stringifyContext) quote(str valueString) {
+func (ctx *_builtinJSON_stringifyContext) quote(str String) {
 	ctx.buf.WriteByte('"')
 	reader := &lenientUtf16Decoder{utf16Reader: str.utf16Reader()}
 	for {

+ 1 - 1
builtin_object.go

@@ -474,7 +474,7 @@ func (r *Runtime) objectproto_toString(call FunctionCall) Value {
 			clsName = obj.self.className()
 		}
 		if tag := obj.self.getSym(SymToStringTag, nil); tag != nil {
-			if str, ok := tag.(valueString); ok {
+			if str, ok := tag.(String); ok {
 				clsName = str.String()
 			}
 		}

+ 61 - 61
builtin_regexp.go

@@ -22,7 +22,7 @@ func (r *Runtime) newRegexpObject(proto *Object) *regexpObject {
 	return o
 }
 
-func (r *Runtime) newRegExpp(pattern *regexpPattern, patternStr valueString, proto *Object) *regexpObject {
+func (r *Runtime) newRegExpp(pattern *regexpPattern, patternStr String, proto *Object) *regexpObject {
 	o := r.newRegexpObject(proto)
 
 	o.pattern = pattern
@@ -68,9 +68,9 @@ func convertRegexpToUnicode(patternStr string) string {
 			i++
 			if patternStr[i] == 'u' && patternStr[i+5] == '\\' && patternStr[i+6] == 'u' {
 				if first, ok := decodeHex(patternStr[i+1 : i+5]); ok {
-					if isUTF16FirstSurrogate(rune(first)) {
+					if isUTF16FirstSurrogate(uint16(first)) {
 						if second, ok := decodeHex(patternStr[i+7 : i+11]); ok {
-							if isUTF16SecondSurrogate(rune(second)) {
+							if isUTF16SecondSurrogate(uint16(second)) {
 								r = utf16.DecodeRune(rune(first), rune(second))
 								sb.WriteString(patternStr[pos : i-1])
 								sb.WriteRune(r)
@@ -124,7 +124,7 @@ func convertRegexpToUtf16(patternStr string) string {
 }
 
 // convert any broken UTF-16 surrogate pairs to \uXXXX
-func escapeInvalidUtf16(s valueString) string {
+func escapeInvalidUtf16(s String) string {
 	if imported, ok := s.(*importedString); ok {
 		return imported.s
 	}
@@ -144,7 +144,7 @@ func escapeInvalidUtf16(s valueString) string {
 		if utf16.IsSurrogate(c) {
 			if sb.Len() == 0 {
 				sb.Grow(utf8Size + 7)
-				hrd := s.reader()
+				hrd := s.Reader()
 				var c rune
 				for p := 0; p < pos; {
 					var size int
@@ -178,7 +178,7 @@ func escapeInvalidUtf16(s valueString) string {
 	return s.String()
 }
 
-func compileRegexpFromValueString(patternStr valueString, flags string) (*regexpPattern, error) {
+func compileRegexpFromValueString(patternStr String, flags string) (*regexpPattern, error) {
 	return compileRegexp(escapeInvalidUtf16(patternStr), flags)
 }
 
@@ -279,7 +279,7 @@ func compileRegexp(patternStr, flags string) (p *regexpPattern, err error) {
 	return
 }
 
-func (r *Runtime) _newRegExp(patternStr valueString, flags string, proto *Object) *regexpObject {
+func (r *Runtime) _newRegExp(patternStr String, flags string, proto *Object) *regexpObject {
 	pattern, err := compileRegexpFromValueString(patternStr, flags)
 	if err != nil {
 		panic(r.newSyntaxError(err.Error(), -1))
@@ -299,7 +299,7 @@ func (r *Runtime) builtin_newRegExp(args []Value, proto *Object) *Object {
 }
 
 func (r *Runtime) newRegExp(patternVal, flagsVal Value, proto *Object) *regexpObject {
-	var pattern valueString
+	var pattern String
 	var flags string
 	if isRegexp(patternVal) { // this may have side effects so need to call it anyway
 		if obj, ok := patternVal.(*Object); ok {
@@ -354,7 +354,7 @@ func (r *Runtime) regexpproto_compile(call FunctionCall) Value {
 	if this, ok := r.toObject(call.This).self.(*regexpObject); ok {
 		var (
 			pattern *regexpPattern
-			source  valueString
+			source  String
 			flags   string
 			err     error
 		)
@@ -416,7 +416,7 @@ func (r *Runtime) regexpproto_test(call FunctionCall) Value {
 func (r *Runtime) regexpproto_toString(call FunctionCall) Value {
 	obj := r.toObject(call.This)
 	if this := r.checkStdRegexp(obj); this != nil {
-		var sb valueStringBuilder
+		var sb StringBuilder
 		sb.WriteRune('/')
 		if !this.writeEscapedSource(&sb) {
 			sb.WriteString(this.source)
@@ -441,7 +441,7 @@ func (r *Runtime) regexpproto_toString(call FunctionCall) Value {
 	}
 	pattern := nilSafe(obj.self.getStr("source", nil)).toString()
 	flags := nilSafe(obj.self.getStr("flags", nil)).toString()
-	var sb valueStringBuilder
+	var sb StringBuilder
 	sb.WriteRune('/')
 	sb.WriteString(pattern)
 	sb.WriteRune('/')
@@ -449,8 +449,8 @@ func (r *Runtime) regexpproto_toString(call FunctionCall) Value {
 	return sb.String()
 }
 
-func (r *regexpObject) writeEscapedSource(sb *valueStringBuilder) bool {
-	if r.source.length() == 0 {
+func (r *regexpObject) writeEscapedSource(sb *StringBuilder) bool {
+	if r.source.Length() == 0 {
 		sb.WriteString(asciiString("(?:)"))
 		return true
 	}
@@ -490,7 +490,7 @@ L:
 		pos += size
 	}
 	if lastPos > 0 {
-		sb.WriteSubstring(r.source, lastPos, r.source.length())
+		sb.WriteSubstring(r.source, lastPos, r.source.Length())
 		return true
 	}
 	return false
@@ -498,7 +498,7 @@ L:
 
 func (r *Runtime) regexpproto_getSource(call FunctionCall) Value {
 	if this, ok := r.toObject(call.This).self.(*regexpObject); ok {
-		var sb valueStringBuilder
+		var sb StringBuilder
 		if this.writeEscapedSource(&sb) {
 			return sb.String()
 		}
@@ -652,7 +652,7 @@ func (r *Runtime) regExpExec(execFn func(FunctionCall) Value, rxObj *Object, arg
 	return res
 }
 
-func (r *Runtime) getGlobalRegexpMatches(rxObj *Object, s valueString) []Value {
+func (r *Runtime) getGlobalRegexpMatches(rxObj *Object, s String) []Value {
 	fullUnicode := nilSafe(rxObj.self.getStr("unicode", nil)).ToBoolean()
 	rxObj.self.setOwnStr("lastIndex", intToValue(0), true)
 	execFn, ok := r.toObject(rxObj.self.getStr("exec", nil)).self.assertCallable()
@@ -667,7 +667,7 @@ func (r *Runtime) getGlobalRegexpMatches(rxObj *Object, s valueString) []Value {
 		}
 		a = append(a, res)
 		matchStr := nilSafe(r.toObject(res).self.getIdx(valueInt(0), nil)).toString()
-		if matchStr.length() == 0 {
+		if matchStr.Length() == 0 {
 			thisIndex := toLength(rxObj.self.getStr("lastIndex", nil))
 			rxObj.self.setOwnStr("lastIndex", valueInt(advanceStringIndex64(s, thisIndex, fullUnicode)), true)
 		}
@@ -676,7 +676,7 @@ func (r *Runtime) getGlobalRegexpMatches(rxObj *Object, s valueString) []Value {
 	return a
 }
 
-func (r *Runtime) regexpproto_stdMatcherGeneric(rxObj *Object, s valueString) Value {
+func (r *Runtime) regexpproto_stdMatcherGeneric(rxObj *Object, s String) Value {
 	rx := rxObj.self
 	global := rx.getStr("global", nil)
 	if global != nil && global.ToBoolean() {
@@ -733,7 +733,7 @@ func (r *Runtime) regexpproto_stdMatcher(call FunctionCall) Value {
 		}
 		a := make([]Value, 0, len(res))
 		for _, result := range res {
-			a = append(a, s.substring(result[0], result[1]))
+			a = append(a, s.Substring(result[0], result[1]))
 		}
 		rx.setOwnStr("lastIndex", intToValue(int64(res[len(res)-1][1])), true)
 		return r.newArrayValues(a)
@@ -742,7 +742,7 @@ func (r *Runtime) regexpproto_stdMatcher(call FunctionCall) Value {
 	}
 }
 
-func (r *Runtime) regexpproto_stdSearchGeneric(rxObj *Object, arg valueString) Value {
+func (r *Runtime) regexpproto_stdSearchGeneric(rxObj *Object, arg String) Value {
 	rx := rxObj.self
 	previousLastIndex := nilSafe(rx.getStr("lastIndex", nil))
 	zero := intToValue(0)
@@ -780,7 +780,7 @@ func (r *Runtime) regexpproto_stdMatcherAll(call FunctionCall) Value {
 	return r.createRegExpStringIterator(matcher, s, global, fullUnicode)
 }
 
-func (r *Runtime) createRegExpStringIterator(matcher *Object, s valueString, global, fullUnicode bool) Value {
+func (r *Runtime) createRegExpStringIterator(matcher *Object, s String, global, fullUnicode bool) Value {
 	o := &Object{runtime: r}
 
 	ri := &regExpStringIterObject{
@@ -802,12 +802,12 @@ func (r *Runtime) createRegExpStringIterator(matcher *Object, s valueString, glo
 type regExpStringIterObject struct {
 	baseObject
 	matcher                   *Object
-	s                         valueString
+	s                         String
 	global, fullUnicode, done bool
 }
 
 // RegExpExec as defined in 21.2.5.2.1
-func regExpExec(r *Object, s valueString) Value {
+func regExpExec(r *Object, s String) Value {
 	exec := r.self.getStr("exec", nil)
 	if execObject, ok := exec.(*Object); ok {
 		if execFn, ok := execObject.self.assertCallable(); ok {
@@ -836,7 +836,7 @@ func (ri *regExpStringIterObject) next() (v Value) {
 	}
 
 	matchStr := nilSafe(ri.val.runtime.toObject(match).self.getIdx(valueInt(0), nil)).toString()
-	if matchStr.length() == 0 {
+	if matchStr.Length() == 0 {
 		thisIndex := toLength(ri.matcher.self.getStr("lastIndex", nil))
 		ri.matcher.self.setOwnStr("lastIndex", valueInt(advanceStringIndex64(ri.s, thisIndex, ri.fullUnicode)), true)
 	}
@@ -863,7 +863,7 @@ func (r *Runtime) regexpproto_stdSearch(call FunctionCall) Value {
 	return intToValue(int64(result[0]))
 }
 
-func (r *Runtime) regexpproto_stdSplitterGeneric(splitter *Object, s valueString, limit Value, unicodeMatching bool) Value {
+func (r *Runtime) regexpproto_stdSplitterGeneric(splitter *Object, s String, limit Value, unicodeMatching bool) Value {
 	var a []Value
 	var lim int64
 	if limit == nil || limit == _undefined {
@@ -874,7 +874,7 @@ func (r *Runtime) regexpproto_stdSplitterGeneric(splitter *Object, s valueString
 	if lim == 0 {
 		return r.newArrayValues(a)
 	}
-	size := s.length()
+	size := s.Length()
 	p := 0
 	execFn := toMethod(splitter.ToObject(r).self.getStr("exec", nil)) // must be non-nil
 
@@ -897,7 +897,7 @@ func (r *Runtime) regexpproto_stdSplitterGeneric(splitter *Object, s valueString
 			if e == int64(p) {
 				q = advanceStringIndex(s, q, unicodeMatching)
 			} else {
-				a = append(a, s.substring(p, q))
+				a = append(a, s.Substring(p, q))
 				if int64(len(a)) == lim {
 					return r.newArrayValues(a)
 				}
@@ -917,41 +917,41 @@ func (r *Runtime) regexpproto_stdSplitterGeneric(splitter *Object, s valueString
 			}
 		}
 	}
-	a = append(a, s.substring(p, size))
+	a = append(a, s.Substring(p, size))
 	return r.newArrayValues(a)
 }
 
-func advanceStringIndex(s valueString, pos int, unicode bool) int {
+func advanceStringIndex(s String, pos int, unicode bool) int {
 	next := pos + 1
 	if !unicode {
 		return next
 	}
-	l := s.length()
+	l := s.Length()
 	if next >= l {
 		return next
 	}
-	if !isUTF16FirstSurrogate(s.charAt(pos)) {
+	if !isUTF16FirstSurrogate(s.CharAt(pos)) {
 		return next
 	}
-	if !isUTF16SecondSurrogate(s.charAt(next)) {
+	if !isUTF16SecondSurrogate(s.CharAt(next)) {
 		return next
 	}
 	return next + 1
 }
 
-func advanceStringIndex64(s valueString, pos int64, unicode bool) int64 {
+func advanceStringIndex64(s String, pos int64, unicode bool) int64 {
 	next := pos + 1
 	if !unicode {
 		return next
 	}
-	l := int64(s.length())
+	l := int64(s.Length())
 	if next >= l {
 		return next
 	}
-	if !isUTF16FirstSurrogate(s.charAt(int(pos))) {
+	if !isUTF16FirstSurrogate(s.CharAt(int(pos))) {
 		return next
 	}
-	if !isUTF16SecondSurrogate(s.charAt(int(next))) {
+	if !isUTF16SecondSurrogate(s.CharAt(int(next))) {
 		return next
 	}
 	return next + 1
@@ -970,7 +970,7 @@ func (r *Runtime) regexpproto_stdSplitter(call FunctionCall) Value {
 
 		// Add 'y' flag if missing
 		if !strings.Contains(flagsStr, "y") {
-			flags = flags.concat(asciiString("y"))
+			flags = flags.Concat(asciiString("y"))
 		}
 		splitter = r.toConstructor(c)([]Value{rxObj, flags}, nil)
 		search = r.checkStdRegexp(splitter)
@@ -989,7 +989,7 @@ func (r *Runtime) regexpproto_stdSplitter(call FunctionCall) Value {
 		return r.newArrayValues(nil)
 	}
 
-	targetLength := s.length()
+	targetLength := s.Length()
 	var valueArray []Value
 	lastIndex := 0
 	found := 0
@@ -1011,7 +1011,7 @@ func (r *Runtime) regexpproto_stdSplitter(call FunctionCall) Value {
 		}
 
 		if lastIndex != match[0] {
-			valueArray = append(valueArray, s.substring(lastIndex, match[0]))
+			valueArray = append(valueArray, s.Substring(lastIndex, match[0]))
 			found++
 		} else if lastIndex == match[0] {
 			if lastIndex != -1 {
@@ -1030,7 +1030,7 @@ func (r *Runtime) regexpproto_stdSplitter(call FunctionCall) Value {
 			offset := index * 2
 			var value Value
 			if match[offset] != -1 {
-				value = s.substring(match[offset], match[offset+1])
+				value = s.Substring(match[offset], match[offset+1])
 			} else {
 				value = _undefined
 			}
@@ -1044,7 +1044,7 @@ func (r *Runtime) regexpproto_stdSplitter(call FunctionCall) Value {
 
 	if found != limit {
 		if lastIndex != targetLength {
-			valueArray = append(valueArray, s.substring(lastIndex, targetLength))
+			valueArray = append(valueArray, s.Substring(lastIndex, targetLength))
 		} else {
 			valueArray = append(valueArray, stringEmpty)
 		}
@@ -1054,7 +1054,7 @@ RETURN:
 	return r.newArrayValues(valueArray)
 }
 
-func (r *Runtime) regexpproto_stdReplacerGeneric(rxObj *Object, s, replaceStr valueString, rcall func(FunctionCall) Value) Value {
+func (r *Runtime) regexpproto_stdReplacerGeneric(rxObj *Object, s, replaceStr String, rcall func(FunctionCall) Value) Value {
 	var results []Value
 	if nilSafe(rxObj.self.getStr("global", nil)).ToBoolean() {
 		results = r.getGlobalRegexpMatches(rxObj, s)
@@ -1065,14 +1065,14 @@ func (r *Runtime) regexpproto_stdReplacerGeneric(rxObj *Object, s, replaceStr va
 			results = append(results, result)
 		}
 	}
-	lengthS := s.length()
+	lengthS := s.Length()
 	nextSourcePosition := 0
-	var resultBuf valueStringBuilder
+	var resultBuf StringBuilder
 	for _, result := range results {
 		obj := r.toObject(result)
 		nCaptures := max(toLength(obj.self.getStr("length", nil))-1, 0)
 		matched := nilSafe(obj.self.getIdx(valueInt(0), nil)).toString()
-		matchLength := matched.length()
+		matchLength := matched.Length()
 		position := toIntStrict(max(min(nilSafe(obj.self.getStr("index", nil)).ToInteger(), int64(lengthS)), 0))
 		var captures []Value
 		if rcall != nil {
@@ -1088,7 +1088,7 @@ func (r *Runtime) regexpproto_stdReplacerGeneric(rxObj *Object, s, replaceStr va
 			}
 			captures = append(captures, capN)
 		}
-		var replacement valueString
+		var replacement String
 		if rcall != nil {
 			captures = append(captures, intToValue(int64(position)), s)
 			replacement = rcall(FunctionCall{
@@ -1096,14 +1096,14 @@ func (r *Runtime) regexpproto_stdReplacerGeneric(rxObj *Object, s, replaceStr va
 				Arguments: captures,
 			}).toString()
 			if position >= nextSourcePosition {
-				resultBuf.WriteString(s.substring(nextSourcePosition, position))
+				resultBuf.WriteString(s.Substring(nextSourcePosition, position))
 				resultBuf.WriteString(replacement)
 				nextSourcePosition = position + matchLength
 			}
 		} else {
 			if position >= nextSourcePosition {
-				resultBuf.WriteString(s.substring(nextSourcePosition, position))
-				writeSubstitution(s, position, len(captures), func(idx int) valueString {
+				resultBuf.WriteString(s.Substring(nextSourcePosition, position))
+				writeSubstitution(s, position, len(captures), func(idx int) String {
 					capture := captures[idx]
 					if capture != _undefined {
 						return capture.toString()
@@ -1115,29 +1115,29 @@ func (r *Runtime) regexpproto_stdReplacerGeneric(rxObj *Object, s, replaceStr va
 		}
 	}
 	if nextSourcePosition < lengthS {
-		resultBuf.WriteString(s.substring(nextSourcePosition, lengthS))
+		resultBuf.WriteString(s.Substring(nextSourcePosition, lengthS))
 	}
 	return resultBuf.String()
 }
 
-func writeSubstitution(s valueString, position int, numCaptures int, getCapture func(int) valueString, replaceStr valueString, buf *valueStringBuilder) {
-	l := s.length()
-	rl := replaceStr.length()
+func writeSubstitution(s String, position int, numCaptures int, getCapture func(int) String, replaceStr String, buf *StringBuilder) {
+	l := s.Length()
+	rl := replaceStr.Length()
 	matched := getCapture(0)
-	tailPos := position + matched.length()
+	tailPos := position + matched.Length()
 
 	for i := 0; i < rl; i++ {
-		c := replaceStr.charAt(i)
+		c := replaceStr.CharAt(i)
 		if c == '$' && i < rl-1 {
-			ch := replaceStr.charAt(i + 1)
+			ch := replaceStr.CharAt(i + 1)
 			switch ch {
 			case '$':
 				buf.WriteRune('$')
 			case '`':
-				buf.WriteString(s.substring(0, position))
+				buf.WriteString(s.Substring(0, position))
 			case '\'':
 				if tailPos < l {
-					buf.WriteString(s.substring(tailPos, l))
+					buf.WriteString(s.Substring(tailPos, l))
 				}
 			case '&':
 				buf.WriteString(matched)
@@ -1145,7 +1145,7 @@ func writeSubstitution(s valueString, position int, numCaptures int, getCapture
 				matchNumber := 0
 				j := i + 1
 				for j < rl {
-					ch := replaceStr.charAt(j)
+					ch := replaceStr.CharAt(j)
 					if ch >= '0' && ch <= '9' {
 						m := matchNumber*10 + int(ch-'0')
 						if m >= numCaptures {
@@ -1163,12 +1163,12 @@ func writeSubstitution(s valueString, position int, numCaptures int, getCapture
 					continue
 				} else {
 					buf.WriteRune('$')
-					buf.WriteRune(ch)
+					buf.WriteRune(rune(ch))
 				}
 			}
 			i++
 		} else {
-			buf.WriteRune(c)
+			buf.WriteRune(rune(c))
 		}
 	}
 }

+ 60 - 60
builtin_string.go

@@ -22,8 +22,8 @@ func (r *Runtime) collator() *collate.Collator {
 	return collator
 }
 
-func toString(arg Value) valueString {
-	if s, ok := arg.(valueString); ok {
+func toString(arg Value) String {
+	if s, ok := arg.(String); ok {
 		return s
 	}
 	if s, ok := arg.(*Symbol); ok {
@@ -40,7 +40,7 @@ func (r *Runtime) builtin_String(call FunctionCall) Value {
 	}
 }
 
-func (r *Runtime) _newString(s valueString, proto *Object) *Object {
+func (r *Runtime) _newString(s String, proto *Object) *Object {
 	v := &Object{runtime: r}
 
 	o := &stringObject{}
@@ -57,7 +57,7 @@ func (r *Runtime) _newString(s valueString, proto *Object) *Object {
 }
 
 func (r *Runtime) builtin_newString(args []Value, proto *Object) *Object {
-	var s valueString
+	var s String
 	if len(args) > 0 {
 		s = args[0].toString()
 	} else {
@@ -67,7 +67,7 @@ func (r *Runtime) builtin_newString(args []Value, proto *Object) *Object {
 }
 
 func (r *Runtime) stringproto_toStringValueOf(this Value, funcName string) Value {
-	if str, ok := this.(valueString); ok {
+	if str, ok := this.(String); ok {
 		return str
 	}
 	if obj, ok := this.(*Object); ok {
@@ -125,7 +125,7 @@ func (r *Runtime) string_fromcharcode(call FunctionCall) Value {
 }
 
 func (r *Runtime) string_fromcodepoint(call FunctionCall) Value {
-	var sb valueStringBuilder
+	var sb StringBuilder
 	for _, arg := range call.Arguments {
 		num := arg.ToNumber()
 		var c rune
@@ -149,7 +149,7 @@ func (r *Runtime) string_raw(call FunctionCall) Value {
 	if literalSegments <= 0 {
 		return stringEmpty
 	}
-	var stringElements valueStringBuilder
+	var stringElements StringBuilder
 	nextIndex := int64(0)
 	numberOfSubstitutions := int64(len(call.Arguments) - 1)
 	for {
@@ -169,52 +169,52 @@ func (r *Runtime) stringproto_at(call FunctionCall) Value {
 	r.checkObjectCoercible(call.This)
 	s := call.This.toString()
 	pos := call.Argument(0).ToInteger()
-	length := int64(s.length())
+	length := int64(s.Length())
 	if pos < 0 {
 		pos = length + pos
 	}
 	if pos >= length || pos < 0 {
 		return _undefined
 	}
-	return s.substring(int(pos), int(pos+1))
+	return s.Substring(int(pos), int(pos+1))
 }
 
 func (r *Runtime) stringproto_charAt(call FunctionCall) Value {
 	r.checkObjectCoercible(call.This)
 	s := call.This.toString()
 	pos := call.Argument(0).ToInteger()
-	if pos < 0 || pos >= int64(s.length()) {
+	if pos < 0 || pos >= int64(s.Length()) {
 		return stringEmpty
 	}
-	return s.substring(int(pos), int(pos+1))
+	return s.Substring(int(pos), int(pos+1))
 }
 
 func (r *Runtime) stringproto_charCodeAt(call FunctionCall) Value {
 	r.checkObjectCoercible(call.This)
 	s := call.This.toString()
 	pos := call.Argument(0).ToInteger()
-	if pos < 0 || pos >= int64(s.length()) {
+	if pos < 0 || pos >= int64(s.Length()) {
 		return _NaN
 	}
-	return intToValue(int64(s.charAt(toIntStrict(pos)) & 0xFFFF))
+	return intToValue(int64(s.CharAt(toIntStrict(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()
+	size := s.Length()
 	if p < 0 || p >= int64(size) {
 		return _undefined
 	}
 	pos := toIntStrict(p)
-	first := s.charAt(pos)
+	first := s.CharAt(pos)
 	if isUTF16FirstSurrogate(first) {
 		pos++
 		if pos < size {
-			second := s.charAt(pos)
+			second := s.CharAt(pos)
 			if isUTF16SecondSurrogate(second) {
-				return intToValue(int64(utf16.DecodeRune(first, second)))
+				return intToValue(int64(utf16.DecodeRune(rune(first), rune(second))))
 			}
 		}
 	}
@@ -223,7 +223,7 @@ func (r *Runtime) stringproto_codePointAt(call FunctionCall) Value {
 
 func (r *Runtime) stringproto_concat(call FunctionCall) Value {
 	r.checkObjectCoercible(call.This)
-	strs := make([]valueString, len(call.Arguments)+1)
+	strs := make([]String, len(call.Arguments)+1)
 	a, u := devirtualizeString(call.This.toString())
 	allAscii := true
 	totalLen := 0
@@ -232,17 +232,17 @@ func (r *Runtime) stringproto_concat(call FunctionCall) Value {
 		totalLen = len(a)
 	} else {
 		strs[0] = u
-		totalLen = u.length()
+		totalLen = u.Length()
 		allAscii = false
 	}
 	for i, arg := range call.Arguments {
 		a, u := devirtualizeString(arg.toString())
 		if u != nil {
 			allAscii = false
-			totalLen += u.length()
+			totalLen += u.Length()
 			strs[i+1] = u
 		} else {
-			totalLen += a.length()
+			totalLen += a.Length()
 			strs[i+1] = a
 		}
 	}
@@ -267,7 +267,7 @@ func (r *Runtime) stringproto_concat(call FunctionCall) Value {
 				}
 			case unicodeString:
 				copy(buf[pos:], s[1:])
-				pos += s.length()
+				pos += s.Length()
 			}
 		}
 		return unicodeString(buf)
@@ -282,7 +282,7 @@ func (r *Runtime) stringproto_endsWith(call FunctionCall) Value {
 		panic(r.NewTypeError("First argument to String.prototype.endsWith must not be a regular expression"))
 	}
 	searchStr := searchString.toString()
-	l := int64(s.length())
+	l := int64(s.Length())
 	var pos int64
 	if posArg := call.Argument(1); posArg != _undefined {
 		pos = posArg.ToInteger()
@@ -290,13 +290,13 @@ func (r *Runtime) stringproto_endsWith(call FunctionCall) Value {
 		pos = l
 	}
 	end := toIntStrict(min(max(pos, 0), l))
-	searchLength := searchStr.length()
+	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) {
+		if s.CharAt(start+i) != searchStr.CharAt(i) {
 			return valueFalse
 		}
 	}
@@ -317,7 +317,7 @@ func (r *Runtime) stringproto_includes(call FunctionCall) Value {
 	} else {
 		pos = 0
 	}
-	start := toIntStrict(min(max(pos, 0), int64(s.length())))
+	start := toIntStrict(min(max(pos, 0), int64(s.Length())))
 	if s.index(searchStr, start) != -1 {
 		return valueTrue
 	}
@@ -333,7 +333,7 @@ func (r *Runtime) stringproto_indexOf(call FunctionCall) Value {
 	if pos < 0 {
 		pos = 0
 	} else {
-		l := int64(value.length())
+		l := int64(value.Length())
 		if pos > l {
 			pos = l
 		}
@@ -350,13 +350,13 @@ func (r *Runtime) stringproto_lastIndexOf(call FunctionCall) Value {
 
 	var pos int64
 	if f, ok := numPos.(valueFloat); ok && math.IsNaN(float64(f)) {
-		pos = int64(value.length())
+		pos = int64(value.Length())
 	} else {
 		pos = numPos.ToInteger()
 		if pos < 0 {
 			pos = 0
 		} else {
-			l := int64(value.length())
+			l := int64(value.Length())
 			if pos > l {
 				pos = l
 			}
@@ -480,17 +480,17 @@ func (r *Runtime) _stringPad(call FunctionCall, start bool) Value {
 	r.checkObjectCoercible(call.This)
 	s := call.This.toString()
 	maxLength := toLength(call.Argument(0))
-	stringLength := int64(s.length())
+	stringLength := int64(s.Length())
 	if maxLength <= stringLength {
 		return s
 	}
 	strAscii, strUnicode := devirtualizeString(s)
-	var filler valueString
+	var filler String
 	var fillerAscii asciiString
 	var fillerUnicode unicodeString
 	if fillString := call.Argument(1); fillString != _undefined {
 		filler = fillString.toString()
-		if filler.length() == 0 {
+		if filler.Length() == 0 {
 			return s
 		}
 		fillerAscii, fillerUnicode = devirtualizeString(filler)
@@ -500,7 +500,7 @@ func (r *Runtime) _stringPad(call FunctionCall, start bool) Value {
 	}
 	remaining := toIntStrict(maxLength - stringLength)
 	if fillerUnicode == nil && strUnicode == nil {
-		fl := fillerAscii.length()
+		fl := fillerAscii.Length()
 		var sb strings.Builder
 		sb.Grow(toIntStrict(maxLength))
 		if !start {
@@ -519,20 +519,20 @@ func (r *Runtime) _stringPad(call FunctionCall, start bool) Value {
 		return asciiString(sb.String())
 	}
 	var sb unicodeStringBuilder
-	sb.Grow(toIntStrict(maxLength))
+	sb.ensureStarted(toIntStrict(maxLength))
 	if !start {
-		sb.WriteString(s)
+		sb.writeString(s)
 	}
-	fl := filler.length()
+	fl := filler.Length()
 	for remaining >= fl {
-		sb.WriteString(filler)
+		sb.writeString(filler)
 		remaining -= fl
 	}
 	if remaining > 0 {
-		sb.WriteString(filler.substring(0, remaining))
+		sb.writeString(filler.Substring(0, remaining))
 	}
 	if start {
-		sb.WriteString(s)
+		sb.writeString(s)
 	}
 
 	return sb.String()
@@ -557,7 +557,7 @@ func (r *Runtime) stringproto_repeat(call FunctionCall) Value {
 	if numInt < 0 {
 		panic(r.newError(r.global.RangeError, "Invalid count value"))
 	}
-	if numInt == 0 || s.length() == 0 {
+	if numInt == 0 || s.Length() == 0 {
 		return stringEmpty
 	}
 	num := toIntStrict(numInt)
@@ -572,14 +572,14 @@ func (r *Runtime) stringproto_repeat(call FunctionCall) Value {
 	}
 
 	var sb unicodeStringBuilder
-	sb.Grow(u.length() * num)
+	sb.Grow(u.Length() * num)
 	for i := 0; i < num; i++ {
 		sb.writeUnicodeString(u)
 	}
 	return sb.String()
 }
 
-func getReplaceValue(replaceValue Value) (str valueString, rcall func(FunctionCall) Value) {
+func getReplaceValue(replaceValue Value) (str String, rcall func(FunctionCall) Value) {
 	if replaceValue, ok := replaceValue.(*Object); ok {
 		if c, ok := replaceValue.self.assertCallable(); ok {
 			rcall = c
@@ -590,17 +590,17 @@ func getReplaceValue(replaceValue Value) (str valueString, rcall func(FunctionCa
 	return
 }
 
-func stringReplace(s valueString, found [][]int, newstring valueString, rcall func(FunctionCall) Value) Value {
+func stringReplace(s String, found [][]int, newstring String, rcall func(FunctionCall) Value) Value {
 	if len(found) == 0 {
 		return s
 	}
 
 	a, u := devirtualizeString(s)
 
-	var buf valueStringBuilder
+	var buf StringBuilder
 
 	lastIndex := 0
-	lengthS := s.length()
+	lengthS := s.Length()
 	if rcall != nil {
 		for _, item := range found {
 			if item[0] != lastIndex {
@@ -614,7 +614,7 @@ func stringReplace(s valueString, found [][]int, newstring valueString, rcall fu
 					if u == nil {
 						argumentList[index] = a[item[offset]:item[offset+1]]
 					} else {
-						argumentList[index] = u.substring(item[offset], item[offset+1])
+						argumentList[index] = u.Substring(item[offset], item[offset+1])
 					}
 				} else {
 					argumentList[index] = _undefined
@@ -632,15 +632,15 @@ func stringReplace(s valueString, found [][]int, newstring valueString, rcall fu
 	} else {
 		for _, item := range found {
 			if item[0] != lastIndex {
-				buf.WriteString(s.substring(lastIndex, item[0]))
+				buf.WriteString(s.Substring(lastIndex, item[0]))
 			}
 			matchCount := len(item) / 2
-			writeSubstitution(s, item[0], matchCount, func(idx int) valueString {
+			writeSubstitution(s, item[0], matchCount, func(idx int) String {
 				if item[idx*2] != -1 {
 					if u == nil {
 						return a[item[idx*2]:item[idx*2+1]]
 					}
-					return u.substring(item[idx*2], item[idx*2+1])
+					return u.Substring(item[idx*2], item[idx*2+1])
 				}
 				return stringEmpty
 			}, newstring, &buf)
@@ -649,7 +649,7 @@ func stringReplace(s valueString, found [][]int, newstring valueString, rcall fu
 	}
 
 	if lastIndex != lengthS {
-		buf.WriteString(s.substring(lastIndex, lengthS))
+		buf.WriteString(s.Substring(lastIndex, lengthS))
 	}
 
 	return buf.String()
@@ -673,7 +673,7 @@ func (r *Runtime) stringproto_replace(call FunctionCall) Value {
 	searchStr := searchValue.toString()
 	pos := s.index(searchStr, 0)
 	if pos != -1 {
-		found = append(found, []int{pos, pos + searchStr.length()})
+		found = append(found, []int{pos, pos + searchStr.Length()})
 	}
 
 	str, rcall := getReplaceValue(replaceValue)
@@ -715,7 +715,7 @@ func (r *Runtime) stringproto_slice(call FunctionCall) Value {
 	r.checkObjectCoercible(call.This)
 	s := call.This.toString()
 
-	l := int64(s.length())
+	l := int64(s.Length())
 	start := call.Argument(0).ToInteger()
 	var end int64
 	if arg1 := call.Argument(1); arg1 != _undefined {
@@ -747,7 +747,7 @@ func (r *Runtime) stringproto_slice(call FunctionCall) Value {
 	}
 
 	if end > start {
-		return s.substring(int(start), int(end))
+		return s.Substring(int(start), int(end))
 	}
 	return stringEmpty
 }
@@ -817,18 +817,18 @@ func (r *Runtime) stringproto_startsWith(call FunctionCall) Value {
 		panic(r.NewTypeError("First argument to String.prototype.startsWith must not be a regular expression"))
 	}
 	searchStr := searchString.toString()
-	l := int64(s.length())
+	l := int64(s.Length())
 	var pos int64
 	if posArg := call.Argument(1); posArg != _undefined {
 		pos = posArg.ToInteger()
 	}
 	start := toIntStrict(min(max(pos, 0), l))
-	searchLength := searchStr.length()
+	searchLength := searchStr.Length()
 	if int64(searchLength+start) > l {
 		return valueFalse
 	}
 	for i := 0; i < searchLength; i++ {
-		if s.charAt(start+i) != searchStr.charAt(i) {
+		if s.CharAt(start+i) != searchStr.CharAt(i) {
 			return valueFalse
 		}
 	}
@@ -839,7 +839,7 @@ func (r *Runtime) stringproto_substring(call FunctionCall) Value {
 	r.checkObjectCoercible(call.This)
 	s := call.This.toString()
 
-	l := int64(s.length())
+	l := int64(s.Length())
 	intStart := call.Argument(0).ToInteger()
 	var intEnd int64
 	if end := call.Argument(1); end != _undefined {
@@ -863,7 +863,7 @@ func (r *Runtime) stringproto_substring(call FunctionCall) Value {
 		intStart, intEnd = intEnd, intStart
 	}
 
-	return s.substring(int(intStart), int(intEnd))
+	return s.Substring(int(intStart), int(intEnd))
 }
 
 func (r *Runtime) stringproto_toLowerCase(call FunctionCall) Value {
@@ -909,7 +909,7 @@ func (r *Runtime) stringproto_substr(call FunctionCall) Value {
 	s := call.This.toString()
 	start := call.Argument(0).ToInteger()
 	var length int64
-	sl := int64(s.length())
+	sl := int64(s.Length())
 	if arg := call.Argument(1); arg != _undefined {
 		length = arg.ToInteger()
 	} else {
@@ -925,7 +925,7 @@ func (r *Runtime) stringproto_substr(call FunctionCall) Value {
 		return stringEmpty
 	}
 
-	return s.substring(int(start), int(start+length))
+	return s.Substring(int(start), int(start+length))
 }
 
 func (r *Runtime) stringIterProto_next(call FunctionCall) Value {

+ 8 - 8
builtin_string_test.go

@@ -190,7 +190,7 @@ if (result.value !== pair) {
 func TestValueStringBuilder(t *testing.T) {
 	t.Run("substringASCII", func(t *testing.T) {
 		t.Parallel()
-		var sb valueStringBuilder
+		var sb StringBuilder
 		str := newStringValue("a\U00010000b")
 		sb.WriteSubstring(str, 0, 1)
 		res := sb.String()
@@ -201,7 +201,7 @@ func TestValueStringBuilder(t *testing.T) {
 
 	t.Run("substringASCIIPure", func(t *testing.T) {
 		t.Parallel()
-		var sb valueStringBuilder
+		var sb StringBuilder
 		str := newStringValue("ab")
 		sb.WriteSubstring(str, 0, 1)
 		res := sb.String()
@@ -212,7 +212,7 @@ func TestValueStringBuilder(t *testing.T) {
 
 	t.Run("substringUnicode", func(t *testing.T) {
 		t.Parallel()
-		var sb valueStringBuilder
+		var sb StringBuilder
 		str := newStringValue("a\U00010000b")
 		sb.WriteSubstring(str, 1, 3)
 		res := sb.String()
@@ -223,7 +223,7 @@ func TestValueStringBuilder(t *testing.T) {
 
 	t.Run("substringASCIIUnicode", func(t *testing.T) {
 		t.Parallel()
-		var sb valueStringBuilder
+		var sb StringBuilder
 		str := newStringValue("a\U00010000b")
 		sb.WriteSubstring(str, 0, 2)
 		res := sb.String()
@@ -234,7 +234,7 @@ func TestValueStringBuilder(t *testing.T) {
 
 	t.Run("substringUnicodeASCII", func(t *testing.T) {
 		t.Parallel()
-		var sb valueStringBuilder
+		var sb StringBuilder
 		str := newStringValue("a\U00010000b")
 		sb.WriteSubstring(str, 2, 4)
 		res := sb.String()
@@ -245,7 +245,7 @@ func TestValueStringBuilder(t *testing.T) {
 
 	t.Run("concatSubstringUnicodeASCII", func(t *testing.T) {
 		t.Parallel()
-		var sb valueStringBuilder
+		var sb StringBuilder
 		sb.WriteString(newStringValue("юникод"))
 		sb.WriteSubstring(asciiString(" ascii"), 0, 6)
 		if res := sb.String(); !res.SameAs(newStringValue("юникод ascii")) {
@@ -255,7 +255,7 @@ func TestValueStringBuilder(t *testing.T) {
 
 	t.Run("concat_ASCII_importedASCII", func(t *testing.T) {
 		t.Parallel()
-		var sb valueStringBuilder
+		var sb StringBuilder
 		sb.WriteString(asciiString("ascii"))
 		sb.WriteString(&importedString{s: " imported_ascii1234567890"})
 		s := sb.String()
@@ -266,7 +266,7 @@ func TestValueStringBuilder(t *testing.T) {
 
 	t.Run("concat_ASCII_importedUnicode", func(t *testing.T) {
 		t.Parallel()
-		var sb valueStringBuilder
+		var sb StringBuilder
 		sb.WriteString(asciiString("ascii"))
 		sb.WriteString(&importedString{s: " imported_юникод"})
 		s := sb.String()

+ 1 - 1
builtin_symbol.go

@@ -18,7 +18,7 @@ var (
 )
 
 func (r *Runtime) builtin_symbol(call FunctionCall) Value {
-	var desc valueString
+	var desc String
 	if arg := call.Argument(0); !IsUndefined(arg) {
 		desc = arg.toString()
 	}

+ 3 - 3
builtin_typedarrays.go

@@ -744,7 +744,7 @@ func (r *Runtime) typedArrayProto_join(call FunctionCall) Value {
 	if ta, ok := r.toObject(call.This).self.(*typedArrayObject); ok {
 		ta.viewedArrayBuf.ensureNotDetached(true)
 		s := call.Argument(0)
-		var sep valueString
+		var sep String
 		if s != _undefined {
 			sep = s.toString()
 		} else {
@@ -755,7 +755,7 @@ func (r *Runtime) typedArrayProto_join(call FunctionCall) Value {
 			return stringEmpty
 		}
 
-		var buf valueStringBuilder
+		var buf StringBuilder
 
 		var element0 Value
 		if ta.isValidIntegerIndex(0) {
@@ -1120,7 +1120,7 @@ func (r *Runtime) typedArrayProto_subarray(call FunctionCall) Value {
 func (r *Runtime) typedArrayProto_toLocaleString(call FunctionCall) Value {
 	if ta, ok := r.toObject(call.This).self.(*typedArrayObject); ok {
 		length := ta.length
-		var buf valueStringBuilder
+		var buf StringBuilder
 		for i := 0; i < length; i++ {
 			ta.viewedArrayBuf.ensureNotDetached(true)
 			if i > 0 {

+ 1 - 1
compiler.go

@@ -1337,7 +1337,7 @@ func (c *compiler) assert(cond bool, offset int, msg string, args ...interface{}
 }
 
 func privateIdString(desc unistring.String) unistring.String {
-	return asciiString("#").concat(stringValueFromRaw(desc)).string()
+	return asciiString("#").Concat(stringValueFromRaw(desc)).string()
 }
 
 type privateName struct {

+ 1 - 1
compiler_expr.go

@@ -904,7 +904,7 @@ func (e *compiledSuperBracketExpr) deleteExpr() compiledExpr {
 func (c *compiler) checkConstantString(expr compiledExpr) (unistring.String, bool) {
 	if expr.constant() {
 		if val, ex := c.evalConst(expr); ex == nil {
-			if s, ok := val.(valueString); ok {
+			if s, ok := val.(String); ok {
 				return s.string(), true
 			}
 		}

+ 1 - 1
destruct.go

@@ -52,7 +52,7 @@ func (d *destructKeyedSource) className() string {
 	return d.w().className()
 }
 
-func (d *destructKeyedSource) typeOf() valueString {
+func (d *destructKeyedSource) typeOf() String {
 	return d.w().typeOf()
 }
 

+ 4 - 4
func.go

@@ -53,7 +53,7 @@ type AsyncContextTracker interface {
 }
 
 type funcObjectImpl interface {
-	source() valueString
+	source() String
 }
 
 type baseFuncObject struct {
@@ -154,7 +154,7 @@ type generatorObject struct {
 	state     generatorState
 }
 
-func (f *nativeFuncObject) source() valueString {
+func (f *nativeFuncObject) source() String {
 	return newStringValue(fmt.Sprintf("function %s() { [native code] }", nilSafe(f.getStr("name", nil)).toString()))
 }
 
@@ -253,7 +253,7 @@ func (f *baseFuncObject) createInstance(newTarget *Object) *Object {
 	return f.val.runtime.newBaseObject(proto, classObject).val
 }
 
-func (f *baseJsFuncObject) source() valueString {
+func (f *baseJsFuncObject) source() String {
 	return newStringValue(f.src)
 }
 
@@ -458,7 +458,7 @@ func (f *baseFuncObject) exportType() reflect.Type {
 	return reflectTypeFunc
 }
 
-func (f *baseFuncObject) typeOf() valueString {
+func (f *baseFuncObject) typeOf() String {
 	return stringFunction
 }
 

+ 2 - 2
object.go

@@ -151,7 +151,7 @@ type objectExportCtx struct {
 type objectImpl interface {
 	sortable
 	className() string
-	typeOf() valueString
+	typeOf() String
 	getStr(p unistring.String, receiver Value) Value
 	getIdx(p valueInt, receiver Value) Value
 	getSym(p *Symbol, receiver Value) Value
@@ -279,7 +279,7 @@ func (o *baseObject) className() string {
 	return o.class
 }
 
-func (o *baseObject) typeOf() valueString {
+func (o *baseObject) typeOf() String {
 	return stringObjectC
 }
 

+ 1 - 1
object_dynamic.go

@@ -555,7 +555,7 @@ func (o *baseDynamicObject) getPrivateEnv(*privateEnvType, bool) *privateElement
 	panic(newTypeError("Dynamic objects cannot have private elements"))
 }
 
-func (o *baseDynamicObject) typeOf() valueString {
+func (o *baseDynamicObject) typeOf() String {
 	return stringObjectC
 }
 

+ 1 - 1
object_goreflect_test.go

@@ -27,7 +27,7 @@ func TestGoReflectGet(t *testing.T) {
 		t.Fatal(err)
 	}
 
-	if s, ok := v.(valueString); ok {
+	if s, ok := v.(String); ok {
 		if s.String() != "42" {
 			t.Fatalf("Unexpected string: %s", s)
 		}

+ 1 - 1
object_lazy.go

@@ -17,7 +17,7 @@ func (o *lazyObject) className() string {
 	return obj.className()
 }
 
-func (o *lazyObject) typeOf() valueString {
+func (o *lazyObject) typeOf() String {
 	obj := o.create(o.val)
 	o.val.self = obj
 	return obj.typeOf()

+ 4 - 4
object_test.go

@@ -619,7 +619,7 @@ func BenchmarkConv(b *testing.B) {
 }
 
 func BenchmarkToUTF8String(b *testing.B) {
-	var s valueString = asciiString("test")
+	var s String = asciiString("test")
 	for i := 0; i < b.N; i++ {
 		_ = s.String()
 	}
@@ -648,9 +648,9 @@ func BenchmarkAddString(b *testing.B) {
 
 	for i := 0; i < b.N; i++ {
 		var z Value
-		if xi, ok := x.(valueString); ok {
-			if yi, ok := y.(valueString); ok {
-				z = xi.concat(yi)
+		if xi, ok := x.(String); ok {
+			if yi, ok := y.(String); ok {
+				z = xi.Concat(yi)
 			}
 		}
 		if !z.StrictEquals(tst) {

+ 2 - 2
proxy.go

@@ -795,7 +795,7 @@ func (p *proxyObject) proxyOwnKeys() ([]Value, bool) {
 		l := toLength(keys.self.getStr("length", nil))
 		for k := int64(0); k < l; k++ {
 			item := keys.self.getIdx(valueInt(k), nil)
-			if _, ok := item.(valueString); !ok {
+			if _, ok := item.(String); !ok {
 				if _, ok := item.(*Symbol); !ok {
 					panic(p.val.runtime.NewTypeError("%s is not a valid property name", item.String()))
 				}
@@ -1050,7 +1050,7 @@ func (p *proxyObject) className() string {
 	return classObject
 }
 
-func (p *proxyObject) typeOf() valueString {
+func (p *proxyObject) typeOf() String {
 	if p.call == nil {
 		return stringObjectC
 	}

+ 24 - 24
regexp.go

@@ -12,7 +12,7 @@ import (
 )
 
 type regexp2MatchCache struct {
-	target valueString
+	target String
 	runes  []rune
 	posMap []int
 }
@@ -96,8 +96,8 @@ func (p *regexpPattern) createRegexp2() {
 }
 
 func buildUTF8PosMap(s unicodeString) (positionMap, string) {
-	pm := make(positionMap, 0, s.length())
-	rd := s.reader()
+	pm := make(positionMap, 0, s.Length())
+	rd := s.Reader()
 	sPos, utf8Pos := 0, 0
 	var sb strings.Builder
 	for {
@@ -117,7 +117,7 @@ func buildUTF8PosMap(s unicodeString) (positionMap, string) {
 	return pm, sb.String()
 }
 
-func (p *regexpPattern) findSubmatchIndex(s valueString, start int) []int {
+func (p *regexpPattern) findSubmatchIndex(s String, start int) []int {
 	if p.regexpWrapper == nil {
 		return p.regexp2Wrapper.findSubmatchIndex(s, start, p.unicode, p.global || p.sticky)
 	}
@@ -131,7 +131,7 @@ func (p *regexpPattern) findSubmatchIndex(s valueString, start int) []int {
 	return p.regexpWrapper.findSubmatchIndex(s, p.unicode)
 }
 
-func (p *regexpPattern) findAllSubmatchIndex(s valueString, start int, limit int, sticky bool) [][]int {
+func (p *regexpPattern) findAllSubmatchIndex(s String, start int, limit int, sticky bool) [][]int {
 	if p.regexpWrapper == nil {
 		return p.regexp2Wrapper.findAllSubmatchIndex(s, start, limit, sticky, p.unicode)
 	}
@@ -190,19 +190,19 @@ func (p *regexpPattern) clone() *regexpPattern {
 type regexpObject struct {
 	baseObject
 	pattern *regexpPattern
-	source  valueString
+	source  String
 
 	standard bool
 }
 
-func (r *regexp2Wrapper) findSubmatchIndex(s valueString, start int, fullUnicode, doCache bool) (result []int) {
+func (r *regexp2Wrapper) findSubmatchIndex(s String, start int, fullUnicode, doCache bool) (result []int) {
 	if fullUnicode {
 		return r.findSubmatchIndexUnicode(s, start, doCache)
 	}
 	return r.findSubmatchIndexUTF16(s, start, doCache)
 }
 
-func (r *regexp2Wrapper) findUTF16Cached(s valueString, start int, doCache bool) (match *regexp2.Match, runes []rune, err error) {
+func (r *regexp2Wrapper) findUTF16Cached(s String, start int, doCache bool) (match *regexp2.Match, runes []rune, err error) {
 	wrapped := r.rx
 	cache := r.cache
 	if cache != nil && cache.posMap == nil && cache.target.SameAs(s) {
@@ -228,7 +228,7 @@ func (r *regexp2Wrapper) findUTF16Cached(s valueString, start int, doCache bool)
 	return
 }
 
-func (r *regexp2Wrapper) findSubmatchIndexUTF16(s valueString, start int, doCache bool) (result []int) {
+func (r *regexp2Wrapper) findSubmatchIndexUTF16(s String, start int, doCache bool) (result []int) {
 	match, _, err := r.findUTF16Cached(s, start, doCache)
 	if err != nil {
 		return
@@ -250,7 +250,7 @@ func (r *regexp2Wrapper) findSubmatchIndexUTF16(s valueString, start int, doCach
 	return
 }
 
-func (r *regexp2Wrapper) findUnicodeCached(s valueString, start int, doCache bool) (match *regexp2.Match, posMap []int, err error) {
+func (r *regexp2Wrapper) findUnicodeCached(s String, start int, doCache bool) (match *regexp2.Match, posMap []int, err error) {
 	var (
 		runes       []rune
 		mappedStart int
@@ -263,7 +263,7 @@ func (r *regexp2Wrapper) findUnicodeCached(s valueString, start int, doCache boo
 		runes, posMap = cache.runes, cache.posMap
 		mappedStart, splitPair = posMapReverseLookup(posMap, start)
 	} else {
-		posMap, runes, mappedStart, splitPair = buildPosMap(&lenientUtf16Decoder{utf16Reader: s.utf16Reader()}, s.length(), start)
+		posMap, runes, mappedStart, splitPair = buildPosMap(&lenientUtf16Decoder{utf16Reader: s.utf16Reader()}, s.Length(), start)
 		cache = nil
 	}
 	if splitPair {
@@ -293,7 +293,7 @@ func (r *regexp2Wrapper) findUnicodeCached(s valueString, start int, doCache boo
 	return
 }
 
-func (r *regexp2Wrapper) findSubmatchIndexUnicode(s valueString, start int, doCache bool) (result []int) {
+func (r *regexp2Wrapper) findSubmatchIndexUnicode(s String, start int, doCache bool) (result []int) {
 	match, posMap, err := r.findUnicodeCached(s, start, doCache)
 	if match == nil || err != nil {
 		return
@@ -312,7 +312,7 @@ func (r *regexp2Wrapper) findSubmatchIndexUnicode(s valueString, start int, doCa
 	return
 }
 
-func (r *regexp2Wrapper) findAllSubmatchIndexUTF16(s valueString, start, limit int, sticky bool) [][]int {
+func (r *regexp2Wrapper) findAllSubmatchIndexUTF16(s String, start, limit int, sticky bool) [][]int {
 	wrapped := r.rx
 	match, runes, err := r.findUTF16Cached(s, start, false)
 	if match == nil || err != nil {
@@ -436,7 +436,7 @@ func (r *regexp2Wrapper) findAllSubmatchIndexUnicode(s unicodeString, start, lim
 	return results
 }
 
-func (r *regexp2Wrapper) findAllSubmatchIndex(s valueString, start, limit int, sticky, fullUnicode bool) [][]int {
+func (r *regexp2Wrapper) findAllSubmatchIndex(s String, start, limit int, sticky, fullUnicode bool) [][]int {
 	a, u := devirtualizeString(s)
 	if u != nil {
 		if fullUnicode {
@@ -470,7 +470,7 @@ func (r *regexpWrapper) findAllSubmatchIndex(s string, limit int, sticky bool) (
 	return
 }
 
-func (r *regexpWrapper) findSubmatchIndex(s valueString, fullUnicode bool) []int {
+func (r *regexpWrapper) findSubmatchIndex(s String, fullUnicode bool) []int {
 	a, u := devirtualizeString(s)
 	if u != nil {
 		return r.findSubmatchIndexUnicode(u, fullUnicode)
@@ -486,7 +486,7 @@ func (r *regexpWrapper) findSubmatchIndexASCII(s string) []int {
 func (r *regexpWrapper) findSubmatchIndexUnicode(s unicodeString, fullUnicode bool) (result []int) {
 	wrapped := (*regexp.Regexp)(r)
 	if fullUnicode {
-		posMap, runes, _, _ := buildPosMap(&lenientUtf16Decoder{utf16Reader: s.utf16Reader()}, s.length(), 0)
+		posMap, runes, _, _ := buildPosMap(&lenientUtf16Decoder{utf16Reader: s.utf16Reader()}, s.Length(), 0)
 		res := wrapped.FindReaderSubmatchIndex(&arrayRuneReader{runes: runes})
 		for i, item := range res {
 			if item >= 0 {
@@ -495,23 +495,23 @@ func (r *regexpWrapper) findSubmatchIndexUnicode(s unicodeString, fullUnicode bo
 		}
 		return res
 	}
-	return wrapped.FindReaderSubmatchIndex(s.utf16Reader())
+	return wrapped.FindReaderSubmatchIndex(s.utf16RuneReader())
 }
 
 func (r *regexpWrapper) clone() *regexpWrapper {
 	return r
 }
 
-func (r *regexpObject) execResultToArray(target valueString, result []int) Value {
+func (r *regexpObject) execResultToArray(target String, result []int) Value {
 	captureCount := len(result) >> 1
 	valueArray := make([]Value, captureCount)
 	matchIndex := result[0]
-	valueArray[0] = target.substring(result[0], result[1])
+	valueArray[0] = target.Substring(result[0], result[1])
 	lowerBound := 0
 	for index := 1; index < captureCount; index++ {
 		offset := index << 1
 		if result[offset] >= 0 && result[offset+1] >= lowerBound {
-			valueArray[index] = target.substring(result[offset], result[offset+1])
+			valueArray[index] = target.Substring(result[offset], result[offset+1])
 			lowerBound = result[offset]
 		} else {
 			valueArray[index] = _undefined
@@ -552,16 +552,16 @@ func (r *regexpObject) updateLastIndex(index int64, firstResult, lastResult []in
 	return true
 }
 
-func (r *regexpObject) execRegexp(target valueString) (match bool, result []int) {
+func (r *regexpObject) execRegexp(target String) (match bool, result []int) {
 	index := r.getLastIndex()
-	if index >= 0 && index <= int64(target.length()) {
+	if index >= 0 && index <= int64(target.Length()) {
 		result = r.pattern.findSubmatchIndex(target, int(index))
 	}
 	match = r.updateLastIndex(index, result, result)
 	return
 }
 
-func (r *regexpObject) exec(target valueString) Value {
+func (r *regexpObject) exec(target String) Value {
 	match, result := r.execRegexp(target)
 	if match {
 		return r.execResultToArray(target, result)
@@ -569,7 +569,7 @@ func (r *regexpObject) exec(target valueString) Value {
 	return _null
 }
 
-func (r *regexpObject) test(target valueString) bool {
+func (r *regexpObject) test(target String) bool {
 	match, _ := r.execRegexp(target)
 	return match
 }

+ 14 - 13
runtime.go

@@ -228,24 +228,24 @@ func (f *StackFrame) Position() file.Position {
 	return f.prg.src.Position(f.prg.sourceOffset(f.pc))
 }
 
-func (f *StackFrame) WriteToValueBuilder(b *valueStringBuilder) {
+func (f *StackFrame) WriteToValueBuilder(b *StringBuilder) {
 	if f.prg != nil {
 		if n := f.prg.funcName; n != "" {
 			b.WriteString(stringValueFromRaw(n))
-			b.WriteASCII(" (")
+			b.writeASCII(" (")
 		}
 		p := f.Position()
 		if p.Filename != "" {
-			b.WriteASCII(p.Filename)
+			b.WriteUTF8String(p.Filename)
 		} else {
-			b.WriteASCII("<eval>")
+			b.writeASCII("<eval>")
 		}
 		b.WriteRune(':')
-		b.WriteASCII(strconv.Itoa(p.Line))
+		b.writeASCII(strconv.Itoa(p.Line))
 		b.WriteRune(':')
-		b.WriteASCII(strconv.Itoa(p.Column))
+		b.writeASCII(strconv.Itoa(p.Column))
 		b.WriteRune('(')
-		b.WriteASCII(strconv.Itoa(f.pc))
+		b.writeASCII(strconv.Itoa(f.pc))
 		b.WriteRune(')')
 		if f.prg.funcName != "" {
 			b.WriteRune(')')
@@ -253,9 +253,9 @@ func (f *StackFrame) WriteToValueBuilder(b *valueStringBuilder) {
 	} else {
 		if f.funcName != "" {
 			b.WriteString(stringValueFromRaw(f.funcName))
-			b.WriteASCII(" (")
+			b.writeASCII(" (")
 		}
-		b.WriteASCII("native")
+		b.writeASCII("native")
 		if f.funcName != "" {
 			b.WriteRune(')')
 		}
@@ -953,7 +953,7 @@ func (r *Runtime) builtin_thrower(call FunctionCall) Value {
 	return nil
 }
 
-func (r *Runtime) eval(srcVal valueString, direct, strict bool) Value {
+func (r *Runtime) eval(srcVal String, direct, strict bool) Value {
 	src := escapeInvalidUtf16(srcVal)
 	vm := r.vm
 	inGlobal := true
@@ -1001,7 +1001,7 @@ func (r *Runtime) builtin_eval(call FunctionCall) Value {
 	if len(call.Arguments) == 0 {
 		return _undefined
 	}
-	if str, ok := call.Arguments[0].(valueString); ok {
+	if str, ok := call.Arguments[0].(String); ok {
 		return r.eval(str, false, false)
 	}
 	return call.Arguments[0]
@@ -1267,7 +1267,7 @@ repeat:
 				goto fail
 			}
 		}
-	case valueString:
+	case String:
 		v = num.ToNumber()
 		goto repeat
 	default:
@@ -1658,7 +1658,8 @@ Notes on individual types:
 
 # Primitive types
 
-Primitive types (numbers, string, bool) are converted to the corresponding JavaScript primitives.
+Primitive types (numbers, string, bool) are converted to the corresponding JavaScript primitives. These values
+are goroutine-safe and can be transferred between runtimes.
 
 # Strings
 

+ 2 - 2
runtime_test.go

@@ -3002,12 +3002,12 @@ func BenchmarkStringMapGet(b *testing.B) {
 }
 
 func BenchmarkValueStringMapGet(b *testing.B) {
-	m := make(map[valueString]Value)
+	m := make(map[String]Value)
 	for i := 0; i < 100; i++ {
 		m[asciiString(strconv.Itoa(i))] = intToValue(int64(i))
 	}
 	b.ResetTimer()
-	var key valueString = asciiString("50")
+	var key String = asciiString("50")
 	for i := 0; i < b.N; i++ {
 		if m[key] == nil {
 			b.Fatal()

+ 88 - 54
string.go

@@ -14,51 +14,60 @@ const (
 )
 
 var (
-	stringTrue        valueString = asciiString("true")
-	stringFalse       valueString = asciiString("false")
-	stringNull        valueString = asciiString("null")
-	stringUndefined   valueString = asciiString("undefined")
-	stringObjectC     valueString = asciiString("object")
-	stringFunction    valueString = asciiString("function")
-	stringBoolean     valueString = asciiString("boolean")
-	stringString      valueString = asciiString("string")
-	stringSymbol      valueString = asciiString("symbol")
-	stringNumber      valueString = asciiString("number")
-	stringNaN         valueString = asciiString("NaN")
-	stringInfinity                = asciiString("Infinity")
-	stringNegInfinity             = asciiString("-Infinity")
-	stringBound_      valueString = asciiString("bound ")
-	stringEmpty       valueString = asciiString("")
-
-	stringError          valueString = asciiString("Error")
-	stringAggregateError valueString = asciiString("AggregateError")
-	stringTypeError      valueString = asciiString("TypeError")
-	stringReferenceError valueString = asciiString("ReferenceError")
-	stringSyntaxError    valueString = asciiString("SyntaxError")
-	stringRangeError     valueString = asciiString("RangeError")
-	stringEvalError      valueString = asciiString("EvalError")
-	stringURIError       valueString = asciiString("URIError")
-	stringGoError        valueString = asciiString("GoError")
-
-	stringObjectNull      valueString = asciiString("[object Null]")
-	stringObjectUndefined valueString = asciiString("[object Undefined]")
-	stringInvalidDate     valueString = asciiString("Invalid Date")
+	stringTrue        String = asciiString("true")
+	stringFalse       String = asciiString("false")
+	stringNull        String = asciiString("null")
+	stringUndefined   String = asciiString("undefined")
+	stringObjectC     String = asciiString("object")
+	stringFunction    String = asciiString("function")
+	stringBoolean     String = asciiString("boolean")
+	stringString      String = asciiString("string")
+	stringSymbol      String = asciiString("symbol")
+	stringNumber      String = asciiString("number")
+	stringNaN         String = asciiString("NaN")
+	stringInfinity           = asciiString("Infinity")
+	stringNegInfinity        = asciiString("-Infinity")
+	stringBound_      String = asciiString("bound ")
+	stringEmpty       String = asciiString("")
+
+	stringError          String = asciiString("Error")
+	stringAggregateError String = asciiString("AggregateError")
+	stringTypeError      String = asciiString("TypeError")
+	stringReferenceError String = asciiString("ReferenceError")
+	stringSyntaxError    String = asciiString("SyntaxError")
+	stringRangeError     String = asciiString("RangeError")
+	stringEvalError      String = asciiString("EvalError")
+	stringURIError       String = asciiString("URIError")
+	stringGoError        String = asciiString("GoError")
+
+	stringObjectNull      String = asciiString("[object Null]")
+	stringObjectUndefined String = asciiString("[object Undefined]")
+	stringInvalidDate     String = asciiString("Invalid Date")
 )
 
-type valueString interface {
+type utf16Reader interface {
+	readChar() (c uint16, err error)
+}
+
+// String represents an ECMAScript string Value. Its internal representation depends on the contents of the
+// string, but in any case it is capable of holding any UTF-16 string, either valid or invalid.
+// Instances of this type, as any other primitive values, are goroutine-safe and can be passed between runtimes.
+// Strings can be created using Runtime.ToValue(goString) or StringFromUTF16.
+type String interface {
 	Value
-	charAt(int) rune
-	length() int
-	concat(valueString) valueString
-	substring(start, end int) valueString
-	compareTo(valueString) int
-	reader() io.RuneReader
-	utf16Reader() io.RuneReader
+	CharAt(int) uint16
+	Length() int
+	Concat(String) String
+	Substring(start, end int) String
+	CompareTo(String) int
+	Reader() io.RuneReader
+	utf16Reader() utf16Reader
+	utf16RuneReader() io.RuneReader
 	utf16Runes() []rune
-	index(valueString, int) int
-	lastIndex(valueString, int) int
-	toLower() valueString
-	toUpper() valueString
+	index(String, int) int
+	lastIndex(String, int) int
+	toLower() String
+	toUpper() String
 	toTrimmedUTF8() string
 }
 
@@ -67,12 +76,12 @@ type stringIterObject struct {
 	reader io.RuneReader
 }
 
-func isUTF16FirstSurrogate(r rune) bool {
-	return r >= 0xD800 && r <= 0xDBFF
+func isUTF16FirstSurrogate(c uint16) bool {
+	return c >= 0xD800 && c <= 0xDBFF
 }
 
-func isUTF16SecondSurrogate(r rune) bool {
-	return r >= 0xDC00 && r <= 0xDFFF
+func isUTF16SecondSurrogate(c uint16) bool {
+	return c >= 0xDC00 && c <= 0xDFFF
 }
 
 func (si *stringIterObject) next() Value {
@@ -87,7 +96,7 @@ func (si *stringIterObject) next() Value {
 	return si.val.runtime.createIterResultObject(stringFromRune(r), false)
 }
 
-func stringFromRune(r rune) valueString {
+func stringFromRune(r rune) String {
 	if r < utf8.RuneSelf {
 		var sb strings.Builder
 		sb.WriteByte(byte(r))
@@ -98,7 +107,7 @@ func stringFromRune(r rune) valueString {
 	return sb.String()
 }
 
-func (r *Runtime) createStringIterator(s valueString) Value {
+func (r *Runtime) createStringIterator(s String) Value {
 	o := &Object{runtime: r}
 
 	si := &stringIterObject{
@@ -116,19 +125,19 @@ func (r *Runtime) createStringIterator(s valueString) Value {
 
 type stringObject struct {
 	baseObject
-	value      valueString
+	value      String
 	length     int
 	lengthProp valueProperty
 }
 
-func newStringValue(s string) valueString {
+func newStringValue(s string) String {
 	if u := unistring.Scan(s); u != nil {
 		return unicodeString(u)
 	}
 	return asciiString(s)
 }
 
-func stringValueFromRaw(raw unistring.String) valueString {
+func stringValueFromRaw(raw unistring.String) String {
 	if b := raw.AsUtf16(); b != nil {
 		return unicodeString(b)
 	}
@@ -142,7 +151,7 @@ func (s *stringObject) init() {
 
 func (s *stringObject) setLength() {
 	if s.value != nil {
-		s.length = s.value.length()
+		s.length = s.value.Length()
 	}
 	s.lengthProp.value = intToValue(int64(s.length))
 	s._put("length", &s.lengthProp)
@@ -192,7 +201,7 @@ func (s *stringObject) getOwnPropIdx(idx valueInt) 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 unistring.String, val Value, throw bool) bool {
@@ -242,7 +251,7 @@ func (s *stringObject) defineOwnPropertyIdx(idx valueInt, descr PropertyDescript
 }
 
 type stringPropIter struct {
-	str         valueString // separate, because obj can be the singleton
+	str         String // separate, because obj can be the singleton
 	obj         *stringObject
 	idx, length int
 }
@@ -307,7 +316,7 @@ func (s *stringObject) hasOwnPropertyIdx(idx valueInt) bool {
 	return s.baseObject.hasOwnPropertyStr(idx.string())
 }
 
-func devirtualizeString(s valueString) (asciiString, unicodeString) {
+func devirtualizeString(s String) (asciiString, unicodeString) {
 	switch s := s.(type) {
 	case asciiString:
 		return s, nil
@@ -327,3 +336,28 @@ func devirtualizeString(s valueString) (asciiString, unicodeString) {
 func unknownStringTypeErr(v Value) interface{} {
 	return newTypeError("Internal bug: unknown string type: %T", v)
 }
+
+// StringFromUTF16 creates a string value from an array of UTF-16 code units. The result is a copy, so the initial
+// slice can be modified after calling this function (but it must not be modified while the function is running).
+// No validation of any kind is performed.
+func StringFromUTF16(chars []uint16) String {
+	isAscii := true
+	for _, c := range chars {
+		if c >= utf8.RuneSelf {
+			isAscii = false
+			break
+		}
+	}
+	if isAscii {
+		var sb strings.Builder
+		sb.Grow(len(chars))
+		for _, c := range chars {
+			sb.WriteByte(byte(c))
+		}
+		return asciiString(sb.String())
+	}
+	buf := make([]uint16, len(chars)+1)
+	buf[0] = unistring.BOM
+	copy(buf[1:], chars)
+	return unicodeString(buf)
+}

+ 48 - 14
string_ascii.go

@@ -29,14 +29,48 @@ func (rr *asciiRuneReader) ReadRune() (r rune, size int, err error) {
 	return
 }
 
-func (s asciiString) reader() io.RuneReader {
+type asciiUtf16Reader struct {
+	s   asciiString
+	pos int
+}
+
+func (rr *asciiUtf16Reader) readChar() (c uint16, err error) {
+	if rr.pos < len(rr.s) {
+		c = uint16(rr.s[rr.pos])
+		rr.pos++
+	} else {
+		err = io.EOF
+	}
+	return
+}
+
+func (rr *asciiUtf16Reader) ReadRune() (r rune, size int, err error) {
+	if rr.pos < len(rr.s) {
+		r = rune(rr.s[rr.pos])
+		rr.pos++
+		size = 1
+	} else {
+		err = io.EOF
+	}
+	return
+}
+
+func (s asciiString) Reader() io.RuneReader {
 	return &asciiRuneReader{
 		s: s,
 	}
 }
 
-func (s asciiString) utf16Reader() io.RuneReader {
-	return s.reader()
+func (s asciiString) utf16Reader() utf16Reader {
+	return &asciiUtf16Reader{
+		s: s,
+	}
+}
+
+func (s asciiString) utf16RuneReader() io.RuneReader {
+	return &asciiUtf16Reader{
+		s: s,
+	}
 }
 
 func (s asciiString) utf16Runes() []rune {
@@ -115,7 +149,7 @@ func (s asciiString) ToInteger() int64 {
 	return i
 }
 
-func (s asciiString) toString() valueString {
+func (s asciiString) toString() String {
 	return s
 }
 
@@ -237,15 +271,15 @@ func (s asciiString) hash(hash *maphash.Hash) uint64 {
 	return h
 }
 
-func (s asciiString) charAt(idx int) rune {
-	return rune(s[idx])
+func (s asciiString) CharAt(idx int) uint16 {
+	return uint16(s[idx])
 }
 
-func (s asciiString) length() int {
+func (s asciiString) Length() int {
 	return len(s)
 }
 
-func (s asciiString) concat(other valueString) valueString {
+func (s asciiString) Concat(other String) String {
 	a, u := devirtualizeString(other)
 	if u != nil {
 		b := make([]uint16, len(s)+len(u))
@@ -259,11 +293,11 @@ func (s asciiString) concat(other valueString) valueString {
 	return s + a
 }
 
-func (s asciiString) substring(start, end int) valueString {
+func (s asciiString) Substring(start, end int) String {
 	return s[start:end]
 }
 
-func (s asciiString) compareTo(other valueString) int {
+func (s asciiString) CompareTo(other String) int {
 	switch other := other.(type) {
 	case asciiString:
 		return strings.Compare(string(s), string(other))
@@ -276,7 +310,7 @@ func (s asciiString) compareTo(other valueString) int {
 	}
 }
 
-func (s asciiString) index(substr valueString, start int) int {
+func (s asciiString) index(substr String, start int) int {
 	a, u := devirtualizeString(substr)
 	if u == nil {
 		p := strings.Index(string(s[start:]), string(a))
@@ -287,7 +321,7 @@ func (s asciiString) index(substr valueString, start int) int {
 	return -1
 }
 
-func (s asciiString) lastIndex(substr valueString, pos int) int {
+func (s asciiString) lastIndex(substr String, pos int) int {
 	a, u := devirtualizeString(substr)
 	if u == nil {
 		end := pos + len(a)
@@ -302,11 +336,11 @@ func (s asciiString) lastIndex(substr valueString, pos int) int {
 	return -1
 }
 
-func (s asciiString) toLower() valueString {
+func (s asciiString) toLower() String {
 	return asciiString(strings.ToLower(string(s)))
 }
 
-func (s asciiString) toUpper() valueString {
+func (s asciiString) toUpper() String {
 	return asciiString(strings.ToUpper(string(s)))
 }
 

+ 52 - 33
string_imported.go

@@ -47,7 +47,7 @@ func (i *importedString) ToInteger() int64 {
 	return asciiString(i.s).ToInteger()
 }
 
-func (i *importedString) toString() valueString {
+func (i *importedString) toString() String {
 	return i
 }
 
@@ -148,23 +148,23 @@ func (i *importedString) hash(hasher *maphash.Hash) uint64 {
 	return asciiString(i.s).hash(hasher)
 }
 
-func (i *importedString) charAt(idx int) rune {
+func (i *importedString) CharAt(idx int) uint16 {
 	i.ensureScanned()
 	if i.u != nil {
-		return i.u.charAt(idx)
+		return i.u.CharAt(idx)
 	}
-	return asciiString(i.s).charAt(idx)
+	return asciiString(i.s).CharAt(idx)
 }
 
-func (i *importedString) length() int {
+func (i *importedString) Length() int {
 	i.ensureScanned()
 	if i.u != nil {
-		return i.u.length()
+		return i.u.Length()
 	}
-	return asciiString(i.s).length()
+	return asciiString(i.s).Length()
 }
 
-func (i *importedString) concat(v valueString) valueString {
+func (i *importedString) Concat(v String) String {
 	if !i.scanned {
 		if v, ok := v.(*importedString); ok {
 			if !v.scanned {
@@ -174,29 +174,29 @@ func (i *importedString) concat(v valueString) valueString {
 		i.ensureScanned()
 	}
 	if i.u != nil {
-		return i.u.concat(v)
+		return i.u.Concat(v)
 	}
-	return asciiString(i.s).concat(v)
+	return asciiString(i.s).Concat(v)
 }
 
-func (i *importedString) substring(start, end int) valueString {
+func (i *importedString) Substring(start, end int) String {
 	i.ensureScanned()
 	if i.u != nil {
-		return i.u.substring(start, end)
+		return i.u.Substring(start, end)
 	}
-	return asciiString(i.s).substring(start, end)
+	return asciiString(i.s).Substring(start, end)
 }
 
-func (i *importedString) compareTo(v valueString) int {
+func (i *importedString) CompareTo(v String) int {
 	return strings.Compare(i.s, v.String())
 }
 
-func (i *importedString) reader() io.RuneReader {
+func (i *importedString) Reader() io.RuneReader {
 	if i.scanned {
 		if i.u != nil {
-			return i.u.reader()
+			return i.u.Reader()
 		}
-		return asciiString(i.s).reader()
+		return asciiString(i.s).Reader()
 	}
 	return strings.NewReader(i.s)
 }
@@ -204,24 +204,22 @@ func (i *importedString) reader() io.RuneReader {
 type stringUtf16Reader struct {
 	s      string
 	pos    int
-	second rune
+	second uint16
 }
 
-func (s *stringUtf16Reader) ReadRune() (r rune, size int, err error) {
-	if s.second >= 0 {
-		r = s.second
-		s.second = -1
-		size = 1
+func (s *stringUtf16Reader) readChar() (c uint16, err error) {
+	if s.second != 0 {
+		c, s.second = s.second, 0
 		return
 	}
 	if s.pos < len(s.s) {
 		r1, size1 := utf8.DecodeRuneInString(s.s[s.pos:])
 		s.pos += size1
-		size = 1
 		if r1 <= 0xFFFF {
-			r = r1
+			c = uint16(r1)
 		} else {
-			r, s.second = utf16.EncodeRune(r1)
+			first, second := utf16.EncodeRune(r1)
+			c, s.second = uint16(first), uint16(second)
 		}
 	} else {
 		err = io.EOF
@@ -229,7 +227,17 @@ func (s *stringUtf16Reader) ReadRune() (r rune, size int, err error) {
 	return
 }
 
-func (i *importedString) utf16Reader() io.RuneReader {
+func (s *stringUtf16Reader) ReadRune() (r rune, size int, err error) {
+	c, err := s.readChar()
+	if err != nil {
+		return
+	}
+	r = rune(c)
+	size = 1
+	return
+}
+
+func (i *importedString) utf16Reader() utf16Reader {
 	if i.scanned {
 		if i.u != nil {
 			return i.u.utf16Reader()
@@ -237,8 +245,19 @@ func (i *importedString) utf16Reader() io.RuneReader {
 		return asciiString(i.s).utf16Reader()
 	}
 	return &stringUtf16Reader{
-		s:      i.s,
-		second: -1,
+		s: i.s,
+	}
+}
+
+func (i *importedString) utf16RuneReader() io.RuneReader {
+	if i.scanned {
+		if i.u != nil {
+			return i.u.utf16RuneReader()
+		}
+		return asciiString(i.s).utf16RuneReader()
+	}
+	return &stringUtf16Reader{
+		s: i.s,
 	}
 }
 
@@ -250,7 +269,7 @@ func (i *importedString) utf16Runes() []rune {
 	return asciiString(i.s).utf16Runes()
 }
 
-func (i *importedString) index(v valueString, start int) int {
+func (i *importedString) index(v String, start int) int {
 	i.ensureScanned()
 	if i.u != nil {
 		return i.u.index(v, start)
@@ -258,7 +277,7 @@ func (i *importedString) index(v valueString, start int) int {
 	return asciiString(i.s).index(v, start)
 }
 
-func (i *importedString) lastIndex(v valueString, pos int) int {
+func (i *importedString) lastIndex(v String, pos int) int {
 	i.ensureScanned()
 	if i.u != nil {
 		return i.u.lastIndex(v, pos)
@@ -266,7 +285,7 @@ func (i *importedString) lastIndex(v valueString, pos int) int {
 	return asciiString(i.s).lastIndex(v, pos)
 }
 
-func (i *importedString) toLower() valueString {
+func (i *importedString) toLower() String {
 	i.ensureScanned()
 	if i.u != nil {
 		return toLower(i.s)
@@ -274,7 +293,7 @@ func (i *importedString) toLower() valueString {
 	return asciiString(i.s).toLower()
 }
 
-func (i *importedString) toUpper() valueString {
+func (i *importedString) toUpper() String {
 	i.ensureScanned()
 	if i.u != nil {
 		caser := cases.Upper(language.Und)

+ 28 - 0
string_test.go

@@ -149,6 +149,34 @@ func TestImportedString(t *testing.T) {
 	}
 }
 
+func TestStringFromUTF16(t *testing.T) {
+	s := StringFromUTF16([]uint16{})
+	if s.Length() != 0 || !s.SameAs(asciiString("")) {
+		t.Fatal(s)
+	}
+
+	s = StringFromUTF16([]uint16{0xD800})
+	if s.Length() != 1 || s.CharAt(0) != 0xD800 {
+		t.Fatal(s)
+	}
+
+	s = StringFromUTF16([]uint16{'A', 'B'})
+	if !s.SameAs(asciiString("AB")) {
+		t.Fatal(s)
+	}
+}
+
+func TestStringBuilder(t *testing.T) {
+	t.Run("writeUTF8String-switch", func(t *testing.T) {
+		var sb StringBuilder
+		sb.WriteUTF8String("Head")
+		sb.WriteUTF8String("1ábc")
+		if res := sb.String().String(); res != "Head1ábc" {
+			t.Fatal(res)
+		}
+	})
+}
+
 func BenchmarkASCIIConcat(b *testing.B) {
 	vm := New()
 

+ 115 - 56
string_unicode.go

@@ -30,12 +30,16 @@ type utf16RuneReader struct {
 
 // passes through invalid surrogate pairs
 type lenientUtf16Decoder struct {
-	utf16Reader io.RuneReader
-	prev        rune
+	utf16Reader utf16Reader
+	prev        uint16
 	prevSet     bool
 }
 
-type valueStringBuilder struct {
+// StringBuilder serves similar purpose to strings.Builder, except it works with ECMAScript String.
+// Use it to efficiently build 'native' ECMAScript values that either contain invalid UTF-16 surrogate pairs
+// (and therefore cannot be represented as UTF-8) or never expected to be exported to Go. See also
+// StringFromUTF16.
+type StringBuilder struct {
 	asciiBuilder   strings.Builder
 	unicodeBuilder unicodeStringBuilder
 }
@@ -49,11 +53,21 @@ var (
 	InvalidRuneError = errors.New("invalid rune")
 )
 
+func (rr *utf16RuneReader) readChar() (c uint16, err error) {
+	if rr.pos < len(rr.s) {
+		c = rr.s[rr.pos]
+		rr.pos++
+		return
+	}
+	err = io.EOF
+	return
+}
+
 func (rr *utf16RuneReader) ReadRune() (r rune, size int, err error) {
 	if rr.pos < len(rr.s) {
 		r = rune(rr.s[rr.pos])
-		size++
 		rr.pos++
+		size = 1
 		return
 	}
 	err = io.EOF
@@ -61,57 +75,60 @@ func (rr *utf16RuneReader) ReadRune() (r rune, size int, err error) {
 }
 
 func (rr *lenientUtf16Decoder) ReadRune() (r rune, size int, err error) {
+	var c uint16
 	if rr.prevSet {
-		r = rr.prev
-		size = 1
+		c = rr.prev
 		rr.prevSet = false
 	} else {
-		r, size, err = rr.utf16Reader.ReadRune()
+		c, err = rr.utf16Reader.readChar()
 		if err != nil {
 			return
 		}
 	}
-	if isUTF16FirstSurrogate(r) {
-		second, _, err1 := rr.utf16Reader.ReadRune()
+	size = 1
+	if isUTF16FirstSurrogate(c) {
+		second, err1 := rr.utf16Reader.readChar()
 		if err1 != nil {
 			if err1 != io.EOF {
 				err = err1
+			} else {
+				r = rune(c)
 			}
 			return
 		}
 		if isUTF16SecondSurrogate(second) {
-			r = utf16.DecodeRune(r, second)
+			r = utf16.DecodeRune(rune(c), rune(second))
 			size++
+			return
 		} else {
 			rr.prev = second
 			rr.prevSet = true
 		}
 	}
-
+	r = rune(c)
 	return
 }
 
 func (rr *unicodeRuneReader) ReadRune() (r rune, size int, err error) {
 	if rr.pos < len(rr.s) {
-		r = rune(rr.s[rr.pos])
+		c := rr.s[rr.pos]
 		size++
 		rr.pos++
-		if isUTF16FirstSurrogate(r) {
+		if isUTF16FirstSurrogate(c) {
 			if rr.pos < len(rr.s) {
-				second := rune(rr.s[rr.pos])
+				second := rr.s[rr.pos]
 				if isUTF16SecondSurrogate(second) {
-					r = utf16.DecodeRune(r, second)
+					r = utf16.DecodeRune(rune(c), rune(second))
 					size++
 					rr.pos++
-				} else {
-					err = InvalidRuneError
+					return
 				}
-			} else {
-				err = InvalidRuneError
 			}
-		} else if isUTF16SecondSurrogate(r) {
+			err = InvalidRuneError
+		} else if isUTF16SecondSurrogate(c) {
 			err = InvalidRuneError
 		}
+		r = rune(c)
 	} else {
 		err = io.EOF
 	}
@@ -136,8 +153,8 @@ func (b *unicodeStringBuilder) ensureStarted(initialSize int) {
 	}
 }
 
-func (b *unicodeStringBuilder) WriteString(s valueString) {
-	b.ensureStarted(s.length())
+// assumes already started
+func (b *unicodeStringBuilder) writeString(s String) {
 	a, u := devirtualizeString(s)
 	if u != nil {
 		b.buf = append(b.buf, u[1:]...)
@@ -149,11 +166,11 @@ func (b *unicodeStringBuilder) WriteString(s valueString) {
 	}
 }
 
-func (b *unicodeStringBuilder) String() valueString {
+func (b *unicodeStringBuilder) String() String {
 	if b.unicode {
 		return unicodeString(b.buf)
 	}
-	if len(b.buf) == 0 {
+	if len(b.buf) < 2 {
 		return stringEmpty
 	}
 	buf := make([]byte, 0, len(b.buf)-1)
@@ -164,14 +181,18 @@ func (b *unicodeStringBuilder) String() valueString {
 }
 
 func (b *unicodeStringBuilder) WriteRune(r rune) {
+	b.ensureStarted(2)
+	b.writeRuneFast(r)
+}
+
+// assumes already started
+func (b *unicodeStringBuilder) writeRuneFast(r rune) {
 	if r <= 0xFFFF {
-		b.ensureStarted(1)
 		b.buf = append(b.buf, uint16(r))
 		if !b.unicode && r >= utf8.RuneSelf {
 			b.unicode = true
 		}
 	} else {
-		b.ensureStarted(2)
 		first, second := utf16.EncodeRune(r)
 		b.buf = append(b.buf, uint16(first), uint16(second))
 		b.unicode = true
@@ -179,26 +200,24 @@ func (b *unicodeStringBuilder) WriteRune(r rune) {
 }
 
 func (b *unicodeStringBuilder) writeASCIIString(bytes string) {
-	b.ensureStarted(len(bytes))
 	for _, c := range bytes {
 		b.buf = append(b.buf, uint16(c))
 	}
 }
 
 func (b *unicodeStringBuilder) writeUnicodeString(str unicodeString) {
-	b.ensureStarted(str.length())
 	b.buf = append(b.buf, str[1:]...)
 	b.unicode = true
 }
 
-func (b *valueStringBuilder) ascii() bool {
+func (b *StringBuilder) ascii() bool {
 	return len(b.unicodeBuilder.buf) == 0
 }
 
-func (b *valueStringBuilder) WriteString(s valueString) {
+func (b *StringBuilder) WriteString(s String) {
 	a, u := devirtualizeString(s)
 	if u != nil {
-		b.switchToUnicode(u.length())
+		b.switchToUnicode(u.Length())
 		b.unicodeBuilder.writeUnicodeString(u)
 	} else {
 		if b.ascii() {
@@ -209,7 +228,27 @@ func (b *valueStringBuilder) WriteString(s valueString) {
 	}
 }
 
-func (b *valueStringBuilder) WriteASCII(s string) {
+func (b *StringBuilder) WriteUTF8String(s string) {
+	firstUnicodeIdx := 0
+	if b.ascii() {
+		for i := 0; i < len(s); i++ {
+			if s[i] >= utf8.RuneSelf {
+				b.switchToUnicode(len(s))
+				b.unicodeBuilder.writeASCIIString(s[:i])
+				firstUnicodeIdx = i
+				goto unicode
+			}
+		}
+		b.asciiBuilder.WriteString(s)
+		return
+	}
+unicode:
+	for _, r := range s[firstUnicodeIdx:] {
+		b.unicodeBuilder.writeRuneFast(r)
+	}
+}
+
+func (b *StringBuilder) writeASCII(s string) {
 	if b.ascii() {
 		b.asciiBuilder.WriteString(s)
 	} else {
@@ -217,12 +256,12 @@ func (b *valueStringBuilder) WriteASCII(s string) {
 	}
 }
 
-func (b *valueStringBuilder) WriteRune(r rune) {
+func (b *StringBuilder) WriteRune(r rune) {
 	if r < utf8.RuneSelf {
 		if b.ascii() {
 			b.asciiBuilder.WriteByte(byte(r))
 		} else {
-			b.unicodeBuilder.WriteRune(r)
+			b.unicodeBuilder.writeRuneFast(r)
 		}
 	} else {
 		var extraLen int
@@ -232,18 +271,18 @@ func (b *valueStringBuilder) WriteRune(r rune) {
 			extraLen = 2
 		}
 		b.switchToUnicode(extraLen)
-		b.unicodeBuilder.WriteRune(r)
+		b.unicodeBuilder.writeRuneFast(r)
 	}
 }
 
-func (b *valueStringBuilder) String() valueString {
+func (b *StringBuilder) String() String {
 	if b.ascii() {
 		return asciiString(b.asciiBuilder.String())
 	}
 	return b.unicodeBuilder.String()
 }
 
-func (b *valueStringBuilder) Grow(n int) {
+func (b *StringBuilder) Grow(n int) {
 	if b.ascii() {
 		b.asciiBuilder.Grow(n)
 	} else {
@@ -251,15 +290,29 @@ func (b *valueStringBuilder) Grow(n int) {
 	}
 }
 
-func (b *valueStringBuilder) switchToUnicode(extraLen int) {
+// LikelyUnicode hints to the builder that the resulting string is likely to contain Unicode (non-ASCII) characters.
+// The argument is an extra capacity (in characters) to reserve on top of the current length (it's like calling
+// Grow() afterwards).
+// This method may be called at any point (not just when the buffer is empty), although for efficiency it should
+// be called as early as possible.
+func (b *StringBuilder) LikelyUnicode(extraLen int) {
+	b.switchToUnicode(extraLen)
+}
+
+func (b *StringBuilder) switchToUnicode(extraLen int) {
 	if b.ascii() {
-		b.unicodeBuilder.ensureStarted(b.asciiBuilder.Len() + extraLen)
+		c := b.asciiBuilder.Cap()
+		newCap := b.asciiBuilder.Len() + extraLen
+		if newCap < c {
+			newCap = c
+		}
+		b.unicodeBuilder.ensureStarted(newCap)
 		b.unicodeBuilder.writeASCIIString(b.asciiBuilder.String())
 		b.asciiBuilder.Reset()
 	}
 }
 
-func (b *valueStringBuilder) WriteSubstring(source valueString, start int, end int) {
+func (b *StringBuilder) WriteSubstring(source String, start int, end int) {
 	a, us := devirtualizeString(source)
 	if us == nil {
 		if b.ascii() {
@@ -272,7 +325,7 @@ func (b *valueStringBuilder) WriteSubstring(source valueString, start int, end i
 	if b.ascii() {
 		uc := false
 		for i := start; i < end; i++ {
-			if us.charAt(i) >= utf8.RuneSelf {
+			if us.CharAt(i) >= utf8.RuneSelf {
 				uc = true
 				break
 			}
@@ -282,7 +335,7 @@ func (b *valueStringBuilder) WriteSubstring(source valueString, start int, end i
 		} else {
 			b.asciiBuilder.Grow(end - start + 1)
 			for i := start; i < end; i++ {
-				b.asciiBuilder.WriteByte(byte(us.charAt(i)))
+				b.asciiBuilder.WriteByte(byte(us.CharAt(i)))
 			}
 			return
 		}
@@ -291,13 +344,19 @@ func (b *valueStringBuilder) WriteSubstring(source valueString, start int, end i
 	b.unicodeBuilder.unicode = true
 }
 
-func (s unicodeString) reader() io.RuneReader {
+func (s unicodeString) Reader() io.RuneReader {
 	return &unicodeRuneReader{
 		s: s[1:],
 	}
 }
 
-func (s unicodeString) utf16Reader() io.RuneReader {
+func (s unicodeString) utf16Reader() utf16Reader {
+	return &utf16RuneReader{
+		s: s[1:],
+	}
+}
+
+func (s unicodeString) utf16RuneReader() io.RuneReader {
 	return &utf16RuneReader{
 		s: s[1:],
 	}
@@ -315,7 +374,7 @@ func (s unicodeString) ToInteger() int64 {
 	return 0
 }
 
-func (s unicodeString) toString() valueString {
+func (s unicodeString) toString() String {
 	return s
 }
 
@@ -394,15 +453,15 @@ func (s unicodeString) baseObject(r *Runtime) *Object {
 	return ss.val
 }
 
-func (s unicodeString) charAt(idx int) rune {
-	return rune(s[idx+1])
+func (s unicodeString) CharAt(idx int) uint16 {
+	return s[idx+1]
 }
 
-func (s unicodeString) length() int {
+func (s unicodeString) Length() int {
 	return len(s) - 1
 }
 
-func (s unicodeString) concat(other valueString) valueString {
+func (s unicodeString) Concat(other String) String {
 	a, u := devirtualizeString(other)
 	if u != nil {
 		b := make(unicodeString, len(s)+len(u)-1)
@@ -419,7 +478,7 @@ func (s unicodeString) concat(other valueString) valueString {
 	return unicodeString(b)
 }
 
-func (s unicodeString) substring(start, end int) valueString {
+func (s unicodeString) Substring(start, end int) String {
 	ss := s[start+1 : end+1]
 	for _, c := range ss {
 		if c >= utf8.RuneSelf {
@@ -440,12 +499,12 @@ func (s unicodeString) String() string {
 	return string(utf16.Decode(s[1:]))
 }
 
-func (s unicodeString) compareTo(other valueString) int {
+func (s unicodeString) CompareTo(other String) int {
 	// TODO handle invalid UTF-16
 	return strings.Compare(s.String(), other.String())
 }
 
-func (s unicodeString) index(substr valueString, start int) int {
+func (s unicodeString) index(substr String, start int) int {
 	var ss []uint16
 	a, u := devirtualizeString(substr)
 	if u != nil {
@@ -473,7 +532,7 @@ func (s unicodeString) index(substr valueString, start int) int {
 	return -1
 }
 
-func (s unicodeString) lastIndex(substr valueString, start int) int {
+func (s unicodeString) lastIndex(substr String, start int) int {
 	var ss []uint16
 	a, u := devirtualizeString(substr)
 	if u != nil {
@@ -508,7 +567,7 @@ func unicodeStringFromRunes(r []rune) unicodeString {
 	return unistring.NewFromRunes(r).AsUtf16()
 }
 
-func toLower(s string) valueString {
+func toLower(s string) String {
 	caser := cases.Lower(language.Und)
 	r := []rune(caser.String(s))
 	// Workaround
@@ -531,11 +590,11 @@ func toLower(s string) valueString {
 	return unicodeStringFromRunes(r)
 }
 
-func (s unicodeString) toLower() valueString {
+func (s unicodeString) toLower() String {
 	return toLower(s.String())
 }
 
-func (s unicodeString) toUpper() valueString {
+func (s unicodeString) toUpper() String {
 	caser := cases.Upper(language.Und)
 	return newStringValue(caser.String(s.String()))
 }

+ 19 - 19
value.go

@@ -74,7 +74,7 @@ var intCache [256]Value
 // For Object it depends on the Object type, see Object.Export() for more details.
 type Value interface {
 	ToInteger() int64
-	toString() valueString
+	toString() String
 	string() unistring.String
 	ToString() Value
 	String() string
@@ -116,7 +116,7 @@ type valueUndefined struct {
 // Symbols can be shared by multiple Runtimes.
 type Symbol struct {
 	h    uintptr
-	desc valueString
+	desc String
 }
 
 type valueUnresolved struct {
@@ -178,7 +178,7 @@ func (i valueInt) ToInteger() int64 {
 	return int64(i)
 }
 
-func (i valueInt) toString() valueString {
+func (i valueInt) toString() String {
 	return asciiString(i.String())
 }
 
@@ -220,7 +220,7 @@ func (i valueInt) Equals(other Value) bool {
 		return i == o
 	case valueFloat:
 		return float64(i) == float64(o)
-	case valueString:
+	case String:
 		return o.ToNumber().Equals(i)
 	case valueBool:
 		return int64(i) == o.ToInteger()
@@ -265,7 +265,7 @@ func (b valueBool) ToInteger() int64 {
 	return 0
 }
 
-func (b valueBool) toString() valueString {
+func (b valueBool) toString() String {
 	if b {
 		return stringTrue
 	}
@@ -360,7 +360,7 @@ func (n valueNull) ToInteger() int64 {
 	return 0
 }
 
-func (n valueNull) toString() valueString {
+func (n valueNull) toString() String {
 	return stringNull
 }
 
@@ -376,7 +376,7 @@ func (n valueNull) String() string {
 	return "null"
 }
 
-func (u valueUndefined) toString() valueString {
+func (u valueUndefined) toString() String {
 	return stringUndefined
 }
 
@@ -470,7 +470,7 @@ func (p *valueProperty) ToInteger() int64 {
 	return 0
 }
 
-func (p *valueProperty) toString() valueString {
+func (p *valueProperty) toString() String {
 	return stringEmpty
 }
 
@@ -579,7 +579,7 @@ func (f valueFloat) ToInteger() int64 {
 	return floatToIntClip(float64(f))
 }
 
-func (f valueFloat) toString() valueString {
+func (f valueFloat) toString() String {
 	return asciiString(f.String())
 }
 
@@ -643,7 +643,7 @@ func (f valueFloat) Equals(other Value) bool {
 		return f == o
 	case valueInt:
 		return float64(f) == float64(o)
-	case valueString, valueBool:
+	case String, valueBool:
 		return float64(f) == o.ToFloat()
 	case *Object:
 		return f.Equals(o.toPrimitive())
@@ -686,7 +686,7 @@ func (o *Object) ToInteger() int64 {
 	return o.toPrimitiveNumber().ToNumber().ToInteger()
 }
 
-func (o *Object) toString() valueString {
+func (o *Object) toString() String {
 	return o.toPrimitiveString().toString()
 }
 
@@ -728,7 +728,7 @@ func (o *Object) Equals(other Value) bool {
 	}
 
 	switch o1 := other.(type) {
-	case valueInt, valueFloat, valueString, *Symbol:
+	case valueInt, valueFloat, String, *Symbol:
 		return o.toPrimitive().Equals(other)
 	case valueBool:
 		return o.Equals(o1.ToNumber())
@@ -952,7 +952,7 @@ func (o valueUnresolved) ToInteger() int64 {
 	return 0
 }
 
-func (o valueUnresolved) toString() valueString {
+func (o valueUnresolved) toString() String {
 	o.throw()
 	return nil
 }
@@ -1031,7 +1031,7 @@ func (s *Symbol) ToInteger() int64 {
 	panic(typeError("Cannot convert a Symbol value to a number"))
 }
 
-func (s *Symbol) toString() valueString {
+func (s *Symbol) toString() String {
 	panic(typeError("Cannot convert a Symbol value to a string"))
 }
 
@@ -1111,7 +1111,7 @@ func exportValue(v Value, ctx *objectExportCtx) interface{} {
 	return v.Export()
 }
 
-func newSymbol(s valueString) *Symbol {
+func newSymbol(s String) *Symbol {
 	r := &Symbol{
 		desc: s,
 	}
@@ -1127,16 +1127,16 @@ func NewSymbol(s string) *Symbol {
 	return newSymbol(newStringValue(s))
 }
 
-func (s *Symbol) descriptiveString() valueString {
+func (s *Symbol) descriptiveString() String {
 	desc := s.desc
 	if desc == nil {
 		desc = stringEmpty
 	}
-	return asciiString("Symbol(").concat(desc).concat(asciiString(")"))
+	return asciiString("Symbol(").Concat(desc).Concat(asciiString(")"))
 }
 
-func funcName(prefix string, n Value) valueString {
-	var b valueStringBuilder
+func funcName(prefix string, n Value) String {
+	var b StringBuilder
 	b.WriteString(asciiString(prefix))
 	if sym, ok := n.(*Symbol); ok {
 		if sym.desc != nil {

+ 16 - 16
vm.go

@@ -1205,8 +1205,8 @@ func (_add) exec(vm *vm) {
 
 	var ret Value
 
-	leftString, isLeftString := left.(valueString)
-	rightString, isRightString := right.(valueString)
+	leftString, isLeftString := left.(String)
+	rightString, isRightString := right.(String)
 
 	if isLeftString || isRightString {
 		if !isLeftString {
@@ -1215,7 +1215,7 @@ func (_add) exec(vm *vm) {
 		if !isRightString {
 			rightString = right.toString()
 		}
-		ret = leftString.concat(rightString)
+		ret = leftString.Concat(rightString)
 	} else {
 		if leftInt, ok := left.(valueInt); ok {
 			if rightInt, ok := right.(valueInt); ok {
@@ -2149,7 +2149,7 @@ func (s *defineGetterKeyed) exec(vm *vm) {
 	val := vm.stack[vm.sp-1]
 	method := vm.r.toObject(val)
 	method.self.defineOwnPropertyStr("name", PropertyDescriptor{
-		Value:        asciiString("get ").concat(stringValueFromRaw(s.key)),
+		Value:        asciiString("get ").Concat(stringValueFromRaw(s.key)),
 		Configurable: FLAG_TRUE,
 	}, true)
 	descr := PropertyDescriptor{
@@ -2174,7 +2174,7 @@ func (s *defineSetterKeyed) exec(vm *vm) {
 	val := vm.stack[vm.sp-1]
 	method := vm.r.toObject(val)
 	method.self.defineOwnPropertyStr("name", PropertyDescriptor{
-		Value:        asciiString("set ").concat(stringValueFromRaw(s.key)),
+		Value:        asciiString("set ").Concat(stringValueFromRaw(s.key)),
 		Configurable: FLAG_TRUE,
 	}, true)
 
@@ -2541,7 +2541,7 @@ func (_newArrayFromIter) exec(vm *vm) {
 
 type newRegexp struct {
 	pattern *regexpPattern
-	src     valueString
+	src     String
 }
 
 func (n *newRegexp) exec(vm *vm) {
@@ -3253,7 +3253,7 @@ func (vm *vm) callEval(n int, strict bool) {
 	if vm.r.toObject(vm.stack[vm.sp-n-1]) == vm.r.global.Eval {
 		if n > 0 {
 			srcVal := vm.stack[vm.sp-n]
-			if src, ok := srcVal.(valueString); ok {
+			if src, ok := srcVal.(String); ok {
 				ret := vm.r.eval(src, true, strict)
 				vm.stack[vm.sp-n-2] = ret
 			} else {
@@ -4171,9 +4171,9 @@ func cmp(px, py Value) Value {
 	var ret bool
 	var nx, ny float64
 
-	if xs, ok := px.(valueString); ok {
-		if ys, ok := py.(valueString); ok {
-			ret = xs.compareTo(ys) < 0
+	if xs, ok := px.(String); ok {
+		if ys, ok := py.(String); ok {
+			ret = xs.CompareTo(ys) < 0
 			goto end
 		}
 	}
@@ -4551,7 +4551,7 @@ func (_typeof) exec(vm *vm) {
 		r = v.self.typeOf()
 	case valueBool:
 		r = stringBoolean
-	case valueString:
+	case String:
 		r = stringString
 	case valueInt, valueFloat:
 		r = stringNumber
@@ -4887,15 +4887,15 @@ func (n concatStrings) exec(vm *vm) {
 	for i, s := range strs {
 		switch s := s.(type) {
 		case asciiString:
-			length += s.length()
+			length += s.Length()
 		case unicodeString:
-			length += s.length()
+			length += s.Length()
 			allAscii = false
 		case *importedString:
 			s.ensureScanned()
 			if s.u != nil {
 				strs[i] = s.u
-				length += s.u.length()
+				length += s.u.Length()
 				allAscii = false
 			} else {
 				strs[i] = asciiString(s.s)
@@ -4916,9 +4916,9 @@ func (n concatStrings) exec(vm *vm) {
 		vm.stack[vm.sp-1] = asciiString(buf.String())
 	} else {
 		var buf unicodeStringBuilder
-		buf.Grow(length)
+		buf.ensureStarted(length)
 		for _, s := range strs {
-			buf.WriteString(s.(valueString))
+			buf.writeString(s.(String))
 		}
 		vm.stack[vm.sp-1] = buf.String()
 	}