365 lines
9.5 KiB
Go
365 lines
9.5 KiB
Go
|
package goja
|
||
|
|
||
|
import (
|
||
|
"io"
|
||
|
"strconv"
|
||
|
"strings"
|
||
|
"unicode/utf8"
|
||
|
|
||
|
"github.com/dop251/goja/unistring"
|
||
|
)
|
||
|
|
||
|
const (
|
||
|
__proto__ = "__proto__"
|
||
|
)
|
||
|
|
||
|
var (
|
||
|
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")
|
||
|
stringBigInt String = asciiString("bigint")
|
||
|
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 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) 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(String, int) int
|
||
|
lastIndex(String, int) int
|
||
|
toLower() String
|
||
|
toUpper() String
|
||
|
toTrimmedUTF8() string
|
||
|
}
|
||
|
|
||
|
type stringIterObject struct {
|
||
|
baseObject
|
||
|
reader io.RuneReader
|
||
|
}
|
||
|
|
||
|
func isUTF16FirstSurrogate(c uint16) bool {
|
||
|
return c >= 0xD800 && c <= 0xDBFF
|
||
|
}
|
||
|
|
||
|
func isUTF16SecondSurrogate(c uint16) bool {
|
||
|
return c >= 0xDC00 && c <= 0xDFFF
|
||
|
}
|
||
|
|
||
|
func (si *stringIterObject) next() Value {
|
||
|
if si.reader == nil {
|
||
|
return si.val.runtime.createIterResultObject(_undefined, true)
|
||
|
}
|
||
|
r, _, err := si.reader.ReadRune()
|
||
|
if err == io.EOF {
|
||
|
si.reader = nil
|
||
|
return si.val.runtime.createIterResultObject(_undefined, true)
|
||
|
}
|
||
|
return si.val.runtime.createIterResultObject(stringFromRune(r), false)
|
||
|
}
|
||
|
|
||
|
func stringFromRune(r rune) String {
|
||
|
if r < utf8.RuneSelf {
|
||
|
var sb strings.Builder
|
||
|
sb.WriteByte(byte(r))
|
||
|
return asciiString(sb.String())
|
||
|
}
|
||
|
var sb unicodeStringBuilder
|
||
|
sb.WriteRune(r)
|
||
|
return sb.String()
|
||
|
}
|
||
|
|
||
|
func (r *Runtime) createStringIterator(s String) Value {
|
||
|
o := &Object{runtime: r}
|
||
|
|
||
|
si := &stringIterObject{
|
||
|
reader: &lenientUtf16Decoder{utf16Reader: s.utf16Reader()},
|
||
|
}
|
||
|
si.class = classObject
|
||
|
si.val = o
|
||
|
si.extensible = true
|
||
|
o.self = si
|
||
|
si.prototype = r.getStringIteratorPrototype()
|
||
|
si.init()
|
||
|
|
||
|
return o
|
||
|
}
|
||
|
|
||
|
type stringObject struct {
|
||
|
baseObject
|
||
|
value String
|
||
|
length int
|
||
|
lengthProp valueProperty
|
||
|
}
|
||
|
|
||
|
func newStringValue(s string) String {
|
||
|
if u := unistring.Scan(s); u != nil {
|
||
|
return unicodeString(u)
|
||
|
}
|
||
|
return asciiString(s)
|
||
|
}
|
||
|
|
||
|
func stringValueFromRaw(raw unistring.String) String {
|
||
|
if b := raw.AsUtf16(); b != nil {
|
||
|
return unicodeString(b)
|
||
|
}
|
||
|
return asciiString(raw)
|
||
|
}
|
||
|
|
||
|
func (s *stringObject) init() {
|
||
|
s.baseObject.init()
|
||
|
s.setLength()
|
||
|
}
|
||
|
|
||
|
func (s *stringObject) setLength() {
|
||
|
if s.value != nil {
|
||
|
s.length = s.value.Length()
|
||
|
}
|
||
|
s.lengthProp.value = intToValue(int64(s.length))
|
||
|
s._put("length", &s.lengthProp)
|
||
|
}
|
||
|
|
||
|
func (s *stringObject) getStr(name unistring.String, receiver Value) Value {
|
||
|
if i := strToGoIdx(name); i >= 0 && i < s.length {
|
||
|
return s._getIdx(i)
|
||
|
}
|
||
|
return s.baseObject.getStr(name, receiver)
|
||
|
}
|
||
|
|
||
|
func (s *stringObject) getIdx(idx valueInt, receiver Value) Value {
|
||
|
i := int(idx)
|
||
|
if i >= 0 && i < s.length {
|
||
|
return s._getIdx(i)
|
||
|
}
|
||
|
return s.baseObject.getStr(idx.string(), receiver)
|
||
|
}
|
||
|
|
||
|
func (s *stringObject) getOwnPropStr(name unistring.String) Value {
|
||
|
if i := strToGoIdx(name); i >= 0 && i < s.length {
|
||
|
val := s._getIdx(i)
|
||
|
return &valueProperty{
|
||
|
value: val,
|
||
|
enumerable: true,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return s.baseObject.getOwnPropStr(name)
|
||
|
}
|
||
|
|
||
|
func (s *stringObject) getOwnPropIdx(idx valueInt) Value {
|
||
|
i := int64(idx)
|
||
|
if i >= 0 {
|
||
|
if i < int64(s.length) {
|
||
|
val := s._getIdx(int(i))
|
||
|
return &valueProperty{
|
||
|
value: val,
|
||
|
enumerable: true,
|
||
|
}
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
return s.baseObject.getOwnPropStr(idx.string())
|
||
|
}
|
||
|
|
||
|
func (s *stringObject) _getIdx(idx int) Value {
|
||
|
return s.value.Substring(idx, idx+1)
|
||
|
}
|
||
|
|
||
|
func (s *stringObject) setOwnStr(name unistring.String, val Value, throw bool) bool {
|
||
|
if i := strToGoIdx(name); i >= 0 && i < s.length {
|
||
|
s.val.runtime.typeErrorResult(throw, "Cannot assign to read only property '%d' of a String", i)
|
||
|
return false
|
||
|
}
|
||
|
|
||
|
return s.baseObject.setOwnStr(name, val, throw)
|
||
|
}
|
||
|
|
||
|
func (s *stringObject) setOwnIdx(idx valueInt, val Value, throw bool) bool {
|
||
|
i := int64(idx)
|
||
|
if i >= 0 && i < int64(s.length) {
|
||
|
s.val.runtime.typeErrorResult(throw, "Cannot assign to read only property '%d' of a String", i)
|
||
|
return false
|
||
|
}
|
||
|
|
||
|
return s.baseObject.setOwnStr(idx.string(), val, throw)
|
||
|
}
|
||
|
|
||
|
func (s *stringObject) setForeignStr(name unistring.String, val, receiver Value, throw bool) (bool, bool) {
|
||
|
return s._setForeignStr(name, s.getOwnPropStr(name), val, receiver, throw)
|
||
|
}
|
||
|
|
||
|
func (s *stringObject) setForeignIdx(idx valueInt, val, receiver Value, throw bool) (bool, bool) {
|
||
|
return s._setForeignIdx(idx, s.getOwnPropIdx(idx), val, receiver, throw)
|
||
|
}
|
||
|
|
||
|
func (s *stringObject) defineOwnPropertyStr(name unistring.String, descr PropertyDescriptor, throw bool) bool {
|
||
|
if i := strToGoIdx(name); i >= 0 && i < s.length {
|
||
|
_, ok := s._defineOwnProperty(name, &valueProperty{enumerable: true}, descr, throw)
|
||
|
return ok
|
||
|
}
|
||
|
|
||
|
return s.baseObject.defineOwnPropertyStr(name, descr, throw)
|
||
|
}
|
||
|
|
||
|
func (s *stringObject) defineOwnPropertyIdx(idx valueInt, descr PropertyDescriptor, throw bool) bool {
|
||
|
i := int64(idx)
|
||
|
if i >= 0 && i < int64(s.length) {
|
||
|
s.val.runtime.typeErrorResult(throw, "Cannot redefine property: %d", i)
|
||
|
return false
|
||
|
}
|
||
|
|
||
|
return s.baseObject.defineOwnPropertyStr(idx.string(), descr, throw)
|
||
|
}
|
||
|
|
||
|
type stringPropIter struct {
|
||
|
str String // separate, because obj can be the singleton
|
||
|
obj *stringObject
|
||
|
idx, length int
|
||
|
}
|
||
|
|
||
|
func (i *stringPropIter) next() (propIterItem, iterNextFunc) {
|
||
|
if i.idx < i.length {
|
||
|
name := strconv.Itoa(i.idx)
|
||
|
i.idx++
|
||
|
return propIterItem{name: asciiString(name), enumerable: _ENUM_TRUE}, i.next
|
||
|
}
|
||
|
|
||
|
return i.obj.baseObject.iterateStringKeys()()
|
||
|
}
|
||
|
|
||
|
func (s *stringObject) iterateStringKeys() iterNextFunc {
|
||
|
return (&stringPropIter{
|
||
|
str: s.value,
|
||
|
obj: s,
|
||
|
length: s.length,
|
||
|
}).next
|
||
|
}
|
||
|
|
||
|
func (s *stringObject) stringKeys(all bool, accum []Value) []Value {
|
||
|
for i := 0; i < s.length; i++ {
|
||
|
accum = append(accum, asciiString(strconv.Itoa(i)))
|
||
|
}
|
||
|
|
||
|
return s.baseObject.stringKeys(all, accum)
|
||
|
}
|
||
|
|
||
|
func (s *stringObject) deleteStr(name unistring.String, throw bool) bool {
|
||
|
if i := strToGoIdx(name); i >= 0 && i < s.length {
|
||
|
s.val.runtime.typeErrorResult(throw, "Cannot delete property '%d' of a String", i)
|
||
|
return false
|
||
|
}
|
||
|
|
||
|
return s.baseObject.deleteStr(name, throw)
|
||
|
}
|
||
|
|
||
|
func (s *stringObject) deleteIdx(idx valueInt, throw bool) bool {
|
||
|
i := int64(idx)
|
||
|
if i >= 0 && i < int64(s.length) {
|
||
|
s.val.runtime.typeErrorResult(throw, "Cannot delete property '%d' of a String", i)
|
||
|
return false
|
||
|
}
|
||
|
|
||
|
return s.baseObject.deleteStr(idx.string(), throw)
|
||
|
}
|
||
|
|
||
|
func (s *stringObject) hasOwnPropertyStr(name unistring.String) bool {
|
||
|
if i := strToGoIdx(name); i >= 0 && i < s.length {
|
||
|
return true
|
||
|
}
|
||
|
return s.baseObject.hasOwnPropertyStr(name)
|
||
|
}
|
||
|
|
||
|
func (s *stringObject) hasOwnPropertyIdx(idx valueInt) bool {
|
||
|
i := int64(idx)
|
||
|
if i >= 0 && i < int64(s.length) {
|
||
|
return true
|
||
|
}
|
||
|
return s.baseObject.hasOwnPropertyStr(idx.string())
|
||
|
}
|
||
|
|
||
|
func devirtualizeString(s String) (asciiString, unicodeString) {
|
||
|
switch s := s.(type) {
|
||
|
case asciiString:
|
||
|
return s, nil
|
||
|
case unicodeString:
|
||
|
return "", s
|
||
|
case *importedString:
|
||
|
s.ensureScanned()
|
||
|
if s.u != nil {
|
||
|
return "", s.u
|
||
|
}
|
||
|
return asciiString(s.s), nil
|
||
|
default:
|
||
|
panic(unknownStringTypeErr(s))
|
||
|
}
|
||
|
}
|
||
|
|
||
|
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)
|
||
|
}
|