Coverage Report

Created: 2018-09-25 14:53

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