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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260
  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. SUCCESS,
  75. "min 0",
  76. R"""({
  77. "type": "integer",
  78. "minimum": 0
  79. })""",
  80. R"""(
  81. root ::= ([0] | [1-9] [0-9]{0,15}) space
  82. space ::= | " " | "\n" [ \t]{0,20}
  83. )"""
  84. });
  85. test({
  86. SUCCESS,
  87. "min 1",
  88. R"""({
  89. "type": "integer",
  90. "minimum": 1
  91. })""",
  92. R"""(
  93. root ::= ([1-9] [0-9]{0,15}) space
  94. space ::= | " " | "\n" [ \t]{0,20}
  95. )"""
  96. });
  97. test({
  98. SUCCESS,
  99. "min 3",
  100. R"""({
  101. "type": "integer",
  102. "minimum": 3
  103. })""",
  104. R"""(
  105. root ::= ([1-2] [0-9]{1,15} | [3-9] [0-9]{0,15}) space
  106. space ::= | " " | "\n" [ \t]{0,20}
  107. )"""
  108. });
  109. test({
  110. SUCCESS,
  111. "min 9",
  112. R"""({
  113. "type": "integer",
  114. "minimum": 9
  115. })""",
  116. R"""(
  117. root ::= ([1-8] [0-9]{1,15} | [9] [0-9]{0,15}) space
  118. space ::= | " " | "\n" [ \t]{0,20}
  119. )"""
  120. });
  121. test({
  122. SUCCESS,
  123. "min 10",
  124. R"""({
  125. "type": "integer",
  126. "minimum": 10
  127. })""",
  128. R"""(
  129. root ::= ([1] ([0-9]{1,15}) | [2-9] [0-9]{1,15}) space
  130. space ::= | " " | "\n" [ \t]{0,20}
  131. )"""
  132. });
  133. test({
  134. SUCCESS,
  135. "min 25",
  136. R"""({
  137. "type": "integer",
  138. "minimum": 25
  139. })""",
  140. R"""(
  141. 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
  142. space ::= | " " | "\n" [ \t]{0,20}
  143. )"""
  144. });
  145. test({
  146. SUCCESS,
  147. "max 30",
  148. R"""({
  149. "type": "integer",
  150. "maximum": 30
  151. })""",
  152. R"""(
  153. root ::= ("-" [1-9] [0-9]{0,15} | [0-9] | ([1-2] [0-9] | [3] "0")) space
  154. space ::= | " " | "\n" [ \t]{0,20}
  155. )"""
  156. });
  157. test({
  158. SUCCESS,
  159. "min -5",
  160. R"""({
  161. "type": "integer",
  162. "minimum": -5
  163. })""",
  164. R"""(
  165. root ::= ("-" ([0-5]) | [0] | [1-9] [0-9]{0,15}) space
  166. space ::= | " " | "\n" [ \t]{0,20}
  167. )"""
  168. });
  169. test({
  170. SUCCESS,
  171. "min -123",
  172. R"""({
  173. "type": "integer",
  174. "minimum": -123
  175. })""",
  176. R"""(
  177. 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
  178. space ::= | " " | "\n" [ \t]{0,20}
  179. )"""
  180. });
  181. test({
  182. SUCCESS,
  183. "max -5",
  184. R"""({
  185. "type": "integer",
  186. "maximum": -5
  187. })""",
  188. R"""(
  189. root ::= ("-" ([0-4] [0-9]{1,15} | [5-9] [0-9]{0,15})) space
  190. space ::= | " " | "\n" [ \t]{0,20}
  191. )"""
  192. });
  193. test({
  194. SUCCESS,
  195. "max 1",
  196. R"""({
  197. "type": "integer",
  198. "maximum": 1
  199. })""",
  200. R"""(
  201. root ::= ("-" [1-9] [0-9]{0,15} | [0-1]) space
  202. space ::= | " " | "\n" [ \t]{0,20}
  203. )"""
  204. });
  205. test({
  206. SUCCESS,
  207. "max 100",
  208. R"""({
  209. "type": "integer",
  210. "maximum": 100
  211. })""",
  212. R"""(
  213. root ::= ("-" [1-9] [0-9]{0,15} | [0-9] | ([1-8] [0-9] | [9] [0-9]) | "100") space
  214. space ::= | " " | "\n" [ \t]{0,20}
  215. )"""
  216. });
  217. test({
  218. SUCCESS,
  219. "min 0 max 23",
  220. R"""({
  221. "type": "integer",
  222. "minimum": 0,
  223. "maximum": 23
  224. })""",
  225. R"""(
  226. root ::= ([0-9] | ([1] [0-9] | [2] [0-3])) space
  227. space ::= | " " | "\n" [ \t]{0,20}
  228. )"""
  229. });
  230. test({
  231. SUCCESS,
  232. "min 15 max 300",
  233. R"""({
  234. "type": "integer",
  235. "minimum": 15,
  236. "maximum": 300
  237. })""",
  238. R"""(
  239. root ::= (([1] ([5-9]) | [2-9] [0-9]) | ([1-2] [0-9]{2} | [3] "00")) space
  240. space ::= | " " | "\n" [ \t]{0,20}
  241. )"""
  242. });
  243. test({
  244. SUCCESS,
  245. "min 5 max 30",
  246. R"""({
  247. "type": "integer",
  248. "minimum": 5,
  249. "maximum": 30
  250. })""",
  251. R"""(
  252. root ::= ([5-9] | ([1-2] [0-9] | [3] "0")) space
  253. space ::= | " " | "\n" [ \t]{0,20}
  254. )"""
  255. });
  256. test({
  257. SUCCESS,
  258. "min -123 max 42",
  259. R"""({
  260. "type": "integer",
  261. "minimum": -123,
  262. "maximum": 42
  263. })""",
  264. R"""(
  265. 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
  266. space ::= | " " | "\n" [ \t]{0,20}
  267. )"""
  268. });
  269. test({
  270. SUCCESS,
  271. "min -10 max 10",
  272. R"""({
  273. "type": "integer",
  274. "minimum": -10,
  275. "maximum": 10
  276. })""",
  277. R"""(
  278. root ::= ("-" ([0-9] | "10") | [0-9] | "10") space
  279. space ::= | " " | "\n" [ \t]{0,20}
  280. )"""
  281. });
  282. test({
  283. FAILURE,
  284. "unknown type",
  285. R"""({
  286. "type": "kaboom"
  287. })""",
  288. ""
  289. });
  290. test({
  291. FAILURE,
  292. "invalid type",
  293. R"""({
  294. "type": 123
  295. })""",
  296. ""
  297. });
  298. test({
  299. SUCCESS,
  300. "empty schema (object)",
  301. "{}",
  302. R"""(
  303. array ::= "[" space ( value ("," space value)* )? "]" space
  304. boolean ::= ("true" | "false") space
  305. char ::= [^"\\\x7F\x00-\x1F] | [\\] (["\\bfnrt] | "u" [0-9a-fA-F]{4})
  306. decimal-part ::= [0-9]{1,16}
  307. integral-part ::= [0] | [1-9] [0-9]{0,15}
  308. null ::= "null" space
  309. number ::= ("-"? integral-part) ("." decimal-part)? ([eE] [-+]? integral-part)? space
  310. object ::= "{" space ( string ":" space value ("," space string ":" space value)* )? "}" space
  311. root ::= object
  312. space ::= | " " | "\n" [ \t]{0,20}
  313. string ::= "\"" char* "\"" space
  314. value ::= object | array | string | number | boolean | null
  315. )"""
  316. });
  317. test({
  318. SUCCESS,
  319. "exotic formats",
  320. R"""({
  321. "items": [
  322. { "format": "date" },
  323. { "format": "uuid" },
  324. { "format": "time" },
  325. { "format": "date-time" }
  326. ]
  327. })""",
  328. R"""(
  329. date ::= [0-9]{4} "-" ( "0" [1-9] | "1" [0-2] ) "-" ( "0" [1-9] | [1-2] [0-9] | "3" [0-1] )
  330. date-string ::= "\"" date "\"" space
  331. date-time ::= date "T" time
  332. date-time-string ::= "\"" date-time "\"" space
  333. root ::= "[" space tuple-0 "," space uuid "," space tuple-2 "," space tuple-3 "]" space
  334. space ::= | " " | "\n" [ \t]{0,20}
  335. 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] )
  336. time-string ::= "\"" time "\"" space
  337. tuple-0 ::= date-string
  338. tuple-2 ::= time-string
  339. tuple-3 ::= date-time-string
  340. 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
  341. )"""
  342. });
  343. test({
  344. SUCCESS,
  345. "string",
  346. R"""({
  347. "type": "string"
  348. })""",
  349. R"""(
  350. char ::= [^"\\\x7F\x00-\x1F] | [\\] (["\\bfnrt] | "u" [0-9a-fA-F]{4})
  351. root ::= "\"" char* "\"" space
  352. space ::= | " " | "\n" [ \t]{0,20}
  353. )"""
  354. });
  355. test({
  356. SUCCESS,
  357. "string w/ min length 1",
  358. R"""({
  359. "type": "string",
  360. "minLength": 1
  361. })""",
  362. R"""(
  363. char ::= [^"\\\x7F\x00-\x1F] | [\\] (["\\bfnrt] | "u" [0-9a-fA-F]{4})
  364. root ::= "\"" char+ "\"" space
  365. space ::= | " " | "\n" [ \t]{0,20}
  366. )"""
  367. });
  368. test({
  369. SUCCESS,
  370. "string w/ min length 3",
  371. R"""({
  372. "type": "string",
  373. "minLength": 3
  374. })""",
  375. R"""(
  376. char ::= [^"\\\x7F\x00-\x1F] | [\\] (["\\bfnrt] | "u" [0-9a-fA-F]{4})
  377. root ::= "\"" char{3,} "\"" space
  378. space ::= | " " | "\n" [ \t]{0,20}
  379. )"""
  380. });
  381. test({
  382. SUCCESS,
  383. "string w/ max length",
  384. R"""({
  385. "type": "string",
  386. "maxLength": 3
  387. })""",
  388. R"""(
  389. char ::= [^"\\\x7F\x00-\x1F] | [\\] (["\\bfnrt] | "u" [0-9a-fA-F]{4})
  390. root ::= "\"" char{0,3} "\"" space
  391. space ::= | " " | "\n" [ \t]{0,20}
  392. )"""
  393. });
  394. test({
  395. SUCCESS,
  396. "string w/ min & max length",
  397. R"""({
  398. "type": "string",
  399. "minLength": 1,
  400. "maxLength": 4
  401. })""",
  402. R"""(
  403. char ::= [^"\\\x7F\x00-\x1F] | [\\] (["\\bfnrt] | "u" [0-9a-fA-F]{4})
  404. root ::= "\"" char{1,4} "\"" space
  405. space ::= | " " | "\n" [ \t]{0,20}
  406. )"""
  407. });
  408. test({
  409. SUCCESS,
  410. "boolean",
  411. R"""({
  412. "type": "boolean"
  413. })""",
  414. R"""(
  415. root ::= ("true" | "false") space
  416. space ::= | " " | "\n" [ \t]{0,20}
  417. )"""
  418. });
  419. test({
  420. SUCCESS,
  421. "integer",
  422. R"""({
  423. "type": "integer"
  424. })""",
  425. R"""(
  426. integral-part ::= [0] | [1-9] [0-9]{0,15}
  427. root ::= ("-"? integral-part) space
  428. space ::= | " " | "\n" [ \t]{0,20}
  429. )"""
  430. });
  431. test({
  432. SUCCESS,
  433. "string const",
  434. R"""({
  435. "const": "foo"
  436. })""",
  437. R"""(
  438. root ::= "\"foo\"" space
  439. space ::= | " " | "\n" [ \t]{0,20}
  440. )"""
  441. });
  442. test({
  443. SUCCESS,
  444. "non-string const",
  445. R"""({
  446. "const": 123
  447. })""",
  448. R"""(
  449. root ::= "123" space
  450. space ::= | " " | "\n" [ \t]{0,20}
  451. )"""
  452. });
  453. test({
  454. SUCCESS,
  455. "non-string enum",
  456. R"""({
  457. "enum": ["red", "amber", "green", null, 42, ["foo"]]
  458. })""",
  459. R"""(
  460. root ::= ("\"red\"" | "\"amber\"" | "\"green\"" | "null" | "42" | "[\"foo\"]") space
  461. space ::= | " " | "\n" [ \t]{0,20}
  462. )"""
  463. });
  464. test({
  465. SUCCESS,
  466. "tuple1",
  467. R"""({
  468. "prefixItems": [{ "type": "string" }]
  469. })""",
  470. R"""(
  471. char ::= [^"\\\x7F\x00-\x1F] | [\\] (["\\bfnrt] | "u" [0-9a-fA-F]{4})
  472. root ::= "[" space string "]" space
  473. space ::= | " " | "\n" [ \t]{0,20}
  474. string ::= "\"" char* "\"" space
  475. )"""
  476. });
  477. test({
  478. SUCCESS,
  479. "tuple2",
  480. R"""({
  481. "prefixItems": [{ "type": "string" }, { "type": "number" }]
  482. })""",
  483. R"""(
  484. char ::= [^"\\\x7F\x00-\x1F] | [\\] (["\\bfnrt] | "u" [0-9a-fA-F]{4})
  485. decimal-part ::= [0-9]{1,16}
  486. integral-part ::= [0] | [1-9] [0-9]{0,15}
  487. number ::= ("-"? integral-part) ("." decimal-part)? ([eE] [-+]? integral-part)? space
  488. root ::= "[" space string "," space number "]" space
  489. space ::= | " " | "\n" [ \t]{0,20}
  490. string ::= "\"" char* "\"" space
  491. )"""
  492. });
  493. test({
  494. SUCCESS,
  495. "number",
  496. R"""({
  497. "type": "number"
  498. })""",
  499. R"""(
  500. decimal-part ::= [0-9]{1,16}
  501. integral-part ::= [0] | [1-9] [0-9]{0,15}
  502. root ::= ("-"? integral-part) ("." decimal-part)? ([eE] [-+]? integral-part)? space
  503. space ::= | " " | "\n" [ \t]{0,20}
  504. )"""
  505. });
  506. test({
  507. SUCCESS,
  508. "minItems",
  509. R"""({
  510. "items": {
  511. "type": "boolean"
  512. },
  513. "minItems": 2
  514. })""",
  515. R"""(
  516. boolean ::= ("true" | "false") space
  517. root ::= "[" space boolean ("," space boolean)+ "]" space
  518. space ::= | " " | "\n" [ \t]{0,20}
  519. )"""
  520. });
  521. test({
  522. SUCCESS,
  523. "maxItems 1",
  524. R"""({
  525. "items": {
  526. "type": "boolean"
  527. },
  528. "maxItems": 1
  529. })""",
  530. R"""(
  531. boolean ::= ("true" | "false") space
  532. root ::= "[" space boolean? "]" space
  533. space ::= | " " | "\n" [ \t]{0,20}
  534. )"""
  535. });
  536. test({
  537. SUCCESS,
  538. "maxItems 2",
  539. R"""({
  540. "items": {
  541. "type": "boolean"
  542. },
  543. "maxItems": 2
  544. })""",
  545. R"""(
  546. boolean ::= ("true" | "false") space
  547. root ::= "[" space (boolean ("," space boolean)?)? "]" space
  548. space ::= | " " | "\n" [ \t]{0,20}
  549. )"""
  550. });
  551. test({
  552. SUCCESS,
  553. "min + maxItems",
  554. R"""({
  555. "items": {
  556. "type": ["number", "integer"]
  557. },
  558. "minItems": 3,
  559. "maxItems": 5
  560. })""",
  561. R"""(
  562. decimal-part ::= [0-9]{1,16}
  563. integer ::= ("-"? integral-part) space
  564. integral-part ::= [0] | [1-9] [0-9]{0,15}
  565. item ::= number | integer
  566. number ::= ("-"? integral-part) ("." decimal-part)? ([eE] [-+]? integral-part)? space
  567. root ::= "[" space item ("," space item){2,4} "]" space
  568. space ::= | " " | "\n" [ \t]{0,20}
  569. )"""
  570. });
  571. test({
  572. SUCCESS,
  573. "min + max items with min + max values across zero",
  574. R"""({
  575. "items": {
  576. "type": "integer",
  577. "minimum": -12,
  578. "maximum": 207
  579. },
  580. "minItems": 3,
  581. "maxItems": 5
  582. })""",
  583. R"""(
  584. item ::= ("-" ([0-9] | "1" [0-2]) | [0-9] | ([1-8] [0-9] | [9] [0-9]) | ([1] [0-9]{2} | [2] "0" [0-7])) space
  585. root ::= "[" space item ("," space item){2,4} "]" space
  586. space ::= | " " | "\n" [ \t]{0,20}
  587. )"""
  588. });
  589. test({
  590. SUCCESS,
  591. "min + max items with min + max values",
  592. R"""({
  593. "items": {
  594. "type": "integer",
  595. "minimum": 12,
  596. "maximum": 207
  597. },
  598. "minItems": 3,
  599. "maxItems": 5
  600. })""",
  601. R"""(
  602. item ::= (([1] ([2-9]) | [2-9] [0-9]) | ([1] [0-9]{2} | [2] "0" [0-7])) space
  603. root ::= "[" space item ("," space item){2,4} "]" space
  604. space ::= | " " | "\n" [ \t]{0,20}
  605. )"""
  606. });
  607. test({
  608. SUCCESS,
  609. "simple regexp",
  610. R"""({
  611. "type": "string",
  612. "pattern": "^abc?d*efg+(hij)?kl$"
  613. })""",
  614. R"""(
  615. root ::= "\"" "ab" "c"? "d"* "ef" "g"+ ("hij")? "kl" "\"" space
  616. space ::= | " " | "\n" [ \t]{0,20}
  617. )"""
  618. });
  619. test({
  620. SUCCESS,
  621. "regexp escapes",
  622. R"""({
  623. "type": "string",
  624. "pattern": "^\\[\\]\\{\\}\\(\\)\\|\\+\\*\\?$"
  625. })""",
  626. R"""(
  627. root ::= "\"" "[]{}()|+*?" "\"" space
  628. space ::= | " " | "\n" [ \t]{0,20}
  629. )"""
  630. });
  631. test({
  632. SUCCESS,
  633. "regexp quote",
  634. R"""({
  635. "type": "string",
  636. "pattern": "^\"$"
  637. })""",
  638. R"""(
  639. root ::= "\"" "\"" "\"" space
  640. space ::= | " " | "\n" [ \t]{0,20}
  641. )"""
  642. });
  643. test({
  644. SUCCESS,
  645. "regexp",
  646. R"""({
  647. "type": "string",
  648. "pattern": "^(\\([0-9]{1,3}\\))?[0-9]{3}-[0-9]{4} a{3,5}nd...$"
  649. })""",
  650. R"""(
  651. dot ::= [^\x0A\x0D]
  652. root ::= "\"" ("(" root-1{1,3} ")")? root-1{3,3} "-" root-1{4,4} " " "a"{3,5} "nd" dot dot dot "\"" space
  653. root-1 ::= [0-9]
  654. space ::= | " " | "\n" [ \t]{0,20}
  655. )"""
  656. });
  657. test({
  658. SUCCESS,
  659. "required props in original order",
  660. R"""({
  661. "type": "object",
  662. "properties": {
  663. "b": {"type": "string"},
  664. "c": {"type": "string"},
  665. "a": {"type": "string"}
  666. },
  667. "required": [
  668. "a",
  669. "b",
  670. "c"
  671. ],
  672. "additionalProperties": false,
  673. "definitions": {}
  674. })""",
  675. R"""(
  676. a-kv ::= "\"a\"" space ":" space string
  677. b-kv ::= "\"b\"" space ":" space string
  678. c-kv ::= "\"c\"" space ":" space string
  679. char ::= [^"\\\x7F\x00-\x1F] | [\\] (["\\bfnrt] | "u" [0-9a-fA-F]{4})
  680. root ::= "{" space b-kv "," space c-kv "," space a-kv "}" space
  681. space ::= | " " | "\n" [ \t]{0,20}
  682. string ::= "\"" char* "\"" space
  683. )"""
  684. });
  685. test({
  686. SUCCESS,
  687. "1 optional prop",
  688. R"""({
  689. "properties": {
  690. "a": {
  691. "type": "string"
  692. }
  693. },
  694. "additionalProperties": false
  695. })""",
  696. R"""(
  697. a-kv ::= "\"a\"" space ":" space string
  698. char ::= [^"\\\x7F\x00-\x1F] | [\\] (["\\bfnrt] | "u" [0-9a-fA-F]{4})
  699. root ::= "{" space (a-kv )? "}" space
  700. space ::= | " " | "\n" [ \t]{0,20}
  701. string ::= "\"" char* "\"" space
  702. )"""
  703. });
  704. test({
  705. SUCCESS,
  706. "N optional props",
  707. R"""({
  708. "properties": {
  709. "a": {"type": "string"},
  710. "b": {"type": "string"},
  711. "c": {"type": "string"}
  712. },
  713. "additionalProperties": false
  714. })""",
  715. R"""(
  716. a-kv ::= "\"a\"" space ":" space string
  717. a-rest ::= ( "," space b-kv )? b-rest
  718. b-kv ::= "\"b\"" space ":" space string
  719. b-rest ::= ( "," space c-kv )?
  720. c-kv ::= "\"c\"" space ":" space string
  721. char ::= [^"\\\x7F\x00-\x1F] | [\\] (["\\bfnrt] | "u" [0-9a-fA-F]{4})
  722. root ::= "{" space (a-kv a-rest | b-kv b-rest | c-kv )? "}" space
  723. space ::= | " " | "\n" [ \t]{0,20}
  724. string ::= "\"" char* "\"" space
  725. )"""
  726. });
  727. test({
  728. SUCCESS,
  729. "required + optional props each in original order",
  730. R"""({
  731. "properties": {
  732. "b": {"type": "string"},
  733. "a": {"type": "string"},
  734. "d": {"type": "string"},
  735. "c": {"type": "string"}
  736. },
  737. "required": ["a", "b"],
  738. "additionalProperties": false
  739. })""",
  740. R"""(
  741. a-kv ::= "\"a\"" space ":" space string
  742. b-kv ::= "\"b\"" space ":" space string
  743. c-kv ::= "\"c\"" space ":" space string
  744. char ::= [^"\\\x7F\x00-\x1F] | [\\] (["\\bfnrt] | "u" [0-9a-fA-F]{4})
  745. d-kv ::= "\"d\"" space ":" space string
  746. d-rest ::= ( "," space c-kv )?
  747. root ::= "{" space b-kv "," space a-kv ( "," space ( d-kv d-rest | c-kv ) )? "}" space
  748. space ::= | " " | "\n" [ \t]{0,20}
  749. string ::= "\"" char* "\"" space
  750. )"""
  751. });
  752. test({
  753. SUCCESS,
  754. "additional props",
  755. R"""({
  756. "type": "object",
  757. "additionalProperties": {"type": "array", "items": {"type": "number"}}
  758. })""",
  759. R"""(
  760. additional-kv ::= string ":" space additional-value
  761. additional-value ::= "[" space (number ("," space number)*)? "]" space
  762. char ::= [^"\\\x7F\x00-\x1F] | [\\] (["\\bfnrt] | "u" [0-9a-fA-F]{4})
  763. decimal-part ::= [0-9]{1,16}
  764. integral-part ::= [0] | [1-9] [0-9]{0,15}
  765. number ::= ("-"? integral-part) ("." decimal-part)? ([eE] [-+]? integral-part)? space
  766. root ::= "{" space (additional-kv ( "," space additional-kv )* )? "}" space
  767. space ::= | " " | "\n" [ \t]{0,20}
  768. string ::= "\"" char* "\"" space
  769. )"""
  770. });
  771. test({
  772. SUCCESS,
  773. "additional props (true)",
  774. R"""({
  775. "type": "object",
  776. "additionalProperties": true
  777. })""",
  778. R"""(
  779. array ::= "[" space ( value ("," space value)* )? "]" space
  780. boolean ::= ("true" | "false") space
  781. char ::= [^"\\\x7F\x00-\x1F] | [\\] (["\\bfnrt] | "u" [0-9a-fA-F]{4})
  782. decimal-part ::= [0-9]{1,16}
  783. integral-part ::= [0] | [1-9] [0-9]{0,15}
  784. null ::= "null" space
  785. number ::= ("-"? integral-part) ("." decimal-part)? ([eE] [-+]? integral-part)? space
  786. object ::= "{" space ( string ":" space value ("," space string ":" space value)* )? "}" space
  787. root ::= object
  788. space ::= | " " | "\n" [ \t]{0,20}
  789. string ::= "\"" char* "\"" space
  790. value ::= object | array | string | number | boolean | null
  791. )"""
  792. });
  793. test({
  794. SUCCESS,
  795. "additional props (implicit)",
  796. R"""({
  797. "type": "object"
  798. })""",
  799. R"""(
  800. array ::= "[" space ( value ("," space value)* )? "]" space
  801. boolean ::= ("true" | "false") space
  802. char ::= [^"\\\x7F\x00-\x1F] | [\\] (["\\bfnrt] | "u" [0-9a-fA-F]{4})
  803. decimal-part ::= [0-9]{1,16}
  804. integral-part ::= [0] | [1-9] [0-9]{0,15}
  805. null ::= "null" space
  806. number ::= ("-"? integral-part) ("." decimal-part)? ([eE] [-+]? integral-part)? space
  807. object ::= "{" space ( string ":" space value ("," space string ":" space value)* )? "}" space
  808. root ::= object
  809. space ::= | " " | "\n" [ \t]{0,20}
  810. string ::= "\"" char* "\"" space
  811. value ::= object | array | string | number | boolean | null
  812. )"""
  813. });
  814. test({
  815. SUCCESS,
  816. "empty w/o additional props",
  817. R"""({
  818. "type": "object",
  819. "additionalProperties": false
  820. })""",
  821. R"""(
  822. root ::= "{" space "}" space
  823. space ::= | " " | "\n" [ \t]{0,20}
  824. )"""
  825. });
  826. test({
  827. SUCCESS,
  828. "required + additional props",
  829. R"""({
  830. "type": "object",
  831. "properties": {
  832. "a": {"type": "number"}
  833. },
  834. "required": ["a"],
  835. "additionalProperties": {"type": "string"}
  836. })""",
  837. R"""(
  838. a-kv ::= "\"a\"" space ":" space number
  839. additional-k ::= ["] ( [a] char+ | [^"a] char* )? ["] space
  840. additional-kv ::= additional-k ":" space string
  841. char ::= [^"\\\x7F\x00-\x1F] | [\\] (["\\bfnrt] | "u" [0-9a-fA-F]{4})
  842. decimal-part ::= [0-9]{1,16}
  843. integral-part ::= [0] | [1-9] [0-9]{0,15}
  844. number ::= ("-"? integral-part) ("." decimal-part)? ([eE] [-+]? integral-part)? space
  845. root ::= "{" space a-kv ( "," space ( additional-kv ( "," space additional-kv )* ) )? "}" space
  846. space ::= | " " | "\n" [ \t]{0,20}
  847. string ::= "\"" char* "\"" space
  848. )"""
  849. });
  850. test({
  851. SUCCESS,
  852. "optional + additional props",
  853. R"""({
  854. "type": "object",
  855. "properties": {
  856. "a": {"type": "number"}
  857. },
  858. "additionalProperties": {"type": "number"}
  859. })""",
  860. R"""(
  861. a-kv ::= "\"a\"" space ":" space number
  862. a-rest ::= ( "," space additional-kv )*
  863. additional-k ::= ["] ( [a] char+ | [^"a] char* )? ["] space
  864. additional-kv ::= additional-k ":" space number
  865. char ::= [^"\\\x7F\x00-\x1F] | [\\] (["\\bfnrt] | "u" [0-9a-fA-F]{4})
  866. decimal-part ::= [0-9]{1,16}
  867. integral-part ::= [0] | [1-9] [0-9]{0,15}
  868. number ::= ("-"? integral-part) ("." decimal-part)? ([eE] [-+]? integral-part)? space
  869. root ::= "{" space (a-kv a-rest | additional-kv ( "," space additional-kv )* )? "}" space
  870. space ::= | " " | "\n" [ \t]{0,20}
  871. )"""
  872. });
  873. test({
  874. SUCCESS,
  875. "required + optional + additional props",
  876. R"""({
  877. "type": "object",
  878. "properties": {
  879. "and": {"type": "number"},
  880. "also": {"type": "number"}
  881. },
  882. "required": ["and"],
  883. "additionalProperties": {"type": "number"}
  884. })""",
  885. R"""(
  886. additional-k ::= ["] ( [a] ([l] ([s] ([o] char+ | [^"o] char*) | [^"s] char*) | [n] ([d] char+ | [^"d] char*) | [^"ln] char*) | [^"a] char* )? ["] space
  887. additional-kv ::= additional-k ":" space number
  888. also-kv ::= "\"also\"" space ":" space number
  889. also-rest ::= ( "," space additional-kv )*
  890. and-kv ::= "\"and\"" space ":" space number
  891. char ::= [^"\\\x7F\x00-\x1F] | [\\] (["\\bfnrt] | "u" [0-9a-fA-F]{4})
  892. decimal-part ::= [0-9]{1,16}
  893. integral-part ::= [0] | [1-9] [0-9]{0,15}
  894. number ::= ("-"? integral-part) ("." decimal-part)? ([eE] [-+]? integral-part)? space
  895. root ::= "{" space and-kv ( "," space ( also-kv also-rest | additional-kv ( "," space additional-kv )* ) )? "}" space
  896. space ::= | " " | "\n" [ \t]{0,20}
  897. )"""
  898. });
  899. test({
  900. SUCCESS,
  901. "optional props with empty name",
  902. R"""({
  903. "properties": {
  904. "": {"type": "integer"},
  905. "a": {"type": "integer"}
  906. },
  907. "additionalProperties": {"type": "integer"}
  908. })""",
  909. R"""(
  910. -kv ::= "\"\"" space ":" space root
  911. -rest ::= ( "," space a-kv )? a-rest
  912. a-kv ::= "\"a\"" space ":" space integer
  913. a-rest ::= ( "," space additional-kv )*
  914. additional-k ::= ["] ( [a] char+ | [^"a] char* ) ["] space
  915. additional-kv ::= additional-k ":" space integer
  916. char ::= [^"\\\x7F\x00-\x1F] | [\\] (["\\bfnrt] | "u" [0-9a-fA-F]{4})
  917. integer ::= ("-"? integral-part) space
  918. integral-part ::= [0] | [1-9] [0-9]{0,15}
  919. root ::= ("-"? integral-part) space
  920. root0 ::= "{" space (-kv -rest | a-kv a-rest | additional-kv ( "," space additional-kv )* )? "}" space
  921. space ::= | " " | "\n" [ \t]{0,20}
  922. )"""
  923. });
  924. test({
  925. SUCCESS,
  926. "optional props with nested names",
  927. R"""({
  928. "properties": {
  929. "a": {"type": "integer"},
  930. "aa": {"type": "integer"}
  931. },
  932. "additionalProperties": {"type": "integer"}
  933. })""",
  934. R"""(
  935. a-kv ::= "\"a\"" space ":" space integer
  936. a-rest ::= ( "," space aa-kv )? aa-rest
  937. aa-kv ::= "\"aa\"" space ":" space integer
  938. aa-rest ::= ( "," space additional-kv )*
  939. additional-k ::= ["] ( [a] ([a] char+ | [^"a] char*) | [^"a] char* )? ["] space
  940. additional-kv ::= additional-k ":" space integer
  941. char ::= [^"\\\x7F\x00-\x1F] | [\\] (["\\bfnrt] | "u" [0-9a-fA-F]{4})
  942. integer ::= ("-"? integral-part) space
  943. integral-part ::= [0] | [1-9] [0-9]{0,15}
  944. root ::= "{" space (a-kv a-rest | aa-kv aa-rest | additional-kv ( "," space additional-kv )* )? "}" space
  945. space ::= | " " | "\n" [ \t]{0,20}
  946. )"""
  947. });
  948. test({
  949. SUCCESS,
  950. "optional props with common prefix",
  951. R"""({
  952. "properties": {
  953. "ab": {"type": "integer"},
  954. "ac": {"type": "integer"}
  955. },
  956. "additionalProperties": {"type": "integer"}
  957. })""",
  958. R"""(
  959. ab-kv ::= "\"ab\"" space ":" space integer
  960. ab-rest ::= ( "," space ac-kv )? ac-rest
  961. ac-kv ::= "\"ac\"" space ":" space integer
  962. ac-rest ::= ( "," space additional-kv )*
  963. additional-k ::= ["] ( [a] ([b] char+ | [c] char+ | [^"bc] char*) | [^"a] char* )? ["] space
  964. additional-kv ::= additional-k ":" space integer
  965. char ::= [^"\\\x7F\x00-\x1F] | [\\] (["\\bfnrt] | "u" [0-9a-fA-F]{4})
  966. integer ::= ("-"? integral-part) space
  967. integral-part ::= [0] | [1-9] [0-9]{0,15}
  968. root ::= "{" space (ab-kv ab-rest | ac-kv ac-rest | additional-kv ( "," space additional-kv )* )? "}" space
  969. space ::= | " " | "\n" [ \t]{0,20}
  970. )"""
  971. });
  972. test({
  973. SUCCESS,
  974. "top-level $ref",
  975. R"""({
  976. "$ref": "#/definitions/foo",
  977. "definitions": {
  978. "foo": {
  979. "type": "object",
  980. "properties": {
  981. "a": {
  982. "type": "string"
  983. }
  984. },
  985. "required": [
  986. "a"
  987. ],
  988. "additionalProperties": false
  989. }
  990. }
  991. })""",
  992. R"""(
  993. char ::= [^"\\\x7F\x00-\x1F] | [\\] (["\\bfnrt] | "u" [0-9a-fA-F]{4})
  994. foo ::= "{" space foo-a-kv "}" space
  995. foo-a-kv ::= "\"a\"" space ":" space string
  996. root ::= foo
  997. space ::= | " " | "\n" [ \t]{0,20}
  998. string ::= "\"" char* "\"" space
  999. )"""
  1000. });
  1001. test({
  1002. SUCCESS,
  1003. "anyOf",
  1004. R"""({
  1005. "anyOf": [
  1006. {"$ref": "#/definitions/foo"},
  1007. {"$ref": "#/definitions/bar"}
  1008. ],
  1009. "definitions": {
  1010. "foo": {
  1011. "properties": {"a": {"type": "number"}}
  1012. },
  1013. "bar": {
  1014. "properties": {"b": {"type": "number"}}
  1015. }
  1016. },
  1017. "type": "object"
  1018. })""",
  1019. R"""(
  1020. alternative-0 ::= foo
  1021. alternative-1 ::= bar
  1022. array ::= "[" space ( value ("," space value)* )? "]" space
  1023. bar ::= "{" space (bar-b-kv bar-b-rest | bar-additional-kv ( "," space bar-additional-kv )* )? "}" space
  1024. bar-additional-k ::= ["] ( [b] char+ | [^"b] char* )? ["] space
  1025. bar-additional-kv ::= bar-additional-k ":" space value
  1026. bar-b-kv ::= "\"b\"" space ":" space number
  1027. bar-b-rest ::= ( "," space bar-additional-kv )*
  1028. boolean ::= ("true" | "false") space
  1029. char ::= [^"\\\x7F\x00-\x1F] | [\\] (["\\bfnrt] | "u" [0-9a-fA-F]{4})
  1030. decimal-part ::= [0-9]{1,16}
  1031. foo ::= "{" space (foo-a-kv foo-a-rest | foo-additional-kv ( "," space foo-additional-kv )* )? "}" space
  1032. foo-a-kv ::= "\"a\"" space ":" space number
  1033. foo-a-rest ::= ( "," space foo-additional-kv )*
  1034. foo-additional-k ::= ["] ( [a] char+ | [^"a] char* )? ["] space
  1035. foo-additional-kv ::= foo-additional-k ":" space value
  1036. integral-part ::= [0] | [1-9] [0-9]{0,15}
  1037. null ::= "null" space
  1038. number ::= ("-"? integral-part) ("." decimal-part)? ([eE] [-+]? integral-part)? space
  1039. object ::= "{" space ( string ":" space value ("," space string ":" space value)* )? "}" space
  1040. root ::= alternative-0 | alternative-1
  1041. space ::= | " " | "\n" [ \t]{0,20}
  1042. string ::= "\"" char* "\"" space
  1043. value ::= object | array | string | number | boolean | null
  1044. )"""
  1045. });
  1046. test({
  1047. SUCCESS,
  1048. "mix of allOf, anyOf and $ref (similar to https://json.schemastore.org/tsconfig.json)",
  1049. R"""({
  1050. "allOf": [
  1051. {"$ref": "#/definitions/foo"},
  1052. {"$ref": "#/definitions/bar"},
  1053. {
  1054. "anyOf": [
  1055. {"$ref": "#/definitions/baz"},
  1056. {"$ref": "#/definitions/bam"}
  1057. ]
  1058. }
  1059. ],
  1060. "definitions": {
  1061. "foo": {
  1062. "properties": {"a": {"type": "number"}}
  1063. },
  1064. "bar": {
  1065. "properties": {"b": {"type": "number"}}
  1066. },
  1067. "bam": {
  1068. "properties": {"c": {"type": "number"}}
  1069. },
  1070. "baz": {
  1071. "properties": {"d": {"type": "number"}}
  1072. }
  1073. },
  1074. "type": "object"
  1075. })""",
  1076. R"""(
  1077. a-kv ::= "\"a\"" space ":" space number
  1078. additional-k ::= ["] ( [a] char+ | [b] char+ | [c] char+ | [d] char+ | [^"abcd] char* )? ["] space
  1079. additional-kv ::= additional-k ":" space value
  1080. array ::= "[" space ( value ("," space value)* )? "]" space
  1081. b-kv ::= "\"b\"" space ":" space number
  1082. boolean ::= ("true" | "false") space
  1083. c-kv ::= "\"c\"" space ":" space number
  1084. c-rest ::= ( "," space additional-kv )*
  1085. char ::= [^"\\\x7F\x00-\x1F] | [\\] (["\\bfnrt] | "u" [0-9a-fA-F]{4})
  1086. d-kv ::= "\"d\"" space ":" space number
  1087. d-rest ::= ( "," space c-kv )? c-rest
  1088. decimal-part ::= [0-9]{1,16}
  1089. integral-part ::= [0] | [1-9] [0-9]{0,15}
  1090. null ::= "null" space
  1091. number ::= ("-"? integral-part) ("." decimal-part)? ([eE] [-+]? integral-part)? space
  1092. object ::= "{" space ( string ":" space value ("," space string ":" space value)* )? "}" space
  1093. root ::= "{" space a-kv "," space b-kv ( "," space ( d-kv d-rest | c-kv c-rest | additional-kv ( "," space additional-kv )* ) )? "}" space
  1094. space ::= | " " | "\n" [ \t]{0,20}
  1095. string ::= "\"" char* "\"" space
  1096. value ::= object | array | string | number | boolean | null
  1097. )"""
  1098. });
  1099. test({
  1100. SUCCESS,
  1101. "conflicting names",
  1102. R"""({
  1103. "type": "object",
  1104. "properties": {
  1105. "number": {
  1106. "type": "object",
  1107. "properties": {
  1108. "number": {
  1109. "type": "object",
  1110. "properties": {
  1111. "root": {
  1112. "type": "number"
  1113. }
  1114. },
  1115. "required": [
  1116. "root"
  1117. ],
  1118. "additionalProperties": false
  1119. }
  1120. },
  1121. "required": [
  1122. "number"
  1123. ],
  1124. "additionalProperties": false
  1125. }
  1126. },
  1127. "required": [
  1128. "number"
  1129. ],
  1130. "additionalProperties": false,
  1131. "definitions": {}
  1132. })""",
  1133. R"""(
  1134. decimal-part ::= [0-9]{1,16}
  1135. integral-part ::= [0] | [1-9] [0-9]{0,15}
  1136. number ::= ("-"? integral-part) ("." decimal-part)? ([eE] [-+]? integral-part)? space
  1137. number- ::= "{" space number-number-kv "}" space
  1138. number-kv ::= "\"number\"" space ":" space number-
  1139. number-number ::= "{" space number-number-root-kv "}" space
  1140. number-number-kv ::= "\"number\"" space ":" space number-number
  1141. number-number-root-kv ::= "\"root\"" space ":" space number
  1142. root ::= "{" space number-kv "}" space
  1143. space ::= | " " | "\n" [ \t]{0,20}
  1144. )"""
  1145. });
  1146. }
  1147. int main() {
  1148. fprintf(stderr, "LLAMA_NODE_AVAILABLE = %s\n", getenv("LLAMA_NODE_AVAILABLE") ? "true" : "false");
  1149. fprintf(stderr, "LLAMA_PYTHON_AVAILABLE = %s\n", getenv("LLAMA_PYTHON_AVAILABLE") ? "true" : "false");
  1150. test_all("C++", [](const TestCase & tc) {
  1151. try {
  1152. tc.verify(json_schema_to_grammar(nlohmann::ordered_json::parse(tc.schema)));
  1153. tc.verify_status(SUCCESS);
  1154. } catch (const std::runtime_error & ex) {
  1155. fprintf(stderr, "Error: %s\n", ex.what());
  1156. tc.verify_status(FAILURE);
  1157. }
  1158. });
  1159. 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)) {
  1160. test_all("Python", [](const TestCase & tc) {
  1161. write("test-json-schema-input.tmp", tc.schema);
  1162. tc.verify_status(std::system(
  1163. "python ./examples/json_schema_to_grammar.py test-json-schema-input.tmp > test-grammar-output.tmp") == 0 ? SUCCESS : FAILURE);
  1164. tc.verify(read("test-grammar-output.tmp"));
  1165. });
  1166. } else {
  1167. fprintf(stderr, "\033[33mWARNING: Python not found (min version required is 3.8), skipping Python JSON schema -> grammar tests.\n\033[0m");
  1168. }
  1169. if (getenv("LLAMA_NODE_AVAILABLE") || (std::system("node --version") == 0)) {
  1170. test_all("JavaScript", [](const TestCase & tc) {
  1171. write("test-json-schema-input.tmp", tc.schema);
  1172. tc.verify_status(std::system(
  1173. "node ./tests/run-json-schema-to-grammar.mjs test-json-schema-input.tmp > test-grammar-output.tmp") == 0 ? SUCCESS : FAILURE);
  1174. tc.verify(read("test-grammar-output.tmp"));
  1175. });
  1176. } else {
  1177. fprintf(stderr, "\033[33mWARNING: Node not found, skipping JavaScript JSON schema -> grammar tests.\n\033[0m");
  1178. }
  1179. test_all("Check Expectations Validity", [](const TestCase & tc) {
  1180. if (tc.expected_status == SUCCESS) {
  1181. tc.verify_expectation_parseable();
  1182. }
  1183. });
  1184. }