Skip to content

Commit 242d586

Browse files
committed
ICU-22908 MF2 ICU4J: Update spec tests and update implementation for recent spec changes
1 parent 2f348f4 commit 242d586

32 files changed

+33503
-386
lines changed

icu4j/main/a

+32,998
Large diffs are not rendered by default.

icu4j/main/core/src/main/java/com/ibm/icu/message2/MFDataModelValidator.java

+6-1
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,7 @@ private boolean validateDeclarations(List<Declaration> declarations) throws MFPa
140140
private void validateExpression(Expression expression, boolean fromInput)
141141
throws MFParseException {
142142
String argName = null;
143+
boolean wasLiteral = false;
143144
Annotation annotation = null;
144145
if (expression instanceof Literal) {
145146
// ...{foo}... or ...{|foo|}... or ...{123}...
@@ -148,6 +149,7 @@ private void validateExpression(Expression expression, boolean fromInput)
148149
LiteralExpression le = (LiteralExpression) expression;
149150
argName = le.arg.value;
150151
annotation = le.annotation;
152+
wasLiteral = true;
151153
} else if (expression instanceof VariableExpression) {
152154
VariableExpression ve = (VariableExpression) expression;
153155
// ...{$foo :bar opt1=|str| opt2=$x opt3=$y}...
@@ -184,7 +186,10 @@ private void validateExpression(Expression expression, boolean fromInput)
184186
addVariableDeclaration(argName);
185187
} else {
186188
// Remember that we've seen it, to complain if there is a declaration later
187-
declaredVars.add(argName);
189+
if (!wasLiteral) {
190+
// We don't consider {|bar| :func} to be a declaration of a "bar" variable
191+
declaredVars.add(argName);
192+
}
188193
}
189194
}
190195
}

icu4j/main/core/src/main/java/com/ibm/icu/message2/MFParser.java

+13-12
Original file line numberDiff line numberDiff line change
@@ -559,9 +559,9 @@ private MFDataModel.Message getComplexMessage() throws MFParseException {
559559
}
560560
}
561561

