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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842
  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. null ::= "null" space
  96. number ::= ("-"? ([0-9] | [1-9] [0-9]*)) ("." [0-9]+)? ([eE] [-+]? [0-9]+)? space
  97. object ::= "{" space ( string ":" space value ("," space string ":" space value)* )? "}" space
  98. root ::= object
  99. space ::= " "?
  100. string ::= "\"" (
  101. [^"\\] |
  102. "\\" (["\\/bfnrt] | "u" [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F])
  103. )* "\"" space
  104. value ::= object | array | string | number | boolean
  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 date-string "," space uuid "," space time-string "," space date-time-string "]" 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. 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
  128. )"""
  129. });
  130. test({
  131. SUCCESS,
  132. "string",
  133. R"""({
  134. "type": "string"
  135. })""",
  136. R"""(
  137. root ::= "\"" (
  138. [^"\\] |
  139. "\\" (["\\/bfnrt] | "u" [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F])
  140. )* "\"" space
  141. space ::= " "?
  142. )"""
  143. });
  144. test({
  145. SUCCESS,
  146. "boolean",
  147. R"""({
  148. "type": "boolean"
  149. })""",
  150. R"""(
  151. root ::= ("true" | "false") space
  152. space ::= " "?
  153. )"""
  154. });
  155. test({
  156. SUCCESS,
  157. "integer",
  158. R"""({
  159. "type": "integer"
  160. })""",
  161. R"""(
  162. root ::= ("-"? ([0-9] | [1-9] [0-9]*)) space
  163. space ::= " "?
  164. )"""
  165. });
  166. test({
  167. SUCCESS,
  168. "string const",
  169. R"""({
  170. "const": "foo"
  171. })""",
  172. R"""(
  173. root ::= "\"foo\""
  174. space ::= " "?
  175. )"""
  176. });
  177. test({
  178. SUCCESS,
  179. "non-string const",
  180. R"""({
  181. "const": 123
  182. })""",
  183. R"""(
  184. root ::= "123"
  185. space ::= " "?
  186. )"""
  187. });
  188. test({
  189. SUCCESS,
  190. "non-string enum",
  191. R"""({
  192. "enum": ["red", "amber", "green", null, 42, ["foo"]]
  193. })""",
  194. R"""(
  195. root ::= "\"red\"" | "\"amber\"" | "\"green\"" | "null" | "42" | "[\"foo\"]"
  196. space ::= " "?
  197. )"""
  198. });
  199. test({
  200. SUCCESS,
  201. "tuple1",
  202. R"""({
  203. "prefixItems": [{ "type": "string" }]
  204. })""",
  205. R"""(
  206. root ::= "[" space string "]" space
  207. space ::= " "?
  208. string ::= "\"" (
  209. [^"\\] |
  210. "\\" (["\\/bfnrt] | "u" [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F])
  211. )* "\"" space
  212. )"""
  213. });
  214. test({
  215. SUCCESS,
  216. "tuple2",
  217. R"""({
  218. "prefixItems": [{ "type": "string" }, { "type": "number" }]
  219. })""",
  220. R"""(
  221. number ::= ("-"? ([0-9] | [1-9] [0-9]*)) ("." [0-9]+)? ([eE] [-+]? [0-9]+)? space
  222. root ::= "[" space string "," space number "]" space
  223. space ::= " "?
  224. string ::= "\"" (
  225. [^"\\] |
  226. "\\" (["\\/bfnrt] | "u" [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F])
  227. )* "\"" space
  228. )"""
  229. });
  230. test({
  231. SUCCESS,
  232. "number",
  233. R"""({
  234. "type": "number"
  235. })""",
  236. R"""(
  237. root ::= ("-"? ([0-9] | [1-9] [0-9]*)) ("." [0-9]+)? ([eE] [-+]? [0-9]+)? space
  238. space ::= " "?
  239. )"""
  240. });
  241. test({
  242. SUCCESS,
  243. "minItems",
  244. R"""({
  245. "items": {
  246. "type": "boolean"
  247. },
  248. "minItems": 2
  249. })""",
  250. R"""(
  251. boolean ::= ("true" | "false") space
  252. root ::= "[" space boolean ( "," space boolean )( "," space boolean )* "]" space
  253. space ::= " "?
  254. )"""
  255. });
  256. test({
  257. SUCCESS,
  258. "maxItems 1",
  259. R"""({
  260. "items": {
  261. "type": "boolean"
  262. },
  263. "maxItems": 1
  264. })""",
  265. R"""(
  266. boolean ::= ("true" | "false") space
  267. root ::= "[" space ( boolean )? "]" space
  268. space ::= " "?
  269. )"""
  270. });
  271. test({
  272. SUCCESS,
  273. "maxItems 2",
  274. R"""({
  275. "items": {
  276. "type": "boolean"
  277. },
  278. "maxItems": 2
  279. })""",
  280. R"""(
  281. boolean ::= ("true" | "false") space
  282. root ::= "[" space ( boolean ( "," space boolean )? )? "]" space
  283. space ::= " "?
  284. )"""
  285. });
  286. test({
  287. SUCCESS,
  288. "min + maxItems",
  289. R"""({
  290. "items": {
  291. "type": ["number", "integer"]
  292. },
  293. "minItems": 3,
  294. "maxItems": 5
  295. })""",
  296. R"""(
  297. integer ::= ("-"? ([0-9] | [1-9] [0-9]*)) space
  298. item ::= number | integer
  299. number ::= ("-"? ([0-9] | [1-9] [0-9]*)) ("." [0-9]+)? ([eE] [-+]? [0-9]+)? space
  300. root ::= "[" space item ( "," space item )( "," space item )( "," space item )?( "," space item )? "]" space
  301. space ::= " "?
  302. )"""
  303. });
  304. test({
  305. SUCCESS,
  306. "simple regexp",
  307. R"""({
  308. "type": "string",
  309. "pattern": "^abc?d*efg+(hij)?kl$"
  310. })""",
  311. R"""(
  312. root ::= "\"" "ab" "c"? "d"* "ef" "g"+ ("hij")? "kl" "\"" space
  313. space ::= " "?
  314. )"""
  315. });
  316. test({
  317. SUCCESS,
  318. "regexp escapes",
  319. R"""({
  320. "type": "string",
  321. "pattern": "^\\[\\]\\{\\}\\(\\)\\|\\+\\*\\?$"
  322. })""",
  323. R"""(
  324. root ::= "\"" "[]{}()|+*?" "\"" space
  325. space ::= " "?
  326. )"""
  327. });
  328. test({
  329. SUCCESS,
  330. "regexp quote",
  331. R"""({
  332. "type": "string",
  333. "pattern": "^\"$"
  334. })""",
  335. R"""(
  336. root ::= "\"" "\"" "\"" space
  337. space ::= " "?
  338. )"""
  339. });
  340. test({
  341. SUCCESS,
  342. "regexp",
  343. R"""({
  344. "type": "string",
  345. "pattern": "^(\\([0-9]{1,3}\\))?[0-9]{3}-[0-9]{4} and...$"
  346. })""",
  347. R"""(
  348. dot ::= [\U00000000-\x09\x0B\x0C\x0E-\U0010FFFF]
  349. root ::= "\"" ("(" root-1 root-1? root-1? ")")? root-1 root-1 root-1 "-" root-1 root-1 root-1 root-1 " and" dot dot dot "\"" space
  350. root-1 ::= [0-9]
  351. space ::= " "?
  352. )"""
  353. });
  354. test({
  355. SUCCESS,
  356. "required props in original order",
  357. R"""({
  358. "type": "object",
  359. "properties": {
  360. "b": {"type": "string"},
  361. "c": {"type": "string"},
  362. "a": {"type": "string"}
  363. },
  364. "required": [
  365. "a",
  366. "b",
  367. "c"
  368. ],
  369. "additionalProperties": false,
  370. "definitions": {}
  371. })""",
  372. R"""(
  373. a-kv ::= "\"a\"" space ":" space string
  374. b-kv ::= "\"b\"" space ":" space string
  375. c-kv ::= "\"c\"" space ":" space string
  376. root ::= "{" space b-kv "," space c-kv "," space a-kv "}" space
  377. space ::= " "?
  378. string ::= "\"" (
  379. [^"\\] |
  380. "\\" (["\\/bfnrt] | "u" [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F])
  381. )* "\"" space
  382. )"""
  383. });
  384. test({
  385. SUCCESS,
  386. "1 optional prop",
  387. R"""({
  388. "properties": {
  389. "a": {
  390. "type": "string"
  391. }
  392. },
  393. "additionalProperties": false
  394. })""",
  395. R"""(
  396. a-kv ::= "\"a\"" space ":" space string
  397. root ::= "{" space (a-kv )? "}" space
  398. space ::= " "?
  399. string ::= "\"" (
  400. [^"\\] |
  401. "\\" (["\\/bfnrt] | "u" [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F])
  402. )* "\"" space
  403. )"""
  404. });
  405. test({
  406. SUCCESS,
  407. "N optional props",
  408. R"""({
  409. "properties": {
  410. "a": {"type": "string"},
  411. "b": {"type": "string"},
  412. "c": {"type": "string"}
  413. },
  414. "additionalProperties": false
  415. })""",
  416. R"""(
  417. a-kv ::= "\"a\"" space ":" space string
  418. a-rest ::= ( "," space b-kv )? b-rest
  419. b-kv ::= "\"b\"" space ":" space string
  420. b-rest ::= ( "," space c-kv )?
  421. c-kv ::= "\"c\"" space ":" space string
  422. root ::= "{" space (a-kv a-rest | b-kv b-rest | c-kv )? "}" space
  423. space ::= " "?
  424. string ::= "\"" (
  425. [^"\\] |
  426. "\\" (["\\/bfnrt] | "u" [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F])
  427. )* "\"" space
  428. )"""
  429. });
  430. test({
  431. SUCCESS,
  432. "required + optional props each in original order",
  433. R"""({
  434. "properties": {
  435. "b": {"type": "string"},
  436. "a": {"type": "string"},
  437. "d": {"type": "string"},
  438. "c": {"type": "string"}
  439. },
  440. "required": ["a", "b"],
  441. "additionalProperties": false
  442. })""",
  443. R"""(
  444. a-kv ::= "\"a\"" space ":" space string
  445. b-kv ::= "\"b\"" space ":" space string
  446. c-kv ::= "\"c\"" space ":" space string
  447. d-kv ::= "\"d\"" space ":" space string
  448. d-rest ::= ( "," space c-kv )?
  449. root ::= "{" space b-kv "," space a-kv ( "," space ( d-kv d-rest | c-kv ) )? "}" space
  450. space ::= " "?
  451. string ::= "\"" (
  452. [^"\\] |
  453. "\\" (["\\/bfnrt] | "u" [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F])
  454. )* "\"" space
  455. )"""
  456. });
  457. test({
  458. SUCCESS,
  459. "additional props",
  460. R"""({
  461. "type": "object",
  462. "additionalProperties": {"type": "array", "items": {"type": "number"}}
  463. })""",
  464. R"""(
  465. additional-kv ::= string ":" space additional-value
  466. additional-kvs ::= additional-kv ( "," space additional-kv )*
  467. additional-value ::= "[" space ( number ( "," space number )* )? "]" space
  468. number ::= ("-"? ([0-9] | [1-9] [0-9]*)) ("." [0-9]+)? ([eE] [-+]? [0-9]+)? space
  469. root ::= "{" space (additional-kvs )? "}" space
  470. space ::= " "?
  471. string ::= "\"" (
  472. [^"\\] |
  473. "\\" (["\\/bfnrt] | "u" [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F])
  474. )* "\"" space
  475. )"""
  476. });
  477. test({
  478. SUCCESS,
  479. "additional props (true)",
  480. R"""({
  481. "type": "object",
  482. "additionalProperties": true
  483. })""",
  484. R"""(
  485. array ::= "[" space ( value ("," space value)* )? "]" space
  486. boolean ::= ("true" | "false") space
  487. null ::= "null" space
  488. number ::= ("-"? ([0-9] | [1-9] [0-9]*)) ("." [0-9]+)? ([eE] [-+]? [0-9]+)? space
  489. object ::= "{" space ( string ":" space value ("," space string ":" space value)* )? "}" space
  490. root ::= object
  491. space ::= " "?
  492. string ::= "\"" (
  493. [^"\\] |
  494. "\\" (["\\/bfnrt] | "u" [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F])
  495. )* "\"" space
  496. value ::= object | array | string | number | boolean
  497. )"""
  498. });
  499. test({
  500. SUCCESS,
  501. "additional props (implicit)",
  502. R"""({
  503. "type": "object"
  504. })""",
  505. R"""(
  506. array ::= "[" space ( value ("," space value)* )? "]" space
  507. boolean ::= ("true" | "false") space
  508. null ::= "null" space
  509. number ::= ("-"? ([0-9] | [1-9] [0-9]*)) ("." [0-9]+)? ([eE] [-+]? [0-9]+)? space
  510. object ::= "{" space ( string ":" space value ("," space string ":" space value)* )? "}" space
  511. root ::= object
  512. space ::= " "?
  513. string ::= "\"" (
  514. [^"\\] |
  515. "\\" (["\\/bfnrt] | "u" [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F])
  516. )* "\"" space
  517. value ::= object | array | string | number | boolean
  518. )"""
  519. });
  520. test({
  521. SUCCESS,
  522. "empty w/o additional props",
  523. R"""({
  524. "type": "object",
  525. "additionalProperties": false
  526. })""",
  527. R"""(
  528. root ::= "{" space "}" space
  529. space ::= " "?
  530. )"""
  531. });
  532. test({
  533. SUCCESS,
  534. "required + additional props",
  535. R"""({
  536. "type": "object",
  537. "properties": {
  538. "a": {"type": "number"}
  539. },
  540. "required": ["a"],
  541. "additionalProperties": {"type": "string"}
  542. })""",
  543. R"""(
  544. a-kv ::= "\"a\"" space ":" space number
  545. additional-kv ::= string ":" space string
  546. additional-kvs ::= additional-kv ( "," space additional-kv )*
  547. number ::= ("-"? ([0-9] | [1-9] [0-9]*)) ("." [0-9]+)? ([eE] [-+]? [0-9]+)? space
  548. root ::= "{" space a-kv ( "," space ( additional-kvs ) )? "}" space
  549. space ::= " "?
  550. string ::= "\"" (
  551. [^"\\] |
  552. "\\" (["\\/bfnrt] | "u" [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F])
  553. )* "\"" space
  554. )"""
  555. });
  556. test({
  557. SUCCESS,
  558. "optional + additional props",
  559. R"""({
  560. "type": "object",
  561. "properties": {
  562. "a": {"type": "number"}
  563. },
  564. "additionalProperties": {"type": "number"}
  565. })""",
  566. R"""(
  567. a-kv ::= "\"a\"" space ":" space number
  568. a-rest ::= additional-kvs
  569. additional-kv ::= string ":" space number
  570. additional-kvs ::= additional-kv ( "," space additional-kv )*
  571. number ::= ("-"? ([0-9] | [1-9] [0-9]*)) ("." [0-9]+)? ([eE] [-+]? [0-9]+)? space
  572. root ::= "{" space (a-kv a-rest | additional-kvs )? "}" space
  573. space ::= " "?
  574. string ::= "\"" (
  575. [^"\\] |
  576. "\\" (["\\/bfnrt] | "u" [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F])
  577. )* "\"" space
  578. )"""
  579. });
  580. test({
  581. SUCCESS,
  582. "required + optional + additional props",
  583. R"""({
  584. "type": "object",
  585. "properties": {
  586. "a": {"type": "number"},
  587. "b": {"type": "number"}
  588. },
  589. "required": ["a"],
  590. "additionalProperties": {"type": "number"}
  591. })""",
  592. R"""(
  593. a-kv ::= "\"a\"" space ":" space number
  594. additional-kv ::= string ":" space number
  595. additional-kvs ::= additional-kv ( "," space additional-kv )*
  596. b-kv ::= "\"b\"" space ":" space number
  597. b-rest ::= additional-kvs
  598. number ::= ("-"? ([0-9] | [1-9] [0-9]*)) ("." [0-9]+)? ([eE] [-+]? [0-9]+)? space
  599. root ::= "{" space a-kv ( "," space ( b-kv b-rest | additional-kvs ) )? "}" space
  600. space ::= " "?
  601. string ::= "\"" (
  602. [^"\\] |
  603. "\\" (["\\/bfnrt] | "u" [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F])
  604. )* "\"" space
  605. )"""
  606. });
  607. test({
  608. SUCCESS,
  609. "top-level $ref",
  610. R"""({
  611. "$ref": "#/definitions/MyType",
  612. "definitions": {
  613. "MyType": {
  614. "type": "object",
  615. "properties": {
  616. "a": {
  617. "type": "string"
  618. }
  619. },
  620. "required": [
  621. "a"
  622. ],
  623. "additionalProperties": false
  624. }
  625. }
  626. })""",
  627. R"""(
  628. MyType ::= "{" space MyType-a-kv "}" space
  629. MyType-a-kv ::= "\"a\"" space ":" space string
  630. root ::= MyType
  631. space ::= " "?
  632. string ::= "\"" (
  633. [^"\\] |
  634. "\\" (["\\/bfnrt] | "u" [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F])
  635. )* "\"" space
  636. )"""
  637. });
  638. test({
  639. SUCCESS,
  640. "anyOf",
  641. R"""({
  642. "anyOf": [
  643. {"$ref": "#/definitions/foo"},
  644. {"$ref": "#/definitions/bar"}
  645. ],
  646. "definitions": {
  647. "foo": {
  648. "properties": {"a": {"type": "number"}}
  649. },
  650. "bar": {
  651. "properties": {"b": {"type": "number"}}
  652. }
  653. },
  654. "type": "object"
  655. })""",
  656. R"""(
  657. alternative-0 ::= foo
  658. alternative-1 ::= bar
  659. bar ::= "{" space (bar-b-kv )? "}" space
  660. bar-b-kv ::= "\"b\"" space ":" space number
  661. foo ::= "{" space (foo-a-kv )? "}" space
  662. foo-a-kv ::= "\"a\"" space ":" space number
  663. number ::= ("-"? ([0-9] | [1-9] [0-9]*)) ("." [0-9]+)? ([eE] [-+]? [0-9]+)? space
  664. root ::= alternative-0 | alternative-1
  665. space ::= " "?
  666. )"""
  667. });
  668. test({
  669. SUCCESS,
  670. "mix of allOf, anyOf and $ref (similar to https://json.schemastore.org/tsconfig.json)",
  671. R"""({
  672. "allOf": [
  673. {"$ref": "#/definitions/foo"},
  674. {"$ref": "#/definitions/bar"},
  675. {
  676. "anyOf": [
  677. {"$ref": "#/definitions/baz"},
  678. {"$ref": "#/definitions/bam"}
  679. ]
  680. }
  681. ],
  682. "definitions": {
  683. "foo": {
  684. "properties": {"a": {"type": "number"}}
  685. },
  686. "bar": {
  687. "properties": {"b": {"type": "number"}}
  688. },
  689. "bam": {
  690. "properties": {"c": {"type": "number"}}
  691. },
  692. "baz": {
  693. "properties": {"d": {"type": "number"}}
  694. }
  695. },
  696. "type": "object"
  697. })""",
  698. R"""(
  699. a-kv ::= "\"a\"" space ":" space number
  700. b-kv ::= "\"b\"" space ":" space number
  701. c-kv ::= "\"c\"" space ":" space number
  702. d-kv ::= "\"d\"" space ":" space number
  703. d-rest ::= ( "," space c-kv )?
  704. number ::= ("-"? ([0-9] | [1-9] [0-9]*)) ("." [0-9]+)? ([eE] [-+]? [0-9]+)? space
  705. root ::= "{" space a-kv "," space b-kv ( "," space ( d-kv d-rest | c-kv ) )? "}" space
  706. space ::= " "?
  707. )"""
  708. });
  709. test({
  710. SUCCESS,
  711. "conflicting names",
  712. R"""({
  713. "type": "object",
  714. "properties": {
  715. "number": {
  716. "type": "object",
  717. "properties": {
  718. "number": {
  719. "type": "object",
  720. "properties": {
  721. "root": {
  722. "type": "number"
  723. }
  724. },
  725. "required": [
  726. "root"
  727. ],
  728. "additionalProperties": false
  729. }
  730. },
  731. "required": [
  732. "number"
  733. ],
  734. "additionalProperties": false
  735. }
  736. },
  737. "required": [
  738. "number"
  739. ],
  740. "additionalProperties": false,
  741. "definitions": {}
  742. })""",
  743. R"""(
  744. number ::= ("-"? ([0-9] | [1-9] [0-9]*)) ("." [0-9]+)? ([eE] [-+]? [0-9]+)? space
  745. number- ::= "{" space number-number-kv "}" space
  746. number-kv ::= "\"number\"" space ":" space number-
  747. number-number ::= "{" space number-number-root-kv "}" space
  748. number-number-kv ::= "\"number\"" space ":" space number-number
  749. number-number-root-kv ::= "\"root\"" space ":" space number
  750. root ::= "{" space number-kv "}" space
  751. space ::= " "?
  752. )"""
  753. });
  754. }
  755. int main() {
  756. fprintf(stderr, "LLAMA_NODE_AVAILABLE = %s\n", getenv("LLAMA_NODE_AVAILABLE") ? "true" : "false");
  757. fprintf(stderr, "LLAMA_PYTHON_AVAILABLE = %s\n", getenv("LLAMA_PYTHON_AVAILABLE") ? "true" : "false");
  758. test_all("C++", [](const TestCase & tc) {
  759. try {
  760. tc.verify(json_schema_to_grammar(nlohmann::ordered_json::parse(tc.schema)));
  761. tc.verify_status(SUCCESS);
  762. } catch (const std::runtime_error & ex) {
  763. fprintf(stderr, "Error: %s\n", ex.what());
  764. tc.verify_status(FAILURE);
  765. }
  766. });
  767. if (getenv("LLAMA_PYTHON_AVAILABLE") || (std::system("python --version") == 0)) {
  768. test_all("Python", [](const TestCase & tc) {
  769. write("test-json-schema-input.tmp", tc.schema);
  770. tc.verify_status(std::system(
  771. "python ./examples/json-schema-to-grammar.py test-json-schema-input.tmp > test-grammar-output.tmp") == 0 ? SUCCESS : FAILURE);
  772. tc.verify(read("test-grammar-output.tmp"));
  773. });
  774. } else {
  775. fprintf(stderr, "\033[33mWARNING: Python not found, skipping Python JSON schema -> grammar tests.\n\033[0m");
  776. }
  777. if (getenv("LLAMA_NODE_AVAILABLE") || (std::system("node --version") == 0)) {
  778. test_all("JavaScript", [](const TestCase & tc) {
  779. write("test-json-schema-input.tmp", tc.schema);
  780. tc.verify_status(std::system(
  781. "node ./tests/run-json-schema-to-grammar.mjs test-json-schema-input.tmp > test-grammar-output.tmp") == 0 ? SUCCESS : FAILURE);
  782. tc.verify(read("test-grammar-output.tmp"));
  783. });
  784. } else {
  785. fprintf(stderr, "\033[33mWARNING: Node not found, skipping JavaScript JSON schema -> grammar tests.\n\033[0m");
  786. }
  787. test_all("Check Expectations Validity", [](const TestCase & tc) {
  788. if (tc.expected_status == SUCCESS) {
  789. tc.verify_expectation_parseable();
  790. }
  791. });
  792. }