| | #ifdef NDEBUG |
| | #undef NDEBUG |
| | #endif |
| |
|
| | #include "unicode.h" |
| | #include "llama-grammar.h" |
| | #include "json-schema-to-grammar.h" |
| |
|
| | #include <cassert> |
| | #include <string> |
| | #include <vector> |
| |
|
| | using json = nlohmann::ordered_json; |
| |
|
| | static llama_grammar * build_grammar(const std::string & grammar_str) { |
| | return llama_grammar_init_impl(nullptr, grammar_str.c_str(), "root"); |
| | } |
| |
|
| | static bool test_build_grammar_fails(const std::string & grammar_str) { |
| | fprintf(stderr, "⚫ Testing failure for grammar: %s\n", grammar_str.c_str()); |
| | bool grammar_fails = false; |
| | llama_grammar * grammar = build_grammar(grammar_str); |
| | if (grammar != nullptr) { |
| | fprintf(stderr, " ❌ Expected build failure, but succeeded\n"); |
| | } else { |
| | grammar_fails = true; |
| | fprintf(stdout, " ✅︎\n"); |
| | } |
| | return grammar_fails; |
| | } |
| |
|
| | static bool match_string(const std::string & input, llama_grammar * grammar) { |
| | const auto cpts = unicode_cpts_from_utf8(input); |
| |
|
| | const llama_grammar_rules & rules = llama_grammar_get_rules (grammar); |
| | llama_grammar_stacks & stacks_cur = llama_grammar_get_stacks(grammar); |
| |
|
| | for (const auto & cpt : cpts) { |
| | const llama_grammar_stacks stacks_prev = llama_grammar_get_stacks(grammar); |
| |
|
| | llama_grammar_accept(rules, stacks_prev, cpt, stacks_cur); |
| |
|
| | if (stacks_cur.empty()) { |
| | |
| | return false; |
| | } |
| | } |
| |
|
| | for (const auto & stack : stacks_cur) { |
| | if (stack.empty()) { |
| | |
| | return true; |
| | } |
| | } |
| |
|
| | return false; |
| | } |
| |
|
| | static 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) { |
| | fprintf(stderr, "⚫ Testing %s\n%s\n", test_desc.c_str(), grammar_str.c_str()); |
| | fflush(stderr); |
| |
|
| | auto * grammar = build_grammar(grammar_str); |
| |
|
| | |
| | const llama_grammar_stacks stacks_org = llama_grammar_get_stacks(grammar); |
| |
|
| | llama_grammar_stacks & stacks_cur = llama_grammar_get_stacks(grammar); |
| |
|
| | fprintf(stderr, " 🔵 Valid strings:\n"); |
| |
|
| | |
| | for (const auto & test_string : passing_strings) { |
| | fprintf(stderr, " \"%s\" ", test_string.c_str()); |
| | fflush(stderr); |
| |
|
| | bool matched = match_string(test_string, grammar); |
| |
|
| | if (!matched) { |
| | fprintf(stderr, "❌ (failed to match)\n"); |
| |
|
| | |
| | |
| | FILE* grammar_file = fopen("test-grammar-integration.grammar.gbnf", "w"); |
| | if (grammar_file) { |
| | fprintf(grammar_file, "%s", grammar_str.c_str()); |
| | fclose(grammar_file); |
| | } |
| |
|
| | |
| | FILE* string_file = fopen("test-grammar-integration.string.txt", "w"); |
| | if (string_file) { |
| | fprintf(string_file, "%s", test_string.c_str()); |
| | fclose(string_file); |
| | } |
| |
|
| | 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"); |
| | } else { |
| | fprintf(stdout, "✅︎\n"); |
| | } |
| |
|
| | assert(matched); |
| |
|
| | |
| | stacks_cur = stacks_org; |
| | } |
| |
|
| | fprintf(stderr, " 🟠 Invalid strings:\n"); |
| |
|
| | |
| | for (const auto & test_string : failing_strings) { |
| | fprintf(stderr, " \"%s\" ", test_string.c_str()); |
| | fflush(stderr); |
| |
|
| | bool matched = match_string(test_string, grammar); |
| |
|
| | if (matched) { |
| | fprintf(stderr, "❌ (incorrectly matched)\n"); |
| | } else { |
| | fprintf(stdout, "✅︎\n"); |
| | } |
| | assert(!matched); |
| |
|
| | |
| | stacks_cur = stacks_org; |
| | } |
| |
|
| | |
| | llama_grammar_free_impl(grammar); |
| | } |
| | static 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) { |
| | test(test_desc + ". Grammar: " + grammar_str, grammar_str, passing_strings, failing_strings); |
| | } |
| | static 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) { |
| | test(test_desc + ". Schema: " + schema_str, json_schema_to_grammar(json::parse(schema_str)), passing_strings, failing_strings); |
| | } |
| |
|
| | static void test_simple_grammar() { |
| | test_schema( |
| | "min 0", |
| | R"""({ |
| | "type": "integer", |
| | "minimum": 0 |
| | })""", |
| | |
| | { |
| | "0", |
| | "10", |
| | "12", |
| | "10000", |
| | }, |
| | |
| | { |
| | "-1", |
| | "-10", |
| | "-10000", |
| | "-100000000000000000000000000000000", |
| | "100000000000000000000000000000000", |
| | "00", |
| | "01", |
| | "-0", |
| | } |
| | ); |
| | test_schema( |
| | "min 2", |
| | |
| | R"""({ |
| | "type": "integer", |
| | "minimum": 2 |
| | })""", |
| | |
| | { |
| | "2", |
| | "3", |
| | "4", |
| | "10", |
| | "20", |
| | "1234567890000000", |
| | }, |
| | |
| | { |
| | "0", |
| | "1", |
| | "-1", |
| | "-100", |
| | "0", |
| | "1", |
| | "01", |
| | "02", |
| | "12345678900000000", |
| | } |
| | ); |
| | test_schema( |
| | "min 456", |
| | R"""({ |
| | "type": "integer", |
| | "minimum": 456 |
| | })""", |
| | |
| | { |
| | "456", |
| | "4560", |
| | "457", |
| | "460", |
| | "500", |
| | }, |
| | |
| | { |
| | "455", |
| | "356", |
| | "50", |
| | "050", |
| | "-1", |
| | "-456", |
| | } |
| | ); |
| | test_schema( |
| | "min -123", |
| | R"""({ |
| | "type": "integer", |
| | "minimum": -123 |
| | })""", |
| | |
| | { |
| | "-123", |
| | "-122", |
| | "-11", |
| | "-1", |
| | "0", |
| | "1", |
| | "123", |
| | "1234", |
| | "2345", |
| | }, |
| | |
| | { |
| | "-1234", |
| | "-124", |
| | } |
| | ); |
| |
|
| | test_schema( |
| | "max 9999", |
| | |
| | R"""({ |
| | "type": "integer", |
| | "maximum": 9999 |
| | })""", |
| | |
| | { |
| | "-99999", |
| | "0", |
| | "9999", |
| | }, |
| | |
| | { |
| | "10000", |
| | "99991", |
| | } |
| | ); |
| | test_schema( |
| | "max -9999", |
| | |
| | R"""({ |
| | "type": "integer", |
| | "maximum": -9999 |
| | })""", |
| | |
| | { |
| | "-10000", |
| | "-9999", |
| | }, |
| | |
| | { |
| | "-9998", |
| | "0", |
| | "9999", |
| | } |
| | ); |
| | test_schema( |
| | "min 5 max 30", |
| | |
| | R"""({ |
| | "type": "integer", |
| | "minimum": 5, |
| | "maximum": 30 |
| | })""", |
| | |
| | { |
| | "5", |
| | "10", |
| | "30", |
| | }, |
| | |
| | { |
| | "05", |
| | "4", |
| | "-1", |
| | "31", |
| | "123", |
| | "0123", |
| | } |
| | ); |
| | test_schema( |
| | "min -1 max 1", |
| | R"""({ |
| | "type": "integer", |
| | "minimum": -1, |
| | "maximum": 1 |
| | })""", |
| | |
| | { |
| | "-1", |
| | "0", |
| | "1", |
| | }, |
| | |
| | { |
| | "-11", |
| | "-10", |
| | "-2", |
| | "2", |
| | "10", |
| | "11", |
| | } |
| | ); |
| | test_schema( |
| | "min -123 max 42", |
| | R"""({ |
| | "type": "integer", |
| | "minimum": -123, |
| | "maximum": 42 |
| | })""", |
| | |
| | { |
| | "-123", |
| | "-122", |
| | "-13", |
| | "-11", |
| | "-2", |
| | "-1", |
| | "0", |
| | "1", |
| | "5", |
| | "10", |
| | "39", |
| | "40", |
| | "42", |
| | }, |
| | |
| | { |
| | "-0123", |
| | "-124", |
| | "-1123", |
| | "-200", |
| | "43", |
| | "123", |
| | "0123", |
| | } |
| | ); |
| | test_schema( |
| | "exclusive min / max", |
| | |
| | R"""({ |
| | "type": "integer", |
| | "exclusiveMinimum": 0, |
| | "exclusiveMaximum": 10000 |
| | })""", |
| | |
| | { |
| | "1", |
| | "9999", |
| | }, |
| | |
| | { |
| | "0", |
| | "01", |
| | "10000", |
| | "99999", |
| | } |
| | ); |
| |
|
| | |
| | test_grammar( |
| | "simple grammar", |
| | R"""( |
| | root ::= expr |
| | expr ::= term ("+" term)* |
| | term ::= number |
| | number ::= [0-9]+)""", |
| | |
| | { |
| | "42", |
| | "1+2+3+4+5", |
| | "123+456", |
| | }, |
| | |
| | { |
| | "+", |
| | "/ 3", |
| | "1+2+3+4+5+", |
| | "12a45", |
| | } |
| | ); |
| | } |
| |
|
| | static void test_complex_grammar() { |
| | |
| | test_grammar( |
| | "medium complexity grammar", |
| | |
| | R"""( |
| | root ::= expression |
| | expression ::= term ws (("+"|"-") ws term)* |
| | term ::= factor ws (("*"|"/") ws factor)* |
| | factor ::= number | variable | "(" expression ")" | function-call |
| | number ::= [0-9]+ |
| | variable ::= [a-zA-Z_][a-zA-Z0-9_]* |
| | function-call ::= variable ws "(" (expression ("," ws expression)*)? ")" |
| | ws ::= [ \t\n\r]?)""", |
| | |
| | { |
| | "42", |
| | "1*2*3*4*5", |
| | "x", |
| | "x+10", |
| | "x1+y2", |
| | "(a+b)*(c-d)", |
| | "func()", |
| | "func(x,y+2)", |
| | "a*(b+c)-d/e", |
| | "f(g(x),h(y,z))", |
| | "x + 10", |
| | "x1 + y2", |
| | "(a + b) * (c - d)", |
| | "func()", |
| | "func(x, y + 2)", |
| | "a * (b + c) - d / e", |
| | "f(g(x), h(y, z))", |
| | "123+456", |
| | "123*456*789-123/456+789*123", |
| | "123+456*789-123/456+789*123-456/789+123*456-789/123+456*789-123/456+789*123-456" |
| | }, |
| | |
| | { |
| | "+", |
| | "/ 3x", |
| | "x + + y", |
| | "a * / b", |
| | "func(,)", |
| | "func(x y)", |
| | "(a + b", |
| | "x + y)", |
| | "a + b * (c - d", |
| | "42 +", |
| | "x +", |
| | "x + 10 +", |
| | "(a + b) * (c - d", |
| | "func(", |
| | "func(x, y + 2", |
| | "a * (b + c) - d /", |
| | "f(g(x), h(y, z)", |
| | "123+456*789-123/456+789*123-456/789+123*456-789/123+456*789-123/456+789*123-456/", |
| | } |
| | ); |
| | } |
| |
|
| | static void test_special_chars() { |
| | |
| | test_grammar( |
| | "special characters", |
| | |
| | R"""( |
| | root ::= ... "abc" ... |
| | )""", |
| | |
| | { |
| | "abcabcabc", |
| | "aaaabcccc", |
| | |
| | "🔵🟠✅abc❌🟠🔵" |
| | }, |
| | |
| | { |
| | "aaabcccc", |
| | "aaaaabcccc", |
| | "aaaabccc", |
| | "aaaabccccc", |
| | "🔵🟠✅❌abc❌✅🟠🔵", |
| | "🔵🟠abc🟠🔵" |
| | } |
| | ); |
| | } |
| |
|
| | static void test_quantifiers() { |
| | |
| |
|
| | test_grammar( |
| | "* quantifier", |
| | |
| | R"""(root ::= "a"*)""", |
| | |
| | { |
| | "", |
| | "a", |
| | "aaaaa", |
| | "aaaaaaaaaaaaaaaaaa", |
| | "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" |
| | }, |
| | |
| | { |
| | "b", |
| | "ab", |
| | "aab", |
| | "ba", |
| | "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaab" |
| | } |
| | ); |
| | test_grammar( |
| | "+ quantifier", |
| | |
| | R"""(root ::= "a"+)""", |
| | |
| | { |
| | "a", |
| | "aaaaa", |
| | "aaaaaaaaaaaaaaaaaa", |
| | "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" |
| | }, |
| | |
| | { |
| | "", |
| | "b", |
| | "ab", |
| | "aab", |
| | "ba", |
| | "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaab" |
| | } |
| | ); |
| | test_grammar( |
| | "? quantifier", |
| | |
| | R"""(root ::= "a"?)""", |
| | |
| | { |
| | "", |
| | "a" |
| | }, |
| | |
| | { |
| | "b", |
| | "ab", |
| | "aa", |
| | "ba", |
| | } |
| | ); |
| | test_grammar( |
| | "mixed quantifiers", |
| | |
| | R"""( |
| | root ::= cons+ vowel* cons? (vowel cons)* |
| | vowel ::= [aeiouy] |
| | cons ::= [bcdfghjklmnpqrstvwxyz] |
| | )""", |
| | |
| | { |
| | "yes", |
| | "no", |
| | "noyes", |
| | "crwth", |
| | "four", |
| | "bryyyy", |
| | }, |
| | |
| | { |
| | "yess", |
| | "yesno", |
| | "forty", |
| | "catyyy", |
| | } |
| | ); |
| | test_grammar( |
| | "simple exact repetition", |
| | |
| | R"""( |
| | root ::= [ab]{4} |
| | )""", |
| | |
| | { |
| | "aaaa", |
| | "bbbb", |
| | "abab", |
| | }, |
| | |
| | { |
| | "a", |
| | "b", |
| | "aaaaa", |
| | } |
| | ); |
| | test_grammar( |
| | "simple min repetition", |
| | |
| | R"""( |
| | root ::= [ab]{4,} |
| | )""", |
| | |
| | { |
| | "aaaa", |
| | "aaaaab", |
| | "bbbb", |
| | "ababab", |
| | }, |
| | |
| | { |
| | "", |
| | "aba", |
| | } |
| | ); |
| | test_grammar( |
| | "simple max repetition", |
| | |
| | R"""( |
| | root ::= [ab]{0,4} |
| | )""", |
| | |
| | { |
| | "", |
| | "a", |
| | "aa", |
| | "aaa", |
| | "aaab", |
| | }, |
| | |
| | { |
| | "aaaaa", |
| | } |
| | ); |
| | test_grammar( |
| | "min / max repetition", |
| | |
| | R"""( |
| | root ::= ("0x" [A-F0-9]{2} " "?){3,5} |
| | )""", |
| | |
| | { |
| | "0xFF 0x12 0xAB", |
| | "0xFF 0x12 0xAB 0x00 0x00", |
| | }, |
| | |
| | { |
| | "", |
| | "0xFF", |
| | "0xFF 0x12", |
| | "0xFF 0x12 0xAB 0x00 0x00 0x00", |
| | } |
| | ); |
| | } |
| |
|
| | static void test_failure_missing_root() { |
| | fprintf(stderr, "⚫ Testing missing root node:\n"); |
| | |
| | const std::string grammar_str = R"""( |
| | rot ::= expr |
| | expr ::= term ("+" term)* |
| | term ::= number |
| | number ::= [0-9]+)"""; |
| |
|
| | llama_grammar_parser parsed_grammar; |
| | parsed_grammar.parse(grammar_str.c_str()); |
| |
|
| | |
| | assert(!parsed_grammar.rules.empty()); |
| |
|
| | |
| | assert(parsed_grammar.symbol_ids.find("root") == parsed_grammar.symbol_ids.end()); |
| | fprintf(stderr, " ✅︎ Passed\n"); |
| | } |
| |
|
| | static void test_failure_missing_reference() { |
| | fprintf(stderr, "⚫ Testing missing reference node:\n"); |
| |
|
| | |
| | const std::string grammar_str = |
| | R"""(root ::= expr |
| | expr ::= term ("+" term)* |
| | term ::= numero |
| | number ::= [0-9]+)"""; |
| |
|
| | fprintf(stderr, " Expected error: "); |
| |
|
| | llama_grammar_parser parsed_grammar; |
| | parsed_grammar.parse(grammar_str.c_str()); |
| |
|
| | |
| | assert(parsed_grammar.rules.empty()); |
| |
|
| | fprintf(stderr, " End of expected error.\n"); |
| | fprintf(stderr, " ✅︎ Passed\n"); |
| | } |
| |
|
| | static void test_failure_left_recursion() { |
| | fprintf(stderr, "⚫ Testing left recursion detection:\n"); |
| |
|
| | |
| | const std::string simple_str = R"""(root ::= "a" | root "a")"""; |
| | assert(test_build_grammar_fails(simple_str)); |
| |
|
| | |
| | const std::string medium_str = R"""( |
| | root ::= asdf |
| | asdf ::= "a" | asdf "a" |
| | )"""; |
| | assert(test_build_grammar_fails(medium_str)); |
| |
|
| | |
| | const std::string hard_str = R"""( |
| | root ::= asdf |
| | asdf ::= "a" | foo "b" |
| | foo ::= "c" | asdf "d" | "e")"""; |
| | assert(test_build_grammar_fails(hard_str)); |
| |
|
| | |
| | const std::string hardest_str = R"""( |
| | root ::= asdf |
| | asdf ::= "a" | foo "b" |
| | foo ::= "c" | empty asdf "d" | "e" |
| | empty ::= "blah" | )"""; |
| | assert(test_build_grammar_fails(hardest_str)); |
| |
|
| | fprintf(stderr, " ✅︎ Passed\n"); |
| | } |
| |
|
| | static void test_json_schema() { |
| | |
| | |
| | |
| |
|
| | test_schema( |
| | "empty schema (object)", |
| | |
| | R"""( |
| | {} |
| | )""", |
| | |
| | { |
| | R"""({})""", |
| | R"""({"foo": "bar"})""", |
| | }, |
| | |
| | { |
| | "", |
| | "[]", |
| | "null", |
| | R"""("")""", |
| | "true", |
| | } |
| | ); |
| |
|
| | test_schema( |
| | "exotic formats (list)", |
| | |
| | R"""({ |
| | "items": [ |
| | { "format": "date" }, |
| | { "format": "uuid" }, |
| | { "format": "time" }, |
| | { "format": "date-time" } |
| | ] |
| | })""", |
| | |
| | { |
| | |
| | |
| | R"""(["2012-04-23", "12345678-1234-1234-1234-1234567890ab", "18:25:43.511Z", "2012-04-23T18:25:43.511Z"])""", |
| | |
| | |
| | }, |
| | |
| | { |
| | R"""(["foo", "bar"])""", |
| | R"""(["12345678-1234-1234-1234-1234567890ab"])""", |
| | } |
| | ); |
| |
|
| | test_schema( |
| | "string", |
| | |
| | R"""({ |
| | "type": "string" |
| | })""", |
| | |
| | { |
| | R"""("foo")""", |
| | R"""("bar")""", |
| | R"""("")""", |
| | }, |
| | |
| | { |
| | R"""({})""", |
| | R"""("foo": "bar")""", |
| | } |
| | ); |
| |
|
| | test_schema( |
| | "string w/ min length 1", |
| | |
| | R"""({ |
| | "type": "string", |
| | "minLength": 1 |
| | })""", |
| | |
| | { |
| | R"""("foo")""", |
| | R"""("bar")""", |
| | }, |
| | |
| | { |
| | R"""("")""", |
| | R"""({})""", |
| | R"""("foo": "bar")""", |
| | } |
| | ); |
| |
|
| | test_schema( |
| | "string w/ min length 3", |
| | |
| | R"""({ |
| | "type": "string", |
| | "minLength": 3 |
| | })""", |
| | |
| | { |
| | R"""("foo")""", |
| | R"""("bar")""", |
| | R"""("foobar")""", |
| | }, |
| | |
| | { |
| | R"""("")""", |
| | R"""("f")""", |
| | R"""("fo")""", |
| | } |
| | ); |
| |
|
| | test_schema( |
| | "string w/ max length", |
| | |
| | R"""({ |
| | "type": "string", |
| | "maxLength": 3 |
| | })""", |
| | |
| | { |
| | R"""("foo")""", |
| | R"""("bar")""", |
| | R"""("")""", |
| | R"""("f")""", |
| | R"""("fo")""", |
| | }, |
| | |
| | { |
| | R"""("foobar")""", |
| | } |
| | ); |
| |
|
| | test_schema( |
| | "string w/ min & max length", |
| | |
| | R"""({ |
| | "type": "string", |
| | "minLength": 1, |
| | "maxLength": 4 |
| | })""", |
| | |
| | { |
| | R"""("foo")""", |
| | R"""("bar")""", |
| | R"""("f")""", |
| | R"""("barf")""", |
| | }, |
| | |
| | { |
| | R"""("")""", |
| | R"""("barfo")""", |
| | R"""("foobar")""", |
| | } |
| | ); |
| |
|
| | test_schema( |
| | "boolean", |
| | |
| | R"""({ |
| | "type": "boolean" |
| | })""", |
| | |
| | { |
| | "true", |
| | "false", |
| | }, |
| | |
| | { |
| | R"""("")""", |
| | R"""("true")""", |
| | R"""(True)""", |
| | R"""(FALSE)""", |
| | } |
| | ); |
| |
|
| | test_schema( |
| | "integer", |
| | |
| | R"""({ |
| | "type": "integer" |
| | })""", |
| | |
| | { |
| | R"""(0)""", |
| | R"""(12345)""", |
| | R"""(1234567890123456)""", |
| | }, |
| | |
| | { |
| | R"""()""", |
| | R"""(01)""", |
| | R"""(007)""", |
| | R"""(12345678901234567 )""", |
| | } |
| | ); |
| |
|
| | test_schema( |
| | "string const", |
| | |
| | R"""({ |
| | "const": "foo" |
| | })""", |
| | |
| | { |
| | R"""("foo")""", |
| | }, |
| | |
| | { |
| | R"""(foo)""", |
| | R"""("bar")""", |
| | } |
| | ); |
| |
|
| | test_schema( |
| | "non-string const", |
| | |
| | R"""({ |
| | "const": true |
| | })""", |
| | |
| | { |
| | R"""(true)""", |
| | }, |
| | |
| | { |
| | R"""()""", |
| | R"""(foo)""", |
| | R"""("true")""", |
| | } |
| | ); |
| |
|
| | test_schema( |
| | "non-string const", |
| | |
| | R"""({ |
| | "enum": ["red", "amber", "green", null, 42, ["foo"]] |
| | })""", |
| | |
| | { |
| | R"""("red")""", |
| | R"""(null)""", |
| | R"""(42)""", |
| | R"""(["foo"])""", |
| | }, |
| | |
| | { |
| | R"""()""", |
| | R"""(420)""", |
| | R"""(true)""", |
| | R"""(foo)""", |
| | } |
| | ); |
| |
|
| | test_schema( |
| | "simple pattern", |
| | |
| | R"""({ |
| | "pattern": "^[a-zA-Z0-9_-]*$" |
| | })""", |
| | |
| | { |
| | R"""("")""", |
| | R"""("He_llo-12")""", |
| | }, |
| | |
| | { |
| | R"""("!")""", |
| | R"""("Hello World")""", |
| | } |
| | ); |
| |
|
| | test_schema( |
| | "pattern with escapes", |
| | |
| | R"""({ |
| | "pattern": "^a\\^\\$\\.\\[\\]\\(\\)\\|\\{\\}\\*\\+\\?b$" |
| | })""", |
| | |
| | { |
| | R"""("a^$.[]()|{}*+?b")""", |
| | }, |
| | |
| | { |
| | R"""("ab")""", |
| | } |
| | ); |
| |
|
| | test_schema( |
| | "", |
| | |
| | R"""( |
| | { |
| | "type": ["array", "null"], |
| | "items": { "type": "string" } |
| | } |
| | )""", |
| | |
| | { |
| | "null", |
| | "[]", |
| | "[\"123\"]", |
| | "[\"foo\", \"bar\"]", |
| | }, |
| | |
| | { |
| | "", |
| | "[123]", |
| | "\"foo\"", |
| | "[\"foo\", 42]", |
| | } |
| | ); |
| |
|
| | test_schema( |
| | "min+max items", |
| | |
| | R"""({ |
| | "items": { |
| | "type": ["number", "integer"] |
| | }, |
| | "minItems": 3, |
| | "maxItems": 5 |
| | })""", |
| | |
| | { |
| | R"""([1, 2, 3])""", |
| | R"""([1, 2, 3, 4])""", |
| | R"""([1, 2, 3, 4, 5])""", |
| | }, |
| | |
| | { |
| | R"""([1, 2])""", |
| | R"""([1, 2, 3, 4, 5, 6])""", |
| | R"""(1)""", |
| | } |
| | ); |
| |
|
| | |
| | test_schema( |
| | "object properties", |
| | |
| | R"""({ |
| | "type": "object", |
| | "properties": { |
| | "number": { "type": "number" }, |
| | "street_name": { "type": "string" }, |
| | "street_type": { "enum": ["Street", "Avenue", "Boulevard"] } |
| | } |
| | })""", |
| | |
| | { |
| | R"""({ "number": 1600, "street_name": "Pennsylvania", "street_type":"Avenue"})""", |
| | |
| | R"""({ "street_name": "Pennsylvania" })""", |
| | R"""({ "number": 1600, "street_name": "Pennsylvania" })""", |
| | |
| | R"""({})""", |
| | R"""({ "number": 1600, "street_name": "Pennsylvania", "street_type": "Avenue" })""", |
| | }, |
| | |
| | { |
| | |
| | R"""({ "number": "1600", "street_name": "Pennsylvania", "street_type":"Avenue"})""", |
| | |
| | R"""({ "street_name": "Pennsylvania", "number": 1600 })""", |
| | |
| | R"""({ "number": "1600", "street_name": "Pennsylvania", "street_type":"Avenue"})""", |
| | |
| | R"""({ "number": 1600, "street_name": "Pennsylvania", "street_type":"Avenue", "direction":"NW"})""", |
| |
|
| | } |
| | ); |
| |
|
| | test_schema( |
| | "additional properties can't override other properties", |
| | R"""({ |
| | "properties": { |
| | "a": {"type": "integer"}, |
| | "b": {"type": "integer"} |
| | }, |
| | "additionalProperties": true |
| | })""", |
| | |
| | { |
| | R"""({"a": 42})""", |
| | R"""({"c": ""})""", |
| | R"""({"a": 42, "c": ""})""", |
| | R"""({"a_": ""})""", |
| | }, |
| | |
| | { |
| | R"""()""", |
| | R"""({"a": ""})""", |
| | R"""({"a": "", "b": ""})""", |
| | } |
| | ); |
| |
|
| | |
| | test_schema( |
| | "object properties, additionalProperties: true", |
| | |
| | R"""({ |
| | "type": "object", |
| | "properties": { |
| | "number": { "type": "number" }, |
| | "street_name": { "type": "string" }, |
| | "street_type": { "enum": ["Street", "Avenue", "Boulevard"] } |
| | }, |
| | "additionalProperties": true |
| | })""", |
| | |
| | { |
| | |
| | R"""({})""", |
| | R"""({"number":1600,"street_name":"Pennsylvania","street_type":"Avenue"})""", |
| | |
| | R"""({ "street_name": "Pennsylvania" })""", |
| | R"""({ "number": 1600, "street_name": "Pennsylvania" })""", |
| | |
| | R"""({ "number": 1600, "street_name": "Pennsylvania", "street_type":"Avenue", "direction":"NW"})""", |
| | R"""({ "number": 1600, "street_name": "Pennsylvania", "street_type": "Avenue" })""", |
| | }, |
| | |
| | { |
| | |
| | R"""({ "number": "1600", "street_name": "Pennsylvania", "street_type":"Avenue"})""", |
| | |
| | R"""({ "street_name": "Pennsylvania", "number": 1600, "street_type":"Avenue"})""", |
| | } |
| | ); |
| |
|
| | |
| | test_schema( |
| | "required + optional props each in original order", |
| | |
| | R"""({ |
| | "type": "object", |
| | "properties": { |
| | "number": { "type": "number" }, |
| | "street_name": { "type": "string" }, |
| | "street_type": { "enum": ["Street", "Avenue", "Boulevard"] } |
| | }, |
| | "additionalProperties": false |
| | })""", |
| | |
| | { |
| | R"""({ "street_name": "Pennsylvania" })""", |
| | R"""({ "number": 1600, "street_type":"Avenue"})""", |
| | R"""({ "number": 1600, "street_name": "Pennsylvania" })""", |
| | R"""({ "number": 1600, "street_name": "Pennsylvania", "street_type":"Avenue"})""", |
| | |
| | R"""({ "number": 1600, "street_name": "Pennsylvania", "street_type": "Avenue" })""", |
| | }, |
| | |
| | { |
| | |
| | R"""({ "street_type": "Avenue", "number": 1600 })""", |
| | |
| | R"""({ "number": 1600, "street_name": "Pennsylvania", "street_type": "Avenue", "direction": "NW" })""", |
| | } |
| | ); |
| |
|
| | test_schema( |
| | "required + optional props each in original order", |
| | |
| | R"""({ |
| | "properties": { |
| | "b": {"type": "string"}, |
| | "a": {"type": "string"}, |
| | "d": {"type": "string"}, |
| | "c": {"type": "string"} |
| | }, |
| | "required": ["a", "b"], |
| | "additionalProperties": false |
| | })""", |
| | |
| | { |
| | R"""({"b": "foo", "a": "bar"})""", |
| | R"""({"b":"foo","a":"bar","d":"qux"})""", |
| | R"""({"b":"foo", "a":"bar", "d":"qux", "c":"baz"})""", |
| | }, |
| | |
| | { |
| | R"""({"a": "foo", "b": "bar"})""", |
| | R"""({"b": "bar"})""", |
| | R"""({"a": "foo", "c": "baz"})""", |
| | R"""({"a":"foo", "b":"bar", "c":"baz", "d":"qux"})""", |
| | } |
| | ); |
| |
|
| | |
| | test_schema( |
| | "required props", |
| | |
| | R"""({ |
| | "$schema": "https://json-schema.org/draft/2020-12/schema", |
| | "$id": "https://example.com/product.schema.json", |
| | "title": "Product", |
| | "description": "A product from Acme's catalog", |
| | "type": "object", |
| | "properties": { |
| | "productId": { |
| | "description": "The unique identifier for a product", |
| | "type": "integer" |
| | }, |
| | "productName": { |
| | "description": "Name of the product", |
| | "type": "string" |
| | }, |
| | "price": { |
| | "description": "The price of the product", |
| | "type": "number", |
| | "exclusiveMinimum": 0 |
| | }, |
| | "tags": { |
| | "description": "Tags for the product", |
| | "type": "array", |
| | "items": { |
| | "type": "string" |
| | }, |
| | "minItems": 1, |
| | "uniqueItems": true |
| | }, |
| | "dimensions": { |
| | "type": "object", |
| | "properties": { |
| | "length": { |
| | "type": "number" |
| | }, |
| | "width": { |
| | "type": "number" |
| | }, |
| | "height": { |
| | "type": "number" |
| | } |
| | }, |
| | "required": [ "length", "width", "height" ] |
| | } |
| | }, |
| | "required": [ "productId", "productName", "price" ] |
| | })""", |
| | |
| | { |
| | R"""({"productId": 1, "productName": "A green door", "price": 12.50})""", |
| | R"""({"productId": 1, "productName": "A green door", "price": 12.50, "tags": ["home", "green"]})""", |
| | R"""({"productId": 1, "productName": "A green door", "price": 12.50, "tags": ["home", "green"], "dimensions": {"length": 785, "width": 250.5, "height": -0.359}})""", |
| | }, |
| | |
| | { |
| | R"""({})""", |
| | R"""({"productName": "A green door", "price": 12.50, "productId": 1})""", |
| | |
| | |
| | |
| | R"""({"productId": 1, "productName": "A green door"})""", |
| | R"""({"productName": "A green door", "price": 12.50})""", |
| | R"""({"productId": 1, "productName": "A green door", "price": 12.50, "tags": []})""", |
| | R"""({"productId": 1, "productName": "A green door", "price": 12.50, "dimensions": {"length": 785, "width": 250.5, "height": -0.359}, "tags": ["home", "green"]})""", |
| | |
| | |
| | } |
| | ); |
| | } |
| |
|
| | int main() { |
| | fprintf(stdout, "Running grammar integration tests...\n"); |
| | test_simple_grammar(); |
| | test_complex_grammar(); |
| | test_special_chars(); |
| | test_quantifiers(); |
| | test_failure_missing_root(); |
| | test_failure_missing_reference(); |
| | test_failure_left_recursion(); |
| | test_json_schema(); |
| | fprintf(stdout, "All tests passed.\n"); |
| | return 0; |
| | } |
| |
|