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