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