123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651 |
- package goja
- import (
- "bytes"
- "github.com/dop251/goja/parser"
- "golang.org/x/text/collate"
- "golang.org/x/text/language"
- "golang.org/x/text/unicode/norm"
- "math"
- "strings"
- "unicode/utf8"
- )
- func (r *Runtime) collator() *collate.Collator {
- collator := r._collator
- if collator == nil {
- collator = collate.New(language.Und)
- r._collator = collator
- }
- return collator
- }
- func toString(arg Value) valueString {
- if s, ok := arg.(valueString); ok {
- return s
- }
- if s, ok := arg.(*valueSymbol); ok {
- return newStringValue(s.descString())
- }
- return arg.toString()
- }
- func (r *Runtime) builtin_String(call FunctionCall) Value {
- if len(call.Arguments) > 0 {
- return toString(call.Arguments[0])
- } else {
- return stringEmpty
- }
- }
- func (r *Runtime) _newString(s valueString, proto *Object) *Object {
- v := &Object{runtime: r}
- o := &stringObject{}
- o.class = classString
- o.val = v
- o.extensible = true
- v.self = o
- o.prototype = proto
- if s != nil {
- o.value = s
- }
- o.init()
- return v
- }
- func (r *Runtime) builtin_newString(args []Value, proto *Object) *Object {
- var s valueString
- if len(args) > 0 {
- s = toString(args[0])
- } else {
- s = stringEmpty
- }
- return r._newString(s, proto)
- }
- func searchSubstringUTF8(str, search string) (ret [][]int) {
- searchPos := 0
- l := len(str)
- if searchPos < l {
- p := strings.Index(str[searchPos:], search)
- if p != -1 {
- p += searchPos
- searchPos = p + len(search)
- ret = append(ret, []int{p, searchPos})
- }
- }
- return
- }
- func (r *Runtime) stringproto_toStringValueOf(this Value, funcName string) Value {
- if str, ok := this.(valueString); ok {
- return str
- }
- if obj, ok := this.(*Object); ok {
- if strObj, ok := obj.self.(*stringObject); ok {
- return strObj.value
- }
- }
- r.typeErrorResult(true, "String.prototype.%s is called on incompatible receiver", funcName)
- return nil
- }
- func (r *Runtime) stringproto_toString(call FunctionCall) Value {
- return r.stringproto_toStringValueOf(call.This, "toString")
- }
- func (r *Runtime) stringproto_valueOf(call FunctionCall) Value {
- return r.stringproto_toStringValueOf(call.This, "valueOf")
- }
- func (r *Runtime) string_fromcharcode(call FunctionCall) Value {
- b := make([]byte, len(call.Arguments))
- for i, arg := range call.Arguments {
- chr := toUInt16(arg)
- if chr >= utf8.RuneSelf {
- bb := make([]uint16, len(call.Arguments))
- for j := 0; j < i; j++ {
- bb[j] = uint16(b[j])
- }
- bb[i] = chr
- i++
- for j, arg := range call.Arguments[i:] {
- bb[i+j] = toUInt16(arg)
- }
- return unicodeString(bb)
- }
- b[i] = byte(chr)
- }
- return asciiString(b)
- }
- 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 >= s.length() {
- return stringEmpty
- }
- return newStringValue(string(s.charAt(pos)))
- }
- 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 >= s.length() {
- return _NaN
- }
- return intToValue(int64(s.charAt(pos) & 0xFFFF))
- }
- func (r *Runtime) stringproto_concat(call FunctionCall) Value {
- r.checkObjectCoercible(call.This)
- strs := make([]valueString, len(call.Arguments)+1)
- strs[0] = call.This.toString()
- _, allAscii := strs[0].(asciiString)
- totalLen := strs[0].length()
- for i, arg := range call.Arguments {
- s := arg.toString()
- if allAscii {
- _, allAscii = s.(asciiString)
- }
- strs[i+1] = s
- totalLen += s.length()
- }
- if allAscii {
- buf := bytes.NewBuffer(make([]byte, 0, totalLen))
- for _, s := range strs {
- buf.WriteString(s.String())
- }
- return asciiString(buf.String())
- } else {
- buf := make([]uint16, totalLen)
- pos := int64(0)
- for _, s := range strs {
- switch s := s.(type) {
- case asciiString:
- for i := 0; i < len(s); i++ {
- buf[pos] = uint16(s[i])
- pos++
- }
- case unicodeString:
- copy(buf[pos:], s)
- pos += s.length()
- }
- }
- return unicodeString(buf)
- }
- }
- func (r *Runtime) stringproto_indexOf(call FunctionCall) Value {
- r.checkObjectCoercible(call.This)
- value := call.This.toString()
- target := call.Argument(0).toString()
- pos := call.Argument(1).ToInteger()
- if pos < 0 {
- pos = 0
- } else {
- l := value.length()
- if pos > l {
- pos = l
- }
- }
- return intToValue(value.index(target, pos))
- }
- func (r *Runtime) stringproto_lastIndexOf(call FunctionCall) Value {
- r.checkObjectCoercible(call.This)
- value := call.This.toString()
- target := call.Argument(0).toString()
- numPos := call.Argument(1).ToNumber()
- var pos int64
- if f, ok := numPos.(valueFloat); ok && math.IsNaN(float64(f)) {
- pos = value.length()
- } else {
- pos = numPos.ToInteger()
- if pos < 0 {
- pos = 0
- } else {
- l := value.length()
- if pos > l {
- pos = l
- }
- }
- }
- return intToValue(value.lastIndex(target, pos))
- }
- func (r *Runtime) stringproto_localeCompare(call FunctionCall) Value {
- r.checkObjectCoercible(call.This)
- this := norm.NFD.String(call.This.String())
- that := norm.NFD.String(call.Argument(0).String())
- return intToValue(int64(r.collator().CompareString(this, that)))
- }
- func (r *Runtime) stringproto_match(call FunctionCall) Value {
- r.checkObjectCoercible(call.This)
- regexp := call.Argument(0)
- if regexp != _undefined && regexp != _null {
- if matcher := toMethod(r.getV(regexp, symMatch)); matcher != nil {
- return matcher(FunctionCall{
- This: regexp,
- Arguments: []Value{call.This},
- })
- }
- }
- var rx *regexpObject
- if regexp, ok := regexp.(*Object); ok {
- rx, _ = regexp.self.(*regexpObject)
- }
- if rx == nil {
- rx = r.builtin_newRegExp([]Value{regexp}, r.global.RegExpPrototype).self.(*regexpObject)
- }
- if matcher, ok := r.toObject(rx.getSym(symMatch, nil)).self.assertCallable(); ok {
- return matcher(FunctionCall{
- This: rx.val,
- Arguments: []Value{call.This.toString()},
- })
- }
- panic(r.NewTypeError("RegExp matcher is not a function"))
- }
- func (r *Runtime) stringproto_replace(call FunctionCall) Value {
- r.checkObjectCoercible(call.This)
- searchValue := call.Argument(0)
- replaceValue := call.Argument(1)
- if searchValue != _undefined && searchValue != _null {
- if replacer := toMethod(r.getV(searchValue, symReplace)); replacer != nil {
- return replacer(FunctionCall{
- This: searchValue,
- Arguments: []Value{call.This, replaceValue},
- })
- }
- }
- s := call.This.toString()
- var str string
- var isASCII bool
- if astr, ok := s.(asciiString); ok {
- str = string(astr)
- isASCII = true
- } else {
- str = s.String()
- }
- var found [][]int
- if searchValue, ok := searchValue.(*Object); ok {
- if regexp, ok := searchValue.self.(*regexpObject); ok {
- find := 1
- if regexp.global {
- find = -1
- }
- if isASCII {
- found = regexp.pattern.FindAllSubmatchIndexASCII(str, find)
- } else {
- found = regexp.pattern.FindAllSubmatchIndexUTF8(str, find)
- }
- if found == nil {
- return s
- }
- }
- }
- if found == nil {
- found = searchSubstringUTF8(str, searchValue.String())
- }
- if len(found) == 0 {
- return s
- }
- var buf bytes.Buffer
- lastIndex := 0
- var rcall func(FunctionCall) Value
- if replaceValue, ok := replaceValue.(*Object); ok {
- if c, ok := replaceValue.self.assertCallable(); ok {
- rcall = c
- }
- }
- if rcall != nil {
- for _, item := range found {
- if item[0] != lastIndex {
- buf.WriteString(str[lastIndex:item[0]])
- }
- matchCount := len(item) / 2
- argumentList := make([]Value, matchCount+2)
- for index := 0; index < matchCount; index++ {
- offset := 2 * index
- if item[offset] != -1 {
- if isASCII {
- argumentList[index] = asciiString(str[item[offset]:item[offset+1]])
- } else {
- argumentList[index] = newStringValue(str[item[offset]:item[offset+1]])
- }
- } else {
- argumentList[index] = _undefined
- }
- }
- argumentList[matchCount] = valueInt(item[0])
- argumentList[matchCount+1] = s
- replacement := rcall(FunctionCall{
- This: _undefined,
- Arguments: argumentList,
- }).String()
- buf.WriteString(replacement)
- lastIndex = item[1]
- }
- } else {
- newstring := replaceValue.String()
- for _, item := range found {
- if item[0] != lastIndex {
- buf.WriteString(str[lastIndex:item[0]])
- }
- matches := len(item) / 2
- for i := 0; i < len(newstring); i++ {
- if newstring[i] == '$' && i < len(newstring)-1 {
- ch := newstring[i+1]
- switch ch {
- case '$':
- buf.WriteByte('$')
- case '`':
- buf.WriteString(str[0:item[0]])
- case '\'':
- buf.WriteString(str[item[1]:])
- case '&':
- buf.WriteString(str[item[0]:item[1]])
- default:
- matchNumber := 0
- l := 0
- for _, ch := range newstring[i+1:] {
- if ch >= '0' && ch <= '9' {
- m := matchNumber*10 + int(ch-'0')
- if m >= matches {
- break
- }
- matchNumber = m
- l++
- } else {
- break
- }
- }
- if l > 0 {
- offset := 2 * matchNumber
- if offset < len(item) && item[offset] != -1 {
- buf.WriteString(str[item[offset]:item[offset+1]])
- }
- i += l - 1
- } else {
- buf.WriteByte('$')
- buf.WriteByte(ch)
- }
- }
- i++
- } else {
- buf.WriteByte(newstring[i])
- }
- }
- lastIndex = item[1]
- }
- }
- if lastIndex != len(str) {
- buf.WriteString(str[lastIndex:])
- }
- return newStringValue(buf.String())
- }
- func (r *Runtime) stringproto_search(call FunctionCall) Value {
- r.checkObjectCoercible(call.This)
- regexp := call.Argument(0)
- if regexp != _undefined && regexp != _null {
- if searcher := toMethod(r.getV(regexp, symSearch)); searcher != nil {
- return searcher(FunctionCall{
- This: regexp,
- Arguments: []Value{call.This},
- })
- }
- }
- var rx *regexpObject
- if regexp, ok := regexp.(*Object); ok {
- rx, _ = regexp.self.(*regexpObject)
- }
- if rx == nil {
- rx = r.builtin_newRegExp([]Value{regexp}, r.global.RegExpPrototype).self.(*regexpObject)
- }
- if searcher, ok := r.toObject(rx.getSym(symSearch, nil)).self.assertCallable(); ok {
- return searcher(FunctionCall{
- This: rx.val,
- Arguments: []Value{call.This.toString()},
- })
- }
- panic(r.NewTypeError("RegExp searcher is not a function"))
- }
- func (r *Runtime) stringproto_slice(call FunctionCall) Value {
- r.checkObjectCoercible(call.This)
- s := call.This.toString()
- l := s.length()
- start := call.Argument(0).ToInteger()
- var end int64
- if arg1 := call.Argument(1); arg1 != _undefined {
- end = arg1.ToInteger()
- } else {
- end = l
- }
- if start < 0 {
- start += l
- if start < 0 {
- start = 0
- }
- } else {
- if start > l {
- start = l
- }
- }
- if end < 0 {
- end += l
- if end < 0 {
- end = 0
- }
- } else {
- if end > l {
- end = l
- }
- }
- if end > start {
- return s.substring(start, end)
- }
- return stringEmpty
- }
- func (r *Runtime) stringproto_split(call FunctionCall) Value {
- r.checkObjectCoercible(call.This)
- separatorValue := call.Argument(0)
- limitValue := call.Argument(1)
- if separatorValue != _undefined && separatorValue != _null {
- if splitter := toMethod(r.getV(separatorValue, symSplit)); splitter != nil {
- return splitter(FunctionCall{
- This: separatorValue,
- Arguments: []Value{call.This, limitValue},
- })
- }
- }
- s := call.This.toString()
- limit := -1
- if limitValue != _undefined {
- limit = int(toUInt32(limitValue))
- }
- if limit == 0 {
- return r.newArrayValues(nil)
- }
- if separatorValue == _undefined {
- return r.newArrayValues([]Value{s})
- }
- separator := separatorValue.String()
- excess := false
- str := s.String()
- if limit > len(str) {
- limit = len(str)
- }
- splitLimit := limit
- if limit > 0 {
- splitLimit = limit + 1
- excess = true
- }
- split := strings.SplitN(str, separator, splitLimit)
- if excess && len(split) > limit {
- split = split[:limit]
- }
- valueArray := make([]Value, len(split))
- for index, value := range split {
- valueArray[index] = newStringValue(value)
- }
- return r.newArrayValues(valueArray)
- }
- func (r *Runtime) stringproto_substring(call FunctionCall) Value {
- r.checkObjectCoercible(call.This)
- s := call.This.toString()
- l := s.length()
- intStart := call.Argument(0).ToInteger()
- var intEnd int64
- if end := call.Argument(1); end != _undefined {
- intEnd = end.ToInteger()
- } else {
- intEnd = l
- }
- if intStart < 0 {
- intStart = 0
- } else if intStart > l {
- intStart = l
- }
- if intEnd < 0 {
- intEnd = 0
- } else if intEnd > l {
- intEnd = l
- }
- if intStart > intEnd {
- intStart, intEnd = intEnd, intStart
- }
- return s.substring(intStart, intEnd)
- }
- func (r *Runtime) stringproto_toLowerCase(call FunctionCall) Value {
- r.checkObjectCoercible(call.This)
- s := call.This.toString()
- return s.toLower()
- }
- func (r *Runtime) stringproto_toUpperCase(call FunctionCall) Value {
- r.checkObjectCoercible(call.This)
- s := call.This.toString()
- return s.toUpper()
- }
- func (r *Runtime) stringproto_trim(call FunctionCall) Value {
- r.checkObjectCoercible(call.This)
- s := call.This.toString()
- return newStringValue(strings.Trim(s.String(), parser.WhitespaceChars))
- }
- func (r *Runtime) stringproto_substr(call FunctionCall) Value {
- s := call.This.toString()
- start := call.Argument(0).ToInteger()
- var length int64
- sl := int64(s.length())
- if arg := call.Argument(1); arg != _undefined {
- length = arg.ToInteger()
- } else {
- length = sl
- }
- if start < 0 {
- start = max(sl+start, 0)
- }
- length = min(max(length, 0), sl-start)
- if length <= 0 {
- return stringEmpty
- }
- return s.substring(start, start+length)
- }
- func (r *Runtime) initString() {
- r.global.StringPrototype = r.builtin_newString([]Value{stringEmpty}, r.global.ObjectPrototype)
- o := r.global.StringPrototype.self
- o._putProp("toString", r.newNativeFunc(r.stringproto_toString, nil, "toString", nil, 0), true, false, true)
- o._putProp("valueOf", r.newNativeFunc(r.stringproto_valueOf, nil, "valueOf", nil, 0), true, false, true)
- o._putProp("charAt", r.newNativeFunc(r.stringproto_charAt, nil, "charAt", nil, 1), true, false, true)
- o._putProp("charCodeAt", r.newNativeFunc(r.stringproto_charCodeAt, nil, "charCodeAt", nil, 1), true, false, true)
- o._putProp("concat", r.newNativeFunc(r.stringproto_concat, nil, "concat", nil, 1), true, false, true)
- o._putProp("indexOf", r.newNativeFunc(r.stringproto_indexOf, nil, "indexOf", nil, 1), true, false, true)
- o._putProp("lastIndexOf", r.newNativeFunc(r.stringproto_lastIndexOf, nil, "lastIndexOf", nil, 1), true, false, true)
- o._putProp("localeCompare", r.newNativeFunc(r.stringproto_localeCompare, nil, "localeCompare", nil, 1), true, false, true)
- o._putProp("match", r.newNativeFunc(r.stringproto_match, nil, "match", nil, 1), true, false, true)
- o._putProp("replace", r.newNativeFunc(r.stringproto_replace, nil, "replace", nil, 2), true, false, true)
- o._putProp("search", r.newNativeFunc(r.stringproto_search, nil, "search", nil, 1), true, false, true)
- o._putProp("slice", r.newNativeFunc(r.stringproto_slice, nil, "slice", nil, 2), true, false, true)
- o._putProp("split", r.newNativeFunc(r.stringproto_split, nil, "split", nil, 2), true, false, true)
- o._putProp("substring", r.newNativeFunc(r.stringproto_substring, nil, "substring", nil, 2), true, false, true)
- o._putProp("toLowerCase", r.newNativeFunc(r.stringproto_toLowerCase, nil, "toLowerCase", nil, 0), true, false, true)
- o._putProp("toLocaleLowerCase", r.newNativeFunc(r.stringproto_toLowerCase, nil, "toLocaleLowerCase", nil, 0), true, false, true)
- o._putProp("toUpperCase", r.newNativeFunc(r.stringproto_toUpperCase, nil, "toUpperCase", nil, 0), true, false, true)
- o._putProp("toLocaleUpperCase", r.newNativeFunc(r.stringproto_toUpperCase, nil, "toLocaleUpperCase", nil, 0), true, false, true)
- o._putProp("trim", r.newNativeFunc(r.stringproto_trim, nil, "trim", nil, 0), true, false, true)
- // Annex B
- o._putProp("substr", r.newNativeFunc(r.stringproto_substr, nil, "substr", nil, 2), true, false, true)
- r.global.String = r.newNativeFunc(r.builtin_String, r.builtin_newString, "String", r.global.StringPrototype, 1)
- o = r.global.String.self
- o._putProp("fromCharCode", r.newNativeFunc(r.string_fromcharcode, nil, "fromCharCode", nil, 1), true, false, true)
- r.addToGlobal("String", r.global.String)
- r.stringSingleton = r.builtin_new(r.global.String, nil).self.(*stringObject)
- }
|