blob: 888ed9d08d7d2080d248b6c06d9df9cb95dc3978 [file] [log] [blame]
// Copyright (c) 2020, 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.
import 'dart:math';
import 'constants.dart' as constants;
import 'regexp.dart' as regexp;
/// A simple and not particularly general stream class to make parsing
/// dates from strings simpler. It is general enough to operate on either
/// lists or strings.
// TODO(alanknight): With the improvements to the collection libraries
// since this was written we might be able to get rid of it entirely
// in favor of e.g. aString.split('') giving us an iterable of one-character
// strings, or else make the implementation trivial.
class IntlStream {
dynamic contents;
int index = 0;
IntlStream(this.contents);
bool atEnd() => index >= contents.length;
dynamic next() => contents[index++];
/// Return the next [howMany] items, or as many as there are remaining.
/// Advance the stream by that many positions.
dynamic read([int howMany = 1]) {
var result = peek(howMany);
index += howMany;
return result;
}
/// Does the input start with the given string, if we start from the
/// current position.
bool startsWith(String pattern) {
if (contents is String) return contents.startsWith(pattern, index);
return pattern == peek(pattern.length);
}
/// Return the next [howMany] items, or as many as there are remaining.
/// Does not modify the stream position.
dynamic peek([int howMany = 1]) {
dynamic result;
if (contents is String) {
String stringContents = contents;
result = stringContents.substring(
index, min(index + howMany, stringContents.length));
} else {
// Assume List
result = contents.sublist(index, index + howMany);
}
return result;
}
/// Return the remaining contents of the stream
dynamic rest() => peek(contents.length - index);
/// Find the index of the first element for which [f] returns true.
/// Advances the stream to that position.
int? findIndex(bool Function(dynamic) f) {
while (!atEnd()) {
if (f(next())) return index - 1;
}
return null;
}
/// Find the indexes of all the elements for which [f] returns true.
/// Leaves the stream positioned at the end.
List<dynamic> findIndexes(bool Function(dynamic) f) {
var results = [];
while (!atEnd()) {
if (f(next())) results.add(index - 1);
}
return results;
}
/// Assuming that the contents are characters, read as many digits as we
/// can see and then return the corresponding integer, advancing the receiver.
///
/// For non-ascii digits, the optional arguments are a regular expression
/// [digitMatcher] to find the next integer, and the codeUnit of the local
/// zero [zeroDigit].
int? nextInteger({RegExp? digitMatcher, int? zeroDigit}) {
var string = (digitMatcher ?? regexp.asciiDigitMatcher).stringMatch(rest());
if (string == null || string.isEmpty) return null;
read(string.length);
if (zeroDigit != null && zeroDigit != constants.asciiZeroCodeUnit) {
// Trying to optimize this, as it might get called a lot.
var oldDigits = string.codeUnits;
var newDigits = List<int>.filled(string.length, 0);
for (var i = 0; i < string.length; i++) {
newDigits[i] = oldDigits[i] - zeroDigit + constants.asciiZeroCodeUnit;
}
string = String.fromCharCodes(newDigits);
}
return int.parse(string);
}
}