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