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