OLD | NEW |
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 _interceptors; | 5 part of _interceptors; |
6 | 6 |
7 /** | 7 /** |
8 * The interceptor class for [List]. The compiler recognizes this | 8 * The interceptor class for [List]. 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 |
(...skipping 96 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
107 if (index is !int) throw new ArgumentError(index); | 107 if (index is !int) throw new ArgumentError(index); |
108 if (index < 0 || index > length) { | 108 if (index < 0 || index > length) { |
109 throw new RangeError.value(index); | 109 throw new RangeError.value(index); |
110 } | 110 } |
111 checkGrowable('insert'); | 111 checkGrowable('insert'); |
112 JS('void', r'#.splice(#, 0, #)', this, index, value); | 112 JS('void', r'#.splice(#, 0, #)', this, index, value); |
113 } | 113 } |
114 | 114 |
115 void insertAll(int index, Iterable<E> iterable) { | 115 void insertAll(int index, Iterable<E> iterable) { |
116 checkGrowable('insertAll'); | 116 checkGrowable('insertAll'); |
117 IterableMixinWorkaround.insertAllList(this, index, iterable); | 117 RangeError.checkValueInInterval(index, 0, this.length, "index"); |
| 118 if (iterable is! EfficientLength) { |
| 119 iterable = iterable.toList(); |
| 120 } |
| 121 int insertionLength = iterable.length; |
| 122 this.length += insertionLength; |
| 123 int end = index + insertionLength; |
| 124 this.setRange(end, this.length, this, index); |
| 125 this.setRange(index, end, iterable); |
118 } | 126 } |
119 | 127 |
120 void setAll(int index, Iterable<E> iterable) { | 128 void setAll(int index, Iterable<E> iterable) { |
121 checkMutable('setAll'); | 129 checkMutable('setAll'); |
122 IterableMixinWorkaround.setAllList(this, index, iterable); | 130 RangeError.checkValueInInterval(index, 0, this.length, "index"); |
| 131 for (var element in iterable) { |
| 132 this[index++] = element; |
| 133 } |
123 } | 134 } |
124 | 135 |
125 E removeLast() { | 136 E removeLast() { |
126 checkGrowable('removeLast'); | 137 checkGrowable('removeLast'); |
127 if (length == 0) throw new RangeError.value(-1); | 138 if (length == 0) throw new RangeError.value(-1); |
128 return JS('var', r'#.pop()', this); | 139 return JS('var', r'#.pop()', this); |
129 } | 140 } |
130 | 141 |
131 bool remove(Object element) { | 142 bool remove(Object element) { |
132 checkGrowable('remove'); | 143 checkGrowable('remove'); |
(...skipping 44 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
177 for (int i = 0; i < retained.length; i++) { | 188 for (int i = 0; i < retained.length; i++) { |
178 this[i] = retained[i]; | 189 this[i] = retained[i]; |
179 } | 190 } |
180 } | 191 } |
181 | 192 |
182 Iterable<E> where(bool f(E element)) { | 193 Iterable<E> where(bool f(E element)) { |
183 return new WhereIterable<E>(this, f); | 194 return new WhereIterable<E>(this, f); |
184 } | 195 } |
185 | 196 |
186 Iterable expand(Iterable f(E element)) { | 197 Iterable expand(Iterable f(E element)) { |
187 return IterableMixinWorkaround.expand(this, f); | 198 return new ExpandIterable<E, dynamic>(this, f); |
188 } | 199 } |
189 | 200 |
190 void addAll(Iterable<E> collection) { | 201 void addAll(Iterable<E> collection) { |
191 for (E e in collection) { | 202 for (E e in collection) { |
192 this.add(e); | 203 this.add(e); |
193 } | 204 } |
194 } | 205 } |
195 | 206 |
196 void clear() { | 207 void clear() { |
197 length = 0; | 208 length = 0; |
(...skipping 16 matching lines...) Expand all Loading... |
214 | 225 |
215 String join([String separator = ""]) { | 226 String join([String separator = ""]) { |
216 var list = new List(this.length); | 227 var list = new List(this.length); |
217 for (int i = 0; i < this.length; i++) { | 228 for (int i = 0; i < this.length; i++) { |
218 list[i] = "${this[i]}"; | 229 list[i] = "${this[i]}"; |
219 } | 230 } |
220 return JS('String', "#.join(#)", list, separator); | 231 return JS('String', "#.join(#)", list, separator); |
221 } | 232 } |
222 | 233 |
223 Iterable<E> take(int n) { | 234 Iterable<E> take(int n) { |
224 return new IterableMixinWorkaround<E>().takeList(this, n); | 235 return new SubListIterable<E>(this, 0, n); |
225 } | 236 } |
226 | 237 |
227 Iterable<E> takeWhile(bool test(E value)) { | 238 Iterable<E> takeWhile(bool test(E value)) { |
228 return new IterableMixinWorkaround<E>().takeWhile(this, test); | 239 return new TakeWhileIterable<E>(this, test); |
229 } | 240 } |
230 | 241 |
231 Iterable<E> skip(int n) { | 242 Iterable<E> skip(int n) { |
232 return new IterableMixinWorkaround<E>().skipList(this, n); | 243 return new SubListIterable<E>(this, n, null); |
233 } | 244 } |
234 | 245 |
235 Iterable<E> skipWhile(bool test(E value)) { | 246 Iterable<E> skipWhile(bool test(E value)) { |
236 return new IterableMixinWorkaround<E>().skipWhile(this, test); | 247 return new SkipWhileIterable<E>(this, test); |
237 } | 248 } |
238 | 249 |
239 E reduce(E combine(E value, E element)) { | 250 E reduce(E combine(E previousValue, E element)) { |
240 return IterableMixinWorkaround.reduce(this, combine); | 251 int length = this.length; |
| 252 if (length == 0) throw IterableElementError.noElement(); |
| 253 E value = this[0]; |
| 254 for (int i = 1; i < length; i++) { |
| 255 // TODO(22407): Improve bounds check elimination to allow this JS code to |
| 256 // be replaced by indexing. |
| 257 var element = JS('', '#[#]', this, i); |
| 258 value = combine(value, element); |
| 259 if (length != this.length) throw new ConcurrentModificationError(this); |
| 260 } |
| 261 return value; |
241 } | 262 } |
242 | 263 |
243 fold(initialValue, combine(previousValue, E element)) { | 264 fold(var initialValue, combine(var previousValue, E element)) { |
244 return IterableMixinWorkaround.fold(this, initialValue, combine); | 265 var value = initialValue; |
| 266 int length = this.length; |
| 267 for (int i = 0; i < length; i++) { |
| 268 // TODO(22407): Improve bounds check elimination to allow this JS code to |
| 269 // be replaced by indexing. |
| 270 var element = JS('', '#[#]', this, i); |
| 271 value = combine(value, element); |
| 272 if (this.length != length) throw new ConcurrentModificationError(this); |
| 273 } |
| 274 return value; |
245 } | 275 } |
246 | 276 |
247 E firstWhere(bool test(E value), {E orElse()}) { | 277 E firstWhere(bool test(E value), {E orElse()}) { |
248 var end = this.length; | 278 var end = this.length; |
249 for (int i = 0; i < end; ++i) { | 279 for (int i = 0; i < end; ++i) { |
250 // TODO(22407): Improve bounds check elimination to allow this JS code to | 280 // TODO(22407): Improve bounds check elimination to allow this JS code to |
251 // be replaced by indexing. | 281 // be replaced by indexing. |
252 var element = JS('', '#[#]', this, i); | 282 var element = JS('', '#[#]', this, i); |
253 if (test(element)) return element; | 283 if (test(element)) return element; |
254 if (this.length != end) throw new ConcurrentModificationError(this); | 284 if (this.length != end) throw new ConcurrentModificationError(this); |
255 } | 285 } |
256 if (orElse != null) return orElse(); | 286 if (orElse != null) return orElse(); |
257 throw IterableElementError.noElement(); | 287 throw IterableElementError.noElement(); |
258 } | 288 } |
259 | 289 |
260 E lastWhere(bool test(E value), {E orElse()}) { | 290 E lastWhere(bool test(E element), { E orElse() }) { |
261 return IterableMixinWorkaround.lastWhereList(this, test, orElse); | 291 int length = this.length; |
| 292 for (int i = length - 1; i >= 0; i--) { |
| 293 // TODO(22407): Improve bounds check elimination to allow this JS code to |
| 294 // be replaced by indexing. |
| 295 var element = JS('', '#[#]', this, i); |
| 296 if (test(element)) return element; |
| 297 if (length != this.length) { |
| 298 throw new ConcurrentModificationError(this); |
| 299 } |
| 300 } |
| 301 if (orElse != null) return orElse(); |
| 302 throw IterableElementError.noElement(); |
262 } | 303 } |
263 | 304 |
264 E singleWhere(bool test(E value)) { | 305 E singleWhere(bool test(E element)) { |
265 return IterableMixinWorkaround.singleWhere(this, test); | 306 int length = this.length; |
| 307 E match = null; |
| 308 bool matchFound = false; |
| 309 for (int i = 0; i < length; i++) { |
| 310 // TODO(22407): Improve bounds check elimination to allow this JS code to |
| 311 // be replaced by indexing. |
| 312 var element = JS('', '#[#]', this, i); |
| 313 if (test(element)) { |
| 314 if (matchFound) { |
| 315 throw IterableElementError.tooMany(); |
| 316 } |
| 317 matchFound = true; |
| 318 match = element; |
| 319 } |
| 320 if (length != this.length) { |
| 321 throw new ConcurrentModificationError(this); |
| 322 } |
| 323 } |
| 324 if (matchFound) return match; |
| 325 throw IterableElementError.noElement(); |
266 } | 326 } |
267 | 327 |
268 E elementAt(int index) { | 328 E elementAt(int index) { |
269 return this[index]; | 329 return this[index]; |
270 } | 330 } |
271 | 331 |
272 List<E> sublist(int start, [int end]) { | 332 List<E> sublist(int start, [int end]) { |
273 checkNull(start); // TODO(ahe): This is not specified but co19 tests it. | 333 checkNull(start); // TODO(ahe): This is not specified but co19 tests it. |
274 if (start is !int) throw new ArgumentError(start); | 334 if (start is !int) throw new ArgumentError(start); |
275 if (start < 0 || start > length) { | 335 if (start < 0 || start > length) { |
276 throw new RangeError.range(start, 0, length); | 336 throw new RangeError.range(start, 0, length); |
277 } | 337 } |
278 if (end == null) { | 338 if (end == null) { |
279 end = length; | 339 end = length; |
280 } else { | 340 } else { |
281 if (end is !int) throw new ArgumentError(end); | 341 if (end is !int) throw new ArgumentError(end); |
282 if (end < start || end > length) { | 342 if (end < start || end > length) { |
283 throw new RangeError.range(end, start, length); | 343 throw new RangeError.range(end, start, length); |
284 } | 344 } |
285 } | 345 } |
286 if (start == end) return <E>[]; | 346 if (start == end) return <E>[]; |
287 return new JSArray<E>.markGrowable( | 347 return new JSArray<E>.markGrowable( |
288 JS('', r'#.slice(#, #)', this, start, end)); | 348 JS('', r'#.slice(#, #)', this, start, end)); |
289 } | 349 } |
290 | 350 |
291 | 351 |
292 Iterable<E> getRange(int start, int end) { | 352 Iterable<E> getRange(int start, int end) { |
293 return new IterableMixinWorkaround<E>().getRangeList(this, start, end); | 353 RangeError.checkValidRange(start, end, this.length); |
| 354 return new SubListIterable<E>(this, start, end); |
294 } | 355 } |
295 | 356 |
296 E get first { | 357 E get first { |
297 if (length > 0) return this[0]; | 358 if (length > 0) return this[0]; |
298 throw IterableElementError.noElement(); | 359 throw IterableElementError.noElement(); |
299 } | 360 } |
300 | 361 |
301 E get last { | 362 E get last { |
302 if (length > 0) return this[length - 1]; | 363 if (length > 0) return this[length - 1]; |
303 throw IterableElementError.noElement(); | 364 throw IterableElementError.noElement(); |
304 } | 365 } |
305 | 366 |
306 E get single { | 367 E get single { |
307 if (length == 1) return this[0]; | 368 if (length == 1) return this[0]; |
308 if (length == 0) throw IterableElementError.noElement(); | 369 if (length == 0) throw IterableElementError.noElement(); |
309 throw IterableElementError.tooMany(); | 370 throw IterableElementError.tooMany(); |
310 } | 371 } |
311 | 372 |
312 void removeRange(int start, int end) { | 373 void removeRange(int start, int end) { |
313 checkGrowable('removeRange'); | 374 checkGrowable('removeRange'); |
314 int receiverLength = this.length; | 375 RangeError.checkValidRange(start, end, this.length); |
315 if (start < 0 || start > receiverLength) { | |
316 throw new RangeError.range(start, 0, receiverLength); | |
317 } | |
318 if (end < start || end > receiverLength) { | |
319 throw new RangeError.range(end, start, receiverLength); | |
320 } | |
321 int deleteCount = end - start; | 376 int deleteCount = end - start; |
322 JS('', '#.splice(#, #)', this, start, deleteCount); | 377 JS('', '#.splice(#, #)', this, start, deleteCount); |
323 } | 378 } |
324 | 379 |
325 void setRange(int start, int end, Iterable<E> iterable, [int skipCount = 0]) { | 380 void setRange(int start, int end, Iterable<E> iterable, [int skipCount = 0]) { |
326 checkMutable('set range'); | 381 checkMutable('set range'); |
327 IterableMixinWorkaround.setRangeList(this, start, end, iterable, skipCount); | 382 |
| 383 RangeError.checkValidRange(start, end, this.length); |
| 384 int length = end - start; |
| 385 if (length == 0) return; |
| 386 RangeError.checkNotNegative(skipCount, "skipCount"); |
| 387 |
| 388 List otherList; |
| 389 int otherStart; |
| 390 // TODO(floitsch): Make this accept more. |
| 391 if (iterable is List) { |
| 392 otherList = iterable; |
| 393 otherStart = skipCount; |
| 394 } else { |
| 395 otherList = iterable.skip(skipCount).toList(growable: false); |
| 396 otherStart = 0; |
| 397 } |
| 398 if (otherStart + length > otherList.length) { |
| 399 throw IterableElementError.tooFew(); |
| 400 } |
| 401 if (otherStart < start) { |
| 402 // Copy backwards to ensure correct copy if [from] is this. |
| 403 // TODO(sra): If [from] is the same Array as [this], we can copy without |
| 404 // type annotation checks on the stores. |
| 405 for (int i = length - 1; i >= 0; i--) { |
| 406 // Use JS to avoid bounds check (the bounds check elimination |
| 407 // optimzation is too weak). The 'E' type annotation is a store type |
| 408 // check - we can't rely on iterable, it could be List<dynamic>. |
| 409 E element = otherList[otherStart + i]; |
| 410 JS('', '#[#] = #', this, start + i, element); |
| 411 } |
| 412 } else { |
| 413 for (int i = 0; i < length; i++) { |
| 414 E element = otherList[otherStart + i]; |
| 415 JS('', '#[#] = #', this, start + i, element); |
| 416 } |
| 417 } |
328 } | 418 } |
329 | 419 |
330 void fillRange(int start, int end, [E fillValue]) { | 420 void fillRange(int start, int end, [E fillValue]) { |
331 checkMutable('fill range'); | 421 checkMutable('fill range'); |
332 IterableMixinWorkaround.fillRangeList(this, start, end, fillValue); | 422 RangeError.checkValidRange(start, end, this.length); |
| 423 for (int i = start; i < end; i++) { |
| 424 // Store is safe since [fillValue] type has been checked as parameter. |
| 425 JS('', '#[#] = #', this, i, fillValue); |
| 426 } |
333 } | 427 } |
334 | 428 |
335 void replaceRange(int start, int end, Iterable<E> iterable) { | 429 void replaceRange(int start, int end, Iterable<E> replacement) { |
336 checkGrowable('removeRange'); | 430 checkGrowable('replace range'); |
337 IterableMixinWorkaround.replaceRangeList(this, start, end, iterable); | 431 RangeError.checkValidRange(start, end, this.length); |
| 432 if (replacement is! EfficientLength) { |
| 433 replacement = replacement.toList(); |
| 434 } |
| 435 int removeLength = end - start; |
| 436 int insertLength = replacement.length; |
| 437 if (removeLength >= insertLength) { |
| 438 int delta = removeLength - insertLength; |
| 439 int insertEnd = start + insertLength; |
| 440 int newLength = this.length - delta; |
| 441 this.setRange(start, insertEnd, replacement); |
| 442 if (delta != 0) { |
| 443 this.setRange(insertEnd, newLength, this, end); |
| 444 this.length = newLength; |
| 445 } |
| 446 } else { |
| 447 int delta = insertLength - removeLength; |
| 448 int newLength = this.length + delta; |
| 449 int insertEnd = start + insertLength; // aka. end + delta. |
| 450 this.length = newLength; |
| 451 this.setRange(insertEnd, newLength, this, end); |
| 452 this.setRange(start, insertEnd, replacement); |
| 453 } |
338 } | 454 } |
339 | 455 |
340 bool any(bool test(E element)) { | 456 bool any(bool test(E element)) { |
341 int end = this.length; | 457 int end = this.length; |
342 for (int i = 0; i < end; i++) { | 458 for (int i = 0; i < end; i++) { |
343 // TODO(22407): Improve bounds check elimination to allow this JS code to | 459 // TODO(22407): Improve bounds check elimination to allow this JS code to |
344 // be replaced by indexing. | 460 // be replaced by indexing. |
345 var element = JS('', '#[#]', this, i); | 461 var element = JS('', '#[#]', this, i); |
346 if (test(element)) return true; | 462 if (test(element)) return true; |
347 if (this.length != end) throw new ConcurrentModificationError(this); | 463 if (this.length != end) throw new ConcurrentModificationError(this); |
348 } | 464 } |
349 return false; | 465 return false; |
350 } | 466 } |
351 | 467 |
352 bool every(bool test(E element)) { | 468 bool every(bool test(E element)) { |
353 int end = this.length; | 469 int end = this.length; |
354 for (int i = 0; i < end; i++) { | 470 for (int i = 0; i < end; i++) { |
355 // TODO(22407): Improve bounds check elimination to allow this JS code to | 471 // TODO(22407): Improve bounds check elimination to allow this JS code to |
356 // be replaced by indexing. | 472 // be replaced by indexing. |
357 var element = JS('', '#[#]', this, i); | 473 var element = JS('', '#[#]', this, i); |
358 if (!test(element)) return false; | 474 if (!test(element)) return false; |
359 if (this.length != end) throw new ConcurrentModificationError(this); | 475 if (this.length != end) throw new ConcurrentModificationError(this); |
360 } | 476 } |
361 return true; | 477 return true; |
362 } | 478 } |
363 | 479 |
364 Iterable<E> get reversed => | 480 Iterable<E> get reversed => new ReversedListIterable<E>(this); |
365 new IterableMixinWorkaround<E>().reversedList(this); | |
366 | 481 |
367 void sort([int compare(E a, E b)]) { | 482 void sort([int compare(E a, E b)]) { |
368 checkMutable('sort'); | 483 checkMutable('sort'); |
369 IterableMixinWorkaround.sortList(this, compare); | 484 Sort.sort(this, compare == null ? Comparable.compare : compare); |
370 } | 485 } |
371 | 486 |
372 void shuffle([Random random]) { | 487 void shuffle([Random random]) { |
373 IterableMixinWorkaround.shuffleList(this, random); | 488 checkMutable('shuffle'); |
| 489 if (random == null) random = new Random(); |
| 490 int length = this.length; |
| 491 while (length > 1) { |
| 492 int pos = random.nextInt(length); |
| 493 length -= 1; |
| 494 var tmp = this[length]; |
| 495 this[length] = this[pos]; |
| 496 this[pos] = tmp; |
| 497 } |
374 } | 498 } |
375 | 499 |
376 int indexOf(Object element, [int start = 0]) { | 500 int indexOf(Object element, [int start = 0]) { |
377 return IterableMixinWorkaround.indexOfList(this, element, start); | 501 if (start >= this.length) { |
| 502 return -1; |
| 503 } |
| 504 if (start < 0) { |
| 505 start = 0; |
| 506 } |
| 507 for (int i = start; i < this.length; i++) { |
| 508 if (this[i] == element) { |
| 509 return i; |
| 510 } |
| 511 } |
| 512 return -1; |
378 } | 513 } |
379 | 514 |
380 int lastIndexOf(Object element, [int start]) { | 515 int lastIndexOf(Object element, [int startIndex]) { |
381 return IterableMixinWorkaround.lastIndexOfList(this, element, start); | 516 if (startIndex == null) { |
| 517 startIndex = this.length - 1; |
| 518 } else { |
| 519 if (startIndex < 0) { |
| 520 return -1; |
| 521 } |
| 522 if (startIndex >= this.length) { |
| 523 startIndex = this.length - 1; |
| 524 } |
| 525 } |
| 526 for (int i = startIndex; i >= 0; i--) { |
| 527 if (this[i] == element) { |
| 528 return i; |
| 529 } |
| 530 } |
| 531 return -1; |
382 } | 532 } |
383 | 533 |
384 bool contains(Object other) { | 534 bool contains(Object other) { |
385 for (int i = 0; i < length; i++) { | 535 for (int i = 0; i < length; i++) { |
386 if (this[i] == other) return true; | 536 if (this[i] == other) return true; |
387 } | 537 } |
388 return false; | 538 return false; |
389 } | 539 } |
390 | 540 |
391 bool get isEmpty => length == 0; | 541 bool get isEmpty => length == 0; |
(...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
424 } | 574 } |
425 | 575 |
426 void operator []=(int index, E value) { | 576 void operator []=(int index, E value) { |
427 checkMutable('indexed set'); | 577 checkMutable('indexed set'); |
428 if (index is !int) throw new ArgumentError(index); | 578 if (index is !int) throw new ArgumentError(index); |
429 if (index >= length || index < 0) throw new RangeError.value(index); | 579 if (index >= length || index < 0) throw new RangeError.value(index); |
430 JS('void', r'#[#] = #', this, index, value); | 580 JS('void', r'#[#] = #', this, index, value); |
431 } | 581 } |
432 | 582 |
433 Map<int, E> asMap() { | 583 Map<int, E> asMap() { |
434 return new IterableMixinWorkaround<E>().asMapList(this); | 584 return new ListMapView<E>(this); |
435 } | 585 } |
436 } | 586 } |
437 | 587 |
438 /** | 588 /** |
439 * Dummy subclasses that allow the backend to track more precise | 589 * Dummy subclasses that allow the backend to track more precise |
440 * information about arrays through their type. The CPA type inference | 590 * information about arrays through their type. The CPA type inference |
441 * relies on the fact that these classes do not override [] nor []=. | 591 * relies on the fact that these classes do not override [] nor []=. |
442 */ | 592 */ |
443 class JSMutableArray<E> extends JSArray<E> implements JSMutableIndexable {} | 593 class JSMutableArray<E> extends JSArray<E> implements JSMutableIndexable {} |
444 class JSFixedArray<E> extends JSMutableArray<E> {} | 594 class JSFixedArray<E> extends JSMutableArray<E> {} |
445 class JSExtendableArray<E> extends JSMutableArray<E> {} | 595 class JSExtendableArray<E> extends JSMutableArray<E> {} |
OLD | NEW |