ICU-8610 Dirty commit of C++ work so far. Probably does not build.

X-SVN-Rev: 41142
This commit is contained in:
Shane Carr 2018-03-23 06:46:19 +00:00
parent c725920cff
commit d8f2d8ce6e
7 changed files with 966 additions and 75 deletions

View file

@ -0,0 +1,563 @@
// © 2018 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html
#include "unicode/utypes.h"
#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT
// Allow implicit conversion from char16_t* to UnicodeString for this file:
// Helpful in toString methods and elsewhere.
#define UNISTR_FROM_STRING_EXPLICIT
#include "number_skeletons.h"
#include "umutex.h"
#include "ucln_in.h"
#include "hash.h"
#include "patternprops.h"
#include "unicode/ucharstriebuilder.h"
using namespace icu;
using namespace icu::number;
using namespace icu::number::impl;
using namespace icu::number::impl::skeleton;
static constexpr UErrorCode U_NUMBER_SKELETON_SYNTAX_ERROR = U_ILLEGAL_ARGUMENT_ERROR;
namespace {
icu::UInitOnce gNumberSkeletonsInitOnce = U_INITONCE_INITIALIZER;
char16_t* kSerializedStemTrie = nullptr;
UBool U_CALLCONV cleanupNumberSkeletons() {
uprv_free(kSerializedStemTrie);
kSerializedStemTrie = nullptr;
}
void U_CALLCONV initNumberSkeletons(UErrorCode& status) {
ucln_i18n_registerCleanup(UCLN_I18N_NUMBER_SKELETONS, cleanupNumberSkeletons);
UCharsTrieBuilder b(status);
if (U_FAILURE(status)) { return; }
// Section 1:
b.add(u"compact-short", STEM_COMPACT_SHORT, status);
b.add(u"compact-long", STEM_COMPACT_LONG, status);
b.add(u"scientific", STEM_SCIENTIFIC, status);
b.add(u"engineering", STEM_ENGINEERING, status);
b.add(u"notation-simple", STEM_NOTATION_SIMPLE, status);
b.add(u"base-unit", STEM_BASE_UNIT, status);
b.add(u"percent", STEM_PERCENT, status);
b.add(u"permille", STEM_PERMILLE, status);
b.add(u"round-integer", STEM_ROUND_INTEGER, status);
b.add(u"round-unlimited", STEM_ROUND_UNLIMITED, status);
b.add(u"round-currency-standard", STEM_ROUND_CURRENCY_STANDARD, status);
b.add(u"round-currency-cash", STEM_ROUND_CURRENCY_CASH, status);
b.add(u"group-off", STEM_GROUP_OFF, status);
b.add(u"group-min2", STEM_GROUP_MIN2, status);
b.add(u"group-auto", STEM_GROUP_AUTO, status);
b.add(u"group-on-aligned", STEM_GROUP_ON_ALIGNED, status);
b.add(u"group-thousands", STEM_GROUP_THOUSANDS, status);
b.add(u"latin", STEM_LATIN, status);
b.add(u"unit-width-narrow", STEM_UNIT_WIDTH_NARROW, status);
b.add(u"unit-width-short", STEM_UNIT_WIDTH_SHORT, status);
b.add(u"unit-width-full-name", STEM_UNIT_WIDTH_FULL_NAME, status);
b.add(u"unit-width-iso-code", STEM_UNIT_WIDTH_ISO_CODE, status);
b.add(u"unit-width-hidden", STEM_UNIT_WIDTH_HIDDEN, status);
b.add(u"sign-auto", STEM_SIGN_AUTO, status);
b.add(u"sign-always", STEM_SIGN_ALWAYS, status);
b.add(u"sign-never", STEM_SIGN_NEVER, status);
b.add(u"sign-accounting", STEM_SIGN_ACCOUNTING, status);
b.add(u"sign-accounting-always", STEM_SIGN_ACCOUNTING_ALWAYS, status);
b.add(u"sign-except-zero", STEM_SIGN_EXCEPT_ZERO, status);
b.add(u"sign-accounting-except-zero", STEM_SIGN_ACCOUNTING_EXCEPT_ZERO, status);
b.add(u"decimal-auto", STEM_DECIMAL_AUTO, status);
b.add(u"decimal-always", STEM_DECIMAL_ALWAYS, status);
if (U_FAILURE(status)) { return; }
// Section 2:
b.add(u"round-increment", STEM_ROUND_INCREMENT, status);
b.add(u"measure-unit", STEM_MEASURE_UNIT, status);
b.add(u"per-measure-unit", STEM_PER_MEASURE_UNIT, status);
b.add(u"currency", STEM_CURRENCY, status);
b.add(u"integer-width", STEM_INTEGER_WIDTH, status);
b.add(u"numbering-system", STEM_NUMBERING_SYSTEM, status);
if (U_FAILURE(status)) { return; }
// Build the CharsTrie
// TODO: Use SLOW or FAST here?
UnicodeString result;
b.buildUnicodeString(USTRINGTRIE_BUILD_FAST, result, status);
if (U_FAILURE(status)) { return; }
// Copy the result into the global constant pointer
size_t numBytes = result.length() * sizeof(char16_t);
kSerializedStemTrie = static_cast<char16_t*>(uprv_malloc(numBytes));
uprv_memcpy(kSerializedStemTrie, result.getBuffer(), numBytes);
}
#define CHECK_NULL(seen, field, status) void; /* for auto-format line wraping */ \
{ \
if ((seen).field) { \
(status) = U_NUMBER_SKELETON_SYNTAX_ERROR; \
return STATE_NULL; \
} \
(seen).field = true; \
}
} // anonymous namespace
Notation stem_to_object::notation(skeleton::StemEnum stem) {
switch (stem) {
case STEM_COMPACT_SHORT:
return Notation::compactShort();
case STEM_COMPACT_LONG:
return Notation::compactLong();
case STEM_SCIENTIFIC:
return Notation::scientific();
case STEM_ENGINEERING:
return Notation::engineering();
case STEM_NOTATION_SIMPLE:
return Notation::simple();
default:
U_ASSERT(false);
}
}
MeasureUnit stem_to_object::unit(skeleton::StemEnum stem) {
switch (stem) {
case STEM_BASE_UNIT:
// Slicing is okay
return NoUnit::base(); // NOLINT
case STEM_PERCENT:
// Slicing is okay
return NoUnit::percent(); // NOLINT
case STEM_PERMILLE:
// Slicing is okay
return NoUnit::permille(); // NOLINT
default:
U_ASSERT(false);
}
}
Rounder stem_to_object::rounder(skeleton::StemEnum stem) {
switch (stem) {
case STEM_ROUND_INTEGER:
return Rounder::integer();
case STEM_ROUND_UNLIMITED:
return Rounder::unlimited();
case STEM_ROUND_CURRENCY_STANDARD:
return Rounder::currency(UCURR_USAGE_STANDARD);
case STEM_ROUND_CURRENCY_CASH:
return Rounder::currency(UCURR_USAGE_CASH);
default:
U_ASSERT(false);
}
}
UGroupingStrategy stem_to_object::groupingStrategy(skeleton::StemEnum stem) {
switch (stem) {
case STEM_GROUP_OFF:
return UNUM_GROUPING_OFF;
case STEM_GROUP_MIN2:
return UNUM_GROUPING_MIN2;
case STEM_GROUP_AUTO:
return UNUM_GROUPING_AUTO;
case STEM_GROUP_ON_ALIGNED:
return UNUM_GROUPING_ON_ALIGNED;
case STEM_GROUP_THOUSANDS:
return UNUM_GROUPING_THOUSANDS;
default:
return UNUM_GROUPING_COUNT; // for objects, throw; for enums, return COUNT
}
}
UNumberUnitWidth stem_to_object::unitWidth(skeleton::StemEnum stem) {
switch (stem) {
case STEM_UNIT_WIDTH_NARROW:
return UNUM_UNIT_WIDTH_NARROW;
case STEM_UNIT_WIDTH_SHORT:
return UNUM_UNIT_WIDTH_SHORT;
case STEM_UNIT_WIDTH_FULL_NAME:
return UNUM_UNIT_WIDTH_FULL_NAME;
case STEM_UNIT_WIDTH_ISO_CODE:
return UNUM_UNIT_WIDTH_ISO_CODE;
case STEM_UNIT_WIDTH_HIDDEN:
return UNUM_UNIT_WIDTH_HIDDEN;
default:
return UNUM_UNIT_WIDTH_COUNT; // for objects, throw; for enums, return COUNT
}
}
UNumberSignDisplay stem_to_object::signDisplay(skeleton::StemEnum stem) {
switch (stem) {
case STEM_SIGN_AUTO:
return UNUM_SIGN_AUTO;
case STEM_SIGN_ALWAYS:
return UNUM_SIGN_ALWAYS;
case STEM_SIGN_NEVER:
return UNUM_SIGN_NEVER;
case STEM_SIGN_ACCOUNTING:
return UNUM_SIGN_ACCOUNTING;
case STEM_SIGN_ACCOUNTING_ALWAYS:
return UNUM_SIGN_ACCOUNTING_ALWAYS;
case STEM_SIGN_EXCEPT_ZERO:
return UNUM_SIGN_EXCEPT_ZERO;
case STEM_SIGN_ACCOUNTING_EXCEPT_ZERO:
return UNUM_SIGN_ACCOUNTING_EXCEPT_ZERO;
default:
return UNUM_SIGN_COUNT; // for objects, throw; for enums, return COUNT
}
}
UNumberDecimalSeparatorDisplay stem_to_object::decimalSeparatorDisplay(skeleton::StemEnum stem) {
switch (stem) {
case STEM_DECIMAL_AUTO:
return UNUM_DECIMAL_SEPARATOR_AUTO;
case STEM_DECIMAL_ALWAYS:
return UNUM_DECIMAL_SEPARATOR_ALWAYS;
default:
return UNUM_DECIMAL_SEPARATOR_COUNT; // for objects, throw; for enums, return COUNT
}
}
void enum_to_stem_string::groupingStrategy(UGroupingStrategy value, UnicodeString& sb) {
switch (value) {
case UNUM_GROUPING_OFF:
sb.append(u"group-off", -1);
break;
case UNUM_GROUPING_MIN2:
sb.append(u"group-min2", -1);
break;
case UNUM_GROUPING_AUTO:
sb.append(u"group-auto", -1);
break;
case UNUM_GROUPING_ON_ALIGNED:
sb.append(u"group-on-aligned", -1);
break;
case UNUM_GROUPING_THOUSANDS:
sb.append(u"group-thousands", -1);
break;
default:
U_ASSERT(false);
}
}
void enum_to_stem_string::unitWidth(UNumberUnitWidth value, UnicodeString& sb) {
switch (value) {
case UNUM_UNIT_WIDTH_NARROW:
sb.append(u"unit-width-narrow", -1);
break;
case UNUM_UNIT_WIDTH_SHORT:
sb.append(u"unit-width-short", -1);
break;
case UNUM_UNIT_WIDTH_FULL_NAME:
sb.append(u"unit-width-full-name", -1);
break;
case UNUM_UNIT_WIDTH_ISO_CODE:
sb.append(u"unit-width-iso-code", -1);
break;
case UNUM_UNIT_WIDTH_HIDDEN:
sb.append(u"unit-width-hidden", -1);
break;
default:
U_ASSERT(false);
}
}
void enum_to_stem_string::signDisplay(UNumberSignDisplay value, UnicodeString& sb) {
switch (value) {
case UNUM_SIGN_AUTO:
sb.append(u"sign-auto", -1);
break;
case UNUM_SIGN_ALWAYS:
sb.append(u"sign-always", -1);
break;
case UNUM_SIGN_NEVER:
sb.append(u"sign-never", -1);
break;
case UNUM_SIGN_ACCOUNTING:
sb.append(u"sign-accounting", -1);
break;
case UNUM_SIGN_ACCOUNTING_ALWAYS:
sb.append(u"sign-accounting-always", -1);
break;
case UNUM_SIGN_EXCEPT_ZERO:
sb.append(u"sign-except-zero", -1);
break;
case UNUM_SIGN_ACCOUNTING_EXCEPT_ZERO:
sb.append(u"sign-accounting-except-zero", -1);
break;
default:
U_ASSERT(false);
}
}
void
enum_to_stem_string::decimalSeparatorDisplay(UNumberDecimalSeparatorDisplay value, UnicodeString& sb) {
switch (value) {
case UNUM_DECIMAL_SEPARATOR_AUTO:
sb.append(u"decimal-auto", -1);
break;
case UNUM_DECIMAL_SEPARATOR_ALWAYS:
sb.append(u"decimal-always", -1);
break;
default:
U_ASSERT(false);
}
}
UnlocalizedNumberFormatter skeleton::create(const UnicodeString& skeletonString, UErrorCode& status) {
if (U_FAILURE(status)) { return {}; }
umtx_initOnce(gNumberSkeletonsInitOnce, &initNumberSkeletons, status);
if (U_FAILURE(status)) { return {}; }
MacroProps macros = parseSkeleton(skeletonString, status);
return NumberFormatter::with().macros(macros);
}
UnicodeString skeleton::generate(const MacroProps& macros, UErrorCode& status) {
if (U_FAILURE(status)) { return {}; }
umtx_initOnce(gNumberSkeletonsInitOnce, &initNumberSkeletons, status);
if (U_FAILURE(status)) { return {}; }
UnicodeString sb;
generateSkeleton(macros, sb, status);
return sb;
}
MacroProps skeleton::parseSkeleton(const UnicodeString& skeletonString, UErrorCode& status) {
// Add a trailing whitespace to the end of the skeleton string to make code cleaner.
UnicodeString tempSkeletonString(skeletonString);
tempSkeletonString.append(u' ');
SeenMacroProps seen;
MacroProps macros;
StringSegment segment(skeletonString, false);
UCharsTrie stemTrie(kSerializedStemTrie);
ParseState stem = STATE_NULL;
int offset = 0;
// Primary skeleton parse loop:
while (offset < segment.length()) {
int cp = segment.codePointAt(offset);
bool isTokenSeparator = PatternProps::isWhiteSpace(cp);
bool isOptionSeparator = (cp == u'/');
if (!isTokenSeparator && !isOptionSeparator) {
// Non-separator token; consume it.
offset += U16_LENGTH(cp);
if (stem == STATE_NULL) {
// We are currently consuming a stem.
// Go to the next state in the stem trie.
stemTrie.nextForCodePoint(cp);
}
continue;
}
// We are looking at a token or option separator.
// If the segment is nonempty, parse it and reset the segment.
// Otherwise, make sure it is a valid repeating separator.
if (offset != 0) {
segment.setLength(offset);
if (stem == STATE_NULL) {
// The first separator after the start of a token. Parse it as a stem.
stem = parseStem(segment, stemTrie, seen, macros, status);
stemTrie.reset();
} else {
// A separator after the first separator of a token. Parse it as an option.
stem = parseOption(stem, segment, macros, status);
}
segment.resetLength();
// Consume the segment:
segment.adjustOffset(offset);
offset = 0;
} else if (stem != STATE_NULL) {
// A separator ('/' or whitespace) following an option separator ('/')
// segment.setLength(U16_LENGTH(cp)); // for error message
// throw new SkeletonSyntaxException("Unexpected separator character", segment);
status = U_NUMBER_SKELETON_SYNTAX_ERROR;
return macros;
} else {
// Two spaces in a row; this is OK.
}
// Does the current stem forbid options?
if (isOptionSeparator && stem == STATE_NULL) {
// segment.setLength(U16_LENGTH(cp)); // for error message
// throw new SkeletonSyntaxException("Unexpected option separator", segment);
status = U_NUMBER_SKELETON_SYNTAX_ERROR;
return macros;
}
// Does the current stem require an option?
if (isTokenSeparator && stem != STATE_NULL) {
switch (stem) {
case STATE_INCREMENT_ROUNDER:
case STATE_MEASURE_UNIT:
case STATE_PER_MEASURE_UNIT:
case STATE_CURRENCY_UNIT:
case STATE_INTEGER_WIDTH:
case STATE_NUMBERING_SYSTEM:
// segment.setLength(U16_LENGTH(cp)); // for error message
// throw new SkeletonSyntaxException("Stem requires an option", segment);
status = U_NUMBER_SKELETON_SYNTAX_ERROR;
return macros;
default:
break;
}
stem = STATE_NULL;
}
// Consume the separator:
segment.adjustOffset(U16_LENGTH(cp));
}
U_ASSERT(stem == STATE_NULL);
return macros;
}
ParseState
skeleton::parseStem(const StringSegment& segment, const UCharsTrie& stemTrie, SeenMacroProps& seen,
MacroProps& macros, UErrorCode& status) {
// First check for "blueprint" stems, which start with a "signal char"
switch (segment.charAt(0)) {
case u'.':
CHECK_NULL(seen, rounder, status);
blueprint_helpers::parseFractionStem(segment, macros, status);
return STATE_FRACTION_ROUNDER;
case u'@':
CHECK_NULL(seen, rounder, status);
blueprint_helpers::parseDigitsStem(segment, macros, status);
return STATE_NULL;
}
// Now look at the stemsTrie, which is already be pointing at our stem.
UStringTrieResult stemResult = stemTrie.current();
if (stemResult != USTRINGTRIE_INTERMEDIATE_VALUE && stemResult != USTRINGTRIE_FINAL_VALUE) {
// throw new SkeletonSyntaxException("Unknown stem", segment);
status = U_NUMBER_SKELETON_SYNTAX_ERROR;
return STATE_NULL;
}
auto stem = static_cast<StemEnum>(stemTrie.getValue());
switch (stem) {
// Stems with meaning on their own, not requiring an option:
case STEM_COMPACT_SHORT:
case STEM_COMPACT_LONG:
case STEM_SCIENTIFIC:
case STEM_ENGINEERING:
case STEM_NOTATION_SIMPLE:
CHECK_NULL(seen, notation, status);
macros.notation = stem_to_object::notation(stem);
switch (stem) {
case STEM_SCIENTIFIC:
case STEM_ENGINEERING:
return STATE_SCIENTIFIC; // allows for scientific options
default:
return STATE_NULL;
}
case STEM_BASE_UNIT:
case STEM_PERCENT:
case STEM_PERMILLE:
CHECK_NULL(seen, unit, status);
macros.unit = stem_to_object::unit(stem);
return STATE_NULL;
case STEM_ROUND_INTEGER:
case STEM_ROUND_UNLIMITED:
case STEM_ROUND_CURRENCY_STANDARD:
case STEM_ROUND_CURRENCY_CASH:
CHECK_NULL(seen, rounder, status);
macros.rounder = stem_to_object::rounder(stem);
switch (stem) {
case STEM_ROUND_INTEGER:
return STATE_FRACTION_ROUNDER; // allows for "round-integer/@##"
default:
return STATE_ROUNDER; // allows for rounding mode options
}
case STEM_GROUP_OFF:
case STEM_GROUP_MIN2:
case STEM_GROUP_AUTO:
case STEM_GROUP_ON_ALIGNED:
case STEM_GROUP_THOUSANDS:
CHECK_NULL(seen, grouper, status);
macros.grouper = Grouper::forStrategy(stem_to_object::groupingStrategy(stem));
return STATE_NULL;
case STEM_LATIN:
CHECK_NULL(seen, symbols, status);
macros.symbols.setTo(NumberingSystem::createInstanceByName("latn", status));
return STATE_NULL;
case STEM_UNIT_WIDTH_NARROW:
case STEM_UNIT_WIDTH_SHORT:
case STEM_UNIT_WIDTH_FULL_NAME:
case STEM_UNIT_WIDTH_ISO_CODE:
case STEM_UNIT_WIDTH_HIDDEN:
CHECK_NULL(seen, unitWidth, status);
macros.unitWidth = stem_to_object::unitWidth(stem);
return STATE_NULL;
case STEM_SIGN_AUTO:
case STEM_SIGN_ALWAYS:
case STEM_SIGN_NEVER:
case STEM_SIGN_ACCOUNTING:
case STEM_SIGN_ACCOUNTING_ALWAYS:
case STEM_SIGN_EXCEPT_ZERO:
case STEM_SIGN_ACCOUNTING_EXCEPT_ZERO:
CHECK_NULL(seen, sign, status);
macros.sign = stem_to_object::signDisplay(stem);
return STATE_NULL;
case STEM_DECIMAL_AUTO:
case STEM_DECIMAL_ALWAYS:
CHECK_NULL(seen, decimal, status);
macros.decimal = stem_to_object::decimalSeparatorDisplay(stem);
return STATE_NULL;
// Stems requiring an option:
case STEM_ROUND_INCREMENT:
CHECK_NULL(seen, rounder, status);
return STATE_INCREMENT_ROUNDER;
case STEM_MEASURE_UNIT:
CHECK_NULL(seen, unit, status);
return STATE_MEASURE_UNIT;
case STEM_PER_MEASURE_UNIT:
CHECK_NULL(seen, perUnit, status);
return STATE_PER_MEASURE_UNIT;
case STEM_CURRENCY:
CHECK_NULL(seen, unit, status);
return STATE_CURRENCY_UNIT;
case STEM_INTEGER_WIDTH:
CHECK_NULL(seen, integerWidth, status);
return STATE_INTEGER_WIDTH;
case STEM_NUMBERING_SYSTEM:
CHECK_NULL(seen, symbols, status);
return STATE_NUMBERING_SYSTEM;
default:
U_ASSERT(false);
}
}
#endif /* #if !UCONFIG_NO_FORMATTING */

