| 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 checkMutable(reason) { | |
| 81 if (this is !JSMutableArray) { | |
| 82 throw new UnsupportedError(reason); | |
| 83 } | |
| 84 } | |
| 85 | |
| 86 checkGrowable(reason) { | |
| 87 if (this is !JSExtendableArray) { | |
| 88 throw new UnsupportedError(reason); | |
| 89 } | |
| 90 } | |
| 91 | |
| 92 void add(E value) { | |
| 93 checkGrowable('add'); | |
| 94 JS('void', r'#.push(#)', this, value); | |
| 95 } | |
| 96 | |
| 97 E removeAt(int index) { | |
| 98 if (index is !int) throw new ArgumentError(index); | |
| 99 if (index < 0 || index >= length) { | |
| 100 throw new RangeError.value(index); | |
| 101 } | |
| 102 checkGrowable('removeAt'); | |
| 103 return JS('var', r'#.splice(#, 1)[0]', this, index); | |
| 104 } | |
| 105 | |
| 106 void insert(int index, E value) { | |
| 107 if (index is !int) throw new ArgumentError(index); | |
| 108 if (index < 0 || index > length) { | |
| 109 throw new RangeError.value(index); | |
| 110 } | |
| 111 checkGrowable('insert'); | |
| 112 JS('void', r'#.splice(#, 0, #)', this, index, value); | |
| 113 } | |
| 114 | |
| 115 void insertAll(int index, Iterable<E> iterable) { | |
| 116 checkGrowable('insertAll'); | |
| 117 IterableMixinWorkaround.insertAllList(this, index, iterable); | |
| 118 } | |
| 119 | |
| 120 void setAll(int index, Iterable<E> iterable) { | |
| 121 checkMutable('setAll'); | |
| 122 IterableMixinWorkaround.setAllList(this, index, iterable); | |
| 123 } | |
| 124 | |
| 125 E removeLast() { | |
| 126 checkGrowable('removeLast'); | |
| 127 if (length == 0) throw new RangeError.value(-1); | |
| 128 return JS('var', r'#.pop()', this); | |
| 129 } | |
| 130 | |
| 131 bool remove(Object element) { | |
| 132 checkGrowable('remove'); | |
| 133 for (int i = 0; i < this.length; i++) { | |
| 134 if (this[i] == element) { | |
| 135 JS('var', r'#.splice(#, 1)', this, i); | |
| 136 return true; | |
| 137 } | |
| 138 } | |
| 139 return false; | |
| 140 } | |
| 141 | |
| 142 void removeWhere(bool test(E element)) { | |
| 143 // This could, and should, be optimized. | |
| 144 IterableMixinWorkaround.removeWhereList(this, test); | |
| 145 } | |
| 146 | |
| 147 void retainWhere(bool test(E element)) { | |
| 148 IterableMixinWorkaround.removeWhereList(this, | |
| 149 (E element) => !test(element)); | |
| 150 } | |
| 151 | |
| 152 Iterable<E> where(bool f(E element)) { | |
| 153 return new IterableMixinWorkaround<E>().where(this, f); | |
| 154 } | |
| 155 | |
| 156 Iterable expand(Iterable f(E element)) { | |
| 157 return IterableMixinWorkaround.expand(this, f); | |
| 158 } | |
| 159 | |
| 160 void addAll(Iterable<E> collection) { | |
| 161 for (E e in collection) { | |
| 162 this.add(e); | |
| 163 } | |
| 164 } | |
| 165 | |
| 166 void clear() { | |
| 167 length = 0; | |
| 168 } | |
| 169 | |
| 170 void forEach(void f(E element)) { | |
| 171 int length = this.length; | |
| 172 for (int i = 0; i < length; i++) { | |
| 173 f(JS('', '#[#]', this, i)); | |
| 174 if (length != this.length) { | |
| 175 throw new ConcurrentModificationError(this); | |
| 176 } | |
| 177 } | |
| 178 } | |
| 179 | |
| 180 Iterable map(f(E element)) { | |
| 181 return IterableMixinWorkaround.mapList(this, f); | |
| 182 } | |
| 183 | |
| 184 String join([String separator = ""]) { | |
| 185 var list = new List(this.length); | |
| 186 for (int i = 0; i < this.length; i++) { | |
| 187 list[i] = "${this[i]}"; | |
| 188 } | |
| 189 return JS('String', "#.join(#)", list, separator); | |
| 190 } | |
| 191 | |
| 192 Iterable<E> take(int n) { | |
| 193 return new IterableMixinWorkaround<E>().takeList(this, n); | |
| 194 } | |
| 195 | |
| 196 Iterable<E> takeWhile(bool test(E value)) { | |
| 197 return new IterableMixinWorkaround<E>().takeWhile(this, test); | |
| 198 } | |
| 199 | |
| 200 Iterable<E> skip(int n) { | |
| 201 return new IterableMixinWorkaround<E>().skipList(this, n); | |
| 202 } | |
| 203 | |
| 204 Iterable<E> skipWhile(bool test(E value)) { | |
| 205 return new IterableMixinWorkaround<E>().skipWhile(this, test); | |
| 206 } | |
| 207 | |
| 208 E reduce(E combine(E value, E element)) { | |
| 209 return IterableMixinWorkaround.reduce(this, combine); | |
| 210 } | |
| 211 | |
| 212 fold(initialValue, combine(previousValue, E element)) { | |
| 213 return IterableMixinWorkaround.fold(this, initialValue, combine); | |
| 214 } | |
| 215 | |
| 216 E firstWhere(bool test(E value), {E orElse()}) { | |
| 217 return IterableMixinWorkaround.firstWhere(this, test, orElse); | |
| 218 } | |
| 219 | |
| 220 E lastWhere(bool test(E value), {E orElse()}) { | |
| 221 return IterableMixinWorkaround.lastWhereList(this, test, orElse); | |
| 222 } | |
| 223 | |
| 224 E singleWhere(bool test(E value)) { | |
| 225 return IterableMixinWorkaround.singleWhere(this, test); | |
| 226 } | |
| 227 | |
| 228 E elementAt(int index) { | |
| 229 return this[index]; | |
| 230 } | |
| 231 | |
| 232 List<E> sublist(int start, [int end]) { | |
| 233 checkNull(start); // TODO(ahe): This is not specified but co19 tests it. | |
| 234 if (start is !int) throw new ArgumentError(start); | |
| 235 if (start < 0 || start > length) { | |
| 236 throw new RangeError.range(start, 0, length); | |
| 237 } | |
| 238 if (end == null) { | |
| 239 end = length; | |
| 240 } else { | |
| 241 if (end is !int) throw new ArgumentError(end); | |
| 242 if (end < start || end > length) { | |
| 243 throw new RangeError.range(end, start, length); | |
| 244 } | |
| 245 } | |
| 246 if (start == end) return <E>[]; | |
| 247 return new JSArray<E>.markGrowable( | |
| 248 JS('', r'#.slice(#, #)', this, start, end)); | |
| 249 } | |
| 250 | |
| 251 | |
| 252 Iterable<E> getRange(int start, int end) { | |
| 253 return new IterableMixinWorkaround<E>().getRangeList(this, start, end); | |
| 254 } | |
| 255 | |
| 256 E get first { | |
| 257 if (length > 0) return this[0]; | |
| 258 throw new StateError("No elements"); | |
| 259 } | |
| 260 | |
| 261 E get last { | |
| 262 if (length > 0) return this[length - 1]; | |
| 263 throw new StateError("No elements"); | |
| 264 } | |
| 265 | |
| 266 E get single { | |
| 267 if (length == 1) return this[0]; | |
| 268 if (length == 0) throw new StateError("No elements"); | |
| 269 throw new StateError("More than one element"); | |
| 270 } | |
| 271 | |
| 272 void removeRange(int start, int end) { | |
| 273 checkGrowable('removeRange'); | |
| 274 int receiverLength = this.length; | |
| 275 if (start < 0 || start > receiverLength) { | |
| 276 throw new RangeError.range(start, 0, receiverLength); | |
| 277 } | |
| 278 if (end < start || end > receiverLength) { | |
| 279 throw new RangeError.range(end, start, receiverLength); | |
| 280 } | |
| 281 Lists.copy(this, | |
| 282 end, | |
| 283 this, | |
| 284 start, | |
| 285 receiverLength - end); | |
| 286 this.length = receiverLength - (end - start); | |
| 287 } | |
| 288 | |
| 289 void setRange(int start, int end, Iterable<E> iterable, [int skipCount = 0]) { | |
| 290 checkMutable('set range'); | |
| 291 IterableMixinWorkaround.setRangeList(this, start, end, iterable, skipCount); | |
| 292 } | |
| 293 | |
| 294 void fillRange(int start, int end, [E fillValue]) { | |
| 295 checkMutable('fill range'); | |
| 296 IterableMixinWorkaround.fillRangeList(this, start, end, fillValue); | |
| 297 } | |
| 298 | |
| 299 void replaceRange(int start, int end, Iterable<E> iterable) { | |
| 300 checkGrowable('removeRange'); | |
| 301 IterableMixinWorkaround.replaceRangeList(this, start, end, iterable); | |
| 302 } | |
| 303 | |
| 304 bool any(bool f(E element)) => IterableMixinWorkaround.any(this, f); | |
| 305 | |
| 306 bool every(bool f(E element)) => IterableMixinWorkaround.every(this, f); | |
| 307 | |
| 308 Iterable<E> get reversed => | |
| 309 new IterableMixinWorkaround<E>().reversedList(this); | |
| 310 | |
| 311 void sort([int compare(E a, E b)]) { | |
| 312 checkMutable('sort'); | |
| 313 IterableMixinWorkaround.sortList(this, compare); | |
| 314 } | |
| 315 | |
| 316 void shuffle([Random random]) { | |
| 317 IterableMixinWorkaround.shuffleList(this, random); | |
| 318 } | |
| 319 | |
| 320 int indexOf(Object element, [int start = 0]) { | |
| 321 return IterableMixinWorkaround.indexOfList(this, element, start); | |
| 322 } | |
| 323 | |
| 324 int lastIndexOf(Object element, [int start]) { | |
| 325 return IterableMixinWorkaround.lastIndexOfList(this, element, start); | |
| 326 } | |
| 327 | |
| 328 bool contains(Object other) { | |
| 329 for (int i = 0; i < length; i++) { | |
| 330 if (this[i] == other) return true; | |
| 331 } | |
| 332 return false; | |
| 333 } | |
| 334 | |
| 335 bool get isEmpty => length == 0; | |
| 336 | |
| 337 bool get isNotEmpty => !isEmpty; | |
| 338 | |
| 339 String toString() => ListBase.listToString(this); | |
| 340 | |
| 341 List<E> toList({ bool growable: true }) { | |
| 342 if (growable) { | |
| 343 return new JSArray<E>.markGrowable(JS('', '#.slice()', this)); | |
| 344 } else { | |
| 345 return new JSArray<E>.markFixed(JS('', '#.slice()', this)); | |
| 346 } | |
| 347 } | |
| 348 | |
| 349 Set<E> toSet() => new Set<E>.from(this); | |
| 350 | |
| 351 Iterator<E> get iterator => new ListIterator<E>(this); | |
| 352 | |
| 353 int get hashCode => Primitives.objectHashCode(this); | |
| 354 | |
| 355 int get length => JS('JSUInt32', r'#.length', this); | |
| 356 | |
| 357 void set length(int newLength) { | |
| 358 if (newLength is !int) throw new ArgumentError(newLength); | |
| 359 if (newLength < 0) throw new RangeError.value(newLength); | |
| 360 checkGrowable('set length'); | |
| 361 JS('void', r'#.length = #', this, newLength); | |
| 362 } | |
| 363 | |
| 364 E operator [](int index) { | |
| 365 if (index is !int) throw new ArgumentError(index); | |
| 366 if (index >= length || index < 0) throw new RangeError.value(index); | |
| 367 return JS('var', '#[#]', this, index); | |
| 368 } | |
| 369 | |
| 370 void operator []=(int index, E value) { | |
| 371 checkMutable('indexed set'); | |
| 372 if (index is !int) throw new ArgumentError(index); | |
| 373 if (index >= length || index < 0) throw new RangeError.value(index); | |
| 374 JS('void', r'#[#] = #', this, index, value); | |
| 375 } | |
| 376 | |
| 377 Map<int, E> asMap() { | |
| 378 return new IterableMixinWorkaround<E>().asMapList(this); | |
| 379 } | |
| 380 } | |
| 381 | |
| 382 /** | |
| 383 * Dummy subclasses that allow the backend to track more precise | |
| 384 * information about arrays through their type. The CPA type inference | |
| 385 * relies on the fact that these classes do not override [] nor []=. | |
| 386 */ | |
| 387 class JSMutableArray<E> extends JSArray<E> implements JSMutableIndexable {} | |
| 388 class JSFixedArray<E> extends JSMutableArray<E> {} | |
| 389 class JSExtendableArray<E> extends JSMutableArray<E> {} | |
| OLD | NEW |