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 |
11 * argument added to each member. | 11 * argument added to each member. |
12 */ | 12 */ |
13 @JsName(name: 'Array') | 13 @JsPeerInterface(name: 'Array') |
14 class JSArray<E> implements List<E>, JSIndexable { | 14 class JSArray<E> implements List<E>, JSIndexable { |
15 | 15 |
16 const JSArray(); | 16 const JSArray(); |
17 | 17 |
18 /** | 18 /** |
19 * Returns a fresh JavaScript Array, marked as fixed-length. | 19 * Constructor for adding type parameters to an existing JavaScript Array. |
20 * | |
21 * [length] must be a non-negative integer. | |
22 */ | 20 */ |
23 factory JSArray.fixed(int length) { | 21 factory JSArray.typed(allocation) => |
24 // Explicit type test is necessary to guard against JavaScript conversions | 22 // TODO(jmesserly): skip this when E is dynamic and Object. |
25 // in unchecked mode. | 23 JS('-dynamic', 'dart.list(#, #)', allocation, E); |
26 if ((length is !int) || (length < 0)) { | |
27 throw new ArgumentError("Length must be a non-negative integer: $length"); | |
28 } | |
29 return new JSArray<E>.markFixed(JS('', 'new Array(#)', length)); | |
30 } | |
31 | 24 |
32 /** | 25 // TODO(jmesserly): consider a fixed array subclass instead. |
33 * Returns a fresh growable JavaScript Array of zero length length. | 26 factory JSArray.markFixed(allocation) => |
34 */ | 27 new JSArray<E>.typed(markFixedList(allocation)); |
35 factory JSArray.emptyGrowable() => new JSArray<E>.markGrowable(JS('', '[]')); | |
36 | 28 |
37 /** | 29 factory JSArray.markGrowable(allocation) = JSArray.typed; |
38 * Returns a fresh growable JavaScript Array with initial length. | |
39 * | |
40 * [validatedLength] must be a non-negative integer. | |
41 */ | |
42 factory JSArray.growable(int length) { | |
43 // Explicit type test is necessary to guard against JavaScript conversions | |
44 // in unchecked mode. | |
45 if ((length is !int) || (length < 0)) { | |
46 throw new ArgumentError("Length must be a non-negative integer: $length"); | |
47 } | |
48 return new JSArray<E>.markGrowable(JS('', 'new Array(#)', length)); | |
49 } | |
50 | |
51 /** | |
52 * Constructor for adding type parameters to an existing JavaScript Array. | |
53 * The compiler specially recognizes this constructor. | |
54 * | |
55 * var a = new JSArray<int>.typed(JS('JSExtendableArray', '[]')); | |
56 * a is List<int> --> true | |
57 * a is List<String> --> false | |
58 * | |
59 * Usually either the [JSArray.markFixed] or [JSArray.markGrowable] | |
60 * constructors is used instead. | |
61 * | |
62 * The input must be a JavaScript Array. The JS form is just a re-assertion | |
63 * to help type analysis when the input type is sloppy. | |
64 */ | |
65 factory JSArray.typed(allocation) => JS('JSArray', '#', allocation); | |
66 | |
67 factory JSArray.markFixed(allocation) => | |
68 JS('JSFixedArray', '#', markFixedList(new JSArray<E>.typed(allocation))); | |
69 | |
70 factory JSArray.markGrowable(allocation) => | |
71 JS('JSExtendableArray', '#', new JSArray<E>.typed(allocation)); | |
72 | 30 |
73 static List markFixedList(List list) { | 31 static List markFixedList(List list) { |
74 // Functions are stored in the hidden class and not as properties in | 32 // Functions are stored in the hidden class and not as properties in |
75 // the object. We never actually look at the value, but only want | 33 // the object. We never actually look at the value, but only want |
76 // to know if the property exists. | 34 // to know if the property exists. |
77 JS('void', r'#.fixed$length = Array', list); | 35 JS('void', r'#.fixed$length = Array', list); |
78 return JS('JSFixedArray', '#', list); | 36 return list; |
79 } | |
80 | |
81 checkMutable(reason) { | |
82 if (this is !JSMutableArray) { | |
83 throw new UnsupportedError(reason); | |
84 } | |
85 } | 37 } |
86 | 38 |
87 checkGrowable(reason) { | 39 checkGrowable(reason) { |
88 if (JS('bool', r'#.fixed$length', this)) { | 40 if (JS('bool', r'#.fixed$length', this)) { |
89 throw new UnsupportedError(reason); | 41 throw new UnsupportedError(reason); |
90 } | 42 } |
91 } | 43 } |
92 | 44 |
93 void add(E value) { | 45 void add(E value) { |
94 checkGrowable('add'); | 46 checkGrowable('add'); |
95 JS('void', r'#.push(#)', this, value); | 47 JS('void', r'#.push(#)', this, value); |
96 } | 48 } |
97 | 49 |
98 E removeAt(int index) { | 50 E removeAt(int index) { |
99 if (index is !int) throw new ArgumentError(index); | 51 if (index is !int) throw new ArgumentError(index); |
100 if (index < 0 || index >= length) { | 52 if (index < 0 || index >= length) { |
101 throw new RangeError.value(index); | 53 throw new RangeError.value(index); |
102 } | 54 } |
103 checkGrowable('removeAt'); | 55 checkGrowable('removeAt'); |
104 return JS('var', r'#.splice(#, 1)[0]', this, index); | 56 return JS('-dynamic', r'#.splice(#, 1)[0]', this, index); |
105 } | 57 } |
106 | 58 |
107 void insert(int index, E value) { | 59 void insert(int index, E value) { |
108 if (index is !int) throw new ArgumentError(index); | 60 if (index is !int) throw new ArgumentError(index); |
109 if (index < 0 || index > length) { | 61 if (index < 0 || index > length) { |
110 throw new RangeError.value(index); | 62 throw new RangeError.value(index); |
111 } | 63 } |
112 checkGrowable('insert'); | 64 checkGrowable('insert'); |
113 JS('void', r'#.splice(#, 0, #)', this, index, value); | 65 JS('void', r'#.splice(#, 0, #)', this, index, value); |
114 } | 66 } |
115 | 67 |
116 void insertAll(int index, Iterable<E> iterable) { | 68 void insertAll(int index, Iterable<E> iterable) { |
117 checkGrowable('insertAll'); | 69 checkGrowable('insertAll'); |
118 IterableMixinWorkaround.insertAllList(this, index, iterable); | 70 IterableMixinWorkaround.insertAllList(this, index, iterable); |
119 } | 71 } |
120 | 72 |
121 void setAll(int index, Iterable<E> iterable) { | 73 void setAll(int index, Iterable<E> iterable) { |
122 checkMutable('setAll'); | |
123 IterableMixinWorkaround.setAllList(this, index, iterable); | 74 IterableMixinWorkaround.setAllList(this, index, iterable); |
124 } | 75 } |
125 | 76 |
126 E removeLast() { | 77 E removeLast() { |
127 checkGrowable('removeLast'); | 78 checkGrowable('removeLast'); |
128 if (length == 0) throw new RangeError.value(-1); | 79 if (length == 0) throw new RangeError.value(-1); |
129 return JS('var', r'#.pop()', this); | 80 return JS('var', r'#.pop()', this); |
130 } | 81 } |
131 | 82 |
132 bool remove(Object element) { | 83 bool remove(Object element) { |
(...skipping 105 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
238 } | 189 } |
239 if (end == null) { | 190 if (end == null) { |
240 end = length; | 191 end = length; |
241 } else { | 192 } else { |
242 if (end is !int) throw new ArgumentError(end); | 193 if (end is !int) throw new ArgumentError(end); |
243 if (end < start || end > length) { | 194 if (end < start || end > length) { |
244 throw new RangeError.range(end, start, length); | 195 throw new RangeError.range(end, start, length); |
245 } | 196 } |
246 } | 197 } |
247 if (start == end) return <E>[]; | 198 if (start == end) return <E>[]; |
248 return new JSArray<E>.markGrowable( | 199 return new JSArray<E>.typed( |
249 JS('', r'#.slice(#, #)', this, start, end)); | 200 JS('', r'#.slice(#, #)', this, start, end)); |
250 } | 201 } |
251 | 202 |
252 | 203 |
253 Iterable<E> getRange(int start, int end) { | 204 Iterable<E> getRange(int start, int end) { |
254 return new IterableMixinWorkaround<E>().getRangeList(this, start, end); | 205 return new IterableMixinWorkaround<E>().getRangeList(this, start, end); |
255 } | 206 } |
256 | 207 |
257 E get first { | 208 E get first { |
258 if (length > 0) return this[0]; | 209 if (length > 0) return this[0]; |
(...skipping 22 matching lines...) Expand all Loading... |
281 } | 232 } |
282 Lists.copy(this, | 233 Lists.copy(this, |
283 end, | 234 end, |
284 this, | 235 this, |
285 start, | 236 start, |
286 receiverLength - end); | 237 receiverLength - end); |
287 this.length = receiverLength - (end - start); | 238 this.length = receiverLength - (end - start); |
288 } | 239 } |
289 | 240 |
290 void setRange(int start, int end, Iterable<E> iterable, [int skipCount = 0]) { | 241 void setRange(int start, int end, Iterable<E> iterable, [int skipCount = 0]) { |
291 checkMutable('set range'); | |
292 IterableMixinWorkaround.setRangeList(this, start, end, iterable, skipCount); | 242 IterableMixinWorkaround.setRangeList(this, start, end, iterable, skipCount); |
293 } | 243 } |
294 | 244 |
295 void fillRange(int start, int end, [E fillValue]) { | 245 void fillRange(int start, int end, [E fillValue]) { |
296 checkMutable('fill range'); | |
297 IterableMixinWorkaround.fillRangeList(this, start, end, fillValue); | 246 IterableMixinWorkaround.fillRangeList(this, start, end, fillValue); |
298 } | 247 } |
299 | 248 |
300 void replaceRange(int start, int end, Iterable<E> iterable) { | 249 void replaceRange(int start, int end, Iterable<E> iterable) { |
301 checkGrowable('removeRange'); | |
302 IterableMixinWorkaround.replaceRangeList(this, start, end, iterable); | 250 IterableMixinWorkaround.replaceRangeList(this, start, end, iterable); |
303 } | 251 } |
304 | 252 |
305 bool any(bool f(E element)) => IterableMixinWorkaround.any(this, f); | 253 bool any(bool f(E element)) => IterableMixinWorkaround.any(this, f); |
306 | 254 |
307 bool every(bool f(E element)) => IterableMixinWorkaround.every(this, f); | 255 bool every(bool f(E element)) => IterableMixinWorkaround.every(this, f); |
308 | 256 |
309 Iterable<E> get reversed => | 257 Iterable<E> get reversed => |
310 new IterableMixinWorkaround<E>().reversedList(this); | 258 new IterableMixinWorkaround<E>().reversedList(this); |
311 | 259 |
312 void sort([int compare(E a, E b)]) { | 260 void sort([int compare(E a, E b)]) { |
313 checkMutable('sort'); | |
314 IterableMixinWorkaround.sortList(this, compare); | 261 IterableMixinWorkaround.sortList(this, compare); |
315 } | 262 } |
316 | 263 |
317 void shuffle([Random random]) { | 264 void shuffle([Random random]) { |
318 IterableMixinWorkaround.shuffleList(this, random); | 265 IterableMixinWorkaround.shuffleList(this, random); |
319 } | 266 } |
320 | 267 |
321 int indexOf(Object element, [int start = 0]) { | 268 int indexOf(Object element, [int start = 0]) { |
322 return IterableMixinWorkaround.indexOfList(this, element, start); | 269 return IterableMixinWorkaround.indexOfList(this, element, start); |
323 } | 270 } |
324 | 271 |
325 int lastIndexOf(Object element, [int start]) { | 272 int lastIndexOf(Object element, [int start]) { |
326 return IterableMixinWorkaround.lastIndexOfList(this, element, start); | 273 return IterableMixinWorkaround.lastIndexOfList(this, element, start); |
327 } | 274 } |
328 | 275 |
329 bool contains(Object other) { | 276 bool contains(Object other) { |
330 for (int i = 0; i < length; i++) { | 277 for (int i = 0; i < length; i++) { |
331 if (this[i] == other) return true; | 278 if (this[i] == other) return true; |
332 } | 279 } |
333 return false; | 280 return false; |
334 } | 281 } |
335 | 282 |
336 bool get isEmpty => length == 0; | 283 bool get isEmpty => length == 0; |
337 | 284 |
338 bool get isNotEmpty => !isEmpty; | 285 bool get isNotEmpty => !isEmpty; |
339 | 286 |
340 String toString() => ListBase.listToString(this); | 287 String toString() => ListBase.listToString(this); |
341 | 288 |
342 List<E> toList({ bool growable: true }) { | 289 List<E> toList({ bool growable: true }) { |
343 if (growable) { | 290 var list = JS('', '#.slice()', this); |
344 return new JSArray<E>.markGrowable(JS('', '#.slice()', this)); | 291 if (!growable) markFixedList(list); |
345 } else { | 292 return new JSArray<E>.typed(list); |
346 return new JSArray<E>.markFixed(JS('', '#.slice()', this)); | |
347 } | |
348 } | 293 } |
349 | 294 |
350 Set<E> toSet() => new Set<E>.from(this); | 295 Set<E> toSet() => new Set<E>.from(this); |
351 | 296 |
352 Iterator<E> get iterator => new ListIterator<E>(this); | 297 Iterator<E> get iterator => new ListIterator<E>(this); |
353 | 298 |
354 int get hashCode => Primitives.objectHashCode(this); | 299 int get hashCode => Primitives.objectHashCode(this); |
355 | 300 |
356 int get length => JS('JSUInt32', r'#.length', this); | 301 int get length => JS('JSUInt32', r'#.length', this); |
357 | 302 |
358 void set length(int newLength) { | 303 void set length(int newLength) { |
359 if (newLength is !int) throw new ArgumentError(newLength); | 304 if (newLength is !int) throw new ArgumentError(newLength); |
360 if (newLength < 0) throw new RangeError.value(newLength); | 305 if (newLength < 0) throw new RangeError.value(newLength); |
361 checkGrowable('set length'); | 306 checkGrowable('set length'); |
362 JS('void', r'#.length = #', this, newLength); | 307 JS('void', r'#.length = #', this, newLength); |
363 } | 308 } |
364 | 309 |
365 E operator [](int index) { | 310 E operator [](int index) { |
366 if (index is !int) throw new ArgumentError(index); | 311 if (index is !int) throw new ArgumentError(index); |
367 if (index >= length || index < 0) throw new RangeError.value(index); | 312 if (index >= length || index < 0) throw new RangeError.value(index); |
368 return JS('var', '#[#]', this, index); | 313 return JS('var', '#[#]', this, index); |
369 } | 314 } |
370 | 315 |
371 void operator []=(int index, E value) { | 316 void operator []=(int index, E value) { |
372 checkMutable('indexed set'); | |
373 if (index is !int) throw new ArgumentError(index); | 317 if (index is !int) throw new ArgumentError(index); |
374 if (index >= length || index < 0) throw new RangeError.value(index); | 318 if (index >= length || index < 0) throw new RangeError.value(index); |
375 JS('void', r'#[#] = #', this, index, value); | 319 JS('void', r'#[#] = #', this, index, value); |
376 } | 320 } |
377 | 321 |
378 Map<int, E> asMap() { | 322 Map<int, E> asMap() { |
379 return new IterableMixinWorkaround<E>().asMapList(this); | 323 return new IterableMixinWorkaround<E>().asMapList(this); |
380 } | 324 } |
381 } | 325 } |
382 | 326 |
383 /** | 327 /** |
384 * Dummy subclasses that allow the backend to track more precise | 328 * Dummy subclasses that allow the backend to track more precise |
385 * information about arrays through their type. The CPA type inference | 329 * information about arrays through their type. The CPA type inference |
386 * relies on the fact that these classes do not override [] nor []=. | 330 * relies on the fact that these classes do not override [] nor []=. |
387 */ | 331 */ |
388 class JSMutableArray<E> extends JSArray<E> implements JSMutableIndexable {} | 332 class JSMutableArray<E> extends JSArray<E> implements JSMutableIndexable {} |
389 class JSFixedArray<E> extends JSMutableArray<E> {} | 333 class JSFixedArray<E> extends JSMutableArray<E> {} |
390 class JSExtendableArray<E> extends JSMutableArray<E> {} | 334 class JSExtendableArray<E> extends JSMutableArray<E> {} |
OLD | NEW |