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 dart.collection; | 5 part of dart.collection; |
6 | 6 |
7 /** | 7 /** |
8 * This [Iterable] mixin implements all [Iterable] members except `iterator`. | 8 * This [Iterable] mixin implements all [Iterable] members except `iterator`. |
9 * | 9 * |
10 * All other methods are implemented in terms of `iterator`. | 10 * All other methods are implemented in terms of `iterator`. |
(...skipping 177 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
188 E elementAt(int index) { | 188 E elementAt(int index) { |
189 if (index is! int || index < 0) throw new RangeError.value(index); | 189 if (index is! int || index < 0) throw new RangeError.value(index); |
190 int remaining = index; | 190 int remaining = index; |
191 for (E element in this) { | 191 for (E element in this) { |
192 if (remaining == 0) return element; | 192 if (remaining == 0) return element; |
193 remaining--; | 193 remaining--; |
194 } | 194 } |
195 throw new RangeError.value(index); | 195 throw new RangeError.value(index); |
196 } | 196 } |
197 | 197 |
198 String toString() => _iterableToString(this); | 198 String toString() => IterableBase.iterableToShortString(this, '(', ')'); |
199 } | 199 } |
200 | 200 |
201 /** | 201 /** |
202 * Base class for implementing [Iterable]. | 202 * Base class for implementing [Iterable]. |
203 * | 203 * |
204 * This class implements all methods of [Iterable] except [Iterable.iterator] | 204 * This class implements all methods of [Iterable] except [Iterable.iterator] |
205 * in terms of `iterator`. | 205 * in terms of `iterator`. |
206 */ | 206 */ |
207 abstract class IterableBase<E> implements Iterable<E> { | 207 abstract class IterableBase<E> implements Iterable<E> { |
208 // TODO(lrn): Base this on IterableMixin if there ever becomes a way | 208 // TODO(lrn): Base this on IterableMixin if there ever becomes a way |
(...skipping 186 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
395 * Elements are represented by their own `toString` results. | 395 * Elements are represented by their own `toString` results. |
396 * | 396 * |
397 * The representation always contains the first three elements. | 397 * The representation always contains the first three elements. |
398 * If there are less than a hundred elements in the iterable, it also | 398 * If there are less than a hundred elements in the iterable, it also |
399 * contains the last two elements. | 399 * contains the last two elements. |
400 * | 400 * |
401 * If the resulting string isn't above 80 characters, more elements are | 401 * If the resulting string isn't above 80 characters, more elements are |
402 * included from the start of the iterable. | 402 * included from the start of the iterable. |
403 * | 403 * |
404 * The conversion may omit calling `toString` on some elements if they | 404 * The conversion may omit calling `toString` on some elements if they |
405 * are known to now occur in the output, and it may stop iterating after | 405 * are known to not occur in the output, and it may stop iterating after |
406 * a hundred elements. | 406 * a hundred elements. |
407 */ | 407 */ |
408 String toString() => _iterableToString(this); | 408 String toString() => iterableToShortString(this, '(', ')'); |
409 } | |
410 | 409 |
411 String _setToString(Set set) => _collectionToString(set, "{" , "}"); | 410 /** |
412 | 411 * Convert an `Iterable` to a string like [IterableBase.toString]. |
413 String _iterableToString(Iterable iterable) => | 412 * |
414 _collectionToString(iterable, "(", ")"); | 413 * Allows using other delimiters than '(' and ')'. |
415 | 414 * |
416 String _collectionToString(Iterable iterable, String before, String after) { | 415 * Handles circular references where converting one of the elements |
417 if (_toStringVisiting.contains(iterable)) return "$before...$after"; | 416 * to a string ends up converting [iterable] to a string again. |
418 _toStringVisiting.add(iterable); | 417 */ |
419 List parts = []; | 418 static String iterableToShortString(Iterable iterable, |
420 try { | 419 [String leftDelimiter = '(', |
421 _collectionPartsToStrings(iterable, parts); | 420 String rightDelimiter = ')']) { |
422 } finally { | 421 if (_toStringVisiting.contains(iterable)) { |
423 _toStringVisiting.remove(iterable); | 422 if (leftDelimiter == "(" && rightDelimiter == ")") { |
424 } | 423 // Avoid creating a new string in the "common" case. |
425 return (new StringBuffer(before) | 424 return "(...)"; |
426 ..writeAll(parts, ", ") | 425 } |
427 ..write(after)).toString(); | 426 return "$leftDelimiter...$rightDelimiter"; |
428 } | |
429 | |
430 /** Convert elments of [iterable] to strings and store them in [parts]. */ | |
431 void _collectionPartsToStrings(Iterable iterable, List parts) { | |
432 /// Try to stay below this many characters. | |
433 const int LENGTH_LIMIT = 80; | |
434 /// Always at least this many elements at the start. | |
435 const int HEAD_COUNT = 3; | |
436 /// Always at least this many elements at the end. | |
437 const int TAIL_COUNT = 2; | |
438 /// Stop iterating after this many elements. Iterables can be infinite. | |
439 const int MAX_COUNT = 100; | |
440 // Per entry length overhead. It's for ", " for all after the first entry, | |
441 // and for "(" and ")" for the initial entry. By pure luck, that's the same | |
442 // number. | |
443 const int OVERHEAD = 2; | |
444 const int ELLIPSIS_SIZE = 3; // "...".length. | |
445 int length = 0; | |
446 int count = 0; | |
447 Iterator it = iterable.iterator; | |
448 // Initial run of elements, at least HEAD_COUNT, and then continue until | |
449 // passing at most LENGTH_LIMIT characters. | |
450 while (length < LENGTH_LIMIT || count < HEAD_COUNT) { | |
451 if (!it.moveNext()) { | |
452 return; | |
453 } | 427 } |
454 String next = "${it.current}"; | 428 List parts = []; |
455 parts.add(next); | 429 _toStringVisiting.add(iterable); |
456 length += next.length + OVERHEAD; | 430 try { |
457 count++; | 431 _iterablePartsToStrings(iterable, parts); |
| 432 } finally { |
| 433 _toStringVisiting.remove(iterable); |
| 434 } |
| 435 return (new StringBuffer(leftDelimiter) |
| 436 ..writeAll(parts, ", ") |
| 437 ..write(rightDelimiter)).toString(); |
458 } | 438 } |
459 | 439 |
460 String penultimateString; | 440 /** |
461 String ultimateString; | 441 * Converts an `Iterable` to a string. |
| 442 * |
| 443 * Converts each elements to a string, and separates the results by ", ". |
| 444 * Then wraps the result in [leftDelimiter] and [rightDelimiter]. |
| 445 * |
| 446 * Unlike [iterableToShortString], this conversion doesn't omit any |
| 447 * elements or puts any limit on the size of the result. |
| 448 * |
| 449 * Handles circular references where converting one of the elements |
| 450 * to a string ends up converting [iterable] to a string again. |
| 451 */ |
| 452 static String iterableToFullString(Iterable iterable, |
| 453 [String leftDelimiter = '(', |
| 454 String rightDelimiter = ')']) { |
| 455 if (_toStringVisiting.contains(iterable)) { |
| 456 return "$leftDelimiter...$rightDelimiter"; |
| 457 } |
| 458 StringBuffer buffer = new StringBuffer(leftDelimiter); |
| 459 _toStringVisiting.add(iterable); |
| 460 try { |
| 461 buffer.writeAll(iterable, ", "); |
| 462 } finally { |
| 463 _toStringVisiting.remove(iterable); |
| 464 } |
| 465 buffer.write(rightDelimiter); |
| 466 return buffer.toString(); |
| 467 } |
462 | 468 |
463 // Find last two elements. One or more of them may already be in the | 469 /** A set used to identify cyclic lists during toString() calls. */ |
464 // parts array. Include their length in `length`. | 470 static Set _toStringVisiting = new HashSet.identity(); |
465 var penultimate = null; | 471 |
466 var ultimate = null; | 472 /** |
467 if (!it.moveNext()) { | 473 * Convert elments of [iterable] to strings and store them in [parts]. |
468 if (count <= HEAD_COUNT + TAIL_COUNT) return; | 474 */ |
469 ultimateString = parts.removeLast(); | 475 static void _iterablePartsToStrings(Iterable iterable, List parts) { |
470 penultimateString = parts.removeLast(); | 476 /* |
471 } else { | 477 * This is the complicated part of [iterableToShortString]. |
472 penultimate = it.current; | 478 * It is extracted as a separate function to avoid having too much code |
473 count++; | 479 * inside the try/finally. |
| 480 */ |
| 481 /// Try to stay below this many characters. |
| 482 const int LENGTH_LIMIT = 80; |
| 483 /// Always at least this many elements at the start. |
| 484 const int HEAD_COUNT = 3; |
| 485 /// Always at least this many elements at the end. |
| 486 const int TAIL_COUNT = 2; |
| 487 /// Stop iterating after this many elements. Iterables can be infinite. |
| 488 const int MAX_COUNT = 100; |
| 489 // Per entry length overhead. It's for ", " for all after the first entry, |
| 490 // and for "(" and ")" for the initial entry. By pure luck, that's the same |
| 491 // number. |
| 492 const int OVERHEAD = 2; |
| 493 const int ELLIPSIS_SIZE = 3; // "...".length. |
| 494 |
| 495 int length = 0; |
| 496 int count = 0; |
| 497 Iterator it = iterable.iterator; |
| 498 // Initial run of elements, at least HEAD_COUNT, and then continue until |
| 499 // passing at most LENGTH_LIMIT characters. |
| 500 while (length < LENGTH_LIMIT || count < HEAD_COUNT) { |
| 501 if (!it.moveNext()) return; |
| 502 String next = "${it.current}"; |
| 503 parts.add(next); |
| 504 length += next.length + OVERHEAD; |
| 505 count++; |
| 506 } |
| 507 |
| 508 String penultimateString; |
| 509 String ultimateString; |
| 510 |
| 511 // Find last two elements. One or more of them may already be in the |
| 512 // parts array. Include their length in `length`. |
| 513 var penultimate = null; |
| 514 var ultimate = null; |
474 if (!it.moveNext()) { | 515 if (!it.moveNext()) { |
475 if (count <= HEAD_COUNT + 1) { | 516 if (count <= HEAD_COUNT + TAIL_COUNT) return; |
476 parts.add("$penultimate"); | 517 ultimateString = parts.removeLast(); |
477 return; | |
478 } | |
479 ultimateString = "$penultimate"; | |
480 penultimateString = parts.removeLast(); | 518 penultimateString = parts.removeLast(); |
481 length += ultimateString.length + OVERHEAD; | |
482 } else { | 519 } else { |
483 ultimate = it.current; | 520 penultimate = it.current; |
484 count++; | 521 count++; |
485 // Then keep looping, keeping the last two elements in variables. | 522 if (!it.moveNext()) { |
486 assert(count < MAX_COUNT); | 523 if (count <= HEAD_COUNT + 1) { |
487 while (it.moveNext()) { | 524 parts.add("$penultimate"); |
488 penultimate = ultimate; | 525 return; |
| 526 } |
| 527 ultimateString = "$penultimate"; |
| 528 penultimateString = parts.removeLast(); |
| 529 length += ultimateString.length + OVERHEAD; |
| 530 } else { |
489 ultimate = it.current; | 531 ultimate = it.current; |
490 count++; | 532 count++; |
491 if (count > MAX_COUNT) { | 533 // Then keep looping, keeping the last two elements in variables. |
492 // If we haven't found the end before MAX_COUNT, give up. | 534 assert(count < MAX_COUNT); |
493 // This cannot happen in the code above because each entry | 535 while (it.moveNext()) { |
494 // increases length by at least two, so there is no way to | 536 penultimate = ultimate; |
495 // visit more than ~40 elements before this loop. | 537 ultimate = it.current; |
| 538 count++; |
| 539 if (count > MAX_COUNT) { |
| 540 // If we haven't found the end before MAX_COUNT, give up. |
| 541 // This cannot happen in the code above because each entry |
| 542 // increases length by at least two, so there is no way to |
| 543 // visit more than ~40 elements before this loop. |
496 | 544 |
497 // Remove any surplus elements until length, including ", ...)", | 545 // Remove any surplus elements until length, including ", ...)", |
498 // is at most LENGTH_LIMIT. | 546 // is at most LENGTH_LIMIT. |
499 while (length > LENGTH_LIMIT - ELLIPSIS_SIZE - OVERHEAD && | 547 while (length > LENGTH_LIMIT - ELLIPSIS_SIZE - OVERHEAD && |
500 count > HEAD_COUNT) { | 548 count > HEAD_COUNT) { |
501 length -= parts.removeLast().length + OVERHEAD; | 549 length -= parts.removeLast().length + OVERHEAD; |
502 count--; | 550 count--; |
| 551 } |
| 552 parts.add("..."); |
| 553 return; |
503 } | 554 } |
504 parts.add("..."); | |
505 return; | |
506 } | 555 } |
| 556 penultimateString = "$penultimate"; |
| 557 ultimateString = "$ultimate"; |
| 558 length += |
| 559 ultimateString.length + penultimateString.length + 2 * OVERHEAD; |
507 } | 560 } |
508 penultimateString = "$penultimate"; | |
509 ultimateString = "$ultimate"; | |
510 length += | |
511 ultimateString.length + penultimateString.length + 2 * OVERHEAD; | |
512 } | 561 } |
513 } | |
514 | 562 |
515 // If there is a gap between the initial run and the last two, | 563 // If there is a gap between the initial run and the last two, |
516 // prepare to add an ellipsis. | 564 // prepare to add an ellipsis. |
517 String elision = null; | 565 String elision = null; |
518 if (count > parts.length + TAIL_COUNT) { | 566 if (count > parts.length + TAIL_COUNT) { |
519 elision = "..."; | |
520 length += ELLIPSIS_SIZE + OVERHEAD; | |
521 } | |
522 | |
523 // If the last two elements were very long, and we have more than | |
524 // HEAD_COUNT elements in the initial run, drop some to make room for | |
525 // the last two. | |
526 while (length > LENGTH_LIMIT && parts.length > HEAD_COUNT) { | |
527 String lastPart = parts.removeLast(); | |
528 length -= lastPart.length + OVERHEAD; | |
529 if (elision == null) { | |
530 elision = "..."; | 567 elision = "..."; |
531 length += ELLIPSIS_SIZE + OVERHEAD; | 568 length += ELLIPSIS_SIZE + OVERHEAD; |
532 } | 569 } |
| 570 |
| 571 // If the last two elements were very long, and we have more than |
| 572 // HEAD_COUNT elements in the initial run, drop some to make room for |
| 573 // the last two. |
| 574 while (length > LENGTH_LIMIT && parts.length > HEAD_COUNT) { |
| 575 length -= parts.removeLast().length + OVERHEAD; |
| 576 if (elision == null) { |
| 577 elision = "..."; |
| 578 length += ELLIPSIS_SIZE + OVERHEAD; |
| 579 } |
| 580 } |
| 581 if (elision != null) { |
| 582 parts.add(elision); |
| 583 } |
| 584 parts.add(penultimateString); |
| 585 parts.add(ultimateString); |
533 } | 586 } |
534 if (elision != null) { | |
535 parts.add(elision); | |
536 } | |
537 parts.add(penultimateString); | |
538 parts.add(ultimateString); | |
539 } | 587 } |
OLD | NEW |