| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112 |
- const SPACE_RULE = '" "?';
- const PRIMITIVE_RULES = {
- boolean: '("true" | "false") space',
- number: '("-"? ([0-9] | [1-9] [0-9]*)) ("." [0-9]+)? ([eE] [-+]? [0-9]+)? space',
- integer: '("-"? ([0-9] | [1-9] [0-9]*)) space',
- string: ` "\\"" (
- [^"\\\\] |
- "\\\\" (["\\\\/bfnrt] | "u" [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F])
- )* "\\"" space`,
- null: '"null" space',
- };
- const INVALID_RULE_CHARS_RE = /[^\dA-Za-z-]+/g;
- const GRAMMAR_LITERAL_ESCAPE_RE = /[\n\r"]/g;
- const GRAMMAR_LITERAL_ESCAPES = {'\r': '\\r', '\n': '\\n', '"': '\\"'};
- export class SchemaConverter {
- constructor(propOrder) {
- this._propOrder = propOrder || {};
- this._rules = new Map();
- this._rules.set('space', SPACE_RULE);
- }
- _formatLiteral(literal) {
- const escaped = JSON.stringify(literal).replace(
- GRAMMAR_LITERAL_ESCAPE_RE,
- m => GRAMMAR_LITERAL_ESCAPES[m]
- );
- return `"${escaped}"`;
- }
- _addRule(name, rule) {
- let escName = name.replace(INVALID_RULE_CHARS_RE, '-');
- let key = escName;
- if (this._rules.has(escName)) {
- if (this._rules.get(escName) === rule) {
- return key;
- }
- let i = 0;
- while (this._rules.has(`${escName}${i}`)) {
- i += 1;
- }
- key = `${escName}${i}`;
- }
- this._rules.set(key, rule);
- return key;
- }
- visit(schema, name) {
- const schemaType = schema.type;
- const ruleName = name || 'root';
- if (schema.oneOf || schema.anyOf) {
- const rule = (schema.oneOf || schema.anyOf).map((altSchema, i) =>
- this.visit(altSchema, `${name}${name ? "-" : ""}${i}`)
- ).join(' | ');
- return this._addRule(ruleName, rule);
- } else if ('const' in schema) {
- return this._addRule(ruleName, this._formatLiteral(schema.const));
- } else if ('enum' in schema) {
- const rule = schema.enum.map(v => this._formatLiteral(v)).join(' | ');
- return this._addRule(ruleName, rule);
- } else if (schemaType === 'object' && 'properties' in schema) {
- // TODO: `required` keyword (from python implementation)
- const propOrder = this._propOrder;
- const propPairs = Object.entries(schema.properties).sort((a, b) => {
- // sort by position in prop_order (if specified) then by key
- const orderA = typeof propOrder[a[0]] === 'number' ? propOrder[a[0]] : Infinity;
- const orderB = typeof propOrder[b[0]] === 'number' ? propOrder[b[0]] : Infinity;
- return orderA - orderB || a[0].localeCompare(b[0]);
- });
- let rule = '"{" space';
- propPairs.forEach(([propName, propSchema], i) => {
- const propRuleName = this.visit(propSchema, `${name}${name ? "-" : ""}${propName}`);
- if (i > 0) {
- rule += ' "," space';
- }
- rule += ` ${this._formatLiteral(propName)} space ":" space ${propRuleName}`;
- });
- rule += ' "}" space';
- return this._addRule(ruleName, rule);
- } else if (schemaType === 'array' && 'items' in schema) {
- // TODO `prefixItems` keyword (from python implementation)
- const itemRuleName = this.visit(schema.items, `${name}${name ? "-" : ""}item`);
- const rule = `"[" space (${itemRuleName} ("," space ${itemRuleName})*)? "]" space`;
- return this._addRule(ruleName, rule);
- } else {
- if (!PRIMITIVE_RULES[schemaType]) {
- throw new Error(`Unrecognized schema: ${JSON.stringify(schema)}`);
- }
- return this._addRule(
- ruleName === 'root' ? 'root' : schemaType,
- PRIMITIVE_RULES[schemaType]
- );
- }
- }
- formatGrammar() {
- let grammar = '';
- this._rules.forEach((rule, name) => {
- grammar += `${name} ::= ${rule}\n`;
- });
- return grammar;
- }
- }
|