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