You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							350 lines
						
					
					
						
							13 KiB
						
					
					
				
			
		
		
	
	
							350 lines
						
					
					
						
							13 KiB
						
					
					
				| "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
 |