test-json-schema-to-grammar.cpp 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899
  1. #ifdef NDEBUG
  2. #undef NDEBUG
  3. #endif
  4. #include <fstream>
  5. #include <sstream>
  6. #include <regex>
  7. #include "json-schema-to-grammar.h"
  8. #include "grammar-parser.h"
  9. static std::string trim(const std::string & source) {
  10. std::string s(source);
  11. s.erase(0,s.find_first_not_of(" \n\r\t"));
  12. s.erase(s.find_last_not_of(" \n\r\t")+1);
  13. return std::regex_replace(s, std::regex("(^|\n)[ \t]+"), "$1");
  14. }
  15. enum TestCaseStatus {
  16. SUCCESS,
  17. FAILURE
  18. };
  19. struct TestCase {
  20. TestCaseStatus expected_status;
  21. std::string name;
  22. std::string schema;
  23. std::string expected_grammar;
  24. void _print_failure_header() const {
  25. fprintf(stderr, "#\n# Test '%s' failed.\n#\n%s\n", name.c_str(), schema.c_str());
  26. }
  27. void verify(const std::string & actual_grammar) const {
  28. if (trim(actual_grammar) != trim(expected_grammar)) {
  29. _print_failure_header();
  30. fprintf(stderr, "# EXPECTED:\n%s\n# ACTUAL:\n%s\n", expected_grammar.c_str(), actual_grammar.c_str());
  31. assert(false);
  32. }
  33. }
  34. void verify_expectation_parseable() const {
  35. try {
  36. auto state = grammar_parser::parse(expected_grammar.c_str());
  37. if (state.symbol_ids.find("root") == state.symbol_ids.end()) {
  38. throw std::runtime_error("Grammar failed to parse:\n" + expected_grammar);
  39. }
  40. } catch (const std::runtime_error & ex) {
  41. _print_failure_header();
  42. fprintf(stderr, "# GRAMMAR ERROR: %s\n", ex.what());
  43. assert(false);
  44. }
  45. }
  46. void verify_status(TestCaseStatus status) const {
  47. if (status != expected_status) {
  48. _print_failure_header();
  49. fprintf(stderr, "# EXPECTED STATUS: %s\n", expected_status == SUCCESS ? "SUCCESS" : "FAILURE");
  50. fprintf(stderr, "# ACTUAL STATUS: %s\n", status == SUCCESS ? "SUCCESS" : "FAILURE");
  51. assert(false);
  52. }
  53. }
  54. };
  55. static void write(const std::string & file, const std::string & content) {
  56. std::ofstream f;
  57. f.open(file.c_str());
  58. f << content.c_str();
  59. f.close();
  60. }
  61. static std::string read(const std::string & file) {
  62. std::ostringstream actuals;
  63. actuals << std::ifstream(file.c_str()).rdbuf();
  64. return actuals.str();
  65. }
  66. static void test_all(const std::string & lang, std::function<void(const TestCase &)> runner) {
  67. fprintf(stderr, "#\n# Testing JSON schema conversion (%s)\n#\n", lang.c_str());
  68. auto test = [&](const TestCase & tc) {
  69. fprintf(stderr, "- %s%s\n", tc.name.c_str(), tc.expected_status == FAILURE ? " (failure expected)" : "");
  70. runner(tc);
  71. };
  72. test({
  73. FAILURE,
  74. "unknown type",
  75. R"""({
  76. "type": "kaboom"
  77. })""",
  78. ""
  79. });
  80. test({
  81. FAILURE,
  82. "invalid type",
  83. R"""({
  84. "type": 123
  85. })""",
  86. ""
  87. });
  88. test({
  89. SUCCESS,
  90. "empty schema (object)",
  91. "{}",
  92. R"""(
  93. array ::= "[" space ( value ("," space value)* )? "]" space
  94. boolean ::= ("true" | "false") space
  95. char ::= [^"\\] | "\\" (["\\/bfnrt] | "u" [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F])
  96. decimal-part ::= [0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9])?)?)?)?)?)?)?)?)?)?)?)?)?)?)?
  97. integral-part ::= [0-9] | [1-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9])?)?)?)?)?)?)?)?)?)?)?)?)?)?)?
  98. null ::= "null" space
  99. number ::= ("-"? integral-part) ("." decimal-part)? ([eE] [-+]? integral-part)? space
  100. object ::= "{" space ( string ":" space value ("," space string ":" space value)* )? "}" space
  101. root ::= object
  102. space ::= " "?
  103. string ::= "\"" char* "\"" space
  104. value ::= object | array | string | number | boolean | null
  105. )"""
  106. });
  107. test({
  108. SUCCESS,
  109. "exotic formats",
  110. R"""({
  111. "items": [
  112. { "format": "date" },
  113. { "format": "uuid" },
  114. { "format": "time" },
  115. { "format": "date-time" }
  116. ]
  117. })""",
  118. R"""(
  119. date ::= [0-9] [0-9] [0-9] [0-9] "-" ( "0" [1-9] | "1" [0-2] ) "-" ( "0" [1-9] | [1-2] [0-9] | "3" [0-1] )
  120. date-string ::= "\"" date "\"" space
  121. date-time ::= date "T" time
  122. date-time-string ::= "\"" date-time "\"" space
  123. root ::= "[" space tuple-0 "," space uuid "," space tuple-2 "," space tuple-3 "]" space
  124. space ::= " "?
  125. time ::= ([01] [0-9] | "2" [0-3]) ":" [0-5] [0-9] ":" [0-5] [0-9] ( "." [0-9] [0-9] [0-9] )? ( "Z" | ( "+" | "-" ) ( [01] [0-9] | "2" [0-3] ) ":" [0-5] [0-9] )
  126. time-string ::= "\"" time "\"" space
  127. tuple-0 ::= date-string
  128. tuple-2 ::= time-string
  129. tuple-3 ::= date-time-string
  130. uuid ::= "\"" [0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F] "-" [0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F] "-" [0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F] "-" [0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F] "-" [0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F] "\"" space
  131. )"""
  132. });
  133. test({
  134. SUCCESS,
  135. "string",
  136. R"""({
  137. "type": "string"
  138. })""",
  139. R"""(
  140. char ::= [^"\\] | "\\" (["\\/bfnrt] | "u" [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F])
  141. root ::= "\"" char* "\"" space
  142. space ::= " "?
  143. )"""
  144. });
  145. test({
  146. SUCCESS,
  147. "string w/ min length 1",
  148. R"""({
  149. "type": "string",
  150. "minLength": 1
  151. })""",
  152. R"""(
  153. char ::= [^"\\] | "\\" (["\\/bfnrt] | "u" [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F])
  154. root ::= "\"" char+ "\"" space
  155. space ::= " "?
  156. )"""
  157. });
  158. test({
  159. SUCCESS,
  160. "string w/ min length 3",
  161. R"""({
  162. "type": "string",
  163. "minLength": 3
  164. })""",
  165. R"""(
  166. char ::= [^"\\] | "\\" (["\\/bfnrt] | "u" [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F])
  167. root ::= "\"" char char char (char)* "\"" space
  168. space ::= " "?
  169. )"""
  170. });
  171. test({
  172. SUCCESS,
  173. "string w/ max length",
  174. R"""({
  175. "type": "string",
  176. "maxLength": 3
  177. })""",
  178. R"""(
  179. char ::= [^"\\] | "\\" (["\\/bfnrt] | "u" [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F])
  180. root ::= "\"" (char (char (char)?)?)? "\"" space
  181. space ::= " "?
  182. )"""
  183. });
  184. test({
  185. SUCCESS,
  186. "string w/ min & max length",
  187. R"""({
  188. "type": "string",
  189. "minLength": 1,
  190. "maxLength": 4
  191. })""",
  192. R"""(
  193. char ::= [^"\\] | "\\" (["\\/bfnrt] | "u" [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F])
  194. root ::= "\"" char (char (char (char)?)?)? "\"" space
  195. space ::= " "?
  196. )"""
  197. });
  198. test({
  199. SUCCESS,
  200. "boolean",
  201. R"""({
  202. "type": "boolean"
  203. })""",
  204. R"""(
  205. root ::= ("true" | "false") space
  206. space ::= " "?
  207. )"""
  208. });
  209. test({
  210. SUCCESS,
  211. "integer",
  212. R"""({
  213. "type": "integer"
  214. })""",
  215. R"""(
  216. integral-part ::= [0-9] | [1-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9])?)?)?)?)?)?)?)?)?)?)?)?)?)?)?
  217. root ::= ("-"? integral-part) space
  218. space ::= " "?
  219. )"""
  220. });
  221. test({
  222. SUCCESS,
  223. "string const",
  224. R"""({
  225. "const": "foo"
  226. })""",
  227. R"""(
  228. root ::= "\"foo\""
  229. space ::= " "?
  230. )"""
  231. });
  232. test({
  233. SUCCESS,
  234. "non-string const",
  235. R"""({
  236. "const": 123
  237. })""",
  238. R"""(
  239. root ::= "123"
  240. space ::= " "?
  241. )"""
  242. });
  243. test({
  244. SUCCESS,
  245. "non-string enum",
  246. R"""({
  247. "enum": ["red", "amber", "green", null, 42, ["foo"]]
  248. })""",
  249. R"""(
  250. root ::= "\"red\"" | "\"amber\"" | "\"green\"" | "null" | "42" | "[\"foo\"]"
  251. space ::= " "?
  252. )"""
  253. });
  254. test({
  255. SUCCESS,
  256. "tuple1",
  257. R"""({
  258. "prefixItems": [{ "type": "string" }]
  259. })""",
  260. R"""(
  261. char ::= [^"\\] | "\\" (["\\/bfnrt] | "u" [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F])
  262. root ::= "[" space string "]" space
  263. space ::= " "?
  264. string ::= "\"" char* "\"" space
  265. )"""
  266. });
  267. test({
  268. SUCCESS,
  269. "tuple2",
  270. R"""({
  271. "prefixItems": [{ "type": "string" }, { "type": "number" }]
  272. })""",
  273. R"""(
  274. char ::= [^"\\] | "\\" (["\\/bfnrt] | "u" [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F])
  275. decimal-part ::= [0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9])?)?)?)?)?)?)?)?)?)?)?)?)?)?)?
  276. integral-part ::= [0-9] | [1-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9])?)?)?)?)?)?)?)?)?)?)?)?)?)?)?
  277. number ::= ("-"? integral-part) ("." decimal-part)? ([eE] [-+]? integral-part)? space
  278. root ::= "[" space string "," space number "]" space
  279. space ::= " "?
  280. string ::= "\"" char* "\"" space
  281. )"""
  282. });
  283. test({
  284. SUCCESS,
  285. "number",
  286. R"""({
  287. "type": "number"
  288. })""",
  289. R"""(
  290. decimal-part ::= [0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9])?)?)?)?)?)?)?)?)?)?)?)?)?)?)?
  291. integral-part ::= [0-9] | [1-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9])?)?)?)?)?)?)?)?)?)?)?)?)?)?)?
  292. root ::= ("-"? integral-part) ("." decimal-part)? ([eE] [-+]? integral-part)? space
  293. space ::= " "?
  294. )"""
  295. });
  296. test({
  297. SUCCESS,
  298. "minItems",
  299. R"""({
  300. "items": {
  301. "type": "boolean"
  302. },
  303. "minItems": 2
  304. })""",
  305. R"""(
  306. boolean ::= ("true" | "false") space
  307. root ::= "[" space boolean "," space boolean ("," space boolean)* "]" space
  308. space ::= " "?
  309. )"""
  310. });
  311. test({
  312. SUCCESS,
  313. "maxItems 1",
  314. R"""({
  315. "items": {
  316. "type": "boolean"
  317. },
  318. "maxItems": 1
  319. })""",
  320. R"""(
  321. boolean ::= ("true" | "false") space
  322. root ::= "[" space (boolean)? "]" space
  323. space ::= " "?
  324. )"""
  325. });
  326. test({
  327. SUCCESS,
  328. "maxItems 2",
  329. R"""({
  330. "items": {
  331. "type": "boolean"
  332. },
  333. "maxItems": 2
  334. })""",
  335. R"""(
  336. boolean ::= ("true" | "false") space
  337. root ::= "[" space (boolean ("," space boolean)?)? "]" space
  338. space ::= " "?
  339. )"""
  340. });
  341. test({
  342. SUCCESS,
  343. "min + maxItems",
  344. R"""({
  345. "items": {
  346. "type": ["number", "integer"]
  347. },
  348. "minItems": 3,
  349. "maxItems": 5
  350. })""",
  351. R"""(
  352. decimal-part ::= [0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9])?)?)?)?)?)?)?)?)?)?)?)?)?)?)?
  353. integer ::= ("-"? integral-part) space
  354. integral-part ::= [0-9] | [1-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9])?)?)?)?)?)?)?)?)?)?)?)?)?)?)?
  355. item ::= number | integer
  356. number ::= ("-"? integral-part) ("." decimal-part)? ([eE] [-+]? integral-part)? space
  357. root ::= "[" space item "," space item "," space item ("," space item ("," space item)?)? "]" space
  358. space ::= " "?
  359. )"""
  360. });
  361. test({
  362. SUCCESS,
  363. "simple regexp",
  364. R"""({
  365. "type": "string",
  366. "pattern": "^abc?d*efg+(hij)?kl$"
  367. })""",
  368. R"""(
  369. root ::= "\"" "ab" "c"? "d"* "ef" "g"+ ("hij")? "kl" "\"" space
  370. space ::= " "?
  371. )"""
  372. });
  373. test({
  374. SUCCESS,
  375. "regexp escapes",
  376. R"""({
  377. "type": "string",
  378. "pattern": "^\\[\\]\\{\\}\\(\\)\\|\\+\\*\\?$"
  379. })""",
  380. R"""(
  381. root ::= "\"" "[]{}()|+*?" "\"" space
  382. space ::= " "?
  383. )"""
  384. });
  385. test({
  386. SUCCESS,
  387. "regexp quote",
  388. R"""({
  389. "type": "string",
  390. "pattern": "^\"$"
  391. })""",
  392. R"""(
  393. root ::= "\"" "\"" "\"" space
  394. space ::= " "?
  395. )"""
  396. });
  397. test({
  398. SUCCESS,
  399. "regexp",
  400. R"""({
  401. "type": "string",
  402. "pattern": "^(\\([0-9]{1,3}\\))?[0-9]{3}-[0-9]{4} a{3,5}nd...$"
  403. })""",
  404. R"""(
  405. dot ::= [^\x0A\x0D]
  406. root ::= "\"" ("(" root-1 (root-1 (root-1)?)? ")")? root-1 root-1 root-1 "-" root-1 root-1 root-1 root-1 " " "aaa" ("a" ("a")?)? "nd" dot dot dot "\"" space
  407. root-1 ::= [0-9]
  408. space ::= " "?
  409. )"""
  410. });
  411. test({
  412. SUCCESS,
  413. "required props in original order",
  414. R"""({
  415. "type": "object",
  416. "properties": {
  417. "b": {"type": "string"},
  418. "c": {"type": "string"},
  419. "a": {"type": "string"}
  420. },
  421. "required": [
  422. "a",
  423. "b",
  424. "c"
  425. ],
  426. "additionalProperties": false,
  427. "definitions": {}
  428. })""",
  429. R"""(
  430. a-kv ::= "\"a\"" space ":" space string
  431. b-kv ::= "\"b\"" space ":" space string
  432. c-kv ::= "\"c\"" space ":" space string
  433. char ::= [^"\\] | "\\" (["\\/bfnrt] | "u" [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F])
  434. root ::= "{" space b-kv "," space c-kv "," space a-kv "}" space
  435. space ::= " "?
  436. string ::= "\"" char* "\"" space
  437. )"""
  438. });
  439. test({
  440. SUCCESS,
  441. "1 optional prop",
  442. R"""({
  443. "properties": {
  444. "a": {
  445. "type": "string"
  446. }
  447. },
  448. "additionalProperties": false
  449. })""",
  450. R"""(
  451. a-kv ::= "\"a\"" space ":" space string
  452. char ::= [^"\\] | "\\" (["\\/bfnrt] | "u" [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F])
  453. root ::= "{" space (a-kv )? "}" space
  454. space ::= " "?
  455. string ::= "\"" char* "\"" space
  456. )"""
  457. });
  458. test({
  459. SUCCESS,
  460. "N optional props",
  461. R"""({
  462. "properties": {
  463. "a": {"type": "string"},
  464. "b": {"type": "string"},
  465. "c": {"type": "string"}
  466. },
  467. "additionalProperties": false
  468. })""",
  469. R"""(
  470. a-kv ::= "\"a\"" space ":" space string
  471. a-rest ::= ( "," space b-kv )? b-rest
  472. b-kv ::= "\"b\"" space ":" space string
  473. b-rest ::= ( "," space c-kv )?
  474. c-kv ::= "\"c\"" space ":" space string
  475. char ::= [^"\\] | "\\" (["\\/bfnrt] | "u" [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F])
  476. root ::= "{" space (a-kv a-rest | b-kv b-rest | c-kv )? "}" space
  477. space ::= " "?
  478. string ::= "\"" char* "\"" space
  479. )"""
  480. });
  481. test({
  482. SUCCESS,
  483. "required + optional props each in original order",
  484. R"""({
  485. "properties": {
  486. "b": {"type": "string"},
  487. "a": {"type": "string"},
  488. "d": {"type": "string"},
  489. "c": {"type": "string"}
  490. },
  491. "required": ["a", "b"],
  492. "additionalProperties": false
  493. })""",
  494. R"""(
  495. a-kv ::= "\"a\"" space ":" space string
  496. b-kv ::= "\"b\"" space ":" space string
  497. c-kv ::= "\"c\"" space ":" space string
  498. char ::= [^"\\] | "\\" (["\\/bfnrt] | "u" [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F])
  499. d-kv ::= "\"d\"" space ":" space string
  500. d-rest ::= ( "," space c-kv )?
  501. root ::= "{" space b-kv "," space a-kv ( "," space ( d-kv d-rest | c-kv ) )? "}" space
  502. space ::= " "?
  503. string ::= "\"" char* "\"" space
  504. )"""
  505. });
  506. test({
  507. SUCCESS,
  508. "additional props",
  509. R"""({
  510. "type": "object",
  511. "additionalProperties": {"type": "array", "items": {"type": "number"}}
  512. })""",
  513. R"""(
  514. additional-kv ::= string ":" space additional-value
  515. additional-kvs ::= additional-kv ( "," space additional-kv )*
  516. additional-value ::= "[" space (number ("," space number)*)? "]" space
  517. char ::= [^"\\] | "\\" (["\\/bfnrt] | "u" [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F])
  518. decimal-part ::= [0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9])?)?)?)?)?)?)?)?)?)?)?)?)?)?)?
  519. integral-part ::= [0-9] | [1-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9])?)?)?)?)?)?)?)?)?)?)?)?)?)?)?
  520. number ::= ("-"? integral-part) ("." decimal-part)? ([eE] [-+]? integral-part)? space
  521. root ::= "{" space (additional-kvs )? "}" space
  522. space ::= " "?
  523. string ::= "\"" char* "\"" space
  524. )"""
  525. });
  526. test({
  527. SUCCESS,
  528. "additional props (true)",
  529. R"""({
  530. "type": "object",
  531. "additionalProperties": true
  532. })""",
  533. R"""(
  534. array ::= "[" space ( value ("," space value)* )? "]" space
  535. boolean ::= ("true" | "false") space
  536. char ::= [^"\\] | "\\" (["\\/bfnrt] | "u" [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F])
  537. decimal-part ::= [0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9])?)?)?)?)?)?)?)?)?)?)?)?)?)?)?
  538. integral-part ::= [0-9] | [1-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9])?)?)?)?)?)?)?)?)?)?)?)?)?)?)?
  539. null ::= "null" space
  540. number ::= ("-"? integral-part) ("." decimal-part)? ([eE] [-+]? integral-part)? space
  541. object ::= "{" space ( string ":" space value ("," space string ":" space value)* )? "}" space
  542. root ::= object
  543. space ::= " "?
  544. string ::= "\"" char* "\"" space
  545. value ::= object | array | string | number | boolean | null
  546. )"""
  547. });
  548. test({
  549. SUCCESS,
  550. "additional props (implicit)",
  551. R"""({
  552. "type": "object"
  553. })""",
  554. R"""(
  555. array ::= "[" space ( value ("," space value)* )? "]" space
  556. boolean ::= ("true" | "false") space
  557. char ::= [^"\\] | "\\" (["\\/bfnrt] | "u" [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F])
  558. decimal-part ::= [0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9])?)?)?)?)?)?)?)?)?)?)?)?)?)?)?
  559. integral-part ::= [0-9] | [1-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9])?)?)?)?)?)?)?)?)?)?)?)?)?)?)?
  560. null ::= "null" space
  561. number ::= ("-"? integral-part) ("." decimal-part)? ([eE] [-+]? integral-part)? space
  562. object ::= "{" space ( string ":" space value ("," space string ":" space value)* )? "}" space
  563. root ::= object
  564. space ::= " "?
  565. string ::= "\"" char* "\"" space
  566. value ::= object | array | string | number | boolean | null
  567. )"""
  568. });
  569. test({
  570. SUCCESS,
  571. "empty w/o additional props",
  572. R"""({
  573. "type": "object",
  574. "additionalProperties": false
  575. })""",
  576. R"""(
  577. root ::= "{" space "}" space
  578. space ::= " "?
  579. )"""
  580. });
  581. test({
  582. SUCCESS,
  583. "required + additional props",
  584. R"""({
  585. "type": "object",
  586. "properties": {
  587. "a": {"type": "number"}
  588. },
  589. "required": ["a"],
  590. "additionalProperties": {"type": "string"}
  591. })""",
  592. R"""(
  593. a-kv ::= "\"a\"" space ":" space number
  594. additional-kv ::= string ":" space string
  595. additional-kvs ::= additional-kv ( "," space additional-kv )*
  596. char ::= [^"\\] | "\\" (["\\/bfnrt] | "u" [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F])
  597. decimal-part ::= [0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9])?)?)?)?)?)?)?)?)?)?)?)?)?)?)?
  598. integral-part ::= [0-9] | [1-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9])?)?)?)?)?)?)?)?)?)?)?)?)?)?)?
  599. number ::= ("-"? integral-part) ("." decimal-part)? ([eE] [-+]? integral-part)? space
  600. root ::= "{" space a-kv ( "," space ( additional-kvs ) )? "}" space
  601. space ::= " "?
  602. string ::= "\"" char* "\"" space
  603. )"""
  604. });
  605. test({
  606. SUCCESS,
  607. "optional + additional props",
  608. R"""({
  609. "type": "object",
  610. "properties": {
  611. "a": {"type": "number"}
  612. },
  613. "additionalProperties": {"type": "number"}
  614. })""",
  615. R"""(
  616. a-kv ::= "\"a\"" space ":" space number
  617. a-rest ::= additional-kvs
  618. additional-kv ::= string ":" space number
  619. additional-kvs ::= additional-kv ( "," space additional-kv )*
  620. char ::= [^"\\] | "\\" (["\\/bfnrt] | "u" [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F])
  621. decimal-part ::= [0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9])?)?)?)?)?)?)?)?)?)?)?)?)?)?)?
  622. integral-part ::= [0-9] | [1-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9])?)?)?)?)?)?)?)?)?)?)?)?)?)?)?
  623. number ::= ("-"? integral-part) ("." decimal-part)? ([eE] [-+]? integral-part)? space
  624. root ::= "{" space (a-kv a-rest | additional-kvs )? "}" space
  625. space ::= " "?
  626. string ::= "\"" char* "\"" space
  627. )"""
  628. });
  629. test({
  630. SUCCESS,
  631. "required + optional + additional props",
  632. R"""({
  633. "type": "object",
  634. "properties": {
  635. "a": {"type": "number"},
  636. "b": {"type": "number"}
  637. },
  638. "required": ["a"],
  639. "additionalProperties": {"type": "number"}
  640. })""",
  641. R"""(
  642. a-kv ::= "\"a\"" space ":" space number
  643. additional-kv ::= string ":" space number
  644. additional-kvs ::= additional-kv ( "," space additional-kv )*
  645. b-kv ::= "\"b\"" space ":" space number
  646. b-rest ::= additional-kvs
  647. char ::= [^"\\] | "\\" (["\\/bfnrt] | "u" [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F])
  648. decimal-part ::= [0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9])?)?)?)?)?)?)?)?)?)?)?)?)?)?)?
  649. integral-part ::= [0-9] | [1-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9])?)?)?)?)?)?)?)?)?)?)?)?)?)?)?
  650. number ::= ("-"? integral-part) ("." decimal-part)? ([eE] [-+]? integral-part)? space
  651. root ::= "{" space a-kv ( "," space ( b-kv b-rest | additional-kvs ) )? "}" space
  652. space ::= " "?
  653. string ::= "\"" char* "\"" space
  654. )"""
  655. });
  656. test({
  657. SUCCESS,
  658. "top-level $ref",
  659. R"""({
  660. "$ref": "#/definitions/foo",
  661. "definitions": {
  662. "foo": {
  663. "type": "object",
  664. "properties": {
  665. "a": {
  666. "type": "string"
  667. }
  668. },
  669. "required": [
  670. "a"
  671. ],
  672. "additionalProperties": false
  673. }
  674. }
  675. })""",
  676. R"""(
  677. char ::= [^"\\] | "\\" (["\\/bfnrt] | "u" [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F])
  678. foo ::= "{" space foo-a-kv "}" space
  679. foo-a-kv ::= "\"a\"" space ":" space string
  680. root ::= foo
  681. space ::= " "?
  682. string ::= "\"" char* "\"" space
  683. )"""
  684. });
  685. test({
  686. SUCCESS,
  687. "anyOf",
  688. R"""({
  689. "anyOf": [
  690. {"$ref": "#/definitions/foo"},
  691. {"$ref": "#/definitions/bar"}
  692. ],
  693. "definitions": {
  694. "foo": {
  695. "properties": {"a": {"type": "number"}}
  696. },
  697. "bar": {
  698. "properties": {"b": {"type": "number"}}
  699. }
  700. },
  701. "type": "object"
  702. })""",
  703. R"""(
  704. alternative-0 ::= foo
  705. alternative-1 ::= bar
  706. bar ::= "{" space (bar-b-kv )? "}" space
  707. bar-b-kv ::= "\"b\"" space ":" space number
  708. decimal-part ::= [0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9])?)?)?)?)?)?)?)?)?)?)?)?)?)?)?
  709. foo ::= "{" space (foo-a-kv )? "}" space
  710. foo-a-kv ::= "\"a\"" space ":" space number
  711. integral-part ::= [0-9] | [1-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9])?)?)?)?)?)?)?)?)?)?)?)?)?)?)?
  712. number ::= ("-"? integral-part) ("." decimal-part)? ([eE] [-+]? integral-part)? space
  713. root ::= alternative-0 | alternative-1
  714. space ::= " "?
  715. )"""
  716. });
  717. test({
  718. SUCCESS,
  719. "mix of allOf, anyOf and $ref (similar to https://json.schemastore.org/tsconfig.json)",
  720. R"""({
  721. "allOf": [
  722. {"$ref": "#/definitions/foo"},
  723. {"$ref": "#/definitions/bar"},
  724. {
  725. "anyOf": [
  726. {"$ref": "#/definitions/baz"},
  727. {"$ref": "#/definitions/bam"}
  728. ]
  729. }
  730. ],
  731. "definitions": {
  732. "foo": {
  733. "properties": {"a": {"type": "number"}}
  734. },
  735. "bar": {
  736. "properties": {"b": {"type": "number"}}
  737. },
  738. "bam": {
  739. "properties": {"c": {"type": "number"}}
  740. },
  741. "baz": {
  742. "properties": {"d": {"type": "number"}}
  743. }
  744. },
  745. "type": "object"
  746. })""",
  747. R"""(
  748. a-kv ::= "\"a\"" space ":" space number
  749. b-kv ::= "\"b\"" space ":" space number
  750. c-kv ::= "\"c\"" space ":" space number
  751. d-kv ::= "\"d\"" space ":" space number
  752. d-rest ::= ( "," space c-kv )?
  753. decimal-part ::= [0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9])?)?)?)?)?)?)?)?)?)?)?)?)?)?)?
  754. integral-part ::= [0-9] | [1-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9])?)?)?)?)?)?)?)?)?)?)?)?)?)?)?
  755. number ::= ("-"? integral-part) ("." decimal-part)? ([eE] [-+]? integral-part)? space
  756. root ::= "{" space a-kv "," space b-kv ( "," space ( d-kv d-rest | c-kv ) )? "}" space
  757. space ::= " "?
  758. )"""
  759. });
  760. test({
  761. SUCCESS,
  762. "conflicting names",
  763. R"""({
  764. "type": "object",
  765. "properties": {
  766. "number": {
  767. "type": "object",
  768. "properties": {
  769. "number": {
  770. "type": "object",
  771. "properties": {
  772. "root": {
  773. "type": "number"
  774. }
  775. },
  776. "required": [
  777. "root"
  778. ],
  779. "additionalProperties": false
  780. }
  781. },
  782. "required": [
  783. "number"
  784. ],
  785. "additionalProperties": false
  786. }
  787. },
  788. "required": [
  789. "number"
  790. ],
  791. "additionalProperties": false,
  792. "definitions": {}
  793. })""",
  794. R"""(
  795. decimal-part ::= [0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9])?)?)?)?)?)?)?)?)?)?)?)?)?)?)?
  796. integral-part ::= [0-9] | [1-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9] ([0-9])?)?)?)?)?)?)?)?)?)?)?)?)?)?)?
  797. number ::= ("-"? integral-part) ("." decimal-part)? ([eE] [-+]? integral-part)? space
  798. number- ::= "{" space number-number-kv "}" space
  799. number-kv ::= "\"number\"" space ":" space number-
  800. number-number ::= "{" space number-number-root-kv "}" space
  801. number-number-kv ::= "\"number\"" space ":" space number-number
  802. number-number-root-kv ::= "\"root\"" space ":" space number
  803. root ::= "{" space number-kv "}" space
  804. space ::= " "?
  805. )"""
  806. });
  807. }
  808. int main() {
  809. fprintf(stderr, "LLAMA_NODE_AVAILABLE = %s\n", getenv("LLAMA_NODE_AVAILABLE") ? "true" : "false");
  810. fprintf(stderr, "LLAMA_PYTHON_AVAILABLE = %s\n", getenv("LLAMA_PYTHON_AVAILABLE") ? "true" : "false");
  811. test_all("C++", [](const TestCase & tc) {
  812. try {
  813. tc.verify(json_schema_to_grammar(nlohmann::ordered_json::parse(tc.schema)));
  814. tc.verify_status(SUCCESS);
  815. } catch (const std::runtime_error & ex) {
  816. fprintf(stderr, "Error: %s\n", ex.what());
  817. tc.verify_status(FAILURE);
  818. }
  819. });
  820. if (getenv("LLAMA_PYTHON_AVAILABLE") || (std::system("python --version") == 0)) {
  821. test_all("Python", [](const TestCase & tc) {
  822. write("test-json-schema-input.tmp", tc.schema);
  823. tc.verify_status(std::system(
  824. "python ./examples/json_schema_to_grammar.py test-json-schema-input.tmp > test-grammar-output.tmp") == 0 ? SUCCESS : FAILURE);
  825. tc.verify(read("test-grammar-output.tmp"));
  826. });
  827. } else {
  828. fprintf(stderr, "\033[33mWARNING: Python not found, skipping Python JSON schema -> grammar tests.\n\033[0m");
  829. }
  830. if (getenv("LLAMA_NODE_AVAILABLE") || (std::system("node --version") == 0)) {
  831. test_all("JavaScript", [](const TestCase & tc) {
  832. write("test-json-schema-input.tmp", tc.schema);
  833. tc.verify_status(std::system(
  834. "node ./tests/run-json-schema-to-grammar.mjs test-json-schema-input.tmp > test-grammar-output.tmp") == 0 ? SUCCESS : FAILURE);
  835. tc.verify(read("test-grammar-output.tmp"));
  836. });
  837. } else {
  838. fprintf(stderr, "\033[33mWARNING: Node not found, skipping JavaScript JSON schema -> grammar tests.\n\033[0m");
  839. }
  840. test_all("Check Expectations Validity", [](const TestCase & tc) {
  841. if (tc.expected_status == SUCCESS) {
  842. tc.verify_expectation_parseable();
  843. }
  844. });
  845. }