562-
// abnf: matcher = match-statement 1*([s] variant)
563-
// abnf: match-statement = match 1*([s] selector)
564-
// abnf: selector = expression
562+
// abnf: matcher = match-statement s variant *([s] variant)
563+
// abnf: match-statement = match 1*(s selector)
564+
// abnf: selector = variable
565565
// abnf: variant = key *(s key) [s] quoted-pattern
566566
// abnf: key = literal / "*"
567567
// abnf: match = %s".match"
@@ -571,17 +571,18 @@ private MFDataModel.SelectMessage getMatch(List<MFDataModel.Declaration> declara
571571
// Look for selectors
572572
List<MFDataModel.Expression> expressions = new ArrayList<>();
573573
while (true) {
574-
// Whitespace not required between selectors:
575-
// match 1*([s] selector)
576-
// Whitespace not required before first variant:
577-
// matcher = match-statement 1*([s] variant)
578-
skipOptionalWhitespaces();
579-
MFDataModel.Expression expression = getPlaceholder();
580-
if (expression == null) {
574+
// Whitespace required between selectors but not required before first variant.
575+
skipMandatoryWhitespaces();
576+
int cp = input.peekChar();
577+
if (cp != '$') {
581578
break;
582579
}
583-
checkCondition(
584-
!(expression instanceof MFDataModel.Markup), "Cannot do selection on markup");
580+
MFDataModel.VariableRef variableRef = getVariableRef();
581+
if (variableRef == null) {
582+
break;
583+
}
584+
MFDataModel.Expression expression =
585+
new MFDataModel.VariableExpression(variableRef, null, new ArrayList<>());
585586
expressions.add(expression);
586587
}
587588

icu4j/main/core/src/main/java/com/ibm/icu/message2/MFSerializer.java

+9-1
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,15 @@ private void selectMessageToString(SelectMessage message) {
7979
result.append(".match");
8080
for (Expression selector : message.selectors) {
8181
result.append(' ');
82-
expressionToString(selector);
82+
if (selector instanceof VariableExpression) {
83+
VariableExpression ve = (VariableExpression) selector;
84+
literalOrVariableRefToString(ve.arg);
85+
} else {
86+
// TODO: we have a (valid?) data model, so do we really want to fail?
87+
// It is very close to release, so I am a bit reluctant to add a throw.
88+
// I tried, and none of the unit tests fail (as expected). But still feels unsafe.
89+
expressionToString(selector);
90+
}
8391
}
8492
for (Variant variant : message.variants) {
8593
variantToString(variant);

icu4j/main/core/src/main/java/com/ibm/icu/message2/NumberFormatterFactory.java

+7-2
Original file line numberDiff line numberDiff line change
@@ -163,14 +163,16 @@ public FormattedPlaceholder format(Object toFormat, Map<String, Object> variable
163163
private static class PluralSelectorImpl implements Selector {
164164
private static final String NO_MATCH = "\uFFFDNO_MATCH\uFFFE"; // Unlikely to show in a key
165165
private final PluralRules rules;
166-
private Map<String, Object> fixedOptions;
167-
private LocalizedNumberFormatter icuFormatter;
166+
private final Map<String, Object> fixedOptions;
167+
private final LocalizedNumberFormatter icuFormatter;
168+
private final String kind;
168169

169170
private PluralSelectorImpl(
170171
Locale locale, PluralRules rules, Map<String, Object> fixedOptions, String kind) {
171172
this.rules = rules;
172173
this.fixedOptions = fixedOptions;
173174
this.icuFormatter = formatterForOptions(locale, fixedOptions, kind);
175+
this.kind = kind;
174176
}
175177

176178
/**
@@ -252,6 +254,9 @@ private boolean matches(Object value, String key, Map<String, Object> variableOp
252254
} else {
253255
return false;
254256
}
257+
if ("integer".equals(kind)) {
258+
valToCheck = valToCheck.intValue();
259+
}
255260

256261
Number keyNrVal = OptUtils.asNumber(key);
257262
if (keyNrVal != null && valToCheck.doubleValue() == keyNrVal.doubleValue()) {

icu4j/main/core/src/test/java/com/ibm/icu/dev/test/message2/CoreTest.java

+33-31
Original file line numberDiff line numberDiff line change
@@ -14,37 +14,39 @@
1414
@SuppressWarnings({"static-method", "javadoc"})
1515
@RunWith(JUnit4.class)
1616
public class CoreTest extends CoreTestFmwk {
17-
private static final String[] JSON_FILES = {"alias-selector-annotations.json",
18-
"duplicate-declarations.json",
19-
"icu-parser-tests.json",
20-
"icu-test-functions.json",
21-
"icu-test-previous-release.json",
22-
"icu-test-selectors.json",
23-
"invalid-number-literals-diagnostics.json",
24-
"invalid-options.json",
25-
"markup.json",
26-
"matches-whitespace.json",
27-
"more-data-model-errors.json",
28-
"more-functions.json",
29-
"resolution-errors.json",
30-
"runtime-errors.json",
31-
"spec/data-model-errors.json",
32-
"spec/syntax-errors.json",
33-
"spec/syntax.json",
34-
"spec/functions/date.json",
35-
"spec/functions/datetime.json",
36-
"spec/functions/integer.json",
37-
"spec/functions/number.json",
38-
"spec/functions/string.json",
39-
"spec/functions/time.json",
40-
"syntax-errors-diagnostics.json",
41-
"syntax-errors-diagnostics-multiline.json",
42-
"syntax-errors-end-of-input.json",
43-
"syntax-errors-reserved.json",
44-
"tricky-declarations.json",
45-
"unsupported-expressions.json",
46-
"unsupported-statements.json",
47-
"valid-tests.json"};
17+
private static final String[] JSON_FILES = {
18+
"alias-selector-annotations.json",
19+
"duplicate-declarations.json",
20+
"icu-parser-tests.json",
21+
"icu-test-functions.json",
22+
"icu-test-previous-release.json",
23+
"icu-test-selectors.json",
24+
"invalid-number-literals-diagnostics.json",
25+
"invalid-options.json",
26+
"markup.json",
27+
"matches-whitespace.json",
28+
"more-data-model-errors.json",
29+
"more-functions.json",
30+
"resolution-errors.json",
31+
"runtime-errors.json",
32+
"spec/data-model-errors.json",
33+
"spec/syntax-errors.json",
34+
"spec/syntax.json",
35+
"spec/functions/date.json",
36+
"spec/functions/datetime.json",
37+
"spec/functions/integer.json",
38+
"spec/functions/number.json",
39+
"spec/functions/string.json",
40+
"spec/functions/time.json",
41+
"syntax-errors-diagnostics.json",
42+
"syntax-errors-diagnostics-multiline.json",
43+
"syntax-errors-end-of-input.json",
44+
"syntax-errors-reserved.json",
45+
"tricky-declarations.json",
46+
"unsupported-expressions.json",
47+
"unsupported-statements.json",
48+
"valid-tests.json"
49+
};
4850

4951
@Test
5052
public void test() throws Exception {

icu4j/main/core/src/test/java/com/ibm/icu/dev/test/message2/CustomFormatterMessageRefTest.java

+3-3
Original file line numberDiff line numberDiff line change
@@ -79,9 +79,9 @@ public String formatToString(Object toFormat, Map<String, Object> variableOption
7979

8080
@BeforeClass
8181
public static void beforeClass() {
82-
PROPERTIES.put("firefox", ".match {$gcase :string} genitive {{Firefoxin}} * {{Firefox}}");
83-
PROPERTIES.put("chrome", ".match {$gcase :string} genitive {{Chromen}} * {{Chrome}}");
84-
PROPERTIES.put("safari", ".match {$gcase :string} genitive {{Safarin}} * {{Safari}}");
82+
PROPERTIES.put("firefox", ".input {$gcase :string} .match $gcase genitive {{Firefoxin}} * {{Firefox}}");
83+
PROPERTIES.put("chrome", ".input {$gcase :string} .match $gcase genitive {{Chromen}} * {{Chrome}}");
84+
PROPERTIES.put("safari", ".input {$gcase :string} .match $gcase genitive {{Safarin}} * {{Safari}}");
8585
}
8686

8787
@Test

icu4j/main/core/src/test/java/com/ibm/icu/dev/test/message2/CustomFormatterPersonTest.java

+3-1
Original file line numberDiff line numberDiff line change
@@ -146,11 +146,13 @@ public void testCustomFunctionsComplexMessage() {
146146
Person malePerson = new Person("Mr.", "John", "Doe");
147147
Person unknownPerson = new Person("Mr./Ms.", "Anonymous", "Doe");
148148
String message = ""
149+
+ ".input {$hostGender :string}\n"
150+
+ ".input {$guestCount :number}\n"
149151
+ ".local $hostName = {$host :person length=long}\n"
150152
+ ".local $guestName = {$guest :person length=long}\n"
151153
+ ".local $guestsOther = {$guestCount :number icu:offset=1}\n"
152154
// + "\n"
153-
+ ".match {$hostGender :icu:gender} {$guestCount :number}\n"
155+
+ ".match $hostGender $guestCount\n"
154156
// + "\n"
155157
+ " female 0 {{{$hostName} does not give a party.}}\n"
156158
+ " female 1 {{{$hostName} invites {$guestName} to her party.}}\n"

icu4j/main/core/src/test/java/com/ibm/icu/dev/test/message2/DefaultTestProperties.java

+23-3
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,37 @@
33

44
package com.ibm.icu.dev.test.message2;
55

6+
import com.google.gson.JsonArray;
7+
import com.google.gson.JsonElement;
8+
69
// See https://github.com/unicode-org/conformance/blob/main/schema/message_fmt2/testgen_schema.json
710

811
// Class corresponding to the json test files.
912
// Since this is serialized by Gson, the field names should match the keys in the .json files.
1013
class DefaultTestProperties {
14+
private static final Object[] NO_ERRORS = {};
1115
// Unused fields ignored
12-
final String locale;
13-
final Object[] expErrors;
16+
private final String locale;
17+
private final JsonElement expErrors;
1418

15-
DefaultTestProperties(String locale, Object[] expErrors) {
19+
DefaultTestProperties(String locale, JsonElement expErrors) {
1620
this.locale = locale;
1721
this.expErrors = expErrors;
1822
}
23+
24+
String getLocale() {
25+
return this.locale;
26+
}
27+
28+
Object[] getExpErrors() {
29+
if (expErrors == null || !expErrors.isJsonArray()) {
30+
return NO_ERRORS;
31+
}
32+
JsonArray arr = expErrors.getAsJsonArray();
33+
Object [] result = new Object[arr.size()];
34+
for (int i = 0; i < result.length; i++) {
35+
result[i] = arr.get(i);
36+
}
37+
return result;
38+
}
1939
}

icu4j/main/core/src/test/java/com/ibm/icu/dev/test/message2/MessageFormat2Test.java

+16-11
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,8 @@ public void testDateFormat() {
129129
@Test
130130
public void testPlural() {
131131
String message = ""
132-
+ ".match {$count :number}\n"
132+
+ ".input {$count :number}\n"
133+
+ ".match $count\n"
133134
+ " 1 {{You have one notification.}}\n"
134135
+ " * {{You have {$count} notifications.}}";
135136

@@ -147,7 +148,8 @@ public void testPlural() {
147148
@Test
148149
public void testPluralOrdinal() {
149150
String message = ""
150-
+ ".match {$place :number select=ordinal}\n"
151+
+ ".input {$place :number select=ordinal}\n"
152+
+ ".match $place\n"
151153
+ " 1 {{You got the gold medal}}\n"
152154
+ " 2 {{You got the silver medal}}\n"
153155
+ " 3 {{You got the bronze medal}}\n"
@@ -332,7 +334,8 @@ public void testFormatterIsCreatedOnce() {
332334
@Test
333335
public void testPluralWithOffset() {
334336
String message = ""
335-
+ ".match {$count :number icu:offset=2}\n"
337+
+ ".input {$count :number icu:offset=2}\n"
338+
+ ".match $count\n"
336339
+ " 1 {{Anna}}\n"
337340
+ " 2 {{Anna and Bob}}\n"
338341
+ " one {{Anna, Bob, and {$count :number icu:offset=2} other guest}}\n"
@@ -361,7 +364,7 @@ public void testPluralWithOffset() {
361364
public void testPluralWithOffsetAndLocalVar() {
362365
String message = ""
363366
+ ".local $foo = {$count :number icu:offset=2}"
364-
+ ".match {$foo :number}\n" // should "inherit" the offset
367+
+ ".match $foo\n" // should "inherit" the offset
365368
+ " 1 {{Anna}}\n"
366369
+ " 2 {{Anna and Bob}}\n"
367370
+ " one {{Anna, Bob, and {$foo} other guest}}\n"
@@ -390,7 +393,7 @@ public void testPluralWithOffsetAndLocalVar() {
390393
public void testPluralWithOffsetAndLocalVar2() {
391394
String message = ""
392395
+ ".local $foo = {$amount :number icu:skeleton=|.00/w|}\n"
393-
+ ".match {$foo :number}\n" // should "inherit" the offset
396+
+ ".match $foo\n" // should "inherit" the offset
394397
+ " 1 {{Last dollar}}\n"
395398
+ " one {{{$foo} dollar}}\n"
396399
+ " * {{{$foo} dollars}}";
@@ -412,7 +415,7 @@ public void testPluralWithOffsetAndLocalVar2() {
412415
public void testPluralWithOffsetAndLocalVar2Options() {
413416
String message = ""
414417
+ ".local $foo = {$amount :number minumumFractionalDigits=2}\n"
415-
+ ".match {$foo :number}\n" // should "inherit" the offset
418+
+ ".match $foo\n" // should "inherit" the offset
416419
+ " 1 {{Last dollar}}\n"
417420
+ " one {{{$foo} dollar}}\n"
418421
+ " * {{{$foo} dollars}}";
@@ -447,7 +450,8 @@ public void testLoopOnLocalVars() {
447450
@Test
448451
public void testVariableOptionsInSelector() {
449452
String messageVar = ""
450-
+ ".match {$count :number icu:offset=$delta}\n"
453+
+ ".input {$count :number icu:offset=$delta}\n"
454+
+ ".match $count\n"
451455
+ " 1 {{A}}\n"
452456
+ " 2 {{A and B}}\n"
453457
+ " one {{A, B, and {$count :number icu:offset=$delta} more character}}\n"
@@ -465,7 +469,8 @@ public void testVariableOptionsInSelector() {
465469
mfVar.formatToString(Args.of("count", 7, "delta", 2)));
466470

467471
String messageVar2 = ""
468-
+ ".match {$count :number icu:offset=$delta}\n"
472+
+ ".input {$count :number icu:offset=$delta}\n"
473+
+ ".match $count\n"
469474
+ " 1 {{Exactly 1}}\n"
470475
+ " 2 {{Exactly 2}}\n"
471476
+ " * {{Count = {$count :number icu:offset=$delta} and delta={$delta}.}}";
@@ -505,7 +510,7 @@ public void testVariableOptionsInSelector() {
505510
public void testVariableOptionsInSelectorWithLocalVar() {
506511
String messageFix = ""
507512
+ ".local $offCount = {$count :number icu:offset=2}"
508-
+ ".match {$offCount :number}\n"
513+
+ ".match $offCount\n"
509514
+ " 1 {{A}}\n"
510515
+ " 2 {{A and B}}\n"
511516
+ " one {{A, B, and {$offCount} more character}}\n"
@@ -520,7 +525,7 @@ public void testVariableOptionsInSelectorWithLocalVar() {
520525

521526
String messageVar = ""
522527
+ ".local $offCount = {$count :number icu:offset=$delta}"
523-
+ ".match {$offCount :number}\n"
528+
+ ".match $offCount\n"
524529
+ " 1 {{A}}\n"
525530
+ " 2 {{A and B}}\n"
526531
+ " one {{A, B, and {$offCount} more character}}\n"
@@ -539,7 +544,7 @@ public void testVariableOptionsInSelectorWithLocalVar() {
539544

540545
String messageVar2 = ""
541546
+ ".local $offCount = {$count :number icu:offset=$delta}"
542-
+ ".match {$offCount :number}\n"
547+
+ ".match $offCount\n"
543548
+ " 1 {{Exactly 1}}\n"
544549
+ " 2 {{Exactly 2}}\n"
545550
+ " * {{Count = {$count}, OffCount = {$offCount}, and delta={$delta}.}}";

icu4j/main/core/src/test/java/com/ibm/icu/dev/test/message2/Mf2IcuTest.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,8 @@ public void testSimpleFormat() {
7474
@Test
7575
public void testSelectFormatToPattern() {
7676
String pattern = ""
77-
+ ".match {$userGender :string}\n"
77+
+ ".input {$userGender :string}\n"
78+
+ ".match $userGender\n"
7879
+ " female {{{$userName} est all\u00E9e \u00E0 Paris.}}"
7980
+ " * {{{$userName} est all\u00E9 \u00E0 Paris.}}"
8081
;

0 commit comments

Comments
 (0)