messageformat2_evaluation.cpp (17594B)
1 // © 2024 and later: Unicode, Inc. and others. 2 // License & terms of use: http://www.unicode.org/copyright.html 3 4 #include "unicode/utypes.h" 5 6 #if !UCONFIG_NO_NORMALIZATION 7 8 #if !UCONFIG_NO_FORMATTING 9 10 #if !UCONFIG_NO_MF2 11 12 #include "messageformat2_allocation.h" 13 #include "messageformat2_evaluation.h" 14 #include "messageformat2_function_registry_internal.h" 15 #include "messageformat2_macros.h" 16 #include "uvector.h" // U_ASSERT 17 18 U_NAMESPACE_BEGIN 19 20 // Auxiliary data structures used during formatting a message 21 22 namespace message2 { 23 24 using namespace data_model; 25 26 // Functions 27 // ------------- 28 29 ResolvedFunctionOption::ResolvedFunctionOption(ResolvedFunctionOption&& other) { 30 name = std::move(other.name); 31 value = std::move(other.value); 32 sourceIsLiteral = other.sourceIsLiteral; 33 } 34 35 ResolvedFunctionOption::~ResolvedFunctionOption() {} 36 37 38 const ResolvedFunctionOption* FunctionOptions::getResolvedFunctionOptions(int32_t& len) const { 39 len = functionOptionsLen; 40 U_ASSERT(len == 0 || options != nullptr); 41 return options; 42 } 43 44 FunctionOptions::FunctionOptions(UVector&& optionsVector, UErrorCode& status) { 45 CHECK_ERROR(status); 46 47 functionOptionsLen = optionsVector.size(); 48 options = moveVectorToArray<ResolvedFunctionOption>(optionsVector, status); 49 } 50 51 // Returns false if option doesn't exist 52 UBool FunctionOptions::wasSetFromLiteral(const UnicodeString& key) const { 53 if (options == nullptr) { 54 U_ASSERT(functionOptionsLen == 0); 55 } 56 for (int32_t i = 0; i < functionOptionsLen; i++) { 57 const ResolvedFunctionOption& opt = options[i]; 58 if (opt.getName() == key) { 59 return opt.isLiteral(); 60 } 61 } 62 return false; 63 } 64 65 UBool FunctionOptions::getFunctionOption(std::u16string_view key, Formattable& option) const { 66 if (options == nullptr) { 67 U_ASSERT(functionOptionsLen == 0); 68 } 69 for (int32_t i = 0; i < functionOptionsLen; i++) { 70 const ResolvedFunctionOption& opt = options[i]; 71 if (opt.getName() == key) { 72 option = opt.getValue(); 73 return true; 74 } 75 } 76 return false; 77 } 78 79 UnicodeString FunctionOptions::getStringFunctionOption(std::u16string_view key) const { 80 Formattable option; 81 if (getFunctionOption(key, option)) { 82 if (option.getType() == UFMT_STRING) { 83 UErrorCode localErrorCode = U_ZERO_ERROR; 84 UnicodeString val = option.getString(localErrorCode); 85 U_ASSERT(U_SUCCESS(localErrorCode)); 86 return val; 87 } 88 } 89 // For anything else, including non-string values, return "". 90 // Alternately, could try to stringify the non-string option. 91 // (Currently, no tests require that.) 92 return {}; 93 } 94 95 FunctionOptions& FunctionOptions::operator=(FunctionOptions&& other) noexcept { 96 functionOptionsLen = other.functionOptionsLen; 97 options = other.options; 98 other.functionOptionsLen = 0; 99 other.options = nullptr; 100 return *this; 101 } 102 103 FunctionOptions::FunctionOptions(FunctionOptions&& other) { 104 *this = std::move(other); 105 } 106 107 FunctionOptions::~FunctionOptions() { 108 if (options != nullptr) { 109 delete[] options; 110 options = nullptr; 111 } 112 } 113 114 static bool containsOption(const UVector& opts, const ResolvedFunctionOption& opt) { 115 for (int32_t i = 0; i < opts.size(); i++) { 116 if (static_cast<ResolvedFunctionOption*>(opts[i])->getName() 117 == opt.getName()) { 118 return true; 119 } 120 } 121 return false; 122 } 123 124 // Options in `this` take precedence 125 // `this` can't be used after mergeOptions is called 126 FunctionOptions FunctionOptions::mergeOptions(FunctionOptions&& other, 127 UErrorCode& status) { 128 UVector mergedOptions(status); 129 mergedOptions.setDeleter(uprv_deleteUObject); 130 131 if (U_FAILURE(status)) { 132 return {}; 133 } 134 135 // Create a new vector consisting of the options from this `FunctionOptions` 136 for (int32_t i = 0; i < functionOptionsLen; i++) { 137 mergedOptions.adoptElement(create<ResolvedFunctionOption>(std::move(options[i]), status), 138 status); 139 } 140 141 // Add each option from `other` that doesn't appear in this `FunctionOptions` 142 for (int i = 0; i < other.functionOptionsLen; i++) { 143 // Note: this is quadratic in the length of `options` 144 if (!containsOption(mergedOptions, other.options[i])) { 145 mergedOptions.adoptElement(create<ResolvedFunctionOption>(std::move(other.options[i]), 146 status), 147 status); 148 } 149 } 150 151 delete[] options; 152 options = nullptr; 153 functionOptionsLen = 0; 154 155 return FunctionOptions(std::move(mergedOptions), status); 156 } 157 158 // PrioritizedVariant 159 // ------------------ 160 161 UBool PrioritizedVariant::operator<(const PrioritizedVariant& other) const { 162 if (priority < other.priority) { 163 return true; 164 } 165 return false; 166 } 167 168 PrioritizedVariant::~PrioritizedVariant() {} 169 170 // ---------------- Environments and closures 171 172 Environment* Environment::create(const VariableName& var, Closure&& c, Environment* parent, UErrorCode& errorCode) { 173 NULL_ON_ERROR(errorCode); 174 Environment* result = new NonEmptyEnvironment(var, std::move(c), parent); 175 if (result == nullptr) { 176 errorCode = U_MEMORY_ALLOCATION_ERROR; 177 return nullptr; 178 } 179 return result; 180 } 181 182 Environment* Environment::create(UErrorCode& errorCode) { 183 NULL_ON_ERROR(errorCode); 184 Environment* result = new EmptyEnvironment(); 185 if (result == nullptr) { 186 errorCode = U_MEMORY_ALLOCATION_ERROR; 187 return nullptr; 188 } 189 return result; 190 } 191 192 const Closure& EmptyEnvironment::lookup(const VariableName& v) const { 193 (void) v; 194 U_ASSERT(false); 195 UPRV_UNREACHABLE_EXIT; 196 } 197 198 const Closure& NonEmptyEnvironment::lookup(const VariableName& v) const { 199 if (v == var) { 200 return rhs; 201 } 202 return parent->lookup(v); 203 } 204 205 bool EmptyEnvironment::has(const VariableName& v) const { 206 (void) v; 207 return false; 208 } 209 210 bool NonEmptyEnvironment::has(const VariableName& v) const { 211 if (v == var) { 212 return true; 213 } 214 return parent->has(v); 215 } 216 217 Environment::~Environment() {} 218 NonEmptyEnvironment::~NonEmptyEnvironment() {} 219 EmptyEnvironment::~EmptyEnvironment() {} 220 221 Closure::~Closure() {} 222 223 // MessageContext methods 224 225 void MessageContext::checkErrors(UErrorCode& status) const { 226 CHECK_ERROR(status); 227 errors.checkErrors(status); 228 } 229 230 const Formattable* MessageContext::getGlobal(const VariableName& v, 231 UErrorCode& errorCode) const { 232 return arguments.getArgument(v, errorCode); 233 } 234 235 MessageContext::MessageContext(const MessageArguments& args, 236 const StaticErrors& e, 237 UErrorCode& status) : arguments(args), errors(e, status) {} 238 239 MessageContext::~MessageContext() {} 240 241 // InternalValue 242 // ------------- 243 244 bool InternalValue::isFallback() const { 245 return std::holds_alternative<FormattedPlaceholder>(argument) 246 && std::get_if<FormattedPlaceholder>(&argument)->isFallback(); 247 } 248 249 bool InternalValue::hasNullOperand() const { 250 return std::holds_alternative<FormattedPlaceholder>(argument) 251 && std::get_if<FormattedPlaceholder>(&argument)->isNullOperand(); 252 } 253 254 FormattedPlaceholder InternalValue::takeArgument(UErrorCode& errorCode) { 255 if (U_FAILURE(errorCode)) { 256 return {}; 257 } 258 259 if (std::holds_alternative<FormattedPlaceholder>(argument)) { 260 return std::move(*std::get_if<FormattedPlaceholder>(&argument)); 261 } 262 errorCode = U_ILLEGAL_ARGUMENT_ERROR; 263 return {}; 264 } 265 266 const UnicodeString& InternalValue::getFallback() const { 267 if (std::holds_alternative<FormattedPlaceholder>(argument)) { 268 return std::get_if<FormattedPlaceholder>(&argument)->getFallback(); 269 } 270 return (*std::get_if<InternalValue*>(&argument))->getFallback(); 271 } 272 273 const Selector* InternalValue::getSelector(UErrorCode& errorCode) const { 274 if (U_FAILURE(errorCode)) { 275 return nullptr; 276 } 277 278 if (selector == nullptr) { 279 errorCode = U_ILLEGAL_ARGUMENT_ERROR; 280 } 281 return selector; 282 } 283 284 InternalValue::InternalValue(FormattedPlaceholder&& arg) { 285 argument = std::move(arg); 286 selector = nullptr; 287 formatter = nullptr; 288 } 289 290 InternalValue::InternalValue(InternalValue* operand, 291 FunctionOptions&& opts, 292 const FunctionName& functionName, 293 const Formatter* f, 294 const Selector* s) { 295 argument = operand; 296 options = std::move(opts); 297 name = functionName; 298 selector = s; 299 formatter = f; 300 U_ASSERT(selector != nullptr || formatter != nullptr); 301 } 302 303 // `this` cannot be used after calling this method 304 void InternalValue::forceSelection(DynamicErrors& errs, 305 const UnicodeString* keys, 306 int32_t keysLen, 307 UnicodeString* prefs, 308 int32_t& prefsLen, 309 UErrorCode& errorCode) { 310 if (U_FAILURE(errorCode)) { 311 return; 312 } 313 314 if (!canSelect()) { 315 errorCode = U_ILLEGAL_ARGUMENT_ERROR; 316 return; 317 } 318 // Find the argument and complete set of options by traversing `argument` 319 FunctionOptions opts; 320 InternalValue* p = this; 321 FunctionName selectorName = name; 322 323 bool operandSelect = false; 324 while (std::holds_alternative<InternalValue*>(p->argument)) { 325 if (p->name != selectorName) { 326 // Can only compose calls to the same selector 327 errorCode = U_ILLEGAL_ARGUMENT_ERROR; 328 return; 329 } 330 // Very special case to detect something like: 331 // .local $sel = {1 :integer select=exact} .local $bad = {$sel :integer} .match $bad 1 {{ONE}} * {{operand select {$bad}}} 332 // This can be done better once function composition is fully implemented. 333 if (p != this && 334 !p->options.getStringFunctionOption(options::SELECT).isEmpty() 335 && (selectorName == functions::NUMBER || selectorName == functions::INTEGER)) { 336 // In this case, we want to call the selector normally but emit a 337 // `bad-option` error, possibly with the outcome of normal-looking output (with relaxed 338 // error handling) and an error (with strict error handling). 339 operandSelect = true; 340 } 341 // First argument to mergeOptions takes precedence 342 opts = opts.mergeOptions(std::move(p->options), errorCode); 343 if (U_FAILURE(errorCode)) { 344 return; 345 } 346 InternalValue* next = *std::get_if<InternalValue*>(&p->argument); 347 p = next; 348 } 349 FormattedPlaceholder arg = std::move(*std::get_if<FormattedPlaceholder>(&p->argument)); 350 351 // This condition can't be checked in the selector. 352 // Effectively, there are two different kinds of "bad option" errors: 353 // one that can be recovered from (used for select=$var) and one that 354 // can't (used for bad digit size options and other cases). 355 // The checking of the recoverable error has to be done here; otherwise, 356 // the "bad option" signaled by the selector implementation would cause 357 // fallback output to be used when formatting the `*` pattern. 358 bool badSelectOption = !checkSelectOption(); 359 360 selector->selectKey(std::move(arg), std::move(opts), 361 keys, keysLen, 362 prefs, prefsLen, errorCode); 363 if (errorCode == U_MF_SELECTOR_ERROR) { 364 errorCode = U_ZERO_ERROR; 365 errs.setSelectorError(selectorName, errorCode); 366 } else if (errorCode == U_MF_BAD_OPTION) { 367 errorCode = U_ZERO_ERROR; 368 errs.setBadOption(selectorName, errorCode); 369 } else if (operandSelect || badSelectOption) { 370 errs.setRecoverableBadOption(selectorName, errorCode); 371 // In this case, only the `*` variant should match 372 prefsLen = 0; 373 } 374 } 375 376 bool InternalValue::checkSelectOption() const { 377 if (name != UnicodeString("number") && name != UnicodeString("integer")) { 378 return true; 379 } 380 381 // Per the spec, if the "select" option is present, it must have been 382 // set from a literal 383 384 Formattable opt; 385 // Returns false if the `select` option is present and it was not set from a literal 386 387 // OK if the option wasn't present 388 if (!options.getFunctionOption(UnicodeString("select"), opt)) { 389 return true; 390 } 391 // Otherwise, return true if the option was set from a literal 392 return options.wasSetFromLiteral(UnicodeString("select")); 393 } 394 395 FormattedPlaceholder InternalValue::forceFormatting(DynamicErrors& errs, UErrorCode& errorCode) { 396 if (U_FAILURE(errorCode)) { 397 return {}; 398 } 399 400 if (formatter == nullptr && selector == nullptr) { 401 U_ASSERT(std::holds_alternative<FormattedPlaceholder>(argument)); 402 return std::move(*std::get_if<FormattedPlaceholder>(&argument)); 403 } 404 if (formatter == nullptr) { 405 errorCode = U_ILLEGAL_ARGUMENT_ERROR; 406 return {}; 407 } 408 409 FormattedPlaceholder arg; 410 411 if (std::holds_alternative<FormattedPlaceholder>(argument)) { 412 arg = std::move(*std::get_if<FormattedPlaceholder>(&argument)); 413 } else { 414 arg = (*std::get_if<InternalValue*>(&argument))->forceFormatting(errs, 415 errorCode); 416 } 417 418 if (U_FAILURE(errorCode)) { 419 return {}; 420 } 421 422 if (arg.isFallback()) { 423 return arg; 424 } 425 426 // The fallback for a nullary function call is the function name 427 UnicodeString fallback; 428 if (arg.isNullOperand()) { 429 fallback = u":"; 430 fallback += name; 431 } else { 432 fallback = arg.getFallback(); 433 } 434 435 // Very special case for :number select=foo and :integer select=foo 436 // This check can't be done inside the function implementation because 437 // it doesn't have a way to both signal an error and return usable output, 438 // and the spec stipulates that fallback output shouldn't be used in the 439 // case of a bad `select` option to a formatting call. 440 bool badSelect = !checkSelectOption(); 441 442 // Call the function with the argument 443 FormattedPlaceholder result = formatter->format(std::move(arg), std::move(options), errorCode); 444 if (U_SUCCESS(errorCode) && errorCode == U_USING_DEFAULT_WARNING) { 445 // Ignore this warning 446 errorCode = U_ZERO_ERROR; 447 } 448 if (U_FAILURE(errorCode)) { 449 if (errorCode == U_MF_OPERAND_MISMATCH_ERROR) { 450 errorCode = U_ZERO_ERROR; 451 errs.setOperandMismatchError(name, errorCode); 452 } else if (errorCode == U_MF_BAD_OPTION) { 453 errorCode = U_ZERO_ERROR; 454 errs.setBadOption(name, errorCode); 455 } else { 456 errorCode = U_ZERO_ERROR; 457 // Convey any other error generated by the formatter 458 // as a formatting error 459 errs.setFormattingError(name, errorCode); 460 } 461 } 462 // Ignore the output if any error occurred 463 // We don't ignore the output in the case of a Bad Option Error, 464 // because of the select=bad case where we want both an error 465 // and non-fallback output. 466 if (errs.hasFormattingError() || errs.hasBadOptionError()) { 467 return FormattedPlaceholder(fallback); 468 } 469 if (badSelect) { 470 // In this case, we want to set an error but not replace 471 // the output with a fallback 472 errs.setRecoverableBadOption(name, errorCode); 473 } 474 return result; 475 } 476 477 InternalValue& InternalValue::operator=(InternalValue&& other) noexcept { 478 argument = std::move(other.argument); 479 other.argument = nullptr; 480 options = std::move(other.options); 481 name = other.name; 482 selector = other.selector; 483 formatter = other.formatter; 484 other.selector = nullptr; 485 other.formatter = nullptr; 486 487 return *this; 488 } 489 490 InternalValue::~InternalValue() { 491 delete selector; 492 selector = nullptr; 493 delete formatter; 494 formatter = nullptr; 495 if (std::holds_alternative<InternalValue*>(argument)) { 496 delete *std::get_if<InternalValue*>(&argument); 497 argument = nullptr; 498 } 499 } 500 501 } // namespace message2 502 U_NAMESPACE_END 503 504 #endif /* #if !UCONFIG_NO_MF2 */ 505 506 #endif /* #if !UCONFIG_NO_FORMATTING */ 507 508 #endif /* #if !UCONFIG_NO_NORMALIZATION */