350 lines
13 KiB
JavaScript
350 lines
13 KiB
JavaScript
|
"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(self.opts.uriResolver, "", 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
|