| 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..c700e6217f0325f13b761aaad7e5e7e470efe279 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.percentFormat("ar");
|
| + * There are four such constructors: decimalFormat, percentFormat,
|
| + * scientificFormat and currencyForamt. However, at the moment,
|
| + * scientificFormat prints only as equivalent to "#E0" and does not take
|
| + * into account significant digits. currencyFormat 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.decimalPattern([String locale]) :
|
| + this._forPattern(locale, (x) => x.DECIMAL_PATTERN);
|
| +
|
| + /** Create a number format that prints as PERCENT_PATTERN. */
|
| + NumberFormat.percentPattern([String locale]) :
|
| + this._forPattern(locale, (x) => x.PERCENT_PATTERN);
|
| +
|
| + /** Create a number format that prints as SCIENTIFIC_PATTERN. */
|
| + NumberFormat.scientificPattern([String locale]) :
|
| + this._forPattern(locale, (x) => x.SCIENTIFIC_PATTERN);
|
| +
|
| + /** Create a number format that prints as CURRENCY_PATTERN. */
|
| + NumberFormat.currencyPattern([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');
|
| + }
|
| + 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;
|
| +}
|
|
|