ICU-8610 C++ number skeleton code is building. Testing is next.

X-SVN-Rev: 41144
This commit is contained in:
Shane Carr 2018-03-23 10:07:38 +00:00
parent 52c665a2bd
commit 7da9e75441
5 changed files with 494 additions and 15 deletions

View file

@ -108,7 +108,7 @@ double-conversion-cached-powers.o double-conversion-diy-fp.o double-conversion-f
numparse_stringsegment.o numparse_unisets.o numparse_parsednumber.o \
numparse_impl.o numparse_symbols.o numparse_decimal.o numparse_scientific.o \
numparse_currency.o numparse_affixes.o numparse_compositions.o numparse_validators.o \
number_mapper.o number_multiplier.o number_currencysymbols.o
number_mapper.o number_multiplier.o number_currencysymbols.o number_skeletons.o
## Header files to install

View file

@ -1,7 +1,6 @@
// © 2018 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html
#include <unicode/numberformatter.h>
#include "unicode/utypes.h"
#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT
@ -13,10 +12,11 @@
#include "number_skeletons.h"
#include "umutex.h"
#include "ucln_in.h"
#include "hash.h"
#include "patternprops.h"
#include "unicode/ucharstriebuilder.h"
#include "number_utils.h"
#include "number_decimalquantity.h"
#include "unicode/numberformatter.h"
using namespace icu;
using namespace icu::number;
@ -34,6 +34,7 @@ char16_t* kSerializedStemTrie = nullptr;
UBool U_CALLCONV cleanupNumberSkeletons() {
uprv_free(kSerializedStemTrie);
kSerializedStemTrie = nullptr;
return TRUE;
}
void U_CALLCONV initNumberSkeletons(UErrorCode& status) {
@ -99,7 +100,14 @@ void U_CALLCONV initNumberSkeletons(UErrorCode& status) {
}
#define CHECK_NULL(seen, field, status) void; /* for auto-format line wraping */ \
inline void appendMultiple(UnicodeString& sb, UChar32 cp, int32_t count) {
for (int i = 0; i < count; i++) {
sb.append(cp);
}
}
#define CHECK_NULL(seen, field, status) (void)(seen); /* for auto-format line wraping */ \
{ \
if ((seen).field) { \
(status) = U_NUMBER_SKELETON_SYNTAX_ERROR; \
@ -109,6 +117,15 @@ void U_CALLCONV initNumberSkeletons(UErrorCode& status) {
}
const char16_t* const kRoundingModeStrings[] = {
u"up", u"down", u"ceiling", u"floor", u"half-up", u"half-down", u"half-even", u"unnecessary"};
constexpr int32_t kRoundingModeCount = 8;
static_assert(
sizeof(kRoundingModeStrings) / sizeof(*kRoundingModeStrings) == kRoundingModeCount,
"kRoundingModeCount should be the number of rounding modes");
} // anonymous namespace
@ -376,6 +393,7 @@ MacroProps skeleton::parseSkeleton(const UnicodeString& skeletonString, UErrorCo
stem = parseOption(stem, segment, macros, status);
}
segment.resetLength();
if (U_FAILURE(status)) { return macros; }
// Consume the segment:
segment.adjustOffset(offset);
@ -439,6 +457,8 @@ skeleton::parseStem(const StringSegment& segment, const UCharsTrie& stemTrie, Se
CHECK_NULL(seen, rounder, status);
blueprint_helpers::parseDigitsStem(segment, macros, status);
return STATE_NULL;
default:
break;
}
// Now look at the stemsTrie, which is already be pointing at our stem.
@ -706,6 +726,461 @@ void GeneratorHelpers::generateSkeleton(const MacroProps& macros, UnicodeString&
}
bool blueprint_helpers::parseExponentWidthOption(const StringSegment& segment, MacroProps& macros,
UErrorCode&) {
if (segment.charAt(0) != u'+') {
return false;
}
int32_t offset = 1;
int32_t minExp = 0;
for (; offset < segment.length(); offset++) {
if (segment.charAt(offset) == u'e') {
minExp++;
} else {
break;
}
}
if (offset < segment.length()) {
return false;
}
// Use the public APIs to enforce bounds checking
macros.notation = static_cast<ScientificNotation&>(macros.notation).withMinExponentDigits(minExp);
return true;
}
void
blueprint_helpers::generateExponentWidthOption(int32_t minExponentDigits, UnicodeString& sb, UErrorCode&) {
sb.append(u'+');
appendMultiple(sb, u'e', minExponentDigits);
}
bool
blueprint_helpers::parseExponentSignOption(const StringSegment& segment, MacroProps& macros, UErrorCode&) {
// Get the sign display type out of the CharsTrie data structure.
UCharsTrie tempStemTrie(kSerializedStemTrie);
UStringTrieResult result = tempStemTrie.next(segment.toUnicodeString().getBuffer(), segment.length());
if (result != USTRINGTRIE_INTERMEDIATE_VALUE && result != USTRINGTRIE_FINAL_VALUE) {
return false;
}
auto sign = stem_to_object::signDisplay(static_cast<StemEnum>(tempStemTrie.getValue()));
if (sign == UNUM_SIGN_COUNT) {
return false;
}
macros.notation = static_cast<ScientificNotation&>(macros.notation).withExponentSignDisplay(sign);
return true;
}
void blueprint_helpers::parseCurrencyOption(const StringSegment& segment, MacroProps& macros,
UErrorCode& status) {
if (segment.length() != 3) {
// throw new SkeletonSyntaxException("Invalid currency", segment);
status = U_NUMBER_SKELETON_SYNTAX_ERROR;
return;
}
const UChar* currencyCode = segment.toUnicodeString().getTerminatedBuffer();
// Check that the currency code is valid:
int32_t numericCode = ucurr_getNumericCode(currencyCode);
if (numericCode == 0) {
// throw new SkeletonSyntaxException("Invalid currency", segment);
status = U_NUMBER_SKELETON_SYNTAX_ERROR;
return;
}
// Slicing is OK
macros.unit = CurrencyUnit(currencyCode, status); // NOLINT
}
void
blueprint_helpers::generateCurrencyOption(const CurrencyUnit& currency, UnicodeString& sb, UErrorCode&) {
sb.append(currency.getISOCurrency(), -1);
}
void blueprint_helpers::parseMeasureUnitOption(const StringSegment& segment, MacroProps& macros,
UErrorCode& status) {
// NOTE: The category (type) of the unit is guaranteed to be a valid subtag (alphanumeric)
// http://unicode.org/reports/tr35/#Validity_Data
int firstHyphen = 0;
while (firstHyphen < segment.length() && segment.charAt(firstHyphen) != '-') {
firstHyphen++;
}
if (firstHyphen == segment.length()) {
// throw new SkeletonSyntaxException("Invalid measure unit option", segment);
status = U_NUMBER_SKELETON_SYNTAX_ERROR;
return;
}
// MeasureUnit is in char space; we need to convert.
// Note: the longest type/subtype as of this writing (March 2018) is 24 chars.
static constexpr int32_t CAPACITY = 30;
char type[CAPACITY];
char subType[CAPACITY];
const int32_t typeLen = firstHyphen;
const int32_t subTypeLen = segment.length() - firstHyphen - 1;
if (typeLen + 1 > CAPACITY || subTypeLen + 1 > CAPACITY) {
// Type or subtype longer than 30?
// The capacity should be increased if this is a problem with a real CLDR unit.
status = U_NUMBER_SKELETON_SYNTAX_ERROR;
return;
}
u_UCharsToChars(segment.toUnicodeString().getBuffer(), type, typeLen);
u_UCharsToChars(segment.toUnicodeString().getBuffer() + firstHyphen + 1, subType, subTypeLen);
type[typeLen] = 0;
subType[subTypeLen] = 0;
// Note: the largest type as of this writing (March 2018) is "volume", which has 24 units.
MeasureUnit units[30];
UErrorCode localStatus = U_ZERO_ERROR;
int32_t numUnits = MeasureUnit::getAvailable(type, units, 30, localStatus);
if (U_FAILURE(localStatus)) {
// More than 30 units in this type?
status = U_NUMBER_SKELETON_SYNTAX_ERROR;
return;
}
for (int32_t i = 0; i < numUnits; i++) {
auto& unit = units[i];
if (uprv_strcmp(subType, unit.getSubtype()) == 0) {
macros.unit = unit;
return;
}
}
// throw new SkeletonSyntaxException("Unknown measure unit", segment);
status = U_NUMBER_SKELETON_SYNTAX_ERROR;
}
void blueprint_helpers::generateMeasureUnitOption(const MeasureUnit& measureUnit, UnicodeString& sb,
UErrorCode& status) {
// We need to convert from char* to UChar*...
// See comments in the previous function about the capacity setting.
static constexpr int32_t CAPACITY = 30;
char16_t type16[CAPACITY];
char16_t subType16[CAPACITY];
const auto typeLen = static_cast<int32_t>(uprv_strlen(measureUnit.getType()));
const auto subTypeLen = static_cast<int32_t>(uprv_strlen(measureUnit.getSubtype()));
if (typeLen + 1 > CAPACITY || subTypeLen + 1 > CAPACITY) {
// Type or subtype longer than 30?
// The capacity should be increased if this is a problem with a real CLDR unit.
status = U_UNSUPPORTED_ERROR;
return;
}
u_charsToUChars(measureUnit.getType(), type16, typeLen);
u_charsToUChars(measureUnit.getSubtype(), subType16, subTypeLen);
sb.append(type16, typeLen);
sb.append(u'-');
sb.append(subType16, subTypeLen);
}
void blueprint_helpers::parseMeasurePerUnitOption(const StringSegment& segment, MacroProps& macros,
UErrorCode& status) {
// A little bit of a hack: safe the current unit (numerator), call the main measure unit
// parsing code, put back the numerator unit, and put the new unit into per-unit.
MeasureUnit numerator = macros.unit;
parseMeasureUnitOption(segment, macros, status);
macros.perUnit = macros.unit;
macros.unit = numerator;
}
void blueprint_helpers::parseFractionStem(const StringSegment& segment, MacroProps& macros,
UErrorCode& status) {
U_ASSERT(segment.charAt(0) == u'.');
int32_t offset = 1;
int32_t minFrac = 0;
int32_t maxFrac;
for (; offset < segment.length(); offset++) {
if (segment.charAt(offset) == u'0') {
minFrac++;
} else {
break;
}
}
if (offset < segment.length()) {
if (segment.charAt(offset) == u'+') {
maxFrac = -1;
offset++;
} else {
maxFrac = minFrac;
for (; offset < segment.length(); offset++) {
if (segment.charAt(offset) == u'#') {
maxFrac++;
} else {
break;
}
}
}
} else {
maxFrac = minFrac;
}
if (offset < segment.length()) {
// throw new SkeletonSyntaxException("Invalid fraction stem", segment);
status = U_NUMBER_SKELETON_SYNTAX_ERROR;
return;
}
// Use the public APIs to enforce bounds checking
if (maxFrac == -1) {
macros.rounder = Rounder::minFraction(minFrac);
} else {
macros.rounder = Rounder::minMaxFraction(minFrac, maxFrac);
}
}
void
blueprint_helpers::generateFractionStem(int32_t minFrac, int32_t maxFrac, UnicodeString& sb, UErrorCode&) {
if (minFrac == 0 && maxFrac == 0) {
sb.append(u"round-integer", -1);
return;
}
sb.append(u'.');
appendMultiple(sb, u'0', minFrac);
if (maxFrac == -1) {
sb.append(u'+');
} else {
appendMultiple(sb, u'#', maxFrac - minFrac);
}
}
void
blueprint_helpers::parseDigitsStem(const StringSegment& segment, MacroProps& macros, UErrorCode& status) {
U_ASSERT(segment.charAt(0) == u'@');
int offset = 0;
int minSig = 0;
int maxSig;
for (; offset < segment.length(); offset++) {
if (segment.charAt(offset) == u'@') {
minSig++;
} else {
break;
}
}
if (offset < segment.length()) {
if (segment.charAt(offset) == u'+') {
maxSig = -1;
offset++;
} else {
maxSig = minSig;
for (; offset < segment.length(); offset++) {
if (segment.charAt(offset) == u'#') {
maxSig++;
} else {
break;
}
}
}
} else {
maxSig = minSig;
}
if (offset < segment.length()) {
// throw new SkeletonSyntaxException("Invalid significant digits stem", segment);
status = U_NUMBER_SKELETON_SYNTAX_ERROR;
}
// Use the public APIs to enforce bounds checking
if (maxSig == -1) {
macros.rounder = Rounder::minDigits(minSig);
} else {
macros.rounder = Rounder::minMaxDigits(minSig, maxSig);
}
}
void
blueprint_helpers::generateDigitsStem(int32_t minSig, int32_t maxSig, UnicodeString& sb, UErrorCode&) {
appendMultiple(sb, u'@', minSig);
if (maxSig == -1) {
sb.append(u'+');
} else {
appendMultiple(sb, u'#', maxSig - minSig);
}
}
bool blueprint_helpers::parseFracSigOption(const StringSegment& segment, MacroProps& macros,
UErrorCode& status) {
if (segment.charAt(0) != u'@') {
return false;
}
int offset = 0;
int minSig = 0;
int maxSig;
for (; offset < segment.length(); offset++) {
if (segment.charAt(offset) == u'@') {
minSig++;
} else {
break;
}
}
// For the frac-sig option, there must be minSig or maxSig but not both.
// Valid: @+, @@+, @@@+
// Valid: @#, @##, @###
// Invalid: @, @@, @@@
// Invalid: @@#, @@##, @@@#
if (offset < segment.length()) {
if (segment.charAt(offset) == u'+') {
maxSig = -1;
offset++;
} else if (minSig > 1) {
// @@#, @@##, @@@#
// throw new SkeletonSyntaxException("Invalid digits option for fraction rounder", segment);
status = U_NUMBER_SKELETON_SYNTAX_ERROR;
return false;
} else {
maxSig = minSig;
for (; offset < segment.length(); offset++) {
if (segment.charAt(offset) == u'#') {
maxSig++;
} else {
break;
}
}
}
} else {
// @, @@, @@@
// throw new SkeletonSyntaxException("Invalid digits option for fraction rounder", segment);
status = U_NUMBER_SKELETON_SYNTAX_ERROR;
return false;
}
if (offset < segment.length()) {
// throw new SkeletonSyntaxException("Invalid digits option for fraction rounder", segment);
status = U_NUMBER_SKELETON_SYNTAX_ERROR;
return false;
}
auto& oldRounder = static_cast<const FractionRounder&>(macros.rounder);
if (maxSig == -1) {
macros.rounder = oldRounder.withMinDigits(minSig);
} else {
macros.rounder = oldRounder.withMaxDigits(maxSig);
}
return true;
}
void blueprint_helpers::parseIncrementOption(const StringSegment& segment, MacroProps& macros,
UErrorCode& status) {
// Utilize DecimalQuantity/decNumber to parse this for us.
static constexpr int32_t CAPACITY = 30;
char buffer[CAPACITY];
if (segment.length() > CAPACITY) {
// No support for numbers this long; they won't fit in a double anyway.
status = U_NUMBER_SKELETON_SYNTAX_ERROR;
return;
}
u_UCharsToChars(segment.toUnicodeString().getBuffer(), buffer, segment.length());
DecimalQuantity dq;
dq.setToDecNumber({buffer, segment.length()});
double increment = dq.toDouble();
macros.rounder = Rounder::increment(increment);
}
void blueprint_helpers::generateIncrementOption(double increment, UnicodeString& sb, UErrorCode&) {
// Utilize DecimalQuantity/double_conversion to format this for us.
DecimalQuantity dq;
dq.setToDouble(increment);
dq.roundToInfinity();
sb.append(dq.toPlainString());
}
bool
blueprint_helpers::parseRoundingModeOption(const StringSegment& segment, MacroProps& macros, UErrorCode&) {
for (int rm = 0; rm < kRoundingModeCount; rm++) {
if (segment == UnicodeString(kRoundingModeStrings[rm], -1)) {
macros.rounder = macros.rounder.withMode(static_cast<RoundingMode>(rm));
return true;
}
}
return false;
}
void blueprint_helpers::generateRoundingModeOption(RoundingMode mode, UnicodeString& sb, UErrorCode&) {
sb.append(kRoundingModeStrings[mode], -1);
}
void blueprint_helpers::parseIntegerWidthOption(const StringSegment& segment, MacroProps& macros,
UErrorCode& status) {
int32_t offset = 0;
int32_t minInt = 0;
int32_t maxInt;
if (segment.charAt(0) == u'+') {
maxInt = -1;
offset++;
} else {
maxInt = 0;
}
for (; offset < segment.length(); offset++) {
if (segment.charAt(offset) == u'#') {
maxInt++;
} else {
break;
}
}
if (offset < segment.length()) {
for (; offset < segment.length(); offset++) {
if (segment.charAt(offset) == u'0') {
minInt++;
} else {
break;
}
}
}
if (maxInt != -1) {
maxInt += minInt;
}
if (offset < segment.length()) {
// throw new SkeletonSyntaxException("Invalid integer width stem", segment);
status = U_NUMBER_SKELETON_SYNTAX_ERROR;
return;
}
// Use the public APIs to enforce bounds checking
if (maxInt == -1) {
macros.integerWidth = IntegerWidth::zeroFillTo(minInt);
} else {
macros.integerWidth = IntegerWidth::zeroFillTo(minInt).truncateAt(maxInt);
}
}
void blueprint_helpers::generateIntegerWidthOption(int32_t minInt, int32_t maxInt, UnicodeString& sb,
UErrorCode&) {
if (maxInt == -1) {
sb.append(u'+');
} else {
appendMultiple(sb, u'#', maxInt - minInt);
}
appendMultiple(sb, u'0', minInt);
}
void blueprint_helpers::parseNumberingSystemOption(const StringSegment& segment, MacroProps& macros,
UErrorCode& status) {
// Need to do char <-> UChar conversion...
static constexpr int32_t CAPACITY = 30;
char buffer[CAPACITY];
if (segment.length() + 1 > CAPACITY) {
// No support for numbers this long; they won't fit in a double anyway.
status = U_NUMBER_SKELETON_SYNTAX_ERROR;
return;
}
u_UCharsToChars(segment.toUnicodeString().getBuffer(), buffer, segment.length());
buffer[segment.length()] = 0;
NumberingSystem* ns = NumberingSystem::createInstanceByName(buffer, status);
if (ns == nullptr) {
// throw new SkeletonSyntaxException("Unknown numbering system", segment);
status = U_NUMBER_SKELETON_SYNTAX_ERROR;
return;
}
macros.symbols.setTo(ns);
}
void blueprint_helpers::generateNumberingSystemOption(const NumberingSystem& ns, UnicodeString& sb,
UErrorCode& status) {
// Need to do char <-> UChar conversion...
static constexpr int32_t CAPACITY = 30;
char16_t buffer16[CAPACITY];
const auto len = static_cast<int32_t>(uprv_strlen(ns.getName()));
if (len > CAPACITY) {
// No support for numbers this long; they won't fit in a double anyway.
status = U_UNSUPPORTED_ERROR;
return;
}
u_charsToUChars(ns.getName(), buffer16, len);
sb.append(buffer16, len);
}
bool GeneratorHelpers::notation(const MacroProps& macros, UnicodeString& sb, UErrorCode& status) {
if (macros.notation.fType == Notation::NTN_COMPACT) {
UNumberCompactStyle style = macros.notation.fUnion.compactStyle;
@ -770,6 +1245,7 @@ bool GeneratorHelpers::perUnit(const MacroProps& macros, UnicodeString& sb, UErr
// Per-units are currently expected to be only MeasureUnits.
if (unitIsCurrency(macros.perUnit) || unitIsNoUnit(macros.perUnit)) {
status = U_UNSUPPORTED_ERROR;
return false;
} else {
sb.append(u"per-measure-unit/", -1);
blueprint_helpers::generateMeasureUnitOption(macros.perUnit, sb, status);
@ -824,10 +1300,12 @@ bool GeneratorHelpers::rounding(const MacroProps& macros, UnicodeString& sb, UEr
bool GeneratorHelpers::grouping(const MacroProps& macros, UnicodeString& sb, UErrorCode& status) {
if (macros.grouper.isBogus() || macros.grouper.fStrategy == UNUM_GROUPING_COUNT) {
status = U_UNSUPPORTED_ERROR;
return false;
} else if (macros.grouper.fStrategy == UNUM_GROUPING_AUTO) {
return false; // Default value
} else {
enum_to_stem_string::groupingStrategy(macros.grouper.fStrategy, sb);
return true;
}
}
@ -864,7 +1342,7 @@ bool GeneratorHelpers::symbols(const MacroProps& macros, UnicodeString& sb, UErr
}
}
bool GeneratorHelpers::unitWidth(const MacroProps& macros, UnicodeString& sb, UErrorCode& status) {
bool GeneratorHelpers::unitWidth(const MacroProps& macros, UnicodeString& sb, UErrorCode&) {
if (macros.unitWidth == UNUM_UNIT_WIDTH_SHORT || macros.unitWidth == UNUM_UNIT_WIDTH_COUNT) {
return false; // Default or Bogus
}
@ -872,7 +1350,7 @@ bool GeneratorHelpers::unitWidth(const MacroProps& macros, UnicodeString& sb, UE
return true;
}
bool GeneratorHelpers::sign(const MacroProps& macros, UnicodeString& sb, UErrorCode& status) {
bool GeneratorHelpers::sign(const MacroProps& macros, UnicodeString& sb, UErrorCode&) {
if (macros.sign == UNUM_SIGN_AUTO || macros.sign == UNUM_SIGN_COUNT) {
return false; // Default or Bogus
}
@ -880,7 +1358,7 @@ bool GeneratorHelpers::sign(const MacroProps& macros, UnicodeString& sb, UErrorC
return true;
}
bool GeneratorHelpers::decimal(const MacroProps& macros, UnicodeString& sb, UErrorCode& status) {
bool GeneratorHelpers::decimal(const MacroProps& macros, UnicodeString& sb, UErrorCode&) {
if (macros.decimal == UNUM_DECIMAL_SEPARATOR_AUTO || macros.decimal == UNUM_DECIMAL_SEPARATOR_COUNT) {
return false; // Default or Bogus
}

View file

@ -192,7 +192,7 @@ namespace blueprint_helpers {
/** @return Whether we successfully found and parsed an exponent width option. */
bool parseExponentWidthOption(const StringSegment& segment, MacroProps& macros, UErrorCode& status);
void generateExponentWidthOption(int32_t xyz, UnicodeString& sb, UErrorCode& status);
void generateExponentWidthOption(int32_t minExponentDigits, UnicodeString& sb, UErrorCode& status);
/** @return Whether we successfully found and parsed an exponent sign option. */
bool parseExponentSignOption(const StringSegment& segment, MacroProps& macros, UErrorCode& status);
@ -225,7 +225,7 @@ void generateIncrementOption(double increment, UnicodeString& sb, UErrorCode& st
/** @return Whether we successfully found and parsed a rounding mode. */
bool parseRoundingModeOption(const StringSegment& segment, MacroProps& macros, UErrorCode& status);
void generateRoundingModeOption(RoundingMode, UnicodeString& sb, UErrorCode& status);
void generateRoundingModeOption(RoundingMode mode, UnicodeString& sb, UErrorCode& status);
void parseIntegerWidthOption(const StringSegment& segment, MacroProps& macros, UErrorCode& status);

View file

@ -61,7 +61,8 @@ UChar32 StringSegment::codePointAt(int32_t index) const {
}
UnicodeString StringSegment::toUnicodeString() const {
return UnicodeString(fStr, fStart, fEnd - fStart);
// Use the readonly-aliasing constructor for efficiency.
return UnicodeString(FALSE, fStr.getBuffer() + fStart, fEnd - fStart);
}
UChar32 StringSegment::getCodePoint() const {

View file

@ -884,13 +884,14 @@ class NumberSkeletonImpl {
}
private static void generateMeasureUnitOption(MeasureUnit unit, StringBuilder sb) {
sb.append(unit.getType() + "-" + unit.getSubtype());
sb.append(unit.getType());
sb.append("-");
sb.append(unit.getSubtype());
}
private static void parseMeasurePerUnitOption(StringSegment segment, MacroProps macros) {
// A little bit of a hack: safe the current unit (numerator), call the main measure unit
// parsing
// code, put back the numerator unit, and put the new unit into per-unit.
// parsing code, put back the numerator unit, and put the new unit into per-unit.
MeasureUnit numerator = macros.unit;
parseMeasureUnitOption(segment, macros);
macros.perUnit = macros.unit;
@ -1083,8 +1084,7 @@ class NumberSkeletonImpl {
}
private static void generateRoundingModeOption(RoundingMode mode, StringBuilder sb) {
String option = ROUNDING_MODE_STRINGS[mode.ordinal()];
sb.append(option);
sb.append(ROUNDING_MODE_STRINGS[mode.ordinal()]);
}
private static void parseIntegerWidthOption(StringSegment segment, MacroProps macros) {