Home | History | Annotate | Download | only in i18n
      1 //  2018 and later: Unicode, Inc. and others.
      2 // License & terms of use: http://www.unicode.org/copyright.html
      3 
      4 #include "unicode/utypes.h"
      5 
      6 #if !UCONFIG_NO_FORMATTING
      7 
      8 // Allow implicit conversion from char16_t* to UnicodeString for this file:
      9 // Helpful in toString methods and elsewhere.
     10 #define UNISTR_FROM_STRING_EXPLICIT
     11 
     12 #include "number_decnum.h"
     13 #include "number_skeletons.h"
     14 #include "umutex.h"
     15 #include "ucln_in.h"
     16 #include "patternprops.h"
     17 #include "unicode/ucharstriebuilder.h"
     18 #include "number_utils.h"
     19 #include "number_decimalquantity.h"
     20 #include "unicode/numberformatter.h"
     21 #include "uinvchar.h"
     22 #include "charstr.h"
     23 
     24 using namespace icu;
     25 using namespace icu::number;
     26 using namespace icu::number::impl;
     27 using namespace icu::number::impl::skeleton;
     28 
     29 namespace {
     30 
     31 icu::UInitOnce gNumberSkeletonsInitOnce = U_INITONCE_INITIALIZER;
     32 
     33 char16_t* kSerializedStemTrie = nullptr;
     34 
     35 UBool U_CALLCONV cleanupNumberSkeletons() {
     36     uprv_free(kSerializedStemTrie);
     37     kSerializedStemTrie = nullptr;
     38     gNumberSkeletonsInitOnce.reset();
     39     return TRUE;
     40 }
     41 
     42 void U_CALLCONV initNumberSkeletons(UErrorCode& status) {
     43     ucln_i18n_registerCleanup(UCLN_I18N_NUMBER_SKELETONS, cleanupNumberSkeletons);
     44 
     45     UCharsTrieBuilder b(status);
     46     if (U_FAILURE(status)) { return; }
     47 
     48     // Section 1:
     49     b.add(u"compact-short", STEM_COMPACT_SHORT, status);
     50     b.add(u"compact-long", STEM_COMPACT_LONG, status);
     51     b.add(u"scientific", STEM_SCIENTIFIC, status);
     52     b.add(u"engineering", STEM_ENGINEERING, status);
     53     b.add(u"notation-simple", STEM_NOTATION_SIMPLE, status);
     54     b.add(u"base-unit", STEM_BASE_UNIT, status);
     55     b.add(u"percent", STEM_PERCENT, status);
     56     b.add(u"permille", STEM_PERMILLE, status);
     57     b.add(u"precision-integer", STEM_PRECISION_INTEGER, status);
     58     b.add(u"precision-unlimited", STEM_PRECISION_UNLIMITED, status);
     59     b.add(u"precision-currency-standard", STEM_PRECISION_CURRENCY_STANDARD, status);
     60     b.add(u"precision-currency-cash", STEM_PRECISION_CURRENCY_CASH, status);
     61     b.add(u"rounding-mode-ceiling", STEM_ROUNDING_MODE_CEILING, status);
     62     b.add(u"rounding-mode-floor", STEM_ROUNDING_MODE_FLOOR, status);
     63     b.add(u"rounding-mode-down", STEM_ROUNDING_MODE_DOWN, status);
     64     b.add(u"rounding-mode-up", STEM_ROUNDING_MODE_UP, status);
     65     b.add(u"rounding-mode-half-even", STEM_ROUNDING_MODE_HALF_EVEN, status);
     66     b.add(u"rounding-mode-half-down", STEM_ROUNDING_MODE_HALF_DOWN, status);
     67     b.add(u"rounding-mode-half-up", STEM_ROUNDING_MODE_HALF_UP, status);
     68     b.add(u"rounding-mode-unnecessary", STEM_ROUNDING_MODE_UNNECESSARY, status);
     69     b.add(u"group-off", STEM_GROUP_OFF, status);
     70     b.add(u"group-min2", STEM_GROUP_MIN2, status);
     71     b.add(u"group-auto", STEM_GROUP_AUTO, status);
     72     b.add(u"group-on-aligned", STEM_GROUP_ON_ALIGNED, status);
     73     b.add(u"group-thousands", STEM_GROUP_THOUSANDS, status);
     74     b.add(u"latin", STEM_LATIN, status);
     75     b.add(u"unit-width-narrow", STEM_UNIT_WIDTH_NARROW, status);
     76     b.add(u"unit-width-short", STEM_UNIT_WIDTH_SHORT, status);
     77     b.add(u"unit-width-full-name", STEM_UNIT_WIDTH_FULL_NAME, status);
     78     b.add(u"unit-width-iso-code", STEM_UNIT_WIDTH_ISO_CODE, status);
     79     b.add(u"unit-width-hidden", STEM_UNIT_WIDTH_HIDDEN, status);
     80     b.add(u"sign-auto", STEM_SIGN_AUTO, status);
     81     b.add(u"sign-always", STEM_SIGN_ALWAYS, status);
     82     b.add(u"sign-never", STEM_SIGN_NEVER, status);
     83     b.add(u"sign-accounting", STEM_SIGN_ACCOUNTING, status);
     84     b.add(u"sign-accounting-always", STEM_SIGN_ACCOUNTING_ALWAYS, status);
     85     b.add(u"sign-except-zero", STEM_SIGN_EXCEPT_ZERO, status);
     86     b.add(u"sign-accounting-except-zero", STEM_SIGN_ACCOUNTING_EXCEPT_ZERO, status);
     87     b.add(u"decimal-auto", STEM_DECIMAL_AUTO, status);
     88     b.add(u"decimal-always", STEM_DECIMAL_ALWAYS, status);
     89     if (U_FAILURE(status)) { return; }
     90 
     91     // Section 2:
     92     b.add(u"precision-increment", STEM_PRECISION_INCREMENT, status);
     93     b.add(u"measure-unit", STEM_MEASURE_UNIT, status);
     94     b.add(u"per-measure-unit", STEM_PER_MEASURE_UNIT, status);
     95     b.add(u"currency", STEM_CURRENCY, status);
     96     b.add(u"integer-width", STEM_INTEGER_WIDTH, status);
     97     b.add(u"numbering-system", STEM_NUMBERING_SYSTEM, status);
     98     b.add(u"scale", STEM_SCALE, status);
     99     if (U_FAILURE(status)) { return; }
    100 
    101     // Build the CharsTrie
    102     // TODO: Use SLOW or FAST here?
    103     UnicodeString result;
    104     b.buildUnicodeString(USTRINGTRIE_BUILD_FAST, result, status);
    105     if (U_FAILURE(status)) { return; }
    106 
    107     // Copy the result into the global constant pointer
    108     size_t numBytes = result.length() * sizeof(char16_t);
    109     kSerializedStemTrie = static_cast<char16_t*>(uprv_malloc(numBytes));
    110     uprv_memcpy(kSerializedStemTrie, result.getBuffer(), numBytes);
    111 }
    112 
    113 
    114 inline void appendMultiple(UnicodeString& sb, UChar32 cp, int32_t count) {
    115     for (int i = 0; i < count; i++) {
    116         sb.append(cp);
    117     }
    118 }
    119 
    120 
    121 #define CHECK_NULL(seen, field, status) (void)(seen); /* for auto-format line wrapping */ \
    122 { \
    123     if ((seen).field) { \
    124         (status) = U_NUMBER_SKELETON_SYNTAX_ERROR; \
    125         return STATE_NULL; \
    126     } \
    127     (seen).field = true; \
    128 }
    129 
    130 
    131 #define SKELETON_UCHAR_TO_CHAR(dest, src, start, end, status) (void)(dest); \
    132 { \
    133     UErrorCode conversionStatus = U_ZERO_ERROR; \
    134     (dest).appendInvariantChars({FALSE, (src).getBuffer() + (start), (end) - (start)}, conversionStatus); \
    135     if (conversionStatus == U_INVARIANT_CONVERSION_ERROR) { \
    136         /* Don't propagate the invariant conversion error; it is a skeleton syntax error */ \
    137         (status) = U_NUMBER_SKELETON_SYNTAX_ERROR; \
    138         return; \
    139     } else if (U_FAILURE(conversionStatus)) { \
    140         (status) = conversionStatus; \
    141         return; \
    142     } \
    143 }
    144 
    145 
    146 } // anonymous namespace
    147 
    148 
    149 Notation stem_to_object::notation(skeleton::StemEnum stem) {
    150     switch (stem) {
    151         case STEM_COMPACT_SHORT:
    152             return Notation::compactShort();
    153         case STEM_COMPACT_LONG:
    154             return Notation::compactLong();
    155         case STEM_SCIENTIFIC:
    156             return Notation::scientific();
    157         case STEM_ENGINEERING:
    158             return Notation::engineering();
    159         case STEM_NOTATION_SIMPLE:
    160             return Notation::simple();
    161         default:
    162             U_ASSERT(false);
    163             return Notation::simple(); // return a value: silence compiler warning
    164     }
    165 }
    166 
    167 MeasureUnit stem_to_object::unit(skeleton::StemEnum stem) {
    168     switch (stem) {
    169         case STEM_BASE_UNIT:
    170             // Slicing is okay
    171             return NoUnit::base(); // NOLINT
    172         case STEM_PERCENT:
    173             // Slicing is okay
    174             return NoUnit::percent(); // NOLINT
    175         case STEM_PERMILLE:
    176             // Slicing is okay
    177             return NoUnit::permille(); // NOLINT
    178         default:
    179             U_ASSERT(false);
    180             return {}; // return a value: silence compiler warning
    181     }
    182 }
    183 
    184 Precision stem_to_object::precision(skeleton::StemEnum stem) {
    185     switch (stem) {
    186         case STEM_PRECISION_INTEGER:
    187             return Precision::integer();
    188         case STEM_PRECISION_UNLIMITED:
    189             return Precision::unlimited();
    190         case STEM_PRECISION_CURRENCY_STANDARD:
    191             return Precision::currency(UCURR_USAGE_STANDARD);
    192         case STEM_PRECISION_CURRENCY_CASH:
    193             return Precision::currency(UCURR_USAGE_CASH);
    194         default:
    195             U_ASSERT(false);
    196             return Precision::integer(); // return a value: silence compiler warning
    197     }
    198 }
    199 
    200 UNumberFormatRoundingMode stem_to_object::roundingMode(skeleton::StemEnum stem) {
    201     switch (stem) {
    202         case STEM_ROUNDING_MODE_CEILING:
    203             return UNUM_ROUND_CEILING;
    204         case STEM_ROUNDING_MODE_FLOOR:
    205             return UNUM_ROUND_FLOOR;
    206         case STEM_ROUNDING_MODE_DOWN:
    207             return UNUM_ROUND_DOWN;
    208         case STEM_ROUNDING_MODE_UP:
    209             return UNUM_ROUND_UP;
    210         case STEM_ROUNDING_MODE_HALF_EVEN:
    211             return UNUM_ROUND_HALFEVEN;
    212         case STEM_ROUNDING_MODE_HALF_DOWN:
    213             return UNUM_ROUND_HALFDOWN;
    214         case STEM_ROUNDING_MODE_HALF_UP:
    215             return UNUM_ROUND_HALFUP;
    216         case STEM_ROUNDING_MODE_UNNECESSARY:
    217             return UNUM_ROUND_UNNECESSARY;
    218         default:
    219             U_ASSERT(false);
    220             return UNUM_ROUND_UNNECESSARY;
    221     }
    222 }
    223 
    224 UGroupingStrategy stem_to_object::groupingStrategy(skeleton::StemEnum stem) {
    225     switch (stem) {
    226         case STEM_GROUP_OFF:
    227             return UNUM_GROUPING_OFF;
    228         case STEM_GROUP_MIN2:
    229             return UNUM_GROUPING_MIN2;
    230         case STEM_GROUP_AUTO:
    231             return UNUM_GROUPING_AUTO;
    232         case STEM_GROUP_ON_ALIGNED:
    233             return UNUM_GROUPING_ON_ALIGNED;
    234         case STEM_GROUP_THOUSANDS:
    235             return UNUM_GROUPING_THOUSANDS;
    236         default:
    237             return UNUM_GROUPING_COUNT; // for objects, throw; for enums, return COUNT
    238     }
    239 }
    240 
    241 UNumberUnitWidth stem_to_object::unitWidth(skeleton::StemEnum stem) {
    242     switch (stem) {
    243         case STEM_UNIT_WIDTH_NARROW:
    244             return UNUM_UNIT_WIDTH_NARROW;
    245         case STEM_UNIT_WIDTH_SHORT:
    246             return UNUM_UNIT_WIDTH_SHORT;
    247         case STEM_UNIT_WIDTH_FULL_NAME:
    248             return UNUM_UNIT_WIDTH_FULL_NAME;
    249         case STEM_UNIT_WIDTH_ISO_CODE:
    250             return UNUM_UNIT_WIDTH_ISO_CODE;
    251         case STEM_UNIT_WIDTH_HIDDEN:
    252             return UNUM_UNIT_WIDTH_HIDDEN;
    253         default:
    254             return UNUM_UNIT_WIDTH_COUNT; // for objects, throw; for enums, return COUNT
    255     }
    256 }
    257 
    258 UNumberSignDisplay stem_to_object::signDisplay(skeleton::StemEnum stem) {
    259     switch (stem) {
    260         case STEM_SIGN_AUTO:
    261             return UNUM_SIGN_AUTO;
    262         case STEM_SIGN_ALWAYS:
    263             return UNUM_SIGN_ALWAYS;
    264         case STEM_SIGN_NEVER:
    265             return UNUM_SIGN_NEVER;
    266         case STEM_SIGN_ACCOUNTING:
    267             return UNUM_SIGN_ACCOUNTING;
    268         case STEM_SIGN_ACCOUNTING_ALWAYS:
    269             return UNUM_SIGN_ACCOUNTING_ALWAYS;
    270         case STEM_SIGN_EXCEPT_ZERO:
    271             return UNUM_SIGN_EXCEPT_ZERO;
    272         case STEM_SIGN_ACCOUNTING_EXCEPT_ZERO:
    273             return UNUM_SIGN_ACCOUNTING_EXCEPT_ZERO;
    274         default:
    275             return UNUM_SIGN_COUNT; // for objects, throw; for enums, return COUNT
    276     }
    277 }
    278 
    279 UNumberDecimalSeparatorDisplay stem_to_object::decimalSeparatorDisplay(skeleton::StemEnum stem) {
    280     switch (stem) {
    281         case STEM_DECIMAL_AUTO:
    282             return UNUM_DECIMAL_SEPARATOR_AUTO;
    283         case STEM_DECIMAL_ALWAYS:
    284             return UNUM_DECIMAL_SEPARATOR_ALWAYS;
    285         default:
    286             return UNUM_DECIMAL_SEPARATOR_COUNT; // for objects, throw; for enums, return COUNT
    287     }
    288 }
    289 
    290 
    291 void enum_to_stem_string::roundingMode(UNumberFormatRoundingMode value, UnicodeString& sb) {
    292     switch (value) {
    293         case UNUM_ROUND_CEILING:
    294             sb.append(u"rounding-mode-ceiling", -1);
    295             break;
    296         case UNUM_ROUND_FLOOR:
    297             sb.append(u"rounding-mode-floor", -1);
    298             break;
    299         case UNUM_ROUND_DOWN:
    300             sb.append(u"rounding-mode-down", -1);
    301             break;
    302         case UNUM_ROUND_UP:
    303             sb.append(u"rounding-mode-up", -1);
    304             break;
    305         case UNUM_ROUND_HALFEVEN:
    306             sb.append(u"rounding-mode-half-even", -1);
    307             break;
    308         case UNUM_ROUND_HALFDOWN:
    309             sb.append(u"rounding-mode-half-down", -1);
    310             break;
    311         case UNUM_ROUND_HALFUP:
    312             sb.append(u"rounding-mode-half-up", -1);
    313             break;
    314         case UNUM_ROUND_UNNECESSARY:
    315             sb.append(u"rounding-mode-unnecessary", -1);
    316             break;
    317         default:
    318             U_ASSERT(false);
    319     }
    320 }
    321 
    322 void enum_to_stem_string::groupingStrategy(UGroupingStrategy value, UnicodeString& sb) {
    323     switch (value) {
    324         case UNUM_GROUPING_OFF:
    325             sb.append(u"group-off", -1);
    326             break;
    327         case UNUM_GROUPING_MIN2:
    328             sb.append(u"group-min2", -1);
    329             break;
    330         case UNUM_GROUPING_AUTO:
    331             sb.append(u"group-auto", -1);
    332             break;
    333         case UNUM_GROUPING_ON_ALIGNED:
    334             sb.append(u"group-on-aligned", -1);
    335             break;
    336         case UNUM_GROUPING_THOUSANDS:
    337             sb.append(u"group-thousands", -1);
    338             break;
    339         default:
    340             U_ASSERT(false);
    341     }
    342 }
    343 
    344 void enum_to_stem_string::unitWidth(UNumberUnitWidth value, UnicodeString& sb) {
    345     switch (value) {
    346         case UNUM_UNIT_WIDTH_NARROW:
    347             sb.append(u"unit-width-narrow", -1);
    348             break;
    349         case UNUM_UNIT_WIDTH_SHORT:
    350             sb.append(u"unit-width-short", -1);
    351             break;
    352         case UNUM_UNIT_WIDTH_FULL_NAME:
    353             sb.append(u"unit-width-full-name", -1);
    354             break;
    355         case UNUM_UNIT_WIDTH_ISO_CODE:
    356             sb.append(u"unit-width-iso-code", -1);
    357             break;
    358         case UNUM_UNIT_WIDTH_HIDDEN:
    359             sb.append(u"unit-width-hidden", -1);
    360             break;
    361         default:
    362             U_ASSERT(false);
    363     }
    364 }
    365 
    366 void enum_to_stem_string::signDisplay(UNumberSignDisplay value, UnicodeString& sb) {
    367     switch (value) {
    368         case UNUM_SIGN_AUTO:
    369             sb.append(u"sign-auto", -1);
    370             break;
    371         case UNUM_SIGN_ALWAYS:
    372             sb.append(u"sign-always", -1);
    373             break;
    374         case UNUM_SIGN_NEVER:
    375             sb.append(u"sign-never", -1);
    376             break;
    377         case UNUM_SIGN_ACCOUNTING:
    378             sb.append(u"sign-accounting", -1);
    379             break;
    380         case UNUM_SIGN_ACCOUNTING_ALWAYS:
    381             sb.append(u"sign-accounting-always", -1);
    382             break;
    383         case UNUM_SIGN_EXCEPT_ZERO:
    384             sb.append(u"sign-except-zero", -1);
    385             break;
    386         case UNUM_SIGN_ACCOUNTING_EXCEPT_ZERO:
    387             sb.append(u"sign-accounting-except-zero", -1);
    388             break;
    389         default:
    390             U_ASSERT(false);
    391     }
    392 }
    393 
    394 void
    395 enum_to_stem_string::decimalSeparatorDisplay(UNumberDecimalSeparatorDisplay value, UnicodeString& sb) {
    396     switch (value) {
    397         case UNUM_DECIMAL_SEPARATOR_AUTO:
    398             sb.append(u"decimal-auto", -1);
    399             break;
    400         case UNUM_DECIMAL_SEPARATOR_ALWAYS:
    401             sb.append(u"decimal-always", -1);
    402             break;
    403         default:
    404             U_ASSERT(false);
    405     }
    406 }
    407 
    408 
    409 UnlocalizedNumberFormatter skeleton::create(const UnicodeString& skeletonString, UErrorCode& status) {
    410     umtx_initOnce(gNumberSkeletonsInitOnce, &initNumberSkeletons, status);
    411     MacroProps macros = parseSkeleton(skeletonString, status);
    412     return NumberFormatter::with().macros(macros);
    413 }
    414 
    415 UnicodeString skeleton::generate(const MacroProps& macros, UErrorCode& status) {
    416     umtx_initOnce(gNumberSkeletonsInitOnce, &initNumberSkeletons, status);
    417     UnicodeString sb;
    418     GeneratorHelpers::generateSkeleton(macros, sb, status);
    419     return sb;
    420 }
    421 
    422 MacroProps skeleton::parseSkeleton(const UnicodeString& skeletonString, UErrorCode& status) {
    423     if (U_FAILURE(status)) { return MacroProps(); }
    424 
    425     // Add a trailing whitespace to the end of the skeleton string to make code cleaner.
    426     UnicodeString tempSkeletonString(skeletonString);
    427     tempSkeletonString.append(u' ');
    428 
    429     SeenMacroProps seen;
    430     MacroProps macros;
    431     StringSegment segment(tempSkeletonString, false);
    432     UCharsTrie stemTrie(kSerializedStemTrie);
    433     ParseState stem = STATE_NULL;
    434     int32_t offset = 0;
    435 
    436     // Primary skeleton parse loop:
    437     while (offset < segment.length()) {
    438         UChar32 cp = segment.codePointAt(offset);
    439         bool isTokenSeparator = PatternProps::isWhiteSpace(cp);
    440         bool isOptionSeparator = (cp == u'/');
    441 
    442         if (!isTokenSeparator && !isOptionSeparator) {
    443             // Non-separator token; consume it.
    444             offset += U16_LENGTH(cp);
    445             if (stem == STATE_NULL) {
    446                 // We are currently consuming a stem.
    447                 // Go to the next state in the stem trie.
    448                 stemTrie.nextForCodePoint(cp);
    449             }
    450             continue;
    451         }
    452 
    453         // We are looking at a token or option separator.
    454         // If the segment is nonempty, parse it and reset the segment.
    455         // Otherwise, make sure it is a valid repeating separator.
    456         if (offset != 0) {
    457             segment.setLength(offset);
    458             if (stem == STATE_NULL) {
    459                 // The first separator after the start of a token. Parse it as a stem.
    460                 stem = parseStem(segment, stemTrie, seen, macros, status);
    461                 stemTrie.reset();
    462             } else {
    463                 // A separator after the first separator of a token. Parse it as an option.
    464                 stem = parseOption(stem, segment, macros, status);
    465             }
    466             segment.resetLength();
    467             if (U_FAILURE(status)) { return macros; }
    468 
    469             // Consume the segment:
    470             segment.adjustOffset(offset);
    471             offset = 0;
    472 
    473         } else if (stem != STATE_NULL) {
    474             // A separator ('/' or whitespace) following an option separator ('/')
    475             // segment.setLength(U16_LENGTH(cp)); // for error message
    476             // throw new SkeletonSyntaxException("Unexpected separator character", segment);
    477             status = U_NUMBER_SKELETON_SYNTAX_ERROR;
    478             return macros;
    479 
    480         } else {
    481             // Two spaces in a row; this is OK.
    482         }
    483 
    484         // Does the current stem forbid options?
    485         if (isOptionSeparator && stem == STATE_NULL) {
    486             // segment.setLength(U16_LENGTH(cp)); // for error message
    487             // throw new SkeletonSyntaxException("Unexpected option separator", segment);
    488             status = U_NUMBER_SKELETON_SYNTAX_ERROR;
    489             return macros;
    490         }
    491 
    492         // Does the current stem require an option?
    493         if (isTokenSeparator && stem != STATE_NULL) {
    494             switch (stem) {
    495                 case STATE_INCREMENT_PRECISION:
    496                 case STATE_MEASURE_UNIT:
    497                 case STATE_PER_MEASURE_UNIT:
    498                 case STATE_CURRENCY_UNIT:
    499                 case STATE_INTEGER_WIDTH:
    500                 case STATE_NUMBERING_SYSTEM:
    501                 case STATE_SCALE:
    502                     // segment.setLength(U16_LENGTH(cp)); // for error message
    503                     // throw new SkeletonSyntaxException("Stem requires an option", segment);
    504                     status = U_NUMBER_SKELETON_SYNTAX_ERROR;
    505                     return macros;
    506                 default:
    507                     break;
    508             }
    509             stem = STATE_NULL;
    510         }
    511 
    512         // Consume the separator:
    513         segment.adjustOffset(U16_LENGTH(cp));
    514     }
    515     U_ASSERT(stem == STATE_NULL);
    516     return macros;
    517 }
    518 
    519 ParseState
    520 skeleton::parseStem(const StringSegment& segment, const UCharsTrie& stemTrie, SeenMacroProps& seen,
    521                     MacroProps& macros, UErrorCode& status) {
    522     // First check for "blueprint" stems, which start with a "signal char"
    523     switch (segment.charAt(0)) {
    524         case u'.':
    525         CHECK_NULL(seen, precision, status);
    526             blueprint_helpers::parseFractionStem(segment, macros, status);
    527             return STATE_FRACTION_PRECISION;
    528         case u'@':
    529         CHECK_NULL(seen, precision, status);
    530             blueprint_helpers::parseDigitsStem(segment, macros, status);
    531             return STATE_NULL;
    532         default:
    533             break;
    534     }
    535 
    536     // Now look at the stemsTrie, which is already be pointing at our stem.
    537     UStringTrieResult stemResult = stemTrie.current();
    538 
    539     if (stemResult != USTRINGTRIE_INTERMEDIATE_VALUE && stemResult != USTRINGTRIE_FINAL_VALUE) {
    540         // throw new SkeletonSyntaxException("Unknown stem", segment);
    541         status = U_NUMBER_SKELETON_SYNTAX_ERROR;
    542         return STATE_NULL;
    543     }
    544 
    545     auto stem = static_cast<StemEnum>(stemTrie.getValue());
    546     switch (stem) {
    547 
    548         // Stems with meaning on their own, not requiring an option:
    549 
    550         case STEM_COMPACT_SHORT:
    551         case STEM_COMPACT_LONG:
    552         case STEM_SCIENTIFIC:
    553         case STEM_ENGINEERING:
    554         case STEM_NOTATION_SIMPLE:
    555         CHECK_NULL(seen, notation, status);
    556             macros.notation = stem_to_object::notation(stem);
    557             switch (stem) {
    558                 case STEM_SCIENTIFIC:
    559                 case STEM_ENGINEERING:
    560                     return STATE_SCIENTIFIC; // allows for scientific options
    561                 default:
    562                     return STATE_NULL;
    563             }
    564 
    565         case STEM_BASE_UNIT:
    566         case STEM_PERCENT:
    567         case STEM_PERMILLE:
    568         CHECK_NULL(seen, unit, status);
    569             macros.unit = stem_to_object::unit(stem);
    570             return STATE_NULL;
    571 
    572         case STEM_PRECISION_INTEGER:
    573         case STEM_PRECISION_UNLIMITED:
    574         case STEM_PRECISION_CURRENCY_STANDARD:
    575         case STEM_PRECISION_CURRENCY_CASH:
    576         CHECK_NULL(seen, precision, status);
    577             macros.precision = stem_to_object::precision(stem);
    578             switch (stem) {
    579                 case STEM_PRECISION_INTEGER:
    580                     return STATE_FRACTION_PRECISION; // allows for "precision-integer/@##"
    581                 default:
    582                     return STATE_NULL;
    583             }
    584 
    585         case STEM_ROUNDING_MODE_CEILING:
    586         case STEM_ROUNDING_MODE_FLOOR:
    587         case STEM_ROUNDING_MODE_DOWN:
    588         case STEM_ROUNDING_MODE_UP:
    589         case STEM_ROUNDING_MODE_HALF_EVEN:
    590         case STEM_ROUNDING_MODE_HALF_DOWN:
    591         case STEM_ROUNDING_MODE_HALF_UP:
    592         case STEM_ROUNDING_MODE_UNNECESSARY:
    593         CHECK_NULL(seen, roundingMode, status);
    594             macros.roundingMode = stem_to_object::roundingMode(stem);
    595             return STATE_NULL;
    596 
    597         case STEM_GROUP_OFF:
    598         case STEM_GROUP_MIN2:
    599         case STEM_GROUP_AUTO:
    600         case STEM_GROUP_ON_ALIGNED:
    601         case STEM_GROUP_THOUSANDS:
    602         CHECK_NULL(seen, grouper, status);
    603             macros.grouper = Grouper::forStrategy(stem_to_object::groupingStrategy(stem));
    604             return STATE_NULL;
    605 
    606         case STEM_LATIN:
    607         CHECK_NULL(seen, symbols, status);
    608             macros.symbols.setTo(NumberingSystem::createInstanceByName("latn", status));
    609             return STATE_NULL;
    610 
    611         case STEM_UNIT_WIDTH_NARROW:
    612         case STEM_UNIT_WIDTH_SHORT:
    613         case STEM_UNIT_WIDTH_FULL_NAME:
    614         case STEM_UNIT_WIDTH_ISO_CODE:
    615         case STEM_UNIT_WIDTH_HIDDEN:
    616         CHECK_NULL(seen, unitWidth, status);
    617             macros.unitWidth = stem_to_object::unitWidth(stem);
    618             return STATE_NULL;
    619 
    620         case STEM_SIGN_AUTO:
    621         case STEM_SIGN_ALWAYS:
    622         case STEM_SIGN_NEVER:
    623         case STEM_SIGN_ACCOUNTING:
    624         case STEM_SIGN_ACCOUNTING_ALWAYS:
    625         case STEM_SIGN_EXCEPT_ZERO:
    626         case STEM_SIGN_ACCOUNTING_EXCEPT_ZERO:
    627         CHECK_NULL(seen, sign, status);
    628             macros.sign = stem_to_object::signDisplay(stem);
    629             return STATE_NULL;
    630 
    631         case STEM_DECIMAL_AUTO:
    632         case STEM_DECIMAL_ALWAYS:
    633         CHECK_NULL(seen, decimal, status);
    634             macros.decimal = stem_to_object::decimalSeparatorDisplay(stem);
    635             return STATE_NULL;
    636 
    637             // Stems requiring an option:
    638 
    639         case STEM_PRECISION_INCREMENT:
    640         CHECK_NULL(seen, precision, status);
    641             return STATE_INCREMENT_PRECISION;
    642 
    643         case STEM_MEASURE_UNIT:
    644         CHECK_NULL(seen, unit, status);
    645             return STATE_MEASURE_UNIT;
    646 
    647         case STEM_PER_MEASURE_UNIT:
    648         CHECK_NULL(seen, perUnit, status);
    649             return STATE_PER_MEASURE_UNIT;
    650 
    651         case STEM_CURRENCY:
    652         CHECK_NULL(seen, unit, status);
    653             return STATE_CURRENCY_UNIT;
    654 
    655         case STEM_INTEGER_WIDTH:
    656         CHECK_NULL(seen, integerWidth, status);
    657             return STATE_INTEGER_WIDTH;
    658 
    659         case STEM_NUMBERING_SYSTEM:
    660         CHECK_NULL(seen, symbols, status);
    661             return STATE_NUMBERING_SYSTEM;
    662 
    663         case STEM_SCALE:
    664         CHECK_NULL(seen, scale, status);
    665             return STATE_SCALE;
    666 
    667         default:
    668             U_ASSERT(false);
    669             return STATE_NULL; // return a value: silence compiler warning
    670     }
    671 }
    672 
    673 ParseState skeleton::parseOption(ParseState stem, const StringSegment& segment, MacroProps& macros,
    674                                  UErrorCode& status) {
    675 
    676     ///// Required options: /////
    677 
    678     switch (stem) {
    679         case STATE_CURRENCY_UNIT:
    680             blueprint_helpers::parseCurrencyOption(segment, macros, status);
    681             return STATE_NULL;
    682         case STATE_MEASURE_UNIT:
    683             blueprint_helpers::parseMeasureUnitOption(segment, macros, status);
    684             return STATE_NULL;
    685         case STATE_PER_MEASURE_UNIT:
    686             blueprint_helpers::parseMeasurePerUnitOption(segment, macros, status);
    687             return STATE_NULL;
    688         case STATE_INCREMENT_PRECISION:
    689             blueprint_helpers::parseIncrementOption(segment, macros, status);
    690             return STATE_NULL;
    691         case STATE_INTEGER_WIDTH:
    692             blueprint_helpers::parseIntegerWidthOption(segment, macros, status);
    693             return STATE_NULL;
    694         case STATE_NUMBERING_SYSTEM:
    695             blueprint_helpers::parseNumberingSystemOption(segment, macros, status);
    696             return STATE_NULL;
    697         case STATE_SCALE:
    698             blueprint_helpers::parseScaleOption(segment, macros, status);
    699             return STATE_NULL;
    700         default:
    701             break;
    702     }
    703 
    704     ///// Non-required options: /////
    705 
    706     // Scientific options
    707     switch (stem) {
    708         case STATE_SCIENTIFIC:
    709             if (blueprint_helpers::parseExponentWidthOption(segment, macros, status)) {
    710                 return STATE_SCIENTIFIC;
    711             }
    712             if (U_FAILURE(status)) {
    713                 return {};
    714             }
    715             if (blueprint_helpers::parseExponentSignOption(segment, macros, status)) {
    716                 return STATE_SCIENTIFIC;
    717             }
    718             if (U_FAILURE(status)) {
    719                 return {};
    720             }
    721             break;
    722         default:
    723             break;
    724     }
    725 
    726     // Frac-sig option
    727     switch (stem) {
    728         case STATE_FRACTION_PRECISION:
    729             if (blueprint_helpers::parseFracSigOption(segment, macros, status)) {
    730                 return STATE_NULL;
    731             }
    732             if (U_FAILURE(status)) {
    733                 return {};
    734             }
    735             break;
    736         default:
    737             break;
    738     }
    739 
    740     // Unknown option
    741     // throw new SkeletonSyntaxException("Invalid option", segment);
    742     status = U_NUMBER_SKELETON_SYNTAX_ERROR;
    743     return STATE_NULL;
    744 }
    745 
    746 void GeneratorHelpers::generateSkeleton(const MacroProps& macros, UnicodeString& sb, UErrorCode& status) {
    747     if (U_FAILURE(status)) { return; }
    748 
    749     // Supported options
    750     if (GeneratorHelpers::notation(macros, sb, status)) {
    751         sb.append(u' ');
    752     }
    753     if (U_FAILURE(status)) { return; }
    754     if (GeneratorHelpers::unit(macros, sb, status)) {
    755         sb.append(u' ');
    756     }
    757     if (U_FAILURE(status)) { return; }
    758     if (GeneratorHelpers::perUnit(macros, sb, status)) {
    759         sb.append(u' ');
    760     }
    761     if (U_FAILURE(status)) { return; }
    762     if (GeneratorHelpers::precision(macros, sb, status)) {
    763         sb.append(u' ');
    764     }
    765     if (U_FAILURE(status)) { return; }
    766     if (GeneratorHelpers::roundingMode(macros, sb, status)) {
    767         sb.append(u' ');
    768     }
    769     if (U_FAILURE(status)) { return; }
    770     if (GeneratorHelpers::grouping(macros, sb, status)) {
    771         sb.append(u' ');
    772     }
    773     if (U_FAILURE(status)) { return; }
    774     if (GeneratorHelpers::integerWidth(macros, sb, status)) {
    775         sb.append(u' ');
    776     }
    777     if (U_FAILURE(status)) { return; }
    778     if (GeneratorHelpers::symbols(macros, sb, status)) {
    779         sb.append(u' ');
    780     }
    781     if (U_FAILURE(status)) { return; }
    782     if (GeneratorHelpers::unitWidth(macros, sb, status)) {
    783         sb.append(u' ');
    784     }
    785     if (U_FAILURE(status)) { return; }
    786     if (GeneratorHelpers::sign(macros, sb, status)) {
    787         sb.append(u' ');
    788     }
    789     if (U_FAILURE(status)) { return; }
    790     if (GeneratorHelpers::decimal(macros, sb, status)) {
    791         sb.append(u' ');
    792     }
    793     if (U_FAILURE(status)) { return; }
    794     if (GeneratorHelpers::scale(macros, sb, status)) {
    795         sb.append(u' ');
    796     }
    797     if (U_FAILURE(status)) { return; }
    798 
    799     // Unsupported options
    800     if (!macros.padder.isBogus()) {
    801         status = U_UNSUPPORTED_ERROR;
    802         return;
    803     }
    804     if (macros.affixProvider != nullptr) {
    805         status = U_UNSUPPORTED_ERROR;
    806         return;
    807     }
    808     if (macros.rules != nullptr) {
    809         status = U_UNSUPPORTED_ERROR;
    810         return;
    811     }
    812     if (macros.currencySymbols != nullptr) {
    813         status = U_UNSUPPORTED_ERROR;
    814         return;
    815     }
    816 
    817     // Remove the trailing space
    818     if (sb.length() > 0) {
    819         sb.truncate(sb.length() - 1);
    820     }
    821 }
    822 
    823 
    824 bool blueprint_helpers::parseExponentWidthOption(const StringSegment& segment, MacroProps& macros,
    825                                                  UErrorCode&) {
    826     if (segment.charAt(0) != u'+') {
    827         return false;
    828     }
    829     int32_t offset = 1;
    830     int32_t minExp = 0;
    831     for (; offset < segment.length(); offset++) {
    832         if (segment.charAt(offset) == u'e') {
    833             minExp++;
    834         } else {
    835             break;
    836         }
    837     }
    838     if (offset < segment.length()) {
    839         return false;
    840     }
    841     // Use the public APIs to enforce bounds checking
    842     macros.notation = static_cast<ScientificNotation&>(macros.notation).withMinExponentDigits(minExp);
    843     return true;
    844 }
    845 
    846 void
    847 blueprint_helpers::generateExponentWidthOption(int32_t minExponentDigits, UnicodeString& sb, UErrorCode&) {
    848     sb.append(u'+');
    849     appendMultiple(sb, u'e', minExponentDigits);
    850 }
    851 
    852 bool
    853 blueprint_helpers::parseExponentSignOption(const StringSegment& segment, MacroProps& macros, UErrorCode&) {
    854     // Get the sign display type out of the CharsTrie data structure.
    855     UCharsTrie tempStemTrie(kSerializedStemTrie);
    856     UStringTrieResult result = tempStemTrie.next(
    857             segment.toTempUnicodeString().getBuffer(),
    858             segment.length());
    859     if (result != USTRINGTRIE_INTERMEDIATE_VALUE && result != USTRINGTRIE_FINAL_VALUE) {
    860         return false;
    861     }
    862     auto sign = stem_to_object::signDisplay(static_cast<StemEnum>(tempStemTrie.getValue()));
    863     if (sign == UNUM_SIGN_COUNT) {
    864         return false;
    865     }
    866     macros.notation = static_cast<ScientificNotation&>(macros.notation).withExponentSignDisplay(sign);
    867     return true;
    868 }
    869 
    870 void blueprint_helpers::parseCurrencyOption(const StringSegment& segment, MacroProps& macros,
    871                                             UErrorCode& status) {
    872     // Unlike ICU4J, have to check length manually because ICU4C CurrencyUnit does not check it for us
    873     if (segment.length() != 3) {
    874         status = U_NUMBER_SKELETON_SYNTAX_ERROR;
    875         return;
    876     }
    877     const UChar* currencyCode = segment.toTempUnicodeString().getBuffer();
    878     UErrorCode localStatus = U_ZERO_ERROR;
    879     CurrencyUnit currency(currencyCode, localStatus);
    880     if (U_FAILURE(localStatus)) {
    881         // Not 3 ascii chars
    882         // throw new SkeletonSyntaxException("Invalid currency", segment);
    883         status = U_NUMBER_SKELETON_SYNTAX_ERROR;
    884         return;
    885     }
    886     // Slicing is OK
    887     macros.unit = currency; // NOLINT
    888 }
    889 
    890 void
    891 blueprint_helpers::generateCurrencyOption(const CurrencyUnit& currency, UnicodeString& sb, UErrorCode&) {
    892     sb.append(currency.getISOCurrency(), -1);
    893 }
    894 
    895 void blueprint_helpers::parseMeasureUnitOption(const StringSegment& segment, MacroProps& macros,
    896                                                UErrorCode& status) {
    897     const UnicodeString stemString = segment.toTempUnicodeString();
    898 
    899     // NOTE: The category (type) of the unit is guaranteed to be a valid subtag (alphanumeric)
    900     // http://unicode.org/reports/tr35/#Validity_Data
    901     int firstHyphen = 0;
    902     while (firstHyphen < stemString.length() && stemString.charAt(firstHyphen) != '-') {
    903         firstHyphen++;
    904     }
    905     if (firstHyphen == stemString.length()) {
    906         // throw new SkeletonSyntaxException("Invalid measure unit option", segment);
    907         status = U_NUMBER_SKELETON_SYNTAX_ERROR;
    908         return;
    909     }
    910 
    911     // Need to do char <-> UChar conversion...
    912     U_ASSERT(U_SUCCESS(status));
    913     CharString type;
    914     SKELETON_UCHAR_TO_CHAR(type, stemString, 0, firstHyphen, status);
    915     CharString subType;
    916     SKELETON_UCHAR_TO_CHAR(subType, stemString, firstHyphen + 1, stemString.length(), status);
    917 
    918     // Note: the largest type as of this writing (March 2018) is "volume", which has 24 units.
    919     static constexpr int32_t CAPACITY = 30;
    920     MeasureUnit units[CAPACITY];
    921     UErrorCode localStatus = U_ZERO_ERROR;
    922     int32_t numUnits = MeasureUnit::getAvailable(type.data(), units, CAPACITY, localStatus);
    923     if (U_FAILURE(localStatus)) {
    924         // More than 30 units in this type?
    925         status = U_INTERNAL_PROGRAM_ERROR;
    926         return;
    927     }
    928     for (int32_t i = 0; i < numUnits; i++) {
    929         auto& unit = units[i];
    930         if (uprv_strcmp(subType.data(), unit.getSubtype()) == 0) {
    931             macros.unit = unit;
    932             return;
    933         }
    934     }
    935 
    936     // throw new SkeletonSyntaxException("Unknown measure unit", segment);
    937     status = U_NUMBER_SKELETON_SYNTAX_ERROR;
    938 }
    939 
    940 void blueprint_helpers::generateMeasureUnitOption(const MeasureUnit& measureUnit, UnicodeString& sb,
    941                                                   UErrorCode&) {
    942     // Need to do char <-> UChar conversion...
    943     sb.append(UnicodeString(measureUnit.getType(), -1, US_INV));
    944     sb.append(u'-');
    945     sb.append(UnicodeString(measureUnit.getSubtype(), -1, US_INV));
    946 }
    947 
    948 void blueprint_helpers::parseMeasurePerUnitOption(const StringSegment& segment, MacroProps& macros,
    949                                                   UErrorCode& status) {
    950     // A little bit of a hack: safe the current unit (numerator), call the main measure unit
    951     // parsing code, put back the numerator unit, and put the new unit into per-unit.
    952     MeasureUnit numerator = macros.unit;
    953     parseMeasureUnitOption(segment, macros, status);
    954     if (U_FAILURE(status)) { return; }
    955     macros.perUnit = macros.unit;
    956     macros.unit = numerator;
    957 }
    958 
    959 void blueprint_helpers::parseFractionStem(const StringSegment& segment, MacroProps& macros,
    960                                           UErrorCode& status) {
    961     U_ASSERT(segment.charAt(0) == u'.');
    962     int32_t offset = 1;
    963     int32_t minFrac = 0;
    964     int32_t maxFrac;
    965     for (; offset < segment.length(); offset++) {
    966         if (segment.charAt(offset) == u'0') {
    967             minFrac++;
    968         } else {
    969             break;
    970         }
    971     }
    972     if (offset < segment.length()) {
    973         if (segment.charAt(offset) == u'+') {
    974             maxFrac = -1;
    975             offset++;
    976         } else {
    977             maxFrac = minFrac;
    978             for (; offset < segment.length(); offset++) {
    979                 if (segment.charAt(offset) == u'#') {
    980                     maxFrac++;
    981                 } else {
    982                     break;
    983                 }
    984             }
    985         }
    986     } else {
    987         maxFrac = minFrac;
    988     }
    989     if (offset < segment.length()) {
    990         // throw new SkeletonSyntaxException("Invalid fraction stem", segment);
    991         status = U_NUMBER_SKELETON_SYNTAX_ERROR;
    992         return;
    993     }
    994     // Use the public APIs to enforce bounds checking
    995     if (maxFrac == -1) {
    996         macros.precision = Precision::minFraction(minFrac);
    997     } else {
    998         macros.precision = Precision::minMaxFraction(minFrac, maxFrac);
    999     }
   1000 }
   1001 
   1002 void
   1003 blueprint_helpers::generateFractionStem(int32_t minFrac, int32_t maxFrac, UnicodeString& sb, UErrorCode&) {
   1004     if (minFrac == 0 && maxFrac == 0) {
   1005         sb.append(u"precision-integer", -1);
   1006         return;
   1007     }
   1008     sb.append(u'.');
   1009     appendMultiple(sb, u'0', minFrac);
   1010     if (maxFrac == -1) {
   1011         sb.append(u'+');
   1012     } else {
   1013         appendMultiple(sb, u'#', maxFrac - minFrac);
   1014     }
   1015 }
   1016 
   1017 void
   1018 blueprint_helpers::parseDigitsStem(const StringSegment& segment, MacroProps& macros, UErrorCode& status) {
   1019     U_ASSERT(segment.charAt(0) == u'@');
   1020     int offset = 0;
   1021     int minSig = 0;
   1022     int maxSig;
   1023     for (; offset < segment.length(); offset++) {
   1024         if (segment.charAt(offset) == u'@') {
   1025             minSig++;
   1026         } else {
   1027             break;
   1028         }
   1029     }
   1030     if (offset < segment.length()) {
   1031         if (segment.charAt(offset) == u'+') {
   1032             maxSig = -1;
   1033             offset++;
   1034         } else {
   1035             maxSig = minSig;
   1036             for (; offset < segment.length(); offset++) {
   1037                 if (segment.charAt(offset) == u'#') {
   1038                     maxSig++;
   1039                 } else {
   1040                     break;
   1041                 }
   1042             }
   1043         }
   1044     } else {
   1045         maxSig = minSig;
   1046     }
   1047     if (offset < segment.length()) {
   1048         // throw new SkeletonSyntaxException("Invalid significant digits stem", segment);
   1049         status = U_NUMBER_SKELETON_SYNTAX_ERROR;
   1050         return;
   1051     }
   1052     // Use the public APIs to enforce bounds checking
   1053     if (maxSig == -1) {
   1054         macros.precision = Precision::minSignificantDigits(minSig);
   1055     } else {
   1056         macros.precision = Precision::minMaxSignificantDigits(minSig, maxSig);
   1057     }
   1058 }
   1059 
   1060 void
   1061 blueprint_helpers::generateDigitsStem(int32_t minSig, int32_t maxSig, UnicodeString& sb, UErrorCode&) {
   1062     appendMultiple(sb, u'@', minSig);
   1063     if (maxSig == -1) {
   1064         sb.append(u'+');
   1065     } else {
   1066         appendMultiple(sb, u'#', maxSig - minSig);
   1067     }
   1068 }
   1069 
   1070 bool blueprint_helpers::parseFracSigOption(const StringSegment& segment, MacroProps& macros,
   1071                                            UErrorCode& status) {
   1072     if (segment.charAt(0) != u'@') {
   1073         return false;
   1074     }
   1075     int offset = 0;
   1076     int minSig = 0;
   1077     int maxSig;
   1078     for (; offset < segment.length(); offset++) {
   1079         if (segment.charAt(offset) == u'@') {
   1080             minSig++;
   1081         } else {
   1082             break;
   1083         }
   1084     }
   1085     // For the frac-sig option, there must be minSig or maxSig but not both.
   1086     // Valid: @+, @@+, @@@+
   1087     // Valid: @#, @##, @###
   1088     // Invalid: @, @@, @@@
   1089     // Invalid: @@#, @@##, @@@#
   1090     if (offset < segment.length()) {
   1091         if (segment.charAt(offset) == u'+') {
   1092             maxSig = -1;
   1093             offset++;
   1094         } else if (minSig > 1) {
   1095             // @@#, @@##, @@@#
   1096             // throw new SkeletonSyntaxException("Invalid digits option for fraction rounder", segment);
   1097             status = U_NUMBER_SKELETON_SYNTAX_ERROR;
   1098             return false;
   1099         } else {
   1100             maxSig = minSig;
   1101             for (; offset < segment.length(); offset++) {
   1102                 if (segment.charAt(offset) == u'#') {
   1103                     maxSig++;
   1104                 } else {
   1105                     break;
   1106                 }
   1107             }
   1108         }
   1109     } else {
   1110         // @, @@, @@@
   1111         // throw new SkeletonSyntaxException("Invalid digits option for fraction rounder", segment);
   1112         status = U_NUMBER_SKELETON_SYNTAX_ERROR;
   1113         return false;
   1114     }
   1115     if (offset < segment.length()) {
   1116         // throw new SkeletonSyntaxException("Invalid digits option for fraction rounder", segment);
   1117         status = U_NUMBER_SKELETON_SYNTAX_ERROR;
   1118         return false;
   1119     }
   1120 
   1121     auto& oldPrecision = static_cast<const FractionPrecision&>(macros.precision);
   1122     if (maxSig == -1) {
   1123         macros.precision = oldPrecision.withMinDigits(minSig);
   1124     } else {
   1125         macros.precision = oldPrecision.withMaxDigits(maxSig);
   1126     }
   1127     return true;
   1128 }
   1129 
   1130 void blueprint_helpers::parseIncrementOption(const StringSegment& segment, MacroProps& macros,
   1131                                              UErrorCode& status) {
   1132     // Need to do char <-> UChar conversion...
   1133     U_ASSERT(U_SUCCESS(status));
   1134     CharString buffer;
   1135     SKELETON_UCHAR_TO_CHAR(buffer, segment.toTempUnicodeString(), 0, segment.length(), status);
   1136 
   1137     // Utilize DecimalQuantity/decNumber to parse this for us.
   1138     DecimalQuantity dq;
   1139     UErrorCode localStatus = U_ZERO_ERROR;
   1140     dq.setToDecNumber({buffer.data(), buffer.length()}, localStatus);
   1141     if (U_FAILURE(localStatus)) {
   1142         // throw new SkeletonSyntaxException("Invalid rounding increment", segment, e);
   1143         status = U_NUMBER_SKELETON_SYNTAX_ERROR;
   1144         return;
   1145     }
   1146     double increment = dq.toDouble();
   1147 
   1148     // We also need to figure out how many digits. Do a brute force string operation.
   1149     int decimalOffset = 0;
   1150     while (decimalOffset < segment.length() && segment.charAt(decimalOffset) != '.') {
   1151         decimalOffset++;
   1152     }
   1153     if (decimalOffset == segment.length()) {
   1154         macros.precision = Precision::increment(increment);
   1155     } else {
   1156         int32_t fractionLength = segment.length() - decimalOffset - 1;
   1157         macros.precision = Precision::increment(increment).withMinFraction(fractionLength);
   1158     }
   1159 }
   1160 
   1161 void blueprint_helpers::generateIncrementOption(double increment, int32_t trailingZeros, UnicodeString& sb,
   1162                                                 UErrorCode&) {
   1163     // Utilize DecimalQuantity/double_conversion to format this for us.
   1164     DecimalQuantity dq;
   1165     dq.setToDouble(increment);
   1166     dq.roundToInfinity();
   1167     sb.append(dq.toPlainString());
   1168 
   1169     // We might need to append extra trailing zeros for min fraction...
   1170     if (trailingZeros > 0) {
   1171         appendMultiple(sb, u'0', trailingZeros);
   1172     }
   1173 }
   1174 
   1175 void blueprint_helpers::parseIntegerWidthOption(const StringSegment& segment, MacroProps& macros,
   1176                                                 UErrorCode& status) {
   1177     int32_t offset = 0;
   1178     int32_t minInt = 0;
   1179     int32_t maxInt;
   1180     if (segment.charAt(0) == u'+') {
   1181         maxInt = -1;
   1182         offset++;
   1183     } else {
   1184         maxInt = 0;
   1185     }
   1186     for (; offset < segment.length(); offset++) {
   1187         if (segment.charAt(offset) == u'#') {
   1188             maxInt++;
   1189         } else {
   1190             break;
   1191         }
   1192     }
   1193     if (offset < segment.length()) {
   1194         for (; offset < segment.length(); offset++) {
   1195             if (segment.charAt(offset) == u'0') {
   1196                 minInt++;
   1197             } else {
   1198                 break;
   1199             }
   1200         }
   1201     }
   1202     if (maxInt != -1) {
   1203         maxInt += minInt;
   1204     }
   1205     if (offset < segment.length()) {
   1206         // throw new SkeletonSyntaxException("Invalid integer width stem", segment);
   1207         status = U_NUMBER_SKELETON_SYNTAX_ERROR;
   1208         return;
   1209     }
   1210     // Use the public APIs to enforce bounds checking
   1211     if (maxInt == -1) {
   1212         macros.integerWidth = IntegerWidth::zeroFillTo(minInt);
   1213     } else {
   1214         macros.integerWidth = IntegerWidth::zeroFillTo(minInt).truncateAt(maxInt);
   1215     }
   1216 }
   1217 
   1218 void blueprint_helpers::generateIntegerWidthOption(int32_t minInt, int32_t maxInt, UnicodeString& sb,
   1219                                                    UErrorCode&) {
   1220     if (maxInt == -1) {
   1221         sb.append(u'+');
   1222     } else {
   1223         appendMultiple(sb, u'#', maxInt - minInt);
   1224     }
   1225     appendMultiple(sb, u'0', minInt);
   1226 }
   1227 
   1228 void blueprint_helpers::parseNumberingSystemOption(const StringSegment& segment, MacroProps& macros,
   1229                                                    UErrorCode& status) {
   1230     // Need to do char <-> UChar conversion...
   1231     U_ASSERT(U_SUCCESS(status));
   1232     CharString buffer;
   1233     SKELETON_UCHAR_TO_CHAR(buffer, segment.toTempUnicodeString(), 0, segment.length(), status);
   1234 
   1235     NumberingSystem* ns = NumberingSystem::createInstanceByName(buffer.data(), status);
   1236     if (ns == nullptr || U_FAILURE(status)) {
   1237         // This is a skeleton syntax error; don't bubble up the low-level NumberingSystem error
   1238         // throw new SkeletonSyntaxException("Unknown numbering system", segment);
   1239         status = U_NUMBER_SKELETON_SYNTAX_ERROR;
   1240         return;
   1241     }
   1242     macros.symbols.setTo(ns);
   1243 }
   1244 
   1245 void blueprint_helpers::generateNumberingSystemOption(const NumberingSystem& ns, UnicodeString& sb,
   1246                                                       UErrorCode&) {
   1247     // Need to do char <-> UChar conversion...
   1248     sb.append(UnicodeString(ns.getName(), -1, US_INV));
   1249 }
   1250 
   1251 void blueprint_helpers::parseScaleOption(const StringSegment& segment, MacroProps& macros,
   1252                                               UErrorCode& status) {
   1253     // Need to do char <-> UChar conversion...
   1254     U_ASSERT(U_SUCCESS(status));
   1255     CharString buffer;
   1256     SKELETON_UCHAR_TO_CHAR(buffer, segment.toTempUnicodeString(), 0, segment.length(), status);
   1257 
   1258     LocalPointer<DecNum> decnum(new DecNum(), status);
   1259     if (U_FAILURE(status)) { return; }
   1260     decnum->setTo({buffer.data(), buffer.length()}, status);
   1261     if (U_FAILURE(status)) {
   1262         // This is a skeleton syntax error; don't let the low-level decnum error bubble up
   1263         status = U_NUMBER_SKELETON_SYNTAX_ERROR;
   1264         return;
   1265     }
   1266 
   1267     // NOTE: The constructor will optimize the decnum for us if possible.
   1268     macros.scale = {0, decnum.orphan()};
   1269 }
   1270 
   1271 void blueprint_helpers::generateScaleOption(int32_t magnitude, const DecNum* arbitrary, UnicodeString& sb,
   1272                                             UErrorCode& status) {
   1273     // Utilize DecimalQuantity/double_conversion to format this for us.
   1274     DecimalQuantity dq;
   1275     if (arbitrary != nullptr) {
   1276         dq.setToDecNum(*arbitrary, status);
   1277         if (U_FAILURE(status)) { return; }
   1278     } else {
   1279         dq.setToInt(1);
   1280     }
   1281     dq.adjustMagnitude(magnitude);
   1282     dq.roundToInfinity();
   1283     sb.append(dq.toPlainString());
   1284 }
   1285 
   1286 
   1287 bool GeneratorHelpers::notation(const MacroProps& macros, UnicodeString& sb, UErrorCode& status) {
   1288     if (macros.notation.fType == Notation::NTN_COMPACT) {
   1289         UNumberCompactStyle style = macros.notation.fUnion.compactStyle;
   1290         if (style == UNumberCompactStyle::UNUM_LONG) {
   1291             sb.append(u"compact-long", -1);
   1292             return true;
   1293         } else if (style == UNumberCompactStyle::UNUM_SHORT) {
   1294             sb.append(u"compact-short", -1);
   1295             return true;
   1296         } else {
   1297             // Compact notation generated from custom data (not supported in skeleton)
   1298             // The other compact notations are literals
   1299             status = U_UNSUPPORTED_ERROR;
   1300             return false;
   1301         }
   1302     } else if (macros.notation.fType == Notation::NTN_SCIENTIFIC) {
   1303         const Notation::ScientificSettings& impl = macros.notation.fUnion.scientific;
   1304         if (impl.fEngineeringInterval == 3) {
   1305             sb.append(u"engineering", -1);
   1306         } else {
   1307             sb.append(u"scientific", -1);
   1308         }
   1309         if (impl.fMinExponentDigits > 1) {
   1310             sb.append(u'/');
   1311             blueprint_helpers::generateExponentWidthOption(impl.fMinExponentDigits, sb, status);
   1312             if (U_FAILURE(status)) {
   1313                 return false;
   1314             }
   1315         }
   1316         if (impl.fExponentSignDisplay != UNUM_SIGN_AUTO) {
   1317             sb.append(u'/');
   1318             enum_to_stem_string::signDisplay(impl.fExponentSignDisplay, sb);
   1319         }
   1320         return true;
   1321     } else {
   1322         // Default value is not shown in normalized form
   1323         return false;
   1324     }
   1325 }
   1326 
   1327 bool GeneratorHelpers::unit(const MacroProps& macros, UnicodeString& sb, UErrorCode& status) {
   1328     if (utils::unitIsCurrency(macros.unit)) {
   1329         sb.append(u"currency/", -1);
   1330         CurrencyUnit currency(macros.unit, status);
   1331         if (U_FAILURE(status)) {
   1332             return false;
   1333         }
   1334         blueprint_helpers::generateCurrencyOption(currency, sb, status);
   1335         return true;
   1336     } else if (utils::unitIsNoUnit(macros.unit)) {
   1337         if (utils::unitIsPercent(macros.unit)) {
   1338             sb.append(u"percent", -1);
   1339             return true;
   1340         } else if (utils::unitIsPermille(macros.unit)) {
   1341             sb.append(u"permille", -1);
   1342             return true;
   1343         } else {
   1344             // Default value is not shown in normalized form
   1345             return false;
   1346         }
   1347     } else {
   1348         sb.append(u"measure-unit/", -1);
   1349         blueprint_helpers::generateMeasureUnitOption(macros.unit, sb, status);
   1350         return true;
   1351     }
   1352 }
   1353 
   1354 bool GeneratorHelpers::perUnit(const MacroProps& macros, UnicodeString& sb, UErrorCode& status) {
   1355     // Per-units are currently expected to be only MeasureUnits.
   1356     if (utils::unitIsNoUnit(macros.perUnit)) {
   1357         if (utils::unitIsPercent(macros.perUnit) || utils::unitIsPermille(macros.perUnit)) {
   1358             status = U_UNSUPPORTED_ERROR;
   1359             return false;
   1360         } else {
   1361             // Default value: ok to ignore
   1362             return false;
   1363         }
   1364     } else if (utils::unitIsCurrency(macros.perUnit)) {
   1365         status = U_UNSUPPORTED_ERROR;
   1366         return false;
   1367     } else {
   1368         sb.append(u"per-measure-unit/", -1);
   1369         blueprint_helpers::generateMeasureUnitOption(macros.perUnit, sb, status);
   1370         return true;
   1371     }
   1372 }
   1373 
   1374 bool GeneratorHelpers::precision(const MacroProps& macros, UnicodeString& sb, UErrorCode& status) {
   1375     if (macros.precision.fType == Precision::RND_NONE) {
   1376         sb.append(u"precision-unlimited", -1);
   1377     } else if (macros.precision.fType == Precision::RND_FRACTION) {
   1378         const Precision::FractionSignificantSettings& impl = macros.precision.fUnion.fracSig;
   1379         blueprint_helpers::generateFractionStem(impl.fMinFrac, impl.fMaxFrac, sb, status);
   1380     } else if (macros.precision.fType == Precision::RND_SIGNIFICANT) {
   1381         const Precision::FractionSignificantSettings& impl = macros.precision.fUnion.fracSig;
   1382         blueprint_helpers::generateDigitsStem(impl.fMinSig, impl.fMaxSig, sb, status);
   1383     } else if (macros.precision.fType == Precision::RND_FRACTION_SIGNIFICANT) {
   1384         const Precision::FractionSignificantSettings& impl = macros.precision.fUnion.fracSig;
   1385         blueprint_helpers::generateFractionStem(impl.fMinFrac, impl.fMaxFrac, sb, status);
   1386         sb.append(u'/');
   1387         if (impl.fMinSig == -1) {
   1388             blueprint_helpers::generateDigitsStem(1, impl.fMaxSig, sb, status);
   1389         } else {
   1390             blueprint_helpers::generateDigitsStem(impl.fMinSig, -1, sb, status);
   1391         }
   1392     } else if (macros.precision.fType == Precision::RND_INCREMENT) {
   1393         const Precision::IncrementSettings& impl = macros.precision.fUnion.increment;
   1394         sb.append(u"precision-increment/", -1);
   1395         blueprint_helpers::generateIncrementOption(
   1396                 impl.fIncrement,
   1397                 impl.fMinFrac - impl.fMaxFrac,
   1398                 sb,
   1399                 status);
   1400     } else if (macros.precision.fType == Precision::RND_CURRENCY) {
   1401         UCurrencyUsage usage = macros.precision.fUnion.currencyUsage;
   1402         if (usage == UCURR_USAGE_STANDARD) {
   1403             sb.append(u"precision-currency-standard", -1);
   1404         } else {
   1405             sb.append(u"precision-currency-cash", -1);
   1406         }
   1407     } else {
   1408         // Bogus or Error
   1409         return false;
   1410     }
   1411 
   1412     // NOTE: Always return true for rounding because the default value depends on other options.
   1413     return true;
   1414 }
   1415 
   1416 bool GeneratorHelpers::roundingMode(const MacroProps& macros, UnicodeString& sb, UErrorCode&) {
   1417     if (macros.roundingMode == kDefaultMode) {
   1418         return false; // Default
   1419     }
   1420     enum_to_stem_string::roundingMode(macros.roundingMode, sb);
   1421     return true;
   1422 }
   1423 
   1424 bool GeneratorHelpers::grouping(const MacroProps& macros, UnicodeString& sb, UErrorCode& status) {
   1425     if (macros.grouper.isBogus()) {
   1426         return false; // No value
   1427     } else if (macros.grouper.fStrategy == UNUM_GROUPING_COUNT) {
   1428         status = U_UNSUPPORTED_ERROR;
   1429         return false;
   1430     } else if (macros.grouper.fStrategy == UNUM_GROUPING_AUTO) {
   1431         return false; // Default value
   1432     } else {
   1433         enum_to_stem_string::groupingStrategy(macros.grouper.fStrategy, sb);
   1434         return true;
   1435     }
   1436 }
   1437 
   1438 bool GeneratorHelpers::integerWidth(const MacroProps& macros, UnicodeString& sb, UErrorCode& status) {
   1439     if (macros.integerWidth.fHasError || macros.integerWidth.isBogus() ||
   1440         macros.integerWidth == IntegerWidth::standard()) {
   1441         // Error or Default
   1442         return false;
   1443     }
   1444     sb.append(u"integer-width/", -1);
   1445     blueprint_helpers::generateIntegerWidthOption(
   1446             macros.integerWidth.fUnion.minMaxInt.fMinInt,
   1447             macros.integerWidth.fUnion.minMaxInt.fMaxInt,
   1448             sb,
   1449             status);
   1450     return true;
   1451 }
   1452 
   1453 bool GeneratorHelpers::symbols(const MacroProps& macros, UnicodeString& sb, UErrorCode& status) {
   1454     if (macros.symbols.isNumberingSystem()) {
   1455         const NumberingSystem& ns = *macros.symbols.getNumberingSystem();
   1456         if (uprv_strcmp(ns.getName(), "latn") == 0) {
   1457             sb.append(u"latin", -1);
   1458         } else {
   1459             sb.append(u"numbering-system/", -1);
   1460             blueprint_helpers::generateNumberingSystemOption(ns, sb, status);
   1461         }
   1462         return true;
   1463     } else if (macros.symbols.isDecimalFormatSymbols()) {
   1464         status = U_UNSUPPORTED_ERROR;
   1465         return false;
   1466     } else {
   1467         // No custom symbols
   1468         return false;
   1469     }
   1470 }
   1471 
   1472 bool GeneratorHelpers::unitWidth(const MacroProps& macros, UnicodeString& sb, UErrorCode&) {
   1473     if (macros.unitWidth == UNUM_UNIT_WIDTH_SHORT || macros.unitWidth == UNUM_UNIT_WIDTH_COUNT) {
   1474         return false; // Default or Bogus
   1475     }
   1476     enum_to_stem_string::unitWidth(macros.unitWidth, sb);
   1477     return true;
   1478 }
   1479 
   1480 bool GeneratorHelpers::sign(const MacroProps& macros, UnicodeString& sb, UErrorCode&) {
   1481     if (macros.sign == UNUM_SIGN_AUTO || macros.sign == UNUM_SIGN_COUNT) {
   1482         return false; // Default or Bogus
   1483     }
   1484     enum_to_stem_string::signDisplay(macros.sign, sb);
   1485     return true;
   1486 }
   1487 
   1488 bool GeneratorHelpers::decimal(const MacroProps& macros, UnicodeString& sb, UErrorCode&) {
   1489     if (macros.decimal == UNUM_DECIMAL_SEPARATOR_AUTO || macros.decimal == UNUM_DECIMAL_SEPARATOR_COUNT) {
   1490         return false; // Default or Bogus
   1491     }
   1492     enum_to_stem_string::decimalSeparatorDisplay(macros.decimal, sb);
   1493     return true;
   1494 }
   1495 
   1496 bool GeneratorHelpers::scale(const MacroProps& macros, UnicodeString& sb, UErrorCode& status) {
   1497     if (!macros.scale.isValid()) {
   1498         return false; // Default or Bogus
   1499     }
   1500     sb.append(u"scale/", -1);
   1501     blueprint_helpers::generateScaleOption(
   1502             macros.scale.fMagnitude,
   1503             macros.scale.fArbitrary,
   1504             sb,
   1505             status);
   1506     return true;
   1507 }
   1508 
   1509 
   1510 #endif /* #if !UCONFIG_NO_FORMATTING */
   1511