Coverage Report

Created: 2025-09-05 07:16

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