View file

@ -0,0 +1,299 @@
// © 2018 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html
#include "unicode/utypes.h"
#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT
#ifndef __SOURCE_NUMBER_SKELETONS_H__
#define __SOURCE_NUMBER_SKELETONS_H__
#include "number_types.h"
#include "numparse_types.h"
#include "unicode/ucharstrie.h"
using icu::numparse::impl::StringSegment;
U_NAMESPACE_BEGIN namespace number {
namespace impl {
// Forward-declaration
struct SeenMacroProps;
// namespace for enums and entrypoint functions
namespace skeleton {
/**
* While parsing a skeleton, this enum records what type of option we expect to find next.
*/
enum ParseState {
// Section 0: We expect whitespace or a stem, but not an option:
STATE_NULL,
// Section 1: We might accept an option, but it is not required:
STATE_SCIENTIFIC,
STATE_ROUNDER,
STATE_FRACTION_ROUNDER,
// Section 2: An option is required:
STATE_INCREMENT_ROUNDER,
STATE_MEASURE_UNIT,
STATE_PER_MEASURE_UNIT,
STATE_CURRENCY_UNIT,
STATE_INTEGER_WIDTH,
STATE_NUMBERING_SYSTEM,
};
/**
* All possible stem literals have an entry in the StemEnum. The enum name is the kebab case stem
* string literal written in upper snake case.
*
* @see StemToObject
* @see #SERIALIZED_STEM_TRIE
*/
enum StemEnum {
// Section 1: Stems that do not require an option:
STEM_COMPACT_SHORT,
STEM_COMPACT_LONG,
STEM_SCIENTIFIC,
STEM_ENGINEERING,
STEM_NOTATION_SIMPLE,
STEM_BASE_UNIT,
STEM_PERCENT,
STEM_PERMILLE,
STEM_ROUND_INTEGER,
STEM_ROUND_UNLIMITED,
STEM_ROUND_CURRENCY_STANDARD,
STEM_ROUND_CURRENCY_CASH,
STEM_GROUP_OFF,
STEM_GROUP_MIN2,
STEM_GROUP_AUTO,
STEM_GROUP_ON_ALIGNED,
STEM_GROUP_THOUSANDS,
STEM_LATIN,
STEM_UNIT_WIDTH_NARROW,
STEM_UNIT_WIDTH_SHORT,
STEM_UNIT_WIDTH_FULL_NAME,
STEM_UNIT_WIDTH_ISO_CODE,
STEM_UNIT_WIDTH_HIDDEN,
STEM_SIGN_AUTO,
STEM_SIGN_ALWAYS,
STEM_SIGN_NEVER,
STEM_SIGN_ACCOUNTING,
STEM_SIGN_ACCOUNTING_ALWAYS,
STEM_SIGN_EXCEPT_ZERO,
STEM_SIGN_ACCOUNTING_EXCEPT_ZERO,
STEM_DECIMAL_AUTO,
STEM_DECIMAL_ALWAYS,
// Section 2: Stems that DO require an option:
STEM_ROUND_INCREMENT,
STEM_MEASURE_UNIT,
STEM_PER_MEASURE_UNIT,
STEM_CURRENCY,
STEM_INTEGER_WIDTH,
STEM_NUMBERING_SYSTEM,
};
/**
* Creates a NumberFormatter corresponding to the given skeleton string.
*
* @param skeletonString
* A number skeleton string, possibly not in its shortest form.
* @return An UnlocalizedNumberFormatter with behavior defined by the given skeleton string.
*/
UnlocalizedNumberFormatter create(const UnicodeString& skeletonString, UErrorCode& status);
/**
* Create a skeleton string corresponding to the given NumberFormatter.
*
* @param macros
* The NumberFormatter options object.
* @return A skeleton string in normalized form.
*/
UnicodeString generate(const MacroProps& macros, UErrorCode& status);
/**
* Converts from a skeleton string to a MacroProps. This method contains the primary parse loop.
*
* Internal: use the create() endpoint instead of this function.
*/
MacroProps parseSkeleton(const UnicodeString& skeletonString, UErrorCode& status);
/**
* Given that the current segment represents an stem, parse it and save the result.
*
* @return The next state after parsing this stem, corresponding to what subset of options to expect.
*/
ParseState parseStem(const StringSegment& segment, const UCharsTrie& stemTrie, SeenMacroProps& seen,
MacroProps& macros, UErrorCode& status);
/**
* Given that the current segment represents an option, parse it and save the result.
*
* @return The next state after parsing this option, corresponding to what subset of options to
* expect next.
*/
ParseState
parseOption(ParseState stem, const StringSegment& segment, MacroProps& macros, UErrorCode& status);
/**
* Main skeleton generator function. Appends the normalized skeleton for the MacroProps to the given
* StringBuilder.
*
* Internal: use the create() endpoint instead of this function.
*/
void generateSkeleton(const MacroProps& macros, UnicodeString& sb, UErrorCode& status);
} // namespace skeleton
/**
* Namespace for utility methods that convert from StemEnum to corresponding objects or enums. This
* applies to only the "Section 1" stems, those that are well-defined without an option.
*/
namespace stem_to_object {
Notation notation(skeleton::StemEnum stem);
MeasureUnit unit(skeleton::StemEnum stem);
Rounder rounder(skeleton::StemEnum stem);
UGroupingStrategy groupingStrategy(skeleton::StemEnum stem);
UNumberUnitWidth unitWidth(skeleton::StemEnum stem);
UNumberSignDisplay signDisplay(skeleton::StemEnum stem);
UNumberDecimalSeparatorDisplay decimalSeparatorDisplay(skeleton::StemEnum stem);
} // namespace stem_to_object
/**
* Namespace for utility methods that convert from enums to stem strings. More complex object conversions
* take place in the object_to_stem_string namespace.
*/
namespace enum_to_stem_string {
void groupingStrategy(UGroupingStrategy value, UnicodeString& sb);
void unitWidth(UNumberUnitWidth value, UnicodeString& sb);
void signDisplay(UNumberSignDisplay value, UnicodeString& sb);
void decimalSeparatorDisplay(UNumberDecimalSeparatorDisplay value, UnicodeString& sb);
} // namespace enum_to_stem_string
/**
* Namespace for utility methods for processing stems and options that cannot be interpreted literally.
*/
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);
/** @return Whether we successfully found and parsed an exponent sign option. */
bool parseExponentSignOption(const StringSegment& segment, MacroProps& macros, UErrorCode& status);
void parseCurrencyOption(const StringSegment& segment, MacroProps& macros, UErrorCode& status);
void generateCurrencyOption(const CurrencyUnit& currency, UnicodeString& sb, UErrorCode& status);
void parseMeasureUnitOption(const StringSegment& segment, MacroProps& macros, UErrorCode& status);
void generateMeasureUnitOption(const MeasureUnit& measureUnit, UnicodeString& sb, UErrorCode& status);
void parseMeasurePerUnitOption(const StringSegment& segment, MacroProps& macros, UErrorCode& status);
void parseFractionStem(const StringSegment& segment, MacroProps& macros, UErrorCode& status);
void generateFractionStem(int32_t minFrac, int32_t maxFrac, UnicodeString& sb, UErrorCode& status);
void parseDigitsStem(const StringSegment& segment, MacroProps& macros, UErrorCode& status);
void generateDigitsStem(int32_t minSig, int32_t maxSig, UnicodeString& sb, UErrorCode& status);
/** @return Whether we successfully found and parsed a frac-sig option. */
bool parseFracSigOption(const StringSegment& segment, MacroProps& macros, UErrorCode& status);
void parseIncrementOption(const StringSegment& segment, MacroProps& macros, UErrorCode& status);
void generateIncrementOption(double increment, UnicodeString& sb, UErrorCode& status);
/** @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 parseIntegerWidthOption(const StringSegment& segment, MacroProps& macros, UErrorCode& status);
void generateIntegerWidthOption(int32_t minInt, int32_t maxInt, UnicodeString& sb, UErrorCode& status);
void parseNumberingSystemOption(const StringSegment& segment, MacroProps& macros, UErrorCode& status);
void generateNumberingSystemOption(NumberingSystem, UnicodeString& sb, UErrorCode& status);
} // namespace blueprint_helpers
/**
* Namespace for utility methods for generating a token corresponding to each macro-prop. Each method
* returns whether or not a token was written to the string builder.
*/
namespace generator_helpers {
bool notation(const MacroProps& macros, UnicodeString& sb, UErrorCode& status);
bool unit(const MacroProps& macros, UnicodeString& sb, UErrorCode& status);
bool perUnit(const MacroProps& macros, UnicodeString& sb, UErrorCode& status);
bool rounding(const MacroProps& macros, UnicodeString& sb, UErrorCode& status);
bool grouping(const MacroProps& macros, UnicodeString& sb, UErrorCode& status);
bool integerWidth(const MacroProps& macros, UnicodeString& sb, UErrorCode& status);
bool symbols(const MacroProps& macros, UnicodeString& sb, UErrorCode& status);
bool unitWidth(const MacroProps& macros, UnicodeString& sb, UErrorCode& status);
bool sign(const MacroProps& macros, UnicodeString& sb, UErrorCode& status);
bool decimal(const MacroProps& macros, UnicodeString& sb, UErrorCode& status);
} // namespace generator_helpers
/**
* Struct for null-checking.
* In Java, we can just check the object reference. In C++, we need a different method.
*/
struct SeenMacroProps {
bool notation = false;
bool unit = false;
bool perUnit = false;
bool rounder = false;
bool grouper = false;
bool padder = false;
bool integerWidth = false;
bool symbols = false;
bool unitWidth = false;
bool sign = false;
bool decimal = false;
};
} // namespace impl
} // namespace number
U_NAMESPACE_END
#endif //__SOURCE_NUMBER_SKELETONS_H__
#endif /* #if !UCONFIG_NO_FORMATTING */

