Split up `number_format.dart`.

PiperOrigin-RevId: 328335245
diff --git a/lib/intl.dart b/lib/intl.dart
index 880dbcc..7b0c6b9 100644
--- a/lib/intl.dart
+++ b/lib/intl.dart
@@ -29,7 +29,8 @@
 export 'src/intl/bidi.dart' show Bidi;
 export 'src/intl/bidi_formatter.dart' show BidiFormatter;
 export 'src/intl/date_format.dart' show DateFormat;
-export 'src/intl/number_format.dart' show NumberFormat, MicroMoney;
+export 'src/intl/micro_money.dart' show MicroMoney;
+export 'src/intl/number_format.dart' show NumberFormat;
 export 'src/intl/text_direction.dart' show TextDirection;
 
 /// The Intl class provides a common entry point for internationalization
diff --git a/lib/src/intl/compact_number_format.dart b/lib/src/intl/compact_number_format.dart
index fd53814..99f5280 100644
--- a/lib/src/intl/compact_number_format.dart
+++ b/lib/src/intl/compact_number_format.dart
@@ -192,10 +192,10 @@
     locale = Intl.verifiedLocale(locale, NumberFormat.localeExists);
     var symbols = numberFormatSymbols[locale];
     var localeZero = symbols.ZERO_DIGIT.codeUnitAt(0);
-    var zeroOffset = localeZero - NumberFormat._zero;
+    var zeroOffset = localeZero - constants.asciiZeroCodeUnit;
     name ??= symbols.DEF_CURRENCY_CODE;
     if (currencySymbol == null && lookupSimpleCurrencySymbol) {
-      currencySymbol = NumberFormat._simpleCurrencySymbols[name];
+      currencySymbol = constants.simpleCurrencySymbols[name];
     }
     currencySymbol ??= name;
     var pattern = getPattern(symbols);
@@ -256,7 +256,7 @@
         pattern,
         symbols,
         zeroOffset,
-        _NumberFormatParser.parse(symbols, pattern, isForCurrency,
+        NumberFormatParser.parse(symbols, pattern, isForCurrency,
             currencySymbol, name, decimalDigits),
         styles);
   }
@@ -270,7 +270,7 @@
       String pattern,
       NumberSymbols symbols,
       int zeroOffset,
