Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(30)

Side by Side Diff: sdk/lib/_internal/compiler/js_lib/js_string.dart

Issue 1212513002: sdk files reorganization to make dart2js a proper package (Closed) Base URL: git@github.com:dart-lang/sdk.git@master
Patch Set: renamed Created 5 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
2 // for details. All rights reserved. Use of this source code is governed by a
3 // BSD-style license that can be found in the LICENSE file.
4
5 part of _interceptors;
6
7 /**
8 * The interceptor class for [String]. The compiler recognizes this
9 * class as an interceptor, and changes references to [:this:] to
10 * actually use the receiver of the method, which is generated as an extra
11 * argument added to each member.
12 */
13 class JSString extends Interceptor implements String, JSIndexable {
14 const JSString();
15
16 int codeUnitAt(int index) {
17 if (index is !int) throw diagnoseIndexError(this, index);
18 if (index < 0) throw diagnoseIndexError(this, index);
19 if (index >= length) throw diagnoseIndexError(this, index);
20 return JS('JSUInt31', r'#.charCodeAt(#)', this, index);
21 }
22
23 Iterable<Match> allMatches(String string, [int start = 0]) {
24 checkString(string);
25 checkInt(start);
26 if (0 > start || start > string.length) {
27 throw new RangeError.range(start, 0, string.length);
28 }
29 return allMatchesInStringUnchecked(this, string, start);
30 }
31
32 Match matchAsPrefix(String string, [int start = 0]) {
33 if (start < 0 || start > string.length) {
34 throw new RangeError.range(start, 0, string.length);
35 }
36 if (start + this.length > string.length) return null;
37 // TODO(lrn): See if this can be optimized.
38 for (int i = 0; i < this.length; i++) {
39 if (string.codeUnitAt(start + i) != this.codeUnitAt(i)) {
40 return null;
41 }
42 }
43 return new StringMatch(start, string, this);
44 }
45
46 String operator +(String other) {
47 if (other is !String) throw new ArgumentError.value(other);
48 return JS('String', r'# + #', this, other);
49 }
50
51 bool endsWith(String other) {
52 checkString(other);
53 int otherLength = other.length;
54 if (otherLength > length) return false;
55 return other == substring(length - otherLength);
56 }
57
58 String replaceAll(Pattern from, String to) {
59 checkString(to);
60 return stringReplaceAllUnchecked(this, from, to);
61 }
62
63 String replaceAllMapped(Pattern from, String convert(Match match)) {
64 return this.splitMapJoin(from, onMatch: convert);
65 }
66
67 String splitMapJoin(Pattern from,
68 {String onMatch(Match match),
69 String onNonMatch(String nonMatch)}) {
70 return stringReplaceAllFuncUnchecked(this, from, onMatch, onNonMatch);
71 }
72
73 String replaceFirst(Pattern from, String to, [int startIndex = 0]) {
74 checkString(to);
75 checkInt(startIndex);
76 RangeError.checkValueInInterval(startIndex, 0, this.length, "startIndex");
77 return stringReplaceFirstUnchecked(this, from, to, startIndex);
78 }
79
80 String replaceFirstMapped(Pattern from, String replace(Match match),
81 [int startIndex = 0]) {
82 checkNull(replace);
83 checkInt(startIndex);
84 RangeError.checkValueInInterval(startIndex, 0, this.length, "startIndex");
85 return stringReplaceFirstMappedUnchecked(this, from, replace, startIndex);
86 }
87
88 List<String> split(Pattern pattern) {
89 checkNull(pattern);
90 if (pattern is String) {
91 return JS('JSExtendableArray', r'#.split(#)', this, pattern);
92 } else if (pattern is JSSyntaxRegExp && regExpCaptureCount(pattern) == 0) {
93 var re = regExpGetNative(pattern);
94 return JS('JSExtendableArray', r'#.split(#)', this, re);
95 } else {
96 return _defaultSplit(pattern);
97 }
98 }
99
100 String replaceRange(int start, int end, String replacement) {
101 checkString(replacement);
102 checkInt(start);
103 end = RangeError.checkValidRange(start, end, this.length);
104 checkInt(end);
105 return stringReplaceRangeUnchecked(this, start, end, replacement);
106 }
107
108 List<String> _defaultSplit(Pattern pattern) {
109 List<String> result = <String>[];
110 // End of most recent match. That is, start of next part to add to result.
111 int start = 0;
112 // Length of most recent match.
113 // Set >0, so no match on the empty string causes the result to be [""].
114 int length = 1;
115 for (var match in pattern.allMatches(this)) {
116 int matchStart = match.start;
117 int matchEnd = match.end;
118 length = matchEnd - matchStart;
119 if (length == 0 && start == matchStart) {
120 // An empty match right after another match is ignored.
121 // This includes an empty match at the start of the string.
122 continue;
123 }
124 int end = matchStart;
125 result.add(this.substring(start, end));
126 start = matchEnd;
127 }
128 if (start < this.length || length > 0) {
129 // An empty match at the end of the string does not cause a "" at the end.
130 // A non-empty match ending at the end of the string does add a "".
131 result.add(this.substring(start));
132 }
133 return result;
134 }
135
136 bool startsWith(Pattern pattern, [int index = 0]) {
137 checkInt(index);
138 if (index < 0 || index > this.length) {
139 throw new RangeError.range(index, 0, this.length);
140 }
141 if (pattern is String) {
142 String other = pattern;
143 int otherLength = other.length;
144 int endIndex = index + otherLength;
145 if (endIndex > length) return false;
146 return other == JS('String', r'#.substring(#, #)', this, index, endIndex);
147 }
148 return pattern.matchAsPrefix(this, index) != null;
149 }
150
151 String substring(int startIndex, [int endIndex]) {
152 checkInt(startIndex);
153 if (endIndex == null) endIndex = length;
154 checkInt(endIndex);
155 if (startIndex < 0 ) throw new RangeError.value(startIndex);
156 if (startIndex > endIndex) throw new RangeError.value(startIndex);
157 if (endIndex > length) throw new RangeError.value(endIndex);
158 return JS('String', r'#.substring(#, #)', this, startIndex, endIndex);
159 }
160
161 String toLowerCase() {
162 return JS(
163 'returns:String;effects:none;depends:none;throws:null(1)',
164 r'#.toLowerCase()', this);
165 }
166
167 String toUpperCase() {
168 return JS(
169 'returns:String;effects:none;depends:none;throws:null(1)',
170 r'#.toUpperCase()', this);
171 }
172
173 // Characters with Whitespace property (Unicode 6.2).
174 // 0009..000D ; White_Space # Cc <control-0009>..<control-000D>
175 // 0020 ; White_Space # Zs SPACE
176 // 0085 ; White_Space # Cc <control-0085>
177 // 00A0 ; White_Space # Zs NO-BREAK SPACE
178 // 1680 ; White_Space # Zs OGHAM SPACE MARK
179 // 180E ; White_Space # Zs MONGOLIAN VOWEL SEPARATOR
180 // 2000..200A ; White_Space # Zs EN QUAD..HAIR SPACE
181 // 2028 ; White_Space # Zl LINE SEPARATOR
182 // 2029 ; White_Space # Zp PARAGRAPH SEPARATOR
183 // 202F ; White_Space # Zs NARROW NO-BREAK SPACE
184 // 205F ; White_Space # Zs MEDIUM MATHEMATICAL SPACE
185 // 3000 ; White_Space # Zs IDEOGRAPHIC SPACE
186 //
187 // BOM: 0xFEFF
188 static bool _isWhitespace(int codeUnit) {
189 // Most codeUnits should be less than 256. Special case with a smaller
190 // switch.
191 if (codeUnit < 256) {
192 switch (codeUnit) {
193 case 0x09:
194 case 0x0A:
195 case 0x0B:
196 case 0x0C:
197 case 0x0D:
198 case 0x20:
199 case 0x85:
200 case 0xA0:
201 return true;
202 default:
203 return false;
204 }
205 }
206 switch (codeUnit) {
207 case 0x1680:
208 case 0x180E:
209 case 0x2000:
210 case 0x2001:
211 case 0x2002:
212 case 0x2003:
213 case 0x2004:
214 case 0x2005:
215 case 0x2006:
216 case 0x2007:
217 case 0x2008:
218 case 0x2009:
219 case 0x200A:
220 case 0x2028:
221 case 0x2029:
222 case 0x202F:
223 case 0x205F:
224 case 0x3000:
225 case 0xFEFF:
226 return true;
227 default:
228 return false;
229 }
230 }
231
232 /// Finds the index of the first non-whitespace character, or the
233 /// end of the string. Start looking at position [index].
234 static int _skipLeadingWhitespace(String string, int index) {
235 const int SPACE = 0x20;
236 const int CARRIAGE_RETURN = 0x0D;
237 while (index < string.length) {
238 int codeUnit = string.codeUnitAt(index);
239 if (codeUnit != SPACE &&
240 codeUnit != CARRIAGE_RETURN &&
241 !_isWhitespace(codeUnit)) {
242 break;
243 }
244 index++;
245 }
246 return index;
247 }
248
249 /// Finds the index after the the last non-whitespace character, or 0.
250 /// Start looking at position [index - 1].
251 static int _skipTrailingWhitespace(String string, int index) {
252 const int SPACE = 0x20;
253 const int CARRIAGE_RETURN = 0x0D;
254 while (index > 0) {
255 int codeUnit = string.codeUnitAt(index - 1);
256 if (codeUnit != SPACE &&
257 codeUnit != CARRIAGE_RETURN &&
258 !_isWhitespace(codeUnit)) {
259 break;
260 }
261 index--;
262 }
263 return index;
264 }
265
266 // Dart2js can't use JavaScript trim directly,
267 // because JavaScript does not trim
268 // the NEXT LINE (NEL) character (0x85).
269 String trim() {
270 const int NEL = 0x85;
271
272 // Start by doing JS trim. Then check if it leaves a NEL at
273 // either end of the string.
274 String result = JS('String', '#.trim()', this);
275 if (result.length == 0) return result;
276 int firstCode = result.codeUnitAt(0);
277 int startIndex = 0;
278 if (firstCode == NEL) {
279 startIndex = _skipLeadingWhitespace(result, 1);
280 if (startIndex == result.length) return "";
281 }
282
283 int endIndex = result.length;
284 // We know that there is at least one character that is non-whitespace.
285 // Therefore we don't need to verify that endIndex > startIndex.
286 int lastCode = result.codeUnitAt(endIndex - 1);
287 if (lastCode == NEL) {
288 endIndex = _skipTrailingWhitespace(result, endIndex - 1);
289 }
290 if (startIndex == 0 && endIndex == result.length) return result;
291 return JS('String', r'#.substring(#, #)', result, startIndex, endIndex);
292 }
293
294 // Dart2js can't use JavaScript trimLeft directly,
295 // because it is not in ES5, so not every browser implements it,
296 // and because those that do will not trim the NEXT LINE character (0x85).
297 String trimLeft() {
298 const int NEL = 0x85;
299
300 // Start by doing JS trim. Then check if it leaves a NEL at
301 // the beginning of the string.
302 String result;
303 int startIndex = 0;
304 if (JS('bool', 'typeof #.trimLeft != "undefined"', this)) {
305 result = JS('String', '#.trimLeft()', this);
306 if (result.length == 0) return result;
307 int firstCode = result.codeUnitAt(0);
308 if (firstCode == NEL) {
309 startIndex = _skipLeadingWhitespace(result, 1);
310 }
311 } else {
312 result = this;
313 startIndex = _skipLeadingWhitespace(this, 0);
314 }
315 if (startIndex == 0) return result;
316 if (startIndex == result.length) return "";
317 return JS('String', r'#.substring(#)', result, startIndex);
318 }
319
320 // Dart2js can't use JavaScript trimRight directly,
321 // because it is not in ES5 and because JavaScript does not trim
322 // the NEXT LINE character (0x85).
323 String trimRight() {
324 const int NEL = 0x85;
325
326 // Start by doing JS trim. Then check if it leaves a NEL or BOM at
327 // the end of the string.
328 String result;
329 int endIndex;
330 // trimRight is implemented by Firefox and Chrome/Blink,
331 // so use it if it is there.
332 if (JS('bool', 'typeof #.trimRight != "undefined"', this)) {
333 result = JS('String', '#.trimRight()', this);
334 endIndex = result.length;
335 if (endIndex == 0) return result;
336 int lastCode = result.codeUnitAt(endIndex - 1);
337 if (lastCode == NEL) {
338 endIndex = _skipTrailingWhitespace(result, endIndex - 1);
339 }
340 } else {
341 result = this;
342 endIndex = _skipTrailingWhitespace(this, this.length);
343 }
344
345 if (endIndex == result.length) return result;
346 if (endIndex == 0) return "";
347 return JS('String', r'#.substring(#, #)', result, 0, endIndex);
348 }
349
350 String operator*(int times) {
351 if (0 >= times) return ''; // Unnecessary but hoists argument type check.
352 if (times == 1 || this.length == 0) return this;
353 if (times != JS('JSUInt32', '# >>> 0', times)) {
354 // times >= 2^32. We can't create a string that big.
355 throw const OutOfMemoryError();
356 }
357 var result = '';
358 var s = this;
359 while (true) {
360 if (times & 1 == 1) result = s + result;
361 times = JS('JSUInt31', '# >>> 1', times);
362 if (times == 0) break;
363 s += s;
364 }
365 return result;
366 }
367
368 String padLeft(int width, [String padding = ' ']) {
369 int delta = width - this.length;
370 if (delta <= 0) return this;
371 return padding * delta + this;
372 }
373
374 String padRight(int width, [String padding = ' ']) {
375 int delta = width - this.length;
376 if (delta <= 0) return this;
377 return this + padding * delta;
378 }
379
380 List<int> get codeUnits => new CodeUnits(this);
381
382 Runes get runes => new Runes(this);
383
384 int indexOf(Pattern pattern, [int start = 0]) {
385 checkNull(pattern);
386 if (start is! int) throw argumentErrorValue(start);
387 if (start < 0 || start > this.length) {
388 throw new RangeError.range(start, 0, this.length);
389 }
390 if (pattern is String) {
391 return stringIndexOfStringUnchecked(this, pattern, start);
392 }
393 if (pattern is JSSyntaxRegExp) {
394 JSSyntaxRegExp re = pattern;
395 Match match = firstMatchAfter(re, this, start);
396 return (match == null) ? -1 : match.start;
397 }
398 for (int i = start; i <= this.length; i++) {
399 if (pattern.matchAsPrefix(this, i) != null) return i;
400 }
401 return -1;
402 }
403
404 int lastIndexOf(Pattern pattern, [int start]) {
405 checkNull(pattern);
406 if (start == null) {
407 start = length;
408 } else if (start is! int) {
409 throw argumentErrorValue(start);
410 } else if (start < 0 || start > this.length) {
411 throw new RangeError.range(start, 0, this.length);
412 }
413 if (pattern is String) {
414 String other = pattern;
415 if (start + other.length > this.length) {
416 start = this.length - other.length;
417 }
418 return stringLastIndexOfUnchecked(this, other, start);
419 }
420 for (int i = start; i >= 0; i--) {
421 if (pattern.matchAsPrefix(this, i) != null) return i;
422 }
423 return -1;
424 }
425
426 bool contains(Pattern other, [int startIndex = 0]) {
427 checkNull(other);
428 if (startIndex < 0 || startIndex > this.length) {
429 throw new RangeError.range(startIndex, 0, this.length);
430 }
431 return stringContainsUnchecked(this, other, startIndex);
432 }
433
434 bool get isEmpty => length == 0;
435
436 bool get isNotEmpty => !isEmpty;
437
438 int compareTo(String other) {
439 if (other is !String) throw argumentErrorValue(other);
440 return this == other ? 0
441 : JS('bool', r'# < #', this, other) ? -1 : 1;
442 }
443
444 // Note: if you change this, also change the function [S].
445 String toString() => this;
446
447 /**
448 * This is the [Jenkins hash function][1] but using masking to keep
449 * values in SMI range.
450 *
451 * [1]: http://en.wikipedia.org/wiki/Jenkins_hash_function
452 */
453 int get hashCode {
454 // TODO(ahe): This method shouldn't have to use JS. Update when our
455 // optimizations are smarter.
456 int hash = 0;
457 for (int i = 0; i < length; i++) {
458 hash = 0x1fffffff & (hash + JS('int', r'#.charCodeAt(#)', this, i));
459 hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10));
460 hash = JS('int', '# ^ (# >> 6)', hash, hash);
461 }
462 hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3));
463 hash = JS('int', '# ^ (# >> 11)', hash, hash);
464 return 0x1fffffff & (hash + ((0x00003fff & hash) << 15));
465 }
466
467 Type get runtimeType => String;
468
469 int get length => JS('int', r'#.length', this);
470
471 String operator [](int index) {
472 if (index is !int) throw diagnoseIndexError(this, index);
473 if (index >= length || index < 0) throw diagnoseIndexError(this, index);
474 return JS('String', '#[#]', this, index);
475 }
476 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698