/
string_patch.dart
1454 lines (1325 loc) · 48.3 KB
/
string_patch.dart
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
// part of "core_patch.dart";
const int _maxAscii = 0x7f;
const int _maxLatin1 = 0xff;
const int _maxUtf16 = 0xffff;
const int _maxUnicode = 0x10ffff;
@patch
class String {
@patch
factory String.fromCharCodes(Iterable<int> charCodes,
[int start = 0, int? end]) {
// TODO: Remove these null checks once all code is opted into strong nonnullable mode.
if (charCodes == null) throw new ArgumentError.notNull("charCodes");
if (start == null) throw new ArgumentError.notNull("start");
return _StringBase.createFromCharCodes(charCodes, start, end, null);
}
@patch
factory String.fromCharCode(int charCode) {
if (charCode >= 0) {
if (charCode <= 0xff) {
return _OneByteString._allocate(1).._setAt(0, charCode);
}
if (charCode <= 0xffff) {
return _StringBase._createFromCodePoints(
new _List(1)..[0] = charCode, 0, 1);
}
if (charCode <= 0x10ffff) {
var low = 0xDC00 | (charCode & 0x3ff);
int bits = charCode - 0x10000;
var high = 0xD800 | (bits >> 10);
return _StringBase._createFromCodePoints(
new _List(2)
..[0] = high
..[1] = low,
0,
2);
}
}
throw new RangeError.range(charCode, 0, 0x10ffff);
}
@patch
@pragma("vm:external-name", "String_fromEnvironment")
external const factory String.fromEnvironment(String name,
{String defaultValue = ""});
bool get _isOneByte;
String _substringUnchecked(int startIndex, int endIndex);
}
/**
* [_StringBase] contains common methods used by concrete String
* implementations, e.g., _OneByteString.
*/
abstract class _StringBase implements String {
bool _isWhitespace(int codeUnit);
// Constants used by replaceAll encoding of string slices between matches.
// A string slice (start+length) is encoded in a single Smi to save memory
// overhead in the common case.
// We use fewer bits for length (11 bits) than for the start index (19+ bits).
// For long strings, it's possible to have many large indices,
// but it's unlikely to have many long lengths since slices don't overlap.
// If there are few matches in a long string, then there are few long slices,
// and if there are many matches, there'll likely be many short slices.
//
// Encoding is: 0((start << _lengthBits) | length)
// Number of bits used by length.
// This is the shift used to encode and decode the start index.
static const int _lengthBits = 11;
// The maximal allowed length value in an encoded slice.
static const int _maxLengthValue = (1 << _lengthBits) - 1;
// Mask of length in encoded smi value.
static const int _lengthMask = _maxLengthValue;
static const int _startBits = _maxUnsignedSmiBits - _lengthBits;
// Maximal allowed start index value in an encoded slice.
static const int _maxStartValue = (1 << _startBits) - 1;
// We pick 30 as a safe lower bound on available bits in a negative smi.
// TODO(lrn): Consider allowing more bits for start on 64-bit systems.
static const int _maxUnsignedSmiBits = 30;
// For longer strings, calling into C++ to create the result of a
// [replaceAll] is faster than [_joinReplaceAllOneByteResult].
// TODO(lrn): See if this limit can be tweaked.
static const int _maxJoinReplaceOneByteStringLength = 500;
factory _StringBase._uninstantiable() {
throw new UnsupportedError("_StringBase can't be instaniated");
}
@pragma("vm:recognized", "asm-intrinsic")
@pragma("vm:exact-result-type", "dart:core#_Smi")
@pragma("vm:external-name", "String_getHashCode")
external int get hashCode;
@pragma("vm:recognized", "asm-intrinsic")
@pragma("vm:exact-result-type", "dart:core#_Smi")
@pragma("vm:external-name", "String_getHashCode")
external int get _identityHashCode;
bool get _isOneByte {
// Alternatively return false and override it on one-byte string classes.
int id = ClassID.getID(this);
return id == ClassID.cidOneByteString ||
id == ClassID.cidExternalOneByteString;
}
/**
* Create the most efficient string representation for specified
* [charCodes].
*
* Only uses the character codes between index [start] and index [end] of
* `charCodes`. They must satisfy `0 <= start <= end <= charCodes.length`.
*
* The [limit] is an upper limit on the character codes in the iterable.
* It's `null` if unknown.
*/
static String createFromCharCodes(
Iterable<int> charCodes, int start, int? end, int? limit) {
// TODO(srdjan): Also skip copying of wide typed arrays.
final ccid = ClassID.getID(charCodes);
if ((ccid != ClassID.cidArray) &&
(ccid != ClassID.cidGrowableObjectArray) &&
(ccid != ClassID.cidImmutableArray)) {
if (charCodes is Uint8List) {
final actualEnd =
RangeError.checkValidRange(start, end, charCodes.length);
return _createOneByteString(charCodes, start, actualEnd - start);
} else if (charCodes is! Uint16List) {
return _createStringFromIterable(charCodes, start, end);
}
}
final int codeCount = charCodes.length;
final actualEnd = RangeError.checkValidRange(start, end, codeCount);
final len = actualEnd - start;
if (len == 0) return "";
final typedCharCodes = unsafeCast<List<int>>(charCodes);
final int actualLimit =
limit ?? _scanCodeUnits(typedCharCodes, start, actualEnd);
if (actualLimit < 0) {
throw new ArgumentError(typedCharCodes);
}
if (actualLimit <= _maxLatin1) {
return _createOneByteString(typedCharCodes, start, len);
}
if (actualLimit <= _maxUtf16) {
return _TwoByteString._allocateFromTwoByteList(
typedCharCodes, start, actualEnd);
}
// TODO(lrn): Consider passing limit to _createFromCodePoints, because
// the function is currently fully generic and doesn't know that its
// charCodes are not all Latin-1 or Utf-16.
return _createFromCodePoints(typedCharCodes, start, actualEnd);
}
static int _scanCodeUnits(List<int> charCodes, int start, int end) {
int bits = 0;
for (int i = start; i < end; i++) {
int code = charCodes[i];
if (code is! _Smi) throw new ArgumentError(charCodes);
bits |= code;
}
return bits;
}
static String _createStringFromIterable(
Iterable<int> charCodes, int start, int? end) {
// Treat charCodes as Iterable.
if (charCodes is EfficientLengthIterable) {
int length = charCodes.length;
final endVal = RangeError.checkValidRange(start, end, length);
final charCodeList = new List<int>.from(
charCodes.take(endVal).skip(start),
growable: false);
return createFromCharCodes(charCodeList, 0, charCodeList.length, null);
}
// Don't know length of iterable, so iterate and see if all the values
// are there.
if (start < 0) throw new RangeError.range(start, 0, charCodes.length);
var it = charCodes.iterator;
for (int i = 0; i < start; i++) {
if (!it.moveNext()) {
throw new RangeError.range(start, 0, i);
}
}
List<int> charCodeList;
int bits = 0; // Bitwise-or of all char codes in list.
final endVal = end;
if (endVal == null) {
var list = <int>[];
while (it.moveNext()) {
int code = it.current;
bits |= code;
list.add(code);
}
charCodeList = makeListFixedLength<int>(list);
} else {
if (endVal < start) {
throw new RangeError.range(endVal, start, charCodes.length);
}
int len = endVal - start;
charCodeList = new List<int>.generate(len, (int i) {
if (!it.moveNext()) {
throw new RangeError.range(endVal, start, start + i);
}
int code = it.current;
bits |= code;
return code;
});
}
int length = charCodeList.length;
if (bits < 0) {
throw new ArgumentError(charCodes);
}
bool isOneByteString = (bits <= _maxLatin1);
if (isOneByteString) {
return _createOneByteString(charCodeList, 0, length);
}
return createFromCharCodes(charCodeList, 0, length, bits);
}
// Inlining is disabled as a workaround to http://dartbug.com/37800.
@pragma("vm:never-inline")
static String _createOneByteString(List<int> charCodes, int start, int len) {
// It's always faster to do this in Dart than to call into the runtime.
var s = _OneByteString._allocate(len);
// Special case for native Uint8 typed arrays.
final int cid = ClassID.getID(charCodes);
if (cid == ClassID.cidUint8ArrayView ||
cid == ClassID.cidUint8Array ||
cid == ClassID.cidExternalUint8Array) {
Uint8List bytes = unsafeCast<Uint8List>(charCodes);
copyRangeFromUint8ListToOneByteString(bytes, s, start, 0, len);
return s;
}
// Fall through to normal case.
for (int i = 0; i < len; i++) {
s._setAt(i, charCodes[start + i]);
}
return s;
}
@pragma("vm:external-name", "StringBase_createFromCodePoints")
external static String _createFromCodePoints(
List<int> codePoints, int start, int end);
@pragma("vm:recognized", "asm-intrinsic")
@pragma("vm:external-name", "String_charAt")
external String operator [](int index);
int codeUnitAt(int index); // Implemented in the subclasses.
@pragma("vm:recognized", "graph-intrinsic")
@pragma("vm:exact-result-type", "dart:core#_Smi")
@pragma("vm:prefer-inline")
@pragma("vm:external-name", "String_getLength")
external int get length;
@pragma("vm:recognized", "asm-intrinsic")
@pragma("vm:exact-result-type", bool)
bool get isEmpty {
return this.length == 0;
}
bool get isNotEmpty => !isEmpty;
@pragma("vm:external-name", "String_concat")
external String operator +(String other);
String toString() {
return this;
}
@pragma("vm:exact-result-type", bool)
bool operator ==(Object other) {
if (identical(this, other)) {
return true;
}
if (other is String && this.length == other.length) {
final len = this.length;
for (int i = 0; i < len; i++) {
if (this.codeUnitAt(i) != other.codeUnitAt(i)) {
return false;
}
}
return true;
}
return false;
}
int compareTo(String other) {
int thisLength = this.length;
int otherLength = other.length;
int len = (thisLength < otherLength) ? thisLength : otherLength;
for (int i = 0; i < len; i++) {
int thisCodeUnit = this.codeUnitAt(i);
int otherCodeUnit = other.codeUnitAt(i);
if (thisCodeUnit < otherCodeUnit) {
return -1;
}
if (thisCodeUnit > otherCodeUnit) {
return 1;
}
}
if (thisLength < otherLength) return -1;
if (thisLength > otherLength) return 1;
return 0;
}
@pragma("vm:recognized", "asm-intrinsic")
@pragma("vm:exact-result-type", bool)
bool _substringMatches(int start, String other) {
if (other.isEmpty) return true;
final len = other.length;
if ((start < 0) || (start + len > this.length)) {
return false;
}
for (int i = 0; i < len; i++) {
if (this.codeUnitAt(i + start) != other.codeUnitAt(i)) {
return false;
}
}
return true;
}
bool endsWith(String other) {
return _substringMatches(this.length - other.length, other);
}
bool startsWith(Pattern pattern, [int index = 0]) {
if ((index < 0) || (index > this.length)) {
throw new RangeError.range(index, 0, this.length);
}
if (pattern is String) {
return _substringMatches(index, pattern);
}
return pattern.matchAsPrefix(this, index) != null;
}
int indexOf(Pattern pattern, [int start = 0]) {
if ((start < 0) || (start > this.length)) {
throw new RangeError.range(start, 0, this.length, "start");
}
if (pattern is String) {
String other = pattern;
int maxIndex = this.length - other.length;
// TODO: Use an efficient string search (e.g. BMH).
for (int index = start; index <= maxIndex; index++) {
if (_substringMatches(index, other)) {
return index;
}
}
return -1;
}
for (int i = start; i <= this.length; i++) {
// TODO(11276); This has quadratic behavior because matchAsPrefix tries
// to find a later match too. Optimize matchAsPrefix to avoid this.
if (pattern.matchAsPrefix(this, i) != null) return i;
}
return -1;
}
int lastIndexOf(Pattern pattern, [int? start]) {
if (start == null) {
start = this.length;
} else if (start < 0 || start > this.length) {
throw new RangeError.range(start, 0, this.length);
}
if (pattern is String) {
String other = pattern;
int maxIndex = this.length - other.length;
if (maxIndex < start) start = maxIndex;
for (int index = start; index >= 0; index--) {
if (_substringMatches(index, other)) {
return index;
}
}
return -1;
}
for (int i = start; i >= 0; i--) {
// TODO(11276); This has quadratic behavior because matchAsPrefix tries
// to find a later match too. Optimize matchAsPrefix to avoid this.
if (pattern.matchAsPrefix(this, i) != null) return i;
}
return -1;
}
String substring(int startIndex, [int? endIndex]) {
endIndex = RangeError.checkValidRange(startIndex, endIndex, this.length);
return _substringUnchecked(startIndex, endIndex);
}
String _substringUnchecked(int startIndex, int endIndex) {
assert(endIndex != null);
assert((startIndex >= 0) && (startIndex <= this.length));
assert((endIndex >= 0) && (endIndex <= this.length));
assert(startIndex <= endIndex);
if (startIndex == endIndex) {
return "";
}
if ((startIndex == 0) && (endIndex == this.length)) {
return this;
}
if ((startIndex + 1) == endIndex) {
return this[startIndex];
}
return _substringUncheckedNative(startIndex, endIndex);
}
@pragma("vm:external-name", "StringBase_substringUnchecked")
external String _substringUncheckedNative(int startIndex, int endIndex);
// Checks for one-byte whitespaces only.
static bool _isOneByteWhitespace(int codeUnit) {
if (codeUnit <= 32) {
return ((codeUnit == 32) || // Space.
((codeUnit <= 13) && (codeUnit >= 9))); // CR, LF, TAB, etc.
}
return (codeUnit == 0x85) || (codeUnit == 0xA0); // NEL, NBSP.
}
// Characters with Whitespace property (Unicode 6.3).
// 0009..000D ; White_Space # Cc <control-0009>..<control-000D>
// 0020 ; White_Space # Zs SPACE
// 0085 ; White_Space # Cc <control-0085>
// 00A0 ; White_Space # Zs NO-BREAK SPACE
// 1680 ; White_Space # Zs OGHAM SPACE MARK
// 2000..200A ; White_Space # Zs EN QUAD..HAIR SPACE
// 2028 ; White_Space # Zl LINE SEPARATOR
// 2029 ; White_Space # Zp PARAGRAPH SEPARATOR
// 202F ; White_Space # Zs NARROW NO-BREAK SPACE
// 205F ; White_Space # Zs MEDIUM MATHEMATICAL SPACE
// 3000 ; White_Space # Zs IDEOGRAPHIC SPACE
//
// BOM: 0xFEFF
static bool _isTwoByteWhitespace(int codeUnit) {
if (codeUnit <= 32) {
return (codeUnit == 32) || ((codeUnit <= 13) && (codeUnit >= 9));
}
if (codeUnit < 0x85) return false;
if ((codeUnit == 0x85) || (codeUnit == 0xA0)) return true;
return (codeUnit <= 0x200A)
? ((codeUnit == 0x1680) || (0x2000 <= codeUnit))
: ((codeUnit == 0x2028) ||
(codeUnit == 0x2029) ||
(codeUnit == 0x202F) ||
(codeUnit == 0x205F) ||
(codeUnit == 0x3000) ||
(codeUnit == 0xFEFF));
}
int _firstNonWhitespace() {
final len = this.length;
int first = 0;
for (; first < len; first++) {
if (!_isWhitespace(this.codeUnitAt(first))) {
break;
}
}
return first;
}
int _lastNonWhitespace() {
int last = this.length - 1;
for (; last >= 0; last--) {
if (!_isWhitespace(this.codeUnitAt(last))) {
break;
}
}
return last;
}
String trim() {
final len = this.length;
int first = _firstNonWhitespace();
if (len == first) {
// String contains only whitespaces.
return "";
}
int last = _lastNonWhitespace() + 1;
if ((first == 0) && (last == len)) {
// Returns this string since it does not have leading or trailing
// whitespaces.
return this;
}
return _substringUnchecked(first, last);
}
String trimLeft() {
final len = this.length;
int first = 0;
for (; first < len; first++) {
if (!_isWhitespace(this.codeUnitAt(first))) {
break;
}
}
if (len == first) {
// String contains only whitespaces.
return "";
}
if (first == 0) {
// Returns this string since it does not have leading or trailing
// whitespaces.
return this;
}
return _substringUnchecked(first, len);
}
String trimRight() {
final len = this.length;
int last = len - 1;
for (; last >= 0; last--) {
if (!_isWhitespace(this.codeUnitAt(last))) {
break;
}
}
if (last == -1) {
// String contains only whitespaces.
return "";
}
if (last == (len - 1)) {
// Returns this string since it does not have trailing whitespaces.
return this;
}
return _substringUnchecked(0, last + 1);
}
String operator *(int times) {
if (times <= 0) return "";
if (times == 1) return this;
StringBuffer buffer = new StringBuffer(this);
for (int i = 1; i < times; i++) {
buffer.write(this);
}
return buffer.toString();
}
String padLeft(int width, [String padding = ' ']) {
int delta = width - this.length;
if (delta <= 0) return this;
StringBuffer buffer = new StringBuffer();
for (int i = 0; i < delta; i++) {
buffer.write(padding);
}
buffer.write(this);
return buffer.toString();
}
String padRight(int width, [String padding = ' ']) {
int delta = width - this.length;
if (delta <= 0) return this;
StringBuffer buffer = new StringBuffer(this);
for (int i = 0; i < delta; i++) {
buffer.write(padding);
}
return buffer.toString();
}
bool contains(Pattern pattern, [int startIndex = 0]) {
if (pattern is String) {
if (startIndex < 0 || startIndex > this.length) {
throw new RangeError.range(startIndex, 0, this.length);
}
return indexOf(pattern, startIndex) >= 0;
}
return pattern.allMatches(this.substring(startIndex)).isNotEmpty;
}
String replaceFirst(Pattern pattern, String replacement,
[int startIndex = 0]) {
// TODO: Remove these null checks once all code is opted into strong nonnullable mode.
if (pattern == null) {
throw new ArgumentError.notNull("pattern");
}
if (replacement == null) {
throw new ArgumentError.notNull("replacement");
}
if (startIndex == null) {
throw new ArgumentError.notNull("startIndex");
}
RangeError.checkValueInInterval(startIndex, 0, this.length, "startIndex");
Iterator iterator = startIndex == 0
? pattern.allMatches(this).iterator
: pattern.allMatches(this, startIndex).iterator;
if (!iterator.moveNext()) return this;
Match match = iterator.current;
return replaceRange(match.start, match.end, replacement);
}
String replaceRange(int start, int? end, String replacement) {
final length = this.length;
final localEnd = RangeError.checkValidRange(start, end, length);
bool replacementIsOneByte = replacement._isOneByte;
if (start == 0 && localEnd == length) return replacement;
int replacementLength = replacement.length;
int totalLength = start + (length - localEnd) + replacementLength;
if (replacementIsOneByte && this._isOneByte) {
var result = _OneByteString._allocate(totalLength);
int index = 0;
index = result._setRange(index, this, 0, start);
index = result._setRange(start, replacement, 0, replacementLength);
result._setRange(index, this, localEnd, length);
return result;
}
List slices = [];
_addReplaceSlice(slices, 0, start);
if (replacement.length > 0) slices.add(replacement);
_addReplaceSlice(slices, localEnd, length);
return _joinReplaceAllResult(
this, slices, totalLength, replacementIsOneByte);
}
static int _addReplaceSlice(List matches, int start, int end) {
int length = end - start;
if (length > 0) {
if (length <= _maxLengthValue && start <= _maxStartValue) {
matches.add(-((start << _lengthBits) | length));
} else {
matches.add(start);
matches.add(end);
}
}
return length;
}
String replaceAll(Pattern pattern, String replacement) {
if (pattern == null) throw new ArgumentError.notNull("pattern");
if (replacement == null) throw new ArgumentError.notNull("replacement");
int startIndex = 0;
// String fragments that replace the prefix [this] up to [startIndex].
List matches = [];
int length = 0; // Length of all fragments.
int replacementLength = replacement.length;
if (replacementLength == 0) {
for (Match match in pattern.allMatches(this)) {
length += _addReplaceSlice(matches, startIndex, match.start);
startIndex = match.end;
}
} else {
for (Match match in pattern.allMatches(this)) {
length += _addReplaceSlice(matches, startIndex, match.start);
matches.add(replacement);
length += replacementLength;
startIndex = match.end;
}
}
// No match, or a zero-length match at start with zero-length replacement.
if (startIndex == 0 && length == 0) return this;
length += _addReplaceSlice(matches, startIndex, this.length);
bool replacementIsOneByte = replacement._isOneByte;
if (replacementIsOneByte &&
length < _maxJoinReplaceOneByteStringLength &&
this._isOneByte) {
// TODO(lrn): Is there a cut-off point, or is runtime always faster?
return _joinReplaceAllOneByteResult(this, matches, length);
}
return _joinReplaceAllResult(this, matches, length, replacementIsOneByte);
}
/**
* As [_joinReplaceAllResult], but knowing that the result
* is always a [_OneByteString].
*/
static String _joinReplaceAllOneByteResult(
String base, List matches, int length) {
_OneByteString result = _OneByteString._allocate(length);
int writeIndex = 0;
for (int i = 0; i < matches.length; i++) {
var entry = matches[i];
if (entry is _Smi) {
int sliceStart = entry;
int sliceEnd;
if (sliceStart < 0) {
int bits = -sliceStart;
int sliceLength = bits & _lengthMask;
sliceStart = bits >> _lengthBits;
sliceEnd = sliceStart + sliceLength;
} else {
i++;
// This function should only be called with valid matches lists.
// If the list is short, or sliceEnd is not an integer, one of
// the next few lines will throw anyway.
assert(i < matches.length);
sliceEnd = matches[i];
}
for (int j = sliceStart; j < sliceEnd; j++) {
result._setAt(writeIndex++, base.codeUnitAt(j));
}
} else {
// Replacement is a one-byte string.
String replacement = entry;
for (int j = 0; j < replacement.length; j++) {
result._setAt(writeIndex++, replacement.codeUnitAt(j));
}
}
}
assert(writeIndex == length);
return result;
}
/**
* Combine the results of a [replaceAll] match into a new string.
*
* The [matches] lists contains Smi index pairs representing slices of
* [base] and [String]s to be put in between the slices.
*
* The total [length] of the resulting string is known, as is
* whether the replacement strings are one-byte strings.
* If they are, then we have to check the base string slices to know
* whether the result must be a one-byte string.
*/
@pragma("vm:external-name", "StringBase_joinReplaceAllResult")
external static String _joinReplaceAllResult(
String base, List matches, int length, bool replacementStringsAreOneByte);
String replaceAllMapped(Pattern pattern, String replace(Match match)) {
if (pattern == null) throw new ArgumentError.notNull("pattern");
if (replace == null) throw new ArgumentError.notNull("replace");
List matches = [];
int length = 0;
int startIndex = 0;
bool replacementStringsAreOneByte = true;
for (Match match in pattern.allMatches(this)) {
length += _addReplaceSlice(matches, startIndex, match.start);
var replacement = "${replace(match)}";
matches.add(replacement);
length += replacement.length;
replacementStringsAreOneByte =
replacementStringsAreOneByte && replacement._isOneByte;
startIndex = match.end;
}
if (matches.isEmpty) return this;
length += _addReplaceSlice(matches, startIndex, this.length);
if (replacementStringsAreOneByte &&
length < _maxJoinReplaceOneByteStringLength &&
this._isOneByte) {
return _joinReplaceAllOneByteResult(this, matches, length);
}
return _joinReplaceAllResult(
this, matches, length, replacementStringsAreOneByte);
}
String replaceFirstMapped(Pattern pattern, String replace(Match match),
[int startIndex = 0]) {
if (pattern == null) throw new ArgumentError.notNull("pattern");
if (replace == null) throw new ArgumentError.notNull("replace");
if (startIndex == null) throw new ArgumentError.notNull("startIndex");
RangeError.checkValueInInterval(startIndex, 0, this.length, "startIndex");
var matches = pattern.allMatches(this, startIndex).iterator;
if (!matches.moveNext()) return this;
var match = matches.current;
var replacement = "${replace(match)}";
return replaceRange(match.start, match.end, replacement);
}
static String _matchString(Match match) => match[0]!;
static String _stringIdentity(String string) => string;
String _splitMapJoinEmptyString(
String onMatch(Match match), String onNonMatch(String nonMatch)) {
// Pattern is the empty string.
StringBuffer buffer = new StringBuffer();
int length = this.length;
int i = 0;
buffer.write(onNonMatch(""));
while (i < length) {
buffer.write(onMatch(new _StringMatch(i, this, "")));
// Special case to avoid splitting a surrogate pair.
int code = this.codeUnitAt(i);
if ((code & ~0x3FF) == 0xD800 && length > i + 1) {
// Leading surrogate;
code = this.codeUnitAt(i + 1);
if ((code & ~0x3FF) == 0xDC00) {
// Matching trailing surrogate.
buffer.write(onNonMatch(this.substring(i, i + 2)));
i += 2;
continue;
}
}
buffer.write(onNonMatch(this[i]));
i++;
}
buffer.write(onMatch(new _StringMatch(i, this, "")));
buffer.write(onNonMatch(""));
return buffer.toString();
}
String splitMapJoin(Pattern pattern,
{String onMatch(Match match)?, String onNonMatch(String nonMatch)?}) {
if (pattern == null) {
throw new ArgumentError.notNull("pattern");
}
onMatch ??= _matchString;
onNonMatch ??= _stringIdentity;
if (pattern is String) {
String stringPattern = pattern;
if (stringPattern.isEmpty) {
return _splitMapJoinEmptyString(onMatch, onNonMatch);
}
}
StringBuffer buffer = new StringBuffer();
int startIndex = 0;
for (Match match in pattern.allMatches(this)) {
buffer.write(onNonMatch(this.substring(startIndex, match.start)));
buffer.write(onMatch(match).toString());
startIndex = match.end;
}
buffer.write(onNonMatch(this.substring(startIndex)));
return buffer.toString();
}
// Convert single object to string.
@pragma("vm:entry-point", "call")
static String _interpolateSingle(Object? o) {
if (o is String) return o;
final s = o.toString();
// TODO(40614): Remove once non-nullability is sound.
if (s is! String) {
throw _interpolationError(o, s);
}
return s;
}
/**
* Convert all objects in [values] to strings and concat them
* into a result string.
* Modifies the input list if it contains non-`String` values.
*/
@pragma("vm:recognized", "other")
@pragma("vm:entry-point", "call")
@pragma("vm:never-inline")
static String _interpolate(final List values) {
final numValues = values.length;
int totalLength = 0;
int i = 0;
while (i < numValues) {
final e = values[i];
final s = e.toString();
values[i] = s;
if (ClassID.getID(s) == ClassID.cidOneByteString) {
totalLength += s.length;
i++;
} else if (s is! String) {
// TODO(40614): Remove once non-nullability is sound.
throw _interpolationError(e, s);
} else {
// Handle remaining elements without checking for one-byte-ness.
while (++i < numValues) {
final e = values[i];
final s = e.toString();
values[i] = s;
// TODO(40614): Remove once non-nullability is sound.
if (s is! String) {
throw _interpolationError(e, s);
}
}
return _concatRangeNative(values, 0, numValues);
}
}
// All strings were one-byte strings.
return _OneByteString._concatAll(values, totalLength);
}
static ArgumentError _interpolationError(Object? o, Object? result) {
// Since Dart 2.0, [result] can only be null.
return new ArgumentError.value(
o, "object", "toString method returned 'null'");
}
Iterable<Match> allMatches(String string, [int start = 0]) {
if (start < 0 || start > string.length) {
throw new RangeError.range(start, 0, string.length, "start");
}
return new _StringAllMatchesIterable(string, this, start);
}
Match? matchAsPrefix(String string, [int start = 0]) {
if (start < 0 || start > string.length) {
throw new RangeError.range(start, 0, string.length);
}
if (start + this.length > string.length) return null;
for (int i = 0; i < this.length; i++) {
if (string.codeUnitAt(start + i) != this.codeUnitAt(i)) {
return null;
}
}
return new _StringMatch(start, string, this);
}
List<String> split(Pattern pattern) {
if ((pattern is String) && pattern.isEmpty) {
List<String> result =
new List<String>.generate(this.length, (int i) => this[i]);
return result;
}
int length = this.length;
Iterator iterator = pattern.allMatches(this).iterator;
if (length == 0 && iterator.moveNext()) {
// A matched empty string input returns the empty list.
return <String>[];
}
List<String> result = <String>[];
int startIndex = 0;
int previousIndex = 0;
// 'pattern' may not be implemented correctly and therefore we cannot
// call _substringUnhchecked unless it is a trustworthy type (e.g. String).
while (true) {
if (startIndex == length || !iterator.moveNext()) {
result.add(this.substring(previousIndex, length));
break;
}
Match match = iterator.current;
if (match.start == length) {
result.add(this.substring(previousIndex, length));
break;
}
int endIndex = match.end;
if (startIndex == endIndex && endIndex == previousIndex) {
++startIndex; // empty match, advance and restart
continue;
}
result.add(this.substring(previousIndex, match.start));
startIndex = previousIndex = endIndex;
}
return result;
}
List<int> get codeUnits => new CodeUnits(this);
Runes get runes => new Runes(this);
@pragma("vm:external-name", "String_toUpperCase")
external String toUpperCase();
@pragma("vm:external-name", "String_toLowerCase")
external String toLowerCase();
// Concatenate ['start', 'end'[ elements of 'strings'.
static String _concatRange(List<String> strings, int start, int end) {
if ((end - start) == 1) {
return strings[start];
}
return _concatRangeNative(strings, start, end);
}
// Call this method if all elements of [strings] are known to be strings
// but not all are known to be OneByteString(s).
@pragma("vm:external-name", "String_concatRange")
external static String _concatRangeNative(List strings, int start, int end);
}
@pragma("vm:entry-point")
class _OneByteString extends _StringBase {
factory _OneByteString._uninstantiable() {
throw "Unreachable";
}
@pragma("vm:recognized", "asm-intrinsic")
@pragma("vm:exact-result-type", "dart:core#_Smi")
@pragma("vm:external-name", "String_getHashCode")
external int get hashCode;
@pragma("vm:recognized", "graph-intrinsic")
@pragma("vm:exact-result-type", "dart:core#_Smi")
@pragma("vm:external-name", "String_codeUnitAt")
external int codeUnitAt(int index);
bool _isWhitespace(int codeUnit) {
return _StringBase._isOneByteWhitespace(codeUnit);
}
@pragma("vm:recognized", "asm-intrinsic")
@pragma("vm:exact-result-type", bool)
bool operator ==(Object other) {
return super == other;
}
@pragma("vm:recognized", "asm-intrinsic")
@pragma("vm:exact-result-type", _OneByteString)
@pragma("vm:external-name", "OneByteString_substringUnchecked")
external String _substringUncheckedNative(int startIndex, int endIndex);
List<String> _splitWithCharCode(int charCode) {
final parts = <String>[];
int i = 0;