Skip to content

Commit

Permalink
ICU-22762 MF2: Add multiple methods for message formatting with and w…
Browse files Browse the repository at this point in the history
…ithout errors
  • Loading branch information
catamorphism committed Jul 23, 2024
1 parent 788b893 commit 14c1f10
Show file tree
Hide file tree
Showing 5 changed files with 88 additions and 32 deletions.
40 changes: 31 additions & 9 deletions icu4c/source/i18n/messageformat2.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -784,17 +784,10 @@ void MessageFormatter::formatSelectors(MessageContext& context, const Environmen
formatPattern(context, env, pat, status, result);
}

// Note: this is non-const due to the function registry being non-const, which is in turn
// due to the values (`FormatterFactory` objects in the map) having mutable state.
// In other words, formatting a message can mutate the underlying `MessageFormatter` by changing
// state within the factory objects that represent custom formatters.
UnicodeString MessageFormatter::formatToString(const MessageArguments& arguments, UErrorCode &status) {
EMPTY_ON_ERROR(status);
UnicodeString MessageFormatter::formatInContext(MessageContext& context, UErrorCode& status) {

// Create a new environment that will store closures for all local variables
Environment* env = Environment::create(status);
// Create a new context with the given arguments and the `errors` structure
MessageContext context(arguments, *errors, status);

// Check for unresolved variable errors
checkDeclarations(context, env, status);
Expand All @@ -813,8 +806,37 @@ UnicodeString MessageFormatter::formatToString(const MessageArguments& arguments
formatSelectors(context, *globalEnv, status, result);
}
}
// Update status according to all errors seen while formatting

return result;
}

// Note: this is non-const due to the function registry being non-const, which is in turn
// due to the values (`FormatterFactory` objects in the map) having mutable state.
// In other words, formatting a message can mutate the underlying `MessageFormatter` by changing
// state within the factory objects that represent custom formatters.
UnicodeString MessageFormatter::formatToStringUseFallback(const MessageArguments& arguments, UErrorCode &status) {
EMPTY_ON_ERROR(status);

// Create a new context with the given arguments and the `errors` structure
MessageContext context(arguments, *errors, status);

UnicodeString result = formatInContext(context, status);
// Ignore errors
return result;
}

UnicodeString MessageFormatter::formatToStringCheckErrors(const MessageArguments& arguments, UErrorCode &status) {
EMPTY_ON_ERROR(status);

// Create a new context with the given arguments and the `errors` structure
MessageContext context(arguments, *errors, status);

UnicodeString result = formatInContext(context, status);

// Update status for any errors encountered
context.checkErrors(status);
EMPTY_ON_ERROR(status);

return result;
}

