Chromium Code Reviews| Index: pkg/intl/lib/number_format.dart |
| diff --git a/pkg/intl/lib/number_format.dart b/pkg/intl/lib/number_format.dart |
| index ed5cf2be23653d36cd506c4fbb3adf35f60a4f4d..bdf514c370c89cccbae159112e816b42bd71d1b5 100644 |
| --- a/pkg/intl/lib/number_format.dart |
| +++ b/pkg/intl/lib/number_format.dart |
| @@ -2,14 +2,41 @@ |
| // 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. |
| -library number_format; |
| - |
| -import 'dart:math'; |
| - |
| -import "intl.dart"; |
| -import "number_symbols.dart"; |
| -import "number_symbols_data.dart"; |
| - |
| +part of intl; |
| +/** |
| + * Provides the ability to format a number in a locale-specific way. The |
| + * format is specified as a pattern using a subset of the ICU formatting |
| + * patterns. |
| + * |
| + * 0 - A single digit |
| + * # - A single digit, omitted if the value is zero |
| + * . - Decimal separator |
| + * - - Minus sign |
| + * , - Grouping separator |
| + * E - Separates mantissa and expontent |
| + * + - Before an exponent, indicates it should be prefixed with a plus sign. |
| + * % - In prefix or suffix, multiply by 100 and show as percentage |
| + * \u2030 - In prefix or suffix, multiply by 1000 and show as per mille |
| + * \u00A4 - Currency sign, replaced by currency name |
| + * ' - Used to quote special characters |
| + * ; - Used to separate the positive and negative patterns if both are present |
| + * |
| + * For example, |
| + * var f = new NumberFormat("###.0#", "en_US"); |
| + * print(f.format(12.345)); |
| + * ==> 12.34 |
| + * If the locale is not specified, it will default to the current locale. If |
| + * the format is not specified it will print in a basic format with at least |
| + * one integer digit and three fraction digits. |
| + * |
| + * There are also standard patterns available via the special constructors. e.g. |
| + * var symbols = new NumberFormat.PERCENT_FORMAT("ar"); |
| + * There are four such constructors: DECIMAL_FORMAT, PERCENT_FORMAT, |
| + * SCIENTIFIC_FORMAT and CURRENCY_FORMAT. However, at the moment, |
| + * SCIENTIFIC_FORMAT prints only as equivalent to "#E0" and does not take |
| + * into account significant digits. CURRENCY_FORMAT will always use the name |
| + * of the currency rather than the symbol. |
| + */ |
| class NumberFormat { |
| /** Variables to determine how number printing behaves. */ |
| // TODO(alanknight): If these remain as variables and are set based on the |
| @@ -18,19 +45,64 @@ class NumberFormat { |
| String _positivePrefix = ''; |
| String _negativeSuffix = ''; |
| String _positiveSuffix = ''; |
| - /** How many numbers in a group when using punctuation to group digits in |
| + /** |
| + * How many numbers in a group when using punctuation to group digits in |
| * large numbers. e.g. in en_US: "1,000,000" has a grouping size of 3 digits |
| * between commas. |
| */ |
| int _groupingSize = 3; |
| bool _decimalSeparatorAlwaysShown = false; |
| + bool _useSignForPositiveExponent = false; |
| bool _useExponentialNotation = false; |
| + |
| int _maximumIntegerDigits = 40; |
| int _minimumIntegerDigits = 1; |
| - int _maximumFractionDigits = 3; // invariant, >= minFractionDigits |
| + int _maximumFractionDigits = 3; |
| int _minimumFractionDigits = 0; |
| int _minimumExponentDigits = 0; |
| - bool _useSignForPositiveExponent = false; |
| + |
| + int _multiplier = 1; |
| + |
| + /** |
| + * Stores the pattern used to create this format. This isn't used, but |
| + * is helpful in debugging. |
| + */ |
| + String _pattern; |
| + /** |
| + * Set the maximum digits printed to the left of the decimal point. |
| + * Normally this is computed from the pattern, but it's exposed here for |
| + * testing purposes and for rare cases where you want to force it explicitly. |
| + */ |
| + void setMaximumIntegerDigits(int max) { |
| + _maximumIntegerDigits = max; |
| + } |
| + |
| + /** |
| + * Set the minimum number of digits printed to the left of the decimal point. |
| + * Normally this is computed from the pattern, but it's exposed here for |
| + * testing purposes and for rare cases where you want to force it explicitly. |
| + */ |
| + void setMinimumIntegerDigits(int min) { |
| + _minimumIntegerDigits = min; |
| + } |
| + |
| + /** |
| + * Set the maximum number of digits printed to the right of the decimal point. |
| + * Normally this is computed from the pattern, but it's exposed here for |
| + * testing purposes and for rare cases where you want to force it explicitly. |
| + */ |
| + void setMaximumFractionDigits(int max) { |
| + _maximumFractionDigits = max; |
| + } |
| + |
| + /** |
| + * Set the minimum digits printed to the left of the decimal point. |
| + * Normally this is computed from the pattern, but it's exposed here for |
| + * testing purposes and for rare cases where you want to force it explicitly. |
| + */ |
| + void setMinimumFractionDigits(int max) { |
| + _minimumFractionDigits = max; |
| + } |
| /** The locale in which we print numbers. */ |
| final String _locale; |
| @@ -48,15 +120,37 @@ class NumberFormat { |
| StringBuffer _buffer; |
| /** |
| - * Create a number format that prints in [newPattern] as it applies in |
| + * Create a number format that prints using [newPattern] as it applies in |
| * [locale]. |
| */ |
| - NumberFormat([String newPattern, String locale]): |
| - _locale = Intl.verifiedLocale(locale, localeExists) { |
| - // TODO(alanknight): There will need to be some kind of async setup |
| - // operations so as not to bring along every locale in every program. |
| + factory NumberFormat([String newPattern, String locale]) { |
| + return new NumberFormat._forPattern(locale, (x) => newPattern); |
| + } |
| + |
| + /** Create a number format that prints as DECIMAL_PATTERN. */ |
| + NumberFormat.DECIMAL_PATTERN([String locale]) : |
|
Emily Fortuna
2013/04/09 19:39:15
personal preference: I'd prefer the name of these
|
| + this._forPattern(locale, (x) => x.DECIMAL_PATTERN); |
| + |
| + /** Create a number format that prints as PERCENT_PATTERN. */ |
| + NumberFormat.PERCENT_PATTERN([String locale]) : |
| + this._forPattern(locale, (x) => x.PERCENT_PATTERN); |
| + |
| + /** Create a number format that prints as SCIENTIFIC_PATTERN. */ |
| + NumberFormat.SCIENTIFIC_PATTERN([String locale]) : |
| + this._forPattern(locale, (x) => x.SCIENTIFIC_PATTERN); |
| + |
| + /** Create a number format that prints as CURRENCY_PATTERN. */ |
| + NumberFormat.CURRENCY_PATTERN([String locale]) : |
| + this._forPattern(locale, (x) => x.CURRENCY_PATTERN); |
| + |
| + /** |
| + * Create a number format that prints in a pattern we get from |
| + * the [getPattern] function using the locale [locale]. |
| + */ |
| + NumberFormat._forPattern(String locale, Function getPattern) : |
| + _locale = Intl.verifiedLocale(locale, localeExists) { |
| _symbols = numberFormatSymbols[_locale]; |
| - _setPattern(newPattern); |
| + _setPattern(getPattern(_symbols)); |
| } |
| /** |
| @@ -81,9 +175,6 @@ class NumberFormat { |
| return _symbols; |
| } |
| - // TODO(alanknight): Actually use the pattern and locale. |
| - _setPattern(String x) {} |
| - |
| /** |
| * Format [number] according to our pattern and return the formatted string. |
| */ |
| @@ -95,7 +186,7 @@ class NumberFormat { |
| _newBuffer(); |
| _add(_signPrefix(number)); |
| - _formatNumber(number.abs()); |
| + _formatNumber(number.abs() * _multiplier); |
| _add(_signSuffix(number)); |
| var result = _buffer.toString(); |
| @@ -115,7 +206,7 @@ class NumberFormat { |
| } |
| /** Format the number in exponential notation. */ |
| - _formatExponential(num number) { |
| + void _formatExponential(num number) { |
| if (number == 0.0) { |
| _formatFixed(number); |
| _formatExponent(0); |
| @@ -123,16 +214,32 @@ class NumberFormat { |
| } |
| var exponent = (log(number) / log(10)).floor(); |
| - var mantissa = number / pow(10, exponent); |
| - |
| - if (_minimumIntegerDigits < 1) { |
| - exponent++; |
| - mantissa /= 10; |
| + var mantissa = number / pow(10.0, exponent); |
| + |
| + var minIntDigits = _minimumIntegerDigits; |
| + if (_maximumIntegerDigits > 1 && |
| + _maximumIntegerDigits > _minimumIntegerDigits) { |
| + // A repeating range is defined; adjust to it as follows. |
| + // If repeat == 3, we have 6,5,4=>3; 3,2,1=>0; 0,-1,-2=>-3; |
| + // -3,-4,-5=>-6, etc. This takes into account that the |
| + // exponent we have here is off by one from what we expect; |
| + // it is for the format 0.MMMMMx10^n. |
| + while ((exponent % _maximumIntegerDigits) != 0) { |
| + mantissa *= 10; |
| + exponent--; |
| + } |
| + minIntDigits = 1; |
| } else { |
| - exponent -= _minimumIntegerDigits - 1; |
| - mantissa *= pow(10, _minimumIntegerDigits - 1); |
| + // No repeating range is defined, use minimum integer digits. |
| + if (_minimumIntegerDigits < 1) { |
| + exponent++; |
| + mantissa /= 10; |
| + } else { |
| + exponent -= _minimumIntegerDigits - 1; |
| + mantissa *= pow(10, _minimumIntegerDigits - 1); |
| + } |
| } |
| - _formatFixed(number); |
| + _formatFixed(mantissa); |
| _formatExponent(exponent); |
| } |
| @@ -150,24 +257,45 @@ class NumberFormat { |
| _pad(_minimumExponentDigits, exponent.toString()); |
| } |
| + /** Used to test if we have exceeded Javascript integer limits. */ |
| + final _maxInt = pow(2, 52); |
| + |
| /** |
| * Format the basic number portion, inluding the fractional digits. |
| */ |
| void _formatFixed(num number) { |
| - // Round the number. |
| + // Very fussy math to get integer and fractional parts. |
| var power = pow(10, _maximumFractionDigits); |
| - var intValue = number.truncate(); |
| - var multiplied = (number * power).round(); |
| - var fracValue = (multiplied - intValue * power).floor(); |
| + var shiftedNumber = (number * power); |
| + // We must not roundToDouble() an int or it will lose precision. We must not |
| + // round() a large double or it will take its loss of precision and |
| + // preserve it in an int, which we will then print to the right |
| + // of the decimal place. Therefore, only roundToDouble if we are already |
| + // a double. |
| + if (shiftedNumber is double) { |
| + shiftedNumber = shiftedNumber.roundToDouble(); |
| + } |
| + var intValue, fracValue; |
| + if (shiftedNumber.isInfinite) { |
| + intValue = number.toInt(); |
| + fracValue = 0; |
| + } else { |
| + intValue = shiftedNumber.round() ~/ power; |
| + fracValue = (shiftedNumber - intValue * power).floor(); |
| + } |
| var fractionPresent = _minimumFractionDigits > 0 || fracValue > 0; |
| - // On dartj2s the integer part may be large enough to be a floating |
| - // point value, in which case we reduce it until it is small enough |
| - // to be printed as an integer and pad the remainder with zeros. |
| + // If the int part is larger than 2^52 and we're on Javascript (so it's |
| + // really a float) it will lose precision, so pad out the rest of it |
| + // with zeros. Check for Javascript by seeing if an integer is double. |
| var paddingDigits = new StringBuffer(); |
| - while ((intValue & 0x7fffffff) != intValue) { |
| - paddingDigits.write(symbols.ZERO_DIGIT); |
| - intValue = intValue ~/ 10; |
| + if (1 is double && intValue > _maxInt) { |
| + var howManyDigitsTooBig = (log(intValue) / LN10).ceil() - 16; |
| + var divisor = pow(10, howManyDigitsTooBig).round(); |
| + for (var each in new List(howManyDigitsTooBig.toInt())) { |
| + paddingDigits.write(symbols.ZERO_DIGIT); |
| + } |
| + intValue = (intValue / divisor).truncate(); |
| } |
| var integerDigits = "${intValue}${paddingDigits}".codeUnits; |
| var digitLength = integerDigits.length; |
| @@ -193,7 +321,7 @@ class NumberFormat { |
| void _formatFractionPart(String fractionPart) { |
| var fractionCodes = fractionPart.codeUnits; |
| var fractionLength = fractionPart.length; |
| - while (fractionPart[fractionLength - 1] == '0' && |
| + while(fractionCodes[fractionLength - 1] == _zero && |
| fractionLength > _minimumFractionDigits + 1) { |
| fractionLength--; |
| } |
| @@ -258,14 +386,14 @@ class NumberFormat { |
| } |
| /** Returns the code point for the character '0'. */ |
| - int get _zero => '0'.codeUnits.first; |
| + final _zero = '0'.codeUnits.first; |
| /** Returns the code point for the locale's zero digit. */ |
| // Note that there is a slight risk of a locale's zero digit not fitting |
| // into a single code unit, but it seems very unlikely, and if it did, |
| // there's a pretty good chance that our assumptions about being able to do |
| // arithmetic on it would also be invalid. |
| - int get _localeZero => symbols.ZERO_DIGIT.codeUnits.first; |
| + get _localeZero => symbols.ZERO_DIGIT.codeUnits.first; |
| /** |
| * Returns the prefix for [x] based on whether it's positive or negative. |
| @@ -282,4 +410,332 @@ class NumberFormat { |
| String _signSuffix(num x) { |
| return x.isNegative ? _negativeSuffix : _positiveSuffix; |
| } |
| + |
| + void _setPattern(String newPattern) { |
| + if (newPattern == null) return; |
| + // Make spaces non-breaking |
| + _pattern = newPattern.replaceAll(' ', '\u00a0'); |
| + var parser = new _NumberFormatParser(this, newPattern); |
| + parser.parse(); |
| + } |
| + |
| + String toString() => "NumberFormat($_locale, $_pattern)"; |
| } |
| + |
| +/** |
| + * Private class that parses the numeric formatting pattern and sets the |
| + * variables in [format] to appropriate values. Instances of this are |
| + * transient and store parsing state in instance variables, so can only be used |
| + * to parse a single pattern. |
| + */ |
| +class _NumberFormatParser { |
| + |
| + /** |
| + * The special characters in the pattern language. All others are treated |
| + * as literals. |
| + */ |
| + static const _PATTERN_SEPARATOR = ';'; |
| + static const _QUOTE = "'"; |
| + static const _PATTERN_DIGIT = '#'; |
| + static const _PATTERN_ZERO_DIGIT = '0'; |
| + static const _PATTERN_GROUPING_SEPARATOR = ','; |
| + static const _PATTERN_DECIMAL_SEPARATOR = '.'; |
| + static const _PATTERN_CURRENCY_SIGN = '\u00A4'; |
| + static const _PATTERN_PER_MILLE = '\u2030'; |
| + static const _PATTERN_PERCENT = '%'; |
| + static const _PATTERN_EXPONENT = 'E'; |
| + static const _PATTERN_PLUS = '+'; |
| + |
| + /** The format whose state we are setting. */ |
| + final NumberFormat format; |
| + |
| + /** The pattern we are parsing. */ |
| + final _StringIterator pattern; |
| + |
| + /** |
| + * Create a new [_NumberFormatParser] for a particular [NumberFormat] and |
| + * [input] pattern. |
| + */ |
| + _NumberFormatParser(this.format, input) : pattern = _iterator(input) { |
| + pattern.moveNext(); |
| + } |
| + |
| + /** The [NumberSymbols] for the locale in which our [format] prints. */ |
| + NumberSymbols get symbols => format.symbols; |
| + |
| + /** Parse the input pattern and set the values. */ |
| + void parse() { |
| + format._positivePrefix = _parseAffix(); |
| + var trunk = _parseTrunk(); |
| + format._positiveSuffix = _parseAffix(); |
| + // If we have separate positive and negative patterns, now parse the |
| + // the negative version. |
| + if (pattern.current == _NumberFormatParser._PATTERN_SEPARATOR) { |
| + pattern.moveNext(); |
| + format._negativePrefix = _parseAffix(); |
| + // Skip over the negative trunk, verifying that it's identical to the |
| + // positive trunk. |
| + for (var each in _iterator(trunk)) { |
| + if (pattern.current != each && pattern.current != null) { |
| + throw new FormatException( |
| + "Positive and negative trunks must be the same"); |
| + } |
| + pattern.moveNext(); |
| + } |
| + format._negativeSuffix = _parseAffix(); |
| + } else { |
| + // If no negative affix is specified, they share the same positive affix. |
| + format._negativePrefix = format._positivePrefix + format._negativePrefix; |
| + format._negativeSuffix = format._negativeSuffix + format._positiveSuffix; |
| + } |
| + } |
| + |
| + /** Variable used in parsing prefixes and suffixes to keep track of |
| + * whether or not we are in a quoted region. */ |
| + bool inQuote = false; |
| + |
| + /** |
| + * Parse a prefix or suffix and return the prefix/suffix string. Note that |
| + * this also may modify the state of [format]. |
| + */ |
| + String _parseAffix() { |
| + var affix = new StringBuffer(); |
| + inQuote = false; |
| + var loop = true; |
| + while (loop) { |
| + loop = parseCharacterAffix(affix) && pattern.moveNext(); |
| + } |
| + return affix.toString(); |
| + } |
| + |
| + /** |
| + * Parse an individual character as part of a prefix or suffix. Return true |
| + * if we should continue to look for more affix characters, and false if |
| + * we have reached the end. |
| + */ |
| + bool parseCharacterAffix(StringBuffer affix) { |
| + var ch = pattern.current; |
| + if (ch == null) return false; |
| + if (ch == _QUOTE) { |
| + var nextChar = pattern.peek; |
| + if (nextChar == _QUOTE) { |
| + pattern.moveNext(); |
| + affix.write(_QUOTE); // 'don''t' |
| + } else { |
| + inQuote = !inQuote; |
| + } |
| + return true; |
| + } |
| + |
| + if (inQuote) { |
| + affix.write(ch); |
| + } else { |
| + switch (ch) { |
| + case _PATTERN_DIGIT: |
| + case _PATTERN_ZERO_DIGIT: |
| + case _PATTERN_GROUPING_SEPARATOR: |
| + case _PATTERN_DECIMAL_SEPARATOR: |
| + case _PATTERN_SEPARATOR: |
| + return false; |
| + case _PATTERN_CURRENCY_SIGN: |
| + // TODO(alanknight): Handle the local/global/portable currency signs |
| + affix.write(symbols.DEF_CURRENCY_CODE); |
| + break; |
| + case _PATTERN_PERCENT: |
| + if (format._multiplier != 1) { |
| + throw new FormatException('Too many percent/permill'); |
|
Emily Fortuna
2013/04/09 19:39:15
nit: can't you make these error messages specific
Alan Knight
2013/04/09 19:58:27
I think I can't, because one of the error cases is
|
| + } |
| + format._multiplier = 100; |
| + affix.write(symbols.PERCENT); |
| + break; |
| + case _PATTERN_PER_MILLE: |
| + if (format._multiplier != 1) { |
| + throw new FormatException('Too many percent/permill'); |
| + } |
| + format._multiplier = 1000; |
| + affix.write(symbols.PERMILL); |
| + break; |
| + default: |
| + affix.write(ch); |
| + } |
| + } |
| + return true; |
| + } |
| + |
| + /** Variables used in [parseTrunk] and [parseTrunkCharacter]. */ |
| + var decimalPos; |
| + var digitLeftCount; |
| + var zeroDigitCount; |
| + var digitRightCount; |
| + var groupingCount; |
| + var trunk; |
| + |
| + /** |
| + * Parse the "trunk" portion of the pattern, the piece that doesn't include |
| + * positive or negative prefixes or suffixes. |
| + */ |
| + String _parseTrunk() { |
| + decimalPos = -1; |
| + digitLeftCount = 0; |
| + zeroDigitCount = 0; |
| + digitRightCount = 0; |
| + groupingCount = -1; |
| + |
| + var loop = true; |
| + trunk = new StringBuffer(); |
| + while (pattern.current != null && loop) { |
| + loop = parseTrunkCharacter(); |
| + } |
| + |
| + if (zeroDigitCount == 0 && digitLeftCount > 0 && decimalPos >= 0) { |
| + // Handle '###.###' and '###.' and '.###' |
| + var n = decimalPos; |
| + if (n == 0) { // Handle '.###' |
| + n++; |
| + } |
| + digitRightCount = digitLeftCount - n; |
| + digitLeftCount = n - 1; |
| + zeroDigitCount = 1; |
| + } |
| + |
| + // Do syntax checking on the digits. |
| + if (decimalPos < 0 && digitRightCount > 0 || |
| + decimalPos >= 0 && (decimalPos < digitLeftCount || |
| + decimalPos > digitLeftCount + zeroDigitCount) || |
| + groupingCount == 0) { |
| + throw new FormatException('Malformed pattern "${pattern.input}"'); |
| + } |
| + var totalDigits = digitLeftCount + zeroDigitCount + digitRightCount; |
| + |
| + format._maximumFractionDigits = |
| + decimalPos >= 0 ? totalDigits - decimalPos : 0; |
| + if (decimalPos >= 0) { |
| + format._minimumFractionDigits = |
| + digitLeftCount + zeroDigitCount - decimalPos; |
| + if (format._minimumFractionDigits < 0) { |
| + format._minimumFractionDigits = 0; |
| + } |
| + } |
| + |
| + // The effectiveDecimalPos is the position the decimal is at or would be at |
| + // if there is no decimal. Note that if decimalPos<0, then digitTotalCount |
| + // == digitLeftCount + zeroDigitCount. |
| + var effectiveDecimalPos = decimalPos >= 0 ? decimalPos : totalDigits; |
| + format._minimumIntegerDigits = effectiveDecimalPos - digitLeftCount; |
| + if (format._useExponentialNotation) { |
| + format._maximumIntegerDigits = |
| + digitLeftCount + format._minimumIntegerDigits; |
| + |
| + // In exponential display, we need to at least show something. |
| + if (format._maximumFractionDigits == 0 && |
| + format._minimumIntegerDigits == 0) { |
| + format._minimumIntegerDigits = 1; |
| + } |
| + } |
| + |
| + format._groupingSize = max(0, groupingCount); |
| + format._decimalSeparatorAlwaysShown = decimalPos == 0 || |
| + decimalPos == totalDigits; |
| + |
| + return trunk.toString(); |
| + } |
| + |
| + /** |
| + * Parse an individual character of the trunk. Return true if we should |
| + * continue to look for additional trunk characters or false if we have |
| + * reached the end. |
| + */ |
| + bool parseTrunkCharacter() { |
| + var ch = pattern.current; |
| + switch (ch) { |
| + case _PATTERN_DIGIT: |
| + if (zeroDigitCount > 0) { |
| + digitRightCount++; |
| + } else { |
| + digitLeftCount++; |
| + } |
| + if (groupingCount >= 0 && decimalPos < 0) { |
| + groupingCount++; |
| + } |
| + break; |
| + case _PATTERN_ZERO_DIGIT: |
| + if (digitRightCount > 0) { |
| + throw new FormatException('Unexpected "0" in pattern "' |
| + + pattern.input + '"'); |
| + } |
| + zeroDigitCount++; |
| + if (groupingCount >= 0 && decimalPos < 0) { |
| + groupingCount++; |
| + } |
| + break; |
| + case _PATTERN_GROUPING_SEPARATOR: |
| + groupingCount = 0; |
| + break; |
| + case _PATTERN_DECIMAL_SEPARATOR: |
| + if (decimalPos >= 0) { |
| + throw new FormatException( |
| + 'Multiple decimal separators in pattern "$pattern"'); |
| + } |
| + decimalPos = digitLeftCount + zeroDigitCount + digitRightCount; |
| + break; |
| + case _PATTERN_EXPONENT: |
| + trunk.write(ch); |
| + if (format._useExponentialNotation) { |
| + throw new FormatException( |
| + 'Multiple exponential symbols in pattern "$pattern"'); |
| + } |
| + format._useExponentialNotation = true; |
| + format._minimumExponentDigits = 0; |
| + |
| + // exponent pattern can have a optional '+'. |
| + pattern.moveNext(); |
| + var nextChar = pattern.current; |
| + if (nextChar == _PATTERN_PLUS) { |
| + trunk.write(pattern.current); |
| + pattern.moveNext(); |
| + format._useSignForPositiveExponent = true; |
| + } |
| + |
| + // Use lookahead to parse out the exponential part |
| + // of the pattern, then jump into phase 2. |
| + while (pattern.current == _PATTERN_ZERO_DIGIT) { |
| + trunk.write(pattern.current); |
| + pattern.moveNext(); |
| + format._minimumExponentDigits++; |
| + } |
| + |
| + if ((digitLeftCount + zeroDigitCount) < 1 || |
| + format._minimumExponentDigits < 1) { |
| + throw new FormatException( |
| + 'Malformed exponential pattern "$pattern"'); |
| + } |
| + return false; |
| + default: |
| + return false; |
| + } |
| + trunk.write(ch); |
| + pattern.moveNext(); |
| + return true; |
| + } |
| +} |
| + |
| +/** |
| + * Return an iterator on the string as a list of substrings. |
| + */ |
| +Iterator _iterator(String s) => new _StringIterator(s); |
| + |
| +/** |
| + * Provides an iterator over a string as a list of substrings, and also |
| + * gives us a lookahead of one via the [peek] method. |
| + */ |
| +class _StringIterator implements Iterator<String> { |
| + String input; |
| + var index = -1; |
| + inBounds(i) => i >= 0 && i < input.length; |
| + _StringIterator(this.input); |
| + String get current => inBounds(index) ? input[index] : null; |
| + |
| + bool moveNext() => inBounds(++index); |
| + String get peek => inBounds(index + 1) ? input[index + 1] : null; |
| + Iterator<String> get iterator => this; |
| +} |