char-fork/llama.cpp
git clone https://git.t4t.associates/char-fork/llama.cpp
52b3df002
char/aliceai-t5-moe
1#include "tests.h" 2 3#include "json-schema-to-grammar.h" 4 5#include <regex> 6 7static std::string trim_leading_space (const std::string & s ) { 8static const std::regex leading_ws_re = std::regex (R"((^|\n)\s+)" ); 9return std::regex_replace (s ,leading_ws_re ,"$1" ); 10} 11 12static void assert_gbnf_equal (testing & t ,const std::string & expected ,const std::string & actual ) { 13t .assert_equal ("gbnf are equal" ,trim_leading_space (expected ),trim_leading_space (actual )); 14} 15 16void test_gbnf_generation (testing & t ) { 17t .test ("literal grammar generation" , [](testing & t ) { 18auto parser = build_peg_parser ([](common_peg_parser_builder & p ) { 19return p .literal ("hello" ); 20 }); 21 22auto gbnf = build_grammar ([& ](const common_grammar_builder & builder ) { 23parser .build_grammar (builder ); 24 }); 25 26assert_gbnf_equal (t ,R"""( 27root ::= "hello" 28space ::= | " " | "\n"{1,2} [ \t]{0,20} 29)""" ,gbnf ); 30 }); 31 32t .test ("char class grammar" , [](testing & t ) { 33auto parser = build_peg_parser ([](common_peg_parser_builder & p ) { 34return p .chars ("[a-z]" ,1 ,1 ); 35 }); 36 37auto gbnf = build_grammar ([& ](const common_grammar_builder & builder ) { 38parser .build_grammar (builder ); 39 }); 40 41assert_gbnf_equal (t ,R"""( 42root ::= [a-z] 43space ::= | " " | "\n"{1,2} [ \t]{0,20} 44)""" ,gbnf ); 45 }); 46 47t .test ("sequence grammar" , [](testing & t ) { 48auto parser = build_peg_parser ([](common_peg_parser_builder & p ) { 49return p .literal ("hello" )+ p .literal (" " )+ p .literal ("world" ); 50 }); 51 52auto gbnf = build_grammar ([& ](const common_grammar_builder & builder ) { 53parser .build_grammar (builder ); 54 }); 55 56assert_gbnf_equal (t ,R"""( 57root ::= "hello" " " "world" 58space ::= | " " | "\n"{1,2} [ \t]{0,20} 59)""" ,gbnf ); 60 }); 61 62t .test ("choice grammar" , [](testing & t ) { 63auto parser = build_peg_parser ([](common_peg_parser_builder & p ) { 64return p .literal ("cat" ) |p .literal ("dog" ); 65 }); 66 67auto gbnf = build_grammar ([& ](const common_grammar_builder & builder ) { 68parser .build_grammar (builder ); 69 }); 70 71assert_gbnf_equal (t ,R"""( 72root ::= "cat" | "dog" 73space ::= | " " | "\n"{1,2} [ \t]{0,20} 74)""" ,gbnf ); 75 }); 76 77t .test ("one_or_more grammar" , [](testing & t ) { 78auto parser = build_peg_parser ([](common_peg_parser_builder & p ) { 79return p .one_or_more (p .literal ("a" )); 80 }); 81 82auto gbnf = build_grammar ([& ](const common_grammar_builder & builder ) { 83parser .build_grammar (builder ); 84 }); 85 86assert_gbnf_equal (t ,R"""( 87root ::= "a"+ 88space ::= | " " | "\n"{1,2} [ \t]{0,20} 89)""" ,gbnf ); 90 }); 91 92t .test ("zero_or_more grammar" , [](testing & t ) { 93auto parser = build_peg_parser ([](common_peg_parser_builder & p ) { 94return p .zero_or_more (p .literal ("a" )); 95 }); 96 97auto gbnf = build_grammar ([& ](const common_grammar_builder & builder ) { 98parser .build_grammar (builder ); 99 }); 100 101assert_gbnf_equal (t ,R"""( 102root ::= "a"* 103space ::= | " " | "\n"{1,2} [ \t]{0,20} 104)""" ,gbnf ); 105 }); 106 107t .test ("optional grammar" , [](testing & t ) { 108auto parser = build_peg_parser ([](common_peg_parser_builder & p ) { 109return p .literal ("hello" )+ p .optional (p .literal (" world" )); 110 }); 111 112auto gbnf = build_grammar ([& ](const common_grammar_builder & builder ) { 113parser .build_grammar (builder ); 114 }); 115 116assert_gbnf_equal (t ,R"""( 117root ::= "hello" " world"? 118space ::= | " " | "\n"{1,2} [ \t]{0,20} 119)""" ,gbnf ); 120 }); 121 122t .test ("until grammar" , [](testing & t ) { 123auto parser = build_peg_parser ([](common_peg_parser_builder & p ) { 124return p .until ("</tag>" ); 125 }); 126 127auto gbnf = build_grammar ([& ](const common_grammar_builder & builder ) { 128parser .build_grammar (builder ); 129 }); 130 131assert_gbnf_equal (t ,R"""( 132root ::= until-0 133space ::= | " " | "\n"{1,2} [ \t]{0,20} 134until-0 ::= | [<] until-0-01 | [^<] until-0 135until-0-01 ::= | [<] until-0-01 | [/] until-0-02 | [^/<] until-0 136until-0-02 ::= | [<] until-0-01 | [t] until-0-03 | [^<t] until-0 137until-0-03 ::= | [<] until-0-01 | [a] until-0-04 | [^<a] until-0 138until-0-04 ::= | [<] until-0-01 | [g] until-0-05 | [^<g] until-0 139until-0-05 ::= | [<] until-0-01 | [^<>] until-0 140)""" ,gbnf ); 141 }); 142 143t .test ("until grammar overlapping delimiter" , [](testing & t ) { 144auto parser = build_peg_parser ([](common_peg_parser_builder & p ) { 145return p .until ("\n</parameter>\n" ); 146 }); 147 148auto gbnf = build_grammar ([& ](const common_grammar_builder & builder ) { 149parser .build_grammar (builder ); 150 }); 151 152assert_gbnf_equal (t ,R"""( 153root ::= until-0 154space ::= | " " | "\n"{1,2} [ \t]{0,20} 155until-0 ::= | [\n] until-0-01 | [^\n] until-0 156until-0-01 ::= | [\n] until-0-01 | [<] until-0-02 | [^\n<] until-0 157until-0-02 ::= | [\n] until-0-01 | [/] until-0-03 | [^\n/] until-0 158until-0-03 ::= | [\n] until-0-01 | [p] until-0-04 | [^\np] until-0 159until-0-04 ::= | [\n] until-0-01 | [a] until-0-05 | [^\na] until-0 160until-0-05 ::= | [\n] until-0-01 | [r] until-0-06 | [^\nr] until-0 161until-0-06 ::= | [\n] until-0-01 | [a] until-0-07 | [^\na] until-0 162until-0-07 ::= | [\n] until-0-01 | [m] until-0-08 | [^\nm] until-0 163until-0-08 ::= | [\n] until-0-01 | [e] until-0-09 | [^\ne] until-0 164until-0-09 ::= | [\n] until-0-01 | [t] until-0-10 | [^\nt] until-0 165until-0-10 ::= | [\n] until-0-01 | [e] until-0-11 | [^\ne] until-0 166until-0-11 ::= | [\n] until-0-01 | [r] until-0-12 | [^\nr] until-0 167until-0-12 ::= | [\n] until-0-01 | [>] until-0-13 | [^\n>] until-0 168until-0-13 ::= | [^\n] until-0 169)""" ,gbnf ); 170 }); 171 172// DeepSeek-V3.2 tag prefix. The DSML token (|DSML|) embeds U+FF5C, 173// so the delimiter mixes ASCII and multi-byte codepoints. 174t .test ("until grammar unicode delimiter" , [](testing & t ) { 175auto parser = build_peg_parser ([](common_peg_parser_builder & p ) { 176return p .until ("<|DSML|" ); 177 }); 178 179auto gbnf = build_grammar ([& ](const common_grammar_builder & builder ) { 180parser .build_grammar (builder ); 181 }); 182 183assert_gbnf_equal (t ,R"""( 184root ::= until-0 185space ::= | " " | "\n"{1,2} [ \t]{0,20} 186until-0 ::= | [<] until-0-01 | [^<] until-0 187until-0-01 ::= | [<] until-0-01 | [\uFF5C] until-0-02 | [^<\uFF5C] until-0 188until-0-02 ::= | [<] until-0-01 | [D] until-0-03 | [^<D] until-0 189until-0-03 ::= | [<] until-0-01 | [S] until-0-04 | [^<S] until-0 190until-0-04 ::= | [<] until-0-01 | [M] until-0-05 | [^<M] until-0 191until-0-05 ::= | [<] until-0-01 | [L] until-0-06 | [^<L] until-0 192until-0-06 ::= | [<] until-0-01 | [^<\uFF5C] until-0 193)""" ,gbnf ); 194 }); 195 196t .test ("until grammar multiple delimiters" , [](testing & t ) { 197auto parser = build_peg_parser ([](common_peg_parser_builder & p ) { 198return p .until_one_of ({"ab" ,"cd" ,"ef" }); 199 }); 200 201auto gbnf = build_grammar ([& ](const common_grammar_builder & builder ) { 202parser .build_grammar (builder ); 203 }); 204 205assert_gbnf_equal (t ,R"""( 206root ::= until-0 207space ::= | " " | "\n"{1,2} [ \t]{0,20} 208until-0 ::= | [a] until-0-01 | [c] until-0-03 | [e] until-0-05 | [^ace] until-0 209until-0-01 ::= | [a] until-0-01 | [c] until-0-03 | [e] until-0-05 | [^abce] until-0 210until-0-03 ::= | [a] until-0-01 | [c] until-0-03 | [e] until-0-05 | [^acde] until-0 211until-0-05 ::= | [a] until-0-01 | [c] until-0-03 | [e] until-0-05 | [^acef] until-0 212)""" ,gbnf ); 213 }); 214 215t .test ("ac grammar" , [](testing & t ) { 216auto parser = build_peg_parser ([](common_peg_parser_builder & p ) { 217return p .ac (p .until ("</tag>" )+ p .literal ("</tag>" ),"</tag>" ); 218 }); 219 220auto gbnf = build_grammar ([& ](const common_grammar_builder & builder ) { 221parser .build_grammar (builder ); 222 }); 223 224assert_gbnf_equal (t ,R"""( 225ac-3 ::= [<] ac-3-01 | [^<] ac-3 226ac-3-01 ::= [<] ac-3-01 | [/] ac-3-02 | [^/<] ac-3 227ac-3-02 ::= [<] ac-3-01 | [t] ac-3-03 | [^<t] ac-3 228ac-3-03 ::= [<] ac-3-01 | [a] ac-3-04 | [^<a] ac-3 229ac-3-04 ::= [<] ac-3-01 | [g] ac-3-05 | [^<g] ac-3 230ac-3-05 ::= [>] | [<] ac-3-01 | [^<>] ac-3 231root ::= ac-3 232space ::= | " " | "\n"{1,2} [ \t]{0,20} 233)""" ,gbnf ); 234 }); 235 236t .test ("ac grammar terminates at first delimiter" , [](testing & t ) { 237auto parser = build_peg_parser ([](common_peg_parser_builder & p ) { 238return p .ac (p .until ("\n</parameter>\n" )+ p .literal ("\n</parameter>\n" ),"\n</parameter>\n" ); 239 }); 240 241auto gbnf = build_grammar ([& ](const common_grammar_builder & builder ) { 242parser .build_grammar (builder ); 243 }); 244 245assert_gbnf_equal (t ,R"""( 246ac-3 ::= [\n] ac-3-01 | [^\n] ac-3 247ac-3-01 ::= [\n] ac-3-01 | [<] ac-3-02 | [^\n<] ac-3 248ac-3-02 ::= [\n] ac-3-01 | [/] ac-3-03 | [^\n/] ac-3 249ac-3-03 ::= [\n] ac-3-01 | [p] ac-3-04 | [^\np] ac-3 250ac-3-04 ::= [\n] ac-3-01 | [a] ac-3-05 | [^\na] ac-3 251ac-3-05 ::= [\n] ac-3-01 | [r] ac-3-06 | [^\nr] ac-3 252ac-3-06 ::= [\n] ac-3-01 | [a] ac-3-07 | [^\na] ac-3 253ac-3-07 ::= [\n] ac-3-01 | [m] ac-3-08 | [^\nm] ac-3 254ac-3-08 ::= [\n] ac-3-01 | [e] ac-3-09 | [^\ne] ac-3 255ac-3-09 ::= [\n] ac-3-01 | [t] ac-3-10 | [^\nt] ac-3 256ac-3-10 ::= [\n] ac-3-01 | [e] ac-3-11 | [^\ne] ac-3 257ac-3-11 ::= [\n] ac-3-01 | [r] ac-3-12 | [^\nr] ac-3 258ac-3-12 ::= [\n] ac-3-01 | [>] ac-3-13 | [^\n>] ac-3 259ac-3-13 ::= [\n] | [^\n] ac-3 260root ::= ac-3 261space ::= | " " | "\n"{1,2} [ \t]{0,20} 262)""" ,gbnf ); 263 }); 264 265t .test ("ac grammar multiple delimiters" , [](testing & t ) { 266auto parser = build_peg_parser ([](common_peg_parser_builder & p ) { 267return p .ac (p .eps (), std::vector < std::string > {"ab" ,"cd" ,"ef" }); 268 }); 269 270auto gbnf = build_grammar ([& ](const common_grammar_builder & builder ) { 271parser .build_grammar (builder ); 272 }); 273 274assert_gbnf_equal (t ,R"""( 275ac-1 ::= [a] ac-1-01 | [c] ac-1-03 | [e] ac-1-05 | [^ace] ac-1 276ac-1-01 ::= [b] | [a] ac-1-01 | [c] ac-1-03 | [e] ac-1-05 | [^abce] ac-1 277ac-1-03 ::= [d] | [a] ac-1-01 | [c] ac-1-03 | [e] ac-1-05 | [^acde] ac-1 278ac-1-05 ::= [f] | [a] ac-1-01 | [c] ac-1-03 | [e] ac-1-05 | [^acef] ac-1 279root ::= ac-1 280space ::= | " " | "\n"{1,2} [ \t]{0,20} 281)""" ,gbnf ); 282 }); 283 284t .test ("complex expressions with parentheses" , [](testing & t ) { 285auto parser = build_peg_parser ([](common_peg_parser_builder & p ) { 286return p .one_or_more (p .literal ("a" ) |p .literal ("b" )); 287 }); 288 289auto gbnf = build_grammar ([& ](const common_grammar_builder & builder ) { 290parser .build_grammar (builder ); 291 }); 292 293assert_gbnf_equal (t ,R"""( 294root ::= ("a" | "b")+ 295space ::= | " " | "\n"{1,2} [ \t]{0,20} 296)""" ,gbnf ); 297 }); 298 299t .test ("rule references" , [](testing & t ) { 300auto parser = build_peg_parser ([](common_peg_parser_builder & p ) { 301auto digit = p .rule ("digit" ,p .chars ("[0-9]" ,1 ,1 )); 302return p .one_or_more (digit ); 303 }); 304 305auto gbnf = build_grammar ([& ](const common_grammar_builder & builder ) { 306parser .build_grammar (builder ); 307 }); 308 309assert_gbnf_equal (t ,R"""( 310digit ::= [0-9] 311root ::= digit+ 312space ::= | " " | "\n"{1,2} [ \t]{0,20} 313)""" ,gbnf ); 314 }); 315 316t .test ("escaping in literals" , [](testing & t ) { 317auto parser = build_peg_parser ([](common_peg_parser_builder & p ) { 318return p .literal ("hello\nworld\n!" ); 319 }); 320 321auto gbnf = build_grammar ([& ](const common_grammar_builder & builder ) { 322parser .build_grammar (builder ); 323 }); 324 325assert_gbnf_equal (t ,R"""( 326root ::= "hello\nworld\n!" 327space ::= | " " | "\n"{1,2} [ \t]{0,20} 328)""" ,gbnf ); 329 }); 330 331t .test ("operator<< (whitespace insertion)" , [](testing & t ) { 332auto parser = build_peg_parser ([](common_peg_parser_builder & p ) { 333return p .literal ("hello" ) <<p .literal ("world" ); 334 }); 335 336auto gbnf = build_grammar ([& ](const common_grammar_builder & builder ) { 337parser .build_grammar (builder ); 338 }); 339 340assert_gbnf_equal (t ,R"""( 341root ::= "hello" space "world" 342space ::= | " " | "\n"{1,2} [ \t]{0,20} 343)""" ,gbnf ); 344 }); 345 346t .test ("emit only reachable rules" , [](testing & t ) { 347auto parser = build_peg_parser ([](common_peg_parser_builder & p ) { 348p .rule ("orphan" ,p .literal ("orphan" )); 349return p .literal ("hello" )+ p .rule ("child" ,p .literal (" world" )); 350 }); 351 352auto gbnf = build_grammar ([& ](const common_grammar_builder & builder ) { 353parser .build_grammar (builder ); 354 }); 355 356assert_gbnf_equal (t ,R"""( 357child ::= " world" 358root ::= "hello" child 359space ::= | " " | "\n"{1,2} [ \t]{0,20} 360)""" ,gbnf ); 361 }); 362 363t .test ("tagged choice inside sequence gets parenthesized" , [](testing & t ) { 364auto parser = build_peg_parser ([](common_peg_parser_builder & p ) { 365return p .literal ("a" )+ p .tag ("t" ,p .literal ("b" ) |p .literal ("c" )); 366 }); 367 368auto gbnf = build_grammar ([& ](const common_grammar_builder & builder ) { 369parser .build_grammar (builder ); 370 }); 371 372assert_gbnf_equal (t ,R"""( 373root ::= "a" ("b" | "c") 374space ::= | " " | "\n"{1,2} [ \t]{0,20} 375)""" ,gbnf ); 376 }); 377 378t .test ("tagged sequence inside choice gets parenthesized" , [](testing & t ) { 379auto parser = build_peg_parser ([](common_peg_parser_builder & p ) { 380return p .tag ("t" ,p .literal ("a" )+ p .literal ("b" )) |p .literal ("c" ); 381 }); 382 383auto gbnf = build_grammar ([& ](const common_grammar_builder & builder ) { 384parser .build_grammar (builder ); 385 }); 386 387assert_gbnf_equal (t ,R"""( 388root ::= "a" "b" | "c" 389space ::= | " " | "\n"{1,2} [ \t]{0,20} 390)""" ,gbnf ); 391 }); 392 393t .test ("atomic choice inside repetition gets parenthesized" , [](testing & t ) { 394auto parser = build_peg_parser ([](common_peg_parser_builder & p ) { 395return p .one_or_more (p .atomic (p .literal ("a" ) |p .literal ("b" ))); 396 }); 397 398auto gbnf = build_grammar ([& ](const common_grammar_builder & builder ) { 399parser .build_grammar (builder ); 400 }); 401 402assert_gbnf_equal (t ,R"""( 403root ::= ("a" | "b")+ 404space ::= | " " | "\n"{1,2} [ \t]{0,20} 405)""" ,gbnf ); 406 }); 407 408t .test ("silent parser emits nothing in gbnf" , [](testing & t ) { 409auto parser = build_peg_parser ([](common_peg_parser_builder & p ) { 410return p .literal ("hello" )+ p .gbnf (p .literal ("world" ),"" ); 411 }); 412 413auto gbnf = build_grammar ([& ](const common_grammar_builder & builder ) { 414parser .build_grammar (builder ); 415 }); 416 417assert_gbnf_equal (t ,R"""( 418root ::= "hello" 419space ::= | " " | "\n"{1,2} [ \t]{0,20} 420)""" ,gbnf ); 421 }); 422 423t .test ("silent choice inside sequence emits nothing" , [](testing & t ) { 424auto parser = build_peg_parser ([](common_peg_parser_builder & p ) { 425return p .literal ("a" )+ p .gbnf (p .literal ("b" ) |p .literal ("c" ),"" )+ p .literal ("d" ); 426 }); 427 428auto gbnf = build_grammar ([& ](const common_grammar_builder & builder ) { 429parser .build_grammar (builder ); 430 }); 431 432assert_gbnf_equal (t ,R"""( 433root ::= "a" "d" 434space ::= | " " | "\n"{1,2} [ \t]{0,20} 435)""" ,gbnf ); 436 }); 437 438t .test ("silent wrapped in tag emits nothing" , [](testing & t ) { 439auto parser = build_peg_parser ([](common_peg_parser_builder & p ) { 440return p .literal ("a" )+ p .tag ("t" ,p .gbnf (p .literal ("b" ),"" )); 441 }); 442 443auto gbnf = build_grammar ([& ](const common_grammar_builder & builder ) { 444parser .build_grammar (builder ); 445 }); 446 447assert_gbnf_equal (t ,R"""( 448root ::= "a" 449space ::= | " " | "\n"{1,2} [ \t]{0,20} 450)""" ,gbnf ); 451 }); 452 453t .test ("gbnf parser emits custom grammar" , [](testing & t ) { 454auto parser = build_peg_parser ([](common_peg_parser_builder & p ) { 455return p .literal ("a" )+ p .gbnf (p .literal ("b" ),"[a-z]+" ); 456 }); 457 458auto gbnf = build_grammar ([& ](const common_grammar_builder & builder ) { 459parser .build_grammar (builder ); 460 }); 461 462assert_gbnf_equal (t ,R"""( 463root ::= "a" [a-z]+ 464space ::= | " " | "\n"{1,2} [ \t]{0,20} 465)""" ,gbnf ); 466 }); 467 468t .test ("nested transparent wrappers get parenthesized" , [](testing & t ) { 469auto parser = build_peg_parser ([](common_peg_parser_builder & p ) { 470return p .literal ("x" )+ p .tag ("outer" ,p .atomic (p .literal ("a" ) |p .literal ("b" ))); 471 }); 472 473auto gbnf = build_grammar ([& ](const common_grammar_builder & builder ) { 474parser .build_grammar (builder ); 475 }); 476 477assert_gbnf_equal (t ,R"""( 478root ::= "x" ("a" | "b") 479space ::= | " " | "\n"{1,2} [ \t]{0,20} 480)""" ,gbnf ); 481 }); 482 483t .test ("emit only trigger rules (and references)" , [](testing & t ) { 484auto parser = build_peg_parser ([](common_peg_parser_builder & p ) { 485auto rule1 = p .rule ("rule-1" ,p .literal ("a" )+ p .ref ("rule-2" )); 486p .rule ("rule-2" ,p .literal ("b" )+ p .ref ("rule-3" ), true); 487p .rule ("rule-3" ,p .literal ("c" )+ p .ref ("rule-4" )); 488p .rule ("rule-4" ,p .literal ("d" ), true); 489return rule1 ; 490 }); 491 492auto gbnf = build_grammar ([& ](const common_grammar_builder & builder ) { 493parser .build_grammar (builder ); 494 }); 495 496assert_gbnf_equal (t ,R"""( 497root ::= rule-1 498rule-1 ::= "a" rule-2 499rule-2 ::= "b" rule-3 500rule-3 ::= "c" rule-4 501rule-4 ::= "d" 502space ::= | " " | "\n"{1,2} [ \t]{0,20} 503)""" ,gbnf ); 504 505auto gbnf_lazy = build_grammar ([& ](const common_grammar_builder & builder ) { 506parser .build_grammar (builder , true); 507 }); 508 509assert_gbnf_equal (t ,R"""( 510root ::= rule-2 | rule-4 511rule-2 ::= "b" rule-3 512rule-3 ::= "c" rule-4 513rule-4 ::= "d" 514space ::= | " " | "\n"{1,2} [ \t]{0,20} 515)""" ,gbnf_lazy ); 516 }); 517}