1111#include " unicode/messageformat2_data_model.h"
1212#include " unicode/messageformat2_formattable.h"
1313#include " unicode/messageformat2.h"
14+ #include " unicode/normalizer2.h"
1415#include " unicode/unistr.h"
1516#include " messageformat2_allocation.h"
17+ #include " messageformat2_checker.h"
1618#include " messageformat2_evaluation.h"
1719#include " messageformat2_macros.h"
1820
@@ -37,7 +39,7 @@ static Formattable evalLiteral(const Literal& lit) {
3739 // The fallback for a variable name is itself.
3840 UnicodeString str (DOLLAR);
3941 str += var;
40- const Formattable* val = context.getGlobal (var, errorCode);
42+ const Formattable* val = context.getGlobal (* this , var, errorCode);
4143 if (U_SUCCESS (errorCode)) {
4244 return (FormattedPlaceholder (*val, str));
4345 }
@@ -52,9 +54,9 @@ static Formattable evalLiteral(const Literal& lit) {
5254}
5355
5456[[nodiscard]] FormattedPlaceholder MessageFormatter::formatOperand (const Environment& env,
55- const Operand& rand,
56- MessageContext& context,
57- UErrorCode &status) const {
57+ const Operand& rand,
58+ MessageContext& context,
59+ UErrorCode &status) const {
5860 if (U_FAILURE (status)) {
5961 return {};
6062 }
@@ -71,15 +73,19 @@ static Formattable evalLiteral(const Literal& lit) {
7173 // Eager vs. lazy evaluation is an open issue:
7274 // see https://github.com/unicode-org/message-format-wg/issues/299
7375
76+ // NFC-normalize the variable name. See
77+ // https://github.com/unicode-org/message-format-wg/blob/main/spec/syntax.md#names-and-identifiers
78+ const VariableName normalized = normalizeNFC (var);
79+
7480 // Look up the variable in the environment
75- if (env.has (var )) {
81+ if (env.has (normalized )) {
7682 // `var` is a local -- look it up
77- const Closure& rhs = env.lookup (var );
83+ const Closure& rhs = env.lookup (normalized );
7884 // Format the expression using the environment from the closure
7985 return formatExpression (rhs.getEnv (), rhs.getExpr (), context, status);
8086 }
8187 // Variable wasn't found in locals -- check if it's global
82- FormattedPlaceholder result = evalArgument (var , context, status);
88+ FormattedPlaceholder result = evalArgument (normalized , context, status);
8389 if (status == U_ILLEGAL_ARGUMENT_ERROR) {
8490 status = U_ZERO_ERROR;
8591 // Unbound variable -- set a resolution error
@@ -761,6 +767,7 @@ void MessageFormatter::formatSelectors(MessageContext& context, const Environmen
761767UnicodeString MessageFormatter::formatToString (const MessageArguments& arguments, UErrorCode &status) {
762768 EMPTY_ON_ERROR (status);
763769
770+
764771 // Create a new environment that will store closures for all local variables
765772 Environment* env = Environment::create (status);
766773 // Create a new context with the given arguments and the `errors` structure
@@ -813,12 +820,14 @@ void MessageFormatter::check(MessageContext& context, const Environment& localEn
813820
814821 // Check that variable is in scope
815822 const VariableName& var = rand.asVariable ();
823+ UnicodeString normalized = normalizeNFC (var);
824+
816825 // Check local scope
817- if (localEnv.has (var )) {
826+ if (localEnv.has (normalized )) {
818827 return ;
819828 }
820829 // Check global scope
821- context.getGlobal (var , status);
830+ context.getGlobal (* this , normalized , status);
822831 if (status == U_ILLEGAL_ARGUMENT_ERROR) {
823832 status = U_ZERO_ERROR;
824833 context.getErrors ().setUnresolvedVariable (var, status);
@@ -855,7 +864,10 @@ void MessageFormatter::checkDeclarations(MessageContext& context, Environment*&
855864 // memoizing the value of localEnv up to this point
856865
857866 // Add the LHS to the environment for checking the next declaration
858- env = Environment::create (decl.getVariable (), Closure (rhs, *env), env, status);
867+ env = Environment::create (normalizeNFC (decl.getVariable ()),
868+ Closure (rhs, *env),
869+ env,
870+ status);
859871 CHECK_ERROR (status);
860872 }
861873}
0 commit comments