-      _NumberFormatParseResult result,
+      NumberFormatParseResult result,
       // Fields introduced in this class.
       this._styles)
       : super._(currencyName, currencySymbol, isForCurrency, locale, localeZero,
diff --git a/lib/src/intl/constants.dart b/lib/src/intl/constants.dart
index 51ac779..83e2607 100644
--- a/lib/src/intl/constants.dart
+++ b/lib/src/intl/constants.dart
@@ -3,3 +3,166 @@
 // BSD-style license that can be found in the LICENSE file.
 
 final int asciiZeroCodeUnit = '0'.codeUnitAt(0);
+
+final Map<String, String> simpleCurrencySymbols = {
+  'AFN': 'Af.',
+  'TOP': r'T$',
+  'MGA': 'Ar',
+  'THB': '\u0e3f',
+  'PAB': 'B/.',
+  'ETB': 'Birr',
+  'VEF': 'Bs',
+  'BOB': 'Bs',
+  'GHS': 'GHS',
+  'CRC': '\u20a1',
+  'NIO': r'C$',
+  'GMD': 'GMD',
+  'MKD': 'din',
+  'BHD': 'din',
+  'DZD': 'din',
+  'IQD': 'din',
+  'JOD': 'din',
+  'KWD': 'din',
+  'LYD': 'din',
+  'RSD': 'din',
+  'TND': 'din',
+  'AED': 'dh',
+  'MAD': 'dh',
+  'STD': 'Db',
+  'BSD': r'$',
+  'FJD': r'$',
+  'GYD': r'$',
+  'KYD': r'$',
+  'LRD': r'$',
+  'SBD': r'$',
+  'SRD': r'$',
+  'AUD': r'$',
+  'BBD': r'$',
+  'BMD': r'$',
+  'BND': r'$',
+  'BZD': r'$',
+  'CAD': r'$',
+  'HKD': r'$',
+  'JMD': r'$',
+  'NAD': r'$',
+  'NZD': r'$',
+  'SGD': r'$',
+  'TTD': r'$',
+  'TWD': r'NT$',
+  'USD': r'$',
+  'XCD': r'$',
+  'VND': '\u20ab',
+  'AMD': 'Dram',
+  'CVE': 'CVE',
+  'EUR': '\u20ac',
+  'AWG': 'Afl.',
+  'HUF': 'Ft',
+  'BIF': 'FBu',
+  'CDF': 'FrCD',
+  'CHF': 'CHF',
+  'DJF': 'Fdj',
+  'GNF': 'FG',
+  'RWF': 'RF',
+  'XOF': 'CFA',
+  'XPF': 'FCFP',
+  'KMF': 'CF',
+  'XAF': 'FCFA',
+  'HTG': 'HTG',
+  'PYG': 'Gs',
+  'UAH': '\u20b4',
+  'PGK': 'PGK',
+  'LAK': '\u20ad',
+  'CZK': 'K\u010d',
+  'SEK': 'kr',
+  'ISK': 'kr',
+  'DKK': 'kr',
+  'NOK': 'kr',
+  'HRK': 'kn',
+  'MWK': 'MWK',
+  'ZMK': 'ZWK',
+  'AOA': 'Kz',
+  'MMK': 'K',
+  'GEL': 'GEL',
+  'LVL': 'Ls',
+  'ALL': 'Lek',
+  'HNL': 'L',
+  'SLL': 'SLL',
+  'MDL': 'MDL',
+  'RON': 'RON',
+  'BGN': 'lev',
+  'SZL': 'SZL',
+  'TRY': 'TL',
+  'LTL': 'Lt',
+  'LSL': 'LSL',
+  'AZN': 'man.',
+  'BAM': 'KM',
+  'MZN': 'MTn',
+  'NGN': '\u20a6',
+  'ERN': 'Nfk',
+  'BTN': 'Nu.',
+  'MRO': 'MRO',
+  'MOP': 'MOP',
+  'CUP': r'$',
+  'CUC': r'$',
+  'ARS': r'$',
+  'CLF': 'UF',
+  'CLP': r'$',
+  'COP': r'$',
+  'DOP': r'$',
+  'MXN': r'$',
+  'PHP': '\u20b1',
+  'UYU': r'$',
+  'FKP': '£',
+  'GIP': '£',
+  'SHP': '£',
+  'EGP': 'E£',
+  'LBP': 'L£',
+  'SDG': 'SDG',
+  'SSP': 'SSP',
+  'GBP': '£',
+  'SYP': '£',
+  'BWP': 'P',
+  'GTQ': 'Q',
+  'ZAR': 'R',
+  'BRL': r'R$',
+  'OMR': 'Rial',
+  'QAR': 'Rial',
+  'YER': 'Rial',
+  'IRR': 'Rial',
+  'KHR': 'Riel',
+  'MYR': 'RM',
+  'SAR': 'Riyal',
+  'BYR': 'BYR',
+  'RUB': 'руб.',
+  'MUR': 'Rs',
+  'SCR': 'SCR',
+  'LKR': 'Rs',
+  'NPR': 'Rs',
+  'INR': '\u20b9',
+  'PKR': 'Rs',
+  'IDR': 'Rp',
+  'ILS': '\u20aa',
+  'KES': 'Ksh',
+  'SOS': 'SOS',
+  'TZS': 'TSh',
+  'UGX': 'UGX',
+  'PEN': 'S/.',
+  'KGS': 'KGS',
+  'UZS': 'so\u02bcm',
+  'TJS': 'Som',
+  'BDT': '\u09f3',
+  'WST': 'WST',
+  'KZT': '\u20b8',
+  'MNT': '\u20ae',
+  'VUV': 'VUV',
+  'KPW': '\u20a9',
+  'KRW': '\u20a9',
+  'JPY': '¥',
+  'CNY': '¥',
+  'PLN': 'z\u0142',
+  'MVR': 'Rf',
+  'NLG': 'NAf',
+  'ZMW': 'ZK',
+  'ANG': 'ƒ',
+  'TMT': 'TMT',
+};
diff --git a/lib/src/intl/micro_money.dart b/lib/src/intl/micro_money.dart
new file mode 100644
index 0000000..f773660
--- /dev/null
+++ b/lib/src/intl/micro_money.dart
@@ -0,0 +1,83 @@
+// 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.
+// @dart=2.9
+
+/// Used primarily for currency formatting, this number-like class stores
+/// millionths of a currency unit, typically as an Int64.
+///
+/// It supports no operations other than being used for Intl number formatting.
+abstract class MicroMoney {
+  factory MicroMoney(micros) => _MicroMoney(micros);
+}
+
+/// Used primarily for currency formatting, this stores millionths of a
+/// currency unit, typically as an Int64.
+///
+/// This private class provides the operations needed by the formatting code.
+class _MicroMoney implements MicroMoney {
+  final dynamic _micros;
+  _MicroMoney(this._micros);
+  static const _multiplier = 1000000;
+
+  dynamic get _integerPart => _micros ~/ _multiplier;
+  int get _fractionPart => (this - _integerPart)._micros.toInt().abs();
+
+  bool get isNegative => _micros.isNegative;
+
+  _MicroMoney abs() => isNegative ? _MicroMoney(_micros.abs()) : this;
+
+  // Note that if this is done in a general way there's a risk of integer
+  // overflow on JS when multiplying out the [other] parameter, which may be
+  // an Int64. In formatting we only ever subtract out our own integer part.
+  _MicroMoney operator -(other) {
+    if (other is _MicroMoney) return _MicroMoney(_micros - other._micros);
+    return _MicroMoney(_micros - (other * _multiplier));
+  }
+
+  _MicroMoney operator +(other) {
+    if (other is _MicroMoney) return _MicroMoney(_micros + other._micros);
+    return _MicroMoney(_micros + (other * _multiplier));
+  }
+
+  _MicroMoney operator ~/(divisor) {
+    if (divisor is! int) {
+      throw ArgumentError.value(
+          divisor, 'divisor', '_MicroMoney ~/ only supports int arguments.');
+    }
+    return _MicroMoney((_integerPart ~/ divisor) * _multiplier);
+  }
+
+  _MicroMoney operator *(other) {
+    if (other is! int) {
+      throw ArgumentError.value(
+          other, 'other', '_MicroMoney * only supports int arguments.');
+    }
+    return _MicroMoney(
+        (_integerPart * other) * _multiplier + (_fractionPart * other));
+  }
+
+  /// Note that this only really supports remainder from an int,
+  /// not division by another MicroMoney
+  _MicroMoney remainder(other) {
+    if (other is! int) {
+      throw ArgumentError.value(
+          other, 'other', '_MicroMoney.remainder only supports int arguments.');
+    }
+    return _MicroMoney(_micros.remainder(other * _multiplier));
+  }
+
+  double toDouble() => _micros.toDouble() / _multiplier;
+
+  int toInt() => _integerPart.toInt();
+
+  String toString() {
+    var beforeDecimal = '$_integerPart';
+    var decimalPart = '';
+    var fractionPart = _fractionPart;
+    if (fractionPart != 0) {
+      decimalPart = '.$fractionPart';
+    }
+    return '$beforeDecimal$decimalPart';
+  }
+}
diff --git a/lib/src/intl/number_format.dart b/lib/src/intl/number_format.dart
index be92ab8..d3d8454 100644
--- a/lib/src/intl/number_format.dart
+++ b/lib/src/intl/number_format.dart
@@ -3,14 +3,15 @@
 // BSD-style license that can be found in the LICENSE file.
 // @dart=2.9
 
-import 'dart:collection';
 import 'dart:math';
 
 import 'package:intl/intl.dart';
 import 'package:intl/number_symbols.dart';
 import 'package:intl/number_symbols_data.dart';
 
-import 'intl_stream.dart';
+import 'constants.dart' as constants;
+import 'number_format_parser.dart';
+import 'number_parser.dart';
 
 part 'compact_number_format.dart';
 
@@ -60,10 +61,10 @@
 /// equivalent to "#E0" and does not take into account significant digits.
 class NumberFormat {
   /// Variables to determine how number printing behaves.
-  final String _negativePrefix;
-  final String _positivePrefix;
-  final String _negativeSuffix;
-  final String _positiveSuffix;
+  final String negativePrefix;
+  final String positivePrefix;
+  final String negativeSuffix;
+  final 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
@@ -106,9 +107,9 @@
 
   /// For percent and permille, what are we multiplying by in order to
   /// get the printed value, e.g. 100 for percent.
-  final int _multiplier;
+  final int multiplier;
 
-  /// How many digits are there in the [_multiplier].
+  /// How many digits are there in the [multiplier].
   final int _multiplierDigits;
 
   /// Stores the pattern used to create this format. This isn't used, but
@@ -295,182 +296,7 @@
   /// (The current implementation is the same for all locales, but this is
   /// temporary and callers shouldn't rely on it.)
   String simpleCurrencySymbol(String currencyCode) =>
-      _simpleCurrencySymbols[currencyCode] ?? currencyCode;
-
-  /// A map from currency names to the simple name/symbol.
-  ///
-  /// The simple currency symbol is generally short, and the same or related to
-  /// what is used in countries having the currency as an official symbol. It
-  /// may be a symbol character, or may have letters, or both. It may be
-  /// different according to the locale: for example, for an Arabic locale it
-  /// may consist of Arabic letters, but for a French locale consist of Latin
-  /// letters. It will not be unique: for example, "$" can appear for both USD
-  /// and CAD.
-  ///
-  /// (The current implementation is the same for all locales, but this is
-  /// temporary and callers shouldn't rely on it.)
-  static final Map<String, String> _simpleCurrencySymbols = {
-    'AFN': 'Af.',
-    'TOP': r'T$',
-    'MGA': 'Ar',
-    'THB': '\u0e3f',
-    'PAB': 'B/.',
-    'ETB': 'Birr',
-    'VEF': 'Bs',
-    'BOB': 'Bs',
-    'GHS': 'GHS',
-    'CRC': '\u20a1',
-    'NIO': r'C$',
-    'GMD': 'GMD',
-    'MKD': 'din',
-    'BHD': 'din',
-    'DZD': 'din',
-    'IQD': 'din',
-    'JOD': 'din',
-    'KWD': 'din',
-    'LYD': 'din',
-    'RSD': 'din',
-    'TND': 'din',
-    'AED': 'dh',
-    'MAD': 'dh',
-    'STD': 'Db',
-    'BSD': r'$',
-    'FJD': r'$',
-    'GYD': r'$',
-    'KYD': r'$',
-    'LRD': r'$',
-    'SBD': r'$',
-    'SRD': r'$',
-    'AUD': r'$',
-    'BBD': r'$',
-    'BMD': r'$',
-    'BND': r'$',
-    'BZD': r'$',
-    'CAD': r'$',
-    'HKD': r'$',
-    'JMD': r'$',
-    'NAD': r'$',
-    'NZD': r'$',
-    'SGD': r'$',
-    'TTD': r'$',
-    'TWD': r'NT$',
-    'USD': r'$',
-    'XCD': r'$',
-    'VND': '\u20ab',
-    'AMD': 'Dram',
-    'CVE': 'CVE',
-    'EUR': '\u20ac',
-    'AWG': 'Afl.',
-    'HUF': 'Ft',
-    'BIF': 'FBu',
-    'CDF': 'FrCD',
-    'CHF': 'CHF',
-    'DJF': 'Fdj',
-    'GNF': 'FG',
-    'RWF': 'RF',
-    'XOF': 'CFA',
-    'XPF': 'FCFP',
-    'KMF': 'CF',
-    'XAF': 'FCFA',
-    'HTG': 'HTG',
-    'PYG': 'Gs',
-    'UAH': '\u20b4',
-    'PGK': 'PGK',
-    'LAK': '\u20ad',
-    'CZK': 'K\u010d',
-    'SEK': 'kr',
-    'ISK': 'kr',
-    'DKK': 'kr',
-    'NOK': 'kr',
-    'HRK': 'kn',
-    'MWK': 'MWK',
-    'ZMK': 'ZWK',
-    'AOA': 'Kz',
-    'MMK': 'K',
-    'GEL': 'GEL',
-    'LVL': 'Ls',
-    'ALL': 'Lek',
-    'HNL': 'L',
-    'SLL': 'SLL',
-    'MDL': 'MDL',
-    'RON': 'RON',
-    'BGN': 'lev',
-    'SZL': 'SZL',
-    'TRY': 'TL',
-    'LTL': 'Lt',
-    'LSL': 'LSL',
-    'AZN': 'man.',
-    'BAM': 'KM',
-    'MZN': 'MTn',
-    'NGN': '\u20a6',
-    'ERN': 'Nfk',
-    'BTN': 'Nu.',
-    'MRO': 'MRO',
-    'MOP': 'MOP',
-    'CUP': r'$',
-    'CUC': r'$',
-    'ARS': r'$',
-    'CLF': 'UF',
-    'CLP': r'$',
-    'COP': r'$',
-    'DOP': r'$',
-    'MXN': r'$',
-    'PHP': '\u20b1',
-    'UYU': r'$',
-    'FKP': '£',
-    'GIP': '£',
-    'SHP': '£',
-    'EGP': 'E£',
-    'LBP': 'L£',
-    'SDG': 'SDG',
-    'SSP': 'SSP',
-    'GBP': '£',
-    'SYP': '£',
-    'BWP': 'P',
-    'GTQ': 'Q',
-    'ZAR': 'R',
-    'BRL': r'R$',
-    'OMR': 'Rial',
-    'QAR': 'Rial',
-    'YER': 'Rial',
-    'IRR': 'Rial',
-    'KHR': 'Riel',
-    'MYR': 'RM',
-    'SAR': 'Riyal',
-    'BYR': 'BYR',
-    'RUB': 'руб.',
-    'MUR': 'Rs',
-    'SCR': 'SCR',
-    'LKR': 'Rs',
-    'NPR': 'Rs',
-    'INR': '\u20b9',
-    'PKR': 'Rs',
-    'IDR': 'Rp',
-    'ILS': '\u20aa',
-    'KES': 'Ksh',
-    'SOS': 'SOS',
-    'TZS': 'TSh',
-    'UGX': 'UGX',
-    'PEN': 'S/.',
-    'KGS': 'KGS',
-    'UZS': 'so\u02bcm',
-    'TJS': 'Som',
-    'BDT': '\u09f3',
-    'WST': 'WST',
-    'KZT': '\u20b8',
-    'MNT': '\u20ae',
-    'VUV': 'VUV',
-    'KPW': '\u20a9',
-    'KRW': '\u20a9',
-    'JPY': '¥',
-    'CNY': '¥',
-    'PLN': 'z\u0142',
-    'MVR': 'Rf',
-    'NLG': 'NAf',
-    'ZMW': 'ZK',
-    'ANG': 'ƒ',
-    'TMT': 'TMT',
-  };
+      constants.simpleCurrencySymbols[currencyCode] ?? currencyCode;
 
   /// Create a number format that prints in a pattern we get from
   /// the [getPattern] function using the locale [locale].
@@ -487,10 +313,10 @@
     locale = Intl.verifiedLocale(locale, localeExists);
     var symbols = numberFormatSymbols[locale];
     var localeZero = symbols.ZERO_DIGIT.codeUnitAt(0);
-    var zeroOffset = localeZero - _zero;
+    var zeroOffset = localeZero - constants.asciiZeroCodeUnit;
     name ??= symbols.DEF_CURRENCY_CODE;
     if (currencySymbol == null && lookupSimpleCurrencySymbol) {
-      currencySymbol = _simpleCurrencySymbols[name];
+      currencySymbol = constants.simpleCurrencySymbols[name];
     }
     currencySymbol ??= name;
 
@@ -505,7 +331,7 @@
         pattern,
         symbols,
         zeroOffset,
-        _NumberFormatParser.parse(symbols, pattern, isForCurrency,
+        NumberFormatParser.parse(symbols, pattern, isForCurrency,
             currencySymbol, name, decimalDigits));
   }
 
@@ -514,16 +340,16 @@
       this.currencySymbol,
       this._isForCurrency,
       this._locale,
-      this._localeZero,
+      this.localeZero,
       this._pattern,
       this._symbols,
       this._zeroOffset,
-      _NumberFormatParseResult result)
-      : _positivePrefix = result.positivePrefix,
-        _negativePrefix = result.negativePrefix,
-        _positiveSuffix = result.positiveSuffix,
-        _negativeSuffix = result.negativeSuffix,
-        _multiplier = result.multiplier,
+      NumberFormatParseResult result)
+      : positivePrefix = result.positivePrefix,
+        negativePrefix = result.negativePrefix,
+        positiveSuffix = result.positiveSuffix,
+        negativeSuffix = result.negativeSuffix,
+        multiplier = result.multiplier,
         _multiplierDigits = result.multiplierDigits,
         _useExponentialNotation = result.useExponentialNotation,
         minimumExponentDigits = result.minimumExponentDigits,
