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-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 
appendMultiple(UnicodeString & sb,UChar32 cp,int32_t count)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 
notation(skeleton::StemEnum stem)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 
unit(skeleton::StemEnum stem)184 MeasureUnit stem_to_object::unit(skeleton::StemEnum stem) {
185     switch (stem) {
186         case STEM_BASE_UNIT:
187             return MeasureUnit();
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 
precision(skeleton::StemEnum stem)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 
roundingMode(skeleton::StemEnum stem)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 
groupingStrategy(skeleton::StemEnum stem)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 
unitWidth(skeleton::StemEnum stem)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 
signDisplay(skeleton::StemEnum stem)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 
decimalSeparatorDisplay(skeleton::StemEnum stem)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 
roundingMode(UNumberFormatRoundingMode value,UnicodeString & sb)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 
groupingStrategy(UNumberGroupingStrategy value,UnicodeString & sb)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 
unitWidth(UNumberUnitWidth value,UnicodeString & sb)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 
signDisplay(UNumberSignDisplay value,UnicodeString & sb)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
decimalSeparatorDisplay(UNumberDecimalSeparatorDisplay value,UnicodeString & sb)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 
create(const UnicodeString & skeletonString,UParseError * perror,UErrorCode & status)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 
generate(const MacroProps & macros,UErrorCode & status)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 
parseSkeleton(const UnicodeString & skeletonString,int32_t & errOffset,UErrorCode & status)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
parseStem(const StringSegment & segment,const UCharsTrie & stemTrie,SeenMacroProps & seen,MacroProps & macros,UErrorCode & status)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 
parseOption(ParseState stem,const StringSegment & segment,MacroProps & macros,UErrorCode & status)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 
generateSkeleton(const MacroProps & macros,UnicodeString & sb,UErrorCode & status)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 
parseExponentWidthOption(const StringSegment & segment,MacroProps & macros,UErrorCode &)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
generateExponentWidthOption(int32_t minExponentDigits,UnicodeString & sb,UErrorCode &)995 blueprint_helpers::generateExponentWidthOption(int32_t minExponentDigits, UnicodeString& sb, UErrorCode&) {
996     sb.append(kWildcardChar);
997     appendMultiple(sb, u'e', minExponentDigits);
998 }
999 
1000 bool
parseExponentSignOption(const StringSegment & segment,MacroProps & macros,UErrorCode &)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 
parseCurrencyOption(const StringSegment & segment,MacroProps & macros,UErrorCode & status)1018 void blueprint_helpers::parseCurrencyOption(const StringSegment& segment, MacroProps& macros,
1019                                             UErrorCode& status) {
1020     // Unlike ICU4J, have to check length manually because ICU4C CurrencyUnit does not check it for us
1021     if (segment.length() != 3) {
1022         status = U_NUMBER_SKELETON_SYNTAX_ERROR;
1023         return;
1024     }
1025     const UChar* currencyCode = segment.toTempUnicodeString().getBuffer();
1026     UErrorCode localStatus = U_ZERO_ERROR;
1027     CurrencyUnit currency(currencyCode, localStatus);
1028     if (U_FAILURE(localStatus)) {
1029         // Not 3 ascii chars
1030         // throw new SkeletonSyntaxException("Invalid currency", segment);
1031         status = U_NUMBER_SKELETON_SYNTAX_ERROR;
1032         return;
1033     }
1034     // Slicing is OK
1035     macros.unit = currency; // NOLINT
1036 }
1037 
1038 void
generateCurrencyOption(const CurrencyUnit & currency,UnicodeString & sb,UErrorCode &)1039 blueprint_helpers::generateCurrencyOption(const CurrencyUnit& currency, UnicodeString& sb, UErrorCode&) {
1040     sb.append(currency.getISOCurrency(), -1);
1041 }
1042 
parseMeasureUnitOption(const StringSegment & segment,MacroProps & macros,UErrorCode & status)1043 void blueprint_helpers::parseMeasureUnitOption(const StringSegment& segment, MacroProps& macros,
1044                                                UErrorCode& status) {
1045     U_ASSERT(U_SUCCESS(status));
1046     const UnicodeString stemString = segment.toTempUnicodeString();
1047 
1048     // NOTE: The category (type) of the unit is guaranteed to be a valid subtag (alphanumeric)
1049     // http://unicode.org/reports/tr35/#Validity_Data
1050     int firstHyphen = 0;
1051     while (firstHyphen < stemString.length() && stemString.charAt(firstHyphen) != '-') {
1052         firstHyphen++;
1053     }
1054     if (firstHyphen == stemString.length()) {
1055         // throw new SkeletonSyntaxException("Invalid measure unit option", segment);
1056         status = U_NUMBER_SKELETON_SYNTAX_ERROR;
1057         return;
1058     }
1059 
1060     // Need to do char <-> UChar conversion...
1061     CharString type;
1062     SKELETON_UCHAR_TO_CHAR(type, stemString, 0, firstHyphen, status);
1063     CharString subType;
1064     SKELETON_UCHAR_TO_CHAR(subType, stemString, firstHyphen + 1, stemString.length(), status);
1065 
1066     // Note: the largest type as of this writing (Aug 2020) is "volume", which has 33 units.
1067     static constexpr int32_t CAPACITY = 40;
1068     MeasureUnit units[CAPACITY];
1069     UErrorCode localStatus = U_ZERO_ERROR;
1070     int32_t numUnits = MeasureUnit::getAvailable(type.data(), units, CAPACITY, localStatus);
1071     if (U_FAILURE(localStatus)) {
1072         // More than 30 units in this type?
1073         status = U_INTERNAL_PROGRAM_ERROR;
1074         return;
1075     }
1076     for (int32_t i = 0; i < numUnits; i++) {
1077         auto& unit = units[i];
1078         if (uprv_strcmp(subType.data(), unit.getSubtype()) == 0) {
1079             macros.unit = unit;
1080             return;
1081         }
1082     }
1083 
1084     // throw new SkeletonSyntaxException("Unknown measure unit", segment);
1085     status = U_NUMBER_SKELETON_SYNTAX_ERROR;
1086 }
1087 
parseMeasurePerUnitOption(const StringSegment & segment,MacroProps & macros,UErrorCode & status)1088 void blueprint_helpers::parseMeasurePerUnitOption(const StringSegment& segment, MacroProps& macros,
1089                                                   UErrorCode& status) {
1090     // A little bit of a hack: save the current unit (numerator), call the main measure unit
1091     // parsing code, put back the numerator unit, and put the new unit into per-unit.
1092     MeasureUnit numerator = macros.unit;
1093     parseMeasureUnitOption(segment, macros, status);
1094     if (U_FAILURE(status)) { return; }
1095     macros.perUnit = macros.unit;
1096     macros.unit = numerator;
1097 }
1098 
parseIdentifierUnitOption(const StringSegment & segment,MacroProps & macros,UErrorCode & status)1099 void blueprint_helpers::parseIdentifierUnitOption(const StringSegment& segment, MacroProps& macros,
1100                                                   UErrorCode& status) {
1101     // Need to do char <-> UChar conversion...
1102     U_ASSERT(U_SUCCESS(status));
1103     CharString buffer;
1104     SKELETON_UCHAR_TO_CHAR(buffer, segment.toTempUnicodeString(), 0, segment.length(), status);
1105 
1106     ErrorCode internalStatus;
1107     macros.unit = MeasureUnit::forIdentifier(buffer.toStringPiece(), internalStatus);
1108     if (internalStatus.isFailure()) {
1109         // throw new SkeletonSyntaxException("Invalid core unit identifier", segment, e);
1110         status = U_NUMBER_SKELETON_SYNTAX_ERROR;
1111         return;
1112     }
1113 }
1114 
parseUnitUsageOption(const StringSegment & segment,MacroProps & macros,UErrorCode & status)1115 void blueprint_helpers::parseUnitUsageOption(const StringSegment &segment, MacroProps &macros,
1116                                              UErrorCode &status) {
1117     // Need to do char <-> UChar conversion...
1118     U_ASSERT(U_SUCCESS(status));
1119     CharString buffer;
1120     SKELETON_UCHAR_TO_CHAR(buffer, segment.toTempUnicodeString(), 0, segment.length(), status);
1121     macros.usage.set(buffer.toStringPiece());
1122     // We do not do any validation of the usage string: it depends on the
1123     // unitPreferenceData in the units resources.
1124 }
1125 
parseFractionStem(const StringSegment & segment,MacroProps & macros,UErrorCode & status)1126 void blueprint_helpers::parseFractionStem(const StringSegment& segment, MacroProps& macros,
1127                                           UErrorCode& status) {
1128     U_ASSERT(segment.charAt(0) == u'.');
1129     int32_t offset = 1;
1130     int32_t minFrac = 0;
1131     int32_t maxFrac;
1132     for (; offset < segment.length(); offset++) {
1133         if (segment.charAt(offset) == u'0') {
1134             minFrac++;
1135         } else {
1136             break;
1137         }
1138     }
1139     if (offset < segment.length()) {
1140         if (isWildcardChar(segment.charAt(offset))) {
1141             maxFrac = -1;
1142             offset++;
1143         } else {
1144             maxFrac = minFrac;
1145             for (; offset < segment.length(); offset++) {
1146                 if (segment.charAt(offset) == u'#') {
1147                     maxFrac++;
1148                 } else {
1149                     break;
1150                 }
1151             }
1152         }
1153     } else {
1154         maxFrac = minFrac;
1155     }
1156     if (offset < segment.length()) {
1157         // throw new SkeletonSyntaxException("Invalid fraction stem", segment);
1158         status = U_NUMBER_SKELETON_SYNTAX_ERROR;
1159         return;
1160     }
1161     // Use the public APIs to enforce bounds checking
1162     if (maxFrac == -1) {
1163         if (minFrac == 0) {
1164             macros.precision = Precision::unlimited();
1165         } else {
1166             macros.precision = Precision::minFraction(minFrac);
1167         }
1168     } else {
1169         macros.precision = Precision::minMaxFraction(minFrac, maxFrac);
1170     }
1171 }
1172 
1173 void
generateFractionStem(int32_t minFrac,int32_t maxFrac,UnicodeString & sb,UErrorCode &)1174 blueprint_helpers::generateFractionStem(int32_t minFrac, int32_t maxFrac, UnicodeString& sb, UErrorCode&) {
1175     if (minFrac == 0 && maxFrac == 0) {
1176         sb.append(u"precision-integer", -1);
1177         return;
1178     }
1179     sb.append(u'.');
1180     appendMultiple(sb, u'0', minFrac);
1181     if (maxFrac == -1) {
1182         sb.append(kWildcardChar);
1183     } else {
1184         appendMultiple(sb, u'#', maxFrac - minFrac);
1185     }
1186 }
1187 
1188 void
parseDigitsStem(const StringSegment & segment,MacroProps & macros,UErrorCode & status)1189 blueprint_helpers::parseDigitsStem(const StringSegment& segment, MacroProps& macros, UErrorCode& status) {
1190     U_ASSERT(segment.charAt(0) == u'@');
1191     int32_t offset = 0;
1192     int32_t minSig = 0;
1193     int32_t maxSig;
1194     for (; offset < segment.length(); offset++) {
1195         if (segment.charAt(offset) == u'@') {
1196             minSig++;
1197         } else {
1198             break;
1199         }
1200     }
1201     if (offset < segment.length()) {
1202         if (isWildcardChar(segment.charAt(offset))) {
1203             maxSig = -1;
1204             offset++;
1205         } else {
1206             maxSig = minSig;
1207             for (; offset < segment.length(); offset++) {
1208                 if (segment.charAt(offset) == u'#') {
1209                     maxSig++;
1210                 } else {
1211                     break;
1212                 }
1213             }
1214         }
1215     } else {
1216         maxSig = minSig;
1217     }
1218     if (offset < segment.length()) {
1219         // throw new SkeletonSyntaxException("Invalid significant digits stem", segment);
1220         status = U_NUMBER_SKELETON_SYNTAX_ERROR;
1221         return;
1222     }
1223     // Use the public APIs to enforce bounds checking
1224     if (maxSig == -1) {
1225         macros.precision = Precision::minSignificantDigits(minSig);
1226     } else {
1227         macros.precision = Precision::minMaxSignificantDigits(minSig, maxSig);
1228     }
1229 }
1230 
1231 void
generateDigitsStem(int32_t minSig,int32_t maxSig,UnicodeString & sb,UErrorCode &)1232 blueprint_helpers::generateDigitsStem(int32_t minSig, int32_t maxSig, UnicodeString& sb, UErrorCode&) {
1233     appendMultiple(sb, u'@', minSig);
1234     if (maxSig == -1) {
1235         sb.append(kWildcardChar);
1236     } else {
1237         appendMultiple(sb, u'#', maxSig - minSig);
1238     }
1239 }
1240 
parseScientificStem(const StringSegment & segment,MacroProps & macros,UErrorCode & status)1241 void blueprint_helpers::parseScientificStem(const StringSegment& segment, MacroProps& macros, UErrorCode& status) {
1242     U_ASSERT(segment.charAt(0) == u'E');
1243     {
1244         int32_t offset = 1;
1245         if (segment.length() == offset) {
1246             goto fail;
1247         }
1248         bool isEngineering = false;
1249         if (segment.charAt(offset) == u'E') {
1250             isEngineering = true;
1251             offset++;
1252             if (segment.length() == offset) {
1253                 goto fail;
1254             }
1255         }
1256         UNumberSignDisplay signDisplay = UNUM_SIGN_AUTO;
1257         if (segment.charAt(offset) == u'+') {
1258             offset++;
1259             if (segment.length() == offset) {
1260                 goto fail;
1261             }
1262             if (segment.charAt(offset) == u'!') {
1263                 signDisplay = UNUM_SIGN_ALWAYS;
1264             } else if (segment.charAt(offset) == u'?') {
1265                 signDisplay = UNUM_SIGN_EXCEPT_ZERO;
1266             } else {
1267                 // NOTE: Other sign displays are not included because they aren't useful in this context
1268                 goto fail;
1269             }
1270             offset++;
1271             if (segment.length() == offset) {
1272                 goto fail;
1273             }
1274         }
1275         int32_t minDigits = 0;
1276         for (; offset < segment.length(); offset++) {
1277             if (segment.charAt(offset) != u'0') {
1278                 goto fail;
1279             }
1280             minDigits++;
1281         }
1282         macros.notation = (isEngineering ? Notation::engineering() : Notation::scientific())
1283             .withExponentSignDisplay(signDisplay)
1284             .withMinExponentDigits(minDigits);
1285         return;
1286     }
1287     fail: void();
1288     // throw new SkeletonSyntaxException("Invalid scientific stem", segment);
1289     status = U_NUMBER_SKELETON_SYNTAX_ERROR;
1290     return;
1291 }
1292 
parseIntegerStem(const StringSegment & segment,MacroProps & macros,UErrorCode & status)1293 void blueprint_helpers::parseIntegerStem(const StringSegment& segment, MacroProps& macros, UErrorCode& status) {
1294     U_ASSERT(segment.charAt(0) == u'0');
1295     int32_t offset = 1;
1296     for (; offset < segment.length(); offset++) {
1297         if (segment.charAt(offset) != u'0') {
1298             offset--;
1299             break;
1300         }
1301     }
1302     if (offset < segment.length()) {
1303         // throw new SkeletonSyntaxException("Invalid integer stem", segment);
1304         status = U_NUMBER_SKELETON_SYNTAX_ERROR;
1305         return;
1306     }
1307     macros.integerWidth = IntegerWidth::zeroFillTo(offset);
1308     return;
1309 }
1310 
parseFracSigOption(const StringSegment & segment,MacroProps & macros,UErrorCode & status)1311 bool blueprint_helpers::parseFracSigOption(const StringSegment& segment, MacroProps& macros,
1312                                            UErrorCode& status) {
1313     if (segment.charAt(0) != u'@') {
1314         return false;
1315     }
1316     int offset = 0;
1317     int minSig = 0;
1318     int maxSig;
1319     for (; offset < segment.length(); offset++) {
1320         if (segment.charAt(offset) == u'@') {
1321             minSig++;
1322         } else {
1323             break;
1324         }
1325     }
1326     if (offset < segment.length()) {
1327         if (isWildcardChar(segment.charAt(offset))) {
1328             // @+, @@+, @@@+
1329             maxSig = -1;
1330             offset++;
1331         } else {
1332             // @#, @##, @###
1333             // @@#, @@##, @@@#
1334             maxSig = minSig;
1335             for (; offset < segment.length(); offset++) {
1336                 if (segment.charAt(offset) == u'#') {
1337                     maxSig++;
1338                 } else {
1339                     break;
1340                 }
1341             }
1342         }
1343     } else {
1344         // @, @@, @@@
1345         maxSig = minSig;
1346     }
1347     UNumberRoundingPriority priority;
1348     if (offset < segment.length()) {
1349         if (maxSig == -1) {
1350             // The wildcard character is not allowed with the priority annotation
1351             status = U_NUMBER_SKELETON_SYNTAX_ERROR;
1352             return false;
1353         }
1354         if (segment.codePointAt(offset) == u'r') {
1355             priority = UNUM_ROUNDING_PRIORITY_RELAXED;
1356             offset++;
1357         } else if (segment.codePointAt(offset) == u's') {
1358             priority = UNUM_ROUNDING_PRIORITY_STRICT;
1359             offset++;
1360         } else {
1361             // Invalid digits option for fraction rounder
1362             status = U_NUMBER_SKELETON_SYNTAX_ERROR;
1363             return false;
1364         }
1365         if (offset < segment.length()) {
1366             // Invalid digits option for fraction rounder
1367             status = U_NUMBER_SKELETON_SYNTAX_ERROR;
1368             return false;
1369         }
1370     } else if (maxSig == -1) {
1371         // withMinDigits
1372         maxSig = minSig;
1373         minSig = 1;
1374         priority = UNUM_ROUNDING_PRIORITY_RELAXED;
1375     } else if (minSig == 1) {
1376         // withMaxDigits
1377         priority = UNUM_ROUNDING_PRIORITY_STRICT;
1378     } else {
1379         // Digits options with both min and max sig require the priority option
1380         status = U_NUMBER_SKELETON_SYNTAX_ERROR;
1381         return false;
1382     }
1383 
1384     auto& oldPrecision = static_cast<const FractionPrecision&>(macros.precision);
1385     macros.precision = oldPrecision.withSignificantDigits(minSig, maxSig, priority);
1386     return true;
1387 }
1388 
parseTrailingZeroOption(const StringSegment & segment,MacroProps & macros,UErrorCode &)1389 bool blueprint_helpers::parseTrailingZeroOption(const StringSegment& segment, MacroProps& macros, UErrorCode&) {
1390     if (segment == u"w") {
1391         macros.precision = macros.precision.trailingZeroDisplay(UNUM_TRAILING_ZERO_HIDE_IF_WHOLE);
1392         return true;
1393     }
1394     return false;
1395 }
1396 
parseIncrementOption(const StringSegment & segment,MacroProps & macros,UErrorCode & status)1397 void blueprint_helpers::parseIncrementOption(const StringSegment &segment, MacroProps &macros,
1398                                              UErrorCode &status) {
1399     number::impl::parseIncrementOption(segment, macros.precision, status);
1400 }
1401 
generateIncrementOption(double increment,int32_t minFrac,UnicodeString & sb,UErrorCode &)1402 void blueprint_helpers::generateIncrementOption(double increment, int32_t minFrac, UnicodeString& sb,
1403                                                 UErrorCode&) {
1404     // Utilize DecimalQuantity/double_conversion to format this for us.
1405     DecimalQuantity dq;
1406     dq.setToDouble(increment);
1407     dq.roundToInfinity();
1408     dq.setMinFraction(minFrac);
1409     sb.append(dq.toPlainString());
1410 }
1411 
parseIntegerWidthOption(const StringSegment & segment,MacroProps & macros,UErrorCode & status)1412 void blueprint_helpers::parseIntegerWidthOption(const StringSegment& segment, MacroProps& macros,
1413                                                 UErrorCode& status) {
1414     int32_t offset = 0;
1415     int32_t minInt = 0;
1416     int32_t maxInt;
1417     if (isWildcardChar(segment.charAt(0))) {
1418         maxInt = -1;
1419         offset++;
1420     } else {
1421         maxInt = 0;
1422     }
1423     for (; offset < segment.length(); offset++) {
1424         if (maxInt != -1 && segment.charAt(offset) == u'#') {
1425             maxInt++;
1426         } else {
1427             break;
1428         }
1429     }
1430     if (offset < segment.length()) {
1431         for (; offset < segment.length(); offset++) {
1432             if (segment.charAt(offset) == u'0') {
1433                 minInt++;
1434             } else {
1435                 break;
1436             }
1437         }
1438     }
1439     if (maxInt != -1) {
1440         maxInt += minInt;
1441     }
1442     if (offset < segment.length()) {
1443         // throw new SkeletonSyntaxException("Invalid integer width stem", segment);
1444         status = U_NUMBER_SKELETON_SYNTAX_ERROR;
1445         return;
1446     }
1447     // Use the public APIs to enforce bounds checking
1448     if (maxInt == -1) {
1449         macros.integerWidth = IntegerWidth::zeroFillTo(minInt);
1450     } else {
1451         macros.integerWidth = IntegerWidth::zeroFillTo(minInt).truncateAt(maxInt);
1452     }
1453 }
1454 
generateIntegerWidthOption(int32_t minInt,int32_t maxInt,UnicodeString & sb,UErrorCode &)1455 void blueprint_helpers::generateIntegerWidthOption(int32_t minInt, int32_t maxInt, UnicodeString& sb,
1456                                                    UErrorCode&) {
1457     if (maxInt == -1) {
1458         sb.append(kWildcardChar);
1459     } else {
1460         appendMultiple(sb, u'#', maxInt - minInt);
1461     }
1462     appendMultiple(sb, u'0', minInt);
1463 }
1464 
parseNumberingSystemOption(const StringSegment & segment,MacroProps & macros,UErrorCode & status)1465 void blueprint_helpers::parseNumberingSystemOption(const StringSegment& segment, MacroProps& macros,
1466                                                    UErrorCode& status) {
1467     // Need to do char <-> UChar conversion...
1468     U_ASSERT(U_SUCCESS(status));
1469     CharString buffer;
1470     SKELETON_UCHAR_TO_CHAR(buffer, segment.toTempUnicodeString(), 0, segment.length(), status);
1471 
1472     NumberingSystem* ns = NumberingSystem::createInstanceByName(buffer.data(), status);
1473     if (ns == nullptr || U_FAILURE(status)) {
1474         // This is a skeleton syntax error; don't bubble up the low-level NumberingSystem error
1475         // throw new SkeletonSyntaxException("Unknown numbering system", segment);
1476         status = U_NUMBER_SKELETON_SYNTAX_ERROR;
1477         return;
1478     }
1479     macros.symbols.setTo(ns);
1480 }
1481 
generateNumberingSystemOption(const NumberingSystem & ns,UnicodeString & sb,UErrorCode &)1482 void blueprint_helpers::generateNumberingSystemOption(const NumberingSystem& ns, UnicodeString& sb,
1483                                                       UErrorCode&) {
1484     // Need to do char <-> UChar conversion...
1485     sb.append(UnicodeString(ns.getName(), -1, US_INV));
1486 }
1487 
parseScaleOption(const StringSegment & segment,MacroProps & macros,UErrorCode & status)1488 void blueprint_helpers::parseScaleOption(const StringSegment& segment, MacroProps& macros,
1489                                               UErrorCode& status) {
1490     // Need to do char <-> UChar conversion...
1491     U_ASSERT(U_SUCCESS(status));
1492     CharString buffer;
1493     SKELETON_UCHAR_TO_CHAR(buffer, segment.toTempUnicodeString(), 0, segment.length(), status);
1494 
1495     LocalPointer<DecNum> decnum(new DecNum(), status);
1496     if (U_FAILURE(status)) { return; }
1497     decnum->setTo({buffer.data(), buffer.length()}, status);
1498     if (U_FAILURE(status) || decnum->isSpecial()) {
1499         // This is a skeleton syntax error; don't let the low-level decnum error bubble up
1500         status = U_NUMBER_SKELETON_SYNTAX_ERROR;
1501         return;
1502     }
1503 
1504     // NOTE: The constructor will optimize the decnum for us if possible.
1505     macros.scale = {0, decnum.orphan()};
1506 }
1507 
generateScaleOption(int32_t magnitude,const DecNum * arbitrary,UnicodeString & sb,UErrorCode & status)1508 void blueprint_helpers::generateScaleOption(int32_t magnitude, const DecNum* arbitrary, UnicodeString& sb,
1509                                             UErrorCode& status) {
1510     // Utilize DecimalQuantity/double_conversion to format this for us.
1511     DecimalQuantity dq;
1512     if (arbitrary != nullptr) {
1513         dq.setToDecNum(*arbitrary, status);
1514         if (U_FAILURE(status)) { return; }
1515     } else {
1516         dq.setToInt(1);
1517     }
1518     dq.adjustMagnitude(magnitude);
1519     dq.roundToInfinity();
1520     sb.append(dq.toPlainString());
1521 }
1522 
1523 
notation(const MacroProps & macros,UnicodeString & sb,UErrorCode & status)1524 bool GeneratorHelpers::notation(const MacroProps& macros, UnicodeString& sb, UErrorCode& status) {
1525     if (macros.notation.fType == Notation::NTN_COMPACT) {
1526         UNumberCompactStyle style = macros.notation.fUnion.compactStyle;
1527         if (style == UNumberCompactStyle::UNUM_LONG) {
1528             sb.append(u"compact-long", -1);
1529             return true;
1530         } else if (style == UNumberCompactStyle::UNUM_SHORT) {
1531             sb.append(u"compact-short", -1);
1532             return true;
1533         } else {
1534             // Compact notation generated from custom data (not supported in skeleton)
1535             // The other compact notations are literals
1536             status = U_UNSUPPORTED_ERROR;
1537             return false;
1538         }
1539     } else if (macros.notation.fType == Notation::NTN_SCIENTIFIC) {
1540         const Notation::ScientificSettings& impl = macros.notation.fUnion.scientific;
1541         if (impl.fEngineeringInterval == 3) {
1542             sb.append(u"engineering", -1);
1543         } else {
1544             sb.append(u"scientific", -1);
1545         }
1546         if (impl.fMinExponentDigits > 1) {
1547             sb.append(u'/');
1548             blueprint_helpers::generateExponentWidthOption(impl.fMinExponentDigits, sb, status);
1549             if (U_FAILURE(status)) {
1550                 return false;
1551             }
1552         }
1553         if (impl.fExponentSignDisplay != UNUM_SIGN_AUTO) {
1554             sb.append(u'/');
1555             enum_to_stem_string::signDisplay(impl.fExponentSignDisplay, sb);
1556         }
1557         return true;
1558     } else {
1559         // Default value is not shown in normalized form
1560         return false;
1561     }
1562 }
1563 
unit(const MacroProps & macros,UnicodeString & sb,UErrorCode & status)1564 bool GeneratorHelpers::unit(const MacroProps& macros, UnicodeString& sb, UErrorCode& status) {
1565     MeasureUnit unit = macros.unit;
1566     if (!utils::unitIsBaseUnit(macros.perUnit)) {
1567         if (utils::unitIsCurrency(macros.unit) || utils::unitIsCurrency(macros.perUnit)) {
1568             status = U_UNSUPPORTED_ERROR;
1569             return false;
1570         }
1571         unit = unit.product(macros.perUnit.reciprocal(status), status);
1572     }
1573 
1574     if (utils::unitIsCurrency(unit)) {
1575         sb.append(u"currency/", -1);
1576         CurrencyUnit currency(unit, status);
1577         if (U_FAILURE(status)) {
1578             return false;
1579         }
1580         blueprint_helpers::generateCurrencyOption(currency, sb, status);
1581         return true;
1582     } else if (utils::unitIsBaseUnit(unit)) {
1583         // Default value is not shown in normalized form
1584         return false;
1585     } else if (utils::unitIsPercent(unit)) {
1586         sb.append(u"percent", -1);
1587         return true;
1588     } else if (utils::unitIsPermille(unit)) {
1589         sb.append(u"permille", -1);
1590         return true;
1591     } else {
1592         sb.append(u"unit/", -1);
1593         sb.append(unit.getIdentifier());
1594         return true;
1595     }
1596 }
1597 
usage(const MacroProps & macros,UnicodeString & sb,UErrorCode &)1598 bool GeneratorHelpers::usage(const MacroProps& macros, UnicodeString& sb, UErrorCode& /* status */) {
1599     if (macros.usage.isSet()) {
1600         sb.append(u"usage/", -1);
1601         sb.append(UnicodeString(macros.usage.fValue, -1, US_INV));
1602         return true;
1603     }
1604     return false;
1605 }
1606 
precision(const MacroProps & macros,UnicodeString & sb,UErrorCode & status)1607 bool GeneratorHelpers::precision(const MacroProps& macros, UnicodeString& sb, UErrorCode& status) {
1608     if (macros.precision.fType == Precision::RND_NONE) {
1609         sb.append(u"precision-unlimited", -1);
1610     } else if (macros.precision.fType == Precision::RND_FRACTION) {
1611         const Precision::FractionSignificantSettings& impl = macros.precision.fUnion.fracSig;
1612         blueprint_helpers::generateFractionStem(impl.fMinFrac, impl.fMaxFrac, sb, status);
1613     } else if (macros.precision.fType == Precision::RND_SIGNIFICANT) {
1614         const Precision::FractionSignificantSettings& impl = macros.precision.fUnion.fracSig;
1615         blueprint_helpers::generateDigitsStem(impl.fMinSig, impl.fMaxSig, sb, status);
1616     } else if (macros.precision.fType == Precision::RND_FRACTION_SIGNIFICANT) {
1617         const Precision::FractionSignificantSettings& impl = macros.precision.fUnion.fracSig;
1618         blueprint_helpers::generateFractionStem(impl.fMinFrac, impl.fMaxFrac, sb, status);
1619         sb.append(u'/');
1620         blueprint_helpers::generateDigitsStem(impl.fMinSig, impl.fMaxSig, sb, status);
1621         if (impl.fPriority == UNUM_ROUNDING_PRIORITY_RELAXED) {
1622             sb.append(u'r');
1623         } else {
1624             sb.append(u's');
1625         }
1626     } else if (macros.precision.fType == Precision::RND_INCREMENT
1627             || macros.precision.fType == Precision::RND_INCREMENT_ONE
1628             || macros.precision.fType == Precision::RND_INCREMENT_FIVE) {
1629         const Precision::IncrementSettings& impl = macros.precision.fUnion.increment;
1630         sb.append(u"precision-increment/", -1);
1631         blueprint_helpers::generateIncrementOption(
1632                 impl.fIncrement,
1633                 impl.fMinFrac,
1634                 sb,
1635                 status);
1636     } else if (macros.precision.fType == Precision::RND_CURRENCY) {
1637         UCurrencyUsage usage = macros.precision.fUnion.currencyUsage;
1638         if (usage == UCURR_USAGE_STANDARD) {
1639             sb.append(u"precision-currency-standard", -1);
1640         } else {
1641             sb.append(u"precision-currency-cash", -1);
1642         }
1643     } else {
1644         // Bogus or Error
1645         return false;
1646     }
1647 
1648     if (macros.precision.fTrailingZeroDisplay == UNUM_TRAILING_ZERO_HIDE_IF_WHOLE) {
1649         sb.append(u"/w", -1);
1650     }
1651 
1652     // NOTE: Always return true for rounding because the default value depends on other options.
1653     return true;
1654 }
1655 
roundingMode(const MacroProps & macros,UnicodeString & sb,UErrorCode &)1656 bool GeneratorHelpers::roundingMode(const MacroProps& macros, UnicodeString& sb, UErrorCode&) {
1657     if (macros.roundingMode == kDefaultMode) {
1658         return false; // Default
1659     }
1660     enum_to_stem_string::roundingMode(macros.roundingMode, sb);
1661     return true;
1662 }
1663 
grouping(const MacroProps & macros,UnicodeString & sb,UErrorCode & status)1664 bool GeneratorHelpers::grouping(const MacroProps& macros, UnicodeString& sb, UErrorCode& status) {
1665     if (macros.grouper.isBogus()) {
1666         return false; // No value
1667     } else if (macros.grouper.fStrategy == UNUM_GROUPING_COUNT) {
1668         status = U_UNSUPPORTED_ERROR;
1669         return false;
1670     } else if (macros.grouper.fStrategy == UNUM_GROUPING_AUTO) {
1671         return false; // Default value
1672     } else {
1673         enum_to_stem_string::groupingStrategy(macros.grouper.fStrategy, sb);
1674         return true;
1675     }
1676 }
1677 
integerWidth(const MacroProps & macros,UnicodeString & sb,UErrorCode & status)1678 bool GeneratorHelpers::integerWidth(const MacroProps& macros, UnicodeString& sb, UErrorCode& status) {
1679     if (macros.integerWidth.fHasError || macros.integerWidth.isBogus() ||
1680         macros.integerWidth == IntegerWidth::standard()) {
1681         // Error or Default
1682         return false;
1683     }
1684     const auto& minMaxInt = macros.integerWidth.fUnion.minMaxInt;
1685     if (minMaxInt.fMinInt == 0 && minMaxInt.fMaxInt == 0) {
1686         sb.append(u"integer-width-trunc", -1);
1687         return true;
1688     }
1689     sb.append(u"integer-width/", -1);
1690     blueprint_helpers::generateIntegerWidthOption(
1691             minMaxInt.fMinInt,
1692             minMaxInt.fMaxInt,
1693             sb,
1694             status);
1695     return true;
1696 }
1697 
symbols(const MacroProps & macros,UnicodeString & sb,UErrorCode & status)1698 bool GeneratorHelpers::symbols(const MacroProps& macros, UnicodeString& sb, UErrorCode& status) {
1699     if (macros.symbols.isNumberingSystem()) {
1700         const NumberingSystem& ns = *macros.symbols.getNumberingSystem();
1701         if (uprv_strcmp(ns.getName(), "latn") == 0) {
1702             sb.append(u"latin", -1);
1703         } else {
1704             sb.append(u"numbering-system/", -1);
1705             blueprint_helpers::generateNumberingSystemOption(ns, sb, status);
1706         }
1707         return true;
1708     } else if (macros.symbols.isDecimalFormatSymbols()) {
1709         status = U_UNSUPPORTED_ERROR;
1710         return false;
1711     } else {
1712         // No custom symbols
1713         return false;
1714     }
1715 }
1716 
unitWidth(const MacroProps & macros,UnicodeString & sb,UErrorCode &)1717 bool GeneratorHelpers::unitWidth(const MacroProps& macros, UnicodeString& sb, UErrorCode&) {
1718     if (macros.unitWidth == UNUM_UNIT_WIDTH_SHORT || macros.unitWidth == UNUM_UNIT_WIDTH_COUNT) {
1719         return false; // Default or Bogus
1720     }
1721     enum_to_stem_string::unitWidth(macros.unitWidth, sb);
1722     return true;
1723 }
1724 
sign(const MacroProps & macros,UnicodeString & sb,UErrorCode &)1725 bool GeneratorHelpers::sign(const MacroProps& macros, UnicodeString& sb, UErrorCode&) {
1726     if (macros.sign == UNUM_SIGN_AUTO || macros.sign == UNUM_SIGN_COUNT) {
1727         return false; // Default or Bogus
1728     }
1729     enum_to_stem_string::signDisplay(macros.sign, sb);
1730     return true;
1731 }
1732 
decimal(const MacroProps & macros,UnicodeString & sb,UErrorCode &)1733 bool GeneratorHelpers::decimal(const MacroProps& macros, UnicodeString& sb, UErrorCode&) {
1734     if (macros.decimal == UNUM_DECIMAL_SEPARATOR_AUTO || macros.decimal == UNUM_DECIMAL_SEPARATOR_COUNT) {
1735         return false; // Default or Bogus
1736     }
1737     enum_to_stem_string::decimalSeparatorDisplay(macros.decimal, sb);
1738     return true;
1739 }
1740 
scale(const MacroProps & macros,UnicodeString & sb,UErrorCode & status)1741 bool GeneratorHelpers::scale(const MacroProps& macros, UnicodeString& sb, UErrorCode& status) {
1742     if (!macros.scale.isValid()) {
1743         return false; // Default or Bogus
1744     }
1745     sb.append(u"scale/", -1);
1746     blueprint_helpers::generateScaleOption(
1747             macros.scale.fMagnitude,
1748             macros.scale.fArbitrary,
1749             sb,
1750             status);
1751     return true;
1752 }
1753 
1754 
1755 // Definitions of public API methods (put here for dependency disentanglement)
1756 
1757 #if (U_PF_WINDOWS <= U_PLATFORM && U_PLATFORM <= U_PF_CYGWIN) && defined(_MSC_VER)
1758 // Ignore MSVC warning 4661. This is generated for NumberFormatterSettings<>::toSkeleton() as this method
1759 // is defined elsewhere (in number_skeletons.cpp). The compiler is warning that the explicit template instantiation
1760 // inside this single translation unit (CPP file) is incomplete, and thus it isn't sure if the template class is
1761 // fully defined. However, since each translation unit explicitly instantiates all the necessary template classes,
1762 // they will all be passed to the linker, and the linker will still find and export all the class members.
1763 #pragma warning(push)
1764 #pragma warning(disable: 4661)
1765 #endif
1766 
1767 template<typename Derived>
toSkeleton(UErrorCode & status) const1768 UnicodeString NumberFormatterSettings<Derived>::toSkeleton(UErrorCode& status) const {
1769     if (U_FAILURE(status)) {
1770         return ICU_Utility::makeBogusString();
1771     }
1772     if (fMacros.copyErrorTo(status)) {
1773         return ICU_Utility::makeBogusString();
1774     }
1775     return skeleton::generate(fMacros, status);
1776 }
1777 
1778 // Declare all classes that implement NumberFormatterSettings
1779 // See https://stackoverflow.com/a/495056/1407170
1780 template
1781 class icu::number::NumberFormatterSettings<icu::number::UnlocalizedNumberFormatter>;
1782 template
1783 class icu::number::NumberFormatterSettings<icu::number::LocalizedNumberFormatter>;
1784 
1785 UnlocalizedNumberFormatter
forSkeleton(const UnicodeString & skeleton,UErrorCode & status)1786 NumberFormatter::forSkeleton(const UnicodeString& skeleton, UErrorCode& status) {
1787     return skeleton::create(skeleton, nullptr, status);
1788 }
1789 
1790 UnlocalizedNumberFormatter
forSkeleton(const UnicodeString & skeleton,UParseError & perror,UErrorCode & status)1791 NumberFormatter::forSkeleton(const UnicodeString& skeleton, UParseError& perror, UErrorCode& status) {
1792     return skeleton::create(skeleton, &perror, status);
1793 }
1794 
1795 #if (U_PF_WINDOWS <= U_PLATFORM && U_PLATFORM <= U_PF_CYGWIN) && defined(_MSC_VER)
1796 // Warning 4661.
1797 #pragma warning(pop)
1798 #endif
1799 
1800 #endif /* #if !UCONFIG_NO_FORMATTING */
1801