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