| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389 |
- #ifdef NDEBUG
- #undef NDEBUG
- #endif
- #include "json-schema-to-grammar.h"
- #include "../src/llama-grammar.h"
- #include <nlohmann/json.hpp>
- #include <cassert>
- #include <fstream>
- #include <sstream>
- #include <regex>
- static std::string trim(const std::string & source) {
- std::string s(source);
- s.erase(0,s.find_first_not_of(" \n\r\t"));
- s.erase(s.find_last_not_of(" \n\r\t")+1);
- return std::regex_replace(s, std::regex("(^|\n)[ \t]+"), "$1");
- }
- enum TestCaseStatus {
- SUCCESS,
- FAILURE
- };
- struct TestCase {
- TestCaseStatus expected_status;
- std::string name;
- std::string schema;
- std::string expected_grammar;
- void _print_failure_header() const {
- fprintf(stderr, "#\n# Test '%s' failed.\n#\n%s\n", name.c_str(), schema.c_str());
- }
- void verify(const std::string & actual_grammar) const {
- if (trim(actual_grammar) != trim(expected_grammar)) {
- _print_failure_header();
- fprintf(stderr, "# EXPECTED:\n%s\n# ACTUAL:\n%s\n", expected_grammar.c_str(), actual_grammar.c_str());
- assert(false);
- }
- }
- void verify_expectation_parseable() const {
- try {
- llama_grammar_parser state;
- state.parse(expected_grammar.c_str());
- if (state.symbol_ids.find("root") == state.symbol_ids.end()) {
- throw std::runtime_error("Grammar failed to parse:\n" + expected_grammar);
- }
- } catch (const std::runtime_error & ex) {
- _print_failure_header();
- fprintf(stderr, "# GRAMMAR ERROR: %s\n", ex.what());
- assert(false);
- }
- }
- void verify_status(TestCaseStatus status) const {
- if (status != expected_status) {
- _print_failure_header();
- fprintf(stderr, "# EXPECTED STATUS: %s\n", expected_status == SUCCESS ? "SUCCESS" : "FAILURE");
- fprintf(stderr, "# ACTUAL STATUS: %s\n", status == SUCCESS ? "SUCCESS" : "FAILURE");
- assert(false);
- }
- }
- };
- static void write(const std::string & file, const std::string & content) {
- std::ofstream f;
- f.open(file.c_str());
- f << content.c_str();
- f.close();
- }
- static std::string read(const std::string & file) {
- std::ostringstream actuals;
- actuals << std::ifstream(file.c_str()).rdbuf();
- return actuals.str();
- }
- static void test_all(const std::string & lang, std::function<void(const TestCase &)> runner) {
- fprintf(stderr, "#\n# Testing JSON schema conversion (%s)\n#\n", lang.c_str());
- auto test = [&](const TestCase & tc) {
- fprintf(stderr, "- %s%s\n", tc.name.c_str(), tc.expected_status == FAILURE ? " (failure expected)" : "");
- runner(tc);
- };
- test({
- SUCCESS,
- "min 0",
- R"""({
- "type": "integer",
- "minimum": 0
- })""",
- R"""(
- root ::= ([0] | [1-9] [0-9]{0,15}) space
- space ::= | " " | "\n"{1,2} [ \t]{0,20}
- )"""
- });
- test({
- SUCCESS,
- "min 1",
- R"""({
- "type": "integer",
- "minimum": 1
- })""",
- R"""(
- root ::= ([1-9] [0-9]{0,15}) space
- space ::= | " " | "\n"{1,2} [ \t]{0,20}
- )"""
- });
- test({
- SUCCESS,
- "min 3",
- R"""({
- "type": "integer",
- "minimum": 3
- })""",
- R"""(
- root ::= ([1-2] [0-9]{1,15} | [3-9] [0-9]{0,15}) space
- space ::= | " " | "\n"{1,2} [ \t]{0,20}
- )"""
- });
- test({
- SUCCESS,
- "min 9",
- R"""({
- "type": "integer",
- "minimum": 9
- })""",
- R"""(
- root ::= ([1-8] [0-9]{1,15} | [9] [0-9]{0,15}) space
- space ::= | " " | "\n"{1,2} [ \t]{0,20}
- )"""
- });
- test({
- SUCCESS,
- "min 10",
- R"""({
- "type": "integer",
- "minimum": 10
- })""",
- R"""(
- root ::= ([1] ([0-9]{1,15}) | [2-9] [0-9]{1,15}) space
- space ::= | " " | "\n"{1,2} [ \t]{0,20}
- )"""
- });
- test({
- SUCCESS,
- "min 25",
- R"""({
- "type": "integer",
- "minimum": 25
- })""",
- R"""(
- root ::= ([1] [0-9]{2,15} | [2] ([0-4] [0-9]{1,14} | [5-9] [0-9]{0,14}) | [3-9] [0-9]{1,15}) space
- space ::= | " " | "\n"{1,2} [ \t]{0,20}
- )"""
- });
- test({
- SUCCESS,
- "max 30",
- R"""({
- "type": "integer",
- "maximum": 30
- })""",
- R"""(
- root ::= ("-" [1-9] [0-9]{0,15} | [0-9] | ([1-2] [0-9] | [3] "0")) space
- space ::= | " " | "\n"{1,2} [ \t]{0,20}
- )"""
- });
- test({
- SUCCESS,
- "min -5",
- R"""({
- "type": "integer",
- "minimum": -5
- })""",
- R"""(
- root ::= ("-" ([0-5]) | [0] | [1-9] [0-9]{0,15}) space
- space ::= | " " | "\n"{1,2} [ \t]{0,20}
- )"""
- });
- test({
- SUCCESS,
- "min -123",
- R"""({
- "type": "integer",
- "minimum": -123
- })""",
- R"""(
- root ::= ("-" ([0-9] | ([1-8] [0-9] | [9] [0-9]) | "1" ([0-1] [0-9] | [2] [0-3])) | [0] | [1-9] [0-9]{0,15}) space
- space ::= | " " | "\n"{1,2} [ \t]{0,20}
- )"""
- });
- test({
- SUCCESS,
- "max -5",
- R"""({
- "type": "integer",
- "maximum": -5
- })""",
- R"""(
- root ::= ("-" ([0-4] [0-9]{1,15} | [5-9] [0-9]{0,15})) space
- space ::= | " " | "\n"{1,2} [ \t]{0,20}
- )"""
- });
- test({
- SUCCESS,
- "max 1",
- R"""({
- "type": "integer",
- "maximum": 1
- })""",
- R"""(
- root ::= ("-" [1-9] [0-9]{0,15} | [0-1]) space
- space ::= | " " | "\n"{1,2} [ \t]{0,20}
- )"""
- });
- test({
- SUCCESS,
- "max 100",
- R"""({
- "type": "integer",
- "maximum": 100
- })""",
- R"""(
- root ::= ("-" [1-9] [0-9]{0,15} | [0-9] | ([1-8] [0-9] | [9] [0-9]) | "100") space
- space ::= | " " | "\n"{1,2} [ \t]{0,20}
- )"""
- });
- test({
- SUCCESS,
- "min 0 max 23",
- R"""({
- "type": "integer",
- "minimum": 0,
- "maximum": 23
- })""",
- R"""(
- root ::= ([0-9] | ([1] [0-9] | [2] [0-3])) space
- space ::= | " " | "\n"{1,2} [ \t]{0,20}
- )"""
- });
- test({
- SUCCESS,
- "min 15 max 300",
- R"""({
- "type": "integer",
- "minimum": 15,
- "maximum": 300
- })""",
- R"""(
- root ::= (([1] ([5-9]) | [2-9] [0-9]) | ([1-2] [0-9]{2} | [3] "00")) space
- space ::= | " " | "\n"{1,2} [ \t]{0,20}
- )"""
- });
- test({
- SUCCESS,
- "min 5 max 30",
- R"""({
- "type": "integer",
- "minimum": 5,
- "maximum": 30
- })""",
- R"""(
- root ::= ([5-9] | ([1-2] [0-9] | [3] "0")) space
- space ::= | " " | "\n"{1,2} [ \t]{0,20}
- )"""
- });
- test({
- SUCCESS,
- "min -123 max 42",
- R"""({
- "type": "integer",
- "minimum": -123,
- "maximum": 42
- })""",
- R"""(
- root ::= ("-" ([0-9] | ([1-8] [0-9] | [9] [0-9]) | "1" ([0-1] [0-9] | [2] [0-3])) | [0-9] | ([1-3] [0-9] | [4] [0-2])) space
- space ::= | " " | "\n"{1,2} [ \t]{0,20}
- )"""
- });
- test({
- SUCCESS,
- "min -10 max 10",
- R"""({
- "type": "integer",
- "minimum": -10,
- "maximum": 10
- })""",
- R"""(
- root ::= ("-" ([0-9] | "10") | [0-9] | "10") space
- space ::= | " " | "\n"{1,2} [ \t]{0,20}
- )"""
- });
- test({
- FAILURE,
- "unknown type",
- R"""({
- "type": "kaboom"
- })""",
- ""
- });
- test({
- FAILURE,
- "invalid type",
- R"""({
- "type": 123
- })""",
- ""
- });
- test({
- SUCCESS,
- "empty schema (object)",
- "{}",
- R"""(
- array ::= "[" space ( value ("," space value)* )? "]" space
- boolean ::= ("true" | "false") space
- char ::= [^"\\\x7F\x00-\x1F] | [\\] (["\\bfnrt] | "u" [0-9a-fA-F]{4})
- decimal-part ::= [0-9]{1,16}
- integral-part ::= [0] | [1-9] [0-9]{0,15}
- null ::= "null" space
- number ::= ("-"? integral-part) ("." decimal-part)? ([eE] [-+]? integral-part)? space
- object ::= "{" space ( string ":" space value ("," space string ":" space value)* )? "}" space
- root ::= object
- space ::= | " " | "\n"{1,2} [ \t]{0,20}
- string ::= "\"" char* "\"" space
- value ::= object | array | string | number | boolean | null
- )"""
- });
- test({
- SUCCESS,
- "exotic formats",
- R"""({
- "items": [
- { "format": "date" },
- { "format": "uuid" },
- { "format": "time" },
- { "format": "date-time" }
- ]
- })""",
- R"""(
- date ::= [0-9]{4} "-" ( "0" [1-9] | "1" [0-2] ) "-" ( "0" [1-9] | [1-2] [0-9] | "3" [0-1] )
- date-string ::= "\"" date "\"" space
- date-time ::= date "T" time
- date-time-string ::= "\"" date-time "\"" space
- root ::= "[" space tuple-0 "," space uuid "," space tuple-2 "," space tuple-3 "]" space
- space ::= | " " | "\n"{1,2} [ \t]{0,20}
- time ::= ([01] [0-9] | "2" [0-3]) ":" [0-5] [0-9] ":" [0-5] [0-9] ( "." [0-9]{3} )? ( "Z" | ( "+" | "-" ) ( [01] [0-9] | "2" [0-3] ) ":" [0-5] [0-9] )
- time-string ::= "\"" time "\"" space
- tuple-0 ::= date-string
- tuple-2 ::= time-string
- tuple-3 ::= date-time-string
- uuid ::= "\"" [0-9a-fA-F]{8} "-" [0-9a-fA-F]{4} "-" [0-9a-fA-F]{4} "-" [0-9a-fA-F]{4} "-" [0-9a-fA-F]{12} "\"" space
- )"""
- });
- test({
- SUCCESS,
- "string",
- R"""({
- "type": "string"
- })""",
- R"""(
- char ::= [^"\\\x7F\x00-\x1F] | [\\] (["\\bfnrt] | "u" [0-9a-fA-F]{4})
- root ::= "\"" char* "\"" space
- space ::= | " " | "\n"{1,2} [ \t]{0,20}
- )"""
- });
- test({
- SUCCESS,
- "string w/ min length 1",
- R"""({
- "type": "string",
- "minLength": 1
- })""",
- R"""(
- char ::= [^"\\\x7F\x00-\x1F] | [\\] (["\\bfnrt] | "u" [0-9a-fA-F]{4})
- root ::= "\"" char+ "\"" space
- space ::= | " " | "\n"{1,2} [ \t]{0,20}
- )"""
- });
- test({
- SUCCESS,
- "string w/ min length 3",
- R"""({
- "type": "string",
- "minLength": 3
- })""",
- R"""(
- char ::= [^"\\\x7F\x00-\x1F] | [\\] (["\\bfnrt] | "u" [0-9a-fA-F]{4})
- root ::= "\"" char{3,} "\"" space
- space ::= | " " | "\n"{1,2} [ \t]{0,20}
- )"""
- });
- test({
- SUCCESS,
- "string w/ max length",
- R"""({
- "type": "string",
- "maxLength": 3
- })""",
- R"""(
- char ::= [^"\\\x7F\x00-\x1F] | [\\] (["\\bfnrt] | "u" [0-9a-fA-F]{4})
- root ::= "\"" char{0,3} "\"" space
- space ::= | " " | "\n"{1,2} [ \t]{0,20}
- )"""
- });
- test({
- SUCCESS,
- "string w/ min & max length",
- R"""({
- "type": "string",
- "minLength": 1,
- "maxLength": 4
- })""",
- R"""(
- char ::= [^"\\\x7F\x00-\x1F] | [\\] (["\\bfnrt] | "u" [0-9a-fA-F]{4})
- root ::= "\"" char{1,4} "\"" space
- space ::= | " " | "\n"{1,2} [ \t]{0,20}
- )"""
- });
- test({
- SUCCESS,
- "boolean",
- R"""({
- "type": "boolean"
- })""",
- R"""(
- root ::= ("true" | "false") space
- space ::= | " " | "\n"{1,2} [ \t]{0,20}
- )"""
- });
- test({
- SUCCESS,
- "integer",
- R"""({
- "type": "integer"
- })""",
- R"""(
- integral-part ::= [0] | [1-9] [0-9]{0,15}
- root ::= ("-"? integral-part) space
- space ::= | " " | "\n"{1,2} [ \t]{0,20}
- )"""
- });
- test({
- SUCCESS,
- "string const",
- R"""({
- "const": "foo"
- })""",
- R"""(
- root ::= "\"foo\"" space
- space ::= | " " | "\n"{1,2} [ \t]{0,20}
- )"""
- });
- test({
- SUCCESS,
- "non-string const",
- R"""({
- "const": 123
- })""",
- R"""(
- root ::= "123" space
- space ::= | " " | "\n"{1,2} [ \t]{0,20}
- )"""
- });
- test({
- SUCCESS,
- "non-string enum",
- R"""({
- "enum": ["red", "amber", "green", null, 42, ["foo"]]
- })""",
- R"""(
- root ::= ("\"red\"" | "\"amber\"" | "\"green\"" | "null" | "42" | "[\"foo\"]") space
- space ::= | " " | "\n"{1,2} [ \t]{0,20}
- )"""
- });
- test({
- SUCCESS,
- "string array",
- R"""({
- "type": "array",
- "prefixItems": { "type": "string" }
- })""",
- R"""(
- char ::= [^"\\\x7F\x00-\x1F] | [\\] (["\\bfnrt] | "u" [0-9a-fA-F]{4})
- root ::= "[" space (string ("," space string)*)? "]" space
- space ::= | " " | "\n"{1,2} [ \t]{0,20}
- string ::= "\"" char* "\"" space
- )"""
- });
- test({
- SUCCESS,
- "nullable string array",
- R"""({
- "type": ["array", "null"],
- "prefixItems": { "type": "string" }
- })""",
- R"""(
- alternative-0 ::= "[" space (string ("," space string)*)? "]" space
- char ::= [^"\\\x7F\x00-\x1F] | [\\] (["\\bfnrt] | "u" [0-9a-fA-F]{4})
- null ::= "null" space
- root ::= alternative-0 | null
- space ::= | " " | "\n"{1,2} [ \t]{0,20}
- string ::= "\"" char* "\"" space
- )"""
- });
- test({
- SUCCESS,
- "tuple1",
- R"""({
- "prefixItems": [{ "type": "string" }]
- })""",
- R"""(
- char ::= [^"\\\x7F\x00-\x1F] | [\\] (["\\bfnrt] | "u" [0-9a-fA-F]{4})
- root ::= "[" space string "]" space
- space ::= | " " | "\n"{1,2} [ \t]{0,20}
- string ::= "\"" char* "\"" space
- )"""
- });
- test({
- SUCCESS,
- "tuple2",
- R"""({
- "prefixItems": [{ "type": "string" }, { "type": "number" }]
- })""",
- R"""(
- char ::= [^"\\\x7F\x00-\x1F] | [\\] (["\\bfnrt] | "u" [0-9a-fA-F]{4})
- decimal-part ::= [0-9]{1,16}
- integral-part ::= [0] | [1-9] [0-9]{0,15}
- number ::= ("-"? integral-part) ("." decimal-part)? ([eE] [-+]? integral-part)? space
- root ::= "[" space string "," space number "]" space
- space ::= | " " | "\n"{1,2} [ \t]{0,20}
- string ::= "\"" char* "\"" space
- )"""
- });
- test({
- SUCCESS,
- "number",
- R"""({
- "type": "number"
- })""",
- R"""(
- decimal-part ::= [0-9]{1,16}
- integral-part ::= [0] | [1-9] [0-9]{0,15}
- root ::= ("-"? integral-part) ("." decimal-part)? ([eE] [-+]? integral-part)? space
- space ::= | " " | "\n"{1,2} [ \t]{0,20}
- )"""
- });
- test({
- SUCCESS,
- "minItems",
- R"""({
- "items": {
- "type": "boolean"
- },
- "minItems": 2
- })""",
- R"""(
- boolean ::= ("true" | "false") space
- root ::= "[" space boolean ("," space boolean)+ "]" space
- space ::= | " " | "\n"{1,2} [ \t]{0,20}
- )"""
- });
- test({
- SUCCESS,
- "maxItems 0",
- R"""({
- "items": {
- "type": "boolean"
- },
- "maxItems": 0
- })""",
- R"""(
- boolean ::= ("true" | "false") space
- root ::= "[" space "]" space
- space ::= | " " | "\n"{1,2} [ \t]{0,20}
- )"""
- });
- test({
- SUCCESS,
- "maxItems 1",
- R"""({
- "items": {
- "type": "boolean"
- },
- "maxItems": 1
- })""",
- R"""(
- boolean ::= ("true" | "false") space
- root ::= "[" space boolean? "]" space
- space ::= | " " | "\n"{1,2} [ \t]{0,20}
- )"""
- });
- test({
- SUCCESS,
- "maxItems 2",
- R"""({
- "items": {
- "type": "boolean"
- },
- "maxItems": 2
- })""",
- R"""(
- boolean ::= ("true" | "false") space
- root ::= "[" space (boolean ("," space boolean)?)? "]" space
- space ::= | " " | "\n"{1,2} [ \t]{0,20}
- )"""
- });
- test({
- SUCCESS,
- "min + maxItems",
- R"""({
- "items": {
- "type": ["number", "integer"]
- },
- "minItems": 3,
- "maxItems": 5
- })""",
- R"""(
- decimal-part ::= [0-9]{1,16}
- integer ::= ("-"? integral-part) space
- integral-part ::= [0] | [1-9] [0-9]{0,15}
- item ::= number | integer
- number ::= ("-"? integral-part) ("." decimal-part)? ([eE] [-+]? integral-part)? space
- root ::= "[" space item ("," space item){2,4} "]" space
- space ::= | " " | "\n"{1,2} [ \t]{0,20}
- )"""
- });
- test({
- SUCCESS,
- "min + max items with min + max values across zero",
- R"""({
- "items": {
- "type": "integer",
- "minimum": -12,
- "maximum": 207
- },
- "minItems": 3,
- "maxItems": 5
- })""",
- R"""(
- item ::= ("-" ([0-9] | "1" [0-2]) | [0-9] | ([1-8] [0-9] | [9] [0-9]) | ([1] [0-9]{2} | [2] "0" [0-7])) space
- root ::= "[" space item ("," space item){2,4} "]" space
- space ::= | " " | "\n"{1,2} [ \t]{0,20}
- )"""
- });
- test({
- SUCCESS,
- "min + max items with min + max values",
- R"""({
- "items": {
- "type": "integer",
- "minimum": 12,
- "maximum": 207
- },
- "minItems": 3,
- "maxItems": 5
- })""",
- R"""(
- item ::= (([1] ([2-9]) | [2-9] [0-9]) | ([1] [0-9]{2} | [2] "0" [0-7])) space
- root ::= "[" space item ("," space item){2,4} "]" space
- space ::= | " " | "\n"{1,2} [ \t]{0,20}
- )"""
- });
- test({
- SUCCESS,
- "simple regexp",
- R"""({
- "type": "string",
- "pattern": "^abc?d*efg+(hij)?kl$"
- })""",
- R"""(
- root ::= "\"" ("ab" "c"? "d"* "ef" "g"+ ("hij")? "kl") "\"" space
- space ::= | " " | "\n"{1,2} [ \t]{0,20}
- )"""
- });
- test({
- SUCCESS,
- "regexp escapes",
- R"""({
- "type": "string",
- "pattern": "^\\[\\]\\{\\}\\(\\)\\|\\+\\*\\?$"
- })""",
- R"""(
- root ::= "\"" ("[]{}()|+*?") "\"" space
- space ::= | " " | "\n"{1,2} [ \t]{0,20}
- )"""
- });
- test({
- SUCCESS,
- "regexp quote",
- R"""({
- "type": "string",
- "pattern": "^\"$"
- })""",
- R"""(
- root ::= "\"" ("\"") "\"" space
- space ::= | " " | "\n"{1,2} [ \t]{0,20}
- )"""
- });
- test({
- SUCCESS,
- "regexp with top-level alternation",
- R"""({
- "type": "string",
- "pattern": "^A|B|C|D$"
- })""",
- R"""(
- root ::= "\"" ("A" | "B" | "C" | "D") "\"" space
- space ::= | " " | "\n"{1,2} [ \t]{0,20}
- )"""
- });
- test({
- SUCCESS,
- "regexp",
- R"""({
- "type": "string",
- "pattern": "^(\\([0-9]{1,3}\\))?[0-9]{3}-[0-9]{4} a{3,5}nd...$"
- })""",
- R"""(
- dot ::= [^\x0A\x0D]
- root ::= "\"" (("(" root-1{1,3} ")")? root-1{3,3} "-" root-1{4,4} " " "a"{3,5} "nd" dot dot dot) "\"" space
- root-1 ::= [0-9]
- space ::= | " " | "\n"{1,2} [ \t]{0,20}
- )"""
- });
- test({
- SUCCESS,
- "required props in original order",
- R"""({
- "type": "object",
- "properties": {
- "b": {"type": "string"},
- "c": {"type": "string"},
- "a": {"type": "string"}
- },
- "required": [
- "a",
- "b",
- "c"
- ],
- "additionalProperties": false,
- "definitions": {}
- })""",
- R"""(
- a-kv ::= "\"a\"" space ":" space string
- b-kv ::= "\"b\"" space ":" space string
- c-kv ::= "\"c\"" space ":" space string
- char ::= [^"\\\x7F\x00-\x1F] | [\\] (["\\bfnrt] | "u" [0-9a-fA-F]{4})
- root ::= "{" space b-kv "," space c-kv "," space a-kv "}" space
- space ::= | " " | "\n"{1,2} [ \t]{0,20}
- string ::= "\"" char* "\"" space
- )"""
- });
- test({
- SUCCESS,
- "1 optional prop",
- R"""({
- "properties": {
- "a": {
- "type": "string"
- }
- },
- "additionalProperties": false
- })""",
- R"""(
- a-kv ::= "\"a\"" space ":" space string
- char ::= [^"\\\x7F\x00-\x1F] | [\\] (["\\bfnrt] | "u" [0-9a-fA-F]{4})
- root ::= "{" space (a-kv )? "}" space
- space ::= | " " | "\n"{1,2} [ \t]{0,20}
- string ::= "\"" char* "\"" space
- )"""
- });
- test({
- SUCCESS,
- "N optional props",
- R"""({
- "properties": {
- "a": {"type": "string"},
- "b": {"type": "string"},
- "c": {"type": "string"}
- },
- "additionalProperties": false
- })""",
- R"""(
- a-kv ::= "\"a\"" space ":" space string
- a-rest ::= ( "," space b-kv )? b-rest
- b-kv ::= "\"b\"" space ":" space string
- b-rest ::= ( "," space c-kv )?
- c-kv ::= "\"c\"" space ":" space string
- char ::= [^"\\\x7F\x00-\x1F] | [\\] (["\\bfnrt] | "u" [0-9a-fA-F]{4})
- root ::= "{" space (a-kv a-rest | b-kv b-rest | c-kv )? "}" space
- space ::= | " " | "\n"{1,2} [ \t]{0,20}
- string ::= "\"" char* "\"" space
- )"""
- });
- test({
- SUCCESS,
- "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"""(
- a-kv ::= "\"a\"" space ":" space string
- b-kv ::= "\"b\"" space ":" space string
- c-kv ::= "\"c\"" space ":" space string
- char ::= [^"\\\x7F\x00-\x1F] | [\\] (["\\bfnrt] | "u" [0-9a-fA-F]{4})
- d-kv ::= "\"d\"" space ":" space string
- d-rest ::= ( "," space c-kv )?
- root ::= "{" space b-kv "," space a-kv ( "," space ( d-kv d-rest | c-kv ) )? "}" space
- space ::= | " " | "\n"{1,2} [ \t]{0,20}
- string ::= "\"" char* "\"" space
- )"""
- });
- test({
- SUCCESS,
- "additional props",
- R"""({
- "type": "object",
- "additionalProperties": {"type": "array", "items": {"type": "number"}}
- })""",
- R"""(
- additional-kv ::= string ":" space additional-value
- additional-value ::= "[" space (number ("," space number)*)? "]" space
- char ::= [^"\\\x7F\x00-\x1F] | [\\] (["\\bfnrt] | "u" [0-9a-fA-F]{4})
- decimal-part ::= [0-9]{1,16}
- integral-part ::= [0] | [1-9] [0-9]{0,15}
- number ::= ("-"? integral-part) ("." decimal-part)? ([eE] [-+]? integral-part)? space
- root ::= "{" space (additional-kv ( "," space additional-kv )* )? "}" space
- space ::= | " " | "\n"{1,2} [ \t]{0,20}
- string ::= "\"" char* "\"" space
- )"""
- });
- test({
- SUCCESS,
- "additional props (true)",
- R"""({
- "type": "object",
- "additionalProperties": true
- })""",
- R"""(
- array ::= "[" space ( value ("," space value)* )? "]" space
- boolean ::= ("true" | "false") space
- char ::= [^"\\\x7F\x00-\x1F] | [\\] (["\\bfnrt] | "u" [0-9a-fA-F]{4})
- decimal-part ::= [0-9]{1,16}
- integral-part ::= [0] | [1-9] [0-9]{0,15}
- null ::= "null" space
- number ::= ("-"? integral-part) ("." decimal-part)? ([eE] [-+]? integral-part)? space
- object ::= "{" space ( string ":" space value ("," space string ":" space value)* )? "}" space
- root ::= object
- space ::= | " " | "\n"{1,2} [ \t]{0,20}
- string ::= "\"" char* "\"" space
- value ::= object | array | string | number | boolean | null
- )"""
- });
- test({
- SUCCESS,
- "additional props (implicit)",
- R"""({
- "type": "object"
- })""",
- R"""(
- array ::= "[" space ( value ("," space value)* )? "]" space
- boolean ::= ("true" | "false") space
- char ::= [^"\\\x7F\x00-\x1F] | [\\] (["\\bfnrt] | "u" [0-9a-fA-F]{4})
- decimal-part ::= [0-9]{1,16}
- integral-part ::= [0] | [1-9] [0-9]{0,15}
- null ::= "null" space
- number ::= ("-"? integral-part) ("." decimal-part)? ([eE] [-+]? integral-part)? space
- object ::= "{" space ( string ":" space value ("," space string ":" space value)* )? "}" space
- root ::= object
- space ::= | " " | "\n"{1,2} [ \t]{0,20}
- string ::= "\"" char* "\"" space
- value ::= object | array | string | number | boolean | null
- )"""
- });
- test({
- SUCCESS,
- "empty w/o additional props",
- R"""({
- "type": "object",
- "additionalProperties": false
- })""",
- R"""(
- root ::= "{" space "}" space
- space ::= | " " | "\n"{1,2} [ \t]{0,20}
- )"""
- });
- test({
- SUCCESS,
- "required + additional props",
- R"""({
- "type": "object",
- "properties": {
- "a": {"type": "number"}
- },
- "required": ["a"],
- "additionalProperties": {"type": "string"}
- })""",
- R"""(
- a-kv ::= "\"a\"" space ":" space number
- additional-k ::= ["] ( [a] char+ | [^"a] char* )? ["] space
- additional-kv ::= additional-k ":" space string
- char ::= [^"\\\x7F\x00-\x1F] | [\\] (["\\bfnrt] | "u" [0-9a-fA-F]{4})
- decimal-part ::= [0-9]{1,16}
- integral-part ::= [0] | [1-9] [0-9]{0,15}
- number ::= ("-"? integral-part) ("." decimal-part)? ([eE] [-+]? integral-part)? space
- root ::= "{" space a-kv ( "," space ( additional-kv ( "," space additional-kv )* ) )? "}" space
- space ::= | " " | "\n"{1,2} [ \t]{0,20}
- string ::= "\"" char* "\"" space
- )"""
- });
- test({
- SUCCESS,
- "optional + additional props",
- R"""({
- "type": "object",
- "properties": {
- "a": {"type": "number"}
- },
- "additionalProperties": {"type": "number"}
- })""",
- R"""(
- a-kv ::= "\"a\"" space ":" space number
- a-rest ::= ( "," space additional-kv )*
- additional-k ::= ["] ( [a] char+ | [^"a] char* )? ["] space
- additional-kv ::= additional-k ":" space number
- char ::= [^"\\\x7F\x00-\x1F] | [\\] (["\\bfnrt] | "u" [0-9a-fA-F]{4})
- decimal-part ::= [0-9]{1,16}
- integral-part ::= [0] | [1-9] [0-9]{0,15}
- number ::= ("-"? integral-part) ("." decimal-part)? ([eE] [-+]? integral-part)? space
- root ::= "{" space (a-kv a-rest | additional-kv ( "," space additional-kv )* )? "}" space
- space ::= | " " | "\n"{1,2} [ \t]{0,20}
- )"""
- });
- test({
- SUCCESS,
- "required + optional + additional props",
- R"""({
- "type": "object",
- "properties": {
- "and": {"type": "number"},
- "also": {"type": "number"}
- },
- "required": ["and"],
- "additionalProperties": {"type": "number"}
- })""",
- R"""(
- additional-k ::= ["] ( [a] ([l] ([s] ([o] char+ | [^"o] char*) | [^"s] char*) | [n] ([d] char+ | [^"d] char*) | [^"ln] char*) | [^"a] char* )? ["] space
- additional-kv ::= additional-k ":" space number
- also-kv ::= "\"also\"" space ":" space number
- also-rest ::= ( "," space additional-kv )*
- and-kv ::= "\"and\"" space ":" space number
- char ::= [^"\\\x7F\x00-\x1F] | [\\] (["\\bfnrt] | "u" [0-9a-fA-F]{4})
- decimal-part ::= [0-9]{1,16}
- integral-part ::= [0] | [1-9] [0-9]{0,15}
- number ::= ("-"? integral-part) ("." decimal-part)? ([eE] [-+]? integral-part)? space
- root ::= "{" space and-kv ( "," space ( also-kv also-rest | additional-kv ( "," space additional-kv )* ) )? "}" space
- space ::= | " " | "\n"{1,2} [ \t]{0,20}
- )"""
- });
- test({
- SUCCESS,
- "optional props with empty name",
- R"""({
- "properties": {
- "": {"type": "integer"},
- "a": {"type": "integer"}
- },
- "additionalProperties": {"type": "integer"}
- })""",
- R"""(
- -kv ::= "\"\"" space ":" space root
- -rest ::= ( "," space a-kv )? a-rest
- a-kv ::= "\"a\"" space ":" space integer
- a-rest ::= ( "," space additional-kv )*
- additional-k ::= ["] ( [a] char+ | [^"a] char* ) ["] space
- additional-kv ::= additional-k ":" space integer
- char ::= [^"\\\x7F\x00-\x1F] | [\\] (["\\bfnrt] | "u" [0-9a-fA-F]{4})
- integer ::= ("-"? integral-part) space
- integral-part ::= [0] | [1-9] [0-9]{0,15}
- root ::= ("-"? integral-part) space
- root0 ::= "{" space (-kv -rest | a-kv a-rest | additional-kv ( "," space additional-kv )* )? "}" space
- space ::= | " " | "\n"{1,2} [ \t]{0,20}
- )"""
- });
- test({
- SUCCESS,
- "optional props with nested names",
- R"""({
- "properties": {
- "a": {"type": "integer"},
- "aa": {"type": "integer"}
- },
- "additionalProperties": {"type": "integer"}
- })""",
- R"""(
- a-kv ::= "\"a\"" space ":" space integer
- a-rest ::= ( "," space aa-kv )? aa-rest
- aa-kv ::= "\"aa\"" space ":" space integer
- aa-rest ::= ( "," space additional-kv )*
- additional-k ::= ["] ( [a] ([a] char+ | [^"a] char*) | [^"a] char* )? ["] space
- additional-kv ::= additional-k ":" space integer
- char ::= [^"\\\x7F\x00-\x1F] | [\\] (["\\bfnrt] | "u" [0-9a-fA-F]{4})
- integer ::= ("-"? integral-part) space
- integral-part ::= [0] | [1-9] [0-9]{0,15}
- root ::= "{" space (a-kv a-rest | aa-kv aa-rest | additional-kv ( "," space additional-kv )* )? "}" space
- space ::= | " " | "\n"{1,2} [ \t]{0,20}
- )"""
- });
- test({
- SUCCESS,
- "optional props with common prefix",
- R"""({
- "properties": {
- "ab": {"type": "integer"},
- "ac": {"type": "integer"}
- },
- "additionalProperties": {"type": "integer"}
- })""",
- R"""(
- ab-kv ::= "\"ab\"" space ":" space integer
- ab-rest ::= ( "," space ac-kv )? ac-rest
- ac-kv ::= "\"ac\"" space ":" space integer
- ac-rest ::= ( "," space additional-kv )*
- additional-k ::= ["] ( [a] ([b] char+ | [c] char+ | [^"bc] char*) | [^"a] char* )? ["] space
- additional-kv ::= additional-k ":" space integer
- char ::= [^"\\\x7F\x00-\x1F] | [\\] (["\\bfnrt] | "u" [0-9a-fA-F]{4})
- integer ::= ("-"? integral-part) space
- integral-part ::= [0] | [1-9] [0-9]{0,15}
- root ::= "{" space (ab-kv ab-rest | ac-kv ac-rest | additional-kv ( "," space additional-kv )* )? "}" space
- space ::= | " " | "\n"{1,2} [ \t]{0,20}
- )"""
- });
- test({
- SUCCESS,
- "top-level $ref",
- R"""({
- "$ref": "#/definitions/foo",
- "definitions": {
- "foo": {
- "type": "object",
- "properties": {
- "a": {
- "type": "string"
- }
- },
- "required": [
- "a"
- ],
- "additionalProperties": false
- }
- }
- })""",
- R"""(
- char ::= [^"\\\x7F\x00-\x1F] | [\\] (["\\bfnrt] | "u" [0-9a-fA-F]{4})
- ref-definitions-foo ::= "{" space ref-definitions-foo-a-kv "}" space
- ref-definitions-foo-a-kv ::= "\"a\"" space ":" space string
- root ::= ref-definitions-foo
- space ::= | " " | "\n"{1,2} [ \t]{0,20}
- string ::= "\"" char* "\"" space
- )"""
- });
- test({
- SUCCESS,
- "anyOf",
- R"""({
- "anyOf": [
- {"$ref": "#/definitions/foo"},
- {"$ref": "#/definitions/bar"}
- ],
- "definitions": {
- "foo": {
- "properties": {"a": {"type": "number"}}
- },
- "bar": {
- "properties": {"b": {"type": "number"}}
- }
- },
- "type": "object"
- })""",
- R"""(
- alternative-0 ::= ref-definitions-foo
- alternative-1 ::= ref-definitions-bar
- decimal-part ::= [0-9]{1,16}
- integral-part ::= [0] | [1-9] [0-9]{0,15}
- number ::= ("-"? integral-part) ("." decimal-part)? ([eE] [-+]? integral-part)? space
- ref-definitions-bar ::= "{" space (ref-definitions-bar-b-kv )? "}" space
- ref-definitions-bar-b-kv ::= "\"b\"" space ":" space number
- ref-definitions-foo ::= "{" space (ref-definitions-foo-a-kv )? "}" space
- ref-definitions-foo-a-kv ::= "\"a\"" space ":" space number
- root ::= alternative-0 | alternative-1
- space ::= | " " | "\n"{1,2} [ \t]{0,20}
- )"""
- });
- test({
- SUCCESS,
- "anyOf $ref",
- R"""({
- "properties": {
- "a": {
- "anyOf": [
- {"type": "string"},
- {"type": "number"}
- ]
- },
- "b": {
- "anyOf": [
- {"$ref": "#/properties/a/anyOf/0"},
- {"type": "boolean"}
- ]
- }
- },
- "type": "object"
- })""",
- R"""(
- a ::= string | number
- a-kv ::= "\"a\"" space ":" space a
- a-rest ::= ( "," space b-kv )?
- b ::= b-0 | boolean
- b-0 ::= string
- b-kv ::= "\"b\"" space ":" space b
- boolean ::= ("true" | "false") space
- char ::= [^"\\\x7F\x00-\x1F] | [\\] (["\\bfnrt] | "u" [0-9a-fA-F]{4})
- decimal-part ::= [0-9]{1,16}
- integral-part ::= [0] | [1-9] [0-9]{0,15}
- number ::= ("-"? integral-part) ("." decimal-part)? ([eE] [-+]? integral-part)? space
- root ::= "{" space (a-kv a-rest | b-kv )? "}" space
- space ::= | " " | "\n"{1,2} [ \t]{0,20}
- string ::= "\"" char* "\"" space
- )"""
- });
- test({
- SUCCESS,
- "mix of allOf, anyOf and $ref (similar to https://json.schemastore.org/tsconfig.json)",
- R"""({
- "allOf": [
- {"$ref": "#/definitions/foo"},
- {"$ref": "#/definitions/bar"},
- {
- "anyOf": [
- {"$ref": "#/definitions/baz"},
- {"$ref": "#/definitions/bam"}
- ]
- }
- ],
- "definitions": {
- "foo": {
- "properties": {"a": {"type": "number"}}
- },
- "bar": {
- "properties": {"b": {"type": "number"}}
- },
- "bam": {
- "properties": {"c": {"type": "number"}}
- },
- "baz": {
- "properties": {"d": {"type": "number"}}
- }
- },
- "type": "object"
- })""",
- R"""(
- a-kv ::= "\"a\"" space ":" space number
- b-kv ::= "\"b\"" space ":" space number
- c-kv ::= "\"c\"" space ":" space number
- d-kv ::= "\"d\"" space ":" space number
- d-rest ::= ( "," space c-kv )?
- decimal-part ::= [0-9]{1,16}
- integral-part ::= [0] | [1-9] [0-9]{0,15}
- number ::= ("-"? integral-part) ("." decimal-part)? ([eE] [-+]? integral-part)? space
- root ::= "{" space a-kv "," space b-kv ( "," space ( d-kv d-rest | c-kv ) )? "}" space
- space ::= | " " | "\n"{1,2} [ \t]{0,20}
- )"""
- });
- test({
- SUCCESS,
- "allOf with enum schema",
- R"""({
- "allOf": [
- {"$ref": "#/definitions/foo"}
- ],
- "definitions": {
- "foo": {
- "type": "string",
- "enum": ["a", "b"]
- }
- }
- })""",
- R"""(
- root ::= ("\"a\"" | "\"b\"") space
- space ::= | " " | "\n"{1,2} [ \t]{0,20}
- )"""
- });
- test({
- SUCCESS,
- "allOf with multiple enum schemas",
- R"""({
- "allOf": [
- {"$ref": "#/definitions/foo"},
- {"$ref": "#/definitions/bar"}
- ],
- "definitions": {
- "foo": {
- "type": "string",
- "enum": ["a", "b", "c"]
- },
- "bar": {
- "type": "string",
- "enum": ["b", "c", "d"]
- }
- }
- })""",
- R"""(
- root ::= ("\"b\"" | "\"c\"") space
- space ::= | " " | "\n"{1,2} [ \t]{0,20}
- )"""
- });
- test({
- SUCCESS,
- "conflicting names",
- R"""({
- "type": "object",
- "properties": {
- "number": {
- "type": "object",
- "properties": {
- "number": {
- "type": "object",
- "properties": {
- "root": {
- "type": "number"
- }
- },
- "required": [
- "root"
- ],
- "additionalProperties": false
- }
- },
- "required": [
- "number"
- ],
- "additionalProperties": false
- }
- },
- "required": [
- "number"
- ],
- "additionalProperties": false,
- "definitions": {}
- })""",
- R"""(
- decimal-part ::= [0-9]{1,16}
- integral-part ::= [0] | [1-9] [0-9]{0,15}
- number ::= ("-"? integral-part) ("." decimal-part)? ([eE] [-+]? integral-part)? space
- number- ::= "{" space number-number-kv "}" space
- number-kv ::= "\"number\"" space ":" space number-
- number-number ::= "{" space number-number-root-kv "}" space
- number-number-kv ::= "\"number\"" space ":" space number-number
- number-number-root-kv ::= "\"root\"" space ":" space number
- root ::= "{" space number-kv "}" space
- space ::= | " " | "\n"{1,2} [ \t]{0,20}
- )"""
- });
- }
- int main() {
- fprintf(stderr, "LLAMA_NODE_AVAILABLE = %s\n", getenv("LLAMA_NODE_AVAILABLE") ? "true" : "false");
- fprintf(stderr, "LLAMA_PYTHON_AVAILABLE = %s\n", getenv("LLAMA_PYTHON_AVAILABLE") ? "true" : "false");
- test_all("C++", [](const TestCase & tc) {
- try {
- tc.verify(json_schema_to_grammar(nlohmann::ordered_json::parse(tc.schema), true));
- tc.verify_status(SUCCESS);
- } catch (const std::runtime_error & ex) {
- fprintf(stderr, "Error: %s\n", ex.what());
- tc.verify_status(FAILURE);
- }
- });
- if (getenv("LLAMA_SKIP_TESTS_SLOW_ON_EMULATOR")) {
- fprintf(stderr, "\033[33mWARNING: Skipping slow tests on emulator.\n\033[0m");
- } else {
- if (getenv("LLAMA_PYTHON_AVAILABLE") || (std::system("python -c \"import sys; exit(1) if sys.version_info < (3, 8) else print('Python version is sufficient')\"") == 0)) {
- test_all("Python", [](const TestCase & tc) {
- write("test-json-schema-input.tmp", tc.schema);
- tc.verify_status(std::system(
- "python ./examples/json_schema_to_grammar.py test-json-schema-input.tmp > test-grammar-output.tmp") == 0 ? SUCCESS : FAILURE);
- tc.verify(read("test-grammar-output.tmp"));
- });
- } else {
- fprintf(stderr, "\033[33mWARNING: Python not found (min version required is 3.8), skipping Python JSON schema -> grammar tests.\n\033[0m");
- }
- if (getenv("LLAMA_NODE_AVAILABLE") || (std::system("node --version") == 0)) {
- test_all("JavaScript", [](const TestCase & tc) {
- write("test-json-schema-input.tmp", tc.schema);
- tc.verify_status(std::system(
- "node ./tests/run-json-schema-to-grammar.mjs test-json-schema-input.tmp > test-grammar-output.tmp") == 0 ? SUCCESS : FAILURE);
- tc.verify(read("test-grammar-output.tmp"));
- });
- } else {
- fprintf(stderr, "\033[33mWARNING: Node not found, skipping JavaScript JSON schema -> grammar tests.\n\033[0m");
- }
- }
- test_all("Check Expectations Validity", [](const TestCase & tc) {
- if (tc.expected_status == SUCCESS) {
- tc.verify_expectation_parseable();
- }
- });
- }
|