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

Side by Side Diff: runtime/lib/string_patch.dart

Issue 2759973004: Fix observatory tests broken by running dartfmt. Temporarily reverted formatting for evaluate_activ… (Closed)
Patch Set: Created 3 years, 9 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
« no previous file with comments | « runtime/lib/string_buffer_patch.dart ('k') | runtime/lib/symbol_patch.dart » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file 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 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. 3 // BSD-style license that can be found in the LICENSE file.
4 4
5 const int _maxAscii = 0x7f; 5 const int _maxAscii = 0x7f;
6 const int _maxLatin1 = 0xff; 6 const int _maxLatin1 = 0xff;
7 const int _maxUtf16 = 0xffff; 7 const int _maxUtf16 = 0xffff;
8 const int _maxUnicode = 0x10ffff; 8 const int _maxUnicode = 0x10ffff;
9 9
10 @patch class String { 10 @patch
11 @patch factory String.fromCharCodes(Iterable<int> charCodes, 11 class String {
12 [int start = 0, int end]) { 12 @patch
13 if (charCodes is! Iterable) throw new ArgumentError.value(charCodes, "charCo des"); 13 factory String.fromCharCodes(Iterable<int> charCodes,
14 [int start = 0, int end]) {
15 if (charCodes is! Iterable)
16 throw new ArgumentError.value(charCodes, "charCodes");
14 if (start is! int) throw new ArgumentError.value(start, "start"); 17 if (start is! int) throw new ArgumentError.value(start, "start");
15 if (end != null && end is! int) throw new ArgumentError.value(end, "end"); 18 if (end != null && end is! int) throw new ArgumentError.value(end, "end");
16 return _StringBase.createFromCharCodes(charCodes, start, end, null); 19 return _StringBase.createFromCharCodes(charCodes, start, end, null);
17 } 20 }
18 21
19 @patch factory String.fromCharCode(int charCode) { 22 @patch
23 factory String.fromCharCode(int charCode) {
20 if (charCode >= 0) { 24 if (charCode >= 0) {
21 if (charCode <= 0xff) { 25 if (charCode <= 0xff) {
22 return _OneByteString._allocate(1).._setAt(0, charCode); 26 return _OneByteString._allocate(1).._setAt(0, charCode);
23 } 27 }
24 if (charCode <= 0xffff) { 28 if (charCode <= 0xffff) {
25 return _StringBase._createFromCodePoints(new _List(1)..[0] = charCode, 29 return _StringBase._createFromCodePoints(
26 0, 1); 30 new _List(1)..[0] = charCode, 0, 1);
27 } 31 }
28 if (charCode <= 0x10ffff) { 32 if (charCode <= 0x10ffff) {
29 var low = 0xDC00 | (charCode & 0x3ff); 33 var low = 0xDC00 | (charCode & 0x3ff);
30 int bits = charCode - 0x10000; 34 int bits = charCode - 0x10000;
31 var high = 0xD800 | (bits >> 10); 35 var high = 0xD800 | (bits >> 10);
32 return _StringBase._createFromCodePoints(new _List(2)..[0] = high 36 return _StringBase._createFromCodePoints(
33 ..[1] = low, 37 new _List(2)
34 0, 2); 38 ..[0] = high
39 ..[1] = low,
40 0,
41 2);
35 } 42 }
36 } 43 }
37 throw new RangeError.range(charCode, 0, 0x10ffff); 44 throw new RangeError.range(charCode, 0, 0x10ffff);
38 } 45 }
39 46
40 @patch const factory String.fromEnvironment(String name, 47 @patch
41 {String defaultValue}) 48 const factory String.fromEnvironment(String name, {String defaultValue})
42 native "String_fromEnvironment"; 49 native "String_fromEnvironment";
43 } 50 }
44 51
45
46 /** 52 /**
47 * [_StringBase] contains common methods used by concrete String 53 * [_StringBase] contains common methods used by concrete String
48 * implementations, e.g., _OneByteString. 54 * implementations, e.g., _OneByteString.
49 */ 55 */
50 abstract class _StringBase { 56 abstract class _StringBase {
51 // Constants used by replaceAll encoding of string slices between matches. 57 // Constants used by replaceAll encoding of string slices between matches.
52 // A string slice (start+length) is encoded in a single Smi to save memory 58 // A string slice (start+length) is encoded in a single Smi to save memory
53 // overhead in the common case. 59 // overhead in the common case.
54 // We use fewer bits for length (11 bits) than for the start index (19+ bits). 60 // We use fewer bits for length (11 bits) than for the start index (19+ bits).
55 // For long strings, it's possible to have many large indices, 61 // For long strings, it's possible to have many large indices,
(...skipping 16 matching lines...) Expand all
72 // We pick 30 as a safe lower bound on available bits in a negative smi. 78 // We pick 30 as a safe lower bound on available bits in a negative smi.
73 // TODO(lrn): Consider allowing more bits for start on 64-bit systems. 79 // TODO(lrn): Consider allowing more bits for start on 64-bit systems.
74 static const int _maxUnsignedSmiBits = 30; 80 static const int _maxUnsignedSmiBits = 30;
75 81
76 // For longer strings, calling into C++ to create the result of a 82 // For longer strings, calling into C++ to create the result of a
77 // [replaceAll] is faster than [_joinReplaceAllOneByteResult]. 83 // [replaceAll] is faster than [_joinReplaceAllOneByteResult].
78 // TODO(lrn): See if this limit can be tweaked. 84 // TODO(lrn): See if this limit can be tweaked.
79 static const int _maxJoinReplaceOneByteStringLength = 500; 85 static const int _maxJoinReplaceOneByteStringLength = 500;
80 86
81 factory _StringBase._uninstantiable() { 87 factory _StringBase._uninstantiable() {
82 throw new UnsupportedError( 88 throw new UnsupportedError("_StringBase can't be instaniated");
83 "_StringBase can't be instaniated");
84 } 89 }
85 90
86 int get hashCode native "String_getHashCode"; 91 int get hashCode native "String_getHashCode";
87 92
88 bool get _isOneByte { 93 bool get _isOneByte {
89 // Alternatively return false and override it on one-byte string classes. 94 // Alternatively return false and override it on one-byte string classes.
90 int id = ClassID.getID(this); 95 int id = ClassID.getID(this);
91 return id == ClassID.cidOneByteString || 96 return id == ClassID.cidOneByteString ||
92 id == ClassID.cidExternalOneByteString; 97 id == ClassID.cidExternalOneByteString;
93 } 98 }
94 99
95 /** 100 /**
96 * Create the most efficient string representation for specified 101 * Create the most efficient string representation for specified
97 * [charCodes]. 102 * [charCodes].
98 * 103 *
99 * Only uses the character codes betwen index [start] and index [end] of 104 * Only uses the character codes betwen index [start] and index [end] of
100 * `charCodes`. They must satisfy `0 <= start <= end <= charCodes.length`. 105 * `charCodes`. They must satisfy `0 <= start <= end <= charCodes.length`.
101 * 106 *
102 * The [limit] is an upper limit on the character codes in the iterable. 107 * The [limit] is an upper limit on the character codes in the iterable.
103 * It's `null` if unknown. 108 * It's `null` if unknown.
104 */ 109 */
105 static String createFromCharCodes(Iterable<int> charCodes, 110 static String createFromCharCodes(
106 int start, int end, 111 Iterable<int> charCodes, int start, int end, int limit) {
107 int limit) {
108 if (start == null) throw new ArgumentError.notNull("start"); 112 if (start == null) throw new ArgumentError.notNull("start");
109 if (charCodes == null) throw new ArgumentError(charCodes); 113 if (charCodes == null) throw new ArgumentError(charCodes);
110 // TODO(srdjan): Also skip copying of wide typed arrays. 114 // TODO(srdjan): Also skip copying of wide typed arrays.
111 final ccid = ClassID.getID(charCodes); 115 final ccid = ClassID.getID(charCodes);
112 bool isOneByteString = false; 116 bool isOneByteString = false;
113 if ((ccid != ClassID.cidArray) && 117 if ((ccid != ClassID.cidArray) &&
114 (ccid != ClassID.cidGrowableObjectArray) && 118 (ccid != ClassID.cidGrowableObjectArray) &&
115 (ccid != ClassID.cidImmutableArray)) { 119 (ccid != ClassID.cidImmutableArray)) {
116 if (charCodes is Uint8List) { 120 if (charCodes is Uint8List) {
117 end = RangeError.checkValidRange(start, end, charCodes.length); 121 end = RangeError.checkValidRange(start, end, charCodes.length);
(...skipping 27 matching lines...) Expand all
145 static int _scanCodeUnits(List<int> charCodes, int start, int end) { 149 static int _scanCodeUnits(List<int> charCodes, int start, int end) {
146 int bits = 0; 150 int bits = 0;
147 for (int i = start; i < end; i++) { 151 for (int i = start; i < end; i++) {
148 int code = charCodes[i]; 152 int code = charCodes[i];
149 if (code is! _Smi) throw new ArgumentError(charCodes); 153 if (code is! _Smi) throw new ArgumentError(charCodes);
150 bits |= code; 154 bits |= code;
151 } 155 }
152 return bits; 156 return bits;
153 } 157 }
154 158
155 static String _createStringFromIterable(Iterable<int> charCodes, 159 static String _createStringFromIterable(
156 int start, int end) { 160 Iterable<int> charCodes, int start, int end) {
157 // Treat charCodes as Iterable. 161 // Treat charCodes as Iterable.
158 if (charCodes is EfficientLengthIterable) { 162 if (charCodes is EfficientLengthIterable) {
159 int length = charCodes.length; 163 int length = charCodes.length;
160 end = RangeError.checkValidRange(start, end, length); 164 end = RangeError.checkValidRange(start, end, length);
161 List charCodeList = new List.from(charCodes.take(end).skip(start), 165 List charCodeList =
162 growable: false); 166 new List.from(charCodes.take(end).skip(start), growable: false);
163 return createFromCharCodes(charCodeList, 0, charCodeList.length, null); 167 return createFromCharCodes(charCodeList, 0, charCodeList.length, null);
164 } 168 }
165 // Don't know length of iterable, so iterate and see if all the values 169 // Don't know length of iterable, so iterate and see if all the values
166 // are there. 170 // are there.
167 if (start < 0) throw new RangeError.range(start, 0, charCodes.length); 171 if (start < 0) throw new RangeError.range(start, 0, charCodes.length);
168 var it = charCodes.iterator; 172 var it = charCodes.iterator;
169 for (int i = 0; i < start; i++) { 173 for (int i = 0; i < start; i++) {
170 if (!it.moveNext()) { 174 if (!it.moveNext()) {
171 throw new RangeError.range(start, 0, i); 175 throw new RangeError.range(start, 0, i);
172 } 176 }
173 } 177 }
174 List charCodeList; 178 List charCodeList;
175 int bits = 0; // Bitwise-or of all char codes in list. 179 int bits = 0; // Bitwise-or of all char codes in list.
176 if (end == null) { 180 if (end == null) {
177 var list = []; 181 var list = [];
178 while (it.moveNext()) { 182 while (it.moveNext()) {
179 int code = it.current; 183 int code = it.current;
180 bits |= code; 184 bits |= code;
181 list.add(code); 185 list.add(code);
182 } 186 }
183 charCodeList = makeListFixedLength(list); 187 charCodeList = makeListFixedLength(list);
184 } else { 188 } else {
185 if (end < start) { 189 if (end < start) {
(...skipping 29 matching lines...) Expand all
215 s._setAt(i, charCodes[start + i]); 219 s._setAt(i, charCodes[start + i]);
216 } 220 }
217 return s; 221 return s;
218 } 222 }
219 223
220 static String _createFromCodePoints(List<int> codePoints, int start, int end) 224 static String _createFromCodePoints(List<int> codePoints, int start, int end)
221 native "StringBase_createFromCodePoints"; 225 native "StringBase_createFromCodePoints";
222 226
223 String operator [](int index) native "String_charAt"; 227 String operator [](int index) native "String_charAt";
224 228
225 int codeUnitAt(int index); // Implemented in the subclasses. 229 int codeUnitAt(int index); // Implemented in the subclasses.
226 230
227 int get length native "String_getLength"; 231 int get length native "String_getLength";
228 232
229 bool get isEmpty { 233 bool get isEmpty {
230 return this.length == 0; 234 return this.length == 0;
231 } 235 }
232 236
233 bool get isNotEmpty => !isEmpty; 237 bool get isNotEmpty => !isEmpty;
234 238
235 String operator +(String other) native "String_concat"; 239 String operator +(String other) native "String_concat";
236 240
237 String toString() { 241 String toString() {
238 return this; 242 return this;
239 } 243 }
240 244
241 bool operator ==(Object other) { 245 bool operator ==(Object other) {
242 if (identical(this, other)) { 246 if (identical(this, other)) {
243 return true; 247 return true;
244 } 248 }
245 if ((other is! String) || 249 if ((other is! String) || (this.length != other.length)) {
246 (this.length != other.length)) {
247 return false; 250 return false;
248 } 251 }
249 final len = this.length; 252 final len = this.length;
250 for (int i = 0; i < len; i++) { 253 for (int i = 0; i < len; i++) {
251 if (this.codeUnitAt(i) != other.codeUnitAt(i)) { 254 if (this.codeUnitAt(i) != other.codeUnitAt(i)) {
252 return false; 255 return false;
253 } 256 }
254 } 257 }
255 return true; 258 return true;
256 } 259 }
(...skipping 125 matching lines...) Expand 10 before | Expand all | Expand 10 after
382 } 385 }
383 return _substringUncheckedNative(startIndex, endIndex); 386 return _substringUncheckedNative(startIndex, endIndex);
384 } 387 }
385 388
386 String _substringUncheckedNative(int startIndex, int endIndex) 389 String _substringUncheckedNative(int startIndex, int endIndex)
387 native "StringBase_substringUnchecked"; 390 native "StringBase_substringUnchecked";
388 391
389 // Checks for one-byte whitespaces only. 392 // Checks for one-byte whitespaces only.
390 static bool _isOneByteWhitespace(int codeUnit) { 393 static bool _isOneByteWhitespace(int codeUnit) {
391 if (codeUnit <= 32) { 394 if (codeUnit <= 32) {
392 return ((codeUnit == 32) || // Space. 395 return ((codeUnit == 32) || // Space.
393 ((codeUnit <= 13) && (codeUnit >= 9))); // CR, LF, TAB, etc. 396 ((codeUnit <= 13) && (codeUnit >= 9))); // CR, LF, TAB, etc.
394 } 397 }
395 return (codeUnit == 0x85) || (codeUnit == 0xA0); // NEL, NBSP. 398 return (codeUnit == 0x85) || (codeUnit == 0xA0); // NEL, NBSP.
396 } 399 }
397 400
398 // Characters with Whitespace property (Unicode 6.2). 401 // Characters with Whitespace property (Unicode 6.2).
399 // 0009..000D ; White_Space # Cc <control-0009>..<control-000D> 402 // 0009..000D ; White_Space # Cc <control-0009>..<control-000D>
400 // 0020 ; White_Space # Zs SPACE 403 // 0020 ; White_Space # Zs SPACE
401 // 0085 ; White_Space # Cc <control-0085> 404 // 0085 ; White_Space # Cc <control-0085>
402 // 00A0 ; White_Space # Zs NO-BREAK SPACE 405 // 00A0 ; White_Space # Zs NO-BREAK SPACE
403 // 1680 ; White_Space # Zs OGHAM SPACE MARK 406 // 1680 ; White_Space # Zs OGHAM SPACE MARK
404 // 180E ; White_Space # Zs MONGOLIAN VOWEL SEPARATOR 407 // 180E ; White_Space # Zs MONGOLIAN VOWEL SEPARATOR
405 // 2000..200A ; White_Space # Zs EN QUAD..HAIR SPACE 408 // 2000..200A ; White_Space # Zs EN QUAD..HAIR SPACE
406 // 2028 ; White_Space # Zl LINE SEPARATOR 409 // 2028 ; White_Space # Zl LINE SEPARATOR
407 // 2029 ; White_Space # Zp PARAGRAPH SEPARATOR 410 // 2029 ; White_Space # Zp PARAGRAPH SEPARATOR
408 // 202F ; White_Space # Zs NARROW NO-BREAK SPACE 411 // 202F ; White_Space # Zs NARROW NO-BREAK SPACE
409 // 205F ; White_Space # Zs MEDIUM MATHEMATICAL SPACE 412 // 205F ; White_Space # Zs MEDIUM MATHEMATICAL SPACE
410 // 3000 ; White_Space # Zs IDEOGRAPHIC SPACE 413 // 3000 ; White_Space # Zs IDEOGRAPHIC SPACE
411 // 414 //
412 // BOM: 0xFEFF 415 // BOM: 0xFEFF
413 static bool _isTwoByteWhitespace(int codeUnit) { 416 static bool _isTwoByteWhitespace(int codeUnit) {
414 if (codeUnit <= 32) { 417 if (codeUnit <= 32) {
415 return (codeUnit == 32) || 418 return (codeUnit == 32) || ((codeUnit <= 13) && (codeUnit >= 9));
416 ((codeUnit <= 13) && (codeUnit >= 9));
417 } 419 }
418 if (codeUnit < 0x85) return false; 420 if (codeUnit < 0x85) return false;
419 if ((codeUnit == 0x85) || (codeUnit == 0xA0)) return true; 421 if ((codeUnit == 0x85) || (codeUnit == 0xA0)) return true;
420 return (codeUnit <= 0x200A) 422 return (codeUnit <= 0x200A)
421 ? ((codeUnit == 0x1680) || 423 ? ((codeUnit == 0x1680) || (codeUnit == 0x180E) || (0x2000 <= codeUnit))
422 (codeUnit == 0x180E) || 424 : ((codeUnit == 0x2028) ||
423 (0x2000 <= codeUnit)) 425 (codeUnit == 0x2029) ||
424 : ((codeUnit == 0x2028) || 426 (codeUnit == 0x202F) ||
425 (codeUnit == 0x2029) || 427 (codeUnit == 0x205F) ||
426 (codeUnit == 0x202F) || 428 (codeUnit == 0x3000) ||
427 (codeUnit == 0x205F) || 429 (codeUnit == 0xFEFF));
428 (codeUnit == 0x3000) ||
429 (codeUnit == 0xFEFF));
430 } 430 }
431 431
432 int _firstNonWhitespace() { 432 int _firstNonWhitespace() {
433 final len = this.length; 433 final len = this.length;
434 int first = 0; 434 int first = 0;
435 for (; first < len; first++) { 435 for (; first < len; first++) {
436 if (!_isWhitespace(this.codeUnitAt(first))) { 436 if (!_isWhitespace(this.codeUnitAt(first))) {
437 break; 437 break;
438 } 438 }
439 } 439 }
(...skipping 58 matching lines...) Expand 10 before | Expand all | Expand 10 after
498 // String contains only whitespaces. 498 // String contains only whitespaces.
499 return ""; 499 return "";
500 } 500 }
501 if (last == (len - 1)) { 501 if (last == (len - 1)) {
502 // Returns this string since it does not have trailing whitespaces. 502 // Returns this string since it does not have trailing whitespaces.
503 return this; 503 return this;
504 } 504 }
505 return _substringUnchecked(0, last + 1); 505 return _substringUnchecked(0, last + 1);
506 } 506 }
507 507
508 String operator*(int times) { 508 String operator *(int times) {
509 if (times <= 0) return ""; 509 if (times <= 0) return "";
510 if (times == 1) return this; 510 if (times == 1) return this;
511 StringBuffer buffer = new StringBuffer(this); 511 StringBuffer buffer = new StringBuffer(this);
512 for (int i = 1; i < times; i++) { 512 for (int i = 1; i < times; i++) {
513 buffer.write(this); 513 buffer.write(this);
514 } 514 }
515 return buffer.toString(); 515 return buffer.toString();
516 } 516 }
517 517
518 String padLeft(int width, [String padding = ' ']) { 518 String padLeft(int width, [String padding = ' ']) {
(...skipping 20 matching lines...) Expand all
539 bool contains(Pattern pattern, [int startIndex = 0]) { 539 bool contains(Pattern pattern, [int startIndex = 0]) {
540 if (pattern is String) { 540 if (pattern is String) {
541 if (startIndex < 0 || startIndex > this.length) { 541 if (startIndex < 0 || startIndex > this.length) {
542 throw new RangeError.range(startIndex, 0, this.length); 542 throw new RangeError.range(startIndex, 0, this.length);
543 } 543 }
544 return indexOf(pattern, startIndex) >= 0; 544 return indexOf(pattern, startIndex) >= 0;
545 } 545 }
546 return pattern.allMatches(this.substring(startIndex)).isNotEmpty; 546 return pattern.allMatches(this.substring(startIndex)).isNotEmpty;
547 } 547 }
548 548
549 String replaceFirst(Pattern pattern, 549 String replaceFirst(Pattern pattern, String replacement,
550 String replacement, 550 [int startIndex = 0]) {
551 [int startIndex = 0]) {
552 if (pattern is! Pattern) { 551 if (pattern is! Pattern) {
553 throw new ArgumentError("${pattern} is not a Pattern"); 552 throw new ArgumentError("${pattern} is not a Pattern");
554 } 553 }
555 if (replacement is! String) { 554 if (replacement is! String) {
556 throw new ArgumentError("${replacement} is not a String"); 555 throw new ArgumentError("${replacement} is not a String");
557 } 556 }
558 if (startIndex is! int) { 557 if (startIndex is! int) {
559 throw new ArgumentError("${startIndex} is not an int"); 558 throw new ArgumentError("${startIndex} is not an int");
560 } 559 }
561 RangeError.checkValueInInterval(startIndex, 0, this.length, "startIndex"); 560 RangeError.checkValueInInterval(startIndex, 0, this.length, "startIndex");
562 Iterator iterator = 561 Iterator iterator = startIndex == 0
563 startIndex == 0 ? pattern.allMatches(this).iterator 562 ? pattern.allMatches(this).iterator
564 : pattern.allMatches(this, startIndex).iterator; 563 : pattern.allMatches(this, startIndex).iterator;
565 if (!iterator.moveNext()) return this; 564 if (!iterator.moveNext()) return this;
566 Match match = iterator.current; 565 Match match = iterator.current;
567 return replaceRange(match.start, match.end, replacement); 566 return replaceRange(match.start, match.end, replacement);
568 } 567 }
569 568
570 String replaceRange(int start, int end, String replacement) { 569 String replaceRange(int start, int end, String replacement) {
571 int length = this.length; 570 int length = this.length;
572 end = RangeError.checkValidRange(start, end, length); 571 end = RangeError.checkValidRange(start, end, length);
573 bool replacementIsOneByte = replacement._isOneByte; 572 bool replacementIsOneByte = replacement._isOneByte;
574 if (start == 0 && end == length) return replacement; 573 if (start == 0 && end == length) return replacement;
575 int replacementLength = replacement.length; 574 int replacementLength = replacement.length;
576 int totalLength = start + (length - end) + replacementLength; 575 int totalLength = start + (length - end) + replacementLength;
577 if (replacementIsOneByte && this._isOneByte) { 576 if (replacementIsOneByte && this._isOneByte) {
578 var result = _OneByteString._allocate(totalLength); 577 var result = _OneByteString._allocate(totalLength);
579 int index = 0; 578 int index = 0;
580 index = result._setRange(index, this, 0, start); 579 index = result._setRange(index, this, 0, start);
581 index = result._setRange(start, replacement, 0, replacementLength); 580 index = result._setRange(start, replacement, 0, replacementLength);
582 result._setRange(index, this, end, length); 581 result._setRange(index, this, end, length);
583 return result; 582 return result;
584 } 583 }
585 List slices = []; 584 List slices = [];
586 _addReplaceSlice(slices, 0, start); 585 _addReplaceSlice(slices, 0, start);
587 if (replacement.length > 0) slices.add(replacement); 586 if (replacement.length > 0) slices.add(replacement);
588 _addReplaceSlice(slices, end, length); 587 _addReplaceSlice(slices, end, length);
589 return _joinReplaceAllResult(this, slices, totalLength, 588 return _joinReplaceAllResult(
590 replacementIsOneByte); 589 this, slices, totalLength, replacementIsOneByte);
591 } 590 }
592 591
593 static int _addReplaceSlice(List matches, int start, int end) { 592 static int _addReplaceSlice(List matches, int start, int end) {
594 int length = end - start; 593 int length = end - start;
595 if (length > 0) { 594 if (length > 0) {
596 if (length <= _maxLengthValue && start <= _maxStartValue) { 595 if (length <= _maxLengthValue && start <= _maxStartValue) {
597 matches.add(-((start << _lengthBits) | length)); 596 matches.add(-((start << _lengthBits) | length));
598 } else { 597 } else {
599 matches.add(start); 598 matches.add(start);
600 matches.add(end); 599 matches.add(end);
(...skipping 24 matching lines...) Expand all
625 } 624 }
626 } 625 }
627 length += _addReplaceSlice(matches, startIndex, this.length); 626 length += _addReplaceSlice(matches, startIndex, this.length);
628 bool replacementIsOneByte = replacement._isOneByte; 627 bool replacementIsOneByte = replacement._isOneByte;
629 if (replacementIsOneByte && 628 if (replacementIsOneByte &&
630 length < _maxJoinReplaceOneByteStringLength && 629 length < _maxJoinReplaceOneByteStringLength &&
631 this._isOneByte) { 630 this._isOneByte) {
632 // TODO(lrn): Is there a cut-off point, or is runtime always faster? 631 // TODO(lrn): Is there a cut-off point, or is runtime always faster?
633 return _joinReplaceAllOneByteResult(this, matches, length); 632 return _joinReplaceAllOneByteResult(this, matches, length);
634 } 633 }
635 return _joinReplaceAllResult(this, matches, length, 634 return _joinReplaceAllResult(this, matches, length, replacementIsOneByte);
636 replacementIsOneByte);
637 } 635 }
638 636
639 /** 637 /**
640 * As [_joinReplaceAllResult], but knowing that the result 638 * As [_joinReplaceAllResult], but knowing that the result
641 * is always a [_OneByteString]. 639 * is always a [_OneByteString].
642 */ 640 */
643 static String _joinReplaceAllOneByteResult(String base, 641 static String _joinReplaceAllOneByteResult(
644 List matches, 642 String base, List matches, int length) {
645 int length) {
646 _OneByteString result = _OneByteString._allocate(length); 643 _OneByteString result = _OneByteString._allocate(length);
647 int writeIndex = 0; 644 int writeIndex = 0;
648 for (int i = 0; i < matches.length; i++) { 645 for (int i = 0; i < matches.length; i++) {
649 var entry = matches[i]; 646 var entry = matches[i];
650 if (entry is _Smi) { 647 if (entry is _Smi) {
651 int sliceStart = entry; 648 int sliceStart = entry;
652 int sliceEnd; 649 int sliceEnd;
653 if (sliceStart < 0) { 650 if (sliceStart < 0) {
654 int bits = -sliceStart; 651 int bits = -sliceStart;
655 int sliceLength = bits & _lengthMask; 652 int sliceLength = bits & _lengthMask;
(...skipping 26 matching lines...) Expand all
682 * Combine the results of a [replaceAll] match into a new string. 679 * Combine the results of a [replaceAll] match into a new string.
683 * 680 *
684 * The [matches] lists contains Smi index pairs representing slices of 681 * The [matches] lists contains Smi index pairs representing slices of
685 * [base] and [String]s to be put in between the slices. 682 * [base] and [String]s to be put in between the slices.
686 * 683 *
687 * The total [length] of the resulting string is known, as is 684 * The total [length] of the resulting string is known, as is
688 * whether the replacement strings are one-byte strings. 685 * whether the replacement strings are one-byte strings.
689 * If they are, then we have to check the base string slices to know 686 * If they are, then we have to check the base string slices to know
690 * whether the result must be a one-byte string. 687 * whether the result must be a one-byte string.
691 */ 688 */
692 static String _joinReplaceAllResult(String base, List matches, int length, 689 static String
693 bool replacementStringsAreOneByte) 690 _joinReplaceAllResult(String base, List matches, int length,
691 bool replacementStringsAreOneByte)
694 native "StringBase_joinReplaceAllResult"; 692 native "StringBase_joinReplaceAllResult";
695 693
696 String replaceAllMapped(Pattern pattern, String replace(Match match)) { 694 String replaceAllMapped(Pattern pattern, String replace(Match match)) {
697 if (pattern == null) throw new ArgumentError.notNull("pattern"); 695 if (pattern == null) throw new ArgumentError.notNull("pattern");
698 if (replace == null) throw new ArgumentError.notNull("replace"); 696 if (replace == null) throw new ArgumentError.notNull("replace");
699 List matches = []; 697 List matches = [];
700 int length = 0; 698 int length = 0;
701 int startIndex = 0; 699 int startIndex = 0;
702 bool replacementStringsAreOneByte = true; 700 bool replacementStringsAreOneByte = true;
703 for (Match match in pattern.allMatches(this)) { 701 for (Match match in pattern.allMatches(this)) {
704 length += _addReplaceSlice(matches, startIndex, match.start); 702 length += _addReplaceSlice(matches, startIndex, match.start);
705 var replacement = "${replace(match)}"; 703 var replacement = "${replace(match)}";
706 matches.add(replacement); 704 matches.add(replacement);
707 length += replacement.length; 705 length += replacement.length;
708 replacementStringsAreOneByte = 706 replacementStringsAreOneByte =
709 replacementStringsAreOneByte && replacement._isOneByte; 707 replacementStringsAreOneByte && replacement._isOneByte;
710 startIndex = match.end; 708 startIndex = match.end;
711 } 709 }
712 if (matches.isEmpty) return this; 710 if (matches.isEmpty) return this;
713 length += _addReplaceSlice(matches, startIndex, this.length); 711 length += _addReplaceSlice(matches, startIndex, this.length);
714 if (replacementStringsAreOneByte && 712 if (replacementStringsAreOneByte &&
715 length < _maxJoinReplaceOneByteStringLength && 713 length < _maxJoinReplaceOneByteStringLength &&
716 this._isOneByte) { 714 this._isOneByte) {
717 return _joinReplaceAllOneByteResult(this, matches, length); 715 return _joinReplaceAllOneByteResult(this, matches, length);
718 } 716 }
719 return _joinReplaceAllResult(this, matches, length, 717 return _joinReplaceAllResult(
720 replacementStringsAreOneByte); 718 this, matches, length, replacementStringsAreOneByte);
721 } 719 }
722 720
723 String replaceFirstMapped(Pattern pattern, String replace(Match match), 721 String replaceFirstMapped(Pattern pattern, String replace(Match match),
724 [int startIndex = 0]) { 722 [int startIndex = 0]) {
725 if (pattern == null) throw new ArgumentError.notNull("pattern"); 723 if (pattern == null) throw new ArgumentError.notNull("pattern");
726 if (replace == null) throw new ArgumentError.notNull("replace"); 724 if (replace == null) throw new ArgumentError.notNull("replace");
727 if (startIndex == null) throw new ArgumentError.notNull("startIndex"); 725 if (startIndex == null) throw new ArgumentError.notNull("startIndex");
728 RangeError.checkValueInInterval(startIndex, 0, this.length, "startIndex"); 726 RangeError.checkValueInInterval(startIndex, 0, this.length, "startIndex");
729 727
730 var matches = pattern.allMatches(this, startIndex).iterator; 728 var matches = pattern.allMatches(this, startIndex).iterator;
731 if (!matches.moveNext()) return this; 729 if (!matches.moveNext()) return this;
732 var match = matches.current; 730 var match = matches.current;
733 var replacement = "${replace(match)}"; 731 var replacement = "${replace(match)}";
734 return replaceRange(match.start, match.end, replacement); 732 return replaceRange(match.start, match.end, replacement);
735 } 733 }
736 734
737 static String _matchString(Match match) => match[0]; 735 static String _matchString(Match match) => match[0];
738 static String _stringIdentity(String string) => string; 736 static String _stringIdentity(String string) => string;
739 737
740 String _splitMapJoinEmptyString(String onMatch(Match match), 738 String _splitMapJoinEmptyString(
741 String onNonMatch(String nonMatch)) { 739 String onMatch(Match match), String onNonMatch(String nonMatch)) {
742 // Pattern is the empty string. 740 // Pattern is the empty string.
743 StringBuffer buffer = new StringBuffer(); 741 StringBuffer buffer = new StringBuffer();
744 int length = this.length; 742 int length = this.length;
745 int i = 0; 743 int i = 0;
746 buffer.write(onNonMatch("")); 744 buffer.write(onNonMatch(""));
747 while (i < length) { 745 while (i < length) {
748 buffer.write(onMatch(new _StringMatch(i, this, ""))); 746 buffer.write(onMatch(new _StringMatch(i, this, "")));
749 // Special case to avoid splitting a surrogate pair. 747 // Special case to avoid splitting a surrogate pair.
750 int code = this.codeUnitAt(i); 748 int code = this.codeUnitAt(i);
751 if ((code & ~0x3FF) == 0xD800 && length > i + 1) { 749 if ((code & ~0x3FF) == 0xD800 && length > i + 1) {
752 // Leading surrogate; 750 // Leading surrogate;
753 code = this.codeUnitAt(i + 1); 751 code = this.codeUnitAt(i + 1);
754 if ((code & ~0x3FF) == 0xDC00) { 752 if ((code & ~0x3FF) == 0xDC00) {
755 // Matching trailing surrogate. 753 // Matching trailing surrogate.
756 buffer.write(onNonMatch(this.substring(i, i + 2))); 754 buffer.write(onNonMatch(this.substring(i, i + 2)));
757 i += 2; 755 i += 2;
758 continue; 756 continue;
759 } 757 }
760 } 758 }
761 buffer.write(onNonMatch(this[i])); 759 buffer.write(onNonMatch(this[i]));
762 i++; 760 i++;
763 } 761 }
764 buffer.write(onMatch(new _StringMatch(i, this, ""))); 762 buffer.write(onMatch(new _StringMatch(i, this, "")));
765 buffer.write(onNonMatch("")); 763 buffer.write(onNonMatch(""));
766 return buffer.toString(); 764 return buffer.toString();
767 } 765 }
768 766
769 String splitMapJoin(Pattern pattern, 767 String splitMapJoin(Pattern pattern,
770 {String onMatch(Match match), 768 {String onMatch(Match match), String onNonMatch(String nonMatch)}) {
771 String onNonMatch(String nonMatch)}) {
772 if (pattern is! Pattern) { 769 if (pattern is! Pattern) {
773 throw new ArgumentError("${pattern} is not a Pattern"); 770 throw new ArgumentError("${pattern} is not a Pattern");
774 } 771 }
775 if (onMatch == null) onMatch = _matchString; 772 if (onMatch == null) onMatch = _matchString;
776 if (onNonMatch == null) onNonMatch = _stringIdentity; 773 if (onNonMatch == null) onNonMatch = _stringIdentity;
777 if (pattern is String) { 774 if (pattern is String) {
778 String stringPattern = pattern; 775 String stringPattern = pattern;
779 if (stringPattern.isEmpty) { 776 if (stringPattern.isEmpty) {
780 return _splitMapJoinEmptyString(onMatch, onNonMatch); 777 return _splitMapJoinEmptyString(onMatch, onNonMatch);
781 } 778 }
(...skipping 98 matching lines...) Expand 10 before | Expand all | Expand 10 after
880 result.add(this.substring(previousIndex, length)); 877 result.add(this.substring(previousIndex, length));
881 break; 878 break;
882 } 879 }
883 Match match = iterator.current; 880 Match match = iterator.current;
884 if (match.start == length) { 881 if (match.start == length) {
885 result.add(this.substring(previousIndex, length)); 882 result.add(this.substring(previousIndex, length));
886 break; 883 break;
887 } 884 }
888 int endIndex = match.end; 885 int endIndex = match.end;
889 if (startIndex == endIndex && endIndex == previousIndex) { 886 if (startIndex == endIndex && endIndex == previousIndex) {
890 ++startIndex; // empty match, advance and restart 887 ++startIndex; // empty match, advance and restart
891 continue; 888 continue;
892 } 889 }
893 result.add(this.substring(previousIndex, match.start)); 890 result.add(this.substring(previousIndex, match.start));
894 startIndex = previousIndex = endIndex; 891 startIndex = previousIndex = endIndex;
895 } 892 }
896 return result; 893 return result;
897 } 894 }
898 895
899 List<int> get codeUnits => new CodeUnits(this); 896 List<int> get codeUnits => new CodeUnits(this);
900 897
(...skipping 11 matching lines...) Expand all
912 } 909 }
913 return _concatRangeNative(strings, start, end); 910 return _concatRangeNative(strings, start, end);
914 } 911 }
915 912
916 // Call this method if not all list elements are known to be OneByteString(s). 913 // Call this method if not all list elements are known to be OneByteString(s).
917 // 'strings' must be an _List or _GrowableList. 914 // 'strings' must be an _List or _GrowableList.
918 static String _concatRangeNative(List<String> strings, int start, int end) 915 static String _concatRangeNative(List<String> strings, int start, int end)
919 native "String_concatRange"; 916 native "String_concatRange";
920 } 917 }
921 918
922
923 class _OneByteString extends _StringBase implements String { 919 class _OneByteString extends _StringBase implements String {
924
925 factory _OneByteString._uninstantiable() { 920 factory _OneByteString._uninstantiable() {
926 throw new UnsupportedError( 921 throw new UnsupportedError(
927 "_OneByteString can only be allocated by the VM"); 922 "_OneByteString can only be allocated by the VM");
928 } 923 }
929 924
930 int get hashCode native "String_getHashCode"; 925 int get hashCode native "String_getHashCode";
931 926
932 int codeUnitAt(int index) native "String_codeUnitAt"; 927 int codeUnitAt(int index) native "String_codeUnitAt";
933 928
934 bool _isWhitespace(int codeUnit) { 929 bool _isWhitespace(int codeUnit) {
(...skipping 76 matching lines...) Expand 10 before | Expand all | Expand 10 after
1011 if (this.codeUnitAt(i) == patternCu0) { 1006 if (this.codeUnitAt(i) == patternCu0) {
1012 return true; 1007 return true;
1013 } 1008 }
1014 } 1009 }
1015 return false; 1010 return false;
1016 } 1011 }
1017 } 1012 }
1018 return super.contains(pattern, start); 1013 return super.contains(pattern, start);
1019 } 1014 }
1020 1015
1021 String operator*(int times) { 1016 String operator *(int times) {
1022 if (times <= 0) return ""; 1017 if (times <= 0) return "";
1023 if (times == 1) return this; 1018 if (times == 1) return this;
1024 int length = this.length; 1019 int length = this.length;
1025 if (this.isEmpty) return this; // Don't clone empty string. 1020 if (this.isEmpty) return this; // Don't clone empty string.
1026 _OneByteString result = _OneByteString._allocate(length * times); 1021 _OneByteString result = _OneByteString._allocate(length * times);
1027 int index = 0; 1022 int index = 0;
1028 for (int i = 0; i < times; i ++) { 1023 for (int i = 0; i < times; i++) {
1029 for (int j = 0; j < length; j++) { 1024 for (int j = 0; j < length; j++) {
1030 result._setAt(index++, this.codeUnitAt(j)); 1025 result._setAt(index++, this.codeUnitAt(j));
1031 } 1026 }
1032 } 1027 }
1033 return result; 1028 return result;
1034 } 1029 }
1035 1030
1036 String padLeft(int width, [String padding = ' ']) { 1031 String padLeft(int width, [String padding = ' ']) {
1037 int padCid = ClassID.getID(padding); 1032 int padCid = ClassID.getID(padding);
1038 if ((padCid != ClassID.cidOneByteString) && 1033 if ((padCid != ClassID.cidOneByteString) &&
(...skipping 147 matching lines...) Expand 10 before | Expand all | Expand 10 after
1186 } 1181 }
1187 return result; 1182 return result;
1188 } 1183 }
1189 return this; 1184 return this;
1190 } 1185 }
1191 1186
1192 // Allocates a string of given length, expecting its content to be 1187 // Allocates a string of given length, expecting its content to be
1193 // set using _setAt. 1188 // set using _setAt.
1194 static _OneByteString _allocate(int length) native "OneByteString_allocate"; 1189 static _OneByteString _allocate(int length) native "OneByteString_allocate";
1195 1190
1196 1191 static _OneByteString _allocateFromOneByteList(List<int> list, int start,
1197 static _OneByteString _allocateFromOneByteList(List<int> list, 1192 int end) native "OneByteString_allocateFromOneByteList";
1198 int start, int end)
1199 native "OneByteString_allocateFromOneByteList";
1200 1193
1201 // This is internal helper method. Code point value must be a valid 1194 // This is internal helper method. Code point value must be a valid
1202 // Latin1 value (0..0xFF), index must be valid. 1195 // Latin1 value (0..0xFF), index must be valid.
1203 void _setAt(int index, int codePoint) native "OneByteString_setAt"; 1196 void _setAt(int index, int codePoint) native "OneByteString_setAt";
1204 1197
1205 // Should be optimizable to a memory move. 1198 // Should be optimizable to a memory move.
1206 // Accepts both _OneByteString and _ExternalOneByteString as argument. 1199 // Accepts both _OneByteString and _ExternalOneByteString as argument.
1207 // Returns index after last character written. 1200 // Returns index after last character written.
1208 int _setRange(int index, String oneByteString, int start, int end) { 1201 int _setRange(int index, String oneByteString, int start, int end) {
1209 assert(oneByteString._isOneByte); 1202 assert(oneByteString._isOneByte);
1210 assert(0 <= start); 1203 assert(0 <= start);
1211 assert(start <= end); 1204 assert(start <= end);
1212 assert(end <= oneByteString.length); 1205 assert(end <= oneByteString.length);
1213 assert(0 <= index); 1206 assert(0 <= index);
1214 assert(index + (end - start) <= length); 1207 assert(index + (end - start) <= length);
1215 for (int i = start; i < end; i++) { 1208 for (int i = start; i < end; i++) {
1216 _setAt(index, oneByteString.codeUnitAt(i)); 1209 _setAt(index, oneByteString.codeUnitAt(i));
1217 index += 1; 1210 index += 1;
1218 } 1211 }
1219 return index; 1212 return index;
1220 } 1213 }
1221 } 1214 }
1222 1215
1223
1224 class _TwoByteString extends _StringBase implements String { 1216 class _TwoByteString extends _StringBase implements String {
1225 factory _TwoByteString._uninstantiable() { 1217 factory _TwoByteString._uninstantiable() {
1226 throw new UnsupportedError( 1218 throw new UnsupportedError(
1227 "_TwoByteString can only be allocated by the VM"); 1219 "_TwoByteString can only be allocated by the VM");
1228 } 1220 }
1229 1221
1230 static String _allocateFromTwoByteList(List list, int start, int end) 1222 static String _allocateFromTwoByteList(List list, int start, int end)
1231 native "TwoByteString_allocateFromTwoByteList"; 1223 native "TwoByteString_allocateFromTwoByteList";
1232 1224
1233 bool _isWhitespace(int codeUnit) { 1225 bool _isWhitespace(int codeUnit) {
1234 return _StringBase._isTwoByteWhitespace(codeUnit); 1226 return _StringBase._isTwoByteWhitespace(codeUnit);
1235 } 1227 }
1236 1228
1237 int codeUnitAt(int index) native "String_codeUnitAt"; 1229 int codeUnitAt(int index) native "String_codeUnitAt";
1238 1230
1239 bool operator ==(Object other) { 1231 bool operator ==(Object other) {
1240 return super == other; 1232 return super == other;
1241 } 1233 }
1242 } 1234 }
1243 1235
1244
1245 class _ExternalOneByteString extends _StringBase implements String { 1236 class _ExternalOneByteString extends _StringBase implements String {
1246 factory _ExternalOneByteString._uninstantiable() { 1237 factory _ExternalOneByteString._uninstantiable() {
1247 throw new UnsupportedError( 1238 throw new UnsupportedError(
1248 "_ExternalOneByteString can only be allocated by the VM"); 1239 "_ExternalOneByteString can only be allocated by the VM");
1249 } 1240 }
1250 1241
1251 bool _isWhitespace(int codeUnit) { 1242 bool _isWhitespace(int codeUnit) {
1252 return _StringBase._isOneByteWhitespace(codeUnit); 1243 return _StringBase._isOneByteWhitespace(codeUnit);
1253 } 1244 }
1254 1245
1255 int codeUnitAt(int index) native "String_codeUnitAt"; 1246 int codeUnitAt(int index) native "String_codeUnitAt";
1256 1247
1257 bool operator ==(Object other) { 1248 bool operator ==(Object other) {
1258 return super == other; 1249 return super == other;
1259 } 1250 }
1260 1251
1261 static int _getCid() native "ExternalOneByteString_getCid"; 1252 static int _getCid() native "ExternalOneByteString_getCid";
1262 } 1253 }
1263 1254
1264
1265 class _ExternalTwoByteString extends _StringBase implements String { 1255 class _ExternalTwoByteString extends _StringBase implements String {
1266 factory _ExternalTwoByteString._uninstantiable() { 1256 factory _ExternalTwoByteString._uninstantiable() {
1267 throw new UnsupportedError( 1257 throw new UnsupportedError(
1268 "_ExternalTwoByteString can only be allocated by the VM"); 1258 "_ExternalTwoByteString can only be allocated by the VM");
1269 } 1259 }
1270 1260
1271 bool _isWhitespace(int codeUnit) { 1261 bool _isWhitespace(int codeUnit) {
1272 return _StringBase._isTwoByteWhitespace(codeUnit); 1262 return _StringBase._isTwoByteWhitespace(codeUnit);
1273 } 1263 }
1274 1264
1275 int codeUnitAt(int index) native "String_codeUnitAt"; 1265 int codeUnitAt(int index) native "String_codeUnitAt";
1276 1266
1277 bool operator ==(Object other) { 1267 bool operator ==(Object other) {
1278 return super == other; 1268 return super == other;
1279 } 1269 }
1280 } 1270 }
1281 1271
1282
1283 class _StringMatch implements Match { 1272 class _StringMatch implements Match {
1284 const _StringMatch(this.start, this.input, this.pattern); 1273 const _StringMatch(this.start, this.input, this.pattern);
1285 1274
1286 int get end => start + pattern.length; 1275 int get end => start + pattern.length;
1287 String operator[](int g) => group(g); 1276 String operator [](int g) => group(g);
1288 int get groupCount => 0; 1277 int get groupCount => 0;
1289 1278
1290 String group(int group) { 1279 String group(int group) {
1291 if (group != 0) { 1280 if (group != 0) {
1292 throw new RangeError.value(group); 1281 throw new RangeError.value(group);
1293 } 1282 }
1294 return pattern; 1283 return pattern;
1295 } 1284 }
1296 1285
1297 List<String> groups(List<int> groups) { 1286 List<String> groups(List<int> groups) {
1298 List<String> result = new List<String>(); 1287 List<String> result = new List<String>();
1299 for (int g in groups) { 1288 for (int g in groups) {
1300 result.add(group(g)); 1289 result.add(group(g));
1301 } 1290 }
1302 return result; 1291 return result;
1303 } 1292 }
1304 1293
1305 final int start; 1294 final int start;
1306 final String input; 1295 final String input;
1307 final String pattern; 1296 final String pattern;
1308 } 1297 }
1309 1298
1310
1311 class _StringAllMatchesIterable extends Iterable<Match> { 1299 class _StringAllMatchesIterable extends Iterable<Match> {
1312 final String _input; 1300 final String _input;
1313 final String _pattern; 1301 final String _pattern;
1314 final int _index; 1302 final int _index;
1315 1303
1316 _StringAllMatchesIterable(this._input, this._pattern, this._index); 1304 _StringAllMatchesIterable(this._input, this._pattern, this._index);
1317 1305
1318 Iterator<Match> get iterator => 1306 Iterator<Match> get iterator =>
1319 new _StringAllMatchesIterator(_input, _pattern, _index); 1307 new _StringAllMatchesIterator(_input, _pattern, _index);
1320 1308
(...skipping 28 matching lines...) Expand all
1349 int end = index + _pattern.length; 1337 int end = index + _pattern.length;
1350 _current = new _StringMatch(index, _input, _pattern); 1338 _current = new _StringMatch(index, _input, _pattern);
1351 // Empty match, don't start at same location again. 1339 // Empty match, don't start at same location again.
1352 if (end == _index) end++; 1340 if (end == _index) end++;
1353 _index = end; 1341 _index = end;
1354 return true; 1342 return true;
1355 } 1343 }
1356 1344
1357 Match get current => _current; 1345 Match get current => _current;
1358 } 1346 }
OLDNEW
« no previous file with comments | « runtime/lib/string_buffer_patch.dart ('k') | runtime/lib/symbol_patch.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698