char-fork/llama.cpp

git clone https://git.t4t.associates/char-fork/llama.cpp

Aldehir Rojascommon : implement common_schema internal representation for JSON schemas (#28736)acecd5603

char/aliceai-t5-moe
41.5 KiB1495 linesraw
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) {
19    return 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) {
23    return build_grammar_with_root(grammar_str, "root");
24}
25
26static bool test_build_grammar_fails(const std::string & grammar_str) {
27    fprintf(stderr, "⚫ Testing failure for grammar: %s\n", grammar_str.c_str());
28    bool grammar_fails = false;
29    llama_grammar * grammar = build_grammar(grammar_str);
30    if (grammar != nullptr) {
31        fprintf(stderr, "  ❌ Expected build failure, but succeeded\n");
32    } else {
33        grammar_fails = true;
34        fprintf(stdout, "  ✅︎\n");
35    }
36    return grammar_fails;
37}
38
39struct token_and_piece {
40    llama_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) {
46    return std::string{
47        static_cast<char>(0xff),
48        static_cast<char>((id >> 24) & 0xff),
49        static_cast<char>((id >> 16) & 0xff),
50        static_cast<char>((id >> 8) & 0xff),
51        static_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;
58    result.reserve(input.size());
59    size_t offset = 0;
60    while (offset < input.size()) {
61        try {
62            if (static_cast<unsigned char>(input[offset]) == 0xff) {
63                if (offset + 5 > input.size()) {
64                    throw std::runtime_error("not enough bytes for token id");
65                }
66                uint32_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]));
71                auto piece = "<[" + std::to_string(val) + "]>";
72                result.push_back({static_cast<llama_token>(val), piece});
73                offset += 5;
74            } else {
75                uint32_t cpt = unicode_cpt_from_utf8(input, offset);
76                result.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;
81            result.push_back({0, unicode_cpt_to_utf8(0xFFFD)}); // replacement character
82        }
83    }
84    return result;
85}
86
87static bool match_string(const std::string & input, llama_grammar * grammar) {
88    const auto parsed = parse_tokens(input);
89
90    auto & stacks_cur = llama_grammar_get_stacks(grammar);
91
92    for (const auto & in : parsed) {
93        try {
94            llama_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
97            return false;
98        }
99
100        if (stacks_cur.empty()) {
101            // no stacks means that the grammar failed to match at this point
102            return false;
103        }
104    }
105
106    for (const auto & stack : stacks_cur) {
107        if (stack.empty()) {
108            // An empty stack means that the grammar has been completed
109            return true;
110        }
111    }
112
113    return 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) {
117    fprintf(stderr, "⚫ Testing %s\n%s\n", test_desc.c_str(), grammar_str.c_str());
118    fflush(stderr);
119
120    auto * 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
123    const llama_grammar_stacks stacks_org = llama_grammar_get_stacks(grammar); // copy
124
125    llama_grammar_stacks & stacks_cur = llama_grammar_get_stacks(grammar);
126
127    fprintf(stderr, "  🔵 Valid strings:\n");
128
129    // Passing strings
130    for (const auto & test_string : passing_strings) {
131        fprintf(stderr, "    \"%s\" ", test_string.c_str());
132        fflush(stderr);
133
134        bool matched = match_string(test_string, grammar);
135
136        if (!matched) {
137            fprintf(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
141            FILE* grammar_file = fopen("test-grammar-integration.grammar.gbnf", "w");
142            if (grammar_file) {
143                fprintf(grammar_file, "%s", grammar_str.c_str());
144                fclose(grammar_file);
145            }
146
147            // DEBUG: Write the test string to test-grammar-integration.string.txt
148            FILE* string_file = fopen("test-grammar-integration.string.txt", "w");
149            if (string_file) {
150                fprintf(string_file, "%s", test_string.c_str());
151                fclose(string_file);
152            }
153
154            fprintf(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 {
156            fprintf(stdout, "✅︎\n");
157        }
158
159        assert(matched);
160
161        // Reset the grammar stacks
162        stacks_cur = stacks_org;
163    }
164
165    fprintf(stderr, "  🟠 Invalid strings:\n");
166
167    // Failing strings
168    for (const auto & test_string : failing_strings) {
169        fprintf(stderr, "    \"%s\" ", test_string.c_str());
170        fflush(stderr);
171
172        bool matched = match_string(test_string, grammar);
173
174        if (matched) {
175            fprintf(stderr, "❌ (incorrectly matched)\n");
176        } else {
177            fprintf(stdout, "✅︎\n");
178        }
179        assert(!matched);
180
181        // Reset the grammar stacks
182        stacks_cur = stacks_org;
183    }
184
185    // Clean up allocated memory
186    llama_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) {
189    test(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) {
192    test(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() {
196    test_schema(
197        "min 0",
198        R"""({
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    );
221    test_schema(
222        "min 2",
223        // Schema
224        R"""({
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    );
250    test_schema(
251        "min 456",
252        R"""({
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    );
274    test_schema(
275        "min -123",
276        R"""({
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
299    test_schema(
300        "max 9999",
301        // Schema
302        R"""({
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    );
318    test_schema(
319        "max -9999",
320        // Schema
321        R"""({
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    );
337    test_schema(
338        "min 5 max 30",
339        // Schema
340        R"""({
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    );
361    test_schema(
362        "min 1 max 900719925474091",
363        // Schema
364        R"""({
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    );
385    test_schema(
386        "min -1 max 1",
387        R"""({
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    );
408    test_schema(
409        "min -123 max 42",
410        R"""({
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    );
442    test_schema(
443        "exclusive min / max",
444        // Schema
445        R"""({
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
465    test_grammar(
466        "simple grammar",
467        R"""(
468            root ::= expr
469            expr ::= term ("+" term)*
470            term ::= number
471            number ::= [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
488    test_grammar(
489        "simple grammar with tokens",
490        R"""(
491            root ::= <[10]> content <[11]>
492            content ::= (!<[11]>)*)""",
493        // Passing strings
494        {
495            token(10) + "hello world" + token(11),
496            token(10) + "text with " + token(12) + " other tokens " + token(13) + " mixed in" + token(11),
497            token(10) + token(11),
498            token(10) + token(12) + token(13) + token(14) + token(15) + token(11),
499            token(10) + "a" + token(11),
500        },
501        // Failing strings
502        {
503            token(10) + "missing end token",
504            token(10),
505            "missing start token" + token(11),
506            token(10) + token(11) + token(11),  // double end token
507            token(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
514    test_grammar(
515        "medium complexity grammar",
516        // Grammar
517        R"""(
518            root ::= expression
519            expression ::= term ws (("+"|"-") ws term)*
520            term ::= factor ws (("*"|"/") ws factor)*
521            factor ::= number | variable | "(" expression ")" | function-call
522            number ::= [0-9]+
523            variable ::= [a-zA-Z_][a-zA-Z0-9_]*
524            function-call ::= variable ws "(" (expression ("," ws expression)*)? ")"
525            ws ::= [ \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
573    test_grammar(
574        "complex grammar with tokens",
575        R"""(
576            root ::= reasoning+ content tool-call*
577            reasoning ::= <[10]> (!<[11]>)* <[11]>
578            content ::= <[20]> (!<[21]>)* <[21]>
579            tool-call ::= <[12]> name <[13]> args <[14]>
580            name ::= (!<[13]>)+
581            args ::= (!<[14]>)*)""",
582        // Passing strings
583        {
584            token(10) + "I am thinking" + token(11) + token(20) + "hello world!" + token(21) + token(12) + "search" + token(13) + "query=test" + token(14),
585            token(10) + "reasoning 1" + token(11) + token(10) + "reasoning 2" + token(11) + token(20) + token(21) + token(12) + "tool" + token(13) + token(14),
586            token(10) + token(11) + token(20) + "content" + token(21),
587            token(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),
588            token(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        {
592            token(20) + "content only" + token(21),
593            token(10) + "no closing reasoning",
594            token(10) + token(11) + token(20) + "no closing content",
595            token(10) + token(11) + token(20) + token(21) + token(12) + "incomplete tool",
596            token(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 "."
603    test_grammar(
604        "special characters",
605        // Grammar
606        R"""(
607            root ::= ... "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
631    test_grammar(
632        "* quantifier",
633        // Grammar
634        R"""(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    );
652    test_grammar(
653        "+ quantifier",
654        // Grammar
655        R"""(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    );
673    test_grammar(
674        "? quantifier",
675        // Grammar
676        R"""(root ::= "a"?)""",
677        // Passing strings
678        {
679            "",
680            "a"
681        },
682        // Failing strings
683        {
684            "b",
685            "ab",
686            "aa",
687            "ba",
688        }
689    );
690    test_grammar(
691        "mixed quantifiers",
692        // Grammar
693        R"""(
694            root ::= cons+ vowel* cons? (vowel cons)*
695            vowel ::= [aeiouy]
696            cons ::= [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    );
715    test_grammar(
716        "simple exact repetition",
717        // Grammar
718        R"""(
719            root ::= [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    );
734    test_grammar(
735        "simple min repetition",
736        // Grammar
737        R"""(
738            root ::= [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    );
753    test_grammar(
754        "simple max repetition",
755        // Grammar
756        R"""(
757            root ::= [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    );
772    test_grammar(
773        "min / max repetition",
774        // Grammar
775        R"""(
776            root ::= ("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    );
791    test_grammar(
792        "segfault",
793        // Grammar
794        R"""(
795            root ::= ( [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() {
812    fprintf(stderr, "⚫ Testing missing root node:\n");
813    // Test case for a grammar that is missing a root rule
814    const std::string grammar_str = R"""(
815        rot ::= expr
816        expr ::= term ("+" term)*
817        term ::= number
818        number ::= [0-9]+)""";
819
820    llama_grammar_parser parsed_grammar;
821    parsed_grammar.parse(grammar_str.c_str());
822
823    // Ensure we parsed correctly
824    assert(!parsed_grammar.rules.empty());
825
826    // Ensure we do NOT have a root node
827    assert(parsed_grammar.symbol_ids.find("root") == parsed_grammar.symbol_ids.end());
828    fprintf(stderr, "  ✅︎ Passed\n");
829}
830
831static void test_failure_missing_reference() {
832    fprintf(stderr, "⚫ Testing missing reference node:\n");
833
834    // Test case for a grammar that is missing a referenced rule
835    const std::string grammar_str =
836        R"""(root ::= expr
837        expr ::= term ("+" term)*
838        term ::= numero
839        number ::= [0-9]+)""";
840
841    fprintf(stderr, "    Expected error:  ");
842
843    llama_grammar_parser parsed_grammar;
844    parsed_grammar.parse(grammar_str.c_str());
845
846    // Ensure we did NOT parsed correctly
847    assert(parsed_grammar.rules.empty());
848
849    fprintf(stderr, "    End of expected error.\n");
850    fprintf(stderr, "  ✅︎ Passed\n");
851}
852
853static void test_failure_left_recursion() {
854    fprintf(stderr, "⚫ Testing left recursion detection:\n");
855
856    // Test simple left recursion detection
857    const std::string simple_str = R"""(root ::= "a" | root "a")""";
858    assert(test_build_grammar_fails(simple_str));
859
860    // Test more complicated left recursion detection
861    const std::string medium_str = R"""(
862        root ::= asdf
863        asdf ::= "a" | asdf "a"
864        )""";
865    assert(test_build_grammar_fails(medium_str));
866
867    // Test even more complicated left recursion detection
868    const std::string hard_str = R"""(
869        root ::= asdf
870        asdf ::= "a" | foo "b"
871        foo ::= "c" | asdf "d" | "e")""";
872    assert(test_build_grammar_fails(hard_str));
873
874    // Test yet even more complicated left recursion detection
875    const std::string hardest_str = R"""(
876        root ::= asdf
877        asdf ::= "a" | foo "b"
878        foo ::= "c" | empty asdf "d" | "e"
879        empty ::= "blah" | )""";
880    assert(test_build_grammar_fails(hardest_str));
881
882    fprintf(stderr, "  ✅︎ Passed\n");
883}
884
885static void test_failure_missing_root_symbol() {
886    fprintf(stderr, "⚫ Testing missing root symbol:\n");
887
888    const std::string grammar_str = R"""(
889        root ::= "foobar"
890    )""";
891
892    llama_grammar * failure_result = build_grammar_with_root(grammar_str, "nonexistent");
893    assert(failure_result == nullptr);
894
895    fprintf(stderr, "  ✅︎ Passed\n");
896}
897
898static void test_custom_root_symbol_check() {
899    fprintf(stderr, "⚫ Testing custom root symbol check:\n");
900
901    const std::string custom_root_grammar_str = R"""(
902        foobar ::= "foobar"
903    )""";
904
905    llama_grammar * failure_result = build_grammar_with_root(custom_root_grammar_str, "root");
906    assert(failure_result == nullptr);
907
908    llama_grammar * success_result = build_grammar_with_root(custom_root_grammar_str, "foobar");
909    assert(success_result != nullptr);
910    llama_grammar_free_impl(success_result);
911
912    fprintf(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
920    test_schema(
921        "empty schema (any value)",
922        // Schema
923        R"""(
924            {}
925        )""",
926        // Passing strings
927        {
928            R"""({})""",
929            R"""({"foo": "bar"})""",
930            "[]",
931            "null",
932            R"""("")""",
933            "true",
934        },
935        // Failing strings
936        {
937            "",
938            R"""({"foo"})""",
939            "foo",
940        }
941    );
942
943    test_schema(
944        "exotic formats (list)",
945        // Schema
946        R"""({
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?
958            R"""(["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        {
964            R"""(["foo", "bar"])""",
965            R"""(["12345678-1234-1234-1234-1234567890ab"])""",
966        }
967    );
968
969    test_schema(
970        "string",
971        // Schema
972        R"""({
973            "type": "string"
974        })""",
975        // Passing strings
976        {
977            R"""("foo")""",
978            R"""("bar")""",
979            R"""("")""",
980        },
981        // Failing strings
982        {
983            R"""({})""",
984            R"""("foo": "bar")""",
985        }
986    );
987
988    test_schema(
989        "string w/ min length 1",
990        // Schema
991        R"""({
992            "type": "string",
993            "minLength": 1
994        })""",
995        // Passing strings
996        {
997            R"""("foo")""",
998            R"""("bar")""",
999        },
1000        // Failing strings
1001        {
1002            R"""("")""",
1003            R"""({})""",
1004            R"""("foo": "bar")""",
1005        }
1006    );
1007
1008    test_schema(
1009        "string w/ min length 3",
1010        // Schema
1011        R"""({
1012                "type": "string",
1013                "minLength": 3
1014        })""",
1015        // Passing strings
1016        {
1017            R"""("foo")""",
1018            R"""("bar")""",
1019            R"""("foobar")""",
1020        },
1021        // Failing strings
1022        {
1023            R"""("")""",
1024            R"""("f")""",
1025            R"""("fo")""",
1026        }
1027    );
1028
1029    test_schema(
1030        "string w/ max length",
1031        // Schema
1032        R"""({
1033            "type": "string",
1034            "maxLength": 3
1035        })""",
1036        // Passing strings
1037        {
1038            R"""("foo")""",
1039            R"""("bar")""",
1040            R"""("")""",
1041            R"""("f")""",
1042            R"""("fo")""",
1043        },
1044        // Failing strings
1045        {
1046            R"""("foobar")""",
1047        }
1048    );
1049
1050    test_schema(
1051        "string w/ min & max length",
1052        // Schema
1053        R"""({
1054            "type": "string",
1055            "minLength": 1,
1056            "maxLength": 4
1057        })""",
1058        // Passing strings
1059        {
1060            R"""("foo")""",
1061            R"""("bar")""",
1062            R"""("f")""",
1063            R"""("barf")""",
1064        },
1065        // Failing strings
1066        {
1067            R"""("")""",
1068            R"""("barfo")""",
1069            R"""("foobar")""",
1070        }
1071    );
1072
1073    test_schema(
1074        "boolean",
1075        // Schema
1076        R"""({
1077            "type": "boolean"
1078        })""",
1079        // Passing strings
1080        {
1081            "true",
1082            "false",
1083        },
1084        // Failing strings
1085        {
1086            R"""("")""",
1087            R"""("true")""",
1088            R"""(True)""",
1089            R"""(FALSE)""",
1090        }
1091    );
1092
1093    test_schema(
1094        "integer",
1095        // Schema
1096        R"""({
1097            "type": "integer"
1098        })""",
1099        // Passing strings
1100        {
1101            R"""(0)""",
1102            R"""(12345)""",
1103            R"""(1234567890123456)""",
1104        },
1105        // Failing strings
1106        {
1107            R"""()""",
1108            R"""(01)""",
1109            R"""(007)""",
1110            R"""(12345678901234567  )""",
1111        }
1112    );
1113
1114    test_schema(
1115        "string const",
1116        // Schema
1117        R"""({
1118            "const": "foo"
1119        })""",
1120        // Passing strings
1121        {
1122            R"""("foo")""",
1123        },
1124        // Failing strings
1125        {
1126            R"""(foo)""",
1127            R"""("bar")""",
1128        }
1129    );
1130
1131    test_schema(
1132        "non-string const",
1133        // Schema
1134        R"""({
1135            "const": true
1136        })""",
1137        // Passing strings
1138        {
1139            R"""(true)""",
1140        },
1141        // Failing strings
1142        {
1143            R"""()""",
1144            R"""(foo)""",
1145            R"""("true")""",
1146        }
1147    );
1148
1149    test_schema(
1150        "non-string const",
1151        // Schema
1152        R"""({
1153            "enum": ["red", "amber", "green", null, 42, ["foo"]]
1154        })""",
1155        // Passing strings
1156        {
1157            R"""("red")""",
1158            R"""(null)""",
1159            R"""(42)""",
1160            R"""(["foo"])""",
1161        },
1162        // Failing strings
1163        {
1164            R"""()""",
1165            R"""(420)""",
1166            R"""(true)""",
1167            R"""(foo)""",
1168        }
1169    );
1170
1171    test_schema(
1172        "simple pattern",
1173        // Schema
1174        R"""({
1175            "pattern": "^[a-zA-Z0-9_-]*$"
1176        })""",
1177        // Passing strings
1178        {
1179            R"""("")""",
1180            R"""("He_llo-12")""",
1181        },
1182        // Failing strings
1183        {
1184            R"""("!")""",
1185            R"""("Hello World")""",
1186        }
1187    );
1188
1189    test_schema(
1190        "pattern with escapes",
1191        // Schema
1192        R"""({
1193            "pattern": "^a\\^\\$\\.\\[\\]\\(\\)\\|\\{\\}\\*\\+\\?b$"
1194        })""",
1195        // Passing strings
1196        {
1197            R"""("a^$.[]()|{}*+?b")""",
1198        },
1199        // Failing strings
1200        {
1201            R"""("ab")""",
1202        }
1203    );
1204
1205    test_schema(
1206        "",
1207        // Schema
1208        R"""(
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
1230    test_schema(
1231        "min+max items",
1232        // Schema
1233        R"""({
1234            "items": {
1235                "type": ["number", "integer"]
1236            },
1237            "minItems": 3,
1238            "maxItems": 5
1239        })""",
1240        // Passing strings
1241        {
1242            R"""([1, 2, 3])""",
1243            R"""([1, 2, 3, 4])""",
1244            R"""([1, 2, 3, 4, 5])""",
1245        },
1246        // Failing strings
1247        {
1248            R"""([1, 2])""",
1249            R"""([1, 2, 3, 4, 5, 6])""",
1250            R"""(1)""",
1251        }
1252    );
1253
1254    // Properties (from: https://json-schema.org/understanding-json-schema/reference/object#properties)
1255    test_schema(
1256        "object properties",
1257        // Schema
1258        R"""({
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        {
1268            R"""({ "number": 1600, "street_name": "Pennsylvania", "street_type":"Avenue"})""",
1269            // "By default, leaving out properties is valid"
1270            R"""({ "street_name": "Pennsylvania" })""",
1271            R"""({ "number": 1600, "street_name": "Pennsylvania" })""",
1272            // "By extension, even an empty object is valid"
1273            R"""({})""",
1274            R"""({ "number": 1600, "street_name": "Pennsylvania", "street_type": "Avenue" })""",
1275        },
1276        // Failing strings
1277        {
1278            // Change datatype from number to string
1279            R"""({ "number": "1600", "street_name": "Pennsylvania", "street_type":"Avenue"})""",
1280            // Reorder properties
1281            R"""({ "street_name": "Pennsylvania", "number": 1600 })""",
1282            // Reorder properties
1283            R"""({ "number": "1600", "street_name": "Pennsylvania", "street_type":"Avenue"})""",
1284            // "Additional properties default to false for generation, even though the spec says true.
1285            R"""({ "number": 1600, "street_name": "Pennsylvania", "street_type":"Avenue", "direction":"NW"})""",
1286
1287        }
1288    );
1289
1290    test_schema(
1291        "additional properties can't override other properties",
1292        R"""({
1293            "properties": {
1294                "a": {"type": "integer"},
1295                "b": {"type": "integer"}
1296            },
1297            "additionalProperties": true
1298        })""",
1299        // Passing strings
1300        {
1301            R"""({"a": 42})""",
1302            R"""({"c": ""})""",
1303            R"""({"a": 42, "c": ""})""",
1304            R"""({"a_": ""})""",
1305        },
1306        // Failing strings
1307        {
1308            R"""()""",
1309            R"""({"a": ""})""",
1310            R"""({"a": "", "b": ""})""",
1311        }
1312    );
1313
1314    // Properties (from: https://json-schema.org/understanding-json-schema/reference/object#properties)
1315    test_schema(
1316        "object properties, additionalProperties: true",
1317        // Schema
1318        R"""({
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"
1330            R"""({})""",
1331            R"""({"number":1600,"street_name":"Pennsylvania","street_type":"Avenue"})""",
1332            // "By default, leaving out properties is valid"
1333            R"""({ "street_name": "Pennsylvania" })""",
1334            R"""({ "number": 1600, "street_name": "Pennsylvania" })""",
1335            // "By default, providing additional properties is valid"
1336            R"""({ "number": 1600, "street_name": "Pennsylvania", "street_type":"Avenue", "direction":"NW"})""",
1337            R"""({ "number": 1600, "street_name": "Pennsylvania", "street_type": "Avenue" })""",
1338        },
1339        // Failing strings
1340        {
1341            // Change datatype from number to string
1342            R"""({ "number": "1600", "street_name": "Pennsylvania", "street_type":"Avenue"})""",
1343            // Reorder properties
1344            R"""({ "street_name": "Pennsylvania", "number": 1600, "street_type":"Avenue"})""",
1345        }
1346    );
1347
1348    // Additional properties: false
1349    test_schema(
1350        "required + optional props each in original order",
1351        // Schema
1352        R"""({
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        {
1363            R"""({ "street_name": "Pennsylvania" })""",
1364            R"""({ "number": 1600, "street_type":"Avenue"})""",
1365            R"""({ "number": 1600, "street_name": "Pennsylvania" })""",
1366            R"""({ "number": 1600, "street_name": "Pennsylvania", "street_type":"Avenue"})""",
1367            // Spaces are permitted around enum values
1368            R"""({ "number": 1600, "street_name": "Pennsylvania", "street_type": "Avenue" })""",
1369        },
1370        // Failing strings
1371        {
1372            // Reorder properties
1373            R"""({ "street_type": "Avenue", "number": 1600 })""",
1374            // Add "direction"
1375            R"""({ "number": 1600, "street_name": "Pennsylvania", "street_type": "Avenue", "direction": "NW" })""",
1376        }
1377    );
1378
1379    test_schema(
1380        "required + optional props each in original order",
1381        // Schema
1382        R"""({
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        {
1394            R"""({"b": "foo", "a": "bar"})""",
1395            R"""({"b":"foo","a":"bar","d":"qux"})""",
1396            R"""({"b":"foo", "a":"bar", "d":"qux", "c":"baz"})""",
1397        },
1398        // Failing strings
1399        {
1400            R"""({"a": "foo", "b": "bar"})""",
1401            R"""({"b": "bar"})""",
1402            R"""({"a": "foo", "c": "baz"})""",
1403            R"""({"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
1408    test_schema(
1409        "required props",
1410        // Schema
1411        R"""({
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        {
1460            R"""({"productId": 1, "productName": "A green door", "price": 12.50})""",
1461            R"""({"productId": 1, "productName": "A green door", "price": 12.50, "tags": ["home", "green"]})""",
1462            R"""({"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        {
1466            R"""({})""", // Missing all required properties
1467            R"""({"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})""",
1471            R"""({"productId": 1, "productName": "A green door"})""", // Missing required property (price)
1472            R"""({"productName": "A green door", "price": 12.50})""", // Missing required property (productId)
1473            R"""({"productId": 1, "productName": "A green door", "price": 12.50, "tags": []})""", // tags is empty, but minItems is 1
1474            R"""({"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() {
1482    fprintf(stdout, "Running grammar integration tests...\n");
1483    test_simple_grammar();
1484    test_complex_grammar();
1485    test_special_chars();
1486    test_quantifiers();
1487    test_failure_missing_root();
1488    test_failure_missing_reference();
1489    test_failure_left_recursion();
1490    test_failure_missing_root_symbol();
1491    test_custom_root_symbol_check();
1492    test_json_schema();
1493    fprintf(stdout, "All tests passed.\n");
1494    return 0;
1495}