tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

number_skeletons.cpp (65342B)


      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_roundingutils.h"
     14 #include "number_skeletons.h"
     15 #include "umutex.h"
     16 #include "ucln_in.h"
     17 #include "patternprops.h"
     18 #include "unicode/ucharstriebuilder.h"
     19 #include "number_utils.h"
     20 #include "number_decimalquantity.h"
     21 #include "unicode/numberformatter.h"
     22 #include "uinvchar.h"
     23 #include "charstr.h"
     24 #include "string_segment.h"
     25 #include "unicode/errorcode.h"
     26 #include "util.h"
     27 #include "measunit_impl.h"
     28 
     29 using namespace icu;
     30 using namespace icu::number;
     31 using namespace icu::number::impl;
     32 using namespace icu::number::impl::skeleton;
     33 
     34 namespace {
     35 
     36 icu::UInitOnce gNumberSkeletonsInitOnce {};
     37 
     38 char16_t* kSerializedStemTrie = nullptr;
     39 
     40 UBool U_CALLCONV cleanupNumberSkeletons() {
     41    uprv_free(kSerializedStemTrie);
     42    kSerializedStemTrie = nullptr;
     43    gNumberSkeletonsInitOnce.reset();
     44    return true;
     45 }
     46 
     47 void U_CALLCONV initNumberSkeletons(UErrorCode& status) {
     48    ucln_i18n_registerCleanup(UCLN_I18N_NUMBER_SKELETONS, cleanupNumberSkeletons);
     49 
     50    UCharsTrieBuilder b(status);
     51    if (U_FAILURE(status)) { return; }
     52 
     53    // Section 1:
     54    b.add(u"compact-short", STEM_COMPACT_SHORT, status);
     55    b.add(u"compact-long", STEM_COMPACT_LONG, status);
     56    b.add(u"scientific", STEM_SCIENTIFIC, status);
     57    b.add(u"engineering", STEM_ENGINEERING, status);
     58    b.add(u"notation-simple", STEM_NOTATION_SIMPLE, status);
     59    b.add(u"base-unit", STEM_BASE_UNIT, status);
     60    b.add(u"percent", STEM_PERCENT, status);
     61    b.add(u"permille", STEM_PERMILLE, status);
     62    b.add(u"precision-integer", STEM_PRECISION_INTEGER, status);
     63    b.add(u"precision-unlimited", STEM_PRECISION_UNLIMITED, status);
     64    b.add(u"precision-currency-standard", STEM_PRECISION_CURRENCY_STANDARD, status);
     65    b.add(u"precision-currency-cash", STEM_PRECISION_CURRENCY_CASH, status);
     66    b.add(u"rounding-mode-ceiling", STEM_ROUNDING_MODE_CEILING, status);
     67    b.add(u"rounding-mode-floor", STEM_ROUNDING_MODE_FLOOR, status);
     68    b.add(u"rounding-mode-down", STEM_ROUNDING_MODE_DOWN, status);
     69    b.add(u"rounding-mode-up", STEM_ROUNDING_MODE_UP, status);
     70    b.add(u"rounding-mode-half-even", STEM_ROUNDING_MODE_HALF_EVEN, status);
     71    b.add(u"rounding-mode-half-odd", STEM_ROUNDING_MODE_HALF_ODD, status);
     72    b.add(u"rounding-mode-half-ceiling", STEM_ROUNDING_MODE_HALF_CEILING, status);
     73    b.add(u"rounding-mode-half-floor", STEM_ROUNDING_MODE_HALF_FLOOR, status);
     74    b.add(u"rounding-mode-half-down", STEM_ROUNDING_MODE_HALF_DOWN, status);
     75    b.add(u"rounding-mode-half-up", STEM_ROUNDING_MODE_HALF_UP, status);
     76    b.add(u"rounding-mode-unnecessary", STEM_ROUNDING_MODE_UNNECESSARY, status);
     77    b.add(u"integer-width-trunc", STEM_INTEGER_WIDTH_TRUNC, status);
     78    b.add(u"group-off", STEM_GROUP_OFF, status);
     79    b.add(u"group-min2", STEM_GROUP_MIN2, status);
     80    b.add(u"group-auto", STEM_GROUP_AUTO, status);
     81    b.add(u"group-on-aligned", STEM_GROUP_ON_ALIGNED, status);
     82    b.add(u"group-thousands", STEM_GROUP_THOUSANDS, status);
     83    b.add(u"latin", STEM_LATIN, status);
     84    b.add(u"unit-width-narrow", STEM_UNIT_WIDTH_NARROW, status);
     85    b.add(u"unit-width-short", STEM_UNIT_WIDTH_SHORT, status);
     86    b.add(u"unit-width-full-name", STEM_UNIT_WIDTH_FULL_NAME, status);
     87    b.add(u"unit-width-iso-code", STEM_UNIT_WIDTH_ISO_CODE, status);
     88    b.add(u"unit-width-formal", STEM_UNIT_WIDTH_FORMAL, status);
     89    b.add(u"unit-width-variant", STEM_UNIT_WIDTH_VARIANT, status);
     90    b.add(u"unit-width-hidden", STEM_UNIT_WIDTH_HIDDEN, status);
     91    b.add(u"sign-auto", STEM_SIGN_AUTO, status);
     92    b.add(u"sign-always", STEM_SIGN_ALWAYS, status);
     93    b.add(u"sign-never", STEM_SIGN_NEVER, status);
     94    b.add(u"sign-accounting", STEM_SIGN_ACCOUNTING, status);
     95    b.add(u"sign-accounting-always", STEM_SIGN_ACCOUNTING_ALWAYS, status);
     96    b.add(u"sign-except-zero", STEM_SIGN_EXCEPT_ZERO, status);
     97    b.add(u"sign-accounting-except-zero", STEM_SIGN_ACCOUNTING_EXCEPT_ZERO, status);
     98    b.add(u"sign-negative", STEM_SIGN_NEGATIVE, status);
     99    b.add(u"sign-accounting-negative", STEM_SIGN_ACCOUNTING_NEGATIVE, status);
    100    b.add(u"decimal-auto", STEM_DECIMAL_AUTO, status);
    101    b.add(u"decimal-always", STEM_DECIMAL_ALWAYS, status);
    102    if (U_FAILURE(status)) { return; }
    103 
    104    // Section 2:
    105    b.add(u"precision-increment", STEM_PRECISION_INCREMENT, status);
    106    b.add(u"measure-unit", STEM_MEASURE_UNIT, status);
    107    b.add(u"per-measure-unit", STEM_PER_MEASURE_UNIT, status);
    108    b.add(u"unit", STEM_UNIT, status);
    109    b.add(u"usage", STEM_UNIT_USAGE, status);
    110    b.add(u"currency", STEM_CURRENCY, status);
    111    b.add(u"integer-width", STEM_INTEGER_WIDTH, status);
    112    b.add(u"numbering-system", STEM_NUMBERING_SYSTEM, status);
    113    b.add(u"scale", STEM_SCALE, status);
    114    if (U_FAILURE(status)) { return; }
    115 
    116    // Section 3 (concise tokens):
    117    b.add(u"K", STEM_COMPACT_SHORT, status);
    118    b.add(u"KK", STEM_COMPACT_LONG, status);
    119    b.add(u"%", STEM_PERCENT, status);
    120    b.add(u"%x100", STEM_PERCENT_100, status);
    121    b.add(u",_", STEM_GROUP_OFF, status);
    122    b.add(u",?", STEM_GROUP_MIN2, status);
    123    b.add(u",!", STEM_GROUP_ON_ALIGNED, status);
    124    b.add(u"+!", STEM_SIGN_ALWAYS, status);
    125    b.add(u"+_", STEM_SIGN_NEVER, status);
    126    b.add(u"()", STEM_SIGN_ACCOUNTING, status);
    127    b.add(u"()!", STEM_SIGN_ACCOUNTING_ALWAYS, status);
    128    b.add(u"+?", STEM_SIGN_EXCEPT_ZERO, status);
    129    b.add(u"()?", STEM_SIGN_ACCOUNTING_EXCEPT_ZERO, status);
    130    b.add(u"+-", STEM_SIGN_NEGATIVE, status);
    131    b.add(u"()-", STEM_SIGN_ACCOUNTING_NEGATIVE, status);
    132    if (U_FAILURE(status)) { return; }
    133 
    134    // Build the CharsTrie
    135    // TODO: Use SLOW or FAST here?
    136    UnicodeString result;
    137    b.buildUnicodeString(USTRINGTRIE_BUILD_FAST, result, status);
    138    if (U_FAILURE(status)) { return; }
    139 
    140    // Copy the result into the global constant pointer
    141    size_t numBytes = result.length() * sizeof(char16_t);
    142    kSerializedStemTrie = static_cast<char16_t*>(uprv_malloc(numBytes));
    143    uprv_memcpy(kSerializedStemTrie, result.getBuffer(), numBytes);
    144 }
    145 
    146 
    147 inline void appendMultiple(UnicodeString& sb, UChar32 cp, int32_t count) {
    148    for (int i = 0; i < count; i++) {
    149        sb.append(cp);
    150    }
    151 }
    152 
    153 
    154 #define CHECK_NULL(seen, field, status) (void)(seen); /* for auto-format line wrapping */ \
    155 UPRV_BLOCK_MACRO_BEGIN { \
    156    if ((seen).field) { \
    157        (status) = U_NUMBER_SKELETON_SYNTAX_ERROR; \
    158        return STATE_NULL; \
    159    } \
    160    (seen).field = true; \
    161 } UPRV_BLOCK_MACRO_END
    162 
    163 
    164 } // anonymous namespace
    165 
    166 
    167 Notation stem_to_object::notation(skeleton::StemEnum stem) {
    168    switch (stem) {
    169        case STEM_COMPACT_SHORT:
    170            return Notation::compactShort();
    171        case STEM_COMPACT_LONG:
    172            return Notation::compactLong();
    173        case STEM_SCIENTIFIC:
    174            return Notation::scientific();
    175        case STEM_ENGINEERING:
    176            return Notation::engineering();
    177        case STEM_NOTATION_SIMPLE:
    178            return Notation::simple();
    179        default:
    180            UPRV_UNREACHABLE_EXIT;
    181    }
    182 }
    183 
    184 MeasureUnit stem_to_object::unit(skeleton::StemEnum stem) {
    185    switch (stem) {
    186        case STEM_BASE_UNIT:
    187            return {};
    188        case STEM_PERCENT:
    189            return MeasureUnit::getPercent();
    190        case STEM_PERMILLE:
    191            return MeasureUnit::getPermille();
    192        default:
    193            UPRV_UNREACHABLE_EXIT;
    194    }
    195 }
    196 
    197 Precision stem_to_object::precision(skeleton::StemEnum stem) {
    198    switch (stem) {
    199        case STEM_PRECISION_INTEGER:
    200            return Precision::integer();
    201        case STEM_PRECISION_UNLIMITED:
    202            return Precision::unlimited();
    203        case STEM_PRECISION_CURRENCY_STANDARD:
    204            return Precision::currency(UCURR_USAGE_STANDARD);
    205        case STEM_PRECISION_CURRENCY_CASH:
    206            return Precision::currency(UCURR_USAGE_CASH);
    207        default:
    208            UPRV_UNREACHABLE_EXIT;
    209    }
    210 }
    211 
    212 UNumberFormatRoundingMode stem_to_object::roundingMode(skeleton::StemEnum stem) {
    213    switch (stem) {
    214        case STEM_ROUNDING_MODE_CEILING:
    215            return UNUM_ROUND_CEILING;
    216        case STEM_ROUNDING_MODE_FLOOR:
    217            return UNUM_ROUND_FLOOR;
    218        case STEM_ROUNDING_MODE_DOWN:
    219            return UNUM_ROUND_DOWN;
    220        case STEM_ROUNDING_MODE_UP:
    221            return UNUM_ROUND_UP;
    222        case STEM_ROUNDING_MODE_HALF_EVEN:
    223            return UNUM_ROUND_HALFEVEN;
    224        case STEM_ROUNDING_MODE_HALF_ODD:
    225            return UNUM_ROUND_HALF_ODD;
    226        case STEM_ROUNDING_MODE_HALF_CEILING:
    227            return UNUM_ROUND_HALF_CEILING;
    228        case STEM_ROUNDING_MODE_HALF_FLOOR:
    229            return UNUM_ROUND_HALF_FLOOR;
    230        case STEM_ROUNDING_MODE_HALF_DOWN:
    231            return UNUM_ROUND_HALFDOWN;
    232        case STEM_ROUNDING_MODE_HALF_UP:
    233            return UNUM_ROUND_HALFUP;
    234        case STEM_ROUNDING_MODE_UNNECESSARY:
    235            return UNUM_ROUND_UNNECESSARY;
    236        default:
    237            UPRV_UNREACHABLE_EXIT;
    238    }
    239 }
    240 
    241 UNumberGroupingStrategy stem_to_object::groupingStrategy(skeleton::StemEnum stem) {
    242    switch (stem) {
    243        case STEM_GROUP_OFF:
    244            return UNUM_GROUPING_OFF;
    245        case STEM_GROUP_MIN2:
    246            return UNUM_GROUPING_MIN2;
    247        case STEM_GROUP_AUTO:
    248            return UNUM_GROUPING_AUTO;
    249        case STEM_GROUP_ON_ALIGNED:
    250            return UNUM_GROUPING_ON_ALIGNED;
    251        case STEM_GROUP_THOUSANDS:
    252            return UNUM_GROUPING_THOUSANDS;
    253        default:
    254            return UNUM_GROUPING_COUNT; // for objects, throw; for enums, return COUNT
    255    }
    256 }
    257 
    258 UNumberUnitWidth stem_to_object::unitWidth(skeleton::StemEnum stem) {
    259    switch (stem) {
    260        case STEM_UNIT_WIDTH_NARROW:
    261            return UNUM_UNIT_WIDTH_NARROW;
    262        case STEM_UNIT_WIDTH_SHORT:
    263            return UNUM_UNIT_WIDTH_SHORT;
    264        case STEM_UNIT_WIDTH_FULL_NAME:
    265            return UNUM_UNIT_WIDTH_FULL_NAME;
    266        case STEM_UNIT_WIDTH_ISO_CODE:
    267            return UNUM_UNIT_WIDTH_ISO_CODE;
    268        case STEM_UNIT_WIDTH_FORMAL:
    269            return UNUM_UNIT_WIDTH_FORMAL;
    270        case STEM_UNIT_WIDTH_VARIANT:
    271            return UNUM_UNIT_WIDTH_VARIANT;
    272        case STEM_UNIT_WIDTH_HIDDEN:
    273            return UNUM_UNIT_WIDTH_HIDDEN;
    274        default:
    275            return UNUM_UNIT_WIDTH_COUNT; // for objects, throw; for enums, return COUNT
    276    }
    277 }
    278 
    279 UNumberSignDisplay stem_to_object::signDisplay(skeleton::StemEnum stem) {
    280    switch (stem) {
    281        case STEM_SIGN_AUTO:
    282            return UNUM_SIGN_AUTO;
    283        case STEM_SIGN_ALWAYS:
    284            return UNUM_SIGN_ALWAYS;
    285        case STEM_SIGN_NEVER:
    286            return UNUM_SIGN_NEVER;
    287        case STEM_SIGN_ACCOUNTING:
    288            return UNUM_SIGN_ACCOUNTING;
    289        case STEM_SIGN_ACCOUNTING_ALWAYS:
    290            return UNUM_SIGN_ACCOUNTING_ALWAYS;
    291        case STEM_SIGN_EXCEPT_ZERO:
    292            return UNUM_SIGN_EXCEPT_ZERO;
    293        case STEM_SIGN_ACCOUNTING_EXCEPT_ZERO:
    294            return UNUM_SIGN_ACCOUNTING_EXCEPT_ZERO;
    295        case STEM_SIGN_NEGATIVE:
    296            return UNUM_SIGN_NEGATIVE;
    297        case STEM_SIGN_ACCOUNTING_NEGATIVE:
    298            return UNUM_SIGN_ACCOUNTING_NEGATIVE;
    299        default:
    300            return UNUM_SIGN_COUNT; // for objects, throw; for enums, return COUNT
    301    }
    302 }
    303 
    304 UNumberDecimalSeparatorDisplay stem_to_object::decimalSeparatorDisplay(skeleton::StemEnum stem) {
    305    switch (stem) {
    306        case STEM_DECIMAL_AUTO:
    307            return UNUM_DECIMAL_SEPARATOR_AUTO;
    308        case STEM_DECIMAL_ALWAYS:
    309            return UNUM_DECIMAL_SEPARATOR_ALWAYS;
    310        default:
    311            return UNUM_DECIMAL_SEPARATOR_COUNT; // for objects, throw; for enums, return COUNT
    312    }
    313 }
    314 
    315 
    316 void enum_to_stem_string::roundingMode(UNumberFormatRoundingMode value, UnicodeString& sb) {
    317    switch (value) {
    318        case UNUM_ROUND_CEILING:
    319            sb.append(u"rounding-mode-ceiling", -1);
    320            break;
    321        case UNUM_ROUND_FLOOR:
    322            sb.append(u"rounding-mode-floor", -1);
    323            break;
    324        case UNUM_ROUND_DOWN:
    325            sb.append(u"rounding-mode-down", -1);
    326            break;
    327        case UNUM_ROUND_UP:
    328            sb.append(u"rounding-mode-up", -1);
    329            break;
    330        case UNUM_ROUND_HALFEVEN:
    331            sb.append(u"rounding-mode-half-even", -1);
    332            break;
    333        case UNUM_ROUND_HALF_ODD:
    334            sb.append(u"rounding-mode-half-odd", -1);
    335            break;
    336        case UNUM_ROUND_HALF_CEILING:
    337            sb.append(u"rounding-mode-half-ceiling", -1);
    338            break;
    339        case UNUM_ROUND_HALF_FLOOR:
    340            sb.append(u"rounding-mode-half-floor", -1);
    341            break;
    342        case UNUM_ROUND_HALFDOWN:
    343            sb.append(u"rounding-mode-half-down", -1);
    344            break;
    345        case UNUM_ROUND_HALFUP:
    346            sb.append(u"rounding-mode-half-up", -1);
    347            break;
    348        case UNUM_ROUND_UNNECESSARY:
    349            sb.append(u"rounding-mode-unnecessary", -1);
    350            break;
    351        default:
    352            UPRV_UNREACHABLE_EXIT;
    353    }
    354 }
    355 
    356 void enum_to_stem_string::groupingStrategy(UNumberGroupingStrategy value, UnicodeString& sb) {
    357    switch (value) {
    358        case UNUM_GROUPING_OFF:
    359            sb.append(u"group-off", -1);
    360            break;
    361        case UNUM_GROUPING_MIN2:
    362            sb.append(u"group-min2", -1);
    363            break;
    364        case UNUM_GROUPING_AUTO:
    365            sb.append(u"group-auto", -1);
    366            break;
    367        case UNUM_GROUPING_ON_ALIGNED:
    368            sb.append(u"group-on-aligned", -1);
    369            break;
    370        case UNUM_GROUPING_THOUSANDS:
    371            sb.append(u"group-thousands", -1);
    372            break;
    373        default:
    374            UPRV_UNREACHABLE_EXIT;
    375    }
    376 }
    377 
    378 void enum_to_stem_string::unitWidth(UNumberUnitWidth value, UnicodeString& sb) {
    379    switch (value) {
    380        case UNUM_UNIT_WIDTH_NARROW:
    381            sb.append(u"unit-width-narrow", -1);
    382            break;
    383        case UNUM_UNIT_WIDTH_SHORT:
    384            sb.append(u"unit-width-short", -1);
    385            break;
    386        case UNUM_UNIT_WIDTH_FULL_NAME:
    387            sb.append(u"unit-width-full-name", -1);
    388            break;
    389        case UNUM_UNIT_WIDTH_ISO_CODE:
    390            sb.append(u"unit-width-iso-code", -1);
    391            break;
    392        case UNUM_UNIT_WIDTH_FORMAL:
    393            sb.append(u"unit-width-formal", -1);
    394            break;
    395        case UNUM_UNIT_WIDTH_VARIANT:
    396            sb.append(u"unit-width-variant", -1);
    397            break;
    398        case UNUM_UNIT_WIDTH_HIDDEN:
    399            sb.append(u"unit-width-hidden", -1);
    400            break;
    401        default:
    402            UPRV_UNREACHABLE_EXIT;
    403    }
    404 }
    405 
    406 void enum_to_stem_string::signDisplay(UNumberSignDisplay value, UnicodeString& sb) {
    407    switch (value) {
    408        case UNUM_SIGN_AUTO:
    409            sb.append(u"sign-auto", -1);
    410            break;
    411        case UNUM_SIGN_ALWAYS:
    412            sb.append(u"sign-always", -1);
    413            break;
    414        case UNUM_SIGN_NEVER:
    415            sb.append(u"sign-never", -1);
    416            break;
    417        case UNUM_SIGN_ACCOUNTING:
    418            sb.append(u"sign-accounting", -1);
    419            break;
    420        case UNUM_SIGN_ACCOUNTING_ALWAYS:
    421            sb.append(u"sign-accounting-always", -1);
    422            break;
    423        case UNUM_SIGN_EXCEPT_ZERO:
    424            sb.append(u"sign-except-zero", -1);
    425            break;
    426        case UNUM_SIGN_ACCOUNTING_EXCEPT_ZERO:
    427            sb.append(u"sign-accounting-except-zero", -1);
    428            break;
    429        case UNUM_SIGN_NEGATIVE:
    430            sb.append(u"sign-negative", -1);
    431            break;
    432        case UNUM_SIGN_ACCOUNTING_NEGATIVE:
    433            sb.append(u"sign-accounting-negative", -1);
    434            break;
    435        default:
    436            UPRV_UNREACHABLE_EXIT;
    437    }
    438 }
    439 
    440 void
    441 enum_to_stem_string::decimalSeparatorDisplay(UNumberDecimalSeparatorDisplay value, UnicodeString& sb) {
    442    switch (value) {
    443        case UNUM_DECIMAL_SEPARATOR_AUTO:
    444            sb.append(u"decimal-auto", -1);
    445            break;
    446        case UNUM_DECIMAL_SEPARATOR_ALWAYS:
    447            sb.append(u"decimal-always", -1);
    448            break;
    449        default:
    450            UPRV_UNREACHABLE_EXIT;
    451    }
    452 }
    453 
    454 
    455 UnlocalizedNumberFormatter skeleton::create(
    456        const UnicodeString& skeletonString, UParseError* perror, UErrorCode& status) {
    457 
    458    // Initialize perror
    459    if (perror != nullptr) {
    460        perror->line = 0;
    461        perror->offset = -1;
    462        perror->preContext[0] = 0;
    463        perror->postContext[0] = 0;
    464    }
    465 
    466    umtx_initOnce(gNumberSkeletonsInitOnce, &initNumberSkeletons, status);
    467    if (U_FAILURE(status)) {
    468        return {};
    469    }
    470 
    471    int32_t errOffset;
    472    MacroProps macros = parseSkeleton(skeletonString, errOffset, status);
    473    if (U_SUCCESS(status)) {
    474        return NumberFormatter::with().macros(macros);
    475    }
    476 
    477    if (perror == nullptr) {
    478        return {};
    479    }
    480 
    481    // Populate the UParseError with the error location
    482    perror->offset = errOffset;
    483    int32_t contextStart = uprv_max(0, errOffset - U_PARSE_CONTEXT_LEN + 1);
    484    int32_t contextEnd = uprv_min(skeletonString.length(), errOffset + U_PARSE_CONTEXT_LEN - 1);
    485    skeletonString.extract(contextStart, errOffset - contextStart, perror->preContext, 0);
    486    perror->preContext[errOffset - contextStart] = 0;
    487    skeletonString.extract(errOffset, contextEnd - errOffset, perror->postContext, 0);
    488    perror->postContext[contextEnd - errOffset] = 0;
    489    return {};
    490 }
    491 
    492 UnicodeString skeleton::generate(const MacroProps& macros, UErrorCode& status) {
    493    umtx_initOnce(gNumberSkeletonsInitOnce, &initNumberSkeletons, status);
    494    UnicodeString sb;
    495    GeneratorHelpers::generateSkeleton(macros, sb, status);
    496    return sb;
    497 }
    498 
    499 MacroProps skeleton::parseSkeleton(
    500        const UnicodeString& skeletonString, int32_t& errOffset, UErrorCode& status) {
    501    U_ASSERT(U_SUCCESS(status));
    502    U_ASSERT(kSerializedStemTrie != nullptr);
    503 
    504    // Add a trailing whitespace to the end of the skeleton string to make code cleaner.
    505    UnicodeString tempSkeletonString(skeletonString);
    506    tempSkeletonString.append(u' ');
    507 
    508    SeenMacroProps seen;
    509    MacroProps macros;
    510    StringSegment segment(tempSkeletonString, false);
    511    UCharsTrie stemTrie(kSerializedStemTrie);
    512    ParseState stem = STATE_NULL;
    513    int32_t offset = 0;
    514 
    515    // Primary skeleton parse loop:
    516    while (offset < segment.length()) {
    517        UChar32 cp = segment.codePointAt(offset);
    518        bool isTokenSeparator = PatternProps::isWhiteSpace(cp);
    519        bool isOptionSeparator = (cp == u'/');
    520 
    521        if (!isTokenSeparator && !isOptionSeparator) {
    522            // Non-separator token; consume it.
    523            offset += U16_LENGTH(cp);
    524            if (stem == STATE_NULL) {
    525                // We are currently consuming a stem.
    526                // Go to the next state in the stem trie.
    527                stemTrie.nextForCodePoint(cp);
    528            }
    529            continue;
    530        }
    531 
    532        // We are looking at a token or option separator.
    533        // If the segment is nonempty, parse it and reset the segment.
    534        // Otherwise, make sure it is a valid repeating separator.
    535        if (offset != 0) {
    536            segment.setLength(offset);
    537            if (stem == STATE_NULL) {
    538                // The first separator after the start of a token. Parse it as a stem.
    539                stem = parseStem(segment, stemTrie, seen, macros, status);
    540                stemTrie.reset();
    541            } else {
    542                // A separator after the first separator of a token. Parse it as an option.
    543                stem = parseOption(stem, segment, macros, status);
    544            }
    545            segment.resetLength();
    546            if (U_FAILURE(status)) {
    547                errOffset = segment.getOffset();
    548                return macros;
    549            }
    550 
    551            // Consume the segment:
    552            segment.adjustOffset(offset);
    553            offset = 0;
    554 
    555        } else if (stem != STATE_NULL) {
    556            // A separator ('/' or whitespace) following an option separator ('/')
    557            // segment.setLength(U16_LENGTH(cp)); // for error message
    558            // throw new SkeletonSyntaxException("Unexpected separator character", segment);
    559            status = U_NUMBER_SKELETON_SYNTAX_ERROR;
    560            errOffset = segment.getOffset();
    561            return macros;
    562 
    563        } else {
    564            // Two spaces in a row; this is OK.
    565        }
    566 
    567        // Does the current stem forbid options?
    568        if (isOptionSeparator && stem == STATE_NULL) {
    569            // segment.setLength(U16_LENGTH(cp)); // for error message
    570            // throw new SkeletonSyntaxException("Unexpected option separator", segment);
    571            status = U_NUMBER_SKELETON_SYNTAX_ERROR;
    572            errOffset = segment.getOffset();
    573            return macros;
    574        }
    575 
    576        // Does the current stem require an option?
    577        if (isTokenSeparator && stem != STATE_NULL) {
    578            switch (stem) {
    579                case STATE_INCREMENT_PRECISION:
    580                case STATE_MEASURE_UNIT:
    581                case STATE_PER_MEASURE_UNIT:
    582                case STATE_IDENTIFIER_UNIT:
    583                case STATE_UNIT_USAGE:
    584                case STATE_CURRENCY_UNIT:
    585                case STATE_INTEGER_WIDTH:
    586                case STATE_NUMBERING_SYSTEM:
    587                case STATE_SCALE:
    588                    // segment.setLength(U16_LENGTH(cp)); // for error message
    589                    // throw new SkeletonSyntaxException("Stem requires an option", segment);
    590                    status = U_NUMBER_SKELETON_SYNTAX_ERROR;
    591                    errOffset = segment.getOffset();
    592                    return macros;
    593                default:
    594                    break;
    595            }
    596            stem = STATE_NULL;
    597        }
    598 
    599        // Consume the separator:
    600        segment.adjustOffset(U16_LENGTH(cp));
    601    }
    602    U_ASSERT(stem == STATE_NULL);
    603    return macros;
    604 }
    605 
    606 ParseState
    607 skeleton::parseStem(const StringSegment& segment, const UCharsTrie& stemTrie, SeenMacroProps& seen,
    608                    MacroProps& macros, UErrorCode& status) {
    609    U_ASSERT(U_SUCCESS(status));
    610 
    611    // First check for "blueprint" stems, which start with a "signal char"
    612    switch (segment.charAt(0)) {
    613        case u'.':
    614            CHECK_NULL(seen, precision, status);
    615            blueprint_helpers::parseFractionStem(segment, macros, status);
    616            return STATE_FRACTION_PRECISION;
    617        case u'@':
    618            CHECK_NULL(seen, precision, status);
    619            blueprint_helpers::parseDigitsStem(segment, macros, status);
    620            return STATE_PRECISION;
    621        case u'E':
    622            CHECK_NULL(seen, notation, status);
    623            blueprint_helpers::parseScientificStem(segment, macros, status);
    624            return STATE_NULL;
    625        case u'0':
    626            CHECK_NULL(seen, integerWidth, status);
    627            blueprint_helpers::parseIntegerStem(segment, macros, status);
    628            return STATE_NULL;
    629        default:
    630            break;
    631    }
    632 
    633    // Now look at the stemsTrie, which is already be pointing at our stem.
    634    UStringTrieResult stemResult = stemTrie.current();
    635 
    636    if (stemResult != USTRINGTRIE_INTERMEDIATE_VALUE && stemResult != USTRINGTRIE_FINAL_VALUE) {
    637        // throw new SkeletonSyntaxException("Unknown stem", segment);
    638        status = U_NUMBER_SKELETON_SYNTAX_ERROR;
    639        return STATE_NULL;
    640    }
    641 
    642    auto stem = static_cast<StemEnum>(stemTrie.getValue());
    643    switch (stem) {
    644 
    645        // Stems with meaning on their own, not requiring an option:
    646 
    647        case STEM_COMPACT_SHORT:
    648        case STEM_COMPACT_LONG:
    649        case STEM_SCIENTIFIC:
    650        case STEM_ENGINEERING:
    651        case STEM_NOTATION_SIMPLE:
    652            CHECK_NULL(seen, notation, status);
    653            macros.notation = stem_to_object::notation(stem);
    654            switch (stem) {
    655                case STEM_SCIENTIFIC:
    656                case STEM_ENGINEERING:
    657                    return STATE_SCIENTIFIC; // allows for scientific options
    658                default:
    659                    return STATE_NULL;
    660            }
    661 
    662        case STEM_BASE_UNIT:
    663        case STEM_PERCENT:
    664        case STEM_PERMILLE:
    665            CHECK_NULL(seen, unit, status);
    666            macros.unit = stem_to_object::unit(stem);
    667            return STATE_NULL;
    668 
    669        case STEM_PERCENT_100:
    670            CHECK_NULL(seen, scale, status);
    671            CHECK_NULL(seen, unit, status);
    672            macros.scale = Scale::powerOfTen(2);
    673            macros.unit = NoUnit::percent();
    674            return STATE_NULL;
    675 
    676        case STEM_PRECISION_INTEGER:
    677        case STEM_PRECISION_UNLIMITED:
    678        case STEM_PRECISION_CURRENCY_STANDARD:
    679        case STEM_PRECISION_CURRENCY_CASH:
    680            CHECK_NULL(seen, precision, status);
    681            macros.precision = stem_to_object::precision(stem);
    682            switch (stem) {
    683                case STEM_PRECISION_INTEGER:
    684                    return STATE_FRACTION_PRECISION; // allows for "precision-integer/@##"
    685                default:
    686                    return STATE_PRECISION;
    687            }
    688 
    689        case STEM_ROUNDING_MODE_CEILING:
    690        case STEM_ROUNDING_MODE_FLOOR:
    691        case STEM_ROUNDING_MODE_DOWN:
    692        case STEM_ROUNDING_MODE_UP:
    693        case STEM_ROUNDING_MODE_HALF_EVEN:
    694        case STEM_ROUNDING_MODE_HALF_ODD:
    695        case STEM_ROUNDING_MODE_HALF_CEILING:
    696        case STEM_ROUNDING_MODE_HALF_FLOOR:
    697        case STEM_ROUNDING_MODE_HALF_DOWN:
    698        case STEM_ROUNDING_MODE_HALF_UP:
    699        case STEM_ROUNDING_MODE_UNNECESSARY:
    700            CHECK_NULL(seen, roundingMode, status);
    701            macros.roundingMode = stem_to_object::roundingMode(stem);
    702            return STATE_NULL;
    703 
    704        case STEM_INTEGER_WIDTH_TRUNC:
    705            CHECK_NULL(seen, integerWidth, status);
    706            macros.integerWidth = IntegerWidth::zeroFillTo(0).truncateAt(0);
    707            return STATE_NULL;
    708 
    709        case STEM_GROUP_OFF:
    710        case STEM_GROUP_MIN2:
    711        case STEM_GROUP_AUTO:
    712        case STEM_GROUP_ON_ALIGNED:
    713        case STEM_GROUP_THOUSANDS:
    714            CHECK_NULL(seen, grouper, status);
    715            macros.grouper = Grouper::forStrategy(stem_to_object::groupingStrategy(stem));
    716            return STATE_NULL;
    717 
    718        case STEM_LATIN:
    719            CHECK_NULL(seen, symbols, status);
    720            macros.symbols.setTo(NumberingSystem::createInstanceByName("latn", status));
    721            return STATE_NULL;
    722 
    723        case STEM_UNIT_WIDTH_NARROW:
    724        case STEM_UNIT_WIDTH_SHORT:
    725        case STEM_UNIT_WIDTH_FULL_NAME:
    726        case STEM_UNIT_WIDTH_ISO_CODE:
    727        case STEM_UNIT_WIDTH_FORMAL:
    728        case STEM_UNIT_WIDTH_VARIANT:
    729        case STEM_UNIT_WIDTH_HIDDEN:
    730            CHECK_NULL(seen, unitWidth, status);
    731            macros.unitWidth = stem_to_object::unitWidth(stem);
    732            return STATE_NULL;
    733 
    734        case STEM_SIGN_AUTO:
    735        case STEM_SIGN_ALWAYS:
    736        case STEM_SIGN_NEVER:
    737        case STEM_SIGN_ACCOUNTING:
    738        case STEM_SIGN_ACCOUNTING_ALWAYS:
    739        case STEM_SIGN_EXCEPT_ZERO:
    740        case STEM_SIGN_ACCOUNTING_EXCEPT_ZERO:
    741        case STEM_SIGN_NEGATIVE:
    742        case STEM_SIGN_ACCOUNTING_NEGATIVE:
    743            CHECK_NULL(seen, sign, status);
    744            macros.sign = stem_to_object::signDisplay(stem);
    745            return STATE_NULL;
    746 
    747        case STEM_DECIMAL_AUTO:
    748        case STEM_DECIMAL_ALWAYS:
    749            CHECK_NULL(seen, decimal, status);
    750            macros.decimal = stem_to_object::decimalSeparatorDisplay(stem);
    751            return STATE_NULL;
    752 
    753        // Stems requiring an option:
    754 
    755        case STEM_PRECISION_INCREMENT:
    756            CHECK_NULL(seen, precision, status);
    757            return STATE_INCREMENT_PRECISION;
    758 
    759        case STEM_MEASURE_UNIT:
    760            CHECK_NULL(seen, unit, status);
    761            return STATE_MEASURE_UNIT;
    762 
    763        case STEM_PER_MEASURE_UNIT:
    764            CHECK_NULL(seen, perUnit, status);
    765            return STATE_PER_MEASURE_UNIT;
    766 
    767        case STEM_UNIT:
    768            CHECK_NULL(seen, unit, status);
    769            CHECK_NULL(seen, perUnit, status);
    770            return STATE_IDENTIFIER_UNIT;
    771 
    772        case STEM_UNIT_USAGE:
    773            CHECK_NULL(seen, usage, status);
    774            return STATE_UNIT_USAGE;
    775 
    776        case STEM_CURRENCY:
    777            CHECK_NULL(seen, unit, status);
    778            CHECK_NULL(seen, perUnit, status);
    779            return STATE_CURRENCY_UNIT;
    780 
    781        case STEM_INTEGER_WIDTH:
    782            CHECK_NULL(seen, integerWidth, status);
    783            return STATE_INTEGER_WIDTH;
    784 
    785        case STEM_NUMBERING_SYSTEM:
    786            CHECK_NULL(seen, symbols, status);
    787            return STATE_NUMBERING_SYSTEM;
    788 
    789        case STEM_SCALE:
    790            CHECK_NULL(seen, scale, status);
    791            return STATE_SCALE;
    792 
    793        default:
    794            UPRV_UNREACHABLE_EXIT;
    795    }
    796 }
    797 
    798 ParseState skeleton::parseOption(ParseState stem, const StringSegment& segment, MacroProps& macros,
    799                                 UErrorCode& status) {
    800    U_ASSERT(U_SUCCESS(status));
    801 
    802    ///// Required options: /////
    803 
    804    switch (stem) {
    805        case STATE_CURRENCY_UNIT:
    806            blueprint_helpers::parseCurrencyOption(segment, macros, status);
    807            return STATE_NULL;
    808        case STATE_MEASURE_UNIT:
    809            blueprint_helpers::parseMeasureUnitOption(segment, macros, status);
    810            return STATE_NULL;
    811        case STATE_PER_MEASURE_UNIT:
    812            blueprint_helpers::parseMeasurePerUnitOption(segment, macros, status);
    813            return STATE_NULL;
    814        case STATE_IDENTIFIER_UNIT:
    815            blueprint_helpers::parseIdentifierUnitOption(segment, macros, status);
    816            return STATE_NULL;
    817        case STATE_UNIT_USAGE:
    818            blueprint_helpers::parseUnitUsageOption(segment, macros, status);
    819            return STATE_NULL;
    820        case STATE_INCREMENT_PRECISION:
    821            blueprint_helpers::parseIncrementOption(segment, macros, status);
    822            return STATE_PRECISION;
    823        case STATE_INTEGER_WIDTH:
    824            blueprint_helpers::parseIntegerWidthOption(segment, macros, status);
    825            return STATE_NULL;
    826        case STATE_NUMBERING_SYSTEM:
    827            blueprint_helpers::parseNumberingSystemOption(segment, macros, status);
    828            return STATE_NULL;
    829        case STATE_SCALE:
    830            blueprint_helpers::parseScaleOption(segment, macros, status);
    831            return STATE_NULL;
    832        default:
    833            break;
    834    }
    835 
    836    ///// Non-required options: /////
    837 
    838    // Scientific options
    839    switch (stem) {
    840        case STATE_SCIENTIFIC:
    841            if (blueprint_helpers::parseExponentWidthOption(segment, macros, status)) {
    842                return STATE_SCIENTIFIC;
    843            }
    844            if (U_FAILURE(status)) {
    845                return {};
    846            }
    847            if (blueprint_helpers::parseExponentSignOption(segment, macros, status)) {
    848                return STATE_SCIENTIFIC;
    849            }
    850            if (U_FAILURE(status)) {
    851                return {};
    852            }
    853            break;
    854        default:
    855            break;
    856    }
    857 
    858    // Frac-sig option
    859    switch (stem) {
    860        case STATE_FRACTION_PRECISION:
    861            if (blueprint_helpers::parseFracSigOption(segment, macros, status)) {
    862                return STATE_PRECISION;
    863            }
    864            if (U_FAILURE(status)) {
    865                return {};
    866            }
    867            // If the fracSig option was not found, try normal precision options.
    868            stem = STATE_PRECISION;
    869            break;
    870        default:
    871            break;
    872    }
    873 
    874    // Trailing zeros option
    875    switch (stem) {
    876        case STATE_PRECISION:
    877            if (blueprint_helpers::parseTrailingZeroOption(segment, macros, status)) {
    878                return STATE_NULL;
    879            }
    880            if (U_FAILURE(status)) {
    881                return {};
    882            }
    883            break;
    884        default:
    885            break;
    886    }
    887 
    888    // Unknown option
    889    // throw new SkeletonSyntaxException("Invalid option", segment);
    890    status = U_NUMBER_SKELETON_SYNTAX_ERROR;
    891    return STATE_NULL;
    892 }
    893 
    894 void GeneratorHelpers::generateSkeleton(const MacroProps& macros, UnicodeString& sb, UErrorCode& status) {
    895    if (U_FAILURE(status)) { return; }
    896 
    897    // Supported options
    898    if (GeneratorHelpers::notation(macros, sb, status)) {
    899        sb.append(u' ');
    900    }
    901    if (U_FAILURE(status)) { return; }
    902    if (GeneratorHelpers::unit(macros, sb, status)) {
    903        sb.append(u' ');
    904    }
    905    if (U_FAILURE(status)) { return; }
    906    if (GeneratorHelpers::usage(macros, sb, status)) {
    907        sb.append(u' ');
    908    }
    909    if (U_FAILURE(status)) { return; }
    910    if (GeneratorHelpers::precision(macros, sb, status)) {
    911        sb.append(u' ');
    912    }
    913    if (U_FAILURE(status)) { return; }
    914    if (GeneratorHelpers::roundingMode(macros, sb, status)) {
    915        sb.append(u' ');
    916    }
    917    if (U_FAILURE(status)) { return; }
    918    if (GeneratorHelpers::grouping(macros, sb, status)) {
    919        sb.append(u' ');
    920    }
    921    if (U_FAILURE(status)) { return; }
    922    if (GeneratorHelpers::integerWidth(macros, sb, status)) {
    923        sb.append(u' ');
    924    }
    925    if (U_FAILURE(status)) { return; }
    926    if (GeneratorHelpers::symbols(macros, sb, status)) {
    927        sb.append(u' ');
    928    }
    929    if (U_FAILURE(status)) { return; }
    930    if (GeneratorHelpers::unitWidth(macros, sb, status)) {
    931        sb.append(u' ');
    932    }
    933    if (U_FAILURE(status)) { return; }
    934    if (GeneratorHelpers::sign(macros, sb, status)) {
    935        sb.append(u' ');
    936    }
    937    if (U_FAILURE(status)) { return; }
    938    if (GeneratorHelpers::decimal(macros, sb, status)) {
    939        sb.append(u' ');
    940    }
    941    if (U_FAILURE(status)) { return; }
    942    if (GeneratorHelpers::scale(macros, sb, status)) {
    943        sb.append(u' ');
    944    }
    945    if (U_FAILURE(status)) { return; }
    946 
    947    // Unsupported options
    948    if (!macros.padder.isBogus()) {
    949        status = U_UNSUPPORTED_ERROR;
    950        return;
    951    }
    952    if (macros.unitDisplayCase.isSet()) {
    953        status = U_UNSUPPORTED_ERROR;
    954        return;
    955    }
    956    if (macros.affixProvider != nullptr) {
    957        status = U_UNSUPPORTED_ERROR;
    958        return;
    959    }
    960    if (macros.rules != nullptr) {
    961        status = U_UNSUPPORTED_ERROR;
    962        return;
    963    }
    964 
    965    // Remove the trailing space
    966    if (sb.length() > 0) {
    967        sb.truncate(sb.length() - 1);
    968    }
    969 }
    970 
    971 
    972 bool blueprint_helpers::parseExponentWidthOption(const StringSegment& segment, MacroProps& macros,
    973                                                 UErrorCode&) {
    974    if (!isWildcardChar(segment.charAt(0))) {
    975        return false;
    976    }
    977    int32_t offset = 1;
    978    int32_t minExp = 0;
    979    for (; offset < segment.length(); offset++) {
    980        if (segment.charAt(offset) == u'e') {
    981            minExp++;
    982        } else {
    983            break;
    984        }
    985    }
    986    if (offset < segment.length()) {
    987        return false;
    988    }
    989    // Use the public APIs to enforce bounds checking
    990    macros.notation = static_cast<ScientificNotation&>(macros.notation).withMinExponentDigits(minExp);
    991    return true;
    992 }
    993 
    994 void
    995 blueprint_helpers::generateExponentWidthOption(int32_t minExponentDigits, UnicodeString& sb, UErrorCode&) {
    996    sb.append(kWildcardChar);
    997    appendMultiple(sb, u'e', minExponentDigits);
    998 }
    999 
   1000 bool
   1001 blueprint_helpers::parseExponentSignOption(const StringSegment& segment, MacroProps& macros, UErrorCode&) {
   1002    // Get the sign display type out of the CharsTrie data structure.
   1003    UCharsTrie tempStemTrie(kSerializedStemTrie);
   1004    UStringTrieResult result = tempStemTrie.next(
   1005            segment.toTempUnicodeString().getBuffer(),
   1006            segment.length());
   1007    if (result != USTRINGTRIE_INTERMEDIATE_VALUE && result != USTRINGTRIE_FINAL_VALUE) {
   1008        return false;
   1009    }
   1010    auto sign = stem_to_object::signDisplay(static_cast<StemEnum>(tempStemTrie.getValue()));
   1011    if (sign == UNUM_SIGN_COUNT) {
   1012        return false;
   1013    }
   1014    macros.notation = static_cast<ScientificNotation&>(macros.notation).withExponentSignDisplay(sign);
   1015    return true;
   1016 }
   1017 
   1018 // The function is called by skeleton::parseOption which called by skeleton::parseSkeleton
   1019 // the data pointed in the return macros.unit is stack allocated in the parseSkeleton function.
   1020 #if U_GCC_MAJOR_MINOR >= 1204
   1021 #pragma GCC diagnostic push
   1022 #pragma GCC diagnostic ignored "-Wdangling-pointer"
   1023 #endif
   1024 void blueprint_helpers::parseCurrencyOption(const StringSegment& segment, MacroProps& macros,
   1025                                            UErrorCode& status) {
   1026    // Unlike ICU4J, have to check length manually because ICU4C CurrencyUnit does not check it for us
   1027    if (segment.length() != 3) {
   1028        status = U_NUMBER_SKELETON_SYNTAX_ERROR;
   1029        return;
   1030    }
   1031    const char16_t* currencyCode = segment.toTempUnicodeString().getBuffer();
   1032    UErrorCode localStatus = U_ZERO_ERROR;
   1033    CurrencyUnit currency(currencyCode, localStatus);
   1034    if (U_FAILURE(localStatus)) {
   1035        // Not 3 ascii chars
   1036        // throw new SkeletonSyntaxException("Invalid currency", segment);
   1037        status = U_NUMBER_SKELETON_SYNTAX_ERROR;
   1038        return;
   1039    }
   1040    // Slicing is OK
   1041    macros.unit = currency; // NOLINT
   1042 }
   1043 #if U_GCC_MAJOR_MINOR >= 1204
   1044 #pragma GCC diagnostic pop
   1045 #endif
   1046 
   1047 void
   1048 blueprint_helpers::generateCurrencyOption(const CurrencyUnit& currency, UnicodeString& sb, UErrorCode&) {
   1049    sb.append(currency.getISOCurrency(), -1);
   1050 }
   1051 
   1052 void blueprint_helpers::parseMeasureUnitOption(const StringSegment& segment, MacroProps& macros,
   1053                                               UErrorCode& status) {
   1054    U_ASSERT(U_SUCCESS(status));
   1055    const UnicodeString stemString = segment.toTempUnicodeString();
   1056 
   1057    // NOTE: The category (type) of the unit is guaranteed to be a valid subtag (alphanumeric)
   1058    // http://unicode.org/reports/tr35/#Validity_Data
   1059    int firstHyphen = 0;
   1060    while (firstHyphen < stemString.length() && stemString.charAt(firstHyphen) != '-') {
   1061        firstHyphen++;
   1062    }
   1063    if (firstHyphen == stemString.length()) {
   1064        // throw new SkeletonSyntaxException("Invalid measure unit option", segment);
   1065        status = U_NUMBER_SKELETON_SYNTAX_ERROR;
   1066        return;
   1067    }
   1068 
   1069    // Need to do char <-> char16_t conversion...
   1070    CharString type;
   1071    SKELETON_UCHAR_TO_CHAR(type, stemString, 0, firstHyphen, status);
   1072    CharString subType;
   1073    SKELETON_UCHAR_TO_CHAR(subType, stemString, firstHyphen + 1, stemString.length(), status);
   1074 
   1075    static constexpr int32_t CAPACITY = 50;
   1076    MeasureUnit units[CAPACITY];
   1077    UErrorCode localStatus = U_ZERO_ERROR;
   1078    int32_t numUnits = MeasureUnit::getAvailable(type.data(), units, CAPACITY, localStatus);
   1079    if (U_FAILURE(localStatus)) {
   1080        // More than 30 units in this type?
   1081        status = U_INTERNAL_PROGRAM_ERROR;
   1082        return;
   1083    }
   1084    for (int32_t i = 0; i < numUnits; i++) {
   1085        auto& unit = units[i];
   1086        if (uprv_strcmp(subType.data(), unit.getSubtype()) == 0) {
   1087            macros.unit = unit;
   1088            return;
   1089        }
   1090    }
   1091 
   1092    // throw new SkeletonSyntaxException("Unknown measure unit", segment);
   1093    status = U_NUMBER_SKELETON_SYNTAX_ERROR;
   1094 }
   1095 
   1096 void blueprint_helpers::parseMeasurePerUnitOption(const StringSegment& segment, MacroProps& macros,
   1097                                                  UErrorCode& status) {
   1098    // A little bit of a hack: save the current unit (numerator), call the main measure unit
   1099    // parsing code, put back the numerator unit, and put the new unit into per-unit.
   1100    MeasureUnit numerator = macros.unit;
   1101    parseMeasureUnitOption(segment, macros, status);
   1102    if (U_FAILURE(status)) { return; }
   1103    macros.perUnit = macros.unit;
   1104    macros.unit = numerator;
   1105 }
   1106 
   1107 void blueprint_helpers::parseIdentifierUnitOption(const StringSegment& segment, MacroProps& macros,
   1108                                                  UErrorCode& status) {
   1109    // Need to do char <-> char16_t conversion...
   1110    U_ASSERT(U_SUCCESS(status));
   1111    CharString buffer;
   1112    SKELETON_UCHAR_TO_CHAR(buffer, segment.toTempUnicodeString(), 0, segment.length(), status);
   1113 
   1114    ErrorCode internalStatus;
   1115    macros.unit = MeasureUnit::forIdentifier(buffer.toStringPiece(), internalStatus);
   1116    if (internalStatus.isFailure()) {
   1117        // throw new SkeletonSyntaxException("Invalid core unit identifier", segment, e);
   1118        status = U_NUMBER_SKELETON_SYNTAX_ERROR;
   1119        return;
   1120    }
   1121 }
   1122 
   1123 void blueprint_helpers::parseUnitUsageOption(const StringSegment &segment, MacroProps &macros,
   1124                                             UErrorCode &status) {
   1125    // Need to do char <-> char16_t conversion...
   1126    U_ASSERT(U_SUCCESS(status));
   1127    CharString buffer;
   1128    SKELETON_UCHAR_TO_CHAR(buffer, segment.toTempUnicodeString(), 0, segment.length(), status);
   1129    macros.usage.set(buffer.toStringPiece());
   1130    // We do not do any validation of the usage string: it depends on the
   1131    // unitPreferenceData in the units resources.
   1132 }
   1133 
   1134 void blueprint_helpers::parseFractionStem(const StringSegment& segment, MacroProps& macros,
   1135                                          UErrorCode& status) {
   1136    U_ASSERT(segment.charAt(0) == u'.');
   1137    int32_t offset = 1;
   1138    int32_t minFrac = 0;
   1139    int32_t maxFrac;
   1140    for (; offset < segment.length(); offset++) {
   1141        if (segment.charAt(offset) == u'0') {
   1142            minFrac++;
   1143        } else {
   1144            break;
   1145        }
   1146    }
   1147    if (offset < segment.length()) {
   1148        if (isWildcardChar(segment.charAt(offset))) {
   1149            maxFrac = -1;
   1150            offset++;
   1151        } else {
   1152            maxFrac = minFrac;
   1153            for (; offset < segment.length(); offset++) {
   1154                if (segment.charAt(offset) == u'#') {
   1155                    maxFrac++;
   1156                } else {
   1157                    break;
   1158                }
   1159            }
   1160        }
   1161    } else {
   1162        maxFrac = minFrac;
   1163    }
   1164    if (offset < segment.length()) {
   1165        // throw new SkeletonSyntaxException("Invalid fraction stem", segment);
   1166        status = U_NUMBER_SKELETON_SYNTAX_ERROR;
   1167        return;
   1168    }
   1169    // Use the public APIs to enforce bounds checking
   1170    if (maxFrac == -1) {
   1171        if (minFrac == 0) {
   1172            macros.precision = Precision::unlimited();
   1173        } else {
   1174            macros.precision = Precision::minFraction(minFrac);
   1175        }
   1176    } else {
   1177        macros.precision = Precision::minMaxFraction(minFrac, maxFrac);
   1178    }
   1179 }
   1180 
   1181 void
   1182 blueprint_helpers::generateFractionStem(int32_t minFrac, int32_t maxFrac, UnicodeString& sb, UErrorCode&) {
   1183    if (minFrac == 0 && maxFrac == 0) {
   1184        sb.append(u"precision-integer", -1);
   1185        return;
   1186    }
   1187    sb.append(u'.');
   1188    appendMultiple(sb, u'0', minFrac);
   1189    if (maxFrac == -1) {
   1190        sb.append(kWildcardChar);
   1191    } else {
   1192        appendMultiple(sb, u'#', maxFrac - minFrac);
   1193    }
   1194 }
   1195 
   1196 void
   1197 blueprint_helpers::parseDigitsStem(const StringSegment& segment, MacroProps& macros, UErrorCode& status) {
   1198    U_ASSERT(segment.charAt(0) == u'@');
   1199    int32_t offset = 0;
   1200    int32_t minSig = 0;
   1201    int32_t maxSig;
   1202    for (; offset < segment.length(); offset++) {
   1203        if (segment.charAt(offset) == u'@') {
   1204            minSig++;
   1205        } else {
   1206            break;
   1207        }
   1208    }
   1209    if (offset < segment.length()) {
   1210        if (isWildcardChar(segment.charAt(offset))) {
   1211            maxSig = -1;
   1212            offset++;
   1213        } else {
   1214            maxSig = minSig;
   1215            for (; offset < segment.length(); offset++) {
   1216                if (segment.charAt(offset) == u'#') {
   1217                    maxSig++;
   1218                } else {
   1219                    break;
   1220                }
   1221            }
   1222        }
   1223    } else {
   1224        maxSig = minSig;
   1225    }
   1226    if (offset < segment.length()) {
   1227        // throw new SkeletonSyntaxException("Invalid significant digits stem", segment);
   1228        status = U_NUMBER_SKELETON_SYNTAX_ERROR;
   1229        return;
   1230    }
   1231    // Use the public APIs to enforce bounds checking
   1232    if (maxSig == -1) {
   1233        macros.precision = Precision::minSignificantDigits(minSig);
   1234    } else {
   1235        macros.precision = Precision::minMaxSignificantDigits(minSig, maxSig);
   1236    }
   1237 }
   1238 
   1239 void
   1240 blueprint_helpers::generateDigitsStem(int32_t minSig, int32_t maxSig, UnicodeString& sb, UErrorCode&) {
   1241    appendMultiple(sb, u'@', minSig);
   1242    if (maxSig == -1) {
   1243        sb.append(kWildcardChar);
   1244    } else {
   1245        appendMultiple(sb, u'#', maxSig - minSig);
   1246    }
   1247 }
   1248 
   1249 void blueprint_helpers::parseScientificStem(const StringSegment& segment, MacroProps& macros, UErrorCode& status) {
   1250    U_ASSERT(segment.charAt(0) == u'E');
   1251    {
   1252        int32_t offset = 1;
   1253        if (segment.length() == offset) {
   1254            goto fail;
   1255        }
   1256        bool isEngineering = false;
   1257        if (segment.charAt(offset) == u'E') {
   1258            isEngineering = true;
   1259            offset++;
   1260            if (segment.length() == offset) {
   1261                goto fail;
   1262            }
   1263        }
   1264        UNumberSignDisplay signDisplay = UNUM_SIGN_AUTO;
   1265        if (segment.charAt(offset) == u'+') {
   1266            offset++;
   1267            if (segment.length() == offset) {
   1268                goto fail;
   1269            }
   1270            if (segment.charAt(offset) == u'!') {
   1271                signDisplay = UNUM_SIGN_ALWAYS;
   1272            } else if (segment.charAt(offset) == u'?') {
   1273                signDisplay = UNUM_SIGN_EXCEPT_ZERO;
   1274            } else {
   1275                // NOTE: Other sign displays are not included because they aren't useful in this context
   1276                goto fail;
   1277            }
   1278            offset++;
   1279            if (segment.length() == offset) {
   1280                goto fail;
   1281            }
   1282        }
   1283        int32_t minDigits = 0;
   1284        for (; offset < segment.length(); offset++) {
   1285            if (segment.charAt(offset) != u'0') {
   1286                goto fail;
   1287            }
   1288            minDigits++;
   1289        }
   1290        macros.notation = (isEngineering ? Notation::engineering() : Notation::scientific())
   1291            .withExponentSignDisplay(signDisplay)
   1292            .withMinExponentDigits(minDigits);
   1293        return;
   1294    }
   1295    fail: void();
   1296    // throw new SkeletonSyntaxException("Invalid scientific stem", segment);
   1297    status = U_NUMBER_SKELETON_SYNTAX_ERROR;
   1298 }
   1299 
   1300 void blueprint_helpers::parseIntegerStem(const StringSegment& segment, MacroProps& macros, UErrorCode& status) {
   1301    U_ASSERT(segment.charAt(0) == u'0');
   1302    int32_t offset = 1;
   1303    for (; offset < segment.length(); offset++) {
   1304        if (segment.charAt(offset) != u'0') {
   1305            offset--;
   1306            break;
   1307        }
   1308    }
   1309    if (offset < segment.length()) {
   1310        // throw new SkeletonSyntaxException("Invalid integer stem", segment);
   1311        status = U_NUMBER_SKELETON_SYNTAX_ERROR;
   1312        return;
   1313    }
   1314    macros.integerWidth = IntegerWidth::zeroFillTo(offset);
   1315 }
   1316 
   1317 bool blueprint_helpers::parseFracSigOption(const StringSegment& segment, MacroProps& macros,
   1318                                           UErrorCode& status) {
   1319    if (segment.charAt(0) != u'@') {
   1320        return false;
   1321    }
   1322    int offset = 0;
   1323    int minSig = 0;
   1324    int maxSig;
   1325    for (; offset < segment.length(); offset++) {
   1326        if (segment.charAt(offset) == u'@') {
   1327            minSig++;
   1328        } else {
   1329            break;
   1330        }
   1331    }
   1332    if (offset < segment.length()) {
   1333        if (isWildcardChar(segment.charAt(offset))) {
   1334            // @+, @@+, @@@+
   1335            maxSig = -1;
   1336            offset++;
   1337        } else {
   1338            // @#, @##, @###
   1339            // @@#, @@##, @@@#
   1340            maxSig = minSig;
   1341            for (; offset < segment.length(); offset++) {
   1342                if (segment.charAt(offset) == u'#') {
   1343                    maxSig++;
   1344                } else {
   1345                    break;
   1346                }
   1347            }
   1348        }
   1349    } else {
   1350        // @, @@, @@@
   1351        maxSig = minSig;
   1352    }
   1353    const auto& oldPrecision = static_cast<const FractionPrecision&>(macros.precision);
   1354    if (offset < segment.length()) {
   1355        UNumberRoundingPriority priority;
   1356        if (maxSig == -1) {
   1357            // The wildcard character is not allowed with the priority annotation
   1358            status = U_NUMBER_SKELETON_SYNTAX_ERROR;
   1359            return false;
   1360        }
   1361        if (segment.codePointAt(offset) == u'r') {
   1362            priority = UNUM_ROUNDING_PRIORITY_RELAXED;
   1363            offset++;
   1364        } else if (segment.codePointAt(offset) == u's') {
   1365            priority = UNUM_ROUNDING_PRIORITY_STRICT;
   1366            offset++;
   1367        } else {
   1368            // Invalid digits option for fraction rounder
   1369            status = U_NUMBER_SKELETON_SYNTAX_ERROR;
   1370            return false;
   1371        }
   1372        if (offset < segment.length()) {
   1373            // Invalid digits option for fraction rounder
   1374            status = U_NUMBER_SKELETON_SYNTAX_ERROR;
   1375            return false;
   1376        }
   1377        macros.precision = oldPrecision.withSignificantDigits(minSig, maxSig, priority);
   1378    } else if (maxSig == -1) {
   1379        // withMinDigits
   1380        macros.precision = oldPrecision.withMinDigits(minSig);
   1381    } else if (minSig == 1) {
   1382        // withMaxDigits
   1383        macros.precision = oldPrecision.withMaxDigits(maxSig);
   1384    } else {
   1385        // Digits options with both min and max sig require the priority option
   1386        status = U_NUMBER_SKELETON_SYNTAX_ERROR;
   1387        return false;
   1388    }
   1389 
   1390    return true;
   1391 }
   1392 
   1393 bool blueprint_helpers::parseTrailingZeroOption(const StringSegment& segment, MacroProps& macros, UErrorCode&) {
   1394    if (segment == u"w") {
   1395        macros.precision = macros.precision.trailingZeroDisplay(UNUM_TRAILING_ZERO_HIDE_IF_WHOLE);
   1396        return true;
   1397    }
   1398    return false;
   1399 }
   1400 
   1401 void blueprint_helpers::parseIncrementOption(const StringSegment &segment, MacroProps &macros,
   1402                                             UErrorCode &status) {
   1403    number::impl::parseIncrementOption(segment, macros.precision, status);
   1404 }
   1405 
   1406 void blueprint_helpers::generateIncrementOption(
   1407        uint32_t increment,
   1408        digits_t incrementMagnitude,
   1409        int32_t minFrac,
   1410        UnicodeString& sb,
   1411        UErrorCode&) {
   1412    // Utilize DecimalQuantity/double_conversion to format this for us.
   1413    DecimalQuantity dq;
   1414    dq.setToLong(increment);
   1415    dq.adjustMagnitude(incrementMagnitude);
   1416    dq.setMinFraction(minFrac);
   1417    sb.append(dq.toPlainString());
   1418 }
   1419 
   1420 void blueprint_helpers::parseIntegerWidthOption(const StringSegment& segment, MacroProps& macros,
   1421                                                UErrorCode& status) {
   1422    int32_t offset = 0;
   1423    int32_t minInt = 0;
   1424    int32_t maxInt;
   1425    if (isWildcardChar(segment.charAt(0))) {
   1426        maxInt = -1;
   1427        offset++;
   1428    } else {
   1429        maxInt = 0;
   1430    }
   1431    for (; offset < segment.length(); offset++) {
   1432        if (maxInt != -1 && segment.charAt(offset) == u'#') {
   1433            maxInt++;
   1434        } else {
   1435            break;
   1436        }
   1437    }
   1438    if (offset < segment.length()) {
   1439        for (; offset < segment.length(); offset++) {
   1440            if (segment.charAt(offset) == u'0') {
   1441                minInt++;
   1442            } else {
   1443                break;
   1444            }
   1445        }
   1446    }
   1447    if (maxInt != -1) {
   1448        maxInt += minInt;
   1449    }
   1450    if (offset < segment.length()) {
   1451        // throw new SkeletonSyntaxException("Invalid integer width stem", segment);
   1452        status = U_NUMBER_SKELETON_SYNTAX_ERROR;
   1453        return;
   1454    }
   1455    // Use the public APIs to enforce bounds checking
   1456    if (maxInt == -1) {
   1457        macros.integerWidth = IntegerWidth::zeroFillTo(minInt);
   1458    } else {
   1459        macros.integerWidth = IntegerWidth::zeroFillTo(minInt).truncateAt(maxInt);
   1460    }
   1461 }
   1462 
   1463 void blueprint_helpers::generateIntegerWidthOption(int32_t minInt, int32_t maxInt, UnicodeString& sb,
   1464                                                   UErrorCode&) {
   1465    if (maxInt == -1) {
   1466        sb.append(kWildcardChar);
   1467    } else {
   1468        appendMultiple(sb, u'#', maxInt - minInt);
   1469    }
   1470    appendMultiple(sb, u'0', minInt);
   1471 }
   1472 
   1473 void blueprint_helpers::parseNumberingSystemOption(const StringSegment& segment, MacroProps& macros,
   1474                                                   UErrorCode& status) {
   1475    // Need to do char <-> char16_t conversion...
   1476    U_ASSERT(U_SUCCESS(status));
   1477    CharString buffer;
   1478    SKELETON_UCHAR_TO_CHAR(buffer, segment.toTempUnicodeString(), 0, segment.length(), status);
   1479 
   1480    NumberingSystem* ns = NumberingSystem::createInstanceByName(buffer.data(), status);
   1481    if (ns == nullptr || U_FAILURE(status)) {
   1482        // This is a skeleton syntax error; don't bubble up the low-level NumberingSystem error
   1483        // throw new SkeletonSyntaxException("Unknown numbering system", segment);
   1484        status = U_NUMBER_SKELETON_SYNTAX_ERROR;
   1485        return;
   1486    }
   1487    macros.symbols.setTo(ns);
   1488 }
   1489 
   1490 void blueprint_helpers::generateNumberingSystemOption(const NumberingSystem& ns, UnicodeString& sb,
   1491                                                      UErrorCode&) {
   1492    // Need to do char <-> char16_t conversion...
   1493    sb.append(UnicodeString(ns.getName(), -1, US_INV));
   1494 }
   1495 
   1496 void blueprint_helpers::parseScaleOption(const StringSegment& segment, MacroProps& macros,
   1497                                              UErrorCode& status) {
   1498    // Need to do char <-> char16_t conversion...
   1499    U_ASSERT(U_SUCCESS(status));
   1500    CharString buffer;
   1501    SKELETON_UCHAR_TO_CHAR(buffer, segment.toTempUnicodeString(), 0, segment.length(), status);
   1502 
   1503    LocalPointer<DecNum> decnum(new DecNum(), status);
   1504    if (U_FAILURE(status)) { return; }
   1505    decnum->setTo({buffer.data(), buffer.length()}, status);
   1506    if (U_FAILURE(status) || decnum->isSpecial()) {
   1507        // This is a skeleton syntax error; don't let the low-level decnum error bubble up
   1508        status = U_NUMBER_SKELETON_SYNTAX_ERROR;
   1509        return;
   1510    }
   1511 
   1512    // NOTE: The constructor will optimize the decnum for us if possible.
   1513    macros.scale = {0, decnum.orphan()};
   1514 }
   1515 
   1516 void blueprint_helpers::generateScaleOption(int32_t magnitude, const DecNum* arbitrary, UnicodeString& sb,
   1517                                            UErrorCode& status) {
   1518    // Utilize DecimalQuantity/double_conversion to format this for us.
   1519    DecimalQuantity dq;
   1520    if (arbitrary != nullptr) {
   1521        dq.setToDecNum(*arbitrary, status);
   1522        if (U_FAILURE(status)) { return; }
   1523    } else {
   1524        dq.setToInt(1);
   1525    }
   1526    dq.adjustMagnitude(magnitude);
   1527    dq.roundToInfinity();
   1528    sb.append(dq.toPlainString());
   1529 }
   1530 
   1531 
   1532 bool GeneratorHelpers::notation(const MacroProps& macros, UnicodeString& sb, UErrorCode& status) {
   1533    if (macros.notation.fType == Notation::NTN_COMPACT) {
   1534        UNumberCompactStyle style = macros.notation.fUnion.compactStyle;
   1535        if (style == UNumberCompactStyle::UNUM_LONG) {
   1536            sb.append(u"compact-long", -1);
   1537            return true;
   1538        } else if (style == UNumberCompactStyle::UNUM_SHORT) {
   1539            sb.append(u"compact-short", -1);
   1540            return true;
   1541        } else {
   1542            // Compact notation generated from custom data (not supported in skeleton)
   1543            // The other compact notations are literals
   1544            status = U_UNSUPPORTED_ERROR;
   1545            return false;
   1546        }
   1547    } else if (macros.notation.fType == Notation::NTN_SCIENTIFIC) {
   1548        const Notation::ScientificSettings& impl = macros.notation.fUnion.scientific;
   1549        if (impl.fEngineeringInterval == 3) {
   1550            sb.append(u"engineering", -1);
   1551        } else {
   1552            sb.append(u"scientific", -1);
   1553        }
   1554        if (impl.fMinExponentDigits > 1) {
   1555            sb.append(u'/');
   1556            blueprint_helpers::generateExponentWidthOption(impl.fMinExponentDigits, sb, status);
   1557            if (U_FAILURE(status)) {
   1558                return false;
   1559            }
   1560        }
   1561        if (impl.fExponentSignDisplay != UNUM_SIGN_AUTO) {
   1562            sb.append(u'/');
   1563            enum_to_stem_string::signDisplay(impl.fExponentSignDisplay, sb);
   1564        }
   1565        return true;
   1566    } else {
   1567        // Default value is not shown in normalized form
   1568        return false;
   1569    }
   1570 }
   1571 
   1572 bool GeneratorHelpers::unit(const MacroProps& macros, UnicodeString& sb, UErrorCode& status) {
   1573    MeasureUnit unit = macros.unit;
   1574    if (!utils::unitIsBaseUnit(macros.perUnit)) {
   1575        if (utils::unitIsCurrency(macros.unit) || utils::unitIsCurrency(macros.perUnit)) {
   1576            status = U_UNSUPPORTED_ERROR;
   1577            return false;
   1578        }
   1579        unit = unit.product(macros.perUnit.reciprocal(status), status);
   1580    }
   1581 
   1582    if (utils::unitIsCurrency(unit)) {
   1583        sb.append(u"currency/", -1);
   1584        CurrencyUnit currency(unit, status);
   1585        if (U_FAILURE(status)) {
   1586            return false;
   1587        }
   1588        blueprint_helpers::generateCurrencyOption(currency, sb, status);
   1589        return true;
   1590    } else if (utils::unitIsBaseUnit(unit)) {
   1591        // Default value is not shown in normalized form
   1592        return false;
   1593    } else if (utils::unitIsPercent(unit)) {
   1594        sb.append(u"percent", -1);
   1595        return true;
   1596    } else if (utils::unitIsPermille(unit)) {
   1597        sb.append(u"permille", -1);
   1598        return true;
   1599    } else {
   1600        sb.append(u"unit/", -1);
   1601        sb.append(unit.getIdentifier());
   1602        return true;
   1603    }
   1604 }
   1605 
   1606 bool GeneratorHelpers::usage(const MacroProps& macros, UnicodeString& sb, UErrorCode& /* status */) {
   1607    if (macros.usage.isSet()) {
   1608        sb.append(u"usage/", -1);
   1609        sb.append(UnicodeString(macros.usage.fValue, -1, US_INV));
   1610        return true;
   1611    }
   1612    return false;
   1613 }
   1614 
   1615 bool GeneratorHelpers::precision(const MacroProps& macros, UnicodeString& sb, UErrorCode& status) {
   1616    if (macros.precision.fType == Precision::RND_NONE) {
   1617        sb.append(u"precision-unlimited", -1);
   1618    } else if (macros.precision.fType == Precision::RND_FRACTION) {
   1619        const Precision::FractionSignificantSettings& impl = macros.precision.fUnion.fracSig;
   1620        blueprint_helpers::generateFractionStem(impl.fMinFrac, impl.fMaxFrac, sb, status);
   1621    } else if (macros.precision.fType == Precision::RND_SIGNIFICANT) {
   1622        const Precision::FractionSignificantSettings& impl = macros.precision.fUnion.fracSig;
   1623        blueprint_helpers::generateDigitsStem(impl.fMinSig, impl.fMaxSig, sb, status);
   1624    } else if (macros.precision.fType == Precision::RND_FRACTION_SIGNIFICANT) {
   1625        const Precision::FractionSignificantSettings& impl = macros.precision.fUnion.fracSig;
   1626        blueprint_helpers::generateFractionStem(impl.fMinFrac, impl.fMaxFrac, sb, status);
   1627        sb.append(u'/');
   1628        if (impl.fRetain) {
   1629            if (impl.fPriority == UNUM_ROUNDING_PRIORITY_RELAXED) {
   1630                // withMinDigits
   1631                blueprint_helpers::generateDigitsStem(impl.fMaxSig, -1, sb, status);
   1632            } else {
   1633                // withMaxDigits
   1634                blueprint_helpers::generateDigitsStem(1, impl.fMaxSig, sb, status);
   1635            }
   1636        } else {
   1637            blueprint_helpers::generateDigitsStem(impl.fMinSig, impl.fMaxSig, sb, status);
   1638            if (impl.fPriority == UNUM_ROUNDING_PRIORITY_RELAXED) {
   1639                sb.append(u'r');
   1640            } else {
   1641                sb.append(u's');
   1642            }
   1643        }
   1644    } else if (macros.precision.fType == Precision::RND_INCREMENT
   1645            || macros.precision.fType == Precision::RND_INCREMENT_ONE
   1646            || macros.precision.fType == Precision::RND_INCREMENT_FIVE) {
   1647        const Precision::IncrementSettings& impl = macros.precision.fUnion.increment;
   1648        sb.append(u"precision-increment/", -1);
   1649        blueprint_helpers::generateIncrementOption(
   1650                impl.fIncrement,
   1651                impl.fIncrementMagnitude,
   1652                impl.fMinFrac,
   1653                sb,
   1654                status);
   1655    } else if (macros.precision.fType == Precision::RND_CURRENCY) {
   1656        UCurrencyUsage usage = macros.precision.fUnion.currencyUsage;
   1657        if (usage == UCURR_USAGE_STANDARD) {
   1658            sb.append(u"precision-currency-standard", -1);
   1659        } else {
   1660            sb.append(u"precision-currency-cash", -1);
   1661        }
   1662    } else {
   1663        // Bogus or Error
   1664        return false;
   1665    }
   1666 
   1667    if (macros.precision.fTrailingZeroDisplay == UNUM_TRAILING_ZERO_HIDE_IF_WHOLE) {
   1668        sb.append(u"/w", -1);
   1669    }
   1670 
   1671    // NOTE: Always return true for rounding because the default value depends on other options.
   1672    return true;
   1673 }
   1674 
   1675 bool GeneratorHelpers::roundingMode(const MacroProps& macros, UnicodeString& sb, UErrorCode&) {
   1676    if (macros.roundingMode == kDefaultMode) {
   1677        return false; // Default
   1678    }
   1679    enum_to_stem_string::roundingMode(macros.roundingMode, sb);
   1680    return true;
   1681 }
   1682 
   1683 bool GeneratorHelpers::grouping(const MacroProps& macros, UnicodeString& sb, UErrorCode& status) {
   1684    if (macros.grouper.isBogus()) {
   1685        return false; // No value
   1686    } else if (macros.grouper.fStrategy == UNUM_GROUPING_COUNT) {
   1687        status = U_UNSUPPORTED_ERROR;
   1688        return false;
   1689    } else if (macros.grouper.fStrategy == UNUM_GROUPING_AUTO) {
   1690        return false; // Default value
   1691    } else {
   1692        enum_to_stem_string::groupingStrategy(macros.grouper.fStrategy, sb);
   1693        return true;
   1694    }
   1695 }
   1696 
   1697 bool GeneratorHelpers::integerWidth(const MacroProps& macros, UnicodeString& sb, UErrorCode& status) {
   1698    if (macros.integerWidth.fHasError || macros.integerWidth.isBogus() ||
   1699        macros.integerWidth == IntegerWidth::standard()) {
   1700        // Error or Default
   1701        return false;
   1702    }
   1703    const auto& minMaxInt = macros.integerWidth.fUnion.minMaxInt;
   1704    if (minMaxInt.fMinInt == 0 && minMaxInt.fMaxInt == 0) {
   1705        sb.append(u"integer-width-trunc", -1);
   1706        return true;
   1707    }
   1708    sb.append(u"integer-width/", -1);
   1709    blueprint_helpers::generateIntegerWidthOption(
   1710            minMaxInt.fMinInt,
   1711            minMaxInt.fMaxInt,
   1712            sb,
   1713            status);
   1714    return true;
   1715 }
   1716 
   1717 bool GeneratorHelpers::symbols(const MacroProps& macros, UnicodeString& sb, UErrorCode& status) {
   1718    if (macros.symbols.isNumberingSystem()) {
   1719        const NumberingSystem& ns = *macros.symbols.getNumberingSystem();
   1720        if (uprv_strcmp(ns.getName(), "latn") == 0) {
   1721            sb.append(u"latin", -1);
   1722        } else {
   1723            sb.append(u"numbering-system/", -1);
   1724            blueprint_helpers::generateNumberingSystemOption(ns, sb, status);
   1725        }
   1726        return true;
   1727    } else if (macros.symbols.isDecimalFormatSymbols()) {
   1728        status = U_UNSUPPORTED_ERROR;
   1729        return false;
   1730    } else {
   1731        // No custom symbols
   1732        return false;
   1733    }
   1734 }
   1735 
   1736 bool GeneratorHelpers::unitWidth(const MacroProps& macros, UnicodeString& sb, UErrorCode&) {
   1737    if (macros.unitWidth == UNUM_UNIT_WIDTH_SHORT || macros.unitWidth == UNUM_UNIT_WIDTH_COUNT) {
   1738        return false; // Default or Bogus
   1739    }
   1740    enum_to_stem_string::unitWidth(macros.unitWidth, sb);
   1741    return true;
   1742 }
   1743 
   1744 bool GeneratorHelpers::sign(const MacroProps& macros, UnicodeString& sb, UErrorCode&) {
   1745    if (macros.sign == UNUM_SIGN_AUTO || macros.sign == UNUM_SIGN_COUNT) {
   1746        return false; // Default or Bogus
   1747    }
   1748    enum_to_stem_string::signDisplay(macros.sign, sb);
   1749    return true;
   1750 }
   1751 
   1752 bool GeneratorHelpers::decimal(const MacroProps& macros, UnicodeString& sb, UErrorCode&) {
   1753    if (macros.decimal == UNUM_DECIMAL_SEPARATOR_AUTO || macros.decimal == UNUM_DECIMAL_SEPARATOR_COUNT) {
   1754        return false; // Default or Bogus
   1755    }
   1756    enum_to_stem_string::decimalSeparatorDisplay(macros.decimal, sb);
   1757    return true;
   1758 }
   1759 
   1760 bool GeneratorHelpers::scale(const MacroProps& macros, UnicodeString& sb, UErrorCode& status) {
   1761    if (!macros.scale.isValid()) {
   1762        return false; // Default or Bogus
   1763    }
   1764    sb.append(u"scale/", -1);
   1765    blueprint_helpers::generateScaleOption(
   1766            macros.scale.fMagnitude,
   1767            macros.scale.fArbitrary,
   1768            sb,
   1769            status);
   1770    return true;
   1771 }
   1772 
   1773 
   1774 // Definitions of public API methods (put here for dependency disentanglement)
   1775 
   1776 #if (U_PF_WINDOWS <= U_PLATFORM && U_PLATFORM <= U_PF_CYGWIN) && defined(_MSC_VER)
   1777 // Ignore MSVC warning 4661. This is generated for NumberFormatterSettings<>::toSkeleton() as this method
   1778 // is defined elsewhere (in number_skeletons.cpp). The compiler is warning that the explicit template instantiation
   1779 // inside this single translation unit (CPP file) is incomplete, and thus it isn't sure if the template class is
   1780 // fully defined. However, since each translation unit explicitly instantiates all the necessary template classes,
   1781 // they will all be passed to the linker, and the linker will still find and export all the class members.
   1782 #pragma warning(push)
   1783 #pragma warning(disable: 4661)
   1784 #endif
   1785 
   1786 template<typename Derived>
   1787 UnicodeString NumberFormatterSettings<Derived>::toSkeleton(UErrorCode& status) const {
   1788    if (U_FAILURE(status)) {
   1789        return ICU_Utility::makeBogusString();
   1790    }
   1791    if (fMacros.copyErrorTo(status)) {
   1792        return ICU_Utility::makeBogusString();
   1793    }
   1794    return skeleton::generate(fMacros, status);
   1795 }
   1796 
   1797 // Declare all classes that implement NumberFormatterSettings
   1798 // See https://stackoverflow.com/a/495056/1407170
   1799 template
   1800 class icu::number::NumberFormatterSettings<icu::number::UnlocalizedNumberFormatter>;
   1801 template
   1802 class icu::number::NumberFormatterSettings<icu::number::LocalizedNumberFormatter>;
   1803 
   1804 UnlocalizedNumberFormatter
   1805 NumberFormatter::forSkeleton(const UnicodeString& skeleton, UErrorCode& status) {
   1806    return skeleton::create(skeleton, nullptr, status);
   1807 }
   1808 
   1809 UnlocalizedNumberFormatter
   1810 NumberFormatter::forSkeleton(const UnicodeString& skeleton, UParseError& perror, UErrorCode& status) {
   1811    return skeleton::create(skeleton, &perror, status);
   1812 }
   1813 
   1814 #if (U_PF_WINDOWS <= U_PLATFORM && U_PLATFORM <= U_PF_CYGWIN) && defined(_MSC_VER)
   1815 // Warning 4661.
   1816 #pragma warning(pop)
   1817 #endif
   1818 
   1819 #endif /* #if !UCONFIG_NO_FORMATTING */