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