github.com/dop251/goja@v0.0.0-20240220182346-e401ed450204/builtin_string.go (about) 1 package goja 2 3 import ( 4 "github.com/dop251/goja/unistring" 5 "math" 6 "strings" 7 "sync" 8 "unicode/utf16" 9 "unicode/utf8" 10 11 "github.com/dop251/goja/parser" 12 "golang.org/x/text/collate" 13 "golang.org/x/text/language" 14 "golang.org/x/text/unicode/norm" 15 ) 16 17 func (r *Runtime) collator() *collate.Collator { 18 collator := r._collator 19 if collator == nil { 20 collator = collate.New(language.Und) 21 r._collator = collator 22 } 23 return collator 24 } 25 26 func toString(arg Value) String { 27 if s, ok := arg.(String); ok { 28 return s 29 } 30 if s, ok := arg.(*Symbol); ok { 31 return s.descriptiveString() 32 } 33 return arg.toString() 34 } 35 36 func (r *Runtime) builtin_String(call FunctionCall) Value { 37 if len(call.Arguments) > 0 { 38 return toString(call.Arguments[0]) 39 } else { 40 return stringEmpty 41 } 42 } 43 44 func (r *Runtime) _newString(s String, proto *Object) *Object { 45 v := &Object{runtime: r} 46 47 o := &stringObject{} 48 o.class = classString 49 o.val = v 50 o.extensible = true 51 v.self = o 52 o.prototype = proto 53 if s != nil { 54 o.value = s 55 } 56 o.init() 57 return v 58 } 59 60 func (r *Runtime) builtin_newString(args []Value, proto *Object) *Object { 61 var s String 62 if len(args) > 0 { 63 s = args[0].toString() 64 } else { 65 s = stringEmpty 66 } 67 return r._newString(s, proto) 68 } 69 70 func (r *Runtime) stringproto_toStringValueOf(this Value, funcName string) Value { 71 if str, ok := this.(String); ok { 72 return str 73 } 74 if obj, ok := this.(*Object); ok { 75 if strObj, ok := obj.self.(*stringObject); ok { 76 return strObj.value 77 } 78 if reflectObj, ok := obj.self.(*objectGoReflect); ok && reflectObj.class == classString { 79 if toString := reflectObj.toString; toString != nil { 80 return toString() 81 } 82 if valueOf := reflectObj.valueOf; valueOf != nil { 83 return valueOf() 84 } 85 } 86 if obj == r.global.StringPrototype { 87 return stringEmpty 88 } 89 } 90 r.typeErrorResult(true, "String.prototype.%s is called on incompatible receiver", funcName) 91 return nil 92 } 93 94 func (r *Runtime) stringproto_toString(call FunctionCall) Value { 95 return r.stringproto_toStringValueOf(call.This, "toString") 96 } 97 98 func (r *Runtime) stringproto_valueOf(call FunctionCall) Value { 99 return r.stringproto_toStringValueOf(call.This, "valueOf") 100 } 101 102 func (r *Runtime) stringproto_iterator(call FunctionCall) Value { 103 r.checkObjectCoercible(call.This) 104 return r.createStringIterator(call.This.toString()) 105 } 106 107 func (r *Runtime) string_fromcharcode(call FunctionCall) Value { 108 b := make([]byte, len(call.Arguments)) 109 for i, arg := range call.Arguments { 110 chr := toUint16(arg) 111 if chr >= utf8.RuneSelf { 112 bb := make([]uint16, len(call.Arguments)+1) 113 bb[0] = unistring.BOM 114 bb1 := bb[1:] 115 for j := 0; j < i; j++ { 116 bb1[j] = uint16(b[j]) 117 } 118 bb1[i] = chr 119 i++ 120 for j, arg := range call.Arguments[i:] { 121 bb1[i+j] = toUint16(arg) 122 } 123 return unicodeString(bb) 124 } 125 b[i] = byte(chr) 126 } 127 128 return asciiString(b) 129 } 130 131 func (r *Runtime) string_fromcodepoint(call FunctionCall) Value { 132 var sb StringBuilder 133 for _, arg := range call.Arguments { 134 num := arg.ToNumber() 135 var c rune 136 if numInt, ok := num.(valueInt); ok { 137 if numInt < 0 || numInt > utf8.MaxRune { 138 panic(r.newError(r.getRangeError(), "Invalid code point %d", numInt)) 139 } 140 c = rune(numInt) 141 } else { 142 panic(r.newError(r.getRangeError(), "Invalid code point %s", num)) 143 } 144 sb.WriteRune(c) 145 } 146 return sb.String() 147 } 148 149 func (r *Runtime) string_raw(call FunctionCall) Value { 150 cooked := call.Argument(0).ToObject(r) 151 raw := nilSafe(cooked.self.getStr("raw", nil)).ToObject(r) 152 literalSegments := toLength(raw.self.getStr("length", nil)) 153 if literalSegments <= 0 { 154 return stringEmpty 155 } 156 var stringElements StringBuilder 157 nextIndex := int64(0) 158 numberOfSubstitutions := int64(len(call.Arguments) - 1) 159 for { 160 nextSeg := nilSafe(raw.self.getIdx(valueInt(nextIndex), nil)).toString() 161 stringElements.WriteString(nextSeg) 162 if nextIndex+1 == literalSegments { 163 return stringElements.String() 164 } 165 if nextIndex < numberOfSubstitutions { 166 stringElements.WriteString(nilSafe(call.Arguments[nextIndex+1]).toString()) 167 } 168 nextIndex++ 169 } 170 } 171 172 func (r *Runtime) stringproto_at(call FunctionCall) Value { 173 r.checkObjectCoercible(call.This) 174 s := call.This.toString() 175 pos := call.Argument(0).ToInteger() 176 length := int64(s.Length()) 177 if pos < 0 { 178 pos = length + pos 179 } 180 if pos >= length || pos < 0 { 181 return _undefined 182 } 183 return s.Substring(int(pos), int(pos+1)) 184 } 185 186 func (r *Runtime) stringproto_charAt(call FunctionCall) Value { 187 r.checkObjectCoercible(call.This) 188 s := call.This.toString() 189 pos := call.Argument(0).ToInteger() 190 if pos < 0 || pos >= int64(s.Length()) { 191 return stringEmpty 192 } 193 return s.Substring(int(pos), int(pos+1)) 194 } 195 196 func (r *Runtime) stringproto_charCodeAt(call FunctionCall) Value { 197 r.checkObjectCoercible(call.This) 198 s := call.This.toString() 199 pos := call.Argument(0).ToInteger() 200 if pos < 0 || pos >= int64(s.Length()) { 201 return _NaN 202 } 203 return intToValue(int64(s.CharAt(toIntStrict(pos)) & 0xFFFF)) 204 } 205 206 func (r *Runtime) stringproto_codePointAt(call FunctionCall) Value { 207 r.checkObjectCoercible(call.This) 208 s := call.This.toString() 209 p := call.Argument(0).ToInteger() 210 size := s.Length() 211 if p < 0 || p >= int64(size) { 212 return _undefined 213 } 214 pos := toIntStrict(p) 215 first := s.CharAt(pos) 216 if isUTF16FirstSurrogate(first) { 217 pos++ 218 if pos < size { 219 second := s.CharAt(pos) 220 if isUTF16SecondSurrogate(second) { 221 return intToValue(int64(utf16.DecodeRune(rune(first), rune(second)))) 222 } 223 } 224 } 225 return intToValue(int64(first & 0xFFFF)) 226 } 227 228 func (r *Runtime) stringproto_concat(call FunctionCall) Value { 229 r.checkObjectCoercible(call.This) 230 strs := make([]String, len(call.Arguments)+1) 231 a, u := devirtualizeString(call.This.toString()) 232 allAscii := true 233 totalLen := 0 234 if u == nil { 235 strs[0] = a 236 totalLen = len(a) 237 } else { 238 strs[0] = u 239 totalLen = u.Length() 240 allAscii = false 241 } 242 for i, arg := range call.Arguments { 243 a, u := devirtualizeString(arg.toString()) 244 if u != nil { 245 allAscii = false 246 totalLen += u.Length() 247 strs[i+1] = u 248 } else { 249 totalLen += a.Length() 250 strs[i+1] = a 251 } 252 } 253 254 if allAscii { 255 var buf strings.Builder 256 buf.Grow(totalLen) 257 for _, s := range strs { 258 buf.WriteString(s.String()) 259 } 260 return asciiString(buf.String()) 261 } else { 262 buf := make([]uint16, totalLen+1) 263 buf[0] = unistring.BOM 264 pos := 1 265 for _, s := range strs { 266 switch s := s.(type) { 267 case asciiString: 268 for i := 0; i < len(s); i++ { 269 buf[pos] = uint16(s[i]) 270 pos++ 271 } 272 case unicodeString: 273 copy(buf[pos:], s[1:]) 274 pos += s.Length() 275 } 276 } 277 return unicodeString(buf) 278 } 279 } 280 281 func (r *Runtime) stringproto_endsWith(call FunctionCall) Value { 282 r.checkObjectCoercible(call.This) 283 s := call.This.toString() 284 searchString := call.Argument(0) 285 if isRegexp(searchString) { 286 panic(r.NewTypeError("First argument to String.prototype.endsWith must not be a regular expression")) 287 } 288 searchStr := searchString.toString() 289 l := int64(s.Length()) 290 var pos int64 291 if posArg := call.Argument(1); posArg != _undefined { 292 pos = posArg.ToInteger() 293 } else { 294 pos = l 295 } 296 end := toIntStrict(min(max(pos, 0), l)) 297 searchLength := searchStr.Length() 298 start := end - searchLength 299 if start < 0 { 300 return valueFalse 301 } 302 for i := 0; i < searchLength; i++ { 303 if s.CharAt(start+i) != searchStr.CharAt(i) { 304 return valueFalse 305 } 306 } 307 return valueTrue 308 } 309 310 func (r *Runtime) stringproto_includes(call FunctionCall) Value { 311 r.checkObjectCoercible(call.This) 312 s := call.This.toString() 313 searchString := call.Argument(0) 314 if isRegexp(searchString) { 315 panic(r.NewTypeError("First argument to String.prototype.includes must not be a regular expression")) 316 } 317 searchStr := searchString.toString() 318 var pos int64 319 if posArg := call.Argument(1); posArg != _undefined { 320 pos = posArg.ToInteger() 321 } else { 322 pos = 0 323 } 324 start := toIntStrict(min(max(pos, 0), int64(s.Length()))) 325 if s.index(searchStr, start) != -1 { 326 return valueTrue 327 } 328 return valueFalse 329 } 330 331 func (r *Runtime) stringproto_indexOf(call FunctionCall) Value { 332 r.checkObjectCoercible(call.This) 333 value := call.This.toString() 334 target := call.Argument(0).toString() 335 pos := call.Argument(1).ToInteger() 336 337 if pos < 0 { 338 pos = 0 339 } else { 340 l := int64(value.Length()) 341 if pos > l { 342 pos = l 343 } 344 } 345 346 return intToValue(int64(value.index(target, toIntStrict(pos)))) 347 } 348 349 func (r *Runtime) stringproto_lastIndexOf(call FunctionCall) Value { 350 r.checkObjectCoercible(call.This) 351 value := call.This.toString() 352 target := call.Argument(0).toString() 353 numPos := call.Argument(1).ToNumber() 354 355 var pos int64 356 if f, ok := numPos.(valueFloat); ok && math.IsNaN(float64(f)) { 357 pos = int64(value.Length()) 358 } else { 359 pos = numPos.ToInteger() 360 if pos < 0 { 361 pos = 0 362 } else { 363 l := int64(value.Length()) 364 if pos > l { 365 pos = l 366 } 367 } 368 } 369 370 return intToValue(int64(value.lastIndex(target, toIntStrict(pos)))) 371 } 372 373 func (r *Runtime) stringproto_localeCompare(call FunctionCall) Value { 374 r.checkObjectCoercible(call.This) 375 this := norm.NFD.String(call.This.toString().String()) 376 that := norm.NFD.String(call.Argument(0).toString().String()) 377 return intToValue(int64(r.collator().CompareString(this, that))) 378 } 379 380 func (r *Runtime) stringproto_match(call FunctionCall) Value { 381 r.checkObjectCoercible(call.This) 382 regexp := call.Argument(0) 383 if regexp != _undefined && regexp != _null { 384 if matcher := toMethod(r.getV(regexp, SymMatch)); matcher != nil { 385 return matcher(FunctionCall{ 386 This: regexp, 387 Arguments: []Value{call.This}, 388 }) 389 } 390 } 391 392 var rx *regexpObject 393 if regexp, ok := regexp.(*Object); ok { 394 rx, _ = regexp.self.(*regexpObject) 395 } 396 397 if rx == nil { 398 rx = r.newRegExp(regexp, nil, r.getRegExpPrototype()) 399 } 400 401 if matcher, ok := r.toObject(rx.getSym(SymMatch, nil)).self.assertCallable(); ok { 402 return matcher(FunctionCall{ 403 This: rx.val, 404 Arguments: []Value{call.This.toString()}, 405 }) 406 } 407 408 panic(r.NewTypeError("RegExp matcher is not a function")) 409 } 410 411 func (r *Runtime) stringproto_matchAll(call FunctionCall) Value { 412 r.checkObjectCoercible(call.This) 413 regexp := call.Argument(0) 414 if regexp != _undefined && regexp != _null { 415 if isRegexp(regexp) { 416 if o, ok := regexp.(*Object); ok { 417 flags := nilSafe(o.self.getStr("flags", nil)) 418 r.checkObjectCoercible(flags) 419 if !strings.Contains(flags.toString().String(), "g") { 420 panic(r.NewTypeError("RegExp doesn't have global flag set")) 421 } 422 } 423 } 424 if matcher := toMethod(r.getV(regexp, SymMatchAll)); matcher != nil { 425 return matcher(FunctionCall{ 426 This: regexp, 427 Arguments: []Value{call.This}, 428 }) 429 } 430 } 431 432 rx := r.newRegExp(regexp, asciiString("g"), r.getRegExpPrototype()) 433 434 if matcher, ok := r.toObject(rx.getSym(SymMatchAll, nil)).self.assertCallable(); ok { 435 return matcher(FunctionCall{ 436 This: rx.val, 437 Arguments: []Value{call.This.toString()}, 438 }) 439 } 440 441 panic(r.NewTypeError("RegExp matcher is not a function")) 442 } 443 444 func (r *Runtime) stringproto_normalize(call FunctionCall) Value { 445 r.checkObjectCoercible(call.This) 446 s := call.This.toString() 447 var form string 448 if formArg := call.Argument(0); formArg != _undefined { 449 form = formArg.toString().toString().String() 450 } else { 451 form = "NFC" 452 } 453 var f norm.Form 454 switch form { 455 case "NFC": 456 f = norm.NFC 457 case "NFD": 458 f = norm.NFD 459 case "NFKC": 460 f = norm.NFKC 461 case "NFKD": 462 f = norm.NFKD 463 default: 464 panic(r.newError(r.getRangeError(), "The normalization form should be one of NFC, NFD, NFKC, NFKD")) 465 } 466 467 switch s := s.(type) { 468 case asciiString: 469 return s 470 case unicodeString: 471 ss := s.String() 472 return newStringValue(f.String(ss)) 473 case *importedString: 474 if s.scanned && s.u == nil { 475 return asciiString(s.s) 476 } 477 return newStringValue(f.String(s.s)) 478 default: 479 panic(unknownStringTypeErr(s)) 480 } 481 } 482 483 func (r *Runtime) _stringPad(call FunctionCall, start bool) Value { 484 r.checkObjectCoercible(call.This) 485 s := call.This.toString() 486 maxLength := toLength(call.Argument(0)) 487 stringLength := int64(s.Length()) 488 if maxLength <= stringLength { 489 return s 490 } 491 strAscii, strUnicode := devirtualizeString(s) 492 var filler String 493 var fillerAscii asciiString 494 var fillerUnicode unicodeString 495 if fillString := call.Argument(1); fillString != _undefined { 496 filler = fillString.toString() 497 if filler.Length() == 0 { 498 return s 499 } 500 fillerAscii, fillerUnicode = devirtualizeString(filler) 501 } else { 502 fillerAscii = " " 503 filler = fillerAscii 504 } 505 remaining := toIntStrict(maxLength - stringLength) 506 if fillerUnicode == nil && strUnicode == nil { 507 fl := fillerAscii.Length() 508 var sb strings.Builder 509 sb.Grow(toIntStrict(maxLength)) 510 if !start { 511 sb.WriteString(string(strAscii)) 512 } 513 for remaining >= fl { 514 sb.WriteString(string(fillerAscii)) 515 remaining -= fl 516 } 517 if remaining > 0 { 518 sb.WriteString(string(fillerAscii[:remaining])) 519 } 520 if start { 521 sb.WriteString(string(strAscii)) 522 } 523 return asciiString(sb.String()) 524 } 525 var sb unicodeStringBuilder 526 sb.ensureStarted(toIntStrict(maxLength)) 527 if !start { 528 sb.writeString(s) 529 } 530 fl := filler.Length() 531 for remaining >= fl { 532 sb.writeString(filler) 533 remaining -= fl 534 } 535 if remaining > 0 { 536 sb.writeString(filler.Substring(0, remaining)) 537 } 538 if start { 539 sb.writeString(s) 540 } 541 542 return sb.String() 543 } 544 545 func (r *Runtime) stringproto_padEnd(call FunctionCall) Value { 546 return r._stringPad(call, false) 547 } 548 549 func (r *Runtime) stringproto_padStart(call FunctionCall) Value { 550 return r._stringPad(call, true) 551 } 552 553 func (r *Runtime) stringproto_repeat(call FunctionCall) Value { 554 r.checkObjectCoercible(call.This) 555 s := call.This.toString() 556 n := call.Argument(0).ToNumber() 557 if n == _positiveInf { 558 panic(r.newError(r.getRangeError(), "Invalid count value")) 559 } 560 numInt := n.ToInteger() 561 if numInt < 0 { 562 panic(r.newError(r.getRangeError(), "Invalid count value")) 563 } 564 if numInt == 0 || s.Length() == 0 { 565 return stringEmpty 566 } 567 num := toIntStrict(numInt) 568 a, u := devirtualizeString(s) 569 if u == nil { 570 var sb strings.Builder 571 sb.Grow(len(a) * num) 572 for i := 0; i < num; i++ { 573 sb.WriteString(string(a)) 574 } 575 return asciiString(sb.String()) 576 } 577 578 var sb unicodeStringBuilder 579 sb.Grow(u.Length() * num) 580 for i := 0; i < num; i++ { 581 sb.writeUnicodeString(u) 582 } 583 return sb.String() 584 } 585 586 func getReplaceValue(replaceValue Value) (str String, rcall func(FunctionCall) Value) { 587 if replaceValue, ok := replaceValue.(*Object); ok { 588 if c, ok := replaceValue.self.assertCallable(); ok { 589 rcall = c 590 return 591 } 592 } 593 str = replaceValue.toString() 594 return 595 } 596 597 func stringReplace(s String, found [][]int, newstring String, rcall func(FunctionCall) Value) Value { 598 if len(found) == 0 { 599 return s 600 } 601 602 a, u := devirtualizeString(s) 603 604 var buf StringBuilder 605 606 lastIndex := 0 607 lengthS := s.Length() 608 if rcall != nil { 609 for _, item := range found { 610 if item[0] != lastIndex { 611 buf.WriteSubstring(s, lastIndex, item[0]) 612 } 613 matchCount := len(item) / 2 614 argumentList := make([]Value, matchCount+2) 615 for index := 0; index < matchCount; index++ { 616 offset := 2 * index 617 if item[offset] != -1 { 618 if u == nil { 619 argumentList[index] = a[item[offset]:item[offset+1]] 620 } else { 621 argumentList[index] = u.Substring(item[offset], item[offset+1]) 622 } 623 } else { 624 argumentList[index] = _undefined 625 } 626 } 627 argumentList[matchCount] = valueInt(item[0]) 628 argumentList[matchCount+1] = s 629 replacement := rcall(FunctionCall{ 630 This: _undefined, 631 Arguments: argumentList, 632 }).toString() 633 buf.WriteString(replacement) 634 lastIndex = item[1] 635 } 636 } else { 637 for _, item := range found { 638 if item[0] != lastIndex { 639 buf.WriteString(s.Substring(lastIndex, item[0])) 640 } 641 matchCount := len(item) / 2 642 writeSubstitution(s, item[0], matchCount, func(idx int) String { 643 if item[idx*2] != -1 { 644 if u == nil { 645 return a[item[idx*2]:item[idx*2+1]] 646 } 647 return u.Substring(item[idx*2], item[idx*2+1]) 648 } 649 return stringEmpty 650 }, newstring, &buf) 651 lastIndex = item[1] 652 } 653 } 654 655 if lastIndex != lengthS { 656 buf.WriteString(s.Substring(lastIndex, lengthS)) 657 } 658 659 return buf.String() 660 } 661 662 func (r *Runtime) stringproto_replace(call FunctionCall) Value { 663 r.checkObjectCoercible(call.This) 664 searchValue := call.Argument(0) 665 replaceValue := call.Argument(1) 666 if searchValue != _undefined && searchValue != _null { 667 if replacer := toMethod(r.getV(searchValue, SymReplace)); replacer != nil { 668 return replacer(FunctionCall{ 669 This: searchValue, 670 Arguments: []Value{call.This, replaceValue}, 671 }) 672 } 673 } 674 675 s := call.This.toString() 676 var found [][]int 677 searchStr := searchValue.toString() 678 pos := s.index(searchStr, 0) 679 if pos != -1 { 680 found = append(found, []int{pos, pos + searchStr.Length()}) 681 } 682 683 str, rcall := getReplaceValue(replaceValue) 684 return stringReplace(s, found, str, rcall) 685 } 686 687 func (r *Runtime) stringproto_replaceAll(call FunctionCall) Value { 688 r.checkObjectCoercible(call.This) 689 searchValue := call.Argument(0) 690 replaceValue := call.Argument(1) 691 if searchValue != _undefined && searchValue != _null { 692 if isRegexp(searchValue) { 693 if o, ok := searchValue.(*Object); ok { 694 flags := nilSafe(o.self.getStr("flags", nil)) 695 r.checkObjectCoercible(flags) 696 if !strings.Contains(flags.toString().String(), "g") { 697 panic(r.NewTypeError("String.prototype.replaceAll called with a non-global RegExp argument")) 698 } 699 } 700 } 701 if replacer := toMethod(r.getV(searchValue, SymReplace)); replacer != nil { 702 return replacer(FunctionCall{ 703 This: searchValue, 704 Arguments: []Value{call.This, replaceValue}, 705 }) 706 } 707 } 708 709 s := call.This.toString() 710 var found [][]int 711 searchStr := searchValue.toString() 712 searchLength := searchStr.Length() 713 advanceBy := toIntStrict(max(1, int64(searchLength))) 714 715 pos := s.index(searchStr, 0) 716 for pos != -1 { 717 found = append(found, []int{pos, pos + searchLength}) 718 pos = s.index(searchStr, pos+advanceBy) 719 } 720 721 str, rcall := getReplaceValue(replaceValue) 722 return stringReplace(s, found, str, rcall) 723 } 724 725 func (r *Runtime) stringproto_search(call FunctionCall) Value { 726 r.checkObjectCoercible(call.This) 727 regexp := call.Argument(0) 728 if regexp != _undefined && regexp != _null { 729 if searcher := toMethod(r.getV(regexp, SymSearch)); searcher != nil { 730 return searcher(FunctionCall{ 731 This: regexp, 732 Arguments: []Value{call.This}, 733 }) 734 } 735 } 736 737 var rx *regexpObject 738 if regexp, ok := regexp.(*Object); ok { 739 rx, _ = regexp.self.(*regexpObject) 740 } 741 742 if rx == nil { 743 rx = r.newRegExp(regexp, nil, r.getRegExpPrototype()) 744 } 745 746 if searcher, ok := r.toObject(rx.getSym(SymSearch, nil)).self.assertCallable(); ok { 747 return searcher(FunctionCall{ 748 This: rx.val, 749 Arguments: []Value{call.This.toString()}, 750 }) 751 } 752 753 panic(r.NewTypeError("RegExp searcher is not a function")) 754 } 755 756 func (r *Runtime) stringproto_slice(call FunctionCall) Value { 757 r.checkObjectCoercible(call.This) 758 s := call.This.toString() 759 760 l := int64(s.Length()) 761 start := call.Argument(0).ToInteger() 762 var end int64 763 if arg1 := call.Argument(1); arg1 != _undefined { 764 end = arg1.ToInteger() 765 } else { 766 end = l 767 } 768 769 if start < 0 { 770 start += l 771 if start < 0 { 772 start = 0 773 } 774 } else { 775 if start > l { 776 start = l 777 } 778 } 779 780 if end < 0 { 781 end += l 782 if end < 0 { 783 end = 0 784 } 785 } else { 786 if end > l { 787 end = l 788 } 789 } 790 791 if end > start { 792 return s.Substring(int(start), int(end)) 793 } 794 return stringEmpty 795 } 796 797 func (r *Runtime) stringproto_split(call FunctionCall) Value { 798 r.checkObjectCoercible(call.This) 799 separatorValue := call.Argument(0) 800 limitValue := call.Argument(1) 801 if separatorValue != _undefined && separatorValue != _null { 802 if splitter := toMethod(r.getV(separatorValue, SymSplit)); splitter != nil { 803 return splitter(FunctionCall{ 804 This: separatorValue, 805 Arguments: []Value{call.This, limitValue}, 806 }) 807 } 808 } 809 s := call.This.toString() 810 811 limit := -1 812 if limitValue != _undefined { 813 limit = int(toUint32(limitValue)) 814 } 815 816 separatorValue = separatorValue.ToString() 817 818 if limit == 0 { 819 return r.newArrayValues(nil) 820 } 821 822 if separatorValue == _undefined { 823 return r.newArrayValues([]Value{s}) 824 } 825 826 separator := separatorValue.String() 827 828 excess := false 829 str := s.String() 830 if limit > len(str) { 831 limit = len(str) 832 } 833 splitLimit := limit 834 if limit > 0 { 835 splitLimit = limit + 1 836 excess = true 837 } 838 839 // TODO handle invalid UTF-16 840 split := strings.SplitN(str, separator, splitLimit) 841 842 if excess && len(split) > limit { 843 split = split[:limit] 844 } 845 846 valueArray := make([]Value, len(split)) 847 for index, value := range split { 848 valueArray[index] = newStringValue(value) 849 } 850 851 return r.newArrayValues(valueArray) 852 } 853 854 func (r *Runtime) stringproto_startsWith(call FunctionCall) Value { 855 r.checkObjectCoercible(call.This) 856 s := call.This.toString() 857 searchString := call.Argument(0) 858 if isRegexp(searchString) { 859 panic(r.NewTypeError("First argument to String.prototype.startsWith must not be a regular expression")) 860 } 861 searchStr := searchString.toString() 862 l := int64(s.Length()) 863 var pos int64 864 if posArg := call.Argument(1); posArg != _undefined { 865 pos = posArg.ToInteger() 866 } 867 start := toIntStrict(min(max(pos, 0), l)) 868 searchLength := searchStr.Length() 869 if int64(searchLength+start) > l { 870 return valueFalse 871 } 872 for i := 0; i < searchLength; i++ { 873 if s.CharAt(start+i) != searchStr.CharAt(i) { 874 return valueFalse 875 } 876 } 877 return valueTrue 878 } 879 880 func (r *Runtime) stringproto_substring(call FunctionCall) Value { 881 r.checkObjectCoercible(call.This) 882 s := call.This.toString() 883 884 l := int64(s.Length()) 885 intStart := call.Argument(0).ToInteger() 886 var intEnd int64 887 if end := call.Argument(1); end != _undefined { 888 intEnd = end.ToInteger() 889 } else { 890 intEnd = l 891 } 892 if intStart < 0 { 893 intStart = 0 894 } else if intStart > l { 895 intStart = l 896 } 897 898 if intEnd < 0 { 899 intEnd = 0 900 } else if intEnd > l { 901 intEnd = l 902 } 903 904 if intStart > intEnd { 905 intStart, intEnd = intEnd, intStart 906 } 907 908 return s.Substring(int(intStart), int(intEnd)) 909 } 910 911 func (r *Runtime) stringproto_toLowerCase(call FunctionCall) Value { 912 r.checkObjectCoercible(call.This) 913 s := call.This.toString() 914 915 return s.toLower() 916 } 917 918 func (r *Runtime) stringproto_toUpperCase(call FunctionCall) Value { 919 r.checkObjectCoercible(call.This) 920 s := call.This.toString() 921 922 return s.toUpper() 923 } 924 925 func (r *Runtime) stringproto_trim(call FunctionCall) Value { 926 r.checkObjectCoercible(call.This) 927 s := call.This.toString() 928 929 // TODO handle invalid UTF-16 930 return newStringValue(strings.Trim(s.String(), parser.WhitespaceChars)) 931 } 932 933 func (r *Runtime) stringproto_trimEnd(call FunctionCall) Value { 934 r.checkObjectCoercible(call.This) 935 s := call.This.toString() 936 937 // TODO handle invalid UTF-16 938 return newStringValue(strings.TrimRight(s.String(), parser.WhitespaceChars)) 939 } 940 941 func (r *Runtime) stringproto_trimStart(call FunctionCall) Value { 942 r.checkObjectCoercible(call.This) 943 s := call.This.toString() 944 945 // TODO handle invalid UTF-16 946 return newStringValue(strings.TrimLeft(s.String(), parser.WhitespaceChars)) 947 } 948 949 func (r *Runtime) stringproto_substr(call FunctionCall) Value { 950 r.checkObjectCoercible(call.This) 951 s := call.This.toString() 952 start := call.Argument(0).ToInteger() 953 var length int64 954 sl := int64(s.Length()) 955 if arg := call.Argument(1); arg != _undefined { 956 length = arg.ToInteger() 957 } else { 958 length = sl 959 } 960 961 if start < 0 { 962 start = max(sl+start, 0) 963 } 964 965 length = min(max(length, 0), sl-start) 966 if length <= 0 { 967 return stringEmpty 968 } 969 970 return s.Substring(int(start), int(start+length)) 971 } 972 973 func (r *Runtime) stringIterProto_next(call FunctionCall) Value { 974 thisObj := r.toObject(call.This) 975 if iter, ok := thisObj.self.(*stringIterObject); ok { 976 return iter.next() 977 } 978 panic(r.NewTypeError("Method String Iterator.prototype.next called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: thisObj}))) 979 } 980 981 func (r *Runtime) createStringIterProto(val *Object) objectImpl { 982 o := newBaseObjectObj(val, r.getIteratorPrototype(), classObject) 983 984 o._putProp("next", r.newNativeFunc(r.stringIterProto_next, "next", 0), true, false, true) 985 o._putSym(SymToStringTag, valueProp(asciiString(classStringIterator), false, false, true)) 986 987 return o 988 } 989 990 func (r *Runtime) getStringIteratorPrototype() *Object { 991 var o *Object 992 if o = r.global.StringIteratorPrototype; o == nil { 993 o = &Object{runtime: r} 994 r.global.StringIteratorPrototype = o 995 o.self = r.createStringIterProto(o) 996 } 997 return o 998 } 999 1000 func createStringProtoTemplate() *objectTemplate { 1001 t := newObjectTemplate() 1002 t.protoFactory = func(r *Runtime) *Object { 1003 return r.global.ObjectPrototype 1004 } 1005 1006 t.putStr("length", func(r *Runtime) Value { return valueProp(intToValue(0), false, false, false) }) 1007 1008 t.putStr("constructor", func(r *Runtime) Value { return valueProp(r.getString(), true, false, true) }) 1009 1010 t.putStr("at", func(r *Runtime) Value { return r.methodProp(r.stringproto_at, "at", 1) }) 1011 t.putStr("charAt", func(r *Runtime) Value { return r.methodProp(r.stringproto_charAt, "charAt", 1) }) 1012 t.putStr("charCodeAt", func(r *Runtime) Value { return r.methodProp(r.stringproto_charCodeAt, "charCodeAt", 1) }) 1013 t.putStr("codePointAt", func(r *Runtime) Value { return r.methodProp(r.stringproto_codePointAt, "codePointAt", 1) }) 1014 t.putStr("concat", func(r *Runtime) Value { return r.methodProp(r.stringproto_concat, "concat", 1) }) 1015 t.putStr("endsWith", func(r *Runtime) Value { return r.methodProp(r.stringproto_endsWith, "endsWith", 1) }) 1016 t.putStr("includes", func(r *Runtime) Value { return r.methodProp(r.stringproto_includes, "includes", 1) }) 1017 t.putStr("indexOf", func(r *Runtime) Value { return r.methodProp(r.stringproto_indexOf, "indexOf", 1) }) 1018 t.putStr("lastIndexOf", func(r *Runtime) Value { return r.methodProp(r.stringproto_lastIndexOf, "lastIndexOf", 1) }) 1019 t.putStr("localeCompare", func(r *Runtime) Value { return r.methodProp(r.stringproto_localeCompare, "localeCompare", 1) }) 1020 t.putStr("match", func(r *Runtime) Value { return r.methodProp(r.stringproto_match, "match", 1) }) 1021 t.putStr("matchAll", func(r *Runtime) Value { return r.methodProp(r.stringproto_matchAll, "matchAll", 1) }) 1022 t.putStr("normalize", func(r *Runtime) Value { return r.methodProp(r.stringproto_normalize, "normalize", 0) }) 1023 t.putStr("padEnd", func(r *Runtime) Value { return r.methodProp(r.stringproto_padEnd, "padEnd", 1) }) 1024 t.putStr("padStart", func(r *Runtime) Value { return r.methodProp(r.stringproto_padStart, "padStart", 1) }) 1025 t.putStr("repeat", func(r *Runtime) Value { return r.methodProp(r.stringproto_repeat, "repeat", 1) }) 1026 t.putStr("replace", func(r *Runtime) Value { return r.methodProp(r.stringproto_replace, "replace", 2) }) 1027 t.putStr("replaceAll", func(r *Runtime) Value { return r.methodProp(r.stringproto_replaceAll, "replaceAll", 2) }) 1028 t.putStr("search", func(r *Runtime) Value { return r.methodProp(r.stringproto_search, "search", 1) }) 1029 t.putStr("slice", func(r *Runtime) Value { return r.methodProp(r.stringproto_slice, "slice", 2) }) 1030 t.putStr("split", func(r *Runtime) Value { return r.methodProp(r.stringproto_split, "split", 2) }) 1031 t.putStr("startsWith", func(r *Runtime) Value { return r.methodProp(r.stringproto_startsWith, "startsWith", 1) }) 1032 t.putStr("substring", func(r *Runtime) Value { return r.methodProp(r.stringproto_substring, "substring", 2) }) 1033 t.putStr("toLocaleLowerCase", func(r *Runtime) Value { return r.methodProp(r.stringproto_toLowerCase, "toLocaleLowerCase", 0) }) 1034 t.putStr("toLocaleUpperCase", func(r *Runtime) Value { return r.methodProp(r.stringproto_toUpperCase, "toLocaleUpperCase", 0) }) 1035 t.putStr("toLowerCase", func(r *Runtime) Value { return r.methodProp(r.stringproto_toLowerCase, "toLowerCase", 0) }) 1036 t.putStr("toString", func(r *Runtime) Value { return r.methodProp(r.stringproto_toString, "toString", 0) }) 1037 t.putStr("toUpperCase", func(r *Runtime) Value { return r.methodProp(r.stringproto_toUpperCase, "toUpperCase", 0) }) 1038 t.putStr("trim", func(r *Runtime) Value { return r.methodProp(r.stringproto_trim, "trim", 0) }) 1039 t.putStr("trimEnd", func(r *Runtime) Value { return valueProp(r.getStringproto_trimEnd(), true, false, true) }) 1040 t.putStr("trimStart", func(r *Runtime) Value { return valueProp(r.getStringproto_trimStart(), true, false, true) }) 1041 t.putStr("trimRight", func(r *Runtime) Value { return valueProp(r.getStringproto_trimEnd(), true, false, true) }) 1042 t.putStr("trimLeft", func(r *Runtime) Value { return valueProp(r.getStringproto_trimStart(), true, false, true) }) 1043 t.putStr("valueOf", func(r *Runtime) Value { return r.methodProp(r.stringproto_valueOf, "valueOf", 0) }) 1044 1045 // Annex B 1046 t.putStr("substr", func(r *Runtime) Value { return r.methodProp(r.stringproto_substr, "substr", 2) }) 1047 1048 t.putSym(SymIterator, func(r *Runtime) Value { 1049 return valueProp(r.newNativeFunc(r.stringproto_iterator, "[Symbol.iterator]", 0), true, false, true) 1050 }) 1051 1052 return t 1053 } 1054 1055 func (r *Runtime) getStringproto_trimEnd() *Object { 1056 ret := r.global.stringproto_trimEnd 1057 if ret == nil { 1058 ret = r.newNativeFunc(r.stringproto_trimEnd, "trimEnd", 0) 1059 r.global.stringproto_trimEnd = ret 1060 } 1061 return ret 1062 } 1063 1064 func (r *Runtime) getStringproto_trimStart() *Object { 1065 ret := r.global.stringproto_trimStart 1066 if ret == nil { 1067 ret = r.newNativeFunc(r.stringproto_trimStart, "trimStart", 0) 1068 r.global.stringproto_trimStart = ret 1069 } 1070 return ret 1071 } 1072 1073 func (r *Runtime) getStringSingleton() *stringObject { 1074 ret := r.stringSingleton 1075 if ret == nil { 1076 ret = r.builtin_new(r.getString(), nil).self.(*stringObject) 1077 r.stringSingleton = ret 1078 } 1079 return ret 1080 } 1081 1082 func (r *Runtime) getString() *Object { 1083 ret := r.global.String 1084 if ret == nil { 1085 ret = &Object{runtime: r} 1086 r.global.String = ret 1087 proto := r.getStringPrototype() 1088 o := r.newNativeFuncAndConstruct(ret, r.builtin_String, r.wrapNativeConstruct(r.builtin_newString, ret, proto), proto, "String", intToValue(1)) 1089 ret.self = o 1090 o._putProp("fromCharCode", r.newNativeFunc(r.string_fromcharcode, "fromCharCode", 1), true, false, true) 1091 o._putProp("fromCodePoint", r.newNativeFunc(r.string_fromcodepoint, "fromCodePoint", 1), true, false, true) 1092 o._putProp("raw", r.newNativeFunc(r.string_raw, "raw", 1), true, false, true) 1093 } 1094 return ret 1095 } 1096 1097 var stringProtoTemplate *objectTemplate 1098 var stringProtoTemplateOnce sync.Once 1099 1100 func getStringProtoTemplate() *objectTemplate { 1101 stringProtoTemplateOnce.Do(func() { 1102 stringProtoTemplate = createStringProtoTemplate() 1103 }) 1104 return stringProtoTemplate 1105 } 1106 1107 func (r *Runtime) getStringPrototype() *Object { 1108 ret := r.global.StringPrototype 1109 if ret == nil { 1110 ret = &Object{runtime: r} 1111 r.global.StringPrototype = ret 1112 o := r.newTemplatedObject(getStringProtoTemplate(), ret) 1113 o.class = classString 1114 } 1115 return ret 1116 }