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

Side by Side Diff: pkg/dev_compiler/tool/input_sdk/private/js_string.dart

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

Powered by Google App Engine
This is Rietveld 408576698