// © 2018 and later: Unicode, Inc. and others. // License & terms of use: http://www.unicode.org/copyright.html #include "unicode/utypes.h" #if !UCONFIG_NO_FORMATTING // Allow implicit conversion from char16_t* to UnicodeString for this file: // Helpful in toString methods and elsewhere. #define UNISTR_FROM_STRING_EXPLICIT #include <typeinfo> #include <array> #include "number_types.h" #include "number_patternstring.h" #include "numparse_types.h" #include "numparse_impl.h" #include "numparse_symbols.h" #include "numparse_decimal.h" #include "unicode/numberformatter.h" #include "cstr.h" #include "number_mapper.h" #include "static_unicode_sets.h" using namespace icu; using namespace icu::number; using namespace icu::number::impl; using namespace icu::numparse; using namespace icu::numparse::impl; NumberParseMatcher::~NumberParseMatcher() = default; NumberParserImpl* NumberParserImpl::createSimpleParser(const Locale& locale, const UnicodeString& patternString, parse_flags_t parseFlags, UErrorCode& status) { LocalPointer<NumberParserImpl> parser(new NumberParserImpl(parseFlags)); DecimalFormatSymbols symbols(locale, status); parser->fLocalMatchers.ignorables = {unisets::DEFAULT_IGNORABLES}; IgnorablesMatcher& ignorables = parser->fLocalMatchers.ignorables; DecimalFormatSymbols dfs(locale, status); dfs.setSymbol(DecimalFormatSymbols::kCurrencySymbol, u"IU$"); dfs.setSymbol(DecimalFormatSymbols::kIntlCurrencySymbol, u"ICU"); CurrencySymbols currencySymbols({u"ICU", status}, locale, dfs, status); ParsedPatternInfo patternInfo; PatternParser::parseToPatternInfo(patternString, patternInfo, status); // The following statements set up the affix matchers. AffixTokenMatcherSetupData affixSetupData = { currencySymbols, symbols, ignorables, locale, parseFlags}; parser->fLocalMatchers.affixTokenMatcherWarehouse = {&affixSetupData}; parser->fLocalMatchers.affixMatcherWarehouse = {&parser->fLocalMatchers.affixTokenMatcherWarehouse}; parser->fLocalMatchers.affixMatcherWarehouse.createAffixMatchers( patternInfo, *parser, ignorables, parseFlags, status); Grouper grouper = Grouper::forStrategy(UNUM_GROUPING_AUTO); grouper.setLocaleData(patternInfo, locale); parser->addMatcher(parser->fLocalMatchers.ignorables); parser->addMatcher(parser->fLocalMatchers.decimal = {symbols, grouper, parseFlags}); parser->addMatcher(parser->fLocalMatchers.minusSign = {symbols, false}); parser->addMatcher(parser->fLocalMatchers.plusSign = {symbols, false}); parser->addMatcher(parser->fLocalMatchers.percent = {symbols}); parser->addMatcher(parser->fLocalMatchers.permille = {symbols}); parser->addMatcher(parser->fLocalMatchers.nan = {symbols}); parser->addMatcher(parser->fLocalMatchers.infinity = {symbols}); parser->addMatcher(parser->fLocalMatchers.padding = {u"@"}); parser->addMatcher(parser->fLocalMatchers.scientific = {symbols, grouper}); parser->addMatcher(parser->fLocalMatchers.currency = {currencySymbols, symbols, parseFlags, status}); // parser.addMatcher(new RequireNumberMatcher()); parser->freeze(); return parser.orphan(); } NumberParserImpl* NumberParserImpl::createParserFromProperties(const number::impl::DecimalFormatProperties& properties, const DecimalFormatSymbols& symbols, bool parseCurrency, UErrorCode& status) { Locale locale = symbols.getLocale(); PropertiesAffixPatternProvider localPAPP; CurrencyPluralInfoAffixProvider localCPIAP; AffixPatternProvider* affixProvider; if (properties.currencyPluralInfo.fPtr.isNull()) { localPAPP.setTo(properties, status); affixProvider = &localPAPP; } else { localCPIAP.setTo(*properties.currencyPluralInfo.fPtr, properties, status); affixProvider = &localCPIAP; } if (affixProvider == nullptr || U_FAILURE(status)) { return nullptr; } CurrencyUnit currency = resolveCurrency(properties, locale, status); CurrencySymbols currencySymbols(currency, locale, symbols, status); bool isStrict = properties.parseMode.getOrDefault(PARSE_MODE_STRICT) == PARSE_MODE_STRICT; Grouper grouper = Grouper::forProperties(properties); int parseFlags = 0; if (affixProvider == nullptr || U_FAILURE(status)) { return nullptr; } if (!properties.parseCaseSensitive) { parseFlags |= PARSE_FLAG_IGNORE_CASE; } if (properties.parseIntegerOnly) { parseFlags |= PARSE_FLAG_INTEGER_ONLY; } if (properties.signAlwaysShown) { parseFlags |= PARSE_FLAG_PLUS_SIGN_ALLOWED; } if (isStrict) { parseFlags |= PARSE_FLAG_STRICT_GROUPING_SIZE; parseFlags |= PARSE_FLAG_STRICT_SEPARATORS; parseFlags |= PARSE_FLAG_USE_FULL_AFFIXES; parseFlags |= PARSE_FLAG_EXACT_AFFIX; } else { parseFlags |= PARSE_FLAG_INCLUDE_UNPAIRED_AFFIXES; } if (grouper.getPrimary() <= 0) { parseFlags |= PARSE_FLAG_GROUPING_DISABLED; } if (parseCurrency || affixProvider->hasCurrencySign()) { parseFlags |= PARSE_FLAG_MONETARY_SEPARATORS; } if (!parseCurrency) { parseFlags |= PARSE_FLAG_NO_FOREIGN_CURRENCY; } LocalPointer<NumberParserImpl> parser(new NumberParserImpl(parseFlags)); parser->fLocalMatchers.ignorables = { isStrict ? unisets::STRICT_IGNORABLES : unisets::DEFAULT_IGNORABLES}; IgnorablesMatcher& ignorables = parser->fLocalMatchers.ignorables; ////////////////////// /// AFFIX MATCHERS /// ////////////////////// // The following statements set up the affix matchers. AffixTokenMatcherSetupData affixSetupData = { currencySymbols, symbols, ignorables, locale, parseFlags}; parser->fLocalMatchers.affixTokenMatcherWarehouse = {&affixSetupData}; parser->fLocalMatchers.affixMatcherWarehouse = {&parser->fLocalMatchers.affixTokenMatcherWarehouse}; parser->fLocalMatchers.affixMatcherWarehouse.createAffixMatchers( *affixProvider, *parser, ignorables, parseFlags, status); //////////////////////// /// CURRENCY MATCHER /// //////////////////////// if (parseCurrency || affixProvider->hasCurrencySign()) { parser->addMatcher(parser->fLocalMatchers.currency = {currencySymbols, symbols, parseFlags, status}); } /////////////// /// PERCENT /// /////////////// // ICU-TC meeting, April 11, 2018: accept percent/permille only if it is in the pattern, // and to maintain regressive behavior, divide by 100 even if no percent sign is present. if (!isStrict && affixProvider->containsSymbolType(AffixPatternType::TYPE_PERCENT, status)) { parser->addMatcher(parser->fLocalMatchers.percent = {symbols}); } if (!isStrict && affixProvider->containsSymbolType(AffixPatternType::TYPE_PERMILLE, status)) { parser->addMatcher(parser->fLocalMatchers.permille = {symbols}); } /////////////////////////////// /// OTHER STANDARD MATCHERS /// /////////////////////////////// if (!isStrict) { parser->addMatcher(parser->fLocalMatchers.plusSign = {symbols, false}); parser->addMatcher(parser->fLocalMatchers.minusSign = {symbols, false}); } parser->addMatcher(parser->fLocalMatchers.nan = {symbols}); parser->addMatcher(parser->fLocalMatchers.infinity = {symbols}); UnicodeString padString = properties.padString; if (!padString.isBogus() && !ignorables.getSet()->contains(padString)) { parser->addMatcher(parser->fLocalMatchers.padding = {padString}); } parser->addMatcher(parser->fLocalMatchers.ignorables); parser->addMatcher(parser->fLocalMatchers.decimal = {symbols, grouper, parseFlags}); // NOTE: parseNoExponent doesn't disable scientific parsing if we have a scientific formatter if (!properties.parseNoExponent || properties.minimumExponentDigits > 0) { parser->addMatcher(parser->fLocalMatchers.scientific = {symbols, grouper}); } ////////////////// /// VALIDATORS /// ////////////////// parser->addMatcher(parser->fLocalValidators.number = {}); if (isStrict) { parser->addMatcher(parser->fLocalValidators.affix = {}); } if (parseCurrency) { parser->addMatcher(parser->fLocalValidators.currency = {}); } if (properties.decimalPatternMatchRequired) { bool patternHasDecimalSeparator = properties.decimalSeparatorAlwaysShown || properties.maximumFractionDigits != 0; parser->addMatcher(parser->fLocalValidators.decimalSeparator = {patternHasDecimalSeparator}); } // The multiplier takes care of scaling percentages. Scale multiplier = scaleFromProperties(properties); if (multiplier.isValid()) { parser->addMatcher(parser->fLocalValidators.multiplier = {multiplier}); } parser->freeze(); return parser.orphan(); } NumberParserImpl::NumberParserImpl(parse_flags_t parseFlags) : fParseFlags(parseFlags) { } NumberParserImpl::~NumberParserImpl() { fNumMatchers = 0; } void NumberParserImpl::addMatcher(NumberParseMatcher& matcher) { if (fNumMatchers + 1 > fMatchers.getCapacity()) { fMatchers.resize(fNumMatchers * 2, fNumMatchers); } fMatchers[fNumMatchers] = &matcher; fNumMatchers++; } void NumberParserImpl::freeze() { fFrozen = true; } parse_flags_t NumberParserImpl::getParseFlags() const { return fParseFlags; } void NumberParserImpl::parse(const UnicodeString& input, bool greedy, ParsedNumber& result, UErrorCode& status) const { return parse(input, 0, greedy, result, status); } void NumberParserImpl::parse(const UnicodeString& input, int32_t start, bool greedy, ParsedNumber& result, UErrorCode& status) const { if (U_FAILURE(status)) { return; } U_ASSERT(fFrozen); // TODO: Check start >= 0 and start < input.length() StringSegment segment(input, 0 != (fParseFlags & PARSE_FLAG_IGNORE_CASE)); segment.adjustOffset(start); if (greedy) { parseGreedyRecursive(segment, result, status); } else { parseLongestRecursive(segment, result, status); } for (int32_t i = 0; i < fNumMatchers; i++) { fMatchers[i]->postProcess(result); } result.postProcess(); } void NumberParserImpl::parseGreedyRecursive(StringSegment& segment, ParsedNumber& result, UErrorCode& status) const { // Base Case if (segment.length() == 0) { return; } int initialOffset = segment.getOffset(); for (int32_t i = 0; i < fNumMatchers; i++) { const NumberParseMatcher* matcher = fMatchers[i]; if (!matcher->smokeTest(segment)) { continue; } matcher->match(segment, result, status); if (U_FAILURE(status)) { return; } if (segment.getOffset() != initialOffset) { // In a greedy parse, recurse on only the first match. parseGreedyRecursive(segment, result, status); // The following line resets the offset so that the StringSegment says the same across // the function // call boundary. Since we recurse only once, this line is not strictly necessary. segment.setOffset(initialOffset); return; } } // NOTE: If we get here, the greedy parse completed without consuming the entire string. } void NumberParserImpl::parseLongestRecursive(StringSegment& segment, ParsedNumber& result, UErrorCode& status) const { // Base Case if (segment.length() == 0) { return; } // TODO: Give a nice way for the matcher to reset the ParsedNumber? ParsedNumber initial(result); ParsedNumber candidate; int initialOffset = segment.getOffset(); for (int32_t i = 0; i < fNumMatchers; i++) { const NumberParseMatcher* matcher = fMatchers[i]; if (!matcher->smokeTest(segment)) { continue; } // In a non-greedy parse, we attempt all possible matches and pick the best. for (int32_t charsToConsume = 0; charsToConsume < segment.length();) { charsToConsume += U16_LENGTH(segment.codePointAt(charsToConsume)); // Run the matcher on a segment of the current length. candidate = initial; segment.setLength(charsToConsume); bool maybeMore = matcher->match(segment, candidate, status); segment.resetLength(); if (U_FAILURE(status)) { return; } // If the entire segment was consumed, recurse. if (segment.getOffset() - initialOffset == charsToConsume) { parseLongestRecursive(segment, candidate, status); if (U_FAILURE(status)) { return; } if (candidate.isBetterThan(result)) { result = candidate; } } // Since the segment can be re-used, reset the offset. // This does not have an effect if the matcher did not consume any chars. segment.setOffset(initialOffset); // Unless the matcher wants to see the next char, continue to the next matcher. if (!maybeMore) { break; } } } } UnicodeString NumberParserImpl::toString() const { UnicodeString result(u"<NumberParserImpl matchers:["); for (int32_t i = 0; i < fNumMatchers; i++) { result.append(u' '); result.append(fMatchers[i]->toString()); } result.append(u" ]>", -1); return result; } #endif /* #if !UCONFIG_NO_FORMATTING */