@@ -613,7 +439,7 @@
 
   /// Parse the number represented by the string. If it's not
   /// parseable, throws a [FormatException].
-  num parse(String text) => _NumberParser(this, text).value;
+  num parse(String text) => NumberParser(this, text).value;
 
   /// Format the main part of the number in the form dictated by the pattern.
   void _formatNumber(number) {
@@ -789,7 +615,7 @@
         }
       }
       power = pow(10, fractionDigits);
-      digitMultiplier = power * _multiplier;
+      digitMultiplier = power * multiplier;
 
       // Multiply out to the number of decimal places and the percent, then
       // round. For fixed-size integer types this should always be zero, so
@@ -871,7 +697,8 @@
   /// Format the part after the decimal place in a fixed point number.
   void _formatFractionPart(String fractionPart) {
     var fractionLength = fractionPart.length;
-    while (fractionPart.codeUnitAt(fractionLength - 1) == _zero &&
+    while (fractionPart.codeUnitAt(fractionLength - 1) ==
+            constants.asciiZeroCodeUnit &&
         fractionLength > minimumFractionDigits + 1) {
       fractionLength--;
     }
@@ -943,13 +770,10 @@
     }
   }
 
-  /// The code point for the character '0'.
-  static const _zero = 48;
-
   /// The code point for the locale's zero digit.
   ///
   ///  Initialized when the locale is set.
