1
0

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

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