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