Skip to content

Commit 3331aca

Browse files
committed
Tests pass
1 parent d5e887a commit 3331aca

10 files changed

+208
-28
lines changed

icu4c/source/i18n/messageformat2.cpp

Lines changed: 95 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ static UnicodeString functionFallback(const InternalValue& operand,
6969
fallbackToUse += var;
7070
}
7171
// If it exists, create a BaseValue (FunctionValue) for it
72-
LocalPointer<BaseValue> result(BaseValue::create(locale, fallbackToUse, *val, errorCode));
72+
LocalPointer<BaseValue> result(BaseValue::create(locale, fallbackToUse, *val, false, errorCode));
7373
// Add fallback and return an InternalValue
7474
if (U_SUCCESS(errorCode)) {
7575
return InternalValue(result.orphan(), fallbackToUse);
@@ -112,6 +112,7 @@ static UnicodeString reserialize(const UnicodeString& s) {
112112
LocalPointer<BaseValue> val(BaseValue::create(locale,
113113
fallbackToUse,
114114
Formattable(lit.unquoted()),
115+
true,
115116
errorCode));
116117
if (U_SUCCESS(errorCode)) {
117118
return InternalValue(val.orphan(), fallbackToUse);
@@ -156,9 +157,24 @@ static UnicodeString reserialize(const UnicodeString& s) {
156157
rhs.update(result.asFallback());
157158
} else {
158159
U_ASSERT(result.isEvaluated());
160+
161+
// Need to create a new InternalValue such that the inner
162+
// FunctionValue does _not_ return true for wasSetFromLiteral()
163+
const FunctionValue* inner = result.getValue(status);
164+
U_ASSERT(U_SUCCESS(status)); // Already checked that result is evaluated
165+
LocalPointer<FunctionValue> variableValue(static_cast<FunctionValue*>(VariableValue::create(inner, status)));
166+
if (U_FAILURE(status) || !variableValue.isValid()) {
167+
return result;
168+
}
169+
170+
InternalValue wrappedResult(variableValue.orphan(), result.asFallback());
171+
InternalValue& ref = env.createUnnamed(std::move(wrappedResult), status);
172+
if (U_FAILURE(status)) {
173+
return result;
174+
}
159175
// Create an indirection to the result returned
160176
// by evalExpression()
161-
rhs.update(result);
177+
rhs.update(ref);
162178
}
163179
return rhs;
164180
}
@@ -241,7 +257,7 @@ FunctionOptions MessageFormatter::resolveOptions(Environment& env,
241257
}
242258

243259
// The option is resolved; add it to the vector
244-
ResolvedFunctionOption resolvedOpt(k, *optVal);
260+
ResolvedFunctionOption resolvedOpt(k, *optVal, false);
245261
LocalPointer<ResolvedFunctionOption>
246262
p(create<ResolvedFunctionOption>(std::move(resolvedOpt), status));
247263
EMPTY_ON_ERROR(status);
@@ -420,6 +436,81 @@ FunctionContext MessageFormatter::makeFunctionContext(const FunctionOptions& opt
420436
}
421437
}
422438

439+
// Evaluates `rand` and requires the value to be a string, setting `result` to it
440+
// if so, and setting a bad option error if not
441+
bool MessageFormatter::operandToStringWithBadOptionError(MessageContext& context,
442+
Environment& globalEnv,
443+
const Operand& rand,
444+
UnicodeString& result,
445+
UErrorCode& status) const {
446+
EMPTY_ON_ERROR(status);
447+
448+
InternalValue& iVal = evalOperand({}, globalEnv, rand, context, status);
449+
EMPTY_ON_ERROR(status);
450+
const FunctionValue* val = iVal.getValue(status);
451+
U_ASSERT(U_SUCCESS(status));
452+
453+
result = val->getOperand().getString(status);
454+
if (U_FAILURE(status)) {
455+
status = U_ZERO_ERROR;
456+
context.getErrors().setBadOption({}, status);
457+
return false;
458+
}
459+
return true;
460+
}
461+
462+
bool isValidLocale(const UnicodeString& localeID) {
463+
std::string asString;
464+
Locale loc(localeID.toUTF8String(asString).c_str());
465+
int32_t count = 0;
466+
const Locale* availableLocales = Locale::getAvailableLocales(count);
467+
468+
for (int32_t i = 0; i < count; i++) {
469+
if (availableLocales[i] == loc) {
470+
return true;
471+
}
472+
}
473+
return false;
474+
}
475+
476+
// Validates u: options on markup parts -- see
477+
// https://github.com/unicode-org/message-format-wg/blob/main/spec/u-namespace.md
478+
void MessageFormatter::validateUOptionsOnMarkup(MessageContext& context,
479+
Environment& globalEnv,
480+
const Markup& markupPart,
481+
UErrorCode& status) const {
482+
CHECK_ERROR(status);
483+
484+
const OptionMap& opts = markupPart.getOptionsInternal();
485+
for (int32_t i = 0; i < opts.len; i++) {
486+
const Option& opt = opts.options[i];
487+
const UnicodeString& optionName = opt.getName();
488+
const Operand& optionValue = opt.getValue();
489+
490+
if (optionName == options::U_ID) {
491+
UnicodeString ignore;
492+
operandToStringWithBadOptionError(context, globalEnv, optionValue, ignore, status);
493+
} else if (optionName == options::U_LOCALE) {
494+
UnicodeString asString;
495+
if (operandToStringWithBadOptionError(context, globalEnv, optionValue, asString, status)) {
496+
if (!isValidLocale(asString)) {
497+
context.getErrors().setBadOption({}, status);
498+
}
499+
}
500+
} else if (optionName == options::U_DIR) {
501+
UnicodeString asString;
502+
if (operandToStringWithBadOptionError(context, globalEnv, optionValue, asString, status)) {
503+
if (!(asString == options::LTR || asString == options::RTL
504+
|| asString == options::AUTO || asString == options::INHERIT)) {
505+
context.getErrors().setBadOption({}, status);
506+
}
507+
}
508+
}
509+
// Any other options are ignored
510+
}
511+
}
512+
513+
423514
// Formats each text and expression part of a pattern, appending the results to `result`
424515
void MessageFormatter::formatPattern(MessageContext& context,
425516
Environment& globalEnv,
@@ -432,7 +523,7 @@ void MessageFormatter::formatPattern(MessageContext& context,
432523
if (part.isText()) {
433524
result += part.asText();
434525
} else if (part.isMarkup()) {
435-
// Markup is ignored
526+
validateUOptionsOnMarkup(context, globalEnv, part.asMarkup(), status);
436527
} else {
437528
// Format the expression
438529
InternalValue& partVal = evalExpression({}, globalEnv, part.contents(), context, status);

icu4c/source/i18n/messageformat2_evaluation.cpp

Lines changed: 40 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@ using namespace data_model;
2727
// BaseValue
2828
// ---------
2929

30-
BaseValue::BaseValue(const Locale& loc, const UnicodeString& fb, const Formattable& source)
31-
: locale(loc) {
30+
BaseValue::BaseValue(const Locale& loc, const UnicodeString& fb, const Formattable& source, bool wasCreatedFromLiteral)
31+
: locale(loc), fromLiteral(wasCreatedFromLiteral) {
3232
operand = source;
3333
fallback += LEFT_CURLY_BRACE;
3434
fallback += fb;
@@ -38,8 +38,9 @@ BaseValue::BaseValue(const Locale& loc, const UnicodeString& fb, const Formattab
3838
/* static */ BaseValue* BaseValue::create(const Locale& locale,
3939
const UnicodeString& fallback,
4040
const Formattable& source,
41+
bool wasCreatedFromLiteral,
4142
UErrorCode& errorCode) {
42-
return message2::create<BaseValue>(BaseValue(locale, fallback, source), errorCode);
43+
return message2::create<BaseValue>(BaseValue(locale, fallback, source, wasCreatedFromLiteral), errorCode);
4344
}
4445

4546
extern UnicodeString formattableToString(const Locale&, const UBiDiDirection, const Formattable&, UErrorCode&);
@@ -56,6 +57,7 @@ BaseValue& BaseValue::operator=(BaseValue&& other) noexcept {
5657
opts = std::move(other.opts);
5758
locale = other.locale;
5859
fallback = other.fallback;
60+
fromLiteral = other.fromLiteral;
5961

6062
return *this;
6163
}
@@ -64,6 +66,30 @@ BaseValue::BaseValue(BaseValue&& other) {
6466
*this = std::move(other);
6567
}
6668

69+
// VariableValue
70+
// -------------
71+
72+
VariableValue::VariableValue(const FunctionValue* v) : underlyingValue(std::move(v)) {}
73+
74+
/* static */ VariableValue* VariableValue::create(const FunctionValue* v,
75+
UErrorCode& errorCode) {
76+
return message2::create<VariableValue>(VariableValue(std::move(v)), errorCode);
77+
}
78+
79+
VariableValue& VariableValue::operator=(VariableValue&& other) noexcept {
80+
underlyingValue = other.underlyingValue;
81+
82+
return *this;
83+
}
84+
85+
VariableValue::VariableValue(VariableValue&& other) {
86+
*this = std::move(other);
87+
}
88+
89+
VariableValue::~VariableValue() {
90+
underlyingValue = nullptr; // not owned
91+
}
92+
6793
// Functions
6894
// -------------
6995

@@ -72,7 +98,8 @@ ResolvedFunctionOption::ResolvedFunctionOption(ResolvedFunctionOption&& other) {
7298
}
7399

74100
ResolvedFunctionOption::ResolvedFunctionOption(const UnicodeString& n,
75-
const FunctionValue& f) : name(n), value(&f) {}
101+
const FunctionValue& f,
102+
bool b) : name(n), value(&f), thisWasMerged(b) {}
76103

77104
ResolvedFunctionOption::~ResolvedFunctionOption() {
78105
value = nullptr; // value is not owned
@@ -100,7 +127,11 @@ UBool FunctionOptions::wasSetFromLiteral(const std::u16string_view key) const {
100127
for (int32_t i = 0; i < functionOptionsLen; i++) {
101128
const ResolvedFunctionOption& opt = options[i];
102129
if (opt.getName() == key) {
103-
return opt.isLiteral();
130+
// Require both: - opt's value was created from a literal;
131+
// - opt does not originate from merging a previous options map
132+
// with this one
133+
return opt.getValue().wasCreatedFromLiteral()
134+
&& (!opt.wasMerged());
104135
}
105136
}
106137
return false;
@@ -203,9 +234,10 @@ FunctionOptions FunctionOptions::mergeOptions(const FunctionOptions& other,
203234
for (int i = 0; i < other.functionOptionsLen; i++) {
204235
// Note: this is quadratic in the length of `options`
205236
if (!containsOption(mergedOptions, other.options[i])) {
206-
mergedOptions.adoptElement(create<ResolvedFunctionOption>(other.options[i],
207-
status),
208-
status);
237+
const ResolvedFunctionOption& oldOpt = other.options[i];
238+
ResolvedFunctionOption newOpt = ResolvedFunctionOption(oldOpt.name, *oldOpt.value, true);
239+
mergedOptions.adoptElement(create<ResolvedFunctionOption>(newOpt, status),
240+
status);
209241
}
210242
}
211243

icu4c/source/i18n/messageformat2_evaluation.h

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ namespace message2 {
9393
return **std::get_if<LocalPointer<Closure>>(&val);
9494
}
9595
const FunctionValue* getValue(UErrorCode& status) const;
96+
9697
UnicodeString asFallback() const { return fallbackString; }
9798

9899
static LocalPointer<InternalValue> null(UErrorCode& status);
@@ -132,17 +133,19 @@ namespace message2 {
132133
// in a context that expects a FunctionValue.
133134
class BaseValue : public FunctionValue {
134135
public:
135-
static BaseValue* create(const Locale&, const UnicodeString&, const Formattable&, UErrorCode&);
136+
static BaseValue* create(const Locale&, const UnicodeString&, const Formattable&, bool, UErrorCode&);
136137
// Apply default formatters to the argument value
137138
UnicodeString formatToString(UErrorCode&) const override;
138139
UBool isSelectable() const override { return true; }
140+
UBool wasCreatedFromLiteral() const override { return fromLiteral; }
139141
BaseValue() {}
140142
BaseValue(BaseValue&&);
141143
BaseValue& operator=(BaseValue&&) noexcept;
142144
private:
143145
Locale locale;
146+
bool fromLiteral = false;
144147

145-
BaseValue(const Locale&, const UnicodeString&, const Formattable&);
148+
BaseValue(const Locale&, const UnicodeString&, const Formattable&, bool);
146149
}; // class BaseValue
147150

148151
// A NullValue represents the absence of an argument.
@@ -151,6 +154,37 @@ namespace message2 {
151154
virtual UBool isNullOperand() const { return true; }
152155
}; // class NullValue
153156

157+
// A VariableValue wraps another FunctionValue and its sole purpose
158+
// is to override the wasCreatedFromLiteral() method to always return false.
159+
// This makes it easy to implement .local $foo = {exact}: the RHS returns a BaseValue
160+
// such that wasCreatedFromLiteral() is true, but then we can wrap it in a VariableValue,
161+
// which will always return false for this method.
162+
class VariableValue : public FunctionValue {
163+
public:
164+
static VariableValue* create(const FunctionValue*, UErrorCode&);
165+
UBool wasCreatedFromLiteral() const override { return false; }
166+
UnicodeString formatToString(UErrorCode& status) const override { return underlyingValue->formatToString(status); }
167+
const Formattable& getOperand() const override { return underlyingValue->getOperand(); }
168+
const FunctionOptions& getResolvedOptions() const override { return underlyingValue->getResolvedOptions(); }
169+
UBool isSelectable() const override { return underlyingValue->isSelectable(); }
170+
UBool isNullOperand() const override { return underlyingValue->isNullOperand(); }
171+
void selectKeys(const UnicodeString* keys,
172+
int32_t keysLen,
173+
int32_t* prefs,
174+
int32_t& prefsLen,
175+
UErrorCode& status) const override { return underlyingValue->selectKeys(keys, keysLen, prefs, prefsLen, status); }
176+
const UnicodeString& getFunctionName() const override { return underlyingValue->getFunctionName(); }
177+
const UnicodeString& getFallback() const { return underlyingValue->getFallback(); }
178+
VariableValue() {}
179+
virtual ~VariableValue();
180+
VariableValue(VariableValue&&);
181+
VariableValue& operator=(VariableValue&&) noexcept;
182+
private:
183+
const FunctionValue* underlyingValue;
184+
185+
VariableValue(const FunctionValue*);
186+
}; // class VariableValue
187+
154188
// PrioritizedVariant
155189

156190
// For how this class is used, see the references to (integer, variant) tuples

icu4c/source/i18n/messageformat2_function_registry.cpp

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -374,20 +374,16 @@ bool isDigitSizeOption(const UnicodeString& s) {
374374
|| s == UnicodeString("maximumSignificantDigits");
375375
}
376376

377-
/* static */ void StandardFunctions::validateDigitSizeOptions(const FunctionOptions& /* opts */,
377+
/* static */ void StandardFunctions::validateDigitSizeOptions(const FunctionOptions& opts,
378378
UErrorCode& status) {
379379
CHECK_ERROR(status);
380-
381-
// FIXME
382-
/*
383380
for (int32_t i = 0; i < opts.optionsCount(); i++) {
384381
const ResolvedFunctionOption& opt = opts.options[i];
385-
if (isDigitSizeOption(opt.getName()) && !isInteger(opt.getValue())) {
382+
if (isDigitSizeOption(opt.getName()) && !isInteger(opt.getValue().getOperand())) {
386383
status = U_MF_BAD_OPTION;
387384
return;
388385
}
389386
}
390-
*/
391387
}
392388

393389
/* static */ StandardFunctions::Number*

icu4c/source/i18n/messageformat2_function_registry_internal.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ namespace message2 {
2626
// Constants for option names
2727
namespace options {
2828
static constexpr std::u16string_view ALWAYS = u"always";
29+
static constexpr std::u16string_view AUTO = u"auto";
2930
static constexpr std::u16string_view COMPACT = u"compact";
3031
static constexpr std::u16string_view COMPACT_DISPLAY = u"compactDisplay";
3132
static constexpr std::u16string_view DATE_STYLE = u"dateStyle";
@@ -38,8 +39,10 @@ static constexpr std::u16string_view EXCEPT_ZERO = u"exceptZero";
3839
static constexpr std::u16string_view FAILS = u"fails";
3940
static constexpr std::u16string_view FULL_UPPER = u"FULL";
4041
static constexpr std::u16string_view HOUR = u"hour";
42+
static constexpr std::u16string_view INHERIT = u"inherit";
4143
static constexpr std::u16string_view LONG = u"long";
4244
static constexpr std::u16string_view LONG_UPPER = u"LONG";
45+
static constexpr std::u16string_view LTR = u"ltr";
4346
static constexpr std::u16string_view MAXIMUM_FRACTION_DIGITS = u"maximumFractionDigits";
4447
static constexpr std::u16string_view MAXIMUM_SIGNIFICANT_DIGITS = u"maximumSignificantDigits";
4548
static constexpr std::u16string_view MEDIUM_UPPER = u"MEDIUM";
@@ -57,6 +60,7 @@ static constexpr std::u16string_view NUMBERING_SYSTEM = u"numberingSystem";
5760
static constexpr std::u16string_view NUMERIC = u"numeric";
5861
static constexpr std::u16string_view ORDINAL = u"ordinal";
5962
static constexpr std::u16string_view PERCENT_STRING = u"percent";
63+
static constexpr std::u16string_view RTL = u"rtl";
6064
static constexpr std::u16string_view SCIENTIFIC = u"scientific";
6165
static constexpr std::u16string_view SECOND = u"second";
6266
static constexpr std::u16string_view SELECT = u"select";
@@ -66,6 +70,9 @@ static constexpr std::u16string_view SIGN_DISPLAY = u"signDisplay";
6670
static constexpr std::u16string_view STYLE = u"style";
6771
static constexpr std::u16string_view TIME_STYLE = u"timeStyle";
6872
static constexpr std::u16string_view TWO_DIGIT = u"2-digit";
73+
static constexpr std::u16string_view U_DIR = u"u:dir";
74+
static constexpr std::u16string_view U_ID = u"u:id";
75+
static constexpr std::u16string_view U_LOCALE = u"u:locale";
6976
static constexpr std::u16string_view USE_GROUPING = u"useGrouping";
7077
static constexpr std::u16string_view WEEKDAY = u"weekday";
7178
static constexpr std::u16string_view YEAR = u"year";

icu4c/source/i18n/unicode/messageformat2.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -365,6 +365,8 @@ namespace message2 {
365365
[[nodiscard]] InternalValue& evalExpression(const UnicodeString&, Environment&, const data_model::Expression&, MessageContext&, UErrorCode&) const;
366366
[[nodiscard]] FunctionOptions resolveOptions(Environment& env, const OptionMap&, MessageContext&, UErrorCode&) const;
367367
[[nodiscard]] InternalValue& evalOperand(const UnicodeString&, Environment&, const data_model::Operand&, MessageContext&, UErrorCode&) const;
368+
bool operandToStringWithBadOptionError(MessageContext&, Environment&, const Operand&, UnicodeString&, UErrorCode&) const;
369+
void validateUOptionsOnMarkup(MessageContext&, Environment&, const Markup&, UErrorCode&) const;
368370
[[nodiscard]] InternalValue& evalVariableReference(const UnicodeString&, Environment&, const data_model::VariableName&, MessageContext&, UErrorCode&) const;
369371
[[nodiscard]] InternalValue evalArgument(const UnicodeString&, const data_model::VariableName&, MessageContext&, UErrorCode&) const;
370372
void formatSelectors(MessageContext& context, Environment& env, UErrorCode &status, UnicodeString& result) const;

icu4c/source/i18n/unicode/messageformat2_data_model.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -821,6 +821,7 @@ namespace message2 {
821821
virtual ~Builder();
822822
}; // class OptionMap::Builder
823823
private:
824+
friend class message2::MessageFormatter;
824825
friend class message2::Serializer;
825826

826827
bool bogus = false;
@@ -1240,6 +1241,7 @@ namespace message2 {
12401241

12411242
private:
12421243
friend class Builder;
1244+
friend class message2::MessageFormatter;
12431245
friend class message2::Serializer;
12441246

12451247
UMarkupType type;

0 commit comments

Comments
 (0)