| 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 |