1 // © 2024 and later: Unicode, Inc. and others.
2
3 #include "unicode/utypes.h"
4
5 #if !UCONFIG_NO_FORMATTING
6
7 #if !UCONFIG_NO_MF2
8
9 #include "unicode/calendar.h"
10 #include "messageformat2test.h"
11
12 using namespace icu::message2;
13
14 /*
15 TODO: Tests need to be unified in a single format that
16 both ICU4C and ICU4J can use, rather than being embedded in code.
17
18 Tests are included in their current state to give a sense of
19 how much test coverage has been achieved. Most of the testing is
20 of the parser/serializer; the formatter needs to be tested more
21 thoroughly.
22 */
23
24 /*
25 Tests reflect the syntax specified in
26
27 https://github.com/unicode-org/message-format-wg/commits/main/spec/message.abnf
28
29 as of the following commit from 2023-05-09:
30 https://github.com/unicode-org/message-format-wg/commit/194f6efcec5bf396df36a19bd6fa78d1fa2e0867
31
32 */
33
34 static const int32_t numValidTestCases = 55;
35 TestResult validTestCases[] = {
36 {"hello {|4.2| :number}", "hello 4.2"},
37 {"hello {|4.2| :number minimumFractionDigits=2}", "hello 4.20"},
38 {"hello {|4.2| :number minimumFractionDigits = 2}", "hello 4.20"},
39 {"hello {|4.2| :number minimumFractionDigits= 2}", "hello 4.20"},
40 {"hello {|4.2| :number minimumFractionDigits =2}", "hello 4.20"},
41 {"hello {|4.2| :number minimumFractionDigits=2 }", "hello 4.20"},
42 {"hello {|4.2| :number minimumFractionDigits=2 bar=3}", "hello 4.20"},
43 {"hello {|4.2| :number minimumFractionDigits=2 bar=3 }", "hello 4.20"},
44 {"hello {|4.2| :number minimumFractionDigits=|2|}", "hello 4.20"},
45 {"content -tag", "content -tag"},
46 {"", ""},
47 // tests for escape sequences in literals
48 {"{|hel\\\\lo|}", "hel\\lo"},
49 {"{|hel\\|lo|}", "hel|lo"},
50 {"{|hel\\|\\\\lo|}", "hel|\\lo"},
51 // tests for text escape sequences
52 {"hel\\{lo", "hel{lo"},
53 {"hel\\}lo", "hel}lo"},
54 {"hel\\\\lo", "hel\\lo"},
55 {"hel\\{\\\\lo", "hel{\\lo"},
56 {"hel\\{\\}lo", "hel{}lo"},
57 // tests for newlines in literals and text
58 {"hello {|wo\nrld|}", "hello wo\nrld"},
59 {"hello wo\nrld", "hello wo\nrld"},
60 // Markup is ignored when formatting to string
61 {"{#tag/} content", " content"},
62 {"{#tag} content", " content"},
63 {"{#tag/} {|content|}", " content"},
64 {"{#tag} {|content|}", " content"},
65 {"{|content|} {#tag/}", "content "},
66 {"{|content|} {#tag}", "content "},
67 {"{/tag} {|content|}", " content"},
68 {"{|content|} {/tag}", "content "},
69 {"{#tag} {|content|} {/tag}", " content "},
70 {"{/tag} {|content|} {#tag}", " content "},
71 {"{#tag/} {|content|} {#tag}", " content "},
72 {"{#tag/} {|content|} {/tag}", " content "},
73 {"{#tag foo=bar/} {|content|}", " content"},
74 {"{#tag foo=bar} {|content|}", " content"},
75 {"{/tag foo=bar} {|content|}", " content"},
76 {"{#tag foo=bar} {|content|} {/tag foo=bar}", " content "},
77 {"{/tag foo=bar} {|content|} {#tag foo=bar}", " content "},
78 {"{#tag foo=bar /} {|content|} {#tag foo=bar}", " content "},
79 {"{#tag foo=bar/} {|content|} {/tag foo=bar}", " content "},
80 // Attributes are ignored
81 {"The value is {horse @horse}.", "The value is horse."},
82 {"hello {|4.2| @number}", "hello 4.2"},
83 {"The value is {horse @horse=cool}.", "The value is horse."},
84 {"hello {|4.2| @number=5}", "hello 4.2"},
85 // Number literals
86 {"{-1}", "-1"},
87 {"{0}", "0"},
88 {"{0.0123}", "0.0123"},
89 {"{1.234e5}", "1.234e5"},
90 {"{1.234E5}", "1.234E5"},
91 {"{1.234E+5}", "1.234E+5"},
92 {"{1.234e-5}", "1.234e-5"},
93 {"{42e5}", "42e5"},
94 {"{42e0}", "42e0"},
95 {"{42e000}", "42e000"},
96 {"{42e369}", "42e369"},
97 };
98
99
100 static const int32_t numResolutionErrors = 3;
101 TestResultError jsonTestCasesResolutionError[] = {
102 {".local $foo = {$bar} .match {$foo :number} one {{one}} * {{other}}", "other", U_MF_UNRESOLVED_VARIABLE_ERROR},
103 {".local $foo = {$bar} .match {$foo :number} one {{one}} * {{other}}", "other", U_MF_UNRESOLVED_VARIABLE_ERROR},
104 {".local $bar = {$none :number} .match {$foo :string} one {{one}} * {{{$bar}}}", "{$none}", U_MF_UNRESOLVED_VARIABLE_ERROR}
105 };
106
107 static const int32_t numReservedErrors = 34;
108 UnicodeString reservedErrors[] = {
109 // tests for reserved syntax
110 "hello {|4.2| %number}",
111 "hello {|4.2| %n|um|ber}",
112 "{+42}",
113 // Private use -- n.b. this implementation doesn't support
114 // any private-use annotations, so it's treated like reserved
115 "hello {|4.2| &num|be|r}",
116 "hello {|4.2| ^num|be|r}",
117 "hello {|4.2| +num|be|r}",
118 "hello {|4.2| ?num|be||r|s}",
119 "hello {|foo| !number}",
120 "hello {|foo| *number}",
121 "hello {#number}",
122 "{<tag}",
123 ".local $bar = {$none ~plural} .match {$foo :string} * {{{$bar}}}",
124 // tests for reserved syntax with escaped chars
125 "hello {|4.2| %num\\\\ber}",
126 "hello {|4.2| %num\\{be\\|r}",
127 "hello {|4.2| %num\\\\\\}ber}",
128 // tests for reserved syntax
129 "hello {|4.2| !}",
130 "hello {|4.2| %}",
131 "hello {|4.2| *}",
132 "hello {|4.2| ^abc|123||5|\\\\}",
133 "hello {|4.2| ^ abc|123||5|\\\\}",
134 "hello {|4.2| ^ abc|123||5|\\\\ \\|def |3.14||2|}",
135 // tests for reserved syntax with trailing whitespace
136 "hello {|4.2| ? }",
137 "hello {|4.2| %xyzz }",
138 "hello {|4.2| >xyzz }",
139 "hello {$foo ~xyzz }",
140 "hello {$x <xyzz }",
141 "{>xyzz }",
142 "{ !xyzz }",
143 "{~xyzz }",
144 "{ <xyzz }",
145 // tests for reserved syntax with space-separated sequences
146 "hello {|4.2| !xy z z }",
147 "hello {|4.2| *num \\\\ b er}",
148 "hello {|4.2| %num \\\\ b |3.14| r }",
149 "hello {|4.2| +num xx \\\\ b |3.14| r }",
150 "hello {$foo +num x \\\\ abcde |3.14| r }",
151 "hello {$foo >num x \\\\ abcde |aaa||3.14||42| r }",
152 "hello {$foo >num x \\\\ abcde |aaa||3.14| |42| r }",
153 0
154 };
155
156 static const int32_t numMatches = 15;
157 UnicodeString matches[] = {
158 // multiple scrutinees, with or without whitespace
159 "match {$foo :string} {$bar :string} when one * {one} when * * {other}",
160 "match {$foo :string} {$bar :string}when one * {one} when * * {other}",
161 "match {$foo :string}{$bar :string} when one * {one} when * * {other}",
162 "match {$foo :string}{$bar :string}when one * {one} when * * {other}",
163 "match{$foo :string} {$bar :string} when one * {one} when * * {other}",
164 "match{$foo :string} {$bar :string}when one * {one} when * * {other}",
165 "match{$foo :string}{$bar :string} when one * {one} when * * {other}",
166 "match{$foo :string}{$bar :string}when one * {one} when * * {other}",
167 // multiple variants, with or without whitespace
168 "match {$foo :string} {$bar :string} when one * {one} when * * {other}",
169 "match {$foo :string} {$bar :string} when one * {one}when * * {other}",
170 "match {$foo :string} {$bar :string}when one * {one} when * * {other}",
171 "match {$foo :string} {$bar :string}when one * {one}when * * {other}",
172 // one or multiple keys, with or without whitespace before pattern
173 "match {$foo :string} {$bar :string} when one *{one} when * * {foo}",
174 "match {$foo :string} {$bar :string} when one * {one} when * * {foo}",
175 "match {$foo :string} {$bar :string} when one * {one} when * * {foo}",
176 0
177 };
178
179 static const int32_t numSyntaxTests = 19;
180 // These patterns are tested to ensure they parse without a syntax error
181 UnicodeString syntaxTests[] = {
182 "hello {|foo| :number }",
183 // zero, one or multiple options, with or without whitespace before '}'
184 "{:foo}",
185 "{:foo }",
186 "{:foo }",
187 "{:foo k=v}",
188 "{:foo k=v }",
189 "{:foo k1=v1 k2=v2}",
190 "{:foo k1=v1 k2=v2 }",
191 // literals or variables followed by space, with or without an annotation following
192 "{|3.14| }",
193 "{|3.14| }",
194 "{|3.14| :foo}",
195 "{|3.14| :foo }",
196 "{$bar }",
197 "{$bar }",
198 "{$bar :foo}",
199 "{$bar :foo }",
200 // Variable names can contain '-'
201 "{$bar-foo}",
202 // Not a syntax error (is a semantic error)
203 ".local $foo = {|hello|} .local $foo = {$foo} {{{$foo}}}",
204 // Unquoted literal -- should work
205 "good {placeholder}",
206 0
207 };
208
209 void
runIndexedTest(int32_t index,UBool exec,const char * & name,char *)210 TestMessageFormat2::runIndexedTest(int32_t index, UBool exec,
211 const char* &name, char* /*par*/) {
212 TESTCASE_AUTO_BEGIN;
213 TESTCASE_AUTO(testAPICustomFunctions);
214 TESTCASE_AUTO(messageFormat1Tests);
215 TESTCASE_AUTO(featureTests);
216 TESTCASE_AUTO(testCustomFunctions);
217 TESTCASE_AUTO(testBuiltInFunctions);
218 TESTCASE_AUTO(testDataModelErrors);
219 TESTCASE_AUTO(testResolutionErrors);
220 TESTCASE_AUTO(testAPI);
221 TESTCASE_AUTO(testAPISimple);
222 TESTCASE_AUTO(testDataModelAPI);
223 TESTCASE_AUTO(testVariousPatterns);
224 TESTCASE_AUTO(testInvalidPatterns);
225 TESTCASE_AUTO(specTests);
226 TESTCASE_AUTO_END;
227 }
228
229 // Needs more tests
testDataModelAPI()230 void TestMessageFormat2::testDataModelAPI() {
231 IcuTestErrorCode errorCode1(*this, "testAPI");
232 UErrorCode errorCode = (UErrorCode) errorCode1;
233
234 using Pattern = data_model::Pattern;
235
236 Pattern::Builder builder(errorCode);
237
238 builder.add("a", errorCode);
239 builder.add("b", errorCode);
240 builder.add("c", errorCode);
241
242 Pattern p = builder.build(errorCode);
243 int32_t i = 0;
244 for (auto iter = p.begin(); iter != p.end(); ++iter) {
245 std::variant<UnicodeString, Expression, Markup> part = *iter;
246 UnicodeString val = *std::get_if<UnicodeString>(&part);
247 if (i == 0) {
248 assertEquals("testDataModelAPI", val, "a");
249 } else if (i == 1) {
250 assertEquals("testDataModelAPI", val, "b");
251 } else if (i == 2) {
252 assertEquals("testDataModelAPI", val, "c");
253 }
254 i++;
255 }
256 assertEquals("testDataModelAPI", i, 3);
257 }
258
259 // Example for design doc -- version without null and error checks
testAPISimple()260 void TestMessageFormat2::testAPISimple() {
261 IcuTestErrorCode errorCode1(*this, "testAPI");
262 UErrorCode errorCode = (UErrorCode) errorCode1;
263 UParseError parseError;
264 Locale locale = "en_US";
265
266 // Since this is the example used in the
267 // design doc, it elides null checks and error checks.
268 // To be used in the test suite, it should include those checks
269 // Null checks and error checks elided
270 MessageFormatter::Builder builder(errorCode);
271 MessageFormatter mf = builder.setPattern(u"Hello, {$userName}!", parseError, errorCode)
272 .build(errorCode);
273
274 std::map<UnicodeString, message2::Formattable> argsBuilder;
275 argsBuilder["userName"] = message2::Formattable("John");
276 MessageArguments args(argsBuilder, errorCode);
277
278 UnicodeString result;
279 result = mf.formatToString(args, errorCode);
280 assertEquals("testAPI", result, "Hello, John!");
281
282 mf = builder.setPattern("Today is {$today :date style=full}.", parseError, errorCode)
283 .setLocale(locale)
284 .build(errorCode);
285
286 Calendar* cal(Calendar::createInstance(errorCode));
287 // Sunday, October 28, 2136 8:39:12 AM PST
288 cal->set(2136, Calendar::OCTOBER, 28, 8, 39, 12);
289 UDate date = cal->getTime(errorCode);
290
291 argsBuilder.clear();
292 argsBuilder["today"] = message2::Formattable::forDate(date);
293 args = MessageArguments(argsBuilder, errorCode);
294 result = mf.formatToString(args, errorCode);
295 assertEquals("testAPI", "Today is Sunday, October 28, 2136.", result);
296
297 argsBuilder.clear();
298 argsBuilder["photoCount"] = message2::Formattable((int64_t) 12);
299 argsBuilder["userGender"] = message2::Formattable("feminine");
300 argsBuilder["userName"] = message2::Formattable("Maria");
301 args = MessageArguments(argsBuilder, errorCode);
302
303 mf = builder.setPattern(".match {$photoCount :number} {$userGender :string}\n\
304 1 masculine {{{$userName} added a new photo to his album.}}\n \
305 1 feminine {{{$userName} added a new photo to her album.}}\n \
306 1 * {{{$userName} added a new photo to their album.}}\n \
307 * masculine {{{$userName} added {$photoCount} photos to his album.}}\n \
308 * feminine {{{$userName} added {$photoCount} photos to her album.}}\n \
309 * * {{{$userName} added {$photoCount} photos to their album.}}", parseError, errorCode)
310 .setLocale(locale)
311 .build(errorCode);
312 result = mf.formatToString(args, errorCode);
313 assertEquals("testAPI", "Maria added 12 photos to her album.", result);
314
315 delete cal;
316 }
317
318 // Design doc example, with more details
testAPI()319 void TestMessageFormat2::testAPI() {
320 IcuTestErrorCode errorCode(*this, "testAPI");
321 TestCase::Builder testBuilder;
322
323 // Pattern: "Hello, {$userName}!"
324 TestCase test(testBuilder.setName("testAPI")
325 .setPattern("Hello, {$userName}!")
326 .setArgument("userName", "John")
327 .setExpected("Hello, John!")
328 .setLocale("en_US")
329 .build());
330 TestUtils::runTestCase(*this, test, errorCode);
331
332 // Pattern: "{Today is {$today ..."
333 LocalPointer<Calendar> cal(Calendar::createInstance(errorCode));
334 // Sunday, October 28, 2136 8:39:12 AM PST
335 cal->set(2136, Calendar::OCTOBER, 28, 8, 39, 12);
336 UDate date = cal->getTime(errorCode);
337
338 test = testBuilder.setName("testAPI")
339 .setPattern("Today is {$today :date style=full}.")
340 .setDateArgument("today", date)
341 .setExpected("Today is Sunday, October 28, 2136.")
342 .setLocale("en_US")
343 .build();
344 TestUtils::runTestCase(*this, test, errorCode);
345
346 // Pattern matching - plural
347 UnicodeString pattern = ".match {$photoCount :string} {$userGender :string}\n\
348 1 masculine {{{$userName} added a new photo to his album.}}\n \
349 1 feminine {{{$userName} added a new photo to her album.}}\n \
350 1 * {{{$userName} added a new photo to their album.}}\n \
351 * masculine {{{$userName} added {$photoCount} photos to his album.}}\n \
352 * feminine {{{$userName} added {$photoCount} photos to her album.}}\n \
353 * * {{{$userName} added {$photoCount} photos to their album.}}";
354
355
356 int64_t photoCount = 12;
357 test = testBuilder.setName("testAPI")
358 .setPattern(pattern)
359 .setArgument("photoCount", photoCount)
360 .setArgument("userGender", "feminine")
361 .setArgument("userName", "Maria")
362 .setExpected("Maria added 12 photos to her album.")
363 .setLocale("en_US")
364 .build();
365 TestUtils::runTestCase(*this, test, errorCode);
366
367 // Built-in functions
368 pattern = ".match {$photoCount :number} {$userGender :string}\n\
369 1 masculine {{{$userName} added a new photo to his album.}}\n \
370 1 feminine {{{$userName} added a new photo to her album.}}\n \
371 1 * {{{$userName} added a new photo to their album.}}\n \
372 * masculine {{{$userName} added {$photoCount} photos to his album.}}\n \
373 * feminine {{{$userName} added {$photoCount} photos to her album.}}\n \
374 * * {{{$userName} added {$photoCount} photos to their album.}}";
375
376 photoCount = 1;
377 test = testBuilder.setName("testAPI")
378 .setPattern(pattern)
379 .setArgument("photoCount", photoCount)
380 .setArgument("userGender", "feminine")
381 .setArgument("userName", "Maria")
382 .setExpected("Maria added a new photo to her album.")
383 .setLocale("en_US")
384 .build();
385 TestUtils::runTestCase(*this, test, errorCode);
386 }
387
388 // Custom functions example from the ICU4C API design doc
389 // Note: error/null checks are omitted
testAPICustomFunctions()390 void TestMessageFormat2::testAPICustomFunctions() {
391 IcuTestErrorCode errorCode1(*this, "testAPICustomFunctions");
392 UErrorCode errorCode = (UErrorCode) errorCode1;
393 UParseError parseError;
394 Locale locale = "en_US";
395
396 // Set up custom function registry
397 MFFunctionRegistry::Builder builder(errorCode);
398 MFFunctionRegistry functionRegistry =
399 builder.adoptFormatter(data_model::FunctionName("person"), new PersonNameFormatterFactory(), errorCode)
400 .build();
401
402 Person* person = new Person(UnicodeString("Mr."), UnicodeString("John"), UnicodeString("Doe"));
403
404 std::map<UnicodeString, message2::Formattable> argsBuilder;
405 argsBuilder["name"] = message2::Formattable(person);
406 MessageArguments arguments(argsBuilder, errorCode);
407
408 MessageFormatter::Builder mfBuilder(errorCode);
409 UnicodeString result;
410 // This fails, because we did not provide a function registry:
411 MessageFormatter mf = mfBuilder.setPattern("Hello {$name :person formality=informal}", parseError, errorCode)
412 .setLocale(locale)
413 .build(errorCode);
414 result = mf.formatToString(arguments, errorCode);
415 assertEquals("testAPICustomFunctions", U_MF_UNKNOWN_FUNCTION_ERROR, errorCode);
416
417 errorCode = U_ZERO_ERROR;
418 mfBuilder.setFunctionRegistry(functionRegistry).setLocale(locale);
419
420 mf = mfBuilder.setPattern("Hello {$name :person formality=informal}", parseError, errorCode)
421 .build(errorCode);
422 result = mf.formatToString(arguments, errorCode);
423 assertEquals("testAPICustomFunctions", "Hello John", result);
424
425 mf = mfBuilder.setPattern("Hello {$name :person formality=formal}", parseError, errorCode)
426 .build(errorCode);
427 result = mf.formatToString(arguments, errorCode);
428 assertEquals("testAPICustomFunctions", "Hello Mr. Doe", result);
429
430 mf = mfBuilder.setPattern("Hello {$name :person formality=formal length=long}", parseError, errorCode)
431 .build(errorCode);
432 result = mf.formatToString(arguments, errorCode);
433 assertEquals("testAPICustomFunctions", "Hello Mr. John Doe", result);
434
435 // By type
436 MFFunctionRegistry::Builder builderByType(errorCode);
437 FunctionName personFormatterName("person");
438 MFFunctionRegistry functionRegistryByType =
439 builderByType.adoptFormatter(personFormatterName,
440 new PersonNameFormatterFactory(),
441 errorCode)
442 .setDefaultFormatterNameByType("person",
443 personFormatterName,
444 errorCode)
445 .build();
446 mfBuilder.setFunctionRegistry(functionRegistryByType);
447 mf = mfBuilder.setPattern("Hello {$name}", parseError, errorCode)
448 .setLocale(locale)
449 .build(errorCode);
450 result = mf.formatToString(arguments, errorCode);
451 assertEquals("testAPICustomFunctions", U_ZERO_ERROR, errorCode);
452 // Expect "Hello John" because in the custom function we registered,
453 // "informal" is the default formality and "length" is the default length
454 assertEquals("testAPICustomFunctions", "Hello John", result);
455 delete person;
456 }
457
testValidPatterns(const TestResult * patterns,int32_t len,IcuTestErrorCode & errorCode)458 void TestMessageFormat2::testValidPatterns(const TestResult* patterns, int32_t len, IcuTestErrorCode& errorCode) {
459 CHECK_ERROR(errorCode);
460
461 TestCase::Builder testBuilder;
462 testBuilder.setName("testOtherJsonPatterns");
463
464 for (int32_t i = 0; i < len - 1; i++) {
465 TestUtils::runTestCase(*this, testBuilder.setPattern(patterns[i].pattern)
466 .setExpected(patterns[i].output)
467 .setExpectSuccess()
468 .build(), errorCode);
469 }
470 }
471
testResolutionErrors(IcuTestErrorCode & errorCode)472 void TestMessageFormat2::testResolutionErrors(IcuTestErrorCode& errorCode) {
473 CHECK_ERROR(errorCode);
474
475 TestCase::Builder testBuilder;
476 testBuilder.setName("testResolutionErrorPattern");
477
478 for (int32_t i = 0; i < numResolutionErrors - 1; i++) {
479 TestUtils::runTestCase(*this, testBuilder.setPattern(jsonTestCasesResolutionError[i].pattern)
480 .setExpected(jsonTestCasesResolutionError[i].output)
481 .setExpectedError(jsonTestCasesResolutionError[i].expected)
482 .build(), errorCode);
483 }
484 }
485
testNoSyntaxErrors(const UnicodeString * patterns,int32_t len,IcuTestErrorCode & errorCode)486 void TestMessageFormat2::testNoSyntaxErrors(const UnicodeString* patterns, int32_t len, IcuTestErrorCode& errorCode) {
487 CHECK_ERROR(errorCode);
488
489 TestCase::Builder testBuilder;
490 testBuilder.setName("testNoSyntaxErrors");
491
492 for (int32_t i = 0; i < len - 1; i++) {
493 TestUtils::runTestCase(*this, testBuilder.setPattern(patterns[i])
494 .setNoSyntaxError()
495 .build(), errorCode);
496 }
497 }
498
testVariousPatterns()499 void TestMessageFormat2::testVariousPatterns() {
500 IcuTestErrorCode errorCode(*this, "jsonTests");
501
502 jsonTests(errorCode);
503 testValidPatterns(validTestCases, numValidTestCases, errorCode);
504 testResolutionErrors(errorCode);
505 testNoSyntaxErrors(reservedErrors, numReservedErrors, errorCode);
506 testNoSyntaxErrors(matches, numMatches, errorCode);
507 testNoSyntaxErrors(syntaxTests, numSyntaxTests, errorCode);
508 }
509
specTests()510 void TestMessageFormat2::specTests() {
511 IcuTestErrorCode errorCode(*this, "specTests");
512
513 runSpecTests(errorCode);
514 }
515
516 /*
517 Tests a single pattern, which is expected to be invalid.
518
519 `testNum`: Test number (only used for diagnostic output)
520 `s`: The pattern string.
521
522 The error is assumed to be on line 0, offset `s.length()`.
523 */
testInvalidPattern(uint32_t testNum,const UnicodeString & s)524 void TestMessageFormat2::testInvalidPattern(uint32_t testNum, const UnicodeString& s) {
525 testInvalidPattern(testNum, s, s.length(), 0);
526 }
527
528 /*
529 Tests a single pattern, which is expected to be invalid.
530
531 `testNum`: Test number (only used for diagnostic output)
532 `s`: The pattern string.
533
534 The error is assumed to be on line 0, offset `expectedErrorOffset`.
535 */
testInvalidPattern(uint32_t testNum,const UnicodeString & s,uint32_t expectedErrorOffset)536 void TestMessageFormat2::testInvalidPattern(uint32_t testNum, const UnicodeString& s, uint32_t expectedErrorOffset) {
537 testInvalidPattern(testNum, s, expectedErrorOffset, 0);
538 }
539
540 /*
541 Tests a single pattern, which is expected to be invalid.
542
543 `testNum`: Test number (only used for diagnostic output)
544 `s`: The pattern string.
545 `expectedErrorOffset`: The expected character offset for the parse error.
546
547 The error is assumed to be on line `expectedErrorLine`, offset `expectedErrorOffset`.
548 */
testInvalidPattern(uint32_t testNum,const UnicodeString & s,uint32_t expectedErrorOffset,uint32_t expectedErrorLine)549 void TestMessageFormat2::testInvalidPattern(uint32_t testNum, const UnicodeString& s, uint32_t expectedErrorOffset, uint32_t expectedErrorLine) {
550 IcuTestErrorCode errorCode(*this, "testInvalidPattern");
551 char testName[50];
552 snprintf(testName, sizeof(testName), "testInvalidPattern: %d", testNum);
553
554 TestCase::Builder testBuilder;
555 testBuilder.setName("testName");
556
557 TestUtils::runTestCase(*this, testBuilder.setPattern(s)
558 .setExpectedError(U_MF_SYNTAX_ERROR)
559 .setExpectedLineNumberAndOffset(expectedErrorLine, expectedErrorOffset)
560 .build(), errorCode);
561 }
562
563 /*
564 Tests a single pattern, which is expected to cause the parser to
565 emit a data model error
566
567 `testNum`: Test number (only used for diagnostic output)
568 `s`: The pattern string.
569 `expectedErrorCode`: the error code expected to be returned by the formatter
570
571 For now, the line and character numbers are not checked
572 */
testSemanticallyInvalidPattern(uint32_t testNum,const UnicodeString & s,UErrorCode expectedErrorCode)573 void TestMessageFormat2::testSemanticallyInvalidPattern(uint32_t testNum, const UnicodeString& s, UErrorCode expectedErrorCode) {
574 IcuTestErrorCode errorCode(*this, "testInvalidPattern");
575
576 char testName[50];
577 snprintf(testName, sizeof(testName), "testSemanticallyInvalidPattern: %d", testNum);
578
579 TestCase::Builder testBuilder;
580 testBuilder.setName("testName").setPattern(s);
581 testBuilder.setExpectedError(expectedErrorCode);
582
583 TestUtils::runTestCase(*this, testBuilder.build(), errorCode);
584 }
585
586 /*
587 Tests a single pattern, which is expected to cause the formatter
588 to emit a resolution error, selection error, or
589 formatting error
590
591 `testNum`: Test number (only used for diagnostic output)
592 `s`: The pattern string.
593 `expectedErrorCode`: the error code expected to be returned by the formatter
594
595 For now, the line and character numbers are not checked
596 */
testRuntimeErrorPattern(uint32_t testNum,const UnicodeString & s,UErrorCode expectedErrorCode)597 void TestMessageFormat2::testRuntimeErrorPattern(uint32_t testNum, const UnicodeString& s, UErrorCode expectedErrorCode) {
598 IcuTestErrorCode errorCode(*this, "testInvalidPattern");
599 char testName[50];
600 snprintf(testName, sizeof(testName), "testInvalidPattern (errors): %u", testNum);
601
602 TestCase::Builder testBuilder;
603 TestUtils::runTestCase(*this, testBuilder.setName(testName)
604 .setPattern(s)
605 .setExpectedError(expectedErrorCode)
606 .build(), errorCode);
607 }
608
609 /*
610 Tests a single pattern, which is expected to cause the formatter
611 to emit a resolution error, selection error, or
612 formatting error
613
614 `testNum`: Test number (only used for diagnostic output)
615 `s`: The pattern string.
616 `expectedErrorCode`: the error code expected to be returned by the formatter
617
618 For now, the line and character numbers are not checked
619 */
testRuntimeWarningPattern(uint32_t testNum,const UnicodeString & s,const UnicodeString & expectedResult,UErrorCode expectedErrorCode)620 void TestMessageFormat2::testRuntimeWarningPattern(uint32_t testNum, const UnicodeString& s, const UnicodeString& expectedResult, UErrorCode expectedErrorCode) {
621 IcuTestErrorCode errorCode(*this, "testInvalidPattern");
622 char testName[50];
623 snprintf(testName, sizeof(testName), "testInvalidPattern (warnings): %u", testNum);
624
625 TestCase::Builder testBuilder;
626 TestUtils::runTestCase(*this, testBuilder.setName(testName)
627 .setPattern(s)
628 .setExpected(expectedResult)
629 .setExpectedError(expectedErrorCode)
630 .build(), errorCode);
631 }
632
testDataModelErrors()633 void TestMessageFormat2::testDataModelErrors() {
634 uint32_t i = 0;
635 IcuTestErrorCode errorCode(*this, "testDataModelErrors");
636
637 // The following tests are syntactically valid but should trigger a data model error
638
639 // Examples taken from https://github.com/unicode-org/message-format-wg/blob/main/spec/formatting.md
640
641 // Variant key mismatch
642 testSemanticallyInvalidPattern(++i, ".match {$foo :number} {$bar :number} one{{one}}", U_MF_VARIANT_KEY_MISMATCH_ERROR);
643 testSemanticallyInvalidPattern(++i, ".match {$foo :number} {$bar :number} one {{one}}", U_MF_VARIANT_KEY_MISMATCH_ERROR);
644 testSemanticallyInvalidPattern(++i, ".match {$foo :number} {$bar :number} one {{one}}", U_MF_VARIANT_KEY_MISMATCH_ERROR);
645
646 testSemanticallyInvalidPattern(++i, ".match {$foo :number} * * {{foo}}", U_MF_VARIANT_KEY_MISMATCH_ERROR);
647 testSemanticallyInvalidPattern(++i, ".match {$one :number}\n\
648 1 2 {{Too many}}\n\
649 * {{Otherwise}}", U_MF_VARIANT_KEY_MISMATCH_ERROR);
650 testSemanticallyInvalidPattern(++i, ".match {$one :number} {$two :number}\n\
651 1 2 {{Two keys}}\n\
652 * {{Missing a key}}\n\
653 * * {{Otherwise}}", U_MF_VARIANT_KEY_MISMATCH_ERROR);
654 testSemanticallyInvalidPattern(++i, ".match {$foo :x} {$bar :x} * {{foo}}", U_MF_VARIANT_KEY_MISMATCH_ERROR);
655
656 // Non-exhaustive patterns
657 testSemanticallyInvalidPattern(++i, ".match {$one :number}\n\
658 1 {{Value is one}}\n\
659 2 {{Value is two}}", U_MF_NONEXHAUSTIVE_PATTERN_ERROR);
660 testSemanticallyInvalidPattern(++i, ".match {$one :number} {$two :number}\n\
661 1 * {{First is one}}\n\
662 * 1 {{Second is one}}", U_MF_NONEXHAUSTIVE_PATTERN_ERROR);
663 testSemanticallyInvalidPattern(++i, ".match {:foo} 1 {{_}}", U_MF_NONEXHAUSTIVE_PATTERN_ERROR);
664 testSemanticallyInvalidPattern(++i, ".match {:foo} other {{_}}", U_MF_NONEXHAUSTIVE_PATTERN_ERROR);
665
666 // Duplicate option names
667 testSemanticallyInvalidPattern(++i, "{:foo a=1 b=2 a=1}", U_MF_DUPLICATE_OPTION_NAME_ERROR);
668 testSemanticallyInvalidPattern(++i, "{:foo a=1 a=1}", U_MF_DUPLICATE_OPTION_NAME_ERROR);
669 testSemanticallyInvalidPattern(++i, "{:foo a=1 a=2}", U_MF_DUPLICATE_OPTION_NAME_ERROR);
670 testSemanticallyInvalidPattern(++i, "{|x| :foo a=1 a=2}", U_MF_DUPLICATE_OPTION_NAME_ERROR);
671 testSemanticallyInvalidPattern(++i, "bad {:placeholder option=x option=x}", U_MF_DUPLICATE_OPTION_NAME_ERROR);
672 testSemanticallyInvalidPattern(++i, "bad {:placeholder ns:option=x ns:option=y}", U_MF_DUPLICATE_OPTION_NAME_ERROR);
673
674 // Missing selector annotation
675 testSemanticallyInvalidPattern(++i, ".match {$one}\n\
676 1 {{Value is one}}\n\
677 * {{Value is not one}}", U_MF_MISSING_SELECTOR_ANNOTATION_ERROR);
678 testSemanticallyInvalidPattern(++i, ".local $one = {|The one|}\n\
679 .match {$one}\n\
680 1 {{Value is one}}\n\
681 * {{Value is not one}}", U_MF_MISSING_SELECTOR_ANNOTATION_ERROR);
682 testSemanticallyInvalidPattern(++i, ".match {|horse| ^private}\n\
683 1 {{The value is one.}}\n \
684 * {{The value is not one.}}", U_MF_MISSING_SELECTOR_ANNOTATION_ERROR);
685 testSemanticallyInvalidPattern(++i, ".match {$foo !select} |1| {{one}} * {{other}}",
686 U_MF_MISSING_SELECTOR_ANNOTATION_ERROR);
687 testSemanticallyInvalidPattern(++i, ".match {$foo ^select} |1| {{one}} * {{other}}",
688 U_MF_MISSING_SELECTOR_ANNOTATION_ERROR);
689 testSemanticallyInvalidPattern(++i, ".input {$foo} .match {$foo} one {{one}} * {{other}}", U_MF_MISSING_SELECTOR_ANNOTATION_ERROR);
690 testSemanticallyInvalidPattern(++i, ".local $foo = {$bar} .match {$foo} one {{one}} * {{other}}", U_MF_MISSING_SELECTOR_ANNOTATION_ERROR);
691
692 // Duplicate declaration errors
693 testSemanticallyInvalidPattern(++i, ".local $x = {|1|} .input {$x :number} {{{$x}}}",
694 U_MF_DUPLICATE_DECLARATION_ERROR);
695 testSemanticallyInvalidPattern(++i, ".input {$x :number} .input {$x :string} {{{$x}}}",
696 U_MF_DUPLICATE_DECLARATION_ERROR);
697 testSemanticallyInvalidPattern(++i, ".input {$foo} .input {$foo} {{_}}", U_MF_DUPLICATE_DECLARATION_ERROR);
698 testSemanticallyInvalidPattern(++i, ".input {$foo} .local $foo = {42} {{_}}", U_MF_DUPLICATE_DECLARATION_ERROR);
699 testSemanticallyInvalidPattern(++i, ".local $foo = {42} .input {$foo} {{_}}", U_MF_DUPLICATE_DECLARATION_ERROR);
700 testSemanticallyInvalidPattern(++i, ".local $foo = {:unknown} .local $foo = {42} {{_}}", U_MF_DUPLICATE_DECLARATION_ERROR);
701 testSemanticallyInvalidPattern(++i, ".local $foo = {$bar} .local $bar = {42} {{_}}", U_MF_DUPLICATE_DECLARATION_ERROR);
702 testSemanticallyInvalidPattern(++i, ".local $foo = {$foo} {{_}}", U_MF_DUPLICATE_DECLARATION_ERROR);
703 testSemanticallyInvalidPattern(++i, ".local $foo = {$bar} .local $bar = {$baz} {{_}}", U_MF_DUPLICATE_DECLARATION_ERROR);
704 testSemanticallyInvalidPattern(++i, ".local $foo = {$bar :func} .local $bar = {$baz} {{_}}", U_MF_DUPLICATE_DECLARATION_ERROR);
705 testSemanticallyInvalidPattern(++i, ".local $foo = {42 :func opt=$foo} {{_}}", U_MF_DUPLICATE_DECLARATION_ERROR);
706 testSemanticallyInvalidPattern(++i, ".local $foo = {42 :func opt=$bar} .local $bar = {42} {{_}}", U_MF_DUPLICATE_DECLARATION_ERROR);
707
708 // Disambiguating unsupported statements from match
709 testSemanticallyInvalidPattern(++i, ".matc {-1} {{hello}}", U_MF_UNSUPPORTED_STATEMENT_ERROR);
710 testSemanticallyInvalidPattern(++i, ".m {-1} {{hello}}", U_MF_UNSUPPORTED_STATEMENT_ERROR);
711
712 TestCase::Builder testBuilder;
713 testBuilder.setName("testDataModelErrors");
714
715 // This should *not* trigger a "missing selector annotation" error
716 TestCase test = testBuilder.setPattern(".local $one = {|The one| :string}\n\
717 .match {$one}\n\
718 1 {{Value is one}}\n\
719 * {{Value is not one}}")
720 .setExpected("Value is not one")
721 .setExpectSuccess()
722 .build();
723 TestUtils::runTestCase(*this, test, errorCode);
724
725 test = testBuilder.setPattern(".local $one = {|The one| :string}\n\
726 .local $two = {$one}\n\
727 .match {$two}\n\
728 1 {{Value is one}}\n\
729 * {{Value is not one}}")
730 .setExpected("Value is not one")
731 .setExpectSuccess()
732 .build();
733 TestUtils::runTestCase(*this, test, errorCode);
734 }
735
testResolutionErrors()736 void TestMessageFormat2::testResolutionErrors() {
737 uint32_t i = 0;
738
739 // The following tests are syntactically valid and free of data model errors,
740 // but should trigger a resolution error
741
742 // Unresolved variable
743 testRuntimeWarningPattern(++i, "{$oops}", "{$oops}", U_MF_UNRESOLVED_VARIABLE_ERROR);
744 // .input of $x but $x is not supplied as an argument -- also unresolved variable
745 testRuntimeWarningPattern(++i, ".input {$x :number} {{{$x}}}", "{$x}", U_MF_UNRESOLVED_VARIABLE_ERROR);
746
747 // Unknown function
748 testRuntimeWarningPattern(++i, "The value is {horse :func}.", "The value is {|horse|}.", U_MF_UNKNOWN_FUNCTION_ERROR);
749 testRuntimeWarningPattern(++i, ".match {|horse| :func}\n\
750 1 {{The value is one.}}\n\
751 * {{The value is not one.}}",
752 "The value is not one.", U_MF_UNKNOWN_FUNCTION_ERROR);
753 // Using formatter as selector
754 // The fallback string will match the '*' variant
755 testRuntimeWarningPattern(++i, ".match {|horse| :number}\n\
756 1 {{The value is one.}}\n\
757 * {{The value is not one.}}", "The value is not one.", U_MF_SELECTOR_ERROR);
758
759 // Using selector as formatter
760 testRuntimeWarningPattern(++i, ".match {|horse| :string}\n\
761 1 {{The value is one.}}\n \
762 * {{{|horse| :string}}}",
763 "{|horse|}", U_MF_FORMATTING_ERROR);
764
765 // Unsupported expressions
766 testRuntimeErrorPattern(++i, "hello {|4.2| !number}", U_MF_UNSUPPORTED_EXPRESSION_ERROR);
767 testRuntimeErrorPattern(++i, "{<tag}", U_MF_UNSUPPORTED_EXPRESSION_ERROR);
768 testRuntimeErrorPattern(++i, ".local $bar = {|42| ~plural} .match {|horse| :string} * {{{$bar}}}",
769 U_MF_UNSUPPORTED_EXPRESSION_ERROR);
770
771 // Selector error
772 // Here, the plural selector returns "no match" so the * variant matches
773 testRuntimeWarningPattern(++i, ".match {|horse| :number}\n\
774 1 {{The value is one.}}\n\
775 * {{The value is not one.}}", "The value is not one.", U_MF_SELECTOR_ERROR);
776 testRuntimeWarningPattern(++i, ".local $sel = {|horse| :number}\n\
777 .match {$sel}\n\
778 1 {{The value is one.}}\n\
779 * {{The value is not one.}}", "The value is not one.", U_MF_SELECTOR_ERROR);
780 }
781
testInvalidPatterns()782 void TestMessageFormat2::testInvalidPatterns() {
783 /*
784 These tests are mostly from the test suite created for the JavaScript implementation of MessageFormat v2:
785 <p>Original JSON file
786 <a href="https://github.com/messageformat/messageformat/blob/master/packages/mf2-messageformat/src/__fixtures/test-messages.json">here</a>.</p>
787 Some have been modified or added to reflect syntax changes that post-date the JSON file.
788
789 */
790 uint32_t i = 0;
791
792 // Unexpected end of input
793 testInvalidPattern(++i, ".local ");
794 testInvalidPattern(++i, ".lo");
795 testInvalidPattern(++i, ".local $foo");
796 testInvalidPattern(++i, ".local $foo = ");
797 testInvalidPattern(++i, "{:fszzz");
798 testInvalidPattern(++i, "{:fszzz ");
799 testInvalidPattern(++i, ".match {$foo} |xyz");
800 testInvalidPattern(++i, "{:f aaa");
801 testInvalidPattern(++i, "{{missing end brace");
802 testInvalidPattern(++i, "{{missing end brace}");
803 testInvalidPattern(++i, "{{missing end {$brace");
804 testInvalidPattern(++i, "{{missing end {$brace}");
805 testInvalidPattern(++i, "{{missing end {$brace}}");
806
807 // Error should be reported at character 0, not end of input
808 testInvalidPattern(++i, "}{|xyz|", 0);
809 testInvalidPattern(++i, "}", 0);
810
811 // %xyz is a valid annotation (`reserved`) so the error should be at the end of input
812 testInvalidPattern(++i, "{{%xyz");
813 // Backslash followed by non-backslash followed by a '{' -- this should be an error
814 // immediately after the first backslash
815 testInvalidPattern(++i, "{{{%\\y{}}", 5);
816
817 // Reserved chars followed by a '|' that doesn't begin a valid literal -- this should be
818 // an error at the first invalid char in the literal
819 testInvalidPattern(++i, "{%abc|\\z}}", 7);
820
821 // Same pattern, but with a valid reserved-char following the erroneous reserved-escape
822 // -- the offset should be the same as with the previous one
823 testInvalidPattern(++i, "{%\\y{p}}", 3);
824 // Erroneous literal inside a reserved string -- the error should be at the first
825 // erroneous literal char
826 testInvalidPattern(++i, "{{{%ab|\\z|cd}}", 8);
827
828 // tests for reserved syntax with bad escaped chars
829 // Single backslash - not allowed
830 testInvalidPattern(++i, "hello {|4.2| %num\\ber}}", 18);
831 // Unescaped '{' -- not allowed
832 testInvalidPattern(++i, "hello {|4.2| %num{be\\|r}}", 17);
833 // Unescaped '}' -- will be interpreted as the end of the reserved
834 // string, and the error will be reported at the index of '|', which is
835 // when the parser determines that "\|" isn't a valid text-escape
836 testInvalidPattern(++i, "hello {|4.2| %num}be\\|r}}", 21);
837 // Unescaped '|' -- will be interpreted as the beginning of a literal
838 // Error at end of input
839 testInvalidPattern(++i, "hello {|4.2| %num\\{be|r}}", 25);
840
841 // Invalid escape sequence in a `text` -- the error should be at the character
842 // following the backslash
843 testInvalidPattern(++i, "a\\qbc", 2);
844
845 // No spaces are required here. The error should be
846 // in the pattern, not before
847 testInvalidPattern(++i, ".match{|y|}|y|{{{|||}}}", 19);
848
849 // Missing spaces betwen keys
850 testInvalidPattern(++i, ".match {|y|}|foo|bar {{{a}}}", 17);
851 testInvalidPattern(++i, ".match {|y|} |quux| |foo|bar {{{a}}}", 25);
852 testInvalidPattern(++i, ".match {|y|} |quux| |foo||bar| {{{a}}}", 26);
853
854 // Error parsing the first key -- the error should be there, not in the
855 // also-erroneous third key
856 testInvalidPattern(++i, ".match {|y|} |\\q| * %{! {z}", 16);
857
858 // Error parsing the second key -- the error should be there, not in the
859 // also-erroneous third key
860 testInvalidPattern(++i, ".match {|y|} * %{! {z} |\\q|", 16);
861
862 // Error parsing the last key -- the error should be there, not in the erroneous
863 // pattern
864 testInvalidPattern(++i, ".match {|y|} * |\\q| {\\z}", 18);
865
866 // Non-expression as scrutinee in pattern -- error should be at the first
867 // non-expression, not the later non-expression
868 testInvalidPattern(++i, ".match {|y|} {\\|} {@} * * * {{a}}", 14);
869
870 // Non-key in variant -- error should be there, not in the next erroneous
871 // variant
872 testInvalidPattern(++i, ".match {|y|} $foo * {{a}} when * :bar {{b}}", 14);
873
874
875 // Error should be within the first erroneous `text` or expression
876 testInvalidPattern(++i, "{{ foo {|bar|} \\q baz ", 16);
877
878 // ':' has to be followed by a function name -- the error should be at the first
879 // whitespace character
880 testInvalidPattern(++i, "{{{: }}}", 4);
881
882 // Expression not starting with a '{'
883 testInvalidPattern(++i, ".local $x = }|foo|}", 12);
884
885 // Error should be at the first declaration not starting with a `.local`
886 testInvalidPattern(++i, ".local $x = {|foo|} .l $y = {|bar|} .local $z {|quux|}", 22);
887
888 // Missing '=' in `.local` declaration
889 testInvalidPattern(++i, ".local $bar {|foo|} {{$bar}}", 12);
890
891 // LHS of declaration doesn't start with a '$'
892 testInvalidPattern(++i, ".local bar = {|foo|} {{$bar}}", 7);
893
894 // `.local` RHS isn't an expression
895 testInvalidPattern(++i, ".local $bar = |foo| {{$bar}}", 14);
896
897 // Trailing characters that are not whitespace
898 testInvalidPattern(++i, "{{extra}}content", 9);
899 testInvalidPattern(++i, ".match {|x|} * {{foo}}extra", 28);
900
901 // Trailing whitespace at end of message should not be accepted either
902 UnicodeString longMsg(".match {$foo :string} {$bar :string} one * {{one}} * * {{other}} ");
903 testInvalidPattern(++i, longMsg, longMsg.length() - 3);
904 testInvalidPattern(++i, "{{hi}} ", 6);
905
906 // Empty expression
907 testInvalidPattern(++i, "empty { }", 8);
908 testInvalidPattern(++i, ".match {} * {{foo}}", 8);
909
910 // ':' not preceding a function name
911 testInvalidPattern(++i, "bad {:}", 6);
912
913 // Missing '=' after option name
914 testInvalidPattern(++i, "{{no-equal {|42| :number m }}}", 27);
915 testInvalidPattern(++i, "{{no-equal {|42| :number minimumFractionDigits 2}}}", 47);
916 testInvalidPattern(++i, "bad {:placeholder option value}", 25);
917
918 // Extra '=' after option value
919 testInvalidPattern(++i, "hello {|4.2| :number min=2=3}", 26),
920 testInvalidPattern(++i, "hello {|4.2| :number min=2max=3}", 26),
921 // Missing whitespace between valid options
922 testInvalidPattern(++i, "hello {|4.2| :number min=|a|max=3}", 28),
923 // Ill-formed RHS of option -- the error should be within the RHS,
924 // not after parsing options
925 testInvalidPattern(++i, "hello {|4.2| :number min=|\\a|}", 27),
926
927
928 // Junk after annotation
929 testInvalidPattern(++i, "no-equal {|42| :number {}", 25);
930
931 // Missing RHS of option
932 testInvalidPattern(++i, "bad {:placeholder option=}", 25);
933 testInvalidPattern(++i, "bad {:placeholder option}", 24);
934
935 // Annotation is not a function or reserved text
936 testInvalidPattern(++i, "bad {$placeholder option}", 18);
937 testInvalidPattern(++i, "no {$placeholder end", 17);
938
939 // Missing expression in selectors
940 testInvalidPattern(++i, ".match * {{foo}}", 8);
941 // Non-expression in selectors
942 testInvalidPattern(++i, ".match |x| * {{foo}}", 7);
943
944 // Missing RHS in variant
945 testInvalidPattern(++i, ".match {|x|} * foo");
946
947 // Text may include newlines; check that the missing closing '}' is
948 // reported on the correct line
949 testInvalidPattern(++i, "{{hello wo\nrld", 3, 1);
950 testInvalidPattern(++i, "{{hello wo\nr\nl\ndddd", 4, 3);
951 // Offset for end-of-input should be 0 here because the line begins
952 // after the '\n', but there is no character after the '\n'
953 testInvalidPattern(++i, "{{hello wo\nr\nl\n", 0, 3);
954
955 // Quoted literals may include newlines; check that the missing closing '|' is
956 // reported on the correct line
957 testInvalidPattern(++i, "hello {|wo\nrld", 3, 1);
958 testInvalidPattern(++i, "hello {|wo\nr\nl\ndddd", 4, 3);
959 // Offset for end-of-input should be 0 here because the line begins
960 // after the '\n', but there is no character after the '\n'
961 testInvalidPattern(++i, "hello {|wo\nr\nl\n", 0, 3);
962
963 // Variable names can't start with a : or -
964 testInvalidPattern(++i, "{$:abc}", 2);
965 testInvalidPattern(++i, "{$-abc}", 2);
966
967 // Missing space before annotation
968 // Note that {{$bar:foo}} and {{$bar-foo}} are valid,
969 // because variable names can contain a ':' or a '-'
970 testInvalidPattern(++i, "{$bar+foo}", 5);
971 testInvalidPattern(++i, "{|3.14|:foo}", 7);
972 testInvalidPattern(++i, "{|3.14|-foo}", 7);
973 testInvalidPattern(++i, "{|3.14|+foo}", 7);
974
975 // Unquoted literals can't begin with a ':'
976 testInvalidPattern(++i, ".local $foo = {$bar} .match {$foo} :one {one} * {other}", 36);
977 testInvalidPattern(++i, ".local $foo = {$bar :fun option=:a} {{bar {$foo}}}", 32);
978
979 // Markup in wrong place
980 testInvalidPattern(++i, "{|foo| {#markup}}", 7);
981 testInvalidPattern(++i, "{|foo| #markup}", 7);
982 testInvalidPattern(++i, "{|foo| {#markup/}}", 7);
983 testInvalidPattern(++i, "{|foo| {/markup}}", 7);
984
985 // .input with non-variable-expression
986 testInvalidPattern(++i, ".input $x = {|1|} {{{$x}}}", 7);
987 testInvalidPattern(++i, ".input $x = {:number} {{{$x}}}", 7);
988 testInvalidPattern(++i, ".input {|1| :number} {{{$x}}}", 7);
989 testInvalidPattern(++i, ".input {:number} {{{$x}}}", 7);
990 testInvalidPattern(++i, ".input {|1|} {{{$x}}}", 7);
991
992 // invalid number literals
993 testInvalidPattern(++i, "{00}", 2);
994 testInvalidPattern(++i, "{042}", 2);
995 testInvalidPattern(++i, "{1.}", 3);
996 testInvalidPattern(++i, "{1e}", 3);
997 testInvalidPattern(++i, "{1E}", 3);
998 testInvalidPattern(++i, "{1.e}", 3);
999 testInvalidPattern(++i, "{1.2e}", 5);
1000 testInvalidPattern(++i, "{1.e3}", 3);
1001 testInvalidPattern(++i, "{1e+}", 4);
1002 testInvalidPattern(++i, "{1e-}", 4);
1003 testInvalidPattern(++i, "{1.0e2.0}", 6);
1004
1005 // The following are from https://github.com/unicode-org/message-format-wg/blob/main/test/syntax-errors.json
1006 testInvalidPattern(++i,".", 1);
1007 testInvalidPattern(++i, "{", 1);
1008 testInvalidPattern(++i, "}", 0);
1009 testInvalidPattern(++i, "{}", 1);
1010 testInvalidPattern(++i, "{{", 2);
1011 testInvalidPattern(++i, "{{}", 3);
1012 testInvalidPattern(++i, "{{}}}", 4);
1013 testInvalidPattern(++i, "{|foo| #markup}", 7);
1014 testInvalidPattern(++i, "{{missing end brace}", 20);
1015 testInvalidPattern(++i, "{{missing end braces", 20);
1016 testInvalidPattern(++i, "{{missing end {$braces", 22);
1017 testInvalidPattern(++i, "{{extra}} content", 9);
1018 testInvalidPattern(++i, "empty { } placeholder", 8);
1019 testInvalidPattern(++i, "missing space {42:func}", 17);
1020 testInvalidPattern(++i, "missing space {|foo|:func}", 20);
1021 testInvalidPattern(++i, "missing space {|foo|@bar}", 20);
1022 testInvalidPattern(++i, "missing space {:func@bar}", 20);
1023 testInvalidPattern(++i, "{:func @bar@baz}", 11);
1024 testInvalidPattern(++i, "{:func @bar=42@baz}", 14);
1025 testInvalidPattern(++i, "{+reserved@bar}", 10);
1026 testInvalidPattern(++i, "{&private@bar}", 9);
1027 testInvalidPattern(++i, "bad {:} placeholder", 6);
1028 testInvalidPattern(++i, "bad {\\u0000placeholder}", 5);
1029 testInvalidPattern(++i, "no-equal {|42| :number minimumFractionDigits 2}", 45);
1030 testInvalidPattern(++i, "bad {:placeholder option=}", 25);
1031 testInvalidPattern(++i, "bad {:placeholder option value}", 25);
1032 testInvalidPattern(++i, "bad {:placeholder option:value}", 30);
1033 testInvalidPattern(++i, "bad {:placeholder option}", 24);
1034 testInvalidPattern(++i, "bad {:placeholder:}", 18);
1035 testInvalidPattern(++i, "bad {::placeholder}", 6);
1036 testInvalidPattern(++i, "bad {:placeholder::foo}", 18);
1037 testInvalidPattern(++i, "bad {:placeholder option:=x}", 25);
1038 testInvalidPattern(++i, "bad {:placeholder :option=x}", 18);
1039 testInvalidPattern(++i, "bad {:placeholder option::x=y}", 25);
1040 testInvalidPattern(++i, "bad {$placeholder option}", 18);
1041 testInvalidPattern(++i, "bad {:placeholder @attribute=}", 29);
1042 testInvalidPattern(++i, "bad {:placeholder @attribute=@foo}", 29);
1043 testInvalidPattern(++i, "no {placeholder end", 16);
1044 testInvalidPattern(++i, "no {$placeholder end", 17);
1045 testInvalidPattern(++i, "no {:placeholder end", 20);
1046 testInvalidPattern(++i, "no {|placeholder| end", 18);
1047 testInvalidPattern(++i, "no {|literal} end", 17);
1048 testInvalidPattern(++i, "no {|literal or placeholder end", 31);
1049 testInvalidPattern(++i, ".local bar = {|foo|} {{_}}", 7);
1050 testInvalidPattern(++i, ".local #bar = {|foo|} {{_}}", 7);
1051 testInvalidPattern(++i, ".local $bar {|foo|} {{_}}", 12);
1052 testInvalidPattern(++i, ".local $bar = |foo| {{_}}", 14);
1053 testInvalidPattern(++i, ".match {#foo} * {{foo}}", 8);
1054 testInvalidPattern(++i, ".match {} * {{foo}}", 8);
1055 testInvalidPattern(++i, ".match {|foo| :x} {|bar| :x} ** {{foo}}", 30);
1056 testInvalidPattern(++i, ".match * {{foo}}", 7);
1057 testInvalidPattern(++i, ".match {|x| :x} * foo", 21);
1058 testInvalidPattern(++i, ".match {|x| :x} * {{foo}} extra", 31);
1059 testInvalidPattern(++i, ".match |x| * {{foo}}", 7);
1060
1061 // tests for ':' in unquoted literals (not allowed)
1062 testInvalidPattern(++i, ".match {|foo| :string} o:ne {{one}} * {{other}}", 24);
1063 testInvalidPattern(++i, ".match {|foo| :string} one: {{one}} * {{other}}", 26);
1064 testInvalidPattern(++i, ".local $foo = {|42| :number option=a:b} {{bar {$foo}}}", 36);
1065 testInvalidPattern(++i, ".local $foo = {|42| :number option=a:b:c} {{bar {$foo}}}", 36);
1066 testInvalidPattern(++i, "{$bar:foo}", 5);
1067
1068 // Disambiguating a wrong .match from an unsupported statement
1069 testInvalidPattern(++i, ".match {1} {{_}}", 12);
1070 }
1071
1072 #endif /* #if !UCONFIG_NO_MF2 */
1073
1074 #endif /* #if !UCONFIG_NO_FORMATTING */
1075
1076