Spaces:
Build error
Build error
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; | |
} | |
} | |