308 lines
5.9 KiB
Go
308 lines
5.9 KiB
Go
package goja
|
|
|
|
import (
|
|
"hash/maphash"
|
|
"io"
|
|
"math"
|
|
"reflect"
|
|
"strings"
|
|
"unicode/utf16"
|
|
"unicode/utf8"
|
|
|
|
"github.com/dop251/goja/parser"
|
|
"github.com/dop251/goja/unistring"
|
|
|
|
"golang.org/x/text/cases"
|
|
"golang.org/x/text/language"
|
|
)
|
|
|
|
// Represents a string imported from Go. The idea is to delay the scanning for unicode characters and converting
|
|
// to unicodeString until necessary. This way strings that are merely passed through never get scanned which
|
|
// saves CPU and memory.
|
|
// Currently, importedString is created in 2 cases: Runtime.ToValue() for strings longer than 16 bytes and as a result
|
|
// of JSON.stringify() if it may contain unicode characters. More cases could be added in the future.
|
|
type importedString struct {
|
|
s string
|
|
u unicodeString
|
|
|
|
scanned bool
|
|
}
|
|
|
|
func (i *importedString) scan() {
|
|
i.u = unistring.Scan(i.s)
|
|
i.scanned = true
|
|
}
|
|
|
|
func (i *importedString) ensureScanned() {
|
|
if !i.scanned {
|
|
i.scan()
|
|
}
|
|
}
|
|
|
|
func (i *importedString) ToInteger() int64 {
|
|
i.ensureScanned()
|
|
if i.u != nil {
|
|
return 0
|
|
}
|
|
return asciiString(i.s).ToInteger()
|
|
}
|
|
|
|
func (i *importedString) toString() String {
|
|
return i
|
|
}
|
|
|
|
func (i *importedString) string() unistring.String {
|
|
i.ensureScanned()
|
|
if i.u != nil {
|
|
return unistring.FromUtf16(i.u)
|
|
}
|
|
return unistring.String(i.s)
|
|
}
|
|
|
|
func (i *importedString) ToString() Value {
|
|
return i
|
|
}
|
|
|
|
func (i *importedString) String() string {
|
|
return i.s
|
|
}
|
|
|
|
func (i *importedString) ToFloat() float64 {
|
|
i.ensureScanned()
|
|
if i.u != nil {
|
|
return math.NaN()
|
|
}
|
|
return asciiString(i.s).ToFloat()
|
|
}
|
|
|
|
func (i *importedString) ToNumber() Value {
|
|
i.ensureScanned()
|
|
if i.u != nil {
|
|
return i.u.ToNumber()
|
|
}
|
|
return asciiString(i.s).ToNumber()
|
|
}
|
|
|
|
func (i *importedString) ToBoolean() bool {
|
|
return len(i.s) != 0
|
|
}
|
|
|
|
func (i *importedString) ToObject(r *Runtime) *Object {
|
|
return r._newString(i, r.getStringPrototype())
|
|
}
|
|
|
|
func (i *importedString) SameAs(other Value) bool {
|
|
return i.StrictEquals(other)
|
|
}
|
|
|
|
func (i *importedString) Equals(other Value) bool {
|
|
if i.StrictEquals(other) {
|
|
return true
|
|
}
|
|
i.ensureScanned()
|
|
if i.u != nil {
|
|
return i.u.Equals(other)
|
|
}
|
|
return asciiString(i.s).Equals(other)
|
|
}
|
|
|
|
func (i *importedString) StrictEquals(other Value) bool {
|
|
switch otherStr := other.(type) {
|
|
case asciiString:
|
|
if i.u != nil {
|
|
return false
|
|
}
|
|
return i.s == string(otherStr)
|
|
case unicodeString:
|
|
i.ensureScanned()
|
|
if i.u != nil && i.u.equals(otherStr) {
|
|
return true
|
|
}
|
|
case *importedString:
|
|
return i.s == otherStr.s
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (i *importedString) Export() interface{} {
|
|
return i.s
|
|
}
|
|
|
|
func (i *importedString) ExportType() reflect.Type {
|
|
return reflectTypeString
|
|
}
|
|
|
|
func (i *importedString) baseObject(r *Runtime) *Object {
|
|
i.ensureScanned()
|
|
if i.u != nil {
|
|
return i.u.baseObject(r)
|
|
}
|
|
return asciiString(i.s).baseObject(r)
|
|
}
|
|
|
|
func (i *importedString) hash(hasher *maphash.Hash) uint64 {
|
|
i.ensureScanned()
|
|
if i.u != nil {
|
|
return i.u.hash(hasher)
|
|
}
|
|
return asciiString(i.s).hash(hasher)
|
|
}
|
|
|
|
func (i *importedString) CharAt(idx int) uint16 {
|
|
i.ensureScanned()
|
|
if i.u != nil {
|
|
return i.u.CharAt(idx)
|
|
}
|
|
return asciiString(i.s).CharAt(idx)
|
|
}
|
|
|
|
func (i *importedString) Length() int {
|
|
i.ensureScanned()
|
|
if i.u != nil {
|
|
return i.u.Length()
|
|
}
|
|
return asciiString(i.s).Length()
|
|
}
|
|
|
|
func (i *importedString) Concat(v String) String {
|
|
if !i.scanned {
|
|
if v, ok := v.(*importedString); ok {
|
|
if !v.scanned {
|
|
return &importedString{s: i.s + v.s}
|
|
}
|
|
}
|
|
i.ensureScanned()
|
|
}
|
|
if i.u != nil {
|
|
return i.u.Concat(v)
|
|
}
|
|
return asciiString(i.s).Concat(v)
|
|
}
|
|
|
|
func (i *importedString) Substring(start, end int) String {
|
|
i.ensureScanned()
|
|
if i.u != nil {
|
|
return i.u.Substring(start, end)
|
|
}
|
|
return asciiString(i.s).Substring(start, end)
|
|
}
|
|
|
|
func (i *importedString) CompareTo(v String) int {
|
|
return strings.Compare(i.s, v.String())
|
|
}
|
|
|
|
func (i *importedString) Reader() io.RuneReader {
|
|
if i.scanned {
|
|
if i.u != nil {
|
|
return i.u.Reader()
|
|
}
|
|
return asciiString(i.s).Reader()
|
|
}
|
|
return strings.NewReader(i.s)
|
|
}
|
|
|
|
type stringUtf16Reader struct {
|
|
s string
|
|
pos int
|
|
second uint16
|
|
}
|
|
|
|
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
|
|
if r1 <= 0xFFFF {
|
|
c = uint16(r1)
|
|
} else {
|
|
first, second := utf16.EncodeRune(r1)
|
|
c, s.second = uint16(first), uint16(second)
|
|
}
|
|
} else {
|
|
err = io.EOF
|
|
}
|
|
return
|
|
}
|
|
|
|
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()
|
|
}
|
|
return asciiString(i.s).utf16Reader()
|
|
}
|
|
return &stringUtf16Reader{
|
|
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,
|
|
}
|
|
}
|
|
|
|
func (i *importedString) utf16Runes() []rune {
|
|
i.ensureScanned()
|
|
if i.u != nil {
|
|
return i.u.utf16Runes()
|
|
}
|
|
return asciiString(i.s).utf16Runes()
|
|
}
|
|
|
|
func (i *importedString) index(v String, start int) int {
|
|
i.ensureScanned()
|
|
if i.u != nil {
|
|
return i.u.index(v, start)
|
|
}
|
|
return asciiString(i.s).index(v, start)
|
|
}
|
|
|
|
func (i *importedString) lastIndex(v String, pos int) int {
|
|
i.ensureScanned()
|
|
if i.u != nil {
|
|
return i.u.lastIndex(v, pos)
|
|
}
|
|
return asciiString(i.s).lastIndex(v, pos)
|
|
}
|
|
|
|
func (i *importedString) toLower() String {
|
|
i.ensureScanned()
|
|
if i.u != nil {
|
|
return toLower(i.s)
|
|
}
|
|
return asciiString(i.s).toLower()
|
|
}
|
|
|
|
func (i *importedString) toUpper() String {
|
|
i.ensureScanned()
|
|
if i.u != nil {
|
|
caser := cases.Upper(language.Und)
|
|
return newStringValue(caser.String(i.s))
|
|
}
|
|
return asciiString(i.s).toUpper()
|
|
}
|
|
|
|
func (i *importedString) toTrimmedUTF8() string {
|
|
return strings.Trim(i.s, parser.WhitespaceChars)
|
|
}
|