go.ketch.com/lib/goja@v0.0.1/string_imported.go (about)

     1  package goja
     2  
     3  import (
     4  	"hash/maphash"
     5  	"io"
     6  	"math"
     7  	"reflect"
     8  	"strings"
     9  	"unicode/utf16"
    10  	"unicode/utf8"
    11  
    12  	"go.ketch.com/lib/goja/parser"
    13  	"go.ketch.com/lib/goja/unistring"
    14  
    15  	"golang.org/x/text/cases"
    16  	"golang.org/x/text/language"
    17  )
    18  
    19  // Represents a string imported from Go. The idea is to delay the scanning for unicode characters and converting
    20  // to unicodeString until necessary. This way strings that are merely passed through never get scanned which
    21  // saves CPU and memory.
    22  // Currently, importedString is created in 2 cases: Runtime.ToValue() for strings longer than 16 bytes and as a result
    23  // of JSON.stringify() if it may contain unicode characters. More cases could be added in the future.
    24  type importedString struct {
    25  	s string
    26  	u unicodeString
    27  
    28  	scanned bool
    29  }
    30  
    31  func (i *importedString) scan() {
    32  	i.u = unistring.Scan(i.s)
    33  	i.scanned = true
    34  }
    35  
    36  func (i *importedString) ensureScanned() {
    37  	if !i.scanned {
    38  		i.scan()
    39  	}
    40  }
    41  
    42  func (i *importedString) ToInteger() int64 {
    43  	i.ensureScanned()
    44  	if i.u != nil {
    45  		return 0
    46  	}
    47  	return asciiString(i.s).ToInteger()
    48  }
    49  
    50  func (i *importedString) toString() valueString {
    51  	return i
    52  }
    53  
    54  func (i *importedString) string() unistring.String {
    55  	i.ensureScanned()
    56  	if i.u != nil {
    57  		return unistring.FromUtf16(i.u)
    58  	}
    59  	return unistring.String(i.s)
    60  }
    61  
    62  func (i *importedString) ToString() Value {
    63  	return i
    64  }
    65  
    66  func (i *importedString) String() string {
    67  	return i.s
    68  }
    69  
    70  func (i *importedString) ToFloat() float64 {
    71  	i.ensureScanned()
    72  	if i.u != nil {
    73  		return math.NaN()
    74  	}
    75  	return asciiString(i.s).ToFloat()
    76  }
    77  
    78  func (i *importedString) ToNumber() Value {
    79  	i.ensureScanned()
    80  	if i.u != nil {
    81  		return i.u.ToNumber()
    82  	}
    83  	return asciiString(i.s).ToNumber()
    84  }
    85  
    86  func (i *importedString) ToBoolean() bool {
    87  	return len(i.s) != 0
    88  }
    89  
    90  func (i *importedString) ToObject(r *Runtime) *Object {
    91  	return r._newString(i, r.global.StringPrototype)
    92  }
    93  
    94  func (i *importedString) SameAs(other Value) bool {
    95  	return i.StrictEquals(other)
    96  }
    97  
    98  func (i *importedString) Equals(other Value) bool {
    99  	if i.StrictEquals(other) {
   100  		return true
   101  	}
   102  	i.ensureScanned()
   103  	if i.u != nil {
   104  		return i.u.Equals(other)
   105  	}
   106  	return asciiString(i.s).Equals(other)
   107  }
   108  
   109  func (i *importedString) StrictEquals(other Value) bool {
   110  	switch otherStr := other.(type) {
   111  	case asciiString:
   112  		if i.u != nil {
   113  			return false
   114  		}
   115  		return i.s == string(otherStr)
   116  	case unicodeString:
   117  		i.ensureScanned()
   118  		if i.u != nil && i.u.equals(otherStr) {
   119  			return true
   120  		}
   121  	case *importedString:
   122  		return i.s == otherStr.s
   123  	}
   124  	return false
   125  }
   126  
   127  func (i *importedString) Export() interface{} {
   128  	return i.s
   129  }
   130  
   131  func (i *importedString) ExportType() reflect.Type {
   132  	return reflectTypeString
   133  }
   134  
   135  func (i *importedString) baseObject(r *Runtime) *Object {
   136  	i.ensureScanned()
   137  	if i.u != nil {
   138  		return i.u.baseObject(r)
   139  	}
   140  	return asciiString(i.s).baseObject(r)
   141  }
   142  
   143  func (i *importedString) hash(hasher *maphash.Hash) uint64 {
   144  	i.ensureScanned()
   145  	if i.u != nil {
   146  		return i.u.hash(hasher)
   147  	}
   148  	return asciiString(i.s).hash(hasher)
   149  }
   150  
   151  func (i *importedString) charAt(idx int) rune {
   152  	i.ensureScanned()
   153  	if i.u != nil {
   154  		return i.u.charAt(idx)
   155  	}
   156  	return asciiString(i.s).charAt(idx)
   157  }
   158  
   159  func (i *importedString) length() int {
   160  	i.ensureScanned()
   161  	if i.u != nil {
   162  		return i.u.length()
   163  	}
   164  	return asciiString(i.s).length()
   165  }
   166  
   167  func (i *importedString) concat(v valueString) valueString {
   168  	if !i.scanned {
   169  		if v, ok := v.(*importedString); ok {
   170  			if !v.scanned {
   171  				return &importedString{s: i.s + v.s}
   172  			}
   173  		}
   174  		i.ensureScanned()
   175  	}
   176  	if i.u != nil {
   177  		return i.u.concat(v)
   178  	}
   179  	return asciiString(i.s).concat(v)
   180  }
   181  
   182  func (i *importedString) substring(start, end int) valueString {
   183  	i.ensureScanned()
   184  	if i.u != nil {
   185  		return i.u.substring(start, end)
   186  	}
   187  	return asciiString(i.s).substring(start, end)
   188  }
   189  
   190  func (i *importedString) compareTo(v valueString) int {
   191  	return strings.Compare(i.s, v.String())
   192  }
   193  
   194  func (i *importedString) reader() io.RuneReader {
   195  	if i.scanned {
   196  		if i.u != nil {
   197  			return i.u.reader()
   198  		}
   199  		return asciiString(i.s).reader()
   200  	}
   201  	return strings.NewReader(i.s)
   202  }
   203  
   204  type stringUtf16Reader struct {
   205  	s      string
   206  	pos    int
   207  	second rune
   208  }
   209  
   210  func (s *stringUtf16Reader) ReadRune() (r rune, size int, err error) {
   211  	if s.second >= 0 {
   212  		r = s.second
   213  		s.second = -1
   214  		size = 1
   215  		return
   216  	}
   217  	if s.pos < len(s.s) {
   218  		r1, size1 := utf8.DecodeRuneInString(s.s[s.pos:])
   219  		s.pos += size1
   220  		size = 1
   221  		if r1 <= 0xFFFF {
   222  			r = r1
   223  		} else {
   224  			r, s.second = utf16.EncodeRune(r1)
   225  		}
   226  	} else {
   227  		err = io.EOF
   228  	}
   229  	return
   230  }
   231  
   232  func (i *importedString) utf16Reader() io.RuneReader {
   233  	if i.scanned {
   234  		if i.u != nil {
   235  			return i.u.utf16Reader()
   236  		}
   237  		return asciiString(i.s).utf16Reader()
   238  	}
   239  	return &stringUtf16Reader{
   240  		s:      i.s,
   241  		second: -1,
   242  	}
   243  }
   244  
   245  func (i *importedString) utf16Runes() []rune {
   246  	i.ensureScanned()
   247  	if i.u != nil {
   248  		return i.u.utf16Runes()
   249  	}
   250  	return asciiString(i.s).utf16Runes()
   251  }
   252  
   253  func (i *importedString) index(v valueString, start int) int {
   254  	i.ensureScanned()
   255  	if i.u != nil {
   256  		return i.u.index(v, start)
   257  	}
   258  	return asciiString(i.s).index(v, start)
   259  }
   260  
   261  func (i *importedString) lastIndex(v valueString, pos int) int {
   262  	i.ensureScanned()
   263  	if i.u != nil {
   264  		return i.u.lastIndex(v, pos)
   265  	}
   266  	return asciiString(i.s).lastIndex(v, pos)
   267  }
   268  
   269  func (i *importedString) toLower() valueString {
   270  	i.ensureScanned()
   271  	if i.u != nil {
   272  		return toLower(i.s)
   273  	}
   274  	return asciiString(i.s).toLower()
   275  }
   276  
   277  func (i *importedString) toUpper() valueString {
   278  	i.ensureScanned()
   279  	if i.u != nil {
   280  		caser := cases.Upper(language.Und)
   281  		return newStringValue(caser.String(i.s))
   282  	}
   283  	return asciiString(i.s).toUpper()
   284  }
   285  
   286  func (i *importedString) toTrimmedUTF8() string {
   287  	return strings.Trim(i.s, parser.WhitespaceChars)
   288  }