char-fork/llama.cpp
git clone https://git.t4t.associates/char-fork/llama.cpp
acecd5603
char/aliceai-t5-moe
1#ifdef NDEBUG 2#undef NDEBUG 3#endif 4 5#include "json-schema-to-grammar.h" 6 7#include "../src/unicode.h" 8#include "../src/llama-grammar.h" 9 10#include "json.h" 11 12#include <cassert> 13#include <string> 14#include <vector> 15 16using json = common_json ; 17 18static llama_grammar * build_grammar_with_root (const std::string & grammar_str ,const char * grammar_root ) { 19return llama_grammar_init_impl (nullptr ,grammar_str .c_str (),grammar_root , false,nullptr ,0 ,nullptr ,0 ); 20} 21 22static llama_grammar * build_grammar (const std::string & grammar_str ) { 23return build_grammar_with_root (grammar_str ,"root" ); 24} 25 26static bool test_build_grammar_fails (const std::string & grammar_str ) { 27fprintf (stderr ,"⚫ Testing failure for grammar: %s\n" ,grammar_str .c_str ()); 28bool grammar_fails = false; 29llama_grammar * grammar = build_grammar (grammar_str ); 30if (grammar != nullptr ) { 31fprintf (stderr ," ❌ Expected build failure, but succeeded\n" ); 32 }else { 33grammar_fails = true; 34fprintf (stdout ," ✅︎\n" ); 35 } 36return grammar_fails ; 37} 38 39struct token_and_piece { 40llama_token token ; 41 std::string piece ; 42}; 43 44// token() encodes a 32-bit ID as 5 bytes: a 0xff marker followed by the ID in big-endian order. 45static std::string token (llama_token id ) { 46return std::string { 47static_cast < char > (0xff ), 48static_cast < char > ((id >>24 )& 0xff ), 49static_cast < char > ((id >>16 )& 0xff ), 50static_cast < char > ((id >>8 )& 0xff ), 51static_cast < char > (id & 0xff ) 52 }; 53} 54 55// parse_tokens() parses the token encodes above and UTF-8 text. 56static std::vector < token_and_piece > parse_tokens (const std::string & input ) { 57 std::vector < token_and_piece > result ; 58result .reserve (input .size ()); 59size_t offset = 0 ; 60while (offset < input .size ()) { 61try { 62if (static_cast < unsigned char > (input [offset ])== 0xff ) { 63if (offset + 5 > input .size ()) { 64throw std::runtime_error ("not enough bytes for token id" ); 65 } 66uint32_t val = 67 (static_cast < unsigned char > (input [offset + 1 ]) <<24 ) | 68 (static_cast < unsigned char > (input [offset + 2 ]) <<16 ) | 69 (static_cast < unsigned char > (input [offset + 3 ]) <<8 ) | 70 (static_cast < unsigned char > (input [offset + 4 ])); 71auto piece = "<[" + std::to_string (val )+ "]>" ; 72result .push_back ({static_cast < llama_token > (val ),piece }); 73offset += 5 ; 74 }else { 75uint32_t cpt = unicode_cpt_from_utf8 (input ,offset ); 76result .push_back ({0 ,unicode_cpt_to_utf8 (cpt )}); 77 } 78 }catch (const std::invalid_argument & /*ex*/ ) { 79// Silently ignore invalid UTF-8 input to avoid leaking the exception beyond llama_tokenize 80++ offset ; 81result .push_back ({0 ,unicode_cpt_to_utf8 (0xFFFD )});// replacement character 82 } 83 } 84return result ; 85} 86 87static bool match_string (const std::string & input ,llama_grammar * grammar ) { 88const auto parsed = parse_tokens (input ); 89 90auto & stacks_cur = llama_grammar_get_stacks (grammar ); 91 92for (const auto & in :parsed ) { 93try { 94llama_grammar_accept_token (* grammar ,in .token ,in .piece ); 95 }catch (const std::runtime_error & /*e*/ ) { 96// normally this shouldn't get hit because of llama_grammar_apply 97return false; 98 } 99 100if (stacks_cur .empty ()) { 101// no stacks means that the grammar failed to match at this point 102return false; 103 } 104 } 105 106for (const auto & stack :stacks_cur ) { 107if (stack .empty ()) { 108// An empty stack means that the grammar has been completed 109return true; 110 } 111 } 112 113return false; 114} 115 116static void test (const std::string & test_desc ,const std::string & grammar_str ,const std::vector < std::string > & passing_strings ,const std::vector < std::string > & failing_strings ) { 117fprintf (stderr ,"⚫ Testing %s\n%s\n" ,test_desc .c_str (),grammar_str .c_str ()); 118fflush (stderr ); 119 120auto * grammar = build_grammar (grammar_str ); 121 122// Save the original grammar stacks so that we can reset after every new string we want to test 123const llama_grammar_stacks stacks_org = llama_grammar_get_stacks (grammar );// copy 124 125llama_grammar_stacks & stacks_cur = llama_grammar_get_stacks (grammar ); 126 127fprintf (stderr ," 🔵 Valid strings:\n" ); 128 129// Passing strings 130for (const auto & test_string :passing_strings ) { 131fprintf (stderr ," \"%s\" " ,test_string .c_str ()); 132fflush (stderr ); 133 134bool matched = match_string (test_string ,grammar ); 135 136if (!matched ) { 137fprintf (stderr ,"❌ (failed to match)\n" ); 138 139// DEBUG: Write strings to files so that we can analyze more easily with gbnf-validator program to see exactly where things failed. 140// DEBUG: Write the grammar_str to test-grammar-integration.grammar.gbnf 141FILE * grammar_file = fopen ("test-grammar-integration.grammar.gbnf" ,"w" ); 142if (grammar_file ) { 143fprintf (grammar_file ,"%s" ,grammar_str .c_str ()); 144fclose (grammar_file ); 145 } 146 147// DEBUG: Write the test string to test-grammar-integration.string.txt 148FILE * string_file = fopen ("test-grammar-integration.string.txt" ,"w" ); 149if (string_file ) { 150fprintf (string_file ,"%s" ,test_string .c_str ()); 151fclose (string_file ); 152 } 153 154fprintf (stderr ,"\n NOTE: Debug grammar file generated. To analyze this failure in detail, run the following command: ./llama-gbnf-validator test-grammar-integration.grammar.gbnf test-grammar-integration.string.txt\n\n" ); 155 }else { 156fprintf (stdout ,"✅︎\n" ); 157 } 158 159assert (matched ); 160 161// Reset the grammar stacks 162stacks_cur = stacks_org ; 163 } 164 165fprintf (stderr ," 🟠 Invalid strings:\n" ); 166 167// Failing strings 168for (const auto & test_string :failing_strings ) { 169fprintf (stderr ," \"%s\" " ,test_string .c_str ()); 170fflush (stderr ); 171 172bool matched = match_string (test_string ,grammar ); 173 174if (matched ) { 175fprintf (stderr ,"❌ (incorrectly matched)\n" ); 176 }else { 177fprintf (stdout ,"✅︎\n" ); 178 } 179assert (!matched ); 180 181// Reset the grammar stacks 182stacks_cur = stacks_org ; 183 } 184 185// Clean up allocated memory 186llama_grammar_free_impl (grammar ); 187} 188static void test_grammar (const std::string & test_desc ,const std::string & grammar_str ,const std::vector < std::string > & passing_strings ,const std::vector < std::string > & failing_strings ) { 189test (test_desc + ". Grammar: " + grammar_str ,grammar_str ,passing_strings ,failing_strings ); 190} 191static void test_schema (const std::string & test_desc ,const std::string & schema_str ,const std::vector < std::string > & passing_strings ,const std::vector < std::string > & failing_strings ) { 192test (test_desc + ". Schema: " + schema_str ,json_schema_to_grammar (json::parse (schema_str ), true),passing_strings ,failing_strings ); 193} 194 195static void test_simple_grammar () { 196test_schema ( 197"min 0" , 198R"""({ 199"type": "integer", 200"minimum": 0 201})""" , 202// Passing strings 203 { 204"0" , 205"10" , 206"12" , 207"10000" , 208 }, 209// Failing strings 210 { 211"-1" , 212"-10" , 213"-10000" , 214"-100000000000000000000000000000000" , 215"100000000000000000000000000000000" , 216"00" , 217"01" , 218"-0" , 219 } 220 ); 221test_schema ( 222"min 2" , 223// Schema 224R"""({ 225"type": "integer", 226"minimum": 2 227})""" , 228// Passing strings 229 { 230"2" , 231"3" , 232"4" , 233"10" , 234"20" , 235"1234567890000000" , 236 }, 237// Failing strings 238 { 239"0" , 240"1" , 241"-1" , 242"-100" , 243"0" , 244"1" , 245"01" , 246"02" , 247"12345678900000000" , 248 } 249 ); 250test_schema ( 251"min 456" , 252R"""({ 253"type": "integer", 254"minimum": 456 255})""" , 256// Passing strings 257 { 258"456" , 259"4560" , 260"457" , 261"460" , 262"500" , 263 }, 264// Failing strings 265 { 266"455" , 267"356" , 268"50" , 269"050" , 270"-1" , 271"-456" , 272 } 273 ); 274test_schema ( 275"min -123" , 276R"""({ 277"type": "integer", 278"minimum": -123 279})""" , 280// Passing strings 281 { 282"-123" , 283"-122" , 284"-11" , 285"-1" , 286"0" , 287"1" , 288"123" , 289"1234" , 290"2345" , 291 }, 292// Failing strings 293 { 294"-1234" , 295"-124" , 296 } 297 ); 298 299test_schema ( 300"max 9999" , 301// Schema 302R"""({ 303"type": "integer", 304"maximum": 9999 305})""" , 306// Passing strings 307 { 308"-99999" , 309"0" , 310"9999" , 311 }, 312// Failing strings 313 { 314"10000" , 315"99991" , 316 } 317 ); 318test_schema ( 319"max -9999" , 320// Schema 321R"""({ 322"type": "integer", 323"maximum": -9999 324})""" , 325// Passing strings 326 { 327"-10000" , 328"-9999" , 329 }, 330// Failing strings 331 { 332"-9998" , 333"0" , 334"9999" , 335 } 336 ); 337test_schema ( 338"min 5 max 30" , 339// Schema 340R"""({ 341"type": "integer", 342"minimum": 5, 343"maximum": 30 344})""" , 345// Passing strings 346 { 347"5" , 348"10" , 349"30" , 350 }, 351// Failing strings 352 { 353"05" , 354"4" , 355"-1" , 356"31" , 357"123" , 358"0123" , 359 } 360 ); 361test_schema ( 362"min 1 max 900719925474091" , 363// Schema 364R"""({ 365"type": "integer", 366"exclusiveMinimum": 0, 367"maximum": 900719925474091 368})""" , 369// Passing strings 370 { 371"1" , 372"2" , 373"10" , 374"900719925474090" , 375"900719925474091" , 376 }, 377// Failing strings 378 { 379"0" , 380"01" , 381"900719925474092" , 382"9007199254740910" , 383 } 384 ); 385test_schema ( 386"min -1 max 1" , 387R"""({ 388"type": "integer", 389"minimum": -1, 390"maximum": 1 391})""" , 392// Passing strings 393 { 394"-1" , 395"0" , 396"1" , 397 }, 398// Failing strings 399 { 400"-11" , 401"-10" , 402"-2" , 403"2" , 404"10" , 405"11" , 406 } 407 ); 408test_schema ( 409"min -123 max 42" , 410R"""({ 411"type": "integer", 412"minimum": -123, 413"maximum": 42 414})""" , 415// Passing strings 416 { 417"-123" , 418"-122" , 419"-13" , 420"-11" , 421"-2" , 422"-1" , 423"0" , 424"1" , 425"5" , 426"10" , 427"39" , 428"40" , 429"42" , 430 }, 431// Failing strings 432 { 433"-0123" , 434"-124" , 435"-1123" , 436"-200" , 437"43" , 438"123" , 439"0123" , 440 } 441 ); 442test_schema ( 443"exclusive min / max" , 444// Schema 445R"""({ 446"type": "integer", 447"exclusiveMinimum": 0, 448"exclusiveMaximum": 10000 449})""" , 450// Passing strings 451 { 452"1" , 453"9999" , 454 }, 455// Failing strings 456 { 457"0" , 458"01" , 459"10000" , 460"99999" , 461 } 462 ); 463 464// Test case for a simple grammar 465test_grammar ( 466"simple grammar" , 467R"""( 468root ::= expr 469expr ::= term ("+" term)* 470term ::= number 471number ::= [0-9]+)""" , 472// Passing strings 473 { 474"42" , 475"1+2+3+4+5" , 476"123+456" , 477 }, 478// Failing strings 479 { 480"+" , 481"/ 3" , 482"1+2+3+4+5+" , 483"12a45" , 484 } 485 ); 486 487// Test case for a simple grammar with tokens 488test_grammar ( 489"simple grammar with tokens" , 490R"""( 491root ::= <[10]> content <[11]> 492content ::= (!<[11]>)*)""" , 493// Passing strings 494 { 495token (10 )+ "hello world" + token (11 ), 496token (10 )+ "text with " + token (12 )+ " other tokens " + token (13 )+ " mixed in" + token (11 ), 497token (10 )+ token (11 ), 498token (10 )+ token (12 )+ token (13 )+ token (14 )+ token (15 )+ token (11 ), 499token (10 )+ "a" + token (11 ), 500 }, 501// Failing strings 502 { 503token (10 )+ "missing end token" , 504token (10 ), 505"missing start token" + token (11 ), 506token (10 )+ token (11 )+ token (11 ),// double end token 507token (11 )+ "wrong order" + token (10 ), 508 } 509 ); 510} 511 512static void test_complex_grammar () { 513// Test case for a more complex grammar, with both failure strings and success strings 514test_grammar ( 515"medium complexity grammar" , 516// Grammar 517R"""( 518root ::= expression 519expression ::= term ws (("+"|"-") ws term)* 520term ::= factor ws (("*"|"/") ws factor)* 521factor ::= number | variable | "(" expression ")" | function-call 522number ::= [0-9]+ 523variable ::= [a-zA-Z_][a-zA-Z0-9_]* 524function-call ::= variable ws "(" (expression ("," ws expression)*)? ")" 525ws ::= [ \t\n\r]?)""" , 526// Passing strings 527 { 528"42" , 529"1*2*3*4*5" , 530"x" , 531"x+10" , 532"x1+y2" , 533"(a+b)*(c-d)" , 534"func()" , 535"func(x,y+2)" , 536"a*(b+c)-d/e" , 537"f(g(x),h(y,z))" , 538"x + 10" , 539"x1 + y2" , 540"(a + b) * (c - d)" , 541"func()" , 542"func(x, y + 2)" , 543"a * (b + c) - d / e" , 544"f(g(x), h(y, z))" , 545"123+456" , 546"123*456*789-123/456+789*123" , 547"123+456*789-123/456+789*123-456/789+123*456-789/123+456*789-123/456+789*123-456" 548 }, 549// Failing strings 550 { 551"+" , 552"/ 3x" , 553"x + + y" , 554"a * / b" , 555"func(,)" , 556"func(x y)" , 557"(a + b" , 558"x + y)" , 559"a + b * (c - d" , 560"42 +" , 561"x +" , 562"x + 10 +" , 563"(a + b) * (c - d" , 564"func(" , 565"func(x, y + 2" , 566"a * (b + c) - d /" , 567"f(g(x), h(y, z)" , 568"123+456*789-123/456+789*123-456/789+123*456-789/123+456*789-123/456+789*123-456/" , 569 } 570 ); 571 572// Test case for a more complex grammar with tokens 573test_grammar ( 574"complex grammar with tokens" , 575R"""( 576root ::= reasoning+ content tool-call* 577reasoning ::= <[10]> (!<[11]>)* <[11]> 578content ::= <[20]> (!<[21]>)* <[21]> 579tool-call ::= <[12]> name <[13]> args <[14]> 580name ::= (!<[13]>)+ 581args ::= (!<[14]>)*)""" , 582// Passing strings 583 { 584token (10 )+ "I am thinking" + token (11 )+ token (20 )+ "hello world!" + token (21 )+ token (12 )+ "search" + token (13 )+ "query=test" + token (14 ), 585token (10 )+ "reasoning 1" + token (11 )+ token (10 )+ "reasoning 2" + token (11 )+ token (20 )+ token (21 )+ token (12 )+ "tool" + token (13 )+ token (14 ), 586token (10 )+ token (11 )+ token (20 )+ "content" + token (21 ), 587token (10 )+ "think" + token (12 )+ " nested" + token (11 )+ token (20 )+ token (10 )+ "more content" + token (21 )+ token (12 )+ "fn" + token (13 )+ "x=1,y=2" + token (14 )+ token (12 )+ "fn2" + token (13 )+ token (14 ), 588token (10 )+ "reasoning" + token (11 )+ token (10 )+ "more" + token (11 )+ token (10 )+ "even more" + token (11 )+ token (20 )+ "text" + token (21 )+ token (12 )+ "a" + token (13 )+ "b" + token (14 )+ token (12 )+ "c" + token (13 )+ "d" + token (14 ), 589 }, 590// Failing strings 591 { 592token (20 )+ "content only" + token (21 ), 593token (10 )+ "no closing reasoning" , 594token (10 )+ token (11 )+ token (20 )+ "no closing content" , 595token (10 )+ token (11 )+ token (20 )+ token (21 )+ token (12 )+ "incomplete tool" , 596token (10 )+ token (11 )+ token (11 )+ token (20 )+ token (21 ), 597 } 598 ); 599} 600 601static void test_special_chars () { 602// A collection of tests to exercise special characters such as "." 603test_grammar ( 604"special characters" , 605// Grammar 606R"""( 607root ::= ... "abc" ... 608)""" , 609// Passing strings 610 { 611"abcabcabc" , 612"aaaabcccc" , 613// NOTE: Also ensures that multi-byte characters still count as a single character 614"🔵🟠✅abc❌🟠🔵" 615 }, 616// Failing strings 617 { 618"aaabcccc" , 619"aaaaabcccc" , 620"aaaabccc" , 621"aaaabccccc" , 622"🔵🟠✅❌abc❌✅🟠🔵" , 623"🔵🟠abc🟠🔵" 624 } 625 ); 626} 627 628static void test_quantifiers () { 629// A collection of tests to exercise * + and ? quantifiers 630 631test_grammar ( 632"* quantifier" , 633// Grammar 634R"""(root ::= "a"*)""" , 635// Passing strings 636 { 637"" , 638"a" , 639"aaaaa" , 640"aaaaaaaaaaaaaaaaaa" , 641"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" 642 }, 643// Failing strings 644 { 645"b" , 646"ab" , 647"aab" , 648"ba" , 649"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaab" 650 } 651 ); 652test_grammar ( 653"+ quantifier" , 654// Grammar 655R"""(root ::= "a"+)""" , 656// Passing strings 657 { 658"a" , 659"aaaaa" , 660"aaaaaaaaaaaaaaaaaa" , 661"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" 662 }, 663// Failing strings 664 { 665"" , 666"b" , 667"ab" , 668"aab" , 669"ba" , 670"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaab" 671 } 672 ); 673test_grammar ( 674"? quantifier" , 675// Grammar 676R"""(root ::= "a"?)""" , 677// Passing strings 678 { 679"" , 680"a" 681 }, 682// Failing strings 683 { 684"b" , 685"ab" , 686"aa" , 687"ba" , 688 } 689 ); 690test_grammar ( 691"mixed quantifiers" , 692// Grammar 693R"""( 694root ::= cons+ vowel* cons? (vowel cons)* 695vowel ::= [aeiouy] 696cons ::= [bcdfghjklmnpqrstvwxyz] 697)""" , 698// Passing strings 699 { 700"yes" , 701"no" , 702"noyes" , 703"crwth" , 704"four" , 705"bryyyy" , 706 }, 707// Failing strings 708 { 709"yess" , 710"yesno" , 711"forty" , 712"catyyy" , 713 } 714 ); 715test_grammar ( 716"simple exact repetition" , 717// Grammar 718R"""( 719root ::= [ab]{4} 720)""" , 721// Passing strings 722 { 723"aaaa" , 724"bbbb" , 725"abab" , 726 }, 727// Failing strings 728 { 729"a" , 730"b" , 731"aaaaa" , 732 } 733 ); 734test_grammar ( 735"simple min repetition" , 736// Grammar 737R"""( 738root ::= [ab]{4,} 739)""" , 740// Passing strings 741 { 742"aaaa" , 743"aaaaab" , 744"bbbb" , 745"ababab" , 746 }, 747// Failing strings 748 { 749"" , 750"aba" , 751 } 752 ); 753test_grammar ( 754"simple max repetition" , 755// Grammar 756R"""( 757root ::= [ab]{0,4} 758)""" , 759// Passing strings 760 { 761"" , 762"a" , 763"aa" , 764"aaa" , 765"aaab" , 766 }, 767// Failing strings 768 { 769"aaaaa" , 770 } 771 ); 772test_grammar ( 773"min / max repetition" , 774// Grammar 775R"""( 776root ::= ("0x" [A-F0-9]{2} " "?){3,5} 777)""" , 778// Passing strings 779 { 780"0xFF 0x12 0xAB" , 781"0xFF 0x12 0xAB 0x00 0x00" , 782 }, 783// Failing strings 784 { 785"" , 786"0xFF" , 787"0xFF 0x12" , 788"0xFF 0x12 0xAB 0x00 0x00 0x00" , 789 } 790 ); 791test_grammar ( 792"segfault" , 793// Grammar 794R"""( 795root ::= ( [x]* )* 796)""" , 797// Passing strings 798 { 799"" , 800"x" , 801"xx" 802 }, 803// Failing strings 804 { 805"y" , 806"yy" 807 } 808 ); 809} 810 811static void test_failure_missing_root () { 812fprintf (stderr ,"⚫ Testing missing root node:\n" ); 813// Test case for a grammar that is missing a root rule 814const std::string grammar_str = R"""( 815rot ::= expr 816expr ::= term ("+" term)* 817term ::= number 818number ::= [0-9]+)""" ; 819 820llama_grammar_parser parsed_grammar ; 821parsed_grammar .parse (grammar_str .c_str ()); 822 823// Ensure we parsed correctly 824assert (!parsed_grammar .rules .empty ()); 825 826// Ensure we do NOT have a root node 827assert (parsed_grammar .symbol_ids .find ("root" )== parsed_grammar .symbol_ids .end ()); 828fprintf (stderr ," ✅︎ Passed\n" ); 829} 830 831static void test_failure_missing_reference () { 832fprintf (stderr ,"⚫ Testing missing reference node:\n" ); 833 834// Test case for a grammar that is missing a referenced rule 835const std::string grammar_str = 836R"""(root ::= expr 837expr ::= term ("+" term)* 838term ::= numero 839number ::= [0-9]+)""" ; 840 841fprintf (stderr ," Expected error: " ); 842 843llama_grammar_parser parsed_grammar ; 844parsed_grammar .parse (grammar_str .c_str ()); 845 846// Ensure we did NOT parsed correctly 847assert (parsed_grammar .rules .empty ()); 848 849fprintf (stderr ," End of expected error.\n" ); 850fprintf (stderr ," ✅︎ Passed\n" ); 851} 852 853static void test_failure_left_recursion () { 854fprintf (stderr ,"⚫ Testing left recursion detection:\n" ); 855 856// Test simple left recursion detection 857const std::string simple_str = R"""(root ::= "a" | root "a")""" ; 858assert (test_build_grammar_fails (simple_str )); 859 860// Test more complicated left recursion detection 861const std::string medium_str = R"""( 862root ::= asdf 863asdf ::= "a" | asdf "a" 864)""" ; 865assert (test_build_grammar_fails (medium_str )); 866 867// Test even more complicated left recursion detection 868const std::string hard_str = R"""( 869root ::= asdf 870asdf ::= "a" | foo "b" 871foo ::= "c" | asdf "d" | "e")""" ; 872assert (test_build_grammar_fails (hard_str )); 873 874// Test yet even more complicated left recursion detection 875const std::string hardest_str = R"""( 876root ::= asdf 877asdf ::= "a" | foo "b" 878foo ::= "c" | empty asdf "d" | "e" 879empty ::= "blah" | )""" ; 880assert (test_build_grammar_fails (hardest_str )); 881 882fprintf (stderr ," ✅︎ Passed\n" ); 883} 884 885static void test_failure_missing_root_symbol () { 886fprintf (stderr ,"⚫ Testing missing root symbol:\n" ); 887 888const std::string grammar_str = R"""( 889root ::= "foobar" 890)""" ; 891 892llama_grammar * failure_result = build_grammar_with_root (grammar_str ,"nonexistent" ); 893assert (failure_result == nullptr ); 894 895fprintf (stderr ," ✅︎ Passed\n" ); 896} 897 898static void test_custom_root_symbol_check () { 899fprintf (stderr ,"⚫ Testing custom root symbol check:\n" ); 900 901const std::string custom_root_grammar_str = R"""( 902foobar ::= "foobar" 903)""" ; 904 905llama_grammar * failure_result = build_grammar_with_root (custom_root_grammar_str ,"root" ); 906assert (failure_result == nullptr ); 907 908llama_grammar * success_result = build_grammar_with_root (custom_root_grammar_str ,"foobar" ); 909assert (success_result != nullptr ); 910llama_grammar_free_impl (success_result ); 911 912fprintf (stderr ," ✅︎ Passed\n" ); 913} 914 915static void test_json_schema () { 916// Note that this is similar to the regular grammar tests, 917// but we convert each json schema to a grammar before parsing. 918// Otherwise, this test structure is the same. 919 920test_schema ( 921"empty schema (any value)" , 922// Schema 923R"""( 924{} 925)""" , 926// Passing strings 927 { 928R"""({})""" , 929R"""({"foo": "bar"})""" , 930"[]" , 931"null" , 932R"""("")""" , 933"true" , 934 }, 935// Failing strings 936 { 937"" , 938R"""({"foo"})""" , 939"foo" , 940 } 941 ); 942 943test_schema ( 944"exotic formats (list)" , 945// Schema 946R"""({ 947"items": [ 948{ "format": "date" }, 949{ "format": "uuid" }, 950{ "format": "time" }, 951{ "format": "date-time" } 952] 953})""" , 954// Passing strings 955 { 956// "{}", // NOTE: This string passes for this schema on https://www.jsonschemavalidator.net/ -- should it? 957// "[]", // NOTE: This string passes for this schema on https://www.jsonschemavalidator.net/ -- should it? 958R"""(["2012-04-23", "12345678-1234-1234-1234-1234567890ab", "18:25:43.511Z", "2012-04-23T18:25:43.511Z"])""" , 959//R"""(["2012-04-23","12345678-1234-1234-1234-1234567890ab"])""", // NOTE: This string passes for this schema on https://www.jsonschemavalidator.net/ -- should it? 960//R"""({"foo": "bar"})""", // NOTE: This string passes for this schema on https://www.jsonschemavalidator.net/ -- should it? 961 }, 962// Failing strings 963 { 964R"""(["foo", "bar"])""" , 965R"""(["12345678-1234-1234-1234-1234567890ab"])""" , 966 } 967 ); 968 969test_schema ( 970"string" , 971// Schema 972R"""({ 973"type": "string" 974})""" , 975// Passing strings 976 { 977R"""("foo")""" , 978R"""("bar")""" , 979R"""("")""" , 980 }, 981// Failing strings 982 { 983R"""({})""" , 984R"""("foo": "bar")""" , 985 } 986 ); 987 988test_schema ( 989"string w/ min length 1" , 990// Schema 991R"""({ 992"type": "string", 993"minLength": 1 994})""" , 995// Passing strings 996 { 997R"""("foo")""" , 998R"""("bar")""" , 999 }, 1000// Failing strings 1001 { 1002R"""("")""" , 1003R"""({})""" , 1004R"""("foo": "bar")""" , 1005 } 1006 ); 1007 1008test_schema ( 1009"string w/ min length 3" , 1010// Schema 1011R"""({ 1012"type": "string", 1013"minLength": 3 1014})""" , 1015// Passing strings 1016 { 1017R"""("foo")""" , 1018R"""("bar")""" , 1019R"""("foobar")""" , 1020 }, 1021// Failing strings 1022 { 1023R"""("")""" , 1024R"""("f")""" , 1025R"""("fo")""" , 1026 } 1027 ); 1028 1029test_schema ( 1030"string w/ max length" , 1031// Schema 1032R"""({ 1033"type": "string", 1034"maxLength": 3 1035})""" , 1036// Passing strings 1037 { 1038R"""("foo")""" , 1039R"""("bar")""" , 1040R"""("")""" , 1041R"""("f")""" , 1042R"""("fo")""" , 1043 }, 1044// Failing strings 1045 { 1046R"""("foobar")""" , 1047 } 1048 ); 1049 1050test_schema ( 1051"string w/ min & max length" , 1052// Schema 1053R"""({ 1054"type": "string", 1055"minLength": 1, 1056"maxLength": 4 1057})""" , 1058// Passing strings 1059 { 1060R"""("foo")""" , 1061R"""("bar")""" , 1062R"""("f")""" , 1063R"""("barf")""" , 1064 }, 1065// Failing strings 1066 { 1067R"""("")""" , 1068R"""("barfo")""" , 1069R"""("foobar")""" , 1070 } 1071 ); 1072 1073test_schema ( 1074"boolean" , 1075// Schema 1076R"""({ 1077"type": "boolean" 1078})""" , 1079// Passing strings 1080 { 1081"true" , 1082"false" , 1083 }, 1084// Failing strings 1085 { 1086R"""("")""" , 1087R"""("true")""" , 1088R"""(True)""" , 1089R"""(FALSE)""" , 1090 } 1091 ); 1092 1093test_schema ( 1094"integer" , 1095// Schema 1096R"""({ 1097"type": "integer" 1098})""" , 1099// Passing strings 1100 { 1101R"""(0)""" , 1102R"""(12345)""" , 1103R"""(1234567890123456)""" , 1104 }, 1105// Failing strings 1106 { 1107R"""()""" , 1108R"""(01)""" , 1109R"""(007)""" , 1110R"""(12345678901234567 )""" , 1111 } 1112 ); 1113 1114test_schema ( 1115"string const" , 1116// Schema 1117R"""({ 1118"const": "foo" 1119})""" , 1120// Passing strings 1121 { 1122R"""("foo")""" , 1123 }, 1124// Failing strings 1125 { 1126R"""(foo)""" , 1127R"""("bar")""" , 1128 } 1129 ); 1130 1131test_schema ( 1132"non-string const" , 1133// Schema 1134R"""({ 1135"const": true 1136})""" , 1137// Passing strings 1138 { 1139R"""(true)""" , 1140 }, 1141// Failing strings 1142 { 1143R"""()""" , 1144R"""(foo)""" , 1145R"""("true")""" , 1146 } 1147 ); 1148 1149test_schema ( 1150"non-string const" , 1151// Schema 1152R"""({ 1153"enum": ["red", "amber", "green", null, 42, ["foo"]] 1154})""" , 1155// Passing strings 1156 { 1157R"""("red")""" , 1158R"""(null)""" , 1159R"""(42)""" , 1160R"""(["foo"])""" , 1161 }, 1162// Failing strings 1163 { 1164R"""()""" , 1165R"""(420)""" , 1166R"""(true)""" , 1167R"""(foo)""" , 1168 } 1169 ); 1170 1171test_schema ( 1172"simple pattern" , 1173// Schema 1174R"""({ 1175"pattern": "^[a-zA-Z0-9_-]*$" 1176})""" , 1177// Passing strings 1178 { 1179R"""("")""" , 1180R"""("He_llo-12")""" , 1181 }, 1182// Failing strings 1183 { 1184R"""("!")""" , 1185R"""("Hello World")""" , 1186 } 1187 ); 1188 1189test_schema ( 1190"pattern with escapes" , 1191// Schema 1192R"""({ 1193"pattern": "^a\\^\\$\\.\\[\\]\\(\\)\\|\\{\\}\\*\\+\\?b$" 1194})""" , 1195// Passing strings 1196 { 1197R"""("a^$.[]()|{}*+?b")""" , 1198 }, 1199// Failing strings 1200 { 1201R"""("ab")""" , 1202 } 1203 ); 1204 1205test_schema ( 1206"" , 1207// Schema 1208R"""( 1209{ 1210"type": ["array", "null"], 1211"items": { "type": "string" } 1212} 1213)""" , 1214// Passing strings 1215 { 1216"null" , 1217"[]" , 1218"[\"123\"]" , 1219"[\"foo\", \"bar\"]" , 1220 }, 1221// Failing strings 1222 { 1223"" , 1224"[123]" , 1225"\"foo\"" , 1226"[\"foo\", 42]" , 1227 } 1228 ); 1229 1230test_schema ( 1231"min+max items" , 1232// Schema 1233R"""({ 1234"items": { 1235"type": ["number", "integer"] 1236}, 1237"minItems": 3, 1238"maxItems": 5 1239})""" , 1240// Passing strings 1241 { 1242R"""([1, 2, 3])""" , 1243R"""([1, 2, 3, 4])""" , 1244R"""([1, 2, 3, 4, 5])""" , 1245 }, 1246// Failing strings 1247 { 1248R"""([1, 2])""" , 1249R"""([1, 2, 3, 4, 5, 6])""" , 1250R"""(1)""" , 1251 } 1252 ); 1253 1254// Properties (from: https://json-schema.org/understanding-json-schema/reference/object#properties) 1255test_schema ( 1256"object properties" , 1257// Schema 1258R"""({ 1259"type": "object", 1260"properties": { 1261"number": { "type": "number" }, 1262"street_name": { "type": "string" }, 1263"street_type": { "enum": ["Street", "Avenue", "Boulevard"] } 1264} 1265})""" , 1266// Passing strings 1267 { 1268R"""({ "number": 1600, "street_name": "Pennsylvania", "street_type":"Avenue"})""" , 1269// "By default, leaving out properties is valid" 1270R"""({ "street_name": "Pennsylvania" })""" , 1271R"""({ "number": 1600, "street_name": "Pennsylvania" })""" , 1272// "By extension, even an empty object is valid" 1273R"""({})""" , 1274R"""({ "number": 1600, "street_name": "Pennsylvania", "street_type": "Avenue" })""" , 1275 }, 1276// Failing strings 1277 { 1278// Change datatype from number to string 1279R"""({ "number": "1600", "street_name": "Pennsylvania", "street_type":"Avenue"})""" , 1280// Reorder properties 1281R"""({ "street_name": "Pennsylvania", "number": 1600 })""" , 1282// Reorder properties 1283R"""({ "number": "1600", "street_name": "Pennsylvania", "street_type":"Avenue"})""" , 1284// "Additional properties default to false for generation, even though the spec says true. 1285R"""({ "number": 1600, "street_name": "Pennsylvania", "street_type":"Avenue", "direction":"NW"})""" , 1286 1287 } 1288 ); 1289 1290test_schema ( 1291"additional properties can't override other properties" , 1292R"""({ 1293"properties": { 1294"a": {"type": "integer"}, 1295"b": {"type": "integer"} 1296}, 1297"additionalProperties": true 1298})""" , 1299// Passing strings 1300 { 1301R"""({"a": 42})""" , 1302R"""({"c": ""})""" , 1303R"""({"a": 42, "c": ""})""" , 1304R"""({"a_": ""})""" , 1305 }, 1306// Failing strings 1307 { 1308R"""()""" , 1309R"""({"a": ""})""" , 1310R"""({"a": "", "b": ""})""" , 1311 } 1312 ); 1313 1314// Properties (from: https://json-schema.org/understanding-json-schema/reference/object#properties) 1315test_schema ( 1316"object properties, additionalProperties: true" , 1317// Schema 1318R"""({ 1319"type": "object", 1320"properties": { 1321"number": { "type": "number" }, 1322"street_name": { "type": "string" }, 1323"street_type": { "enum": ["Street", "Avenue", "Boulevard"] } 1324}, 1325"additionalProperties": true 1326})""" , 1327// Passing strings 1328 { 1329// "By extension, even an empty object is valid" 1330R"""({})""" , 1331R"""({"number":1600,"street_name":"Pennsylvania","street_type":"Avenue"})""" , 1332// "By default, leaving out properties is valid" 1333R"""({ "street_name": "Pennsylvania" })""" , 1334R"""({ "number": 1600, "street_name": "Pennsylvania" })""" , 1335// "By default, providing additional properties is valid" 1336R"""({ "number": 1600, "street_name": "Pennsylvania", "street_type":"Avenue", "direction":"NW"})""" , 1337R"""({ "number": 1600, "street_name": "Pennsylvania", "street_type": "Avenue" })""" , 1338 }, 1339// Failing strings 1340 { 1341// Change datatype from number to string 1342R"""({ "number": "1600", "street_name": "Pennsylvania", "street_type":"Avenue"})""" , 1343// Reorder properties 1344R"""({ "street_name": "Pennsylvania", "number": 1600, "street_type":"Avenue"})""" , 1345 } 1346 ); 1347 1348// Additional properties: false 1349test_schema ( 1350"required + optional props each in original order" , 1351// Schema 1352R"""({ 1353"type": "object", 1354"properties": { 1355"number": { "type": "number" }, 1356"street_name": { "type": "string" }, 1357"street_type": { "enum": ["Street", "Avenue", "Boulevard"] } 1358}, 1359"additionalProperties": false 1360})""" , 1361// Passing strings 1362 { 1363R"""({ "street_name": "Pennsylvania" })""" , 1364R"""({ "number": 1600, "street_type":"Avenue"})""" , 1365R"""({ "number": 1600, "street_name": "Pennsylvania" })""" , 1366R"""({ "number": 1600, "street_name": "Pennsylvania", "street_type":"Avenue"})""" , 1367// Spaces are permitted around enum values 1368R"""({ "number": 1600, "street_name": "Pennsylvania", "street_type": "Avenue" })""" , 1369 }, 1370// Failing strings 1371 { 1372// Reorder properties 1373R"""({ "street_type": "Avenue", "number": 1600 })""" , 1374// Add "direction" 1375R"""({ "number": 1600, "street_name": "Pennsylvania", "street_type": "Avenue", "direction": "NW" })""" , 1376 } 1377 ); 1378 1379test_schema ( 1380"required + optional props each in original order" , 1381// Schema 1382R"""({ 1383"properties": { 1384"b": {"type": "string"}, 1385"a": {"type": "string"}, 1386"d": {"type": "string"}, 1387"c": {"type": "string"} 1388}, 1389"required": ["a", "b"], 1390"additionalProperties": false 1391})""" , 1392// Passing strings 1393 { 1394R"""({"b": "foo", "a": "bar"})""" , 1395R"""({"b":"foo","a":"bar","d":"qux"})""" , 1396R"""({"b":"foo", "a":"bar", "d":"qux", "c":"baz"})""" , 1397 }, 1398// Failing strings 1399 { 1400R"""({"a": "foo", "b": "bar"})""" , 1401R"""({"b": "bar"})""" , 1402R"""({"a": "foo", "c": "baz"})""" , 1403R"""({"a":"foo", "b":"bar", "c":"baz", "d":"qux"})""" , 1404 } 1405 ); 1406 1407// NOTE: Example from https://json-schema.org/learn/getting-started-step-by-step#define-required-properties 1408test_schema ( 1409"required props" , 1410// Schema 1411R"""({ 1412"$schema": "https://json-schema.org/draft/2020-12/schema", 1413"$id": "https://example.com/product.schema.json", 1414"title": "Product", 1415"description": "A product from Acme's catalog", 1416"type": "object", 1417"properties": { 1418"productId": { 1419"description": "The unique identifier for a product", 1420"type": "integer" 1421}, 1422"productName": { 1423"description": "Name of the product", 1424"type": "string" 1425}, 1426"price": { 1427"description": "The price of the product", 1428"type": "number", 1429"exclusiveMinimum": 0 1430}, 1431"tags": { 1432"description": "Tags for the product", 1433"type": "array", 1434"items": { 1435"type": "string" 1436}, 1437"minItems": 1, 1438"uniqueItems": true 1439}, 1440"dimensions": { 1441"type": "object", 1442"properties": { 1443"length": { 1444"type": "number" 1445}, 1446"width": { 1447"type": "number" 1448}, 1449"height": { 1450"type": "number" 1451} 1452}, 1453"required": [ "length", "width", "height" ] 1454} 1455}, 1456"required": [ "productId", "productName", "price" ] 1457})""" , 1458// Passing strings 1459 { 1460R"""({"productId": 1, "productName": "A green door", "price": 12.50})""" , 1461R"""({"productId": 1, "productName": "A green door", "price": 12.50, "tags": ["home", "green"]})""" , 1462R"""({"productId": 1, "productName": "A green door", "price": 12.50, "tags": ["home", "green"], "dimensions": {"length": 785, "width": 250.5, "height": -0.359}})""" , 1463 }, 1464// Failing strings 1465 { 1466R"""({})""" ,// Missing all required properties 1467R"""({"productName": "A green door", "price": 12.50, "productId": 1})""" ,// Out of order properties 1468// TODO: The following line should fail, but currently it passes. `exclusiveMinimum` is not supported, as it would likely be too difficult to implement. 1469// Perhaps special checks for minimum and maximum values of 0 could be added (since that's relatively easy to do with grammars), but anything else would likely be too complex. 1470// R"""({"productId": 1, "productName": "A green door", "price": -12.50})""", 1471R"""({"productId": 1, "productName": "A green door"})""" ,// Missing required property (price) 1472R"""({"productName": "A green door", "price": 12.50})""" ,// Missing required property (productId) 1473R"""({"productId": 1, "productName": "A green door", "price": 12.50, "tags": []})""" ,// tags is empty, but minItems is 1 1474R"""({"productId": 1, "productName": "A green door", "price": 12.50, "dimensions": {"length": 785, "width": 250.5, "height": -0.359}, "tags": ["home", "green"]})""" ,// Tags and dimensions are out of order 1475// TODO: The following line should fail, but currently it passes. `uniqueItems` is not supported, as it would likely be too difficult to implement. 1476// R"""({"productId": 1, "productName": "A green door", "price": 12.50, "tags": ["home", "green", "home"]})""", 1477 } 1478 ); 1479} 1480 1481int main () { 1482fprintf (stdout ,"Running grammar integration tests...\n" ); 1483test_simple_grammar (); 1484test_complex_grammar (); 1485test_special_chars (); 1486test_quantifiers (); 1487test_failure_missing_root (); 1488test_failure_missing_reference (); 1489test_failure_left_recursion (); 1490test_failure_missing_root_symbol (); 1491test_custom_root_symbol_check (); 1492test_json_schema (); 1493fprintf (stdout ,"All tests passed.\n" ); 1494return 0 ; 1495}