@@ -118,6 +118,66 @@ static UnicodeString reserialize(const UnicodeString& s) {
118118 return {};
119119}
120120
121+ [[nodiscard]] InternalValue& MessageFormatter::evalVariableReference (const UnicodeString& fallback,
122+ Environment& env,
123+ const VariableName& var,
124+ MessageContext& context,
125+ UErrorCode &status) const {
126+ // Check if it's local or global
127+ // Note: there is no name shadowing; this is enforced by the parser
128+
129+ // This code implements lazy call-by-need evaluation of locals.
130+ // That is, the environment binds names to a closure, not a resolved value.
131+ // The spec does not require either eager or lazy evaluation.
132+
133+ // NFC-normalize the variable name. See
134+ // https://github.com/unicode-org/message-format-wg/blob/main/spec/syntax.md#names-and-identifiers
135+ const VariableName normalized = StandardFunctions::normalizeNFC (var);
136+
137+ // Look up the variable in the environment
138+ if (env.has (normalized)) {
139+ // `var` is a local -- look it up
140+ InternalValue& rhs = env.lookup (normalized);
141+ // Evaluate the expression using the environment from the closure
142+ // The name of this local variable is the fallback for its RHS.
143+ UnicodeString newFallback (DOLLAR);
144+ newFallback += var;
145+
146+ if (!rhs.isEvaluated ()) {
147+ Closure& c = rhs.asClosure ();
148+ InternalValue& result = evalExpression (newFallback,
149+ c.getEnv (),
150+ c.getExpr (),
151+ context,
152+ status);
153+ // Overwrite the closure with the result of evaluation
154+ if (result.isFallback ()) {
155+ rhs.update (result.asFallback ());
156+ } else {
157+ U_ASSERT (result.isEvaluated ());
158+ // Create an indirection to the result returned
159+ // by evalExpression()
160+ rhs.update (result);
161+ }
162+ return rhs;
163+ }
164+ // If it's already evaluated, just return the value
165+ return rhs;
166+ }
167+ // Variable wasn't found in locals -- check if it's global
168+ InternalValue result = evalArgument (fallback, normalized, context, status);
169+ if (status == U_ILLEGAL_ARGUMENT_ERROR) {
170+ status = U_ZERO_ERROR;
171+ // Unbound variable -- set a resolution error
172+ context.getErrors ().setUnresolvedVariable (var, status);
173+ // Use fallback per
174+ // https://github.com/unicode-org/message-format-wg/blob/main/spec/formatting.md#fallback-resolution
175+ return env.createFallback (varFallback (var), status);
176+ }
177+ // Looking up the global variable succeeded; return it
178+ return env.createUnnamed (std::move (result), status);
179+ }
180+
121181// InternalValues are passed as references into a global environment object
122182// that is live for the duration of one formatter call.
123183// They are mutable references so that they can be updated with a new value
@@ -141,60 +201,7 @@ static UnicodeString reserialize(const UnicodeString& s) {
141201 }
142202 // Variable reference
143203 if (rand.isVariable ()) {
144- // Check if it's local or global
145- // Note: there is no name shadowing; this is enforced by the parser
146- const VariableName& var = rand.asVariable ();
147-
148- // This code implements lazy call-by-need evaluation of locals.
149- // That is, the environment binds names to a closure, not a resolved value.
150- // The spec does not require either eager or lazy evaluation.
151-
152- // NFC-normalize the variable name. See
153- // https://github.com/unicode-org/message-format-wg/blob/main/spec/syntax.md#names-and-identifiers
154- const VariableName normalized = StandardFunctions::normalizeNFC (var);
155-
156- // Look up the variable in the environment
157- if (env.has (normalized)) {
158- // `var` is a local -- look it up
159- InternalValue& rhs = env.lookup (var);
160- // Evaluate the expression using the environment from the closure
161- // The name of this local variable is the fallback for its RHS.
162- UnicodeString newFallback (DOLLAR);
163- newFallback += var;
164-
165- if (!rhs.isEvaluated ()) {
166- Closure& c = rhs.asClosure ();
167- InternalValue& result = evalExpression (newFallback,
168- c.getEnv (),
169- c.getExpr (),
170- context,
171- status);
172- // Overwrite the closure with the result of evaluation
173- if (result.isFallback ()) {
174- rhs.update (result.asFallback ());
175- } else {
176- U_ASSERT (result.isEvaluated ());
177- // Create an indirection to the result returned
178- // by evalExpression()
179- rhs.update (result);
180- }
181- return rhs;
182- }
183- // If it's already evaluated, just return the value
184- return rhs;
185- }
186- // Variable wasn't found in locals -- check if it's global
187- InternalValue result = evalArgument (fallback, var, context, status);
188- if (status == U_ILLEGAL_ARGUMENT_ERROR) {
189- status = U_ZERO_ERROR;
190- // Unbound variable -- set a resolution error
191- context.getErrors ().setUnresolvedVariable (var, status);
192- // Use fallback per
193- // https://github.com/unicode-org/message-format-wg/blob/main/spec/formatting.md#fallback-resolution
194- return env.createFallback (varFallback (var), status);
195- }
196- // Looking up the global variable succeeded; return it
197- return env.createUnnamed (std::move (result), status);
204+ return evalVariableReference (fallback, env, rand.asVariable (), context, status);
198205 }
199206 // Literal
200207 else {
@@ -462,7 +469,7 @@ void MessageFormatter::resolveSelectors(MessageContext& context, Environment& en
462469 // 2. For each expression exp of the message's selectors
463470 for (int32_t i = 0 ; i < dataModel.numSelectors (); i++) {
464471 // 2i. Let rv be the resolved value of exp.
465- InternalValue& rv = evalExpression ({}, env, selectors[i], context, status);
472+ InternalValue& rv = evalVariableReference ({}, env, selectors[i], context, status);
466473 if (rv.isSelectable ()) {
467474 // 2ii. If selection is supported for rv:
468475 // (True if this code has been reached)
@@ -783,6 +790,9 @@ void MessageFormatter::formatSelectors(MessageContext& context,
783790UnicodeString MessageFormatter::formatToString (const MessageArguments& arguments, UErrorCode &status) {
784791 EMPTY_ON_ERROR (status);
785792
793+ // Create a new environment that will store closures for all local variables
794+ Environment* env = Environment::create (status);
795+
786796 // Create a new context with the given arguments and the `errors` structure
787797 MessageContext context (arguments, *errors, status);
788798
0 commit comments