json-schema-to-grammar.mjs.hpp 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115
  1. const char json_schema_to_grammar_mjs[] = R"LITERAL(
  2. const SPACE_RULE = '" "?';
  3. const PRIMITIVE_RULES = {
  4. boolean: '("true" | "false") space',
  5. number: '("-"? ([0-9] | [1-9] [0-9]*)) ("." [0-9]+)? ([eE] [-+]? [0-9]+)? space',
  6. integer: '("-"? ([0-9] | [1-9] [0-9]*)) space',
  7. string: ` "\\"" (
  8. [^"\\\\] |
  9. "\\\\" (["\\\\/bfnrt] | "u" [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F])
  10. )* "\\"" space`,
  11. null: '"null" space',
  12. };
  13. const INVALID_RULE_CHARS_RE = /[^\dA-Za-z-]+/g;
  14. const GRAMMAR_LITERAL_ESCAPE_RE = /[\n\r"]/g;
  15. const GRAMMAR_LITERAL_ESCAPES = {'\r': '\\r', '\n': '\\n', '"': '\\"'};
  16. export class SchemaConverter {
  17. constructor(propOrder) {
  18. this._propOrder = propOrder || {};
  19. this._rules = new Map();
  20. this._rules.set('space', SPACE_RULE);
  21. }
  22. _formatLiteral(literal) {
  23. const escaped = JSON.stringify(literal).replace(
  24. GRAMMAR_LITERAL_ESCAPE_RE,
  25. m => GRAMMAR_LITERAL_ESCAPES[m]
  26. );
  27. return `"${escaped}"`;
  28. }
  29. _addRule(name, rule) {
  30. let escName = name.replace(INVALID_RULE_CHARS_RE, '-');
  31. let key = escName;
  32. if (this._rules.has(escName)) {
  33. if (this._rules.get(escName) === rule) {
  34. return key;
  35. }
  36. let i = 0;
  37. while (this._rules.has(`${escName}${i}`)) {
  38. i += 1;
  39. }
  40. key = `${escName}${i}`;
  41. }
  42. this._rules.set(key, rule);
  43. return key;
  44. }
  45. visit(schema, name) {
  46. const schemaType = schema.type;
  47. const ruleName = name || 'root';
  48. if (schema.oneOf || schema.anyOf) {
  49. const rule = (schema.oneOf || schema.anyOf).map((altSchema, i) =>
  50. this.visit(altSchema, `${name}${name ? "-" : ""}${i}`)
  51. ).join(' | ');
  52. return this._addRule(ruleName, rule);
  53. } else if ('const' in schema) {
  54. return this._addRule(ruleName, this._formatLiteral(schema.const));
  55. } else if ('enum' in schema) {
  56. const rule = schema.enum.map(v => this._formatLiteral(v)).join(' | ');
  57. return this._addRule(ruleName, rule);
  58. } else if (schemaType === 'object' && 'properties' in schema) {
  59. // TODO: `required` keyword (from python implementation)
  60. const propOrder = this._propOrder;
  61. const propPairs = Object.entries(schema.properties).sort((a, b) => {
  62. // sort by position in prop_order (if specified) then by key
  63. const orderA = typeof propOrder[a[0]] === 'number' ? propOrder[a[0]] : Infinity;
  64. const orderB = typeof propOrder[b[0]] === 'number' ? propOrder[b[0]] : Infinity;
  65. return orderA - orderB || a[0].localeCompare(b[0]);
  66. });
  67. let rule = '"{" space';
  68. propPairs.forEach(([propName, propSchema], i) => {
  69. const propRuleName = this.visit(propSchema, `${name}${name ? "-" : ""}${propName}`);
  70. if (i > 0) {
  71. rule += ' "," space';
  72. }
  73. rule += ` ${this._formatLiteral(propName)} space ":" space ${propRuleName}`;
  74. });
  75. rule += ' "}" space';
  76. return this._addRule(ruleName, rule);
  77. } else if (schemaType === 'array' && 'items' in schema) {
  78. // TODO `prefixItems` keyword (from python implementation)
  79. const itemRuleName = this.visit(schema.items, `${name}${name ? "-" : ""}item`);
  80. const rule = `"[" space (${itemRuleName} ("," space ${itemRuleName})*)? "]" space`;
  81. return this._addRule(ruleName, rule);
  82. } else {
  83. if (!PRIMITIVE_RULES[schemaType]) {
  84. throw new Error(`Unrecognized schema: ${JSON.stringify(schema)}`);
  85. }
  86. return this._addRule(
  87. ruleName === 'root' ? 'root' : schemaType,
  88. PRIMITIVE_RULES[schemaType]
  89. );
  90. }
  91. }
  92. formatGrammar() {
  93. let grammar = '';
  94. this._rules.forEach((rule, name) => {
  95. grammar += `${name} ::= ${rule}\n`;
  96. });
  97. return grammar;
  98. }
  99. }
  100. )LITERAL";
  101. unsigned int json_schema_to_grammar_mjs_len = sizeof(json_schema_to_grammar_mjs);