Simplify `NumberFormat` initialization.
Move as much initialization logic as possible to `NumberFormatParser`, and make it output a new value instead of modifying a `NumberFormat` in place.
PiperOrigin-RevId: 328332371
diff --git a/lib/src/intl/compact_number_format.dart b/lib/src/intl/compact_number_format.dart
index 272ef22..ceb13d1 100644
--- a/lib/src/intl/compact_number_format.dart
+++ b/lib/src/intl/compact_number_format.dart
@@ -151,14 +151,14 @@
String name,
String currencySymbol,
String Function(NumberSymbols) getPattern = _forDecimal,
- String Function(NumberFormat) computeCurrencySymbol,
int decimalDigits,
+ bool lookupSimpleCurrencySymbol = false,
bool isForCurrency = false})
: super._forPattern(locale, getPattern,
name: name,
currencySymbol: currencySymbol,
- computeCurrencySymbol: computeCurrencySymbol,
decimalDigits: decimalDigits,
+ lookupSimpleCurrencySymbol: lookupSimpleCurrencySymbol,
isForCurrency: isForCurrency) {
significantDigits = 3;
turnOffGrouping();
diff --git a/lib/src/intl/number_format.dart b/lib/src/intl/number_format.dart
index 487e2e3..bfa4237 100644
--- a/lib/src/intl/number_format.dart
+++ b/lib/src/intl/number_format.dart
@@ -60,42 +60,37 @@
/// equivalent to "#E0" and does not take into account significant digits.
class NumberFormat {
/// Variables to determine how number printing behaves.
- // TODO(alanknight): If these remain as variables and are set based on the
- // pattern, can we make them final?
- String _negativePrefix = '-';
- String _positivePrefix = '';
- String _negativeSuffix = '';
- String _positiveSuffix = '';
+ String _negativePrefix;
+ String _positivePrefix;
+ String _negativeSuffix;
+ String _positiveSuffix;
/// 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;
+ int _groupingSize;
/// In some formats the last grouping size may be different than previous
/// ones, e.g. Hindi.
- int _finalGroupingSize = 3;
+ int _finalGroupingSize;
/// Set to true if the format has explicitly set the grouping size.
- bool _groupingSizeSetExplicitly = false;
- bool _decimalSeparatorAlwaysShown = false;
- bool _useSignForPositiveExponent = false;
- bool _useExponentialNotation = false;
+ bool _decimalSeparatorAlwaysShown;
+ bool _useSignForPositiveExponent;
+ bool _useExponentialNotation;
/// Explicitly store if we are a currency format, and so should use the
/// appropriate number of decimal digits for a currency.
// TODO(alanknight): Handle currency formats which are specified in a raw
/// pattern, not using one of the currency constructors.
- bool _isForCurrency = false;
+ bool _isForCurrency;
- int maximumIntegerDigits = 40;
- int minimumIntegerDigits = 1;
- int maximumFractionDigits = 3;
- int minimumFractionDigits = 0;
- int minimumExponentDigits = 0;
- int _significantDigits = 0;
-
- static final _ln10 = log(10);
+ int maximumIntegerDigits;
+ int minimumIntegerDigits;
+ int maximumFractionDigits;
+ int minimumFractionDigits;
+ int minimumExponentDigits;
+ int _significantDigits;
/// How many significant digits should we print.
///
@@ -111,16 +106,10 @@
/// For percent and permille, what are we multiplying by in order to
/// get the printed value, e.g. 100 for percent.
- int get _multiplier => _internalMultiplier;
- set _multiplier(int x) {
- _internalMultiplier = x;
- _multiplierDigits = (log(_multiplier) / _ln10).round();
- }
-
- int _internalMultiplier = 1;
+ int _multiplier;
/// How many digits are there in the [_multiplier].
- int _multiplierDigits = 0;
+ int _multiplierDigits;
/// Stores the pattern used to create this format. This isn't used, but
/// is helpful in debugging.
@@ -143,7 +132,7 @@
/// The symbol to be used when formatting this as currency.
///
/// For example, "$", "US$", or "€".
- String get currencySymbol => _currencySymbol ?? currencyName;
+ String get currencySymbol => _currencySymbol;
/// The number of decimal places to use when formatting.
///
@@ -164,17 +153,6 @@
int _decimalDigits;
- /// For currencies, the default number of decimal places to use in
- /// formatting. Defaults to two for non-currencies or currencies where it's
- /// not specified.
- int get _defaultDecimalDigits =>
- currencyFractionDigits[currencyName.toUpperCase()] ??
- currencyFractionDigits['DEFAULT'];
-
- /// If we have a currencyName, use the decimal digits for that currency,
- /// unless we've explicitly specified some other number.
- bool get _overridesDecimalDigits => decimalDigits != null || _isForCurrency;
-
/// Transient internal state in which to build up the result of the format
/// operation. We can have this be just an instance variable because Dart is
/// single-threaded and unless we do an asynchronous operation in the process
@@ -303,9 +281,8 @@
{String locale, String name, int decimalDigits}) {
return NumberFormat._forPattern(locale, (x) => x.CURRENCY_PATTERN,
name: name,
- computeCurrencySymbol: (format) =>
- _simpleCurrencySymbols[format.currencyName] ?? format.currencyName,
decimalDigits: decimalDigits,
+ lookupSimpleCurrencySymbol: true,
isForCurrency: true);
}
@@ -509,22 +486,53 @@
NumberFormat._forPattern(String locale, _PatternGetter getPattern,
{String name,
String currencySymbol,
- String Function(NumberFormat) computeCurrencySymbol,
int decimalDigits,
+ bool lookupSimpleCurrencySymbol = false,
bool isForCurrency = false})
: _locale = Intl.verifiedLocale(locale, localeExists),
_isForCurrency = isForCurrency {
- _currencySymbol = currencySymbol;
- _decimalDigits = decimalDigits;
_symbols = numberFormatSymbols[_locale];
_localeZero = _symbols.ZERO_DIGIT.codeUnitAt(0);
_zeroOffset = _localeZero - _zero;
- _negativePrefix = _symbols.MINUS_SIGN;
currencyName = name ?? _symbols.DEF_CURRENCY_CODE;
- if (_currencySymbol == null && computeCurrencySymbol != null) {
- _currencySymbol = computeCurrencySymbol(this);
+
+ _currencySymbol = currencySymbol;
+ if (_currencySymbol == null && lookupSimpleCurrencySymbol) {
+ _currencySymbol = _simpleCurrencySymbols[currencyName];
}
- _setPattern(getPattern(_symbols));
+ _currencySymbol ??= currencyName;
+
+ var pattern = getPattern(symbols);
+
+ // Save pattern with spaces converted to hard spaces, just for debugging.
+ _pattern = pattern?.replaceAll(' ', '\u00a0');
+
+ var result = _NumberFormatParser.parse(symbols, pattern, _isForCurrency,
+ _currencySymbol, currencyName, decimalDigits);
+
+ _positivePrefix = result.positivePrefix;
+ _negativePrefix = result.negativePrefix;
+ _positiveSuffix = result.positiveSuffix;
+ _negativeSuffix = result.negativeSuffix;
+
+ _multiplier = result.multiplier;
+ _multiplierDigits = result.multiplierDigits;
+
+ _useExponentialNotation = result.useExponentialNotation;
+ minimumExponentDigits = result.minimumExponentDigits;
+
+ maximumIntegerDigits = result.maximumIntegerDigits;
+ minimumIntegerDigits = result.minimumIntegerDigits;
+ maximumFractionDigits = result.maximumFractionDigits;
+ minimumFractionDigits = result.minimumFractionDigits;
+
+ _groupingSize = result.groupingSize;
+ _finalGroupingSize = result.finalGroupingSize;
+
+ _useSignForPositiveExponent = result.useSignForPositiveExponent;
+ _decimalSeparatorAlwaysShown = result.decimalSeparatorAlwaysShown;
+
+ _decimalDigits = result.decimalDigits;
}
/// A number format for compact representations, e.g. "1.2M" instead
@@ -554,9 +562,8 @@
formatType: _CompactFormatType.COMPACT_DECIMAL_SHORT_CURRENCY_PATTERN,
name: name,
getPattern: (symbols) => symbols.CURRENCY_PATTERN,
- computeCurrencySymbol: (format) =>
- _simpleCurrencySymbols[format.currencyName] ?? format.currencyName,
decimalDigits: decimalDigits,
+ lookupSimpleCurrencySymbol: true,
isForCurrency: true);
}
@@ -956,20 +963,6 @@
/// In en_US there are no suffixes for positive or negative.
String _signSuffix(x) => x.isNegative ? _negativeSuffix : _positiveSuffix;
- void _setPattern(String newPattern) {
- if (newPattern == null) return;
- // Make spaces non-breaking
- _pattern = newPattern.replaceAll(' ', '\u00a0');
- var parser =
- _NumberFormatParser(this, newPattern, currencySymbol, decimalDigits);
- parser.parse();
- if (_overridesDecimalDigits) {
- _decimalDigits ??= _defaultDecimalDigits;
- minimumFractionDigits = _decimalDigits;
- maximumFractionDigits = _decimalDigits;
- }
- }
-
/// Explicitly turn off any grouping (e.g. by thousands) in this format.
///
/// This is used in compact number formatting, where we
@@ -1040,7 +1033,7 @@
/// Create a new [_NumberParser] on which we can call parse().
_NumberParser(this.format, this.text) : input = IntlStream(text) {
- scale = format._internalMultiplier;
+ scale = format._multiplier;
value = parse();
}
@@ -1214,6 +1207,40 @@
}
}
+/// Output of [_NumberFormatParser.parse].
+///
+/// Everything needed to initialize a [NumberFormat].
+class _NumberFormatParseResult {
+ String negativePrefix;
+ String positivePrefix = '';
+ String negativeSuffix = '';
+ String positiveSuffix = '';
+
+ int multiplier = 1;
+ int get multiplierDigits => (log(multiplier) / _ln10).round();
+
+ int minimumExponentDigits = 0;
+
+ int maximumIntegerDigits = 40;
+ int minimumIntegerDigits = 1;
+ int maximumFractionDigits = 3;
+ int minimumFractionDigits = 0;
+
+ int groupingSize = 3;
+ int finalGroupingSize = 3;
+
+ bool decimalSeparatorAlwaysShown = false;
+ bool useSignForPositiveExponent = false;
+ bool useExponentialNotation = false;
+
+ int decimalDigits;
+
+ // [decimalDigits] is both input and output of parsing.
+ _NumberFormatParseResult(NumberSymbols symbols, this.decimalDigits) {
+ negativePrefix = symbols.MINUS_SIGN;
+ }
+}
+
/// 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
@@ -1236,39 +1263,66 @@
static const _PATTERN_PLUS = '+';
/// The format whose state we are setting.
- final NumberFormat format;
+ final NumberSymbols symbols;
/// The pattern we are parsing.
final _StringIterator pattern;
- /// We can be passed a specific currency symbol, regardless of the locale.
- String currencySymbol;
+ /// Whether this is a currency.
+ final bool isForCurrency;
- /// We can be given a specific number of decimal places, overriding the
- /// default.
- final int decimalDigits;
+ /// We can be passed a specific currency symbol, regardless of the locale.
+ final String currencySymbol;
+
+ final String currencyName;
+
+ // The result being constructed.
+ final _NumberFormatParseResult result;
+
+ bool groupingSizeSetExplicitly = false;
/// Create a new [_NumberFormatParser] for a particular [NumberFormat] and
/// [input] pattern.
- _NumberFormatParser(
- this.format, input, this.currencySymbol, this.decimalDigits)
- : pattern = _iterator(input) {
+ ///
+ /// [decimalDigits] is optional, if specified it overrides the default.
+ _NumberFormatParser(this.symbols, String input, this.isForCurrency,
+ this.currencySymbol, this.currencyName, int decimalDigits)
+ : result = _NumberFormatParseResult(symbols, decimalDigits),
+ pattern = _iterator(input) {
pattern.moveNext();
}
- /// The [NumberSymbols] for the locale in which our [format] prints.
- NumberSymbols get symbols => format.symbols;
+ static _NumberFormatParseResult parse(
+ NumberSymbols symbols,
+ String input,
+ bool isForCurrency,
+ String currencySymbol,
+ String currencyName,
+ int decimalDigits) =>
+ input == null
+ ? _NumberFormatParseResult(symbols, decimalDigits)
+ : (_NumberFormatParser(symbols, input, isForCurrency, currencySymbol,
+ currencyName, decimalDigits)
+ .._parse())
+ .result;
- /// Parse the input pattern and set the values.
- void parse() {
- format._positivePrefix = _parseAffix();
+ /// For currencies, the default number of decimal places to use in
+ /// formatting. Defaults to two for non-currencies or currencies where it's
+ /// not specified.
+ int get _defaultDecimalDigits =>
+ currencyFractionDigits[currencyName.toUpperCase()] ??
+ currencyFractionDigits['DEFAULT'];
+
+ /// Parse the input pattern and update [result].
+ void _parse() {
+ result.positivePrefix = _parseAffix();
var trunk = _parseTrunk();
- format._positiveSuffix = _parseAffix();
+ result.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();
+ result.negativePrefix = _parseAffix();
// Skip over the negative trunk, verifying that it's identical to the
// positive trunk.
for (var each in _iterable(trunk)) {
@@ -1278,11 +1332,19 @@
}
pattern.moveNext();
}
- format._negativeSuffix = _parseAffix();
+ result.negativeSuffix = _parseAffix();
} else {
// If no negative affix is specified, they share the same positive affix.
- format._negativePrefix = format._negativePrefix + format._positivePrefix;
- format._negativeSuffix = format._positiveSuffix + format._negativeSuffix;
+ result.negativePrefix = result.negativePrefix + result.positivePrefix;
+ result.negativeSuffix = result.positiveSuffix + result.negativeSuffix;
+ }
+
+ if (isForCurrency) {
+ result.decimalDigits ??= _defaultDecimalDigits;
+ }
+ if (result.decimalDigits != null) {
+ result.minimumFractionDigits = result.decimalDigits;
+ result.maximumFractionDigits = result.decimalDigits;
}
}
@@ -1330,18 +1392,17 @@
affix.write(currencySymbol);
break;
case _PATTERN_PERCENT:
- if (format._multiplier != 1 && format._multiplier != _PERCENT_SCALE) {
- throw FormatException('Too many percent/permill', format);
+ if (result.multiplier != 1 && result.multiplier != _PERCENT_SCALE) {
+ throw const FormatException('Too many percent/permill');
}
- format._multiplier = _PERCENT_SCALE;
+ result.multiplier = _PERCENT_SCALE;
affix.write(symbols.PERCENT);
break;
case _PATTERN_PER_MILLE:
- if (format._multiplier != 1 &&
- format._multiplier != _PER_MILLE_SCALE) {
- throw FormatException('Too many percent/permill', format);
+ if (result.multiplier != 1 && result.multiplier != _PER_MILLE_SCALE) {
+ throw const FormatException('Too many percent/permill');
}
- format._multiplier = _PER_MILLE_SCALE;
+ result.multiplier = _PER_MILLE_SCALE;
affix.write(symbols.PERMILL);
break;
default:
@@ -1386,13 +1447,13 @@
}
var totalDigits = digitLeftCount + zeroDigitCount + digitRightCount;
- format.maximumFractionDigits =
+ result.maximumFractionDigits =
decimalPos >= 0 ? totalDigits - decimalPos : 0;
if (decimalPos >= 0) {
- format.minimumFractionDigits =
+ result.minimumFractionDigits =
digitLeftCount + zeroDigitCount - decimalPos;
- if (format.minimumFractionDigits < 0) {
- format.minimumFractionDigits = 0;
+ if (result.minimumFractionDigits < 0) {
+ result.minimumFractionDigits = 0;
}
}
@@ -1400,23 +1461,23 @@
// 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;
+ result.minimumIntegerDigits = effectiveDecimalPos - digitLeftCount;
+ if (result.useExponentialNotation) {
+ result.maximumIntegerDigits =
+ digitLeftCount + result.minimumIntegerDigits;
// In exponential display, we need to at least show something.
- if (format.maximumFractionDigits == 0 &&
- format.minimumIntegerDigits == 0) {
- format.minimumIntegerDigits = 1;
+ if (result.maximumFractionDigits == 0 &&
+ result.minimumIntegerDigits == 0) {
+ result.minimumIntegerDigits = 1;
}
}
- format._finalGroupingSize = max(0, groupingCount);
- if (!format._groupingSizeSetExplicitly) {
- format._groupingSize = format._finalGroupingSize;
+ result.finalGroupingSize = max(0, groupingCount);
+ if (!groupingSizeSetExplicitly) {
+ result.groupingSize = result.finalGroupingSize;
}
- format._decimalSeparatorAlwaysShown =
+ result.decimalSeparatorAlwaysShown =
decimalPos == 0 || decimalPos == totalDigits;
return trunk.toString();
@@ -1449,8 +1510,8 @@
break;
case _PATTERN_GROUPING_SEPARATOR:
if (groupingCount > 0) {
- format._groupingSizeSetExplicitly = true;
- format._groupingSize = groupingCount;
+ groupingSizeSetExplicitly = true;
+ result.groupingSize = groupingCount;
}
groupingCount = 0;
break;
@@ -1463,12 +1524,12 @@
break;
case _PATTERN_EXPONENT:
trunk.write(ch);
- if (format._useExponentialNotation) {
+ if (result.useExponentialNotation) {
throw FormatException(
'Multiple exponential symbols in pattern "$pattern"');
}
- format._useExponentialNotation = true;
- format.minimumExponentDigits = 0;
+ result.useExponentialNotation = true;
+ result.minimumExponentDigits = 0;
// exponent pattern can have a optional '+'.
pattern.moveNext();
@@ -1476,7 +1537,7 @@
if (nextChar == _PATTERN_PLUS) {
trunk.write(pattern.current);
pattern.moveNext();
- format._useSignForPositiveExponent = true;
+ result.useSignForPositiveExponent = true;
}
// Use lookahead to parse out the exponential part
@@ -1484,11 +1545,11 @@
while (pattern.current == _PATTERN_ZERO_DIGIT) {
trunk.write(pattern.current);
pattern.moveNext();
- format.minimumExponentDigits++;
+ result.minimumExponentDigits++;
}
if ((digitLeftCount + zeroDigitCount) < 1 ||
- format.minimumExponentDigits < 1) {
+ result.minimumExponentDigits < 1) {
throw FormatException('Malformed exponential pattern "$pattern"');
}
return false;
@@ -1624,3 +1685,5 @@
return '$beforeDecimal$decimalPart';
}
}
+
+final _ln10 = log(10);