• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (c) 2021-2024 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16package std.core;
17
18const MAX_CODE_POINT = 0x10FFFF
19const MAX_CODE_UNIT = 0xFFFF
20
21type StringOrRegExp = String | RegExp
22
23/**
24 * Unicode string
25 */
26export final class String extends Object implements Comparable<String>, JSONable<String>, Iterable<String> {
27
28    // The constructors below are implemented via initobj instruction
29    /**
30     * Constructs an empty String
31     */
32    public constructor() {}
33
34    /**
35     * Constructs String from chars array initializer
36     *
37     * @param data initializer
38     */
39    public constructor(data: char[]) {}
40
41    /**
42     * Constructs String from another String
43     *
44     * @param otherStr initializer
45     */
46    public constructor(otherStr: String) {}
47
48    /**
49     * Constructs String from NullishType
50     *
51     * @param object initializer
52     */
53    public constructor(object: NullishType) {}
54
55    /**
56     * Creates a new instance of a String
57     *
58     * @returns A new String instance
59     */
60    static invoke(): string {
61        return new String();
62    }
63
64    /**
65     * Creates a new instance of a String
66     *
67     * @param value The value to be converted to a string. Can be an Object or null.
68     *
69     * @returns A new String instance
70     */
71    static invoke(value: Object | null | undefined): string {
72        return new String(value);
73    }
74
75    /**
76     * Returns an instance of string at passed index.
77     *
78     * @param index index to look at
79     *
80     * @returns a primitive at index
81     */
82    public $_get(index: number): char {
83        return this.$_get(index as int);
84    }
85
86    /**
87     * Returns an instance of string at passed index.
88     *
89     * @param index index to look at
90     *
91     * @returns char value at index
92     *
93     * @throws StringIndexOutOfBoundsError if index is negative or >= length
94     */
95    public $_get(index: int): char {
96        return this.charAt(index);
97    }
98
99    /**
100     * Checks equality of this string and another Object as String
101     *
102     * @param to another object to compare
103     *
104     * @returns true if strings are equal and false otherwise
105     *
106     * @remarks
107     * Implemented as native function,  @see `equals()` intrinsic [declaration](https://gitee.com/openharmony-sig/arkcompiler_runtime_core/blob/master/plugins/ets/runtime/ets_libbase_runtime.yaml#L596).
108     */
109    public native equals(to: NullishType): boolean;
110
111    /**
112     * Length of this string
113     *
114     * @returns length of this string
115     *
116     * @remarks
117     * Implemented as native function,  @see `getLength()` intrinsic [declaration](https://gitee.com/openharmony-sig/arkcompiler_runtime_core/blob/master/plugins/ets/runtime/ets_libbase_runtime.yaml#L563).
118     */
119    public native getLength(): int;
120
121    /**
122     * Length of this string
123     *
124     * @returns length of this string
125     */
126    public get length(): number {
127        return this.getLength() as number
128    }
129
130    /**
131     * Getter for char at some index
132     *
133     * @param index index in char array inside String
134     *
135     * @returns char value at index
136     *
137     * @throws StringIndexOutOfBoundsError if index is negative or >= length
138     *
139     * @remarks
140     * Implemented as native function,  @see `charAt()` intrinsic [declaration](https://gitee.com/openharmony-sig/arkcompiler_runtime_core/blob/master/plugins/ets/runtime/ets_libbase_runtime.yaml#L585).
141     */
142    public charAt(index: number): String {
143        if (index < 0 || index >= this.getLength() || this.getLength() == 0) {
144            return "";
145        }
146        let c = new char[1];
147        c[0] = this.charAt(index as int)
148        return new String(c)
149    }
150
151    /**
152     * Getter for char at some index
153     *
154     * @param index index in char array inside String
155     *
156     * @returns char value at index
157     *
158     * @throws StringIndexOutOfBoundsError if index is negative or >= length
159     *
160     * @remarks
161     * Implemented as native function,  @see `charAt()` intrinsic [declaration](https://gitee.com/openharmony-sig/arkcompiler_runtime_core/blob/master/plugins/ets/runtime/ets_libbase_runtime.yaml#L585).
162     */
163    public native charAt(index: int): char;
164
165    /**
166     * Checks if this string is empty
167     *
168     * @returns true if empty and false otherwise
169     * @remarks
170     * Implemented as native function,  @see `length()` intrinsic [declaration](https://gitee.com/openharmony-sig/arkcompiler_runtime_core/blob/master/plugins/ets/runtime/ets_libbase_runtime.yaml#L574).
171     */
172    public native isEmpty(): boolean;
173
174    /**
175     * Gets the codepoint at the specified index in this string.
176     * Is similar to charAt(index), but if the character at the
177     * index is the beginning of a surrogate pair, the int
178     * representation for this codepoint is returned. Otherwise,
179     * the character at the index is returned.
180     *
181     * @param index index of the potential surrogate pair
182     *
183     * @returns the codepoint at the specified index
184     */
185    public codePointAt(index: number): Number | undefined {
186        if (index < 0 || index >= this.getLength() || this.getLength() == 0) {
187            return undefined
188        }
189        return this.codePointAtInt(index as int) as number;
190    }
191
192    /**
193     * Gets the codepoint at the specified index in this string.
194     * Is similar to charAt(index), but if the character at the
195     * index is the beginning of a surrogate pair, the int
196     * representation for this codepoint is returned. Otherwise,
197     * the character at the index is returned.
198     *
199     * @param index index of the potential surrogate pair
200     *
201     * @returns the codepoint at the specified index
202     */
203    public codePointAt(index: int): Number | undefined {
204        if (index < 0 || index >= this.getLength() || this.getLength() == 0) {
205            return undefined
206        }
207        return this.codePointAtInt(index) as number;
208    }
209
210    /**
211     * Gets the codepoint at the specified index in this string.
212     * Is similar to charAt(index), but if the character at the
213     * index is the beginning of a surrogate pair, the int
214     * representation for this codepoint is returned. Otherwise,
215     * the character at the index is returned.
216     *
217     * @param index index of the potential surrogate pair
218     *
219     * @returns the codepoint at the specified index
220     */
221    private codePointAtInt(index: int): int {
222        assert index >= 0 && index < this.getLength(): "Index is out of bound";
223        let highValue: char = this.charAt(index);
224        if (!Char.isHighSurrogate(highValue) || ++index == this.getLength()) {
225            return highValue as int;
226        }
227        let lowValue: char = this.charAt(index);
228        if (!Char.isLowSurrogate(lowValue)) {
229            return highValue as int;
230        }
231        return Char.charsToCodePoint(highValue, lowValue);
232    }
233
234    /**
235     * Returns the amount of full codepoints between begin and end
236     * indexes. Characters outside the range are not counted, even if
237     * the range ends in the middle of a surrogate pair.
238     *
239     * @param begin index to start from
240     * @param end past the ending index
241     *
242     * @throws StringIndexOutOfBoundsError if begin is negative or >= length
243     * @throws StringIndexOutOfBoundsError if end >= length
244     * @throws AssertionError if begin > end
245     *
246     * @returns the amount of completed codepoints
247     */
248    public codePointCount(begin: number, end: number): number {
249        return this.codePointCount(begin as int, end as int)
250    }
251
252    /**
253     * Returns the amount of full codepoints between begin and end
254     * indexes. Characters outside the range are not counted, even if
255     * the range ends in the middle of a surrogate pair.
256     *
257     * @param begin index to start from
258     * @param end past the ending index
259     *
260     * @throws StringIndexOutOfBoundsError if begin is negative or >= length
261     * @throws StringIndexOutOfBoundsError if end >= length
262     * @throws AssertionError if begin > end
263     *
264     * @returns the amount of completed codepoints
265     */
266    public codePointCount(begin: int, end: int): int {
267        assert begin <= end: "Begin idx must be less then end idx";
268        let counter: int = 0;
269        for (let i: int = begin; i < end; i++) {
270            ++counter;
271            if (Char.isHighSurrogate(this.charAt(i)) &&
272                (i + 1 < end) &&
273                Char.isLowSurrogate(this.charAt(i + 1))) {
274                ++i;
275            }
276        }
277        return counter;
278    }
279
280    /**
281     * Gets the full char sequence that is representing
282     * this string.
283     *
284     * @returns char[] array
285     */
286    public getChars(): char[] {
287        return this.getChars(0, this.getLength());
288    }
289
290    /**
291     * Gets the char sequence that is representing the part of
292     * this string between begin and end indexes. The range is
293     * a half-interview [begin, end).
294     *
295     * @param begin index to start from
296     * @param end past the ending index
297     *
298     * @throws StringIndexOutOfBoundsError if begin > end
299     * @throws StringIndexOutOfBoundsError if begin is negative or >= length
300     * @throws StringIndexOutOfBoundsError if end > length
301     *
302     * @returns char[] array
303     *
304     * @remark
305     * Implemented as native function,  @see `getChars()` intrinsic [declaration](https://gitee.com/openharmony-sig/arkcompiler_runtime_core/blob/master/plugins/ets/runtime/ets_libbase_runtime.yaml#L231).
306     */
307    public native getChars(begin: int, end: int): char[];
308
309    /**
310     * Gets the byte sequence that is representing the part of
311     * this string between begin and end indexes. The range is
312     * a half-interview [begin, end).
313     *
314     * @param begin index to start from
315     * @param end past the ending index
316     *
317     * @throws StringIndexOutOfBoundsError if begin is negative or >= length
318     * @throws StringIndexOutOfBoundsError if end >= length
319     * @throws AssertionError if begin > end
320     *
321     * @returns char[] array
322     */
323    public getBytes(begin: int, end: int): byte[] {
324        assert begin <= end: "Begin idx must be less then end idx";
325        let resChars: char[] = this.getChars(begin, end);
326        let resBytes: byte[] = new byte[resChars.length];
327        for (let i: int = 0; i < resChars.length; i++) {
328            resBytes[i] = resChars[i] as byte;
329        }
330        return resBytes;
331    }
332
333    /**
334     * Compares the given StringBuilder to this String. The
335     * result is true if the StringBuilder has the same content
336     * as this String at this moment and false otherwise.
337     *
338     * @param sb StringBuilder to compare to
339     *
340     * @throws NullPointerError if sb param is null
341     *
342     * @returns true if StringBuilder has the same String
343     */
344    public contentEquals(sb: StringBuilder): boolean {
345        return this.equals(sb.toString());
346    }
347
348    /**
349     * Lexicographical comparison between this String and another one.
350     * The result is less than 0 if this string sorts before the other,
351     * 0 if they are equal, and greater than 0 otherwise.
352     *
353     * @param other String to compare with
354     *
355     * @throws NullPointerError if to param is null
356     *
357     * @returns the comparison result
358     */
359    public native override compareTo(other: String): int;
360
361    /**
362     * Comparison between this String and another one based on locale.
363     * The result is -1 if this string sorts before the another string,
364     * 0 if they are equal, and 1 otherwise.
365     *
366     * @param another String to compare with
367     * @param locale String representing the BCP47 language tag
368     *
369     * @throws RangeError if the locale tag is invalid or not found
370     * @throws NullPointerError if another or locale is null
371     *
372     * @returns the comparison result
373     */
374    public native localeCompare(another: String, locale: String | null): number;
375
376    /**
377     * Comparison between this String and another one based on default
378     * host locale. The result is -1 if this string sorts before the
379     * another string, 0 if they are equal, and 1 otherwise.
380     *
381     * @param another String to compare with
382     *
383     * @throws RangeError if the locale tag is invalid or not found
384     * @throws NullPointerError if another param is null
385     *
386     * @returns the comparison result
387     */
388    public localeCompare(another: String): number {
389        return this.localeCompare(another, null);
390    }
391
392    /**
393     * Checks that the substring of this string that starts from
394     * the specified index starts with the specified prefix.
395     * Negative `fromIndex` is treated as 0. Result is always true
396     * if `prefix` is empty.
397     *
398     * @param prefix prefix string
399     * @param fromIndex index to start from
400     *
401     * @throws NullPointerError if prefix param is null
402     *
403     * @returns true if the substring begins with prefix
404     */
405    public startsWith(prefix: String, fromIndex?: Number): boolean {
406        if (fromIndex == undefined) {
407            return this.startsWith(prefix, 0)
408        }
409        return this.startsWith(prefix, fromIndex.intValue())
410    }
411
412    /**
413     * Checks that the substring of this string that starts from
414     * the specified index starts with the specified prefix.
415     *
416     * @param prefix prefix string
417     * @param fromIndex index to start from
418     *
419     * @throws NullPointerError if prefix param is null
420     * @throws StringIndexOutOfBoundsError if fromIndex param is negative or >= length
421     *
422     * @returns true if the substring begins with prefix
423     */
424    public startsWith(prefix: String, fromIndex: int): boolean {
425        let prefixLen = prefix.getLength()
426        if (fromIndex < 0) {
427            fromIndex = 0;
428        }
429        if (fromIndex > this.getLength() - prefixLen) {
430            return prefix.isEmpty();
431        }
432        for (let i: int = 0; i < prefixLen; i++) {
433            if (this.charAt(fromIndex + i) != prefix.charAt(i)) {
434                return false;
435            }
436        }
437        return true;
438    }
439
440    /**
441     * Checks that this string ends with the specified suffix.
442     * Result is always true if `suffix` is empty.
443     *
444     * @param suffix suffix string
445     *
446     * @param endPosition at which suffix is expected to be found. Defaults to str.length.
447     *
448     * @throws NullPointerError if suffix param is null
449     *
450     * @returns true if this string ends with suffix
451     */
452    public endsWith(suffix: String, endPosition?: Number): boolean {
453        let ep = asIntOrDefault(endPosition, this.getLength())
454        if (ep > this.getLength()) {
455            ep = this.getLength()
456        } else if (ep <= 0) {
457            return suffix == "";
458        }
459        let suffixLen = suffix.getLength()
460        let fromIndex: int = ep - suffixLen;
461        if (fromIndex < 0 && !suffix.isEmpty()) {
462            return false;
463        }
464        for (let i: int = 0; i < suffixLen; i++) {
465            if (this.charAt(fromIndex + i) != suffix.charAt(i)) {
466                return false;
467            }
468        }
469        return true;
470    }
471
472    /**
473     * Computes the hashcode for this String.
474     *
475     * @returns hashcode value of this String
476     */
477    public native override $_hashCode(): int;
478
479    /**
480     * Finds the first occurrence of a character in this String.
481     *
482     * @param ch to find
483     *
484     * @returns index of the character from the beginning of this string, or -1 if not found
485     */
486    public native indexOf(ch: char): int;
487
488    /**
489     * Finds the first occurrence of a character in this String at position >= fromIndex.
490     * Negative fromIndex is equivalent to 0, and fromIndex >= length implies no match.
491     *
492     * @param ch to find
493     * @param fromIndex to start searching from
494     *
495     * @returns index of the character from the beginning of this string, or -1 if not found
496     */
497    public native indexOf(ch: char, fromIndex: int): int;
498
499    /**
500     * Finds the first occurrence of another String in this String
501     *
502     * @param str to find
503     *
504     * @param fromIndex to start searching from
505     *
506     * @returns index of the str from the beginning of this string, or -1 if not found
507     */
508    public indexOf(str: String, fromIndex?: Number): number {
509        if (fromIndex == undefined) {
510            return this.indexOf(str, 0)
511        }
512        return this.indexOf(str, fromIndex.intValue())
513    }
514
515    /**
516     * Finds the first occurrence of another String in this String at position >= fromIndex.
517     * Negative fromIndex is equivalent to fromIndex = 0, and fromIndex >= length implies no match.
518     *
519     * @param str to find
520     * @param fromIndex to start searching from
521     *
522     * @returns index of the str from the beginning of this string, or -1 if not found
523     */
524    public native indexOf(str: String, fromIndex: int): int;
525
526    /**
527     * Finds the last occurrence of a character in this String.
528     *
529     * @param ch to find
530     *
531     * @returns index of the character from the beginning of this string, or -1 if not found
532     */
533    public lastIndexOf(ch: char): int {
534        return this.lastIndexOf(ch, this.getLength());
535    }
536
537    /**
538     * Finds the last occurrence of a character in this String at position <= fromIndex.
539     * All values of fromIndex >= length are equivalent, and negative fromIndex implies no match.
540     *
541     * @param ch to find
542     * @param fromIndex to start searching from
543     *
544     * @returns index of the character from the beginning of this string, or -1 if not found
545     */
546    public lastIndexOf(ch: char, fromIndex: int): int {
547        if (fromIndex >= this.getLength()) {
548            fromIndex = this.getLength() - 1;
549        }
550        for (let i: int = fromIndex; i >= 0; i--) {
551            if (this.charAt(i) == ch) {
552                return i;
553            }
554        }
555        return -1;
556    }
557
558    /**
559     * Finds the last occurrence of another String in this String.
560     *
561     * @param str to find
562     *
563     * @param fromIndex to start searching from
564    *
565     * @throws NullPointerError if str param is null
566     *
567     * @returns index of the str from the beginning of this string, or -1 if not found
568     */
569    public lastIndexOf(str: String, fromIndex?: Number): number {
570        if (fromIndex == undefined || isNaN(fromIndex)) {
571            return this.lastIndexOf(str, Int.MAX_VALUE as int)
572        }
573        return this.lastIndexOf(str, fromIndex.intValue())
574    }
575
576    /**
577     * Finds the last occurrence of another String in this String at position <= fromIndex.
578     * All values of fromIndex >= length are equivalent, and negative fromIndex implies no match.
579     *
580     * @param str to find
581     * @param fromIndex to start searching from
582     *
583     * @returns index of the str from the beginning of this string, or -1 if not found
584     */
585    public native lastIndexOf(str: String, fromIndex: int): int;
586
587    /**
588     * Selects a substring of this String, starting at a specified index
589     * and ending at the end of this String.
590     *
591     * @param begin to start substring
592     *
593     * @returns new String which is a substring of this String
594     */
595    public substring(begin: int): String {
596        return this.substring(begin, this.getLength());
597    }
598
599    /**
600     * Selects a substring of this String, starting at a specified index
601     * and ending at index before another specified index.
602     *
603     * @param begin to start substring
604     * @param end to end before at
605     *
606     * @returns new String which is a substring of this String
607     */
608    public substring(begin: number, end?: Number): String {
609        return this.substring(begin as int, asIntOrDefault(end, this.getLength()))
610    }
611
612    /**
613     * Selects a substring of this String, starting at a specified index
614     * and ending at index before another specified index.
615     *
616     * If 'begin' < 0, then 'begin' is assumed to be equal to zero.
617     * If 'begin' > this.length, then 'begin' is assumed to be equal to the this.length.
618     * If 'end' < 0, then 'end' is assumed to be equal to zero.
619     * If 'end' > this.length, then 'end' is assumed to be equal to this.length.
620     * If 'begin' > 'end', then these are swapped.
621     * If 'begin' == 'end', then an empty string is returned.
622     *
623     * @param begin to start substring
624     * @param end to end before at
625     *
626     * @returns new String which is a substring of this String
627     */
628    public native substring(begin: int, end: int): String;
629
630    /**
631     * Concatenation of this and array of strings.
632     *
633     * @param strings strings to concat with
634     *
635     * @returns new String which is a concatenation of this + strings[0] + ... + strings[string.length - 1]
636     */
637    public concat(...strings: String[]): String {
638        let length = strings.length
639        if (length == 0) {
640            return this
641        }
642        if (length == 1) {
643            return String.concat2(this, strings[0]);
644        }
645        if (length == 2) {
646            return String.concat3(this, strings[0], strings[1]);
647        }
648        if (length == 3) {
649            return String.concat4(this, strings[0], strings[1], strings[2]);
650        }
651        let res = new StringBuilder(this);
652        for (let i = 0; i < length; i++) {
653            res = res.append(strings[i])
654        }
655        return res.toString();
656    }
657
658    /**
659     * Concatenation of two, three of four strings
660     *
661     * @param strings strings to concat
662     *
663     * @returns new String is the concatenation of the
664     * arguments passed to the function:
665     *
666     * str1 + str2 - for concat2
667     * str1 + str3 + str3 - for concat3
668     * str1 + str2 + str3 + str4 - for concat4
669     */
670    private static native concat2(str1: String, str2: String): String;
671    private static native concat3(str1: String, str2: String, str3: String): String;
672    private static native concat4(str1: String, str2: String, str3: String, str4: String): String;
673
674    /*
675     * Creates a String using substitutions
676     *
677     * @param d a template
678     *
679     * @param substs strings to be substituted
680     *
681     * @returns string with respected data being substituted
682     */
683    // TODO(ivan-tyulyandin): uncomment when #12735 will be fixed
684    // public static raw(d: String, substs: ...String): String {
685    //     throw new Error("String.raw: not implemented")
686    // }
687
688    /**
689     * Replaces all occurrences of the specified character
690     * with another specified character. If no specified
691     * character in this string is found then the original
692     * string will be returned
693     *
694     * @param oldCh character which occurrences will be replaced
695     * @param newCh character to replace
696     *
697     * @returns new String with replaced characters
698     */
699    public replaceChar(oldCh: char, newCh: char): String {
700        let newChars: char[] = this.getChars();
701        for (let i: int = 0; i < newChars.length; i++) {
702            if (newChars[i] == oldCh) {
703                newChars[i] = newCh;
704            }
705        }
706        return new String(newChars);
707    }
708
709    /**
710     * Checks if this String contains the specified string.
711     * The search starts from specified index (negative fromIndex is equivalent to fromIndex = 0, and fromIndex >= length implies no match).
712     *
713     * @param str string to search
714     * @param fromIndex index to start search from
715     *
716     * @throws NullPointerError if str param is null
717     *
718     * @returns true if this String contains str and false otherwise
719     */
720    public contains(str: String, fromIndex: number): boolean {
721        return this.contains(str, fromIndex as int);
722    }
723
724    /**
725     * Checks if this String contains the specified string.
726     * The search starts from specified index.
727     *
728     * @param str string to search
729     * @param fromIndex index to start search from
730     *
731     * @throws NullPointerError if str param is null
732     * @throws StringIndexOutOfBoundsError if fromIndex param is negative or >= length
733     *
734     * @returns true if this String contains str and false otherwise
735     */
736    public contains(str: String, fromIndex: int): boolean {
737        return this.indexOf(str, fromIndex) != -1;
738    }
739
740    private static splitMatch(s: String, q: int, r: String): int {
741        let rLength = r.getLength();
742        if (q + rLength > s.getLength()) {
743            return -1;
744        }
745        if (rLength == 0) {
746            // For making algorithm compiler-friendly
747            // should be removed after BoundsAnalysis improvement
748            return q;
749        }
750        for (let i = 0; i < rLength - 1; ++i) {
751            if (s.codePointAtInt(q + i) != r.codePointAtInt(i)) {
752                return -1;
753            }
754        }
755        if (s.codePointAtInt(q + rLength - 1) != r.codePointAtInt(rLength - 1) as int) {
756            // For making algorithm compiler-friendly
757            // should be removed after BoundsAnalysis improvement
758            return -1;
759        }
760        return q + rLength;
761    }
762
763    /**
764     * Splits this String by pattern and returns ordered array of substrings.
765     * The order of the resulted array corresponds to the order of the
766     * passage of this String from beginning to end. The pattern is
767     * excluded from substrings. The array is limited by some specified value.
768     *
769     * @param pattern String or RegExp to split by
770     * @param limit max length of the returned array. If it's negative then there is no limit.
771     *
772     * @throws NullPointerError if pattern param is null
773     *
774     * @returns string array contains substrings from this String
775     */
776    public split(separator: StringOrRegExp, limit?: Number): String[] {
777        if (separator instanceof RegExp) {
778            return (separator as RegExp).split(this, limit)
779        }
780        return this.split(separator as String, limit)
781    }
782
783    public split(separator: RegExp, limit?: Number): String[] {
784        return separator.split(this, limit)
785    }
786
787    public split(separator: String, limit?: Number): String[] {
788        let lim : long
789        if (limit == undefined) {
790            lim = (1 << 32) - 1
791        } else if (limit == 0) {
792            return new String[0]
793        } else {
794            lim = limit.unboxed() as long
795        }
796        let s = this.getLength()
797        if (s == 0) {
798            let z = String.splitMatch(this, 0, separator)
799            if (z != -1) {
800                return new String[0]
801            }
802            return [(this)]
803        }
804        let splittedStrings = new NullableStringArray(s)
805        let lastStart = 0
806        for (let lastEnd = 0; lastEnd < s;) {
807            let separatorRight = String.splitMatch(this, lastEnd, separator)
808            assert(separatorRight <= s)
809            if (separatorRight != -1 && separatorRight != lastStart) {
810                let substr = this.substring(lastStart, lastEnd)
811                splittedStrings.pushBack(substr)
812                if (splittedStrings.size() == lim) {
813                    return splittedStrings.toArray() as String[]
814                }
815                lastStart = separatorRight
816                lastEnd = lastStart
817            } else {
818                ++lastEnd
819            }
820        }
821        let substr = this.substring(lastStart, s)
822        splittedStrings.pushBack(substr)
823        return splittedStrings.toArray() as String[]
824    }
825
826    /**
827     * Concatenates the specified string array by inserting
828     * the specified separator between all elements.
829     *
830     * @param strings string array
831     * @param delim separator between all elements
832     *
833     * @throws NullPointerError if strings param is null
834     * @throws NullPointerError if delim param is null
835     *
836     * @returns newly created string from string array and delimiter
837     */
838    public static join(strings: String[], delim: String): String {
839        return String.join(strings, delim, "", "");
840    }
841
842    /**
843     * Concatenates the specified string array by inserting
844     * the specified prefix before each element, the specified
845     * suffix after each element, and the specified separator
846     * between all elements.
847     *
848     * @param strings string array
849     * @param delim separator between all elements
850     * @param prefix prefix before each element
851     * @param suffix suffix after each element
852     *
853     * @throws NullPointerError if strings param is null
854     * @throws NullPointerError if delim param is null
855     * @throws NullPointerError if prefix param is null
856     * @throws NullPointerError if suffix param is null
857     *
858     * @returns newly created string from string array, prefix, suffix and delimiter
859     */
860    public static join(strings: String[], delim: String, prefix: String, suffix: String): String {
861        let resStr: String = "";
862        for (let i: int = 0; i < strings.length; i++) {
863            resStr += prefix + strings[i] + suffix;
864            if (i != strings.length - 1) {
865                resStr += delim;
866            }
867        }
868        return resStr;
869    }
870
871    /**
872     * Creates new string similar to this String but with
873     * all characters in lower case.
874     *
875     * @returns new string with all characters in lower case
876     */
877    public native toLowerCase(): String;
878
879    /**
880     * Creates new string similar to this String but with
881     * all characters in upper case.
882     *
883     * @returns new string with all characters in upper case
884     */
885    public native toUpperCase(): String;
886
887    /**
888     * Trims all whitespaces from the beginning and end of this String.
889     *
890     * @returns new trimmed string
891     */
892    public native trim(): String;
893
894    /**
895     * Trims all whitespaces from the beginning of this String.
896     *
897     * @returns new left trimmed string
898     */
899    public native trimLeft(): String;
900
901    /**
902     * Trims all whitespaces from the end of this String.
903     *
904     * @returns new right trimmed string
905     */
906    public native trimRight(): String;
907
908    /**
909     * Checks whether the specified char is in the specified
910     * char array or not.
911     *
912     * @param ch character to search
913     * @param data char array to search in
914     *
915     * @throws NullPointerError if data param is null
916     *
917     * @returns true if ch is in the data and false otherwise
918     */
919    private static isCharOneOf(ch: char, data: char[]): boolean {
920        for (let i: int = 0; i < data.length; i++) {
921            if (ch == data[i]) {
922                return true;
923            }
924        }
925        return false;
926    }
927
928    /**
929     * Trims all specified characters from the beginning and
930     * end of this String.
931     *
932     * @param remove that contains the characters to trim
933     *
934     * @throws NullPointerError if remove param is null
935     *
936     * @returns new trimmed string
937     */
938    public trim(remove: char[]): String {
939        if (this.isEmpty()) {
940            return this;
941        }
942        let right: int = this.getLength() - 1;
943        let left: int = 0;
944        if (String.isCharOneOf(this.charAt(right), remove)) {
945            right--;
946            while (left <= right && String.isCharOneOf(this.charAt(right), remove)) {
947                right--;
948            }
949        } else {
950            if (right != 0) {
951                if (!String.isCharOneOf(this.charAt(left), remove)) {
952                    return this;
953                } else {
954                    left++;
955                }
956            } else {
957                return this;
958            }
959        }
960
961        while (left <= right && String.isCharOneOf(this.charAt(left), remove)) {
962            left++;
963        }
964
965        return this.substring(left, right + 1);
966    }
967
968    /**
969     * Trims all specified characters from the beginning this String.
970     *
971     * @param remove that contains the characters to trim
972     *
973     * @throws NullPointerError if remove param is null
974     *
975     * @returns new left trimmed string
976     */
977    public trimLeft(remove: char[]): String {
978        if (this.isEmpty() || !String.isCharOneOf(this.charAt(0), remove)) {
979            return this;
980        }
981        let firstNotSpecCharIdx: int = 0;
982        for (let i: int = 1; i < this.getLength(); i++) {
983            if (!String.isCharOneOf(this.charAt(i), remove)) {
984                firstNotSpecCharIdx = i;
985                break;
986            }
987        }
988        return this.substring(firstNotSpecCharIdx, this.getLength());
989    }
990
991    /**
992     * Trims all specified characters from the end of this String.
993     *
994     * @param remove that contains the characters to trim
995     *
996     * @throws NullPointerError if remove param is null
997     *
998     * @returns new right trimmed string
999     */
1000    public trimRight(remove: char[]): String {
1001        let last: int = this.getLength() - 1;
1002        if (this.isEmpty() || !String.isCharOneOf(this.charAt(last), remove)) {
1003            return this;
1004        }
1005        let lastNotSpecCharIdx: int = 0;
1006        for (let i: int = last - 1; i >= 0; i--) {
1007            if (!String.isCharOneOf(this.charAt(i), remove)) {
1008                lastNotSpecCharIdx = i;
1009                break;
1010            }
1011        }
1012        return this.substring(0, lastNotSpecCharIdx + 1);
1013    }
1014
1015    /**
1016     * Creates a new string of a specified length in which
1017     * the beginning of this String is padded with a
1018     * specified character. `padStart` is an alias of this method,
1019     * except the parameter order.
1020     *
1021     * @param pad to repeat
1022     * @param count of characters in the resulting string
1023     *
1024     * @returns new string with padding at the beginning
1025     */
1026    public padLeft(pad: char, count: int): String {
1027        return this.padStart(count, pad)
1028    }
1029
1030    /**
1031     * Creates a new string of a specified length in which
1032     * the end of this String is padded with a specified
1033     * character. `padEnd` is an alias of this method,
1034     * except the parameter order.
1035     *
1036     * @param pad to repeat
1037     * @param count of characters in the resulting string
1038     *
1039     * @returns new string with padding at the end
1040     */
1041    public padRight(pad: char, count: int): String {
1042        return this.padEnd(count, pad)
1043    }
1044
1045    /**
1046     * Repeats this string count times, i.e.
1047     *    a = "A",
1048     *    a.repeat(2) == "AA"
1049     *
1050     * @param count number of repetitions of this String
1051     *
1052     * @throws ArgumentOutOfRangeException if count < 0
1053     *
1054     * @returns this string that is repeated count times
1055     */
1056    public repeat(count: number): String {
1057        return this.repeat(count as int)
1058    }
1059
1060    /**
1061     * Repeats this string count times, i.e.
1062     *    a = "A",
1063     *    a.repeat(2) == "AA"
1064    *
1065    * @param count number of repetitions of this String
1066    *
1067    * @throws ArgumentOutOfRangeException if count < 0
1068    *
1069    * @returns this string that is repeated count times
1070    */
1071    public repeat(count: int): String {
1072        if (count == 0) {
1073            return new String();
1074        }
1075        if (count < 0) {
1076            throw new RangeError("repeat: count is negative")
1077        }
1078        let length: int = this.getLength();
1079        let resChars: char[] = new char[length * count];
1080        for (let i: int = 0; i < length; i++) {
1081            resChars[i] = this.charAt(i);
1082        }
1083        try {
1084            for (let repeat = 1; repeat < count; repeat++) {
1085                copyWithin(resChars, repeat * length, 0, length);
1086            }
1087        } catch (e) {
1088            throw new Error("impossible")
1089        }
1090        return new String(resChars);
1091    }
1092
1093    /**
1094     * The `toString()` method returns the string representation of the given String
1095     * in the form of a copy of the original object.
1096     *
1097     * @returns a copy of the original String
1098     */
1099    public override toString(): String {
1100        return this;
1101    }
1102
1103    /**
1104     * The `toString()` method returns the string representation of the given String
1105     * in the form of a copy of the original object.
1106     *
1107     * @returns a copy of the original String
1108     */
1109    public override toLocaleString(): String {
1110        return this;
1111    }
1112
1113    /**
1114     * The at() method takes an integer value and returns a new String consisting of the single UTF-16 code unit located
1115     * at the specified offset. This method allows for positive and negative integers. Negative integers count back from the last string character.
1116     *
1117     * @returns A String consisting of the single UTF-16 code unit located at the specified position.
1118     * Returns undefined if the given index can not be found.
1119     */
1120    public at(index: number): string | undefined {
1121        if (isNaN(index)) {
1122            index = 0;
1123        }
1124        const n = this.getLength();
1125        if (index < 0) {
1126            index += n;
1127        }
1128        if (index < 0 || index >= n) {
1129            return undefined;
1130        }
1131        return new String(this.charAt(index as int));
1132    }
1133
1134    private CreateHTMLString(tag: String, param: String): String{
1135        return "<" + tag + param + ">" + (this) + "</" + tag + ">"
1136    }
1137
1138    /**
1139     * The anchor() method creates a string that embeds a string in an <a> element with a name (<a name="...">str</a>)
1140     *
1141     * @returns A string beginning with an <a name="name"> start tag (double quotes in name are replaced with &quot;),
1142     * then the text str, and then an </a> end tag.
1143     */
1144    public anchor(name: String): String {
1145        return this.CreateHTMLString("a", " name=\"" + name + "\"")
1146    }
1147
1148    /*
1149     * The big() method creates a string that embeds a string in a <big> element (<big>str</big>), which causes a string to be displayed in a big font.
1150     */
1151    public big(): String{
1152        return this.CreateHTMLString("big", "")
1153    }
1154
1155
1156    /*
1157     * The small() method creates a string that embeds a string in a <small> element (<small>str</small>), which causes a string to be displayed in a big font.
1158     */
1159    public small(): String{
1160        return this.CreateHTMLString("small", "")
1161    }
1162
1163    /*
1164     * The blink() method creates a string that embeds a string in a <blink> element (<blink>str</blink>), which causes a string to be displayed in a big font.
1165     */
1166    public blink(): String{
1167        return this.CreateHTMLString("blink", "")
1168    }
1169
1170    /*
1171     * The bold() method creates a string that embeds a string in a <bold> element (<b>str</b>), which causes a string to be displayed in a big font.
1172     */
1173    public bold(): String{
1174        return this.CreateHTMLString("b", "")
1175    }
1176
1177    /*
1178     * The italics() method creates a string that embeds a string in a <i> element (<i>str</i>), which causes a string to be displayed in a big font.
1179     */
1180    public italics(): String{
1181        return this.CreateHTMLString("i", "")
1182    }
1183
1184    /*
1185     * The strike() method creates a string that embeds a string in a <strike> element (<strike>str</strike>), which causes a string to be displayed in a big font.
1186     */
1187    public strike(): String{
1188        return this.CreateHTMLString("strike", "")
1189    }
1190
1191    /*
1192     * The sub() method creates a string that embeds a string in a <sub> element (<sub>str</sub>), which causes a string to be displayed in a big font.
1193     */
1194    public sub(): String{
1195        return this.CreateHTMLString("sub", "")
1196    }
1197
1198    /*
1199     * The sup() method creates a string that embeds a string in a <sup> element (<sup>str</sup>), which causes a string to be displayed in a big font.
1200     */
1201    public sup(): String{
1202        return this.CreateHTMLString("sup", "")
1203    }
1204
1205    /*
1206     * The fixed() method creates a string that embeds a string in a <tt> element (<tt>str</tt>), which causes a string to be displayed in a big font.
1207     */
1208    public fixed(): String{
1209        return this.CreateHTMLString("tt", "")
1210    }
1211
1212    /*
1213     * The fontcolor() method creates a string that embeds a string in a <font> element (<font color="...">str</font>), which causes a string to be displayed in the specified font color.
1214     */
1215    public fontcolor(color: String): String{
1216        return this.CreateHTMLString("font", " color=\"" + color + "\"")
1217    }
1218
1219    /*
1220     * The fontsize() method creates a string that embeds a string in a <font> element (<font size="...">str</font>), which causes a string to be displayed in the specified font size.
1221     */
1222    public fontsize(size: String): String {
1223        return this.CreateHTMLString("font", " size=\"" + size.replaceAll("\"", "&quot;") + "\"")
1224    }
1225
1226    /*
1227     * The fontsize() method creates a string that embeds a string in a <font> element (<font size="...">str</font>), which causes a string to be displayed in the specified font size.
1228     */
1229    public fontsize(size: number): String {
1230        return this.CreateHTMLString("font", " size=\"" + size + "\"")
1231    }
1232
1233    /*
1234     * The fontsize() method creates a string that embeds a string in a <font> element (<font size="...">str</font>), which causes a string to be displayed in the specified font size.
1235     */
1236    public fontsize(size: int): String{
1237        return this.CreateHTMLString("font", " size=\"" + size + "\"")
1238    }
1239
1240    /*
1241     * The link() method creates a string that embeds a string in an <a> element (<a href="...">str</a>), to be used as a hypertext link to another URL.
1242     */
1243    public link(link: String): String{
1244        return this.CreateHTMLString("a", " href=\"" + link + "\"")
1245    }
1246
1247    /*
1248     * The charCodeAt() method returns an integer between 0 and 65535 representing the UTF-16 code unit at the given index.
1249     */
1250    public charCodeAt(index: number): number {
1251        if (index < 0 || index >= this.getLength() || this.getLength() == 0) {
1252            return NaN;
1253        }
1254        return this.charAt(index as int);
1255    }
1256
1257    /*
1258     * The charCodeAt() method returns an integer between 0 and 65535 representing the UTF-16 code unit at the given index.
1259     */
1260    public charCodeAt(index: int): char {
1261        return this.charAt(index) as char;
1262    }
1263
1264    /**
1265     * The String.fromCharCode() static method returns a string created from the specified sequence of UTF-16 code units
1266     *
1267     * @param codes are numbers between 0 and 65535 (0xFFFF) representing a UTF-16 code unit or NaN
1268     *
1269     * @returns string consisting of specified UTF-16 code units.
1270     *
1271     */
1272    public static fromCharCode(...codes: number[]): String {
1273        let res = new StringBuilder();
1274        for (const cu of codes) {
1275            res.append((cu % (0x10000)) as char)
1276        }
1277        return res.toString()
1278    }
1279
1280    /*
1281     * The valueOf() method returns the primitive value of a String object.
1282     */
1283    public valueOf(): String {
1284        return this;
1285    }
1286
1287    /**
1288     * The includes() method performs a case-sensitive search to determine whether one string may
1289     * be found within another string, returning true or false as appropriate.
1290     *
1291     * @param searchString to be searched
1292     *
1293     * @param position within the string at which to begin searching for searchString
1294     *
1295     * @returns true if the search string is found anywhere within the given string,
1296     *  including when searchString is an empty string; otherwise, false
1297     */
1298    public includes(searchString: String, position?: Number): boolean {
1299        if (position == undefined) {
1300            return this.indexOf(searchString) == -1 ? false : true;
1301        }
1302        return this.indexOf(searchString, position.intValue()) == -1 ? false : true;
1303    }
1304
1305    /**
1306     * The padEnd() method pads the current string with a given string (repeated, if needed)
1307     * so that the resulting string reaches a given length.
1308     * The padding is applied from the end of the current string.
1309     *
1310     * @param maxLength of the resulting string once the current str has been padded
1311     *
1312     * @param fillString to pad the current str with
1313     *
1314     * @returns string with the padString applied at the end of the current str
1315     */
1316    public padEnd(maxLength: number, fillString?: String): String {
1317        const str = (fillString == undefined) ? "\u0020" : fillString
1318        return this.padEnd(maxLength as int, str)
1319    }
1320
1321    /*
1322     * The padEnd() method pads the current string with a given string (repeated, if needed)
1323     * so that the resulting string reaches a given length.
1324     * The padding is applied from the end of the current string.
1325     */
1326    public padEnd(end: int, ch: char): String {
1327        if (this.getLength() >= end) {
1328            return this;
1329        }
1330        let arr = new char[end]
1331        for (let i: int = 0; i < this.getLength(); i++) {
1332            arr[i] = this.charAt(i)
1333        }
1334        for(let i = this.getLength(); i < end; i++){
1335            arr[i] = ch
1336        }
1337        return new String(arr)
1338    }
1339
1340    public padEnd(end: int): String {
1341        return this.padEnd(end, ' ')
1342    }
1343
1344    public padEnd(end: int, str: String): String {
1345        if (this.getLength() >= end) {
1346            return this;
1347        }
1348        let arr = new char[end];
1349        let length = this.getLength();
1350        let strLength = str.getLength()
1351        for (let i: int = 0; i < length; i++) {
1352            arr[i] = this.charAt(i);
1353        }
1354        for (let i = 0; i < strLength && length + i < end; i++) {
1355            arr[length + i] = str.charAt(i);
1356        }
1357        for (let i = this.getLength() + strLength; i < end; i++) {
1358            arr[i] = arr[i - strLength];
1359        }
1360        return new String(arr);
1361    }
1362
1363    /**
1364     * The padStart() method pads the current string with another string (multiple times, if needed)
1365     * until the resulting string reaches the given length.
1366     * The padding is applied from the start of the resulting string.
1367     *
1368     * @param maxLength of the resulting string once the current str has been padded
1369     *
1370     * @param fillString to pad the current str with
1371     *
1372     * @returns string with the padString applied at the end of the current str
1373     */
1374    public padStart(maxLength: number, fillString?: String): String {
1375        const str = (fillString == undefined) ? "\u0020" : fillString
1376        return this.padStart(maxLength as int, str)
1377    }
1378
1379    /*
1380     * The padStart() method pads the current string with another string (multiple times, if needed)
1381     * until the resulting string reaches the given length.
1382     * The padding is applied from the start of the current string.
1383     */
1384    public padStart(end: int, ch: char): String {
1385        let padLen = end - this.getLength();
1386        if (padLen <= 0) {
1387            return this;
1388        }
1389        let arr: char[] = new char[end]
1390        for(let i = 0; i < padLen; i++){
1391            arr[i] = ch
1392        }
1393        for (let i: int = 0; i < this.getLength(); i++) {
1394            arr[padLen + i] = this.charAt(i);
1395        }
1396        return new String(arr)
1397    }
1398
1399    public padStart(end: int): String {
1400        return this.padStart(end, ' ')
1401    }
1402
1403    public padStart(end: int, str: String): String {
1404        if (this.getLength() >= end) {
1405            return this;
1406        }
1407        let arr: char[] = new char[end];
1408        let padLen: int = end - this.getLength();
1409        for (let i: int = 0; i < this.getLength(); i++) {
1410            arr[padLen + i] = this.charAt(i);
1411        }
1412        let strLength = str.getLength()
1413        for (let i = 0; i < strLength && i < padLen; i++) {
1414            arr[i] = str.charAt(i);
1415        }
1416        for (let i = strLength; i < padLen; i++) {
1417            arr[i] = arr[i - strLength];
1418        }
1419        return new String(arr);
1420    }
1421
1422    /**
1423     * The substr() method returns a portion of the string, starting at the specified
1424     * index and extending for a given number of characters afterwards.
1425     *
1426     * @param begin is index of the first character to include in the returned substring
1427     *
1428     * @param length is number of characters to extract
1429     *
1430     * @returns new string containing the specified part of the given string
1431     */
1432    public substr(begin: number, length?: Number): String {
1433        return this.substr(begin as int, asIntOrDefault(length, this.getLength()));
1434    }
1435
1436    public substr(begin: number, length: number): String {
1437        return this.substr(begin as int, length as int);
1438    }
1439
1440    /*
1441     * The substr() method returns a portion of the string, starting at the specified
1442     * index and extending for a given number of characters afterwards.
1443     */
1444    public substr(begin: int): String {
1445        return this.substring(begin);
1446    }
1447
1448    public substr(begin: int, length: int): String {
1449        begin = normalizeIndex(begin, this.getLength());
1450        let end = begin + length
1451        if (length > this.getLength() - begin) {
1452            end = this.getLength()
1453        }
1454        if (end <= begin) {
1455            return "";
1456        }
1457        return this.substring(begin, end);
1458    }
1459
1460    /*
1461     * The trimEnd() method removes whitespace from the end of a string and returns a new string,
1462     * without modifying the original string. trimRight() is an alias of this method.
1463     */
1464    public trimEnd(): String {
1465        return this.trimRight()
1466    }
1467
1468    /*
1469     * The trimStart() method removes whitespace from the beginning of a string and returns a new string,
1470     * without modifying the original string. trimLeft() is an alias of this method.
1471     */
1472    public trimStart(): String {
1473        return this.trimLeft()
1474    }
1475
1476    /*
1477     * The slice() method extracts a section of a string and returns it as a new string,
1478     * without modifying the original string.
1479     */
1480    public slice(begin?: Number, end?: Number): String {
1481        return this.slice(asIntOrDefault(begin, 0), asIntOrDefault(end, this.getLength()))
1482    }
1483
1484    /*
1485     * The slice() method extracts a section of a string and returns it as a new string,
1486     * without modifying the original string.
1487     */
1488    public slice(begin: int): String {
1489        return this.substring(normalizeIndex(begin, this.getLength()))
1490    }
1491
1492    //public slice(begin: number, end: number): String {
1493    //    return this.slice(begin as int, end as int)
1494    //}
1495
1496    public slice(begin: int, end: int): String {
1497        begin = normalizeIndex(begin, this.getLength())
1498        end = normalizeIndex(end, this.getLength())
1499        if (end <= begin) {
1500            return "";
1501        }
1502        return this.substring(begin, end)
1503    }
1504
1505    //NOTE(kirill-mitkin): Replace namedCaptures type with record type when it will be possible
1506    static getSubstitution(matched: String, str: String, position: int, captures: String[], namedCaptures: Object | undefined, replacement: String) {
1507        let matchLength = matched.getLength()
1508        let stringLength = str.getLength()
1509        assert(position >= 0 && position <= stringLength)
1510        let tailPos = position + matchLength
1511        let m = captures.length
1512        let result = ""
1513        let doubleCapture = true;
1514        for (let i: int = 0; i < replacement.getLength();) {
1515            if (i + 1 < replacement.getLength()
1516                        && replacement.charAt(i) == c'$'
1517                        && replacement.charAt(i + 1) == c'$') {
1518                result += c'$'
1519                i += 2
1520            } else if (i + 1 < replacement.getLength()
1521                        && replacement.charAt(i) == c'$'
1522                        && replacement.charAt(i + 1) == c'&') {
1523                result += matched
1524                i += 2
1525            } else if (i + 1 < replacement.getLength()
1526                        && replacement.charAt(i) == c'$'
1527                        && replacement.charAt(i + 1) == c'`') {
1528                if (position != 0) {
1529                    result += str.substring(0, position)
1530                }
1531                i += 2
1532            } else if (i + 1 < replacement.getLength()
1533                        && replacement.charAt(i) == c'$'
1534                        && replacement.charAt(i + 1) == c'\'') {
1535                if (tailPos < stringLength) {
1536                    result += str.substring(tailPos, stringLength)
1537                }
1538                i += 2
1539            } else if (i + 2 < replacement.getLength()
1540                            && doubleCapture
1541                            && replacement.charAt(i) == c'$'
1542                            && Char.isDecDigit(replacement.charAt(i + 1))
1543                            && Char.isDecDigit(replacement.charAt(i + 2))) {
1544                let firstDigit = replacement.charAt(i + 1) - c'0';
1545                let secondDigit = replacement.charAt(i + 2) - c'0';
1546                let digit = firstDigit * 10 + secondDigit;
1547                if (digit == 0 || digit > m) {
1548                    doubleCapture = false;
1549                } else {
1550                    result += captures[digit - 1];
1551                    i += 3
1552                }
1553            } else if (i + 2 < replacement.getLength()
1554                            && replacement.charAt(i) == c'$'
1555                            && Char.isDecDigit(replacement.charAt(i + 1))
1556                            && !Char.isDecDigit(replacement.charAt(i + 2))
1557                        || i + 1 < replacement.getLength()
1558                            && replacement.charAt(i) == c'$'
1559                            && Char.isDecDigit(replacement.charAt(i + 1))) {
1560                let digit = replacement.charAt(i + 1) - c'0';
1561                if (digit == 0 || digit > m) {
1562                    result += replacement.substring(i, i + 2)
1563                } else {
1564                    result += captures[digit - 1];
1565                }
1566                doubleCapture = true;
1567                i += 2
1568            } else if (i + 1 < replacement.getLength()
1569                            && replacement.charAt(i) == c'$'
1570                            && replacement.charAt(i + 1) == c'<') {
1571                if (namedCaptures == undefined) {
1572                    result += "$<";
1573                    i += 2;
1574                } else {
1575                    let j = i + 2;
1576                    for (; j < replacement.getLength() && replacement.charAt(j) != c'>'; ++j) {}
1577                    if (j < replacement.getLength()) {
1578                        let groupName = replacement.substring(i + 2, j);
1579                        /*let capture = namedCaptures[groupName];
1580                        if (capture != undefined) {
1581                            result += capture
1582                        }
1583                        */
1584                        i = j;
1585                    } else {
1586                        result += "$<";
1587                        i += 2;
1588                    }
1589                }
1590            } else {
1591                result += replacement.charAt(i);
1592                i += 1;
1593            }
1594        }
1595        return result
1596    }
1597
1598    /**
1599     * Returns a new string with one, some, or all matches of a pattern replaced by a replacement
1600     *
1601     * @param searchValue is pattern which can be a String or RegExp
1602     *
1603     * @param replaceValue is replacement String
1604     *
1605     * @returns a new replaced string
1606     */
1607    public replace(searchValue: StringOrRegExp, replaceValue: String) : String {
1608        if (searchValue instanceof RegExp) {
1609            return (searchValue as RegExp).replace(this, replaceValue)
1610        }
1611        return this.replace(searchValue as String, replaceValue)
1612    }
1613
1614    public replace(searchValue: RegExp, replaceValue: String) : String {
1615        return searchValue.replace(this, replaceValue)
1616    }
1617
1618    public replace(searchValue: String, replaceValue: String) : String {
1619        let searchLength = searchValue.getLength()
1620        let position = this.indexOf(searchValue)
1621        if (position == -1) {
1622            return this
1623        }
1624        let preceding = this.substring(0, position)
1625        let following = this.substring(position + searchLength)
1626        let replacement = String.getSubstitution(searchValue, this, position as int, new String[0], undefined, replaceValue)
1627        return preceding + replacement + following
1628    }
1629
1630    /**
1631     * Returns a new string with one, some, or all matches of a pattern replaced by a replacement
1632     *
1633     * @param searchValue is pattern which can be a String or RegExp
1634     *
1635     * @param replacer is replacement function
1636     *
1637     * @returns a new replaced string
1638     */
1639    public replace(searchValue: StringOrRegExp, replacer: (substr: String, args: Object[]) => String): String {
1640        if (searchValue instanceof RegExp) {
1641            return (searchValue as RegExp).replace(this, replacer)
1642        }
1643        return this.replace(searchValue as String, replacer)
1644    }
1645
1646    public replace(searchValue: RegExp, replacer: (substr: String, args: Object[]) => String): String {
1647        return searchValue.replace(this, replacer)
1648    }
1649
1650    public replace(searchValue: String, replacer: (substr: String, args: Object[]) => String): String {
1651        let searchLength = searchValue.getLength()
1652        let position = this.indexOf(searchValue)
1653        if (position == -1) {
1654            return this
1655        }
1656        let preceding = this.substring(0, position)
1657        let following = this.substring(position + searchLength)
1658        let replacement = replacer(searchValue, [new Double(position as double) as Object, this as Object] as Object[])
1659        return preceding + replacement + following
1660    }
1661
1662     /**
1663     * Returns a new string with all matches of a pattern replaced by a replacement
1664     *
1665     * @param searchValue is pattern which can be a String or RegExp
1666     *
1667     * @param replaceValue is replacement String
1668     *
1669     * @returns a new replaced string
1670     */
1671    public replaceAll(searchValue: StringOrRegExp, replaceValue: String): String {
1672        if (searchValue instanceof RegExp) {
1673            const re = searchValue as RegExp
1674            if (!re.global) {
1675                throw new Error("Global flag expected for regexp")
1676            }
1677            return re.replace(this, replaceValue)
1678        }
1679        return this.replaceAll(searchValue as String, replaceValue)
1680    }
1681
1682    public replaceAll(searchValue: RegExp, replaceValue: String): String {
1683        if (!searchValue.global) {
1684            throw new Error("Global flag expected for regexp")
1685        }
1686        return searchValue.replace(this, replaceValue)
1687    }
1688
1689    public replaceAll(searchValue: String, replaceValue: String): String {
1690        let searchLength = searchValue.getLength()
1691        let advanceBy = max(1, searchLength)
1692        let matchPositions = new ArrayAsListInt();
1693        let position = this.indexOf(searchValue, 0)
1694        while (position != -1) {
1695            matchPositions.pushBack(position)
1696            if (position == this.getLength()) {
1697                break;
1698            }
1699            position = this.indexOf(searchValue, position + advanceBy)
1700        }
1701        let endOfLastMatch = 0
1702        let result = ""
1703        let arrayMatchPositions = matchPositions.toArray()
1704        for (let i = 0; i < arrayMatchPositions.length; ++i) {
1705            let p = arrayMatchPositions[i].unboxed()
1706            let preserved = this.substring(endOfLastMatch, p)
1707            let replacement = String.getSubstitution(searchValue, this, p, new String[0], undefined, replaceValue)
1708            result = result + preserved + replacement
1709            endOfLastMatch = p + searchLength
1710        }
1711        if (endOfLastMatch < this.getLength()) {
1712            result += this.substring(endOfLastMatch)
1713        }
1714        return result
1715    }
1716
1717    /**
1718     * Returns a new string with all matches of a pattern replaced by a replacement
1719     *
1720     * @param searchValue is pattern which can be a String or RegExp
1721     *
1722     * @param replacer is replacement function
1723     *
1724     * @returns a new replaced string
1725     */
1726    public replaceAll(searchValue: StringOrRegExp, replacer: (substr: String, args: Object[]) => String): String {
1727        if (searchValue instanceof RegExp) {
1728            const re = searchValue as RegExp
1729            if (!re.global) {
1730                throw new Error("Global flag expected for regexp")
1731            }
1732            return re.replace(this, replacer)
1733        }
1734        return this.replaceAll(searchValue as String, replacer)
1735    }
1736
1737    public replaceAll(searchValue: RegExp, replacer: (substr: String, args: Object[]) => String): String {
1738        if (!searchValue.global) {
1739            throw new Error("Global flag expected for regexp")
1740        }
1741        return searchValue.replace(this, replacer)
1742    }
1743
1744    public replaceAll(searchValue: String, replacer: (substr: String, args: Object[]) => String): String {
1745        let searchLength = searchValue.getLength()
1746        let advanceBy = max(1, searchLength)
1747        let matchPositions = new ArrayAsListInt();
1748        let position = this.indexOf(searchValue, 0)
1749        while (position != -1) {
1750            matchPositions.pushBack(position)
1751            position = this.indexOf(searchValue, position + advanceBy)
1752        }
1753        let endOfLastMatch = 0
1754        let result = ""
1755        let arrayMatchPositions = matchPositions.toArray()
1756        for (let i = 0; i < arrayMatchPositions.length; ++i) {
1757            let p = arrayMatchPositions[i].unboxed()
1758            let preserved = this.substring(endOfLastMatch, p)
1759            let args = new NullableObjectArray(2)
1760            args.pushBack(Double.valueOf(p))
1761            args.pushBack(this)
1762            let replacement = replacer(searchValue, args.toArray() as Object[])
1763            result = result + preserved + replacement
1764            endOfLastMatch = p + searchLength
1765        }
1766        if (endOfLastMatch < this.getLength()) {
1767            result += this.substring(endOfLastMatch)
1768        }
1769        return result
1770    }
1771
1772      /**
1773     * Executes a search for a match between a regular expression and this String object.
1774     *
1775     * @param regexp a regular expression object or implicit regular expression
1776     *
1777     * @returns the index of the first match between the regular expression and the given string,
1778     * or -1 if no match was found.
1779     */
1780    public search(regexp: StringOrRegExp): number {
1781        if (regexp instanceof String) {
1782            return new RegExp(regexp as String).search(this)
1783        }
1784        return (regexp as RegExp).search(this)
1785    }
1786
1787    public search(implicitRegExp: String): number {
1788        return new RegExp(implicitRegExp).search(this)
1789    }
1790
1791    public search(regexp: RegExp): number {
1792        return regexp.search(this)
1793    }
1794
1795    /*
1796     * The toLocaleLowerCase() method returns the calling string value converted to lower case,
1797     * according to any locale-specific case mappings.
1798     */
1799    public native toLocaleLowerCase(locale: String): String;
1800
1801    public toLocaleLowerCase(): String {
1802        return this.toLocaleLowerCase("");
1803    }
1804
1805    /*
1806     * The toLocaleUpperCase() method returns the calling string value converted to upper case,
1807     * according to any locale-specific case mappings.
1808     */
1809    public native toLocaleUpperCase(locale: String): String;
1810
1811    public toLocaleUpperCase(): String {
1812        return this.toLocaleUpperCase("");
1813    }
1814    /**
1815     * Retrieves the result of matching a string against a regular expression
1816     *
1817     * @param regexp a regular expression object
1818     *
1819     * @returns
1820     * If the regexp.global is true, all results matching the complete regular expression will be returned,
1821     * but capturing groups are not included
1822     * Otherwise, only the first complete match and its related capturing groups are returned
1823     */
1824    public match(regexp: StringOrRegExp): RegExpMatchArray | null {
1825        if (regexp instanceof String) {
1826            return new RegExp(regexp as String).match(this)
1827        }
1828        return (regexp as RegExp).match(this)
1829    }
1830
1831    public match(implicitRegExp: String): RegExpMatchArray | null {
1832        return new RegExp(implicitRegExp).match(this)
1833    }
1834
1835    public match(regexp: RegExp): RegExpMatchArray | null {
1836        return regexp.match(this)
1837    }
1838
1839
1840    /**
1841     * Returns an iterator of all results matching a string against a regular expression,
1842     * including capturing groups
1843     *
1844     * @param regexp a regular expression object
1845     */
1846    public matchAll(reg: RegExp): IterableIterator<RegExpMatchArray> {
1847        let flags = reg.flags;
1848        if (!reg.global) {
1849            throw new Error("matchAll must be called with a global RegExp")
1850        }
1851        return reg.matchAll(this)
1852    }
1853
1854    public native normalizeNFC(): String;
1855
1856    public native normalizeNFD(): String;
1857
1858    public native normalizeNFKC(): String;
1859
1860    public native normalizeNFKD(): String;
1861
1862    /**
1863     * The normalize() method of String values returns the Unicode Normalization Form of this string
1864     *
1865     * @param form is "NFC" or "NFD" or "NFKC" or "NFKD"
1866     *
1867     * @throws RangeError if form is not "NFC" or "NFD" or "NFKC" or "NFKD"
1868     *
1869     * @returns the Unicode Normalization Form of the string
1870     */
1871    public normalize(form?: String): String {
1872        const f = (form == undefined) ? "NFC" : form
1873        switch (f) {
1874        case "NFC":
1875            return this.normalizeNFC()
1876        case "NFD":
1877            return this.normalizeNFD()
1878        case "NFKC":
1879            return this.normalizeNFKC()
1880        case "NFKD":
1881            return this.normalizeNFKD()
1882        default:
1883            throw new RangeError("The normalization form should be one of NFC, NFD, NFKC, NFKD.")
1884        }
1885    }
1886
1887    /*
1888     * The toWellFormed() method of String values returns a string where all lone surrogates of
1889     * this string are replaced with the Unicode replacement character U+FFFD.
1890     */
1891    public toWellFormed(): String {
1892        return this.normalize()
1893    }
1894
1895    private static native codePointToChar(cp: int): int
1896
1897    /**
1898     * The String.fromCodePoint() static method returns a string created by using the specified sequence of code points
1899     *
1900     * @param codePoints are integers between 0 and 0x10FFFF (inclusive) representing a Unicode code point
1901     *
1902     * @throws RangeError if codePoints[i] is less than 0, or is greater than 0x10FFFF
1903     *
1904     * @returns string created by using the specified sequence of code points
1905     */
1906    public static fromCodePoint(...codePoints: number[]): String {
1907        let res = new StringBuilder();
1908        for (const cp of codePoints) {
1909            if (cp < 0 || cp > MAX_CODE_POINT || isNaN(cp) || !Number.isInteger(cp)) {
1910                throw new RangeError("Invalid code point: " + new Number(cp).toString())
1911            }
1912            let chrs = String.codePointToChar(cp as int);
1913            res.append((chrs & 0xffff) as char)
1914            chrs = (chrs >> 16) & 0xffff
1915            if (chrs > 0) {
1916                res.append(chrs as char);
1917            }
1918        }
1919        return res.toString()
1920    }
1921
1922    /*
1923     * The isWellFormed() method of String values returns a boolean indicating whether this string contains any lone surrogates.
1924     */
1925    public native isWellFormed(): boolean;
1926
1927    /**
1928     * Creates a String instance based on JSONValue
1929     *
1930     * @param json: JSONValue - a JSON representation
1931     *
1932     * @throws JSONTypeError if json does not encode a valid String
1933     *
1934     * @returns String - string value decoded from JSON
1935     */
1936    static createFromJSONValue(json: JSONValue): String {
1937        if (json instanceof JSONString) {
1938            return (json as JSONString).value
1939        }
1940        throw new JSONTypeError("Cannot create String from JSON", new ErrorOptions(json as Object))
1941    }
1942
1943    /**
1944     * Check if a string is compressed
1945     *
1946     * @param s string to be checked
1947     *
1948     * @returns true - if s is compressed, false - otherwise
1949     */
1950    public native isCompressed(): boolean;
1951
1952    public override $_iterator(): IterableIterator<String> {
1953        return new StringIterator(this)
1954    }
1955}
1956
1957class StringIterator implements IterableIterator<string> {
1958    private s: String
1959    private idx: int = 0
1960
1961    constructor(s: String) {
1962        this.s = s
1963    }
1964
1965    public override next(): IteratorResult<String> {
1966        if (this.idx == this.s.getLength()) {
1967            return new IteratorResult<String>()
1968        }
1969        let highValue: char = this.s.charAt(this.idx);
1970        this.idx += 1
1971        if (!Char.isHighSurrogate(highValue) || this.idx == this.s.getLength()) {
1972            return new IteratorResult<String>(String.fromCharCode(highValue as number));
1973        }
1974        let lowValue: char = this.s.charAt(this.idx);
1975        if (!Char.isLowSurrogate(lowValue)) {
1976            return new IteratorResult<String>(String.fromCharCode(highValue as number));
1977        }
1978        this.idx += 1
1979        return new IteratorResult<String>(String.fromCharCode(highValue as number, lowValue as number));
1980    }
1981}
1982