View file

@ -20,9 +20,9 @@ using namespace icu::numparse;
using namespace icu::numparse::impl;
StringSegment::StringSegment(const UnicodeString& str, parse_flags_t parseFlags)
StringSegment::StringSegment(const UnicodeString& str, bool ignoreCase)
: fStr(str), fStart(0), fEnd(str.length()),
fFoldCase(0 != (parseFlags & PARSE_FLAG_IGNORE_CASE)) {}
fFoldCase(ignoreCase) {}
int32_t StringSegment::getOffset() const {
return fStart;

View file

@ -170,7 +170,7 @@ class ParsedNumber {
*/
class StringSegment : public UMemory, public ::icu::number::impl::CharSequence {
public:
explicit StringSegment(const UnicodeString& str, parse_flags_t parseFlags);
StringSegment(const UnicodeString& str, bool ignoreCase);
int32_t getOffset() const;
@ -248,6 +248,8 @@ class StringSegment : public UMemory, public ::icu::number::impl::CharSequence {
*/
int32_t getCaseSensitivePrefixLength(const UnicodeString& other);
bool operator==(const UnicodeString& other) const;
private:
const UnicodeString fStr;
int32_t fStart;

View file

@ -26,6 +26,7 @@ as the functions are suppose to be called.
It's usually best to have child dependencies called first. */
typedef enum ECleanupI18NType {
UCLN_I18N_START = -1,
UCLN_I18N_NUMBER_SKELETONS,
UCLN_I18N_NUMPARSE_UNISETS,
UCLN_I18N_CURRENCY_SPACING,
UCLN_I18N_SPOOF,

View file

@ -105,7 +105,7 @@ typedef enum UNumberUnitWidth {
*
* @draft ICU 60
*/
UNUM_UNIT_WIDTH_NARROW,
UNUM_UNIT_WIDTH_NARROW,
/**
* Print an abbreviated version of the unit name. Similar to NARROW, but use a slightly wider abbreviation or
@ -121,7 +121,7 @@ typedef enum UNumberUnitWidth {
*
* @draft ICU 60
*/
UNUM_UNIT_WIDTH_SHORT,
UNUM_UNIT_WIDTH_SHORT,
/**
* Print the full name of the unit, without any abbreviations.
@ -132,7 +132,7 @@ typedef enum UNumberUnitWidth {
*
* @draft ICU 60
*/
UNUM_UNIT_WIDTH_FULL_NAME,
UNUM_UNIT_WIDTH_FULL_NAME,
/**
* Use the three-digit ISO XXX code in place of the symbol for displaying currencies. The behavior of this
@ -143,7 +143,7 @@ typedef enum UNumberUnitWidth {
*
* @draft ICU 60
*/
UNUM_UNIT_WIDTH_ISO_CODE,
UNUM_UNIT_WIDTH_ISO_CODE,
/**
* Format the number according to the specified unit, but do not display the unit. For currencies, apply
@ -152,14 +152,14 @@ typedef enum UNumberUnitWidth {
*
* @draft ICU 60
*/
UNUM_UNIT_WIDTH_HIDDEN,
UNUM_UNIT_WIDTH_HIDDEN,
/**
* One more than the highest UNumberUnitWidth value.
*
* @internal ICU 60: The numeric value may change over time; see ICU ticket #12420.
*/
UNUM_UNIT_WIDTH_COUNT
UNUM_UNIT_WIDTH_COUNT
} UNumberUnitWidth;
/**
@ -186,7 +186,8 @@ typedef enum UNumberUnitWidth {
* Note: This enum specifies the strategy for grouping sizes. To set which character to use as the
* grouping separator, use the "symbols" setter.
*
* @draft ICU 61
* @draft ICU 61 -- TODO: This should be renamed to UNumberGroupingStrategy before promoting to stable,
* for consistency with the other enums.
*/
typedef enum UGroupingStrategy {
/**
@ -249,7 +250,14 @@ typedef enum UGroupingStrategy {
*
* @draft ICU 61
*/
UNUM_GROUPING_THOUSANDS
UNUM_GROUPING_THOUSANDS,
/**
* One more than the highest UNumberSignDisplay value.
*
* @internal ICU 62: The numeric value may change over time; see ICU ticket #12420.
*/
UNUM_GROUPING_COUNT
} UGroupingStrategy;
@ -363,22 +371,22 @@ typedef enum UNumberDecimalSeparatorDisplay {
*
* @draft ICU 60
*/
UNUM_DECIMAL_SEPARATOR_AUTO,
UNUM_DECIMAL_SEPARATOR_AUTO,
/**
* Always show the decimal separator, even if there are no digits to display after the separator.
*
* @draft ICU 60
*/
UNUM_DECIMAL_SEPARATOR_ALWAYS,
UNUM_DECIMAL_SEPARATOR_ALWAYS,
/**
* One more than the highest UNumberDecimalSeparatorDisplay value.
*
* @internal ICU 60: The numeric value may change over time; see ICU ticket #12420.
*/
UNUM_DECIMAL_SEPARATOR_COUNT
} UNumberDecimalMarkDisplay;
UNUM_DECIMAL_SEPARATOR_COUNT
} UNumberDecimalSeparatorDisplay;
U_NAMESPACE_BEGIN

View file

@ -107,6 +107,59 @@ class NumberSkeletonImpl {
/** For mapping from ordinal back to StemEnum in Java. */
static final StemEnum[] STEM_ENUM_VALUES = StemEnum.values();
/** A data structure for mapping from stem strings to the stem enum. Built at startup. */
static final String SERIALIZED_STEM_TRIE = buildStemTrie();
static String buildStemTrie() {
CharsTrieBuilder b = new CharsTrieBuilder();
// Section 1:
b.add("compact-short", StemEnum.STEM_COMPACT_SHORT.ordinal());
b.add("compact-long", StemEnum.STEM_COMPACT_LONG.ordinal());
b.add("scientific", StemEnum.STEM_SCIENTIFIC.ordinal());
b.add("engineering", StemEnum.STEM_ENGINEERING.ordinal());
b.add("notation-simple", StemEnum.STEM_NOTATION_SIMPLE.ordinal());
b.add("base-unit", StemEnum.STEM_BASE_UNIT.ordinal());
b.add("percent", StemEnum.STEM_PERCENT.ordinal());
b.add("permille", StemEnum.STEM_PERMILLE.ordinal());
b.add("round-integer", StemEnum.STEM_ROUND_INTEGER.ordinal());
b.add("round-unlimited", StemEnum.STEM_ROUND_UNLIMITED.ordinal());
b.add("round-currency-standard", StemEnum.STEM_ROUND_CURRENCY_STANDARD.ordinal());
b.add("round-currency-cash", StemEnum.STEM_ROUND_CURRENCY_CASH.ordinal());
b.add("group-off", StemEnum.STEM_GROUP_OFF.ordinal());
b.add("group-min2", StemEnum.STEM_GROUP_MIN2.ordinal());
b.add("group-auto", StemEnum.STEM_GROUP_AUTO.ordinal());
b.add("group-on-aligned", StemEnum.STEM_GROUP_ON_ALIGNED.ordinal());
b.add("group-thousands", StemEnum.STEM_GROUP_THOUSANDS.ordinal());
b.add("latin", StemEnum.STEM_LATIN.ordinal());
b.add("unit-width-narrow", StemEnum.STEM_UNIT_WIDTH_NARROW.ordinal());
b.add("unit-width-short", StemEnum.STEM_UNIT_WIDTH_SHORT.ordinal());
b.add("unit-width-full-name", StemEnum.STEM_UNIT_WIDTH_FULL_NAME.ordinal());
b.add("unit-width-iso-code", StemEnum.STEM_UNIT_WIDTH_ISO_CODE.ordinal());
b.add("unit-width-hidden", StemEnum.STEM_UNIT_WIDTH_HIDDEN.ordinal());
b.add("sign-auto", StemEnum.STEM_SIGN_AUTO.ordinal());
b.add("sign-always", StemEnum.STEM_SIGN_ALWAYS.ordinal());
b.add("sign-never", StemEnum.STEM_SIGN_NEVER.ordinal());
b.add("sign-accounting", StemEnum.STEM_SIGN_ACCOUNTING.ordinal());
b.add("sign-accounting-always", StemEnum.STEM_SIGN_ACCOUNTING_ALWAYS.ordinal());
b.add("sign-except-zero", StemEnum.STEM_SIGN_EXCEPT_ZERO.ordinal());
b.add("sign-accounting-except-zero", StemEnum.STEM_SIGN_ACCOUNTING_EXCEPT_ZERO.ordinal());
b.add("decimal-auto", StemEnum.STEM_DECIMAL_AUTO.ordinal());
b.add("decimal-always", StemEnum.STEM_DECIMAL_ALWAYS.ordinal());
// Section 2:
b.add("round-increment", StemEnum.STEM_ROUND_INCREMENT.ordinal());
b.add("measure-unit", StemEnum.STEM_MEASURE_UNIT.ordinal());
b.add("per-measure-unit", StemEnum.STEM_PER_MEASURE_UNIT.ordinal());
b.add("currency", StemEnum.STEM_CURRENCY.ordinal());
b.add("integer-width", StemEnum.STEM_INTEGER_WIDTH.ordinal());
b.add("numbering-system", StemEnum.STEM_NUMBERING_SYSTEM.ordinal());
// Build the CharsTrie
// TODO: Use SLOW or FAST here?
return b.buildCharSequence(StringTrieBuilder.Option.FAST).toString();
}
/**
* Utility class for methods that convert from StemEnum to corresponding objects or enums. This
* applies to only the "Section 1" stems, those that are well-defined without an option.
@ -126,7 +179,7 @@ class NumberSkeletonImpl {
case STEM_NOTATION_SIMPLE:
return Notation.simple();
default:
return null;
throw new AssertionError();
}
}
@ -139,7 +192,7 @@ class NumberSkeletonImpl {
case STEM_PERMILLE:
return NoUnit.PERMILLE;
default:
return null;
throw new AssertionError();
}
}
@ -154,7 +207,7 @@ class NumberSkeletonImpl {
case STEM_ROUND_CURRENCY_CASH:
return Rounder.currency(CurrencyUsage.CASH);
default:
return null;
throw new AssertionError();
}
}
@ -171,7 +224,7 @@ class NumberSkeletonImpl {
case STEM_GROUP_THOUSANDS:
return GroupingStrategy.THOUSANDS;
default:
return null;
return null; // for objects, throw; for enums, return null
}
}
@ -188,7 +241,7 @@ class NumberSkeletonImpl {
case STEM_UNIT_WIDTH_HIDDEN:
return UnitWidth.HIDDEN;
default:
return null;
return null; // for objects, throw; for enums, return null
}
}
@ -209,7 +262,7 @@ class NumberSkeletonImpl {
case STEM_SIGN_ACCOUNTING_EXCEPT_ZERO:
return SignDisplay.ACCOUNTING_EXCEPT_ZERO;
default:
return null;
return null; // for objects, throw; for enums, return null
}
}
@ -220,7 +273,7 @@ class NumberSkeletonImpl {
case STEM_DECIMAL_ALWAYS:
return DecimalSeparatorDisplay.ALWAYS;
default:
return null;
return null; // for objects, throw; for enums, return null
}
}
}
@ -317,58 +370,6 @@ class NumberSkeletonImpl {
}
}
/** A data structure for mapping from stem strings to the stem enum. Built at startup. */
static final String SERIALIZED_STEM_TRIE = buildStemTrie();
static String buildStemTrie() {
CharsTrieBuilder b = new CharsTrieBuilder();
// Section 1:
b.add("compact-short", StemEnum.STEM_COMPACT_SHORT.ordinal());
b.add("compact-long", StemEnum.STEM_COMPACT_LONG.ordinal());
b.add("scientific", StemEnum.STEM_SCIENTIFIC.ordinal());
b.add("engineering", StemEnum.STEM_ENGINEERING.ordinal());
b.add("notation-simple", StemEnum.STEM_NOTATION_SIMPLE.ordinal());
b.add("base-unit", StemEnum.STEM_BASE_UNIT.ordinal());
b.add("percent", StemEnum.STEM_PERCENT.ordinal());
b.add("permille", StemEnum.STEM_PERMILLE.ordinal());
b.add("round-integer", StemEnum.STEM_ROUND_INTEGER.ordinal());
b.add("round-unlimited", StemEnum.STEM_ROUND_UNLIMITED.ordinal());
b.add("round-currency-standard", StemEnum.STEM_ROUND_CURRENCY_STANDARD.ordinal());
b.add("round-currency-cash", StemEnum.STEM_ROUND_CURRENCY_CASH.ordinal());
b.add("group-off", StemEnum.STEM_GROUP_OFF.ordinal());
b.add("group-min2", StemEnum.STEM_GROUP_MIN2.ordinal());
b.add("group-auto", StemEnum.STEM_GROUP_AUTO.ordinal());
b.add("group-on-aligned", StemEnum.STEM_GROUP_ON_ALIGNED.ordinal());
b.add("group-thousands", StemEnum.STEM_GROUP_THOUSANDS.ordinal());
b.add("latin", StemEnum.STEM_LATIN.ordinal());
b.add("unit-width-narrow", StemEnum.STEM_UNIT_WIDTH_NARROW.ordinal());
b.add("unit-width-short", StemEnum.STEM_UNIT_WIDTH_SHORT.ordinal());
b.add("unit-width-full-name", StemEnum.STEM_UNIT_WIDTH_FULL_NAME.ordinal());
b.add("unit-width-iso-code", StemEnum.STEM_UNIT_WIDTH_ISO_CODE.ordinal());
b.add("unit-width-hidden", StemEnum.STEM_UNIT_WIDTH_HIDDEN.ordinal());
b.add("sign-auto", StemEnum.STEM_SIGN_AUTO.ordinal());
b.add("sign-always", StemEnum.STEM_SIGN_ALWAYS.ordinal());
b.add("sign-never", StemEnum.STEM_SIGN_NEVER.ordinal());
b.add("sign-accounting", StemEnum.STEM_SIGN_ACCOUNTING.ordinal());
b.add("sign-accounting-always", StemEnum.STEM_SIGN_ACCOUNTING_ALWAYS.ordinal());
b.add("sign-except-zero", StemEnum.STEM_SIGN_EXCEPT_ZERO.ordinal());
b.add("sign-accounting-except-zero", StemEnum.STEM_SIGN_ACCOUNTING_EXCEPT_ZERO.ordinal());
b.add("decimal-auto", StemEnum.STEM_DECIMAL_AUTO.ordinal());
b.add("decimal-always", StemEnum.STEM_DECIMAL_ALWAYS.ordinal());
// Section 2:
b.add("round-increment", StemEnum.STEM_ROUND_INCREMENT.ordinal());
b.add("measure-unit", StemEnum.STEM_MEASURE_UNIT.ordinal());
b.add("per-measure-unit", StemEnum.STEM_PER_MEASURE_UNIT.ordinal());
b.add("currency", StemEnum.STEM_CURRENCY.ordinal());
b.add("integer-width", StemEnum.STEM_INTEGER_WIDTH.ordinal());
b.add("numbering-system", StemEnum.STEM_NUMBERING_SYSTEM.ordinal());
// TODO: Use SLOW or FAST here?
return b.buildCharSequence(StringTrieBuilder.Option.FAST).toString();
}
/** Kebab case versions of the rounding mode enum. */
static final String[] ROUNDING_MODE_STRINGS = {
"up",
@ -443,6 +444,7 @@ class NumberSkeletonImpl {
CharsTrie stemTrie = new CharsTrie(SERIALIZED_STEM_TRIE, 0);
ParseState stem = ParseState.STATE_NULL;
int offset = 0;
// Primary skeleton parse loop:
while (offset < segment.length()) {
int cp = segment.codePointAt(offset);
@ -722,6 +724,10 @@ class NumberSkeletonImpl {
///// MAIN SKELETON GENERATION FUNCTION /////
/**
* Main skeleton generator function. Appends the normalized skeleton for the MacroProps to the given
* StringBuilder.
*/
private static void generateSkeleton(MacroProps macros, StringBuilder sb) {
// Supported options
if (macros.notation != null && GeneratorHelpers.notation(macros, sb)) {
@ -779,9 +785,14 @@ class NumberSkeletonImpl {
}
}
///// BLUEPRINT HELPER FUNCTIONS (stem and options that cannot be interpreted literally) /////
///// BLUEPRINT HELPER FUNCTIONS /////
/**
* Utility class for methods for processing stems and options that cannot be interpreted literally.
*/
static final class BlueprintHelpers {
/** @return Whether we successfully found and parsed an exponent width option. */
private static boolean parseExponentWidthOption(StringSegment segment, MacroProps macros) {
if (segment.charAt(0) != '+') {
return false;
@ -808,6 +819,7 @@ class NumberSkeletonImpl {
appendMultiple(sb, 'e', minExponentDigits);
}
/** @return Whether we successfully found and parsed an exponent sign option. */
private static boolean parseExponentSignOption(StringSegment segment, MacroProps macros) {
// Get the sign display type out of the CharsTrie data structure.
// TODO: Make this more efficient (avoid object allocation)? It shouldn't be very hot code.
@ -977,6 +989,7 @@ class NumberSkeletonImpl {
}
}
/** @return Whether we successfully found and parsed a frac-sig option. */
private static boolean parseFracSigOption(StringSegment segment, MacroProps macros) {
if (segment.charAt(0) != '@') {
return false;
@ -1047,6 +1060,7 @@ class NumberSkeletonImpl {
sb.append(increment.toPlainString());
}
/** @return Whether we successfully found and parsed a rounding mode. */
private static boolean parseRoundingModeOption(StringSegment segment, MacroProps macros) {
for (int rm = 0; rm < ROUNDING_MODE_STRINGS.length; rm++) {
if (segment.equals(ROUNDING_MODE_STRINGS[rm])) {
@ -1127,6 +1141,10 @@ class NumberSkeletonImpl {
///// STEM GENERATION HELPER FUNCTIONS /////
/**
* Utility class for methods for generating a token corresponding to each macro-prop. Each method
* returns whether or not a token was written to the string builder.
*/
static final class GeneratorHelpers {
private static boolean notation(MacroProps macros, StringBuilder sb) {