Expand Down
50 changes: 41 additions & 9 deletions icu4c/source/i18n/unicode/messageformat2.h
Original file line number Diff line number Diff line change
Expand Up @@ -75,14 +75,26 @@ namespace message2 {
* @param arguments Reference to message arguments
* @param status Input/output error code used to indicate syntax errors, data model
* errors, resolution errors, formatting errors, selection errors, as well
* as other errors (such as memory allocation failures). Partial output
* is still provided in the presence of most error types.
* @return The string result of formatting the message with the given arguments.
* as other errors (such as memory allocation failures).
* @return The string result of formatting the message with the given arguments. Undefined if U_FAILURE(status).
*
* @internal ICU 75 technology preview
* @internal ICU 76 technology preview
* @deprecated This API is for technology preview only.
*/
UnicodeString formatToString(const MessageArguments& arguments, UErrorCode &status);
UnicodeString formatToStringCheckErrors(const MessageArguments& arguments, UErrorCode &status);

/**
* Formats the message to a string, using the data model that was previously set or parsed,
* and the given `arguments` object.
*
* @param arguments Reference to message arguments
* @param status Input/output error code. Not used to indicate MF2 syntax, data model or runtime errors; if those errors are desired, call formatToStringCheckErrors(). May signal other ICU errors such as memory allocation failures.
* @return The string result of formatting the message with the given arguments. In error conditions, fallback strings are used according to the MF2 specification.
*
* @internal ICU 76 technology preview
* @deprecated This API is for technology preview only.
*/
UnicodeString formatToStringUseFallback(const MessageArguments& arguments, UErrorCode &status);

/**
* Not yet implemented; formats the message to a `FormattedMessage` object,
Expand All @@ -92,14 +104,33 @@ namespace message2 {
* @param arguments Reference to message arguments
* @param status Input/output error code used to indicate syntax errors, data model
* errors, resolution errors, formatting errors, selection errors, as well
* as other errors (such as memory allocation failures). Partial output
* is still provided in the presence of most error types.
* as other errors (such as memory allocation failures).
* @return The `FormattedMessage` representing the formatted message.
*
* @internal ICU 75 technology preview
* @internal ICU 76 technology preview
* @deprecated This API is for technology preview only.
*/
FormattedMessage formatCheckErrors(const MessageArguments& arguments, UErrorCode &status) const {
(void) arguments;
if (U_SUCCESS(status)) {
status = U_UNSUPPORTED_ERROR;
}
return FormattedMessage(status);
}

/**
* Not yet implemented; formats the message to a `FormattedMessage` object,
* using the data model that was previously set or parsed,
* and the given `arguments` object.
*
* @param arguments Reference to message arguments
* @param status Input/output error code; used in an analogous way to formatToStringUseFallback().
* @return The `FormattedMessage` representing the formatted message. String parts may include fallback strings as defined in the MF2 specification, if errors occurred while processing the message.
*
* @internal ICU 76 technology preview
* @deprecated This API is for technology preview only.
*/
FormattedMessage format(const MessageArguments& arguments, UErrorCode &status) const {
FormattedMessage formatUseFallback(const MessageArguments& arguments, UErrorCode &status) const {
(void) arguments;
if (U_SUCCESS(status)) {
status = U_UNSUPPORTED_ERROR;
Expand Down Expand Up @@ -294,6 +325,7 @@ namespace message2 {
void resolvePreferences(MessageContext&, UVector&, UVector&, UErrorCode&) const;

// Formatting methods
[[nodiscard]] UnicodeString formatInContext(MessageContext&, UErrorCode&);
[[nodiscard]] FormattedPlaceholder formatLiteral(const data_model::Literal&) const;
void formatPattern(MessageContext&, const Environment&, const data_model::Pattern&, UErrorCode&, UnicodeString&) const;
// Formats a call to a formatting function
Expand Down
16 changes: 8 additions & 8 deletions icu4c/source/test/intltest/messageformat2test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ void TestMessageFormat2::testAPISimple() {
MessageArguments args(argsBuilder, errorCode);

UnicodeString result;
result = mf.formatToString(args, errorCode);
result = mf.formatToStringCheckErrors(args, errorCode);
assertEquals("testAPI", result, "Hello, John!");

mf = builder.setPattern("Today is {$today :date style=full}.", parseError, errorCode)
Expand All @@ -99,7 +99,7 @@ void TestMessageFormat2::testAPISimple() {
argsBuilder.clear();
argsBuilder["today"] = message2::Formattable::forDate(date);
args = MessageArguments(argsBuilder, errorCode);
result = mf.formatToString(args, errorCode);
result = mf.formatToStringCheckErrors(args, errorCode);
assertEquals("testAPI", "Today is Sunday, October 28, 2136.", result);

argsBuilder.clear();
Expand All @@ -117,7 +117,7 @@ void TestMessageFormat2::testAPISimple() {
* * {{{$userName} added {$photoCount} photos to their album.}}", parseError, errorCode)
.setLocale(locale)
.build(errorCode);
result = mf.formatToString(args, errorCode);
result = mf.formatToStringCheckErrors(args, errorCode);
assertEquals("testAPI", "Maria added 12 photos to her album.", result);

delete cal;
Expand Down Expand Up @@ -219,25 +219,25 @@ void TestMessageFormat2::testAPICustomFunctions() {
MessageFormatter mf = mfBuilder.setPattern("Hello {$name :person formality=informal}", parseError, errorCode)
.setLocale(locale)
.build(errorCode);
result = mf.formatToString(arguments, errorCode);
result = mf.formatToStringCheckErrors(arguments, errorCode);
assertEquals("testAPICustomFunctions", U_MF_UNKNOWN_FUNCTION_ERROR, errorCode);

errorCode = U_ZERO_ERROR;
mfBuilder.setFunctionRegistry(functionRegistry).setLocale(locale);

mf = mfBuilder.setPattern("Hello {$name :person formality=informal}", parseError, errorCode)
.build(errorCode);
result = mf.formatToString(arguments, errorCode);
result = mf.formatToStringCheckErrors(arguments, errorCode);
assertEquals("testAPICustomFunctions", "Hello John", result);

mf = mfBuilder.setPattern("Hello {$name :person formality=formal}", parseError, errorCode)
.build(errorCode);
result = mf.formatToString(arguments, errorCode);
result = mf.formatToStringCheckErrors(arguments, errorCode);
assertEquals("testAPICustomFunctions", "Hello Mr. Doe", result);

mf = mfBuilder.setPattern("Hello {$name :person formality=formal length=long}", parseError, errorCode)
.build(errorCode);
result = mf.formatToString(arguments, errorCode);
result = mf.formatToStringCheckErrors(arguments, errorCode);
assertEquals("testAPICustomFunctions", "Hello Mr. John Doe", result);

// By type
Expand All @@ -255,7 +255,7 @@ void TestMessageFormat2::testAPICustomFunctions() {
mf = mfBuilder.setPattern("Hello {$name}", parseError, errorCode)
.setLocale(locale)
.build(errorCode);
result = mf.formatToString(arguments, errorCode);
result = mf.formatToStringCheckErrors(arguments, errorCode);
assertEquals("testAPICustomFunctions", U_ZERO_ERROR, errorCode);
// Expect "Hello John" because in the custom function we registered,
// "informal" is the default formality and "length" is the default length
Expand Down
6 changes: 1 addition & 5 deletions icu4c/source/test/intltest/messageformat2test_custom.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -638,14 +638,10 @@ message2::FormattedPlaceholder ResourceManager::format(FormattedPlaceholder&& ar
return errorVal;
}

UErrorCode savedStatus = errorCode;
UnicodeString result = mf.formatToString(arguments, errorCode);
// Here, we want to ignore errors (this matches the behavior in the ICU4J test).
// For example: we want $gcase to default to "$gcase" if the gcase option was
// omitted.
if (U_FAILURE(errorCode)) {
errorCode = savedStatus;
}
UnicodeString result = mf.formatToStringUseFallback(arguments, errorCode);
return FormattedPlaceholder(arg, FormattedValue(std::move(result)));
} else {
// Properties must be provided
Expand Down
8 changes: 7 additions & 1 deletion icu4c/source/test/intltest/messageformat2test_utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -231,8 +231,14 @@ class TestUtils {
MessageFormatter mf = mfBuilder.build(errorCode);
UnicodeString result;

// Test both endpoints in order to get both the string result,
// and the error code if applicable
if (U_SUCCESS(errorCode)) {
result = mf.formatToString(MessageArguments(testCase.getArguments(), errorCode), errorCode);
result = mf.formatToStringUseFallback(MessageArguments(testCase.getArguments(), errorCode), errorCode);
if (U_SUCCESS(errorCode)) {
UnicodeString resultErrors = mf.formatToStringCheckErrors(MessageArguments(testCase.getArguments(), errorCode), errorCode);
U_ASSERT(U_FAILURE(errorCode) || result == resultErrors);
}
}

if (testCase.expectSuccess() || (testCase.expectedErrorCode() != U_MF_SYNTAX_ERROR
Expand Down

0 comments on commit 14c1f10

Please sign in to comment.