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