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