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

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