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