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