OLD | NEW |
| (Empty) |
1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | |
2 // for details. All rights reserved. Use of this source code is governed by a | |
3 // BSD-style license that can be found in the LICENSE file. | |
4 | |
5 part of _interceptors; | |
6 | |
7 /** | |
8 * The interceptor class for [List]. The compiler recognizes this | |
9 * class as an interceptor, and changes references to [:this:] to | |
10 * actually use the receiver of the method, which is generated as an extra | |
11 * argument added to each member. | |
12 */ | |
13 class JSArray<E> extends Interceptor implements List<E>, JSIndexable { | |
14 | |
15 const JSArray(); | |
16 | |
17 /** | |
18 * Returns a fresh JavaScript Array, marked as fixed-length. | |
19 * | |
20 * [length] must be a non-negative integer. | |
21 */ | |
22 factory JSArray.fixed(int length) { | |
23 // Explicit type test is necessary to guard against JavaScript conversions | |
24 // in unchecked mode. | |
25 if ((length is !int) || (length < 0)) { | |
26 throw new ArgumentError("Length must be a non-negative integer: $length"); | |
27 } | |
28 return new JSArray<E>.markFixed(JS('', 'new Array(#)', length)); | |
29 } | |
30 | |
31 /** | |
32 * Returns a fresh growable JavaScript Array of zero length length. | |
33 */ | |
34 factory JSArray.emptyGrowable() => new JSArray<E>.markGrowable(JS('', '[]')); | |
35 | |
36 /** | |
37 * Returns a fresh growable JavaScript Array with initial length. | |
38 * | |
39 * [validatedLength] must be a non-negative integer. | |
40 */ | |
41 factory JSArray.growable(int length) { | |
42 // Explicit type test is necessary to guard against JavaScript conversions | |
43 // in unchecked mode. | |
44 if ((length is !int) || (length < 0)) { | |
45 throw new ArgumentError("Length must be a non-negative integer: $length"); | |
46 } | |
47 return new JSArray<E>.markGrowable(JS('', 'new Array(#)', length)); | |
48 } | |
49 | |
50 /** | |
51 * Constructor for adding type parameters to an existing JavaScript Array. | |
52 * The compiler specially recognizes this constructor. | |
53 * | |
54 * var a = new JSArray<int>.typed(JS('JSExtendableArray', '[]')); | |
55 * a is List<int> --> true | |
56 * a is List<String> --> false | |
57 * | |
58 * Usually either the [JSArray.markFixed] or [JSArray.markGrowable] | |
59 * constructors is used instead. | |
60 * | |
61 * The input must be a JavaScript Array. The JS form is just a re-assertion | |
62 * to help type analysis when the input type is sloppy. | |
63 */ | |
64 factory JSArray.typed(allocation) => JS('JSArray', '#', allocation); | |
65 | |
66 factory JSArray.markFixed(allocation) => | |
67 JS('JSFixedArray', '#', markFixedList(new JSArray<E>.typed(allocation))); | |
68 | |
69 factory JSArray.markGrowable(allocation) => | |
70 JS('JSExtendableArray', '#', new JSArray<E>.typed(allocation)); | |
71 | |
72 static List markFixedList(List list) { | |
73 // Functions are stored in the hidden class and not as properties in | |
74 // the object. We never actually look at the value, but only want | |
75 // to know if the property exists. | |
76 JS('void', r'#.fixed$length = Array', list); | |
77 return JS('JSFixedArray', '#', list); | |
78 } | |
79 | |
80 static List markUnmodifiableList(List list) { | |
81 // Functions are stored in the hidden class and not as properties in | |
82 // the object. We never actually look at the value, but only want | |
83 // to know if the property exists. | |
84 JS('void', r'#.fixed$length = Array', list); | |
85 JS('void', r'#.immutable$list = Array', list); | |
86 return JS('JSUnmodifiableArray', '#', list); | |
87 } | |
88 | |
89 checkMutable(reason) { | |
90 if (this is !JSMutableArray) { | |
91 throw new UnsupportedError(reason); | |
92 } | |
93 } | |
94 | |
95 checkGrowable(reason) { | |
96 if (this is !JSExtendableArray) { | |
97 throw new UnsupportedError(reason); | |
98 } | |
99 } | |
100 | |
101 void add(E value) { | |
102 checkGrowable('add'); | |
103 JS('void', r'#.push(#)', this, value); | |
104 } | |
105 | |
106 E removeAt(int index) { | |
107 checkGrowable('removeAt'); | |
108 if (index is !int) throw argumentErrorValue(index); | |
109 if (index < 0 || index >= length) { | |
110 throw new RangeError.value(index); | |
111 } | |
112 return JS('var', r'#.splice(#, 1)[0]', this, index); | |
113 } | |
114 | |
115 void insert(int index, E value) { | |
116 checkGrowable('insert'); | |
117 if (index is !int) throw argumentErrorValue(index); | |
118 if (index < 0 || index > length) { | |
119 throw new RangeError.value(index); | |
120 } | |
121 JS('void', r'#.splice(#, 0, #)', this, index, value); | |
122 } | |
123 | |
124 void insertAll(int index, Iterable<E> iterable) { | |
125 checkGrowable('insertAll'); | |
126 RangeError.checkValueInInterval(index, 0, this.length, "index"); | |
127 if (iterable is! EfficientLength) { | |
128 iterable = iterable.toList(); | |
129 } | |
130 int insertionLength = iterable.length; | |
131 this.length += insertionLength; | |
132 int end = index + insertionLength; | |
133 this.setRange(end, this.length, this, index); | |
134 this.setRange(index, end, iterable); | |
135 } | |
136 | |
137 void setAll(int index, Iterable<E> iterable) { | |
138 checkMutable('setAll'); | |
139 RangeError.checkValueInInterval(index, 0, this.length, "index"); | |
140 for (var element in iterable) { | |
141 this[index++] = element; | |
142 } | |
143 } | |
144 | |
145 E removeLast() { | |
146 checkGrowable('removeLast'); | |
147 if (length == 0) throw new RangeError.value(-1); | |
148 return JS('var', r'#.pop()', this); | |
149 } | |
150 | |
151 bool remove(Object element) { | |
152 checkGrowable('remove'); | |
153 for (int i = 0; i < this.length; i++) { | |
154 if (this[i] == element) { | |
155 JS('var', r'#.splice(#, 1)', this, i); | |
156 return true; | |
157 } | |
158 } | |
159 return false; | |
160 } | |
161 | |
162 /** | |
163 * Removes elements matching [test] from [this] List. | |
164 */ | |
165 void removeWhere(bool test(E element)) { | |
166 checkGrowable('removeWhere'); | |
167 _removeWhere(test, true); | |
168 } | |
169 | |
170 void retainWhere(bool test(E element)) { | |
171 checkGrowable('retainWhere'); | |
172 _removeWhere(test, false); | |
173 } | |
174 | |
175 void _removeWhere(bool test(E element), bool removeMatching) { | |
176 // Performed in two steps, to avoid exposing an inconsistent state | |
177 // to the [test] function. First the elements to retain are found, and then | |
178 // the original list is updated to contain those elements. | |
179 | |
180 // TODO(sra): Replace this algorthim with one that retains a list of ranges | |
181 // to be removed. Most real uses remove 0, 1 or a few clustered elements. | |
182 | |
183 List retained = []; | |
184 int end = this.length; | |
185 for (int i = 0; i < end; i++) { | |
186 // TODO(22407): Improve bounds check elimination to allow this JS code to | |
187 // be replaced by indexing. | |
188 var element = JS('', '#[#]', this, i); | |
189 // !test() ensures bool conversion in checked mode. | |
190 if (!test(element) == removeMatching) { | |
191 retained.add(element); | |
192 } | |
193 if (this.length != end) throw new ConcurrentModificationError(this); | |
194 } | |
195 if (retained.length == end) return; | |
196 this.length = retained.length; | |
197 for (int i = 0; i < retained.length; i++) { | |
198 this[i] = retained[i]; | |
199 } | |
200 } | |
201 | |
202 Iterable<E> where(bool f(E element)) { | |
203 return new WhereIterable<E>(this, f); | |
204 } | |
205 | |
206 Iterable expand(Iterable f(E element)) { | |
207 return new ExpandIterable<E, dynamic>(this, f); | |
208 } | |
209 | |
210 void addAll(Iterable<E> collection) { | |
211 checkGrowable('addAll'); | |
212 for (E e in collection) { | |
213 JS('void', r'#.push(#)', this, e); | |
214 } | |
215 } | |
216 | |
217 void clear() { | |
218 length = 0; | |
219 } | |
220 | |
221 void forEach(void f(E element)) { | |
222 int end = this.length; | |
223 for (int i = 0; i < end; i++) { | |
224 // TODO(22407): Improve bounds check elimination to allow this JS code to | |
225 // be replaced by indexing. | |
226 var element = JS('', '#[#]', this, i); | |
227 f(element); | |
228 if (this.length != end) throw new ConcurrentModificationError(this); | |
229 } | |
230 } | |
231 | |
232 Iterable map(f(E element)) { | |
233 return new MappedListIterable(this, f); | |
234 } | |
235 | |
236 String join([String separator = ""]) { | |
237 var list = new List(this.length); | |
238 for (int i = 0; i < this.length; i++) { | |
239 list[i] = "${this[i]}"; | |
240 } | |
241 return JS('String', "#.join(#)", list, separator); | |
242 } | |
243 | |
244 Iterable<E> take(int n) { | |
245 return new SubListIterable<E>(this, 0, n); | |
246 } | |
247 | |
248 Iterable<E> takeWhile(bool test(E value)) { | |
249 return new TakeWhileIterable<E>(this, test); | |
250 } | |
251 | |
252 Iterable<E> skip(int n) { | |
253 return new SubListIterable<E>(this, n, null); | |
254 } | |
255 | |
256 Iterable<E> skipWhile(bool test(E value)) { | |
257 return new SkipWhileIterable<E>(this, test); | |
258 } | |
259 | |
260 E reduce(E combine(E previousValue, E element)) { | |
261 int length = this.length; | |
262 if (length == 0) throw IterableElementError.noElement(); | |
263 E value = this[0]; | |
264 for (int i = 1; i < length; i++) { | |
265 // TODO(22407): Improve bounds check elimination to allow this JS code to | |
266 // be replaced by indexing. | |
267 var element = JS('', '#[#]', this, i); | |
268 value = combine(value, element); | |
269 if (length != this.length) throw new ConcurrentModificationError(this); | |
270 } | |
271 return value; | |
272 } | |
273 | |
274 fold(var initialValue, combine(var previousValue, E element)) { | |
275 var value = initialValue; | |
276 int length = this.length; | |
277 for (int i = 0; i < length; i++) { | |
278 // TODO(22407): Improve bounds check elimination to allow this JS code to | |
279 // be replaced by indexing. | |
280 var element = JS('', '#[#]', this, i); | |
281 value = combine(value, element); | |
282 if (this.length != length) throw new ConcurrentModificationError(this); | |
283 } | |
284 return value; | |
285 } | |
286 | |
287 E firstWhere(bool test(E value), {E orElse()}) { | |
288 var end = this.length; | |
289 for (int i = 0; i < end; ++i) { | |
290 // TODO(22407): Improve bounds check elimination to allow this JS code to | |
291 // be replaced by indexing. | |
292 var element = JS('', '#[#]', this, i); | |
293 if (test(element)) return element; | |
294 if (this.length != end) throw new ConcurrentModificationError(this); | |
295 } | |
296 if (orElse != null) return orElse(); | |
297 throw IterableElementError.noElement(); | |
298 } | |
299 | |
300 E lastWhere(bool test(E element), { E orElse() }) { | |
301 int length = this.length; | |
302 for (int i = length - 1; i >= 0; i--) { | |
303 // TODO(22407): Improve bounds check elimination to allow this JS code to | |
304 // be replaced by indexing. | |
305 var element = JS('', '#[#]', this, i); | |
306 if (test(element)) return element; | |
307 if (length != this.length) { | |
308 throw new ConcurrentModificationError(this); | |
309 } | |
310 } | |
311 if (orElse != null) return orElse(); | |
312 throw IterableElementError.noElement(); | |
313 } | |
314 | |
315 E singleWhere(bool test(E element)) { | |
316 int length = this.length; | |
317 E match = null; | |
318 bool matchFound = false; | |
319 for (int i = 0; i < length; i++) { | |
320 // TODO(22407): Improve bounds check elimination to allow this JS code to | |
321 // be replaced by indexing. | |
322 var element = JS('', '#[#]', this, i); | |
323 if (test(element)) { | |
324 if (matchFound) { | |
325 throw IterableElementError.tooMany(); | |
326 } | |
327 matchFound = true; | |
328 match = element; | |
329 } | |
330 if (length != this.length) { | |
331 throw new ConcurrentModificationError(this); | |
332 } | |
333 } | |
334 if (matchFound) return match; | |
335 throw IterableElementError.noElement(); | |
336 } | |
337 | |
338 E elementAt(int index) { | |
339 return this[index]; | |
340 } | |
341 | |
342 List<E> sublist(int start, [int end]) { | |
343 checkNull(start); // TODO(ahe): This is not specified but co19 tests it. | |
344 if (start is !int) throw argumentErrorValue(start); | |
345 if (start < 0 || start > length) { | |
346 throw new RangeError.range(start, 0, length); | |
347 } | |
348 if (end == null) { | |
349 end = length; | |
350 } else { | |
351 if (end is !int) throw argumentErrorValue(end); | |
352 if (end < start || end > length) { | |
353 throw new RangeError.range(end, start, length); | |
354 } | |
355 } | |
356 if (start == end) return <E>[]; | |
357 return new JSArray<E>.markGrowable( | |
358 JS('', r'#.slice(#, #)', this, start, end)); | |
359 } | |
360 | |
361 | |
362 Iterable<E> getRange(int start, int end) { | |
363 RangeError.checkValidRange(start, end, this.length); | |
364 return new SubListIterable<E>(this, start, end); | |
365 } | |
366 | |
367 E get first { | |
368 if (length > 0) return this[0]; | |
369 throw IterableElementError.noElement(); | |
370 } | |
371 | |
372 E get last { | |
373 if (length > 0) return this[length - 1]; | |
374 throw IterableElementError.noElement(); | |
375 } | |
376 | |
377 E get single { | |
378 if (length == 1) return this[0]; | |
379 if (length == 0) throw IterableElementError.noElement(); | |
380 throw IterableElementError.tooMany(); | |
381 } | |
382 | |
383 void removeRange(int start, int end) { | |
384 checkGrowable('removeRange'); | |
385 RangeError.checkValidRange(start, end, this.length); | |
386 int deleteCount = end - start; | |
387 JS('', '#.splice(#, #)', this, start, deleteCount); | |
388 } | |
389 | |
390 void setRange(int start, int end, Iterable<E> iterable, [int skipCount = 0]) { | |
391 checkMutable('set range'); | |
392 | |
393 RangeError.checkValidRange(start, end, this.length); | |
394 int length = end - start; | |
395 if (length == 0) return; | |
396 RangeError.checkNotNegative(skipCount, "skipCount"); | |
397 | |
398 List otherList; | |
399 int otherStart; | |
400 // TODO(floitsch): Make this accept more. | |
401 if (iterable is List) { | |
402 otherList = iterable; | |
403 otherStart = skipCount; | |
404 } else { | |
405 otherList = iterable.skip(skipCount).toList(growable: false); | |
406 otherStart = 0; | |
407 } | |
408 if (otherStart + length > otherList.length) { | |
409 throw IterableElementError.tooFew(); | |
410 } | |
411 if (otherStart < start) { | |
412 // Copy backwards to ensure correct copy if [from] is this. | |
413 // TODO(sra): If [from] is the same Array as [this], we can copy without | |
414 // type annotation checks on the stores. | |
415 for (int i = length - 1; i >= 0; i--) { | |
416 // Use JS to avoid bounds check (the bounds check elimination | |
417 // optimzation is too weak). The 'E' type annotation is a store type | |
418 // check - we can't rely on iterable, it could be List<dynamic>. | |
419 E element = otherList[otherStart + i]; | |
420 JS('', '#[#] = #', this, start + i, element); | |
421 } | |
422 } else { | |
423 for (int i = 0; i < length; i++) { | |
424 E element = otherList[otherStart + i]; | |
425 JS('', '#[#] = #', this, start + i, element); | |
426 } | |
427 } | |
428 } | |
429 | |
430 void fillRange(int start, int end, [E fillValue]) { | |
431 checkMutable('fill range'); | |
432 RangeError.checkValidRange(start, end, this.length); | |
433 for (int i = start; i < end; i++) { | |
434 // Store is safe since [fillValue] type has been checked as parameter. | |
435 JS('', '#[#] = #', this, i, fillValue); | |
436 } | |
437 } | |
438 | |
439 void replaceRange(int start, int end, Iterable<E> replacement) { | |
440 checkGrowable('replace range'); | |
441 RangeError.checkValidRange(start, end, this.length); | |
442 if (replacement is! EfficientLength) { | |
443 replacement = replacement.toList(); | |
444 } | |
445 int removeLength = end - start; | |
446 int insertLength = replacement.length; | |
447 if (removeLength >= insertLength) { | |
448 int delta = removeLength - insertLength; | |
449 int insertEnd = start + insertLength; | |
450 int newLength = this.length - delta; | |
451 this.setRange(start, insertEnd, replacement); | |
452 if (delta != 0) { | |
453 this.setRange(insertEnd, newLength, this, end); | |
454 this.length = newLength; | |
455 } | |
456 } else { | |
457 int delta = insertLength - removeLength; | |
458 int newLength = this.length + delta; | |
459 int insertEnd = start + insertLength; // aka. end + delta. | |
460 this.length = newLength; | |
461 this.setRange(insertEnd, newLength, this, end); | |
462 this.setRange(start, insertEnd, replacement); | |
463 } | |
464 } | |
465 | |
466 bool any(bool test(E element)) { | |
467 int end = this.length; | |
468 for (int i = 0; i < end; i++) { | |
469 // TODO(22407): Improve bounds check elimination to allow this JS code to | |
470 // be replaced by indexing. | |
471 var element = JS('', '#[#]', this, i); | |
472 if (test(element)) return true; | |
473 if (this.length != end) throw new ConcurrentModificationError(this); | |
474 } | |
475 return false; | |
476 } | |
477 | |
478 bool every(bool test(E element)) { | |
479 int end = this.length; | |
480 for (int i = 0; i < end; i++) { | |
481 // TODO(22407): Improve bounds check elimination to allow this JS code to | |
482 // be replaced by indexing. | |
483 var element = JS('', '#[#]', this, i); | |
484 if (!test(element)) return false; | |
485 if (this.length != end) throw new ConcurrentModificationError(this); | |
486 } | |
487 return true; | |
488 } | |
489 | |
490 Iterable<E> get reversed => new ReversedListIterable<E>(this); | |
491 | |
492 void sort([int compare(E a, E b)]) { | |
493 checkMutable('sort'); | |
494 Sort.sort(this, compare == null ? Comparable.compare : compare); | |
495 } | |
496 | |
497 void shuffle([Random random]) { | |
498 checkMutable('shuffle'); | |
499 if (random == null) random = new Random(); | |
500 int length = this.length; | |
501 while (length > 1) { | |
502 int pos = random.nextInt(length); | |
503 length -= 1; | |
504 var tmp = this[length]; | |
505 this[length] = this[pos]; | |
506 this[pos] = tmp; | |
507 } | |
508 } | |
509 | |
510 int indexOf(Object element, [int start = 0]) { | |
511 if (start >= this.length) { | |
512 return -1; | |
513 } | |
514 if (start < 0) { | |
515 start = 0; | |
516 } | |
517 for (int i = start; i < this.length; i++) { | |
518 if (this[i] == element) { | |
519 return i; | |
520 } | |
521 } | |
522 return -1; | |
523 } | |
524 | |
525 int lastIndexOf(Object element, [int startIndex]) { | |
526 if (startIndex == null) { | |
527 startIndex = this.length - 1; | |
528 } else { | |
529 if (startIndex < 0) { | |
530 return -1; | |
531 } | |
532 if (startIndex >= this.length) { | |
533 startIndex = this.length - 1; | |
534 } | |
535 } | |
536 for (int i = startIndex; i >= 0; i--) { | |
537 if (this[i] == element) { | |
538 return i; | |
539 } | |
540 } | |
541 return -1; | |
542 } | |
543 | |
544 bool contains(Object other) { | |
545 for (int i = 0; i < length; i++) { | |
546 if (this[i] == other) return true; | |
547 } | |
548 return false; | |
549 } | |
550 | |
551 bool get isEmpty => length == 0; | |
552 | |
553 bool get isNotEmpty => !isEmpty; | |
554 | |
555 String toString() => ListBase.listToString(this); | |
556 | |
557 List<E> toList({ bool growable: true }) => | |
558 growable | |
559 ? _toListGrowable() | |
560 : _toListFixed(); | |
561 | |
562 List<E> _toListGrowable() => | |
563 new JSArray<E>.markGrowable(JS('', '#.slice()', this)); | |
564 | |
565 List<E> _toListFixed() => | |
566 new JSArray<E>.markFixed(JS('', '#.slice()', this)); | |
567 | |
568 Set<E> toSet() => new Set<E>.from(this); | |
569 | |
570 Iterator<E> get iterator => new ArrayIterator<E>(this); | |
571 | |
572 int get hashCode => Primitives.objectHashCode(this); | |
573 | |
574 int get length => JS('JSUInt32', r'#.length', this); | |
575 | |
576 void set length(int newLength) { | |
577 checkGrowable('set length'); | |
578 if (newLength is !int) { | |
579 throw new ArgumentError.value(newLength, 'newLength'); | |
580 } | |
581 // TODO(sra): Remove this test and let JavaScript throw an error. | |
582 if (newLength < 0) { | |
583 throw new RangeError.range(newLength, 0, null, 'newLength'); | |
584 } | |
585 // JavaScript with throw a RangeError for numbers that are too big. The | |
586 // message does not contain the value. | |
587 JS('void', r'#.length = #', this, newLength); | |
588 } | |
589 | |
590 E operator [](int index) { | |
591 if (index is !int) throw diagnoseIndexError(this, index); | |
592 if (index >= length || index < 0) throw diagnoseIndexError(this, index); | |
593 return JS('var', '#[#]', this, index); | |
594 } | |
595 | |
596 void operator []=(int index, E value) { | |
597 checkMutable('indexed set'); | |
598 if (index is !int) throw diagnoseIndexError(this, index); | |
599 if (index >= length || index < 0) throw diagnoseIndexError(this, index); | |
600 JS('void', r'#[#] = #', this, index, value); | |
601 } | |
602 | |
603 Map<int, E> asMap() { | |
604 return new ListMapView<E>(this); | |
605 } | |
606 } | |
607 | |
608 /** | |
609 * Dummy subclasses that allow the backend to track more precise | |
610 * information about arrays through their type. The CPA type inference | |
611 * relies on the fact that these classes do not override [] nor []=. | |
612 * | |
613 * These classes are really a fiction, and can have no methods, since | |
614 * getInterceptor always returns JSArray. We should consider pushing the | |
615 * 'isGrowable' and 'isMutable' checks into the getInterceptor implementation so | |
616 * these classes can have specialized implementations. Doing so will challenge | |
617 * many assuptions in the JS backend. | |
618 */ | |
619 class JSMutableArray<E> extends JSArray<E> implements JSMutableIndexable {} | |
620 class JSFixedArray<E> extends JSMutableArray<E> {} | |
621 class JSExtendableArray<E> extends JSMutableArray<E> {} | |
622 class JSUnmodifiableArray<E> extends JSArray<E> {} // Already is JSIndexable. | |
623 | |
624 | |
625 /// An [Iterator] that iterates a JSArray. | |
626 /// | |
627 class ArrayIterator<E> implements Iterator<E> { | |
628 final JSArray<E> _iterable; | |
629 final int _length; | |
630 int _index; | |
631 E _current; | |
632 | |
633 ArrayIterator(JSArray<E> iterable) | |
634 : _iterable = iterable, _length = iterable.length, _index = 0; | |
635 | |
636 E get current => _current; | |
637 | |
638 bool moveNext() { | |
639 int length = _iterable.length; | |
640 | |
641 // We have to do the length check even on fixed length Arrays. If we can | |
642 // inline moveNext() we might be able to GVN the length and eliminate this | |
643 // check on known fixed length JSArray. | |
644 if (_length != length) { | |
645 throw new ConcurrentModificationError(_iterable); | |
646 } | |
647 | |
648 if (_index >= length) { | |
649 _current = null; | |
650 return false; | |
651 } | |
652 _current = _iterable[_index]; | |
653 _index++; | |
654 return true; | |
655 } | |
656 } | |
OLD | NEW |