-  final int _localeZero;
+  final int localeZero;
 
   /// The difference between our zero and '0'.
   ///
@@ -959,11 +783,11 @@
 
   /// Returns the prefix for [x] based on whether it's positive or negative.
   /// In en_US this would be '' and '-' respectively.
-  String _signPrefix(x) => x.isNegative ? _negativePrefix : _positivePrefix;
+  String _signPrefix(x) => x.isNegative ? negativePrefix : positivePrefix;
 
   /// Returns the suffix for [x] based on wether it's positive or negative.
   /// In en_US there are no suffixes for positive or negative.
-  String _signSuffix(x) => x.isNegative ? _negativeSuffix : _positiveSuffix;
+  String _signSuffix(x) => x.isNegative ? negativeSuffix : positiveSuffix;
 
   /// Explicitly turn off any grouping (e.g. by thousands) in this format.
   ///
@@ -977,715 +801,4 @@
   String toString() => 'NumberFormat($_locale, $_pattern)';
 }
 
-///  A one-time object for parsing a particular numeric string. One-time here
-/// means an instance can only parse one string. This is implemented by
-/// transforming from a locale-specific format to one that the system can parse,
-/// then calls the system parsing methods on it.
-class _NumberParser {
-  /// The format for which we are parsing.
-  final NumberFormat format;
-
-  /// The text we are parsing.
-  final String text;
-
-  /// What we use to iterate over the input text.
-  final IntlStream input;
-
-  /// The result of parsing [text] according to [format]. Automatically
-  /// populated in the constructor.
-  num value;
-
-  /// The symbols used by our format.
-  NumberSymbols get symbols => format.symbols;
-
-  /// Where we accumulate the normalized representation of the number.
-  final StringBuffer _normalized = StringBuffer();
-
-  /// Did we see something that indicates this is, or at least might be,
-  /// a positive number.
-  bool gotPositive = false;
-
-  /// Did we see something that indicates this is, or at least might be,
-  /// a negative number.
-  bool gotNegative = false;
-
-  /// Did we see the required positive suffix at the end. Should
-  /// match [gotPositive].
-  bool gotPositiveSuffix = false;
-
-  /// Did we see the required negative suffix at the end. Should
-  /// match [gotNegative].
-  bool gotNegativeSuffix = false;
-
-  /// Should we stop parsing before hitting the end of the string.
-  bool done = false;
-
-  /// Have we already skipped over any required prefixes.
-  bool prefixesSkipped = false;
-
-  /// If the number is percent or permill, what do we divide by at the end.
-  int scale = 1;
-
-  String get _positivePrefix => format._positivePrefix;
-  String get _negativePrefix => format._negativePrefix;
-  String get _positiveSuffix => format._positiveSuffix;
-  String get _negativeSuffix => format._negativeSuffix;
-  int get _zero => NumberFormat._zero;
-  int get _localeZero => format._localeZero;
-
-  ///  Create a new [_NumberParser] on which we can call parse().
-  _NumberParser(this.format, this.text) : input = IntlStream(text) {
-    scale = format._multiplier;
-    value = parse();
-  }
-
-  ///  The strings we might replace with functions that return the replacement
-  /// values. They are functions because we might need to check something
-  /// in the context. Note that the ordering is important here. For example,
-  /// `symbols.PERCENT` might be " %", and we must handle that before we
-  /// look at an individual space.
-  Map<String, Function> get replacements =>
-      _replacements ??= _initializeReplacements();
-
-  Map<String, Function> _replacements;
-
-  Map<String, Function> _initializeReplacements() => {
-        symbols.DECIMAL_SEP: () => '.',
-        symbols.EXP_SYMBOL: () => 'E',
-        symbols.GROUP_SEP: handleSpace,
-        symbols.PERCENT: () {
-          scale = _NumberFormatParser._PERCENT_SCALE;
-          return '';
-        },
-        symbols.PERMILL: () {
-          scale = _NumberFormatParser._PER_MILLE_SCALE;
-          return '';
-        },
-        ' ': handleSpace,
-        '\u00a0': handleSpace,
-        '+': () => '+',
-        '-': () => '-',
-      };
-
-  void invalidFormat() =>
-      throw FormatException('Invalid number: ${input.contents}');
-
-  /// Replace a space in the number with the normalized form. If space is not
-  /// a significant character (normally grouping) then it's just invalid. If it
-  /// is the grouping character, then it's only valid if it's followed by a
-  /// digit. e.g. '$12 345.00'
-  void handleSpace() =>
-      groupingIsNotASpaceOrElseItIsSpaceFollowedByADigit ? '' : invalidFormat();
-
-  /// Determine if a space is a valid character in the number. See
-  /// [handleSpace].
-  bool get groupingIsNotASpaceOrElseItIsSpaceFollowedByADigit {
-    if (symbols.GROUP_SEP != '\u00a0' || symbols.GROUP_SEP != ' ') return true;
-    var peeked = input.peek(symbols.GROUP_SEP.length + 1);
-    return asDigit(peeked[peeked.length - 1]) != null;
-  }
-
-  /// Turn [char] into a number representing a digit, or null if it doesn't
-  /// represent a digit in this locale.
-  int asDigit(String char) {
-    var charCode = char.codeUnitAt(0);
-    var digitValue = charCode - _localeZero;
-    if (digitValue >= 0 && digitValue < 10) {
-      return digitValue;
-    } else {
-      return null;
-    }
-  }
-
-  /// Check to see if the input begins with either the positive or negative
-  /// prefixes. Set the [gotPositive] and [gotNegative] variables accordingly.
-  void checkPrefixes({bool skip = false}) {
-    bool checkPrefix(String prefix) =>
-        prefix.isNotEmpty && input.startsWith(prefix);
-
-    // TODO(alanknight): There's a faint possibility of a bug here where
-    // a positive prefix is followed by a negative prefix that's also a valid
-    // part of the number, but that seems very unlikely.
-    if (checkPrefix(_positivePrefix)) gotPositive = true;
-    if (checkPrefix(_negativePrefix)) gotNegative = true;
-
-    // The positive prefix might be a substring of the negative, in
-    // which case both would match.
-    if (gotPositive && gotNegative) {
-      if (_positivePrefix.length > _negativePrefix.length) {
-        gotNegative = false;
-      } else if (_negativePrefix.length > _positivePrefix.length) {
-        gotPositive = false;
-      }
-    }
-    if (skip) {
-      if (gotPositive) input.read(_positivePrefix.length);
-      if (gotNegative) input.read(_negativePrefix.length);
-    }
-  }
-
-  /// If the rest of our input is either the positive or negative suffix,
-  /// set [gotPositiveSuffix] or [gotNegativeSuffix] accordingly.
-  void checkSuffixes() {
-    var remainder = input.rest();
-    if (remainder == _positiveSuffix) gotPositiveSuffix = true;
-    if (remainder == _negativeSuffix) gotNegativeSuffix = true;
-  }
-
-  /// We've encountered a character that's not a digit. Go through our
-  /// replacement rules looking for how to handle it. If we see something
-  /// that's not a digit and doesn't have a replacement, then we're done
-  /// and the number is probably invalid.
-  void processNonDigit() {
-    // It might just be a prefix that we haven't skipped. We don't want to
-    // skip them initially because they might also be semantically meaningful,
-    // e.g. leading %. So we allow them through the loop, but only once.
-    var foundAnInterpretation = false;
-    if (input.index == 0 && !prefixesSkipped) {
-      prefixesSkipped = true;
-      checkPrefixes(skip: true);
-      foundAnInterpretation = true;
-    }
-
-    for (var key in replacements.keys) {
-      if (input.startsWith(key)) {
-        _normalized.write(replacements[key]());
-        input.read(key.length);
-        return;
-      }
-    }
-    // We haven't found either of these things, this seems invalid.
-    if (!foundAnInterpretation) {
-      done = true;
-    }
-  }
-
-  /// Parse [text] and return the resulting number. Throws [FormatException]
-  /// if we can't parse it.
-  num parse() {
-    if (text == symbols.NAN) return 0.0 / 0.0;
-    if (text == '$_positivePrefix${symbols.INFINITY}$_positiveSuffix') {
-      return 1.0 / 0.0;
-    }
-    if (text == '$_negativePrefix${symbols.INFINITY}$_negativeSuffix') {
-      return -1.0 / 0.0;
-    }
-
-    checkPrefixes();
-    var parsed = parseNumber(input);
-
-    if (gotPositive && !gotPositiveSuffix) invalidNumber();
-    if (gotNegative && !gotNegativeSuffix) invalidNumber();
-    if (!input.atEnd()) invalidNumber();
-
-    return parsed;
-  }
-
-  /// The number is invalid, throw a [FormatException].
-  void invalidNumber() =>
-      throw FormatException('Invalid Number: ${input.contents}');
-
-  /// Parse the number portion of the input, i.e. not any prefixes or suffixes,
-  /// and assuming NaN and Infinity are already handled.
-  num parseNumber(IntlStream input) {
-    if (gotNegative) {
-      _normalized.write('-');
-    }
-    while (!done && !input.atEnd()) {
-      var digit = asDigit(input.peek());
-      if (digit != null) {
-        _normalized.writeCharCode(_zero + digit);
-        input.next();
-      } else {
-        processNonDigit();
-      }
-      checkSuffixes();
-    }
-
-    var normalizedText = _normalized.toString();
-    num parsed = int.tryParse(normalizedText);
-    parsed ??= double.parse(normalizedText);
-    return parsed / scale;
-  }
-}
-
-/// 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
-/// 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 _PER_MILLE_SCALE = 1000;
-  static const _PATTERN_PERCENT = '%';
-  static const _PERCENT_SCALE = 100;
-  static const _PATTERN_EXPONENT = 'E';
-  static const _PATTERN_PLUS = '+';
-
-  /// The format whose state we are setting.
-  final NumberSymbols symbols;
-
-  /// The pattern we are parsing.
-  final _StringIterator pattern;
-
-  /// Whether this is a currency.
-  final bool isForCurrency;
-
-  /// 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.
-  ///
-  /// [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();
-  }
-
-  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;
-
-  /// 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();
-    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();
-      result.negativePrefix = _parseAffix();
-      // Skip over the negative trunk, verifying that it's identical to the
-      // positive trunk.
-      for (var each in _iterable(trunk)) {
-        if (pattern.current != each && pattern.current != null) {
-          throw FormatException(
-              'Positive and negative trunks must be the same', trunk);
-        }
-        pattern.moveNext();
-      }
-      result.negativeSuffix = _parseAffix();
-    } else {
-      // If no negative affix is specified, they share the same positive affix.
-      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;
-    }
-  }
-
-  /// 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 = StringBuffer();
-    inQuote = false;
-    while (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) {
-      if (pattern.peek == _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(currencySymbol);
-          break;
-        case _PATTERN_PERCENT:
-          if (result.multiplier != 1 && result.multiplier != _PERCENT_SCALE) {
-            throw const FormatException('Too many percent/permill');
-          }
-          result.multiplier = _PERCENT_SCALE;
-          affix.write(symbols.PERCENT);
-          break;
-        case _PATTERN_PER_MILLE:
-          if (result.multiplier != 1 && result.multiplier != _PER_MILLE_SCALE) {
-            throw const FormatException('Too many percent/permill');
-          }
-          result.multiplier = _PER_MILLE_SCALE;
-          affix.write(symbols.PERMILL);
-          break;
-        default:
-          affix.write(ch);
-      }
-    }
-    return true;
-  }
-
-  /// Variables used in [_parseTrunk] and [parseTrunkCharacter].
-  var decimalPos = -1;
-  var digitLeftCount = 0;
-  var zeroDigitCount = 0;
-  var digitRightCount = 0;
-  var groupingCount = -1;
-
-  /// Parse the "trunk" portion of the pattern, the piece that doesn't include
-  /// positive or negative prefixes or suffixes.
-  String _parseTrunk() {
-    var loop = true;
-    var trunk = StringBuffer();
-    while (pattern.current != null && loop) {
-      loop = parseTrunkCharacter(trunk);
-    }
-
-    if (zeroDigitCount == 0 && digitLeftCount > 0 && decimalPos >= 0) {
-      // Handle '###.###' and '###.' and '.###'
-      // Handle '.###'
-      var n = decimalPos == 0 ? 1 : decimalPos;
-      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 FormatException('Malformed pattern "${pattern.input}"');
-    }
-    var totalDigits = digitLeftCount + zeroDigitCount + digitRightCount;
-
-    result.maximumFractionDigits =
-        decimalPos >= 0 ? totalDigits - decimalPos : 0;
-    if (decimalPos >= 0) {
-      result.minimumFractionDigits =
-          digitLeftCount + zeroDigitCount - decimalPos;
-      if (result.minimumFractionDigits < 0) {
-        result.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;
-    result.minimumIntegerDigits = effectiveDecimalPos - digitLeftCount;
-    if (result.useExponentialNotation) {
-      result.maximumIntegerDigits =
-          digitLeftCount + result.minimumIntegerDigits;
-
-      // In exponential display, we need to at least show something.
-      if (result.maximumFractionDigits == 0 &&
-          result.minimumIntegerDigits == 0) {
-        result.minimumIntegerDigits = 1;
-      }
-    }
-
-    result.finalGroupingSize = max(0, groupingCount);
-    if (!groupingSizeSetExplicitly) {
-      result.groupingSize = result.finalGroupingSize;
-    }
-    result.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(trunk) {
-    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 FormatException('Unexpected "0" in pattern "${pattern.input}');
-        }
-        zeroDigitCount++;
-        if (groupingCount >= 0 && decimalPos < 0) {
-          groupingCount++;
-        }
-        break;
-      case _PATTERN_GROUPING_SEPARATOR:
-        if (groupingCount > 0) {
-          groupingSizeSetExplicitly = true;
-          result.groupingSize = groupingCount;
-        }
-        groupingCount = 0;
-        break;
-      case _PATTERN_DECIMAL_SEPARATOR:
-        if (decimalPos >= 0) {
-          throw FormatException(
-              'Multiple decimal separators in pattern "$pattern"');
-        }
-        decimalPos = digitLeftCount + zeroDigitCount + digitRightCount;
-        break;
-      case _PATTERN_EXPONENT:
-        trunk.write(ch);
-        if (result.useExponentialNotation) {
-          throw FormatException(
-              'Multiple exponential symbols in pattern "$pattern"');
-        }
-        result.useExponentialNotation = true;
-        result.minimumExponentDigits = 0;
-
-        // exponent pattern can have a optional '+'.
-        pattern.moveNext();
-        var nextChar = pattern.current;
-        if (nextChar == _PATTERN_PLUS) {
-          trunk.write(pattern.current);
-          pattern.moveNext();
-          result.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();
-          result.minimumExponentDigits++;
-        }
-
-        if ((digitLeftCount + zeroDigitCount) < 1 ||
-            result.minimumExponentDigits < 1) {
-          throw FormatException('Malformed exponential pattern "$pattern"');
-        }
-        return false;
-      default:
-        return false;
-    }
-    trunk.write(ch);
-    pattern.moveNext();
-    return true;
-  }
-}
-
-/// Returns an [Iterable] on the string as a list of substrings.
-Iterable<String> _iterable(String s) => _StringIterable(s);
-
-/// Return an iterator on the string as a list of substrings.
-Iterator<String> _iterator(String s) => _StringIterator(s);
-
-// TODO(nweiz): remove this when issue 3780 is fixed.
-/// Provides an Iterable that wraps [_iterator] so it can be used in a `for`
-/// loop.
-class _StringIterable extends IterableBase<String> {
-  final Iterator<String> iterator;
-
-  _StringIterable(String s) : iterator = _iterator(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> {
-  final String input;
-  int nextIndex = 0;
-  String _current;
-
-  _StringIterator(input) : input = _validate(input);
-
-  String get current => _current;
-
-  bool moveNext() {
-    if (nextIndex >= input.length) {
-      _current = null;
-      return false;
-    }
-    _current = input[nextIndex++];
-    return true;
-  }
-
-  String get peek => nextIndex >= input.length ? null : input[nextIndex];
-
-  Iterator<String> get iterator => this;
-
-  static String _validate(input) {
-    if (input is! String) throw ArgumentError(input);
-    return input;
-  }
-}
-
-/// Used primarily for currency formatting, this number-like class stores
-/// millionths of a currency unit, typically as an Int64.
-///
-/// It supports no operations other than being used for Intl number formatting.
-abstract class MicroMoney {
-  factory MicroMoney(micros) => _MicroMoney(micros);
-}
-
-/// Used primarily for currency formatting, this stores millionths of a
-/// currency unit, typically as an Int64.
-///
-/// This private class provides the operations needed by the formatting code.
-class _MicroMoney implements MicroMoney {
-  final dynamic _micros;
-  _MicroMoney(this._micros);
-  static const _multiplier = 1000000;
-
-  dynamic get _integerPart => _micros ~/ _multiplier;
-  int get _fractionPart => (this - _integerPart)._micros.toInt().abs();
-
-  bool get isNegative => _micros.isNegative;
-
-  _MicroMoney abs() => isNegative ? _MicroMoney(_micros.abs()) : this;
-
-  // Note that if this is done in a general way there's a risk of integer
-  // overflow on JS when multiplying out the [other] parameter, which may be
-  // an Int64. In formatting we only ever subtract out our own integer part.
-  _MicroMoney operator -(other) {
-    if (other is _MicroMoney) return _MicroMoney(_micros - other._micros);
-    return _MicroMoney(_micros - (other * _multiplier));
-  }
-
-  _MicroMoney operator +(other) {
-    if (other is _MicroMoney) return _MicroMoney(_micros + other._micros);
-    return _MicroMoney(_micros + (other * _multiplier));
-  }
-
-  _MicroMoney operator ~/(divisor) {
-    if (divisor is! int) {
-      throw ArgumentError.value(
-          divisor, 'divisor', '_MicroMoney ~/ only supports int arguments.');
-    }
-    return _MicroMoney((_integerPart ~/ divisor) * _multiplier);
-  }
-
-  _MicroMoney operator *(other) {
-    if (other is! int) {
-      throw ArgumentError.value(
-          other, 'other', '_MicroMoney * only supports int arguments.');
-    }
-    return _MicroMoney(
-        (_integerPart * other) * _multiplier + (_fractionPart * other));
-  }
-
-  /// Note that this only really supports remainder from an int,
-  /// not division by another MicroMoney
-  _MicroMoney remainder(other) {
-    if (other is! int) {
-      throw ArgumentError.value(
-          other, 'other', '_MicroMoney.remainder only supports int arguments.');
-    }
-    return _MicroMoney(_micros.remainder(other * _multiplier));
-  }
-
-  double toDouble() => _micros.toDouble() / _multiplier;
-
-  int toInt() => _integerPart.toInt();
-
-  String toString() {
-    var beforeDecimal = '$_integerPart';
-    var decimalPart = '';
-    var fractionPart = _fractionPart;
-    if (fractionPart != 0) {
-      decimalPart = '.$fractionPart';
-    }
-    return '$beforeDecimal$decimalPart';
-  }
-}
-
 final _ln10 = log(10);
diff --git a/lib/src/intl/number_format_parser.dart b/lib/src/intl/number_format_parser.dart
new file mode 100644
index 0000000..a2609b1
--- /dev/null
+++ b/lib/src/intl/number_format_parser.dart
@@ -0,0 +1,369 @@
+// 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.
+// @dart=2.9
+import 'dart:math';
+
+import 'package:intl/number_symbols.dart';
+import 'package:intl/number_symbols_data.dart';
+
+import 'string_iterator.dart';
+
+// ignore_for_file: constant_identifier_names
+
+/// 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
+/// 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 PER_MILLE_SCALE = 1000;
+  static const PATTERN_PERCENT = '%';
+  static const PERCENT_SCALE = 100;
+  static const PATTERN_EXPONENT = 'E';
+  static const PATTERN_PLUS = '+';
+
+  /// The format whose state we are setting.
+  final NumberSymbols symbols;
+
+  /// The pattern we are parsing.
+  final StringIterator pattern;
+
+  /// Whether this is a currency.
+  final bool isForCurrency;
+
+  /// 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.
+  ///
+  /// [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 = StringIterator(input) {
+    pattern.moveNext();
+  }
+
+  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;
+
+  /// 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();
+    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();
+      result.negativePrefix = _parseAffix();
+      // Skip over the negative trunk, verifying that it's identical to the
+      // positive trunk.
+      for (var each in StringIterable(trunk)) {
+        if (pattern.current != each && pattern.current != null) {
+          throw FormatException(
+              'Positive and negative trunks must be the same', trunk);
+        }
+        pattern.moveNext();
+      }
+      result.negativeSuffix = _parseAffix();
+    } else {
+      // If no negative affix is specified, they share the same positive affix.
+      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;
+    }
+  }
+
+  /// 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 = StringBuffer();
+    inQuote = false;
+    while (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) {
+      if (pattern.peek == 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(currencySymbol);
+          break;
+        case PATTERN_PERCENT:
+          if (result.multiplier != 1 && result.multiplier != PERCENT_SCALE) {
+            throw const FormatException('Too many percent/permill');
+          }
+          result.multiplier = PERCENT_SCALE;
+          affix.write(symbols.PERCENT);
+          break;
+        case PATTERN_PER_MILLE:
+          if (result.multiplier != 1 && result.multiplier != PER_MILLE_SCALE) {
+            throw const FormatException('Too many percent/permill');
+          }
+          result.multiplier = PER_MILLE_SCALE;
+          affix.write(symbols.PERMILL);
+          break;
+        default:
+          affix.write(ch);
+      }
+    }
+    return true;
+  }
+
+  /// Variables used in [_parseTrunk] and [parseTrunkCharacter].
+  var decimalPos = -1;
+  var digitLeftCount = 0;
+  var zeroDigitCount = 0;
+  var digitRightCount = 0;
+  var groupingCount = -1;
+
+  /// Parse the "trunk" portion of the pattern, the piece that doesn't include
+  /// positive or negative prefixes or suffixes.
+  String _parseTrunk() {
+    var loop = true;
+    var trunk = StringBuffer();
+    while (pattern.current != null && loop) {
+      loop = parseTrunkCharacter(trunk);
+    }
+
+    if (zeroDigitCount == 0 && digitLeftCount > 0 && decimalPos >= 0) {
+      // Handle '###.###' and '###.' and '.###'
+      // Handle '.###'
+      var n = decimalPos == 0 ? 1 : decimalPos;
+      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 FormatException('Malformed pattern "${pattern.input}"');
+    }
+    var totalDigits = digitLeftCount + zeroDigitCount + digitRightCount;
+
+    result.maximumFractionDigits =
+        decimalPos >= 0 ? totalDigits - decimalPos : 0;
+    if (decimalPos >= 0) {
+      result.minimumFractionDigits =
+          digitLeftCount + zeroDigitCount - decimalPos;
+      if (result.minimumFractionDigits < 0) {
+        result.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;
+    result.minimumIntegerDigits = effectiveDecimalPos - digitLeftCount;
+    if (result.useExponentialNotation) {
+      result.maximumIntegerDigits =
+          digitLeftCount + result.minimumIntegerDigits;
+
+      // In exponential display, we need to at least show something.
+      if (result.maximumFractionDigits == 0 &&
+          result.minimumIntegerDigits == 0) {
+        result.minimumIntegerDigits = 1;
+      }
+    }
+
+    result.finalGroupingSize = max(0, groupingCount);
+    if (!groupingSizeSetExplicitly) {
+      result.groupingSize = result.finalGroupingSize;
+    }
+    result.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(trunk) {
+    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 FormatException('Unexpected "0" in pattern "${pattern.input}');
+        }
+        zeroDigitCount++;
+        if (groupingCount >= 0 && decimalPos < 0) {
+          groupingCount++;
+        }
+        break;
+      case PATTERN_GROUPING_SEPARATOR:
+        if (groupingCount > 0) {
+          groupingSizeSetExplicitly = true;
+          result.groupingSize = groupingCount;
+        }
+        groupingCount = 0;
+        break;
+      case PATTERN_DECIMAL_SEPARATOR:
+        if (decimalPos >= 0) {
+          throw FormatException(
+              'Multiple decimal separators in pattern "$pattern"');
+        }
+        decimalPos = digitLeftCount + zeroDigitCount + digitRightCount;
+        break;
+      case PATTERN_EXPONENT:
+        trunk.write(ch);
+        if (result.useExponentialNotation) {
+          throw FormatException(
+              'Multiple exponential symbols in pattern "$pattern"');
+        }
+        result.useExponentialNotation = true;
+        result.minimumExponentDigits = 0;
+
+        // exponent pattern can have a optional '+'.
+        pattern.moveNext();
+        var nextChar = pattern.current;
+        if (nextChar == PATTERN_PLUS) {
+          trunk.write(pattern.current);
+          pattern.moveNext();
+          result.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();
+          result.minimumExponentDigits++;
+        }
+
+        if ((digitLeftCount + zeroDigitCount) < 1 ||
+            result.minimumExponentDigits < 1) {
+          throw FormatException('Malformed exponential pattern "$pattern"');
+        }
+        return false;
+      default:
+        return false;
+    }
+    trunk.write(ch);
+    pattern.moveNext();
+    return true;
+  }
+}
+
+final _ln10 = log(10);
diff --git a/lib/src/intl/number_parser.dart b/lib/src/intl/number_parser.dart
new file mode 100644
index 0000000..318a18b
--- /dev/null
+++ b/lib/src/intl/number_parser.dart
@@ -0,0 +1,242 @@
+// 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.
+// @dart=2.9
+
+import 'package:intl/number_symbols.dart';
+
+import 'constants.dart' as constants;
+import 'intl_stream.dart';
+import 'number_format.dart';
+import 'number_format_parser.dart';
+
+///  A one-time object for parsing a particular numeric string. One-time here
+/// means an instance can only parse one string. This is implemented by
+/// transforming from a locale-specific format to one that the system can parse,
+/// then calls the system parsing methods on it.
+class NumberParser {
+  /// The format for which we are parsing.
+  final NumberFormat format;
+
+  /// The text we are parsing.
+  final String text;
+
+  /// What we use to iterate over the input text.
+  final IntlStream input;
+
+  /// The result of parsing [text] according to [format]. Automatically
+  /// populated in the constructor.
+  num value;
+
+  /// The symbols used by our format.
+  NumberSymbols get symbols => format.symbols;
+
+  /// Where we accumulate the normalized representation of the number.
+  final StringBuffer _normalized = StringBuffer();
+
+  /// Did we see something that indicates this is, or at least might be,
+  /// a positive number.
+  bool gotPositive = false;
+
+  /// Did we see something that indicates this is, or at least might be,
+  /// a negative number.
+  bool gotNegative = false;
+
+  /// Did we see the required positive suffix at the end. Should
+  /// match [gotPositive].
+  bool gotPositiveSuffix = false;
+
+  /// Did we see the required negative suffix at the end. Should
+  /// match [gotNegative].
+  bool gotNegativeSuffix = false;
+
+  /// Should we stop parsing before hitting the end of the string.
+  bool done = false;
+
+  /// Have we already skipped over any required prefixes.
+  bool prefixesSkipped = false;
+
+  /// If the number is percent or permill, what do we divide by at the end.
+  int scale = 1;
+
+  String get _positivePrefix => format.positivePrefix;
+  String get _negativePrefix => format.negativePrefix;
+  String get _positiveSuffix => format.positiveSuffix;
+  String get _negativeSuffix => format.negativeSuffix;
+  int get _localeZero => format.localeZero;
+
+  ///  Create a new [_NumberParser] on which we can call parse().
+  NumberParser(this.format, this.text) : input = IntlStream(text) {
+    scale = format.multiplier;
+    value = parse();
+  }
+
+  ///  The strings we might replace with functions that return the replacement
+  /// values. They are functions because we might need to check something
+  /// in the context. Note that the ordering is important here. For example,
+  /// `symbols.PERCENT` might be " %", and we must handle that before we
+  /// look at an individual space.
+  Map<String, Function> get replacements =>
+      _replacements ??= _initializeReplacements();
+
+  Map<String, Function> _replacements;
+
+  Map<String, Function> _initializeReplacements() => {
+        symbols.DECIMAL_SEP: () => '.',
+        symbols.EXP_SYMBOL: () => 'E',
+        symbols.GROUP_SEP: handleSpace,
+        symbols.PERCENT: () {
+          scale = NumberFormatParser.PERCENT_SCALE;
+          return '';
+        },
+        symbols.PERMILL: () {
+          scale = NumberFormatParser.PER_MILLE_SCALE;
+          return '';
+        },
+        ' ': handleSpace,
+        '\u00a0': handleSpace,
+        '+': () => '+',
+        '-': () => '-',
+      };
+
+  void invalidFormat() =>
+      throw FormatException('Invalid number: ${input.contents}');
+
+  /// Replace a space in the number with the normalized form. If space is not
+  /// a significant character (normally grouping) then it's just invalid. If it
+  /// is the grouping character, then it's only valid if it's followed by a
+  /// digit. e.g. '$12 345.00'
+  void handleSpace() =>
+      groupingIsNotASpaceOrElseItIsSpaceFollowedByADigit ? '' : invalidFormat();
+
+  /// Determine if a space is a valid character in the number. See
+  /// [handleSpace].
+  bool get groupingIsNotASpaceOrElseItIsSpaceFollowedByADigit {
+    if (symbols.GROUP_SEP != '\u00a0' || symbols.GROUP_SEP != ' ') return true;
+    var peeked = input.peek(symbols.GROUP_SEP.length + 1);
+    return asDigit(peeked[peeked.length - 1]) != null;
+  }
+
+  /// Turn [char] into a number representing a digit, or null if it doesn't
+  /// represent a digit in this locale.
+  int asDigit(String char) {
+    var charCode = char.codeUnitAt(0);
+    var digitValue = charCode - _localeZero;
+    if (digitValue >= 0 && digitValue < 10) {
+      return digitValue;
+    } else {
+      return null;
+    }
+  }
+
+  /// Check to see if the input begins with either the positive or negative
+  /// prefixes. Set the [gotPositive] and [gotNegative] variables accordingly.
+  void checkPrefixes({bool skip = false}) {
+    bool checkPrefix(String prefix) =>
+        prefix.isNotEmpty && input.startsWith(prefix);
+
+    // TODO(alanknight): There's a faint possibility of a bug here where
+    // a positive prefix is followed by a negative prefix that's also a valid
+    // part of the number, but that seems very unlikely.
+    if (checkPrefix(_positivePrefix)) gotPositive = true;
+    if (checkPrefix(_negativePrefix)) gotNegative = true;
+
+    // The positive prefix might be a substring of the negative, in
+    // which case both would match.
+    if (gotPositive && gotNegative) {
+      if (_positivePrefix.length > _negativePrefix.length) {
+        gotNegative = false;
+      } else if (_negativePrefix.length > _positivePrefix.length) {
+        gotPositive = false;
+      }
+    }
+    if (skip) {
+      if (gotPositive) input.read(_positivePrefix.length);
+      if (gotNegative) input.read(_negativePrefix.length);
+    }
+  }
+
+  /// If the rest of our input is either the positive or negative suffix,
+  /// set [gotPositiveSuffix] or [gotNegativeSuffix] accordingly.
+  void checkSuffixes() {
+    var remainder = input.rest();
+    if (remainder == _positiveSuffix) gotPositiveSuffix = true;
+    if (remainder == _negativeSuffix) gotNegativeSuffix = true;
+  }
+
+  /// We've encountered a character that's not a digit. Go through our
+  /// replacement rules looking for how to handle it. If we see something
+  /// that's not a digit and doesn't have a replacement, then we're done
+  /// and the number is probably invalid.
+  void processNonDigit() {
+    // It might just be a prefix that we haven't skipped. We don't want to
+    // skip them initially because they might also be semantically meaningful,
+    // e.g. leading %. So we allow them through the loop, but only once.
+    var foundAnInterpretation = false;
+    if (input.index == 0 && !prefixesSkipped) {
+      prefixesSkipped = true;
+      checkPrefixes(skip: true);
+      foundAnInterpretation = true;
+    }
+
+    for (var key in replacements.keys) {
+      if (input.startsWith(key)) {
+        _normalized.write(replacements[key]());
+        input.read(key.length);
+        return;
+      }
+    }
+    // We haven't found either of these things, this seems invalid.
+    if (!foundAnInterpretation) {
+      done = true;
+    }
+  }
+
+  /// Parse [text] and return the resulting number. Throws [FormatException]
+  /// if we can't parse it.
+  num parse() {
+    if (text == symbols.NAN) return 0.0 / 0.0;
+    if (text == '$_positivePrefix${symbols.INFINITY}$_positiveSuffix') {
+      return 1.0 / 0.0;
+    }
+    if (text == '$_negativePrefix${symbols.INFINITY}$_negativeSuffix') {
+      return -1.0 / 0.0;
+    }
+
+    checkPrefixes();
+    var parsed = parseNumber(input);
+
+    if (gotPositive && !gotPositiveSuffix) invalidNumber();
+    if (gotNegative && !gotNegativeSuffix) invalidNumber();
+    if (!input.atEnd()) invalidNumber();
+
+    return parsed;
+  }
+
+  /// The number is invalid, throw a [FormatException].
+  void invalidNumber() =>
+      throw FormatException('Invalid Number: ${input.contents}');
+
+  /// Parse the number portion of the input, i.e. not any prefixes or suffixes,
+  /// and assuming NaN and Infinity are already handled.
+  num parseNumber(IntlStream input) {
+    if (gotNegative) {
+      _normalized.write('-');
+    }
+    while (!done && !input.atEnd()) {
+      var digit = asDigit(input.peek());
+      if (digit != null) {
+        _normalized.writeCharCode(constants.asciiZeroCodeUnit + digit);
+        input.next();
+      } else {
+        processNonDigit();
+      }
+      checkSuffixes();
+    }
+
+    var normalizedText = _normalized.toString();
+    num parsed = int.tryParse(normalizedText);
+    parsed ??= double.parse(normalizedText);
+    return parsed / scale;
+  }
+}
diff --git a/lib/src/intl/string_iterator.dart b/lib/src/intl/string_iterator.dart
new file mode 100644
index 0000000..839574d
--- /dev/null
+++ b/lib/src/intl/string_iterator.dart
@@ -0,0 +1,45 @@
+// 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.
+// @dart=2.9
+
+// TODO(nweiz): remove this when issue 3780 is fixed.
+import 'dart:collection';
+
+/// Provides an Iterable that wraps [_iterator] so it can be used in a `for`
+/// loop.
+class StringIterable extends IterableBase<String> {
+  final Iterator<String> iterator;
+
+  StringIterable(String s) : iterator = 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> {
+  final String input;
+  int nextIndex = 0;
+  String _current;
+
+  StringIterator(input) : input = _validate(input);
+
+  String get current => _current;
+
+  bool moveNext() {
+    if (nextIndex >= input.length) {
+      _current = null;
+      return false;
+    }
+    _current = input[nextIndex++];
+    return true;
+  }
+
+  String get peek => nextIndex >= input.length ? null : input[nextIndex];
+
+  Iterator<String> get iterator => this;
+
+  static String _validate(input) {
+    if (input is! String) throw ArgumentError(input);
+    return input;
+  }
+}