| "use strict"; |
| Object.defineProperty(exports, "__esModule", { value: true }); |
| const types_1 = require("./types"); |
| const __1 = require(".."); |
| const codegen_1 = require("../codegen"); |
| const ref_error_1 = require("../ref_error"); |
| const names_1 = require("../names"); |
| const code_1 = require("../../vocabularies/code"); |
| const ref_1 = require("../../vocabularies/jtd/ref"); |
| const type_1 = require("../../vocabularies/jtd/type"); |
| const parseJson_1 = require("../../runtime/parseJson"); |
| const util_1 = require("../util"); |
| const timestamp_1 = require("../../runtime/timestamp"); |
| const genParse = { |
| elements: parseElements, |
| values: parseValues, |
| discriminator: parseDiscriminator, |
| properties: parseProperties, |
| optionalProperties: parseProperties, |
| enum: parseEnum, |
| type: parseType, |
| ref: parseRef, |
| }; |
| function compileParser(sch, definitions) { |
| const _sch = __1.getCompilingSchema.call(this, sch); |
| if (_sch) |
| return _sch; |
| const { es5, lines } = this.opts.code; |
| const { ownProperties } = this.opts; |
| const gen = new codegen_1.CodeGen(this.scope, { es5, lines, ownProperties }); |
| const parseName = gen.scopeName("parse"); |
| const cxt = { |
| self: this, |
| gen, |
| schema: sch.schema, |
| schemaEnv: sch, |
| definitions, |
| data: names_1.default.data, |
| parseName, |
| char: gen.name("c"), |
| }; |
| let sourceCode; |
| try { |
| this._compilations.add(sch); |
| sch.parseName = parseName; |
| parserFunction(cxt); |
| gen.optimize(this.opts.code.optimize); |
| const parseFuncCode = gen.toString(); |
| sourceCode = `${gen.scopeRefs(names_1.default.scope)}return ${parseFuncCode}`; |
| const makeParse = new Function(`${names_1.default.scope}`, sourceCode); |
| const parse = makeParse(this.scope.get()); |
| this.scope.value(parseName, { ref: parse }); |
| sch.parse = parse; |
| } |
| catch (e) { |
| if (sourceCode) |
| this.logger.error("Error compiling parser, function code:", sourceCode); |
| delete sch.parse; |
| delete sch.parseName; |
| throw e; |
| } |
| finally { |
| this._compilations.delete(sch); |
| } |
| return sch; |
| } |
| exports.default = compileParser; |
| const undef = (0, codegen_1._) `undefined`; |
| function parserFunction(cxt) { |
| const { gen, parseName, char } = cxt; |
| gen.func(parseName, (0, codegen_1._) `${names_1.default.json}, ${names_1.default.jsonPos}, ${names_1.default.jsonPart}`, false, () => { |
| gen.let(names_1.default.data); |
| gen.let(char); |
| gen.assign((0, codegen_1._) `${parseName}.message`, undef); |
| gen.assign((0, codegen_1._) `${parseName}.position`, undef); |
| gen.assign(names_1.default.jsonPos, (0, codegen_1._) `${names_1.default.jsonPos} || 0`); |
| gen.const(names_1.default.jsonLen, (0, codegen_1._) `${names_1.default.json}.length`); |
| parseCode(cxt); |
| skipWhitespace(cxt); |
| gen.if(names_1.default.jsonPart, () => { |
| gen.assign((0, codegen_1._) `${parseName}.position`, names_1.default.jsonPos); |
| gen.return(names_1.default.data); |
| }); |
| gen.if((0, codegen_1._) `${names_1.default.jsonPos} === ${names_1.default.jsonLen}`, () => gen.return(names_1.default.data)); |
| jsonSyntaxError(cxt); |
| }); |
| } |
| function parseCode(cxt) { |
| let form; |
| for (const key of types_1.jtdForms) { |
| if (key in cxt.schema) { |
| form = key; |
| break; |
| } |
| } |
| if (form) |
| parseNullable(cxt, genParse[form]); |
| else |
| parseEmpty(cxt); |
| } |
| const parseBoolean = parseBooleanToken(true, parseBooleanToken(false, jsonSyntaxError)); |
| function parseNullable(cxt, parseForm) { |
| const { gen, schema, data } = cxt; |
| if (!schema.nullable) |
| return parseForm(cxt); |
| tryParseToken(cxt, "null", parseForm, () => gen.assign(data, null)); |
| } |
| function parseElements(cxt) { |
| const { gen, schema, data } = cxt; |
| parseToken(cxt, "["); |
| const ix = gen.let("i", 0); |
| gen.assign(data, (0, codegen_1._) `[]`); |
| parseItems(cxt, "]", () => { |
| const el = gen.let("el"); |
| parseCode({ ...cxt, schema: schema.elements, data: el }); |
| gen.assign((0, codegen_1._) `${data}[${ix}++]`, el); |
| }); |
| } |
| function parseValues(cxt) { |
| const { gen, schema, data } = cxt; |
| parseToken(cxt, "{"); |
| gen.assign(data, (0, codegen_1._) `{}`); |
| parseItems(cxt, "}", () => parseKeyValue(cxt, schema.values)); |
| } |
| function parseItems(cxt, endToken, block) { |
| tryParseItems(cxt, endToken, block); |
| parseToken(cxt, endToken); |
| } |
| function tryParseItems(cxt, endToken, block) { |
| const { gen } = cxt; |
| gen.for((0, codegen_1._) `;${names_1.default.jsonPos}<${names_1.default.jsonLen} && ${jsonSlice(1)}!==${endToken};`, () => { |
| block(); |
| tryParseToken(cxt, ",", () => gen.break(), hasItem); |
| }); |
| function hasItem() { |
| tryParseToken(cxt, endToken, () => { }, jsonSyntaxError); |
| } |
| } |
| function parseKeyValue(cxt, schema) { |
| const { gen } = cxt; |
| const key = gen.let("key"); |
| parseString({ ...cxt, data: key }); |
| parseToken(cxt, ":"); |
| parsePropertyValue(cxt, key, schema); |
| } |
| function parseDiscriminator(cxt) { |
| const { gen, data, schema } = cxt; |
| const { discriminator, mapping } = schema; |
| parseToken(cxt, "{"); |
| gen.assign(data, (0, codegen_1._) `{}`); |
| const startPos = gen.const("pos", names_1.default.jsonPos); |
| const value = gen.let("value"); |
| const tag = gen.let("tag"); |
| tryParseItems(cxt, "}", () => { |
| const key = gen.let("key"); |
| parseString({ ...cxt, data: key }); |
| parseToken(cxt, ":"); |
| gen.if((0, codegen_1._) `${key} === ${discriminator}`, () => { |
| parseString({ ...cxt, data: tag }); |
| gen.assign((0, codegen_1._) `${data}[${key}]`, tag); |
| gen.break(); |
| }, () => parseEmpty({ ...cxt, data: value }) // can be discarded/skipped |
| ); |
| }); |
| gen.assign(names_1.default.jsonPos, startPos); |
| gen.if((0, codegen_1._) `${tag} === undefined`); |
| parsingError(cxt, (0, codegen_1.str) `discriminator tag not found`); |
| for (const tagValue in mapping) { |
| gen.elseIf((0, codegen_1._) `${tag} === ${tagValue}`); |
| parseSchemaProperties({ ...cxt, schema: mapping[tagValue] }, discriminator); |
| } |
| gen.else(); |
| parsingError(cxt, (0, codegen_1.str) `discriminator value not in schema`); |
| gen.endIf(); |
| } |
| function parseProperties(cxt) { |
| const { gen, data } = cxt; |
| parseToken(cxt, "{"); |
| gen.assign(data, (0, codegen_1._) `{}`); |
| parseSchemaProperties(cxt); |
| } |
| function parseSchemaProperties(cxt, discriminator) { |
| const { gen, schema, data } = cxt; |
| const { properties, optionalProperties, additionalProperties } = schema; |
| parseItems(cxt, "}", () => { |
| const key = gen.let("key"); |
| parseString({ ...cxt, data: key }); |
| parseToken(cxt, ":"); |
| gen.if(false); |
| parseDefinedProperty(cxt, key, properties); |
| parseDefinedProperty(cxt, key, optionalProperties); |
| if (discriminator) { |
| gen.elseIf((0, codegen_1._) `${key} === ${discriminator}`); |
| const tag = gen.let("tag"); |
| parseString({ ...cxt, data: tag }); // can be discarded, it is already assigned |
| } |
| gen.else(); |
| if (additionalProperties) { |
| parseEmpty({ ...cxt, data: (0, codegen_1._) `${data}[${key}]` }); |
| } |
| else { |
| parsingError(cxt, (0, codegen_1.str) `property ${key} not allowed`); |
| } |
| gen.endIf(); |
| }); |
| if (properties) { |
| const hasProp = (0, code_1.hasPropFunc)(gen); |
| const allProps = (0, codegen_1.and)(...Object.keys(properties).map((p) => (0, codegen_1._) `${hasProp}.call(${data}, ${p})`)); |
| gen.if((0, codegen_1.not)(allProps), () => parsingError(cxt, (0, codegen_1.str) `missing required properties`)); |
| } |
| } |
| function parseDefinedProperty(cxt, key, schemas = {}) { |
| const { gen } = cxt; |
| for (const prop in schemas) { |
| gen.elseIf((0, codegen_1._) `${key} === ${prop}`); |
| parsePropertyValue(cxt, key, schemas[prop]); |
| } |
| } |
| function parsePropertyValue(cxt, key, schema) { |
| parseCode({ ...cxt, schema, data: (0, codegen_1._) `${cxt.data}[${key}]` }); |
| } |
| function parseType(cxt) { |
| const { gen, schema, data, self } = cxt; |
| switch (schema.type) { |
| case "boolean": |
| parseBoolean(cxt); |
| break; |
| case "string": |
| parseString(cxt); |
| break; |
| case "timestamp": { |
| parseString(cxt); |
| const vts = (0, util_1.useFunc)(gen, timestamp_1.default); |
| const { allowDate, parseDate } = self.opts; |
| const notValid = allowDate ? (0, codegen_1._) `!${vts}(${data}, true)` : (0, codegen_1._) `!${vts}(${data})`; |
| const fail = parseDate |
| ? (0, codegen_1.or)(notValid, (0, codegen_1._) `(${data} = new Date(${data}), false)`, (0, codegen_1._) `isNaN(${data}.valueOf())`) |
| : notValid; |
| gen.if(fail, () => parsingError(cxt, (0, codegen_1.str) `invalid timestamp`)); |
| break; |
| } |
| case "float32": |
| case "float64": |
| parseNumber(cxt); |
| break; |
| default: { |
| const t = schema.type; |
| if (!self.opts.int32range && (t === "int32" || t === "uint32")) { |
| parseNumber(cxt, 16); // 2 ** 53 - max safe integer |
| if (t === "uint32") { |
| gen.if((0, codegen_1._) `${data} < 0`, () => parsingError(cxt, (0, codegen_1.str) `integer out of range`)); |
| } |
| } |
| else { |
| const [min, max, maxDigits] = type_1.intRange[t]; |
| parseNumber(cxt, maxDigits); |
| gen.if((0, codegen_1._) `${data} < ${min} || ${data} > ${max}`, () => parsingError(cxt, (0, codegen_1.str) `integer out of range`)); |
| } |
| } |
| } |
| } |
| function parseString(cxt) { |
| parseToken(cxt, '"'); |
| parseWith(cxt, parseJson_1.parseJsonString); |
| } |
| function parseEnum(cxt) { |
| const { gen, data, schema } = cxt; |
| const enumSch = schema.enum; |
| parseToken(cxt, '"'); |
| // TODO loopEnum |
| gen.if(false); |
| for (const value of enumSch) { |
| const valueStr = JSON.stringify(value).slice(1); // remove starting quote |
| gen.elseIf((0, codegen_1._) `${jsonSlice(valueStr.length)} === ${valueStr}`); |
| gen.assign(data, (0, codegen_1.str) `${value}`); |
| gen.add(names_1.default.jsonPos, valueStr.length); |
| } |
| gen.else(); |
| jsonSyntaxError(cxt); |
| gen.endIf(); |
| } |
| function parseNumber(cxt, maxDigits) { |
| const { gen } = cxt; |
| skipWhitespace(cxt); |
| gen.if((0, codegen_1._) `"-0123456789".indexOf(${jsonSlice(1)}) < 0`, () => jsonSyntaxError(cxt), () => parseWith(cxt, parseJson_1.parseJsonNumber, maxDigits)); |
| } |
| function parseBooleanToken(bool, fail) { |
| return (cxt) => { |
| const { gen, data } = cxt; |
| tryParseToken(cxt, `${bool}`, () => fail(cxt), () => gen.assign(data, bool)); |
| }; |
| } |
| function parseRef(cxt) { |
| const { gen, self, definitions, schema, schemaEnv } = cxt; |
| const { ref } = schema; |
| const refSchema = definitions[ref]; |
| if (!refSchema) |
| throw new ref_error_1.default("", ref, `No definition ${ref}`); |
| if (!(0, ref_1.hasRef)(refSchema)) |
| return parseCode({ ...cxt, schema: refSchema }); |
| const { root } = schemaEnv; |
| const sch = compileParser.call(self, new __1.SchemaEnv({ schema: refSchema, root }), definitions); |
| partialParse(cxt, getParser(gen, sch), true); |
| } |
| function getParser(gen, sch) { |
| return sch.parse |
| ? gen.scopeValue("parse", { ref: sch.parse }) |
| : (0, codegen_1._) `${gen.scopeValue("wrapper", { ref: sch })}.parse`; |
| } |
| function parseEmpty(cxt) { |
| parseWith(cxt, parseJson_1.parseJson); |
| } |
| function parseWith(cxt, parseFunc, args) { |
| partialParse(cxt, (0, util_1.useFunc)(cxt.gen, parseFunc), args); |
| } |
| function partialParse(cxt, parseFunc, args) { |
| const { gen, data } = cxt; |
| gen.assign(data, (0, codegen_1._) `${parseFunc}(${names_1.default.json}, ${names_1.default.jsonPos}${args ? (0, codegen_1._) `, ${args}` : codegen_1.nil})`); |
| gen.assign(names_1.default.jsonPos, (0, codegen_1._) `${parseFunc}.position`); |
| gen.if((0, codegen_1._) `${data} === undefined`, () => parsingError(cxt, (0, codegen_1._) `${parseFunc}.message`)); |
| } |
| function parseToken(cxt, tok) { |
| tryParseToken(cxt, tok, jsonSyntaxError); |
| } |
| function tryParseToken(cxt, tok, fail, success) { |
| const { gen } = cxt; |
| const n = tok.length; |
| skipWhitespace(cxt); |
| gen.if((0, codegen_1._) `${jsonSlice(n)} === ${tok}`, () => { |
| gen.add(names_1.default.jsonPos, n); |
| success === null || success === void 0 ? void 0 : success(cxt); |
| }, () => fail(cxt)); |
| } |
| function skipWhitespace({ gen, char: c }) { |
| gen.code((0, codegen_1._) `while((${c}=${names_1.default.json}[${names_1.default.jsonPos}],${c}===" "||${c}==="\\n"||${c}==="\\r"||${c}==="\\t"))${names_1.default.jsonPos}++;`); |
| } |
| function jsonSlice(len) { |
| return len === 1 |
| ? (0, codegen_1._) `${names_1.default.json}[${names_1.default.jsonPos}]` |
| : (0, codegen_1._) `${names_1.default.json}.slice(${names_1.default.jsonPos}, ${names_1.default.jsonPos}+${len})`; |
| } |
| function jsonSyntaxError(cxt) { |
| parsingError(cxt, (0, codegen_1._) `"unexpected token " + ${names_1.default.json}[${names_1.default.jsonPos}]`); |
| } |
| function parsingError({ gen, parseName }, msg) { |
| gen.assign((0, codegen_1._) `${parseName}.message`, msg); |
| gen.assign((0, codegen_1._) `${parseName}.position`, names_1.default.jsonPos); |
| gen.return(undef); |
| } |
| //# sourceMappingURL=parse.js.map |