diff --git a/runtime/lib/string_base.dart b/runtime/lib/string_base.dart index 2a777c2b7a5..8b28aaeba83 100644 --- a/runtime/lib/string_base.dart +++ b/runtime/lib/string_base.dart @@ -433,9 +433,7 @@ class _StringBase { return result; } - Iterable get codeUnits { - throw new UnimplementedError("String.codeUnits"); - } + Iterable get codeUnits => new CodeUnits(this); Runes get runes => new Runes(this); diff --git a/sdk/lib/_collection_dev/iterable.dart b/sdk/lib/_collection_dev/iterable.dart index ac490e1cf2f..676ad733364 100644 --- a/sdk/lib/_collection_dev/iterable.dart +++ b/sdk/lib/_collection_dev/iterable.dart @@ -4,6 +4,317 @@ part of dart._collection.dev; + +/** + * An [Iterable] for classes that have efficient [length] and [elementAt]. + * + * All other methods are implemented in terms of [length] and [elementAt], + * including [iterator]. + */ +abstract class ListIterable extends Iterable { + int get length; + E elementAt(int i); + + Iterator get iterator => new ListIterableIterator(this); + + void forEach(void action(E element)) { + int length = this.length; + for (int i = 0; i < length; i++) { + action(elementAt(i)); + if (length != this.length) { + throw new ConcurrentModificationError(this); + } + } + } + + bool get isEmpty => length != 0; + + E get first { + if (length == 0) throw new StateError("No elements"); + return elementAt(0); + } + + E get last { + if (length == 0) throw new StateError("No elements"); + return elementAt(length - 1); + } + + E get single { + if (length == 0) throw new StateError("No elements"); + if (length > 1) throw new StateError("Too many elements"); + return elementAt(0); + } + + bool contains(E element) { + int length = this.length; + for (int i = 0; i < length; i++) { + if (elementAt(i) == element) return true; + if (length != this.length) { + throw new ConcurrentModificationError(this); + } + } + return false; + } + + bool every(bool test(E element)) { + int length = this.length; + for (int i = 0; i < length; i++) { + if (!test(elementAt(i))) return false; + if (length != this.length) { + throw new ConcurrentModificationError(this); + } + } + return true; + } + + bool any(bool test(E element)) { + int length = this.length; + for (int i = 0; i < length; i++) { + if (test(elementAt(i))) return true; + if (length != this.length) { + throw new ConcurrentModificationError(this); + } + } + return false; + } + + E firstMatching(bool test(E element), { E orElse() }) { + int length = this.length; + for (int i = 0; i < length; i++) { + E element = elementAt(i); + if (test(element)) return element; + if (length != this.length) { + throw new ConcurrentModificationError(this); + } + } + if (orElse != null) return orElse(); + throw new StateError("No matching element"); + } + + E lastMatching(bool test(E element), { E orElse() }) { + int length = this.length; + for (int i = length - 1; i >= 0; i++) { + E element = elementAt(i); + if (test(element)) return element; + if (length != this.length) { + throw new ConcurrentModificationError(this); + } + } + if (orElse != null) return orElse(); + throw new StateError("No matching element"); + } + + E singleMatching(bool test(E element)) { + int length = this.length; + E match = null; + bool matchFound = false; + for (int i = 0; i < length; i++) { + E element = elementAt(i); + if (test(element)) { + if (matchFound) { + throw new StateError("More than one matching element"); + } + matchFound = true; + match = element; + } + if (length != this.length) { + throw new ConcurrentModificationError(this); + } + } + if (matchFound) return match; + throw new StateError("No matching element"); + } + + E min([int compare(E a, E b)]) { + if (length == 0) return null; + if (compare == null) { + var defaultCompare = Comparable.compare; + compare = defaultCompare; + } + E min = elementAt(0); + int length = this.length; + for (int i = 1; i < length; i++) { + E element = elementAt(i); + if (compare(min, element) > 0) { + min = element; + } + if (length != this.length) { + throw new ConcurrentModificationError(this); + } + } + return min; + } + + E max([int compare(E a, E b)]) { + if (length == 0) return null; + if (compare == null) { + var defaultCompare = Comparable.compare; + compare = defaultCompare; + } + E max = elementAt(0); + int length = this.length; + for (int i = 1; i < length; i++) { + E element = elementAt(i); + if (compare(max, element) < 0) { + max = element; + } + if (length != this.length) { + throw new ConcurrentModificationError(this); + } + } + return max; + } + + String join([String separator]) { + int length = this.length; + if (separator != null && !separator.isEmpty) { + if (length == 0) return ""; + String first = "${elementAt(0)}"; + if (length != this.length) { + throw new ConcurrentModificationError(this); + } + StringBuffer buffer = new StringBuffer(first); + for (int i = 1; i < length; i++) { + buffer.add(separator); + buffer.add("${elementAt(i)}"); + if (length != this.length) { + throw new ConcurrentModificationError(this); + } + } + return buffer.toString(); + } else { + StringBuffer buffer = new StringBuffer(); + for (int i = 0; i < length; i++) { + buffer.add("${elementAt(i)}"); + if (length != this.length) { + throw new ConcurrentModificationError(this); + } + } + return buffer.toString(); + } + } + + Iterable where(bool test(E element)) => super.where(test); + + Iterable map(f(E element)) => new MappedIterable(this, f); + + Iterable mappedBy(f(E element)) => super.mappedBy(f); + + reduce(var initialValue, combine(var previousValue, E element)) { + var value = initialValue; + int length = this.length; + for (int i = 0; i < length; i++) { + value = reduce(value, elementAt(i)); + if (length != this.length) { + throw new ConcurrentModificationError(this); + } + } + return value; + } + + Iterable skip(int count) => new SubListIterable(this, count, null); + + Iterable skipWhile(bool test(E element)) => super.skipWhile(test); + + Iterable take(int count) => new SubListIterable(this, 0, count); + + Iterable takeWhile(bool test(E element)) => super.takeWhile(test); + + List toList() { + List result = new List(length); + for (int i = 0; i < length; i++) { + result[i] = elementAt(i); + } + return result; + } + + Set toSet() { + Set result = new Set(); + for (int i = 0; i < length; i++) { + result.add(elementAt(i)); + } + return result; + } +} + +abstract class SubListIterable extends ListIterable { + final Iterable _iterable; + final int _start; + /** If null, represents the length of the iterable. */ + final int _endOrLength; + + SubListIterable(this._iterable, this._start, this._endOrLength); + + int get _endIndex { + int length = _iterable.length; + if (_endOrLength == null || _endOrLength > length) return length; + return _endOrLength; + } + + int get _startIndex { + int length = _iterable.length; + if (_start > length) return length; + return _start; + } + + int get length { + int length = _iterable.length; + if (_start >= length) return 0; + if (_endOrLength == null || _endOrLength >= length) { + return length - _start; + } + return _endOrLength - _start; + } + + E elementAt(int index) { + int realIndex = _startIndex + index; + if (index < 0 || realIndex >= _endIndex) { + throw new RangeError.range(index, 0, length); + } + return _iterable.elementAt(realIndex); + } + + Iterable skip(int count) { + if (count < 0) throw new ArgumentError(count); + return new SubListIterable(_iterable, _start + count, _endOrLength); + } + + Iterable take(int count) { + if (count < 0) throw new ArgumentError(count); + if (_endOrLength == null) { + return new SubListIterable(_iterable, _start, _start + count); + } else { + newEnd = _start + count; + if (_endOrLength < newEnd) return this; + return new SubListIterable(_iterable, _start, newEnd); + } + } +} + +class ListIterableIterator implements Iterator { + final Iterable _iterable; + final int _length; + int _index; + E _current; + ListIterableIterator(Iterable iterable) + : _iterable = iterable, _length = iterable.length, _index = 0; + + E get current => _current; + + bool moveNext() { + if (_length != _iterable.length) { + throw new ConcurrentModificationError(_iterable); + } + if (_index == _length) { + _current = null; + return false; + } + _current = _iterable.elementAt(_index); + _index++; + return true; + } +} + typedef T _Transformation(S value); class MappedIterable extends Iterable { diff --git a/sdk/lib/_internal/compiler/implementation/lib/js_string.dart b/sdk/lib/_internal/compiler/implementation/lib/js_string.dart index dcb5b4ef98a..6d81f320c99 100644 --- a/sdk/lib/_internal/compiler/implementation/lib/js_string.dart +++ b/sdk/lib/_internal/compiler/implementation/lib/js_string.dart @@ -149,9 +149,7 @@ class JSString implements String { return result; } - Iterable get codeUnits { - throw new UnimplementedError("String.codeUnits"); - } + Iterable get codeUnits => new CodeUnits(this); Runes get runes => new Runes(this); diff --git a/sdk/lib/core/string.dart b/sdk/lib/core/string.dart index 3584db51c90..4e5b95021a6 100644 --- a/sdk/lib/core/string.dart +++ b/sdk/lib/core/string.dart @@ -449,7 +449,6 @@ class RuneIterator implements BiDirectionalIterator { return string.substring(_position, _nextPosition); } - bool moveNext() { _position = _nextPosition; if (_position == string.length) { @@ -492,3 +491,16 @@ class RuneIterator implements BiDirectionalIterator { return true; } } + +/** + * An [Iterable] of the UTF-16 code units of a [String] in index order. + */ +class CodeUnits extends ListIterable { + /** The string that this is the code units of. */ + String string; + + CodeUnits(this.string); + + int get length => string.length; + int elementAt(int i) => string.codeUnitAt(i); +} diff --git a/tests/corelib/string_codeunits_test.dart b/tests/corelib/string_codeunits_test.dart new file mode 100644 index 00000000000..81625c5d186 --- /dev/null +++ b/tests/corelib/string_codeunits_test.dart @@ -0,0 +1,44 @@ +// Copyright (c) 2011, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +main() { + test(String s) { + Iterable units = s.codeUnits; + List expectedUnits = []; + for (int i = 0; i < s.length; i++) { + expectedUnits.add(s.codeUnitAt(i)); + } + + Expect.equals(s.length, units.length); + for (int i = 0; i < s.length; i++) { + Expect.equals(s.codeUnitAt(i), units.elementAt(i)); + } + + // for-in + var res = []; + for (int unit in units) { + res.add(unit); + } + Expect.listEquals(expectedUnits, res); + + // .map + Expect.listEquals(expectedUnits.map((x) => x.toRadixString(16)).toList(), + units.map((x) => x.toRadixString(16)).toList()); + } + + test("abc"); + test("\x00\u0000\u{000000}"); + test("\u{ffff}\u{10000}\u{10ffff}"); + String string = new String.fromCharCodes( + [0xdc00, 0xd800, 61, 0xd800, 0xdc00, 62, 0xdc00, 0xd800]); + test(string); + + // Setting position in the middle of a surrogate pair is not allowed. + var r = new CodeUnits("\u{10000}"); + var it = r.iterator; + Expect.isTrue(it.moveNext()); + Expect.equals(0xD800, it.current); + Expect.isTrue(it.moveNext()); + Expect.equals(0xDC00, it.current); +}