parse.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. const types_1 = require("./types");
  4. const __1 = require("..");
  5. const codegen_1 = require("../codegen");
  6. const ref_error_1 = require("../ref_error");
  7. const names_1 = require("../names");
  8. const code_1 = require("../../vocabularies/code");
  9. const ref_1 = require("../../vocabularies/jtd/ref");
  10. const type_1 = require("../../vocabularies/jtd/type");
  11. const parseJson_1 = require("../../runtime/parseJson");
  12. const util_1 = require("../util");
  13. const timestamp_1 = require("../../runtime/timestamp");
  14. const genParse = {
  15. elements: parseElements,
  16. values: parseValues,
  17. discriminator: parseDiscriminator,
  18. properties: parseProperties,
  19. optionalProperties: parseProperties,
  20. enum: parseEnum,
  21. type: parseType,
  22. ref: parseRef,
  23. };
  24. function compileParser(sch, definitions) {
  25. const _sch = __1.getCompilingSchema.call(this, sch);
  26. if (_sch)
  27. return _sch;
  28. const { es5, lines } = this.opts.code;
  29. const { ownProperties } = this.opts;
  30. const gen = new codegen_1.CodeGen(this.scope, { es5, lines, ownProperties });
  31. const parseName = gen.scopeName("parse");
  32. const cxt = {
  33. self: this,
  34. gen,
  35. schema: sch.schema,
  36. schemaEnv: sch,
  37. definitions,
  38. data: names_1.default.data,
  39. parseName,
  40. char: gen.name("c"),
  41. };
  42. let sourceCode;
  43. try {
  44. this._compilations.add(sch);
  45. sch.parseName = parseName;
  46. parserFunction(cxt);
  47. gen.optimize(this.opts.code.optimize);
  48. const parseFuncCode = gen.toString();
  49. sourceCode = `${gen.scopeRefs(names_1.default.scope)}return ${parseFuncCode}`;
  50. const makeParse = new Function(`${names_1.default.scope}`, sourceCode);
  51. const parse = makeParse(this.scope.get());
  52. this.scope.value(parseName, { ref: parse });
  53. sch.parse = parse;
  54. }
  55. catch (e) {
  56. if (sourceCode)
  57. this.logger.error("Error compiling parser, function code:", sourceCode);
  58. delete sch.parse;
  59. delete sch.parseName;
  60. throw e;
  61. }
  62. finally {
  63. this._compilations.delete(sch);
  64. }
  65. return sch;
  66. }
  67. exports.default = compileParser;
  68. const undef = (0, codegen_1._) `undefined`;
  69. function parserFunction(cxt) {
  70. const { gen, parseName, char } = cxt;
  71. gen.func(parseName, (0, codegen_1._) `${names_1.default.json}, ${names_1.default.jsonPos}, ${names_1.default.jsonPart}`, false, () => {
  72. gen.let(names_1.default.data);
  73. gen.let(char);
  74. gen.assign((0, codegen_1._) `${parseName}.message`, undef);
  75. gen.assign((0, codegen_1._) `${parseName}.position`, undef);
  76. gen.assign(names_1.default.jsonPos, (0, codegen_1._) `${names_1.default.jsonPos} || 0`);
  77. gen.const(names_1.default.jsonLen, (0, codegen_1._) `${names_1.default.json}.length`);
  78. parseCode(cxt);
  79. skipWhitespace(cxt);
  80. gen.if(names_1.default.jsonPart, () => {
  81. gen.assign((0, codegen_1._) `${parseName}.position`, names_1.default.jsonPos);
  82. gen.return(names_1.default.data);
  83. });
  84. gen.if((0, codegen_1._) `${names_1.default.jsonPos} === ${names_1.default.jsonLen}`, () => gen.return(names_1.default.data));
  85. jsonSyntaxError(cxt);
  86. });
  87. }
  88. function parseCode(cxt) {
  89. let form;
  90. for (const key of types_1.jtdForms) {
  91. if (key in cxt.schema) {
  92. form = key;
  93. break;
  94. }
  95. }
  96. if (form)
  97. parseNullable(cxt, genParse[form]);
  98. else
  99. parseEmpty(cxt);
  100. }
  101. const parseBoolean = parseBooleanToken(true, parseBooleanToken(false, jsonSyntaxError));
  102. function parseNullable(cxt, parseForm) {
  103. const { gen, schema, data } = cxt;
  104. if (!schema.nullable)
  105. return parseForm(cxt);
  106. tryParseToken(cxt, "null", parseForm, () => gen.assign(data, null));
  107. }
  108. function parseElements(cxt) {
  109. const { gen, schema, data } = cxt;
  110. parseToken(cxt, "[");
  111. const ix = gen.let("i", 0);
  112. gen.assign(data, (0, codegen_1._) `[]`);
  113. parseItems(cxt, "]", () => {
  114. const el = gen.let("el");
  115. parseCode({ ...cxt, schema: schema.elements, data: el });
  116. gen.assign((0, codegen_1._) `${data}[${ix}++]`, el);
  117. });
  118. }
  119. function parseValues(cxt) {
  120. const { gen, schema, data } = cxt;
  121. parseToken(cxt, "{");
  122. gen.assign(data, (0, codegen_1._) `{}`);
  123. parseItems(cxt, "}", () => parseKeyValue(cxt, schema.values));
  124. }
  125. function parseItems(cxt, endToken, block) {
  126. tryParseItems(cxt, endToken, block);
  127. parseToken(cxt, endToken);
  128. }
  129. function tryParseItems(cxt, endToken, block) {
  130. const { gen } = cxt;
  131. gen.for((0, codegen_1._) `;${names_1.default.jsonPos}<${names_1.default.jsonLen} && ${jsonSlice(1)}!==${endToken};`, () => {
  132. block();
  133. tryParseToken(cxt, ",", () => gen.break(), hasItem);
  134. });
  135. function hasItem() {
  136. tryParseToken(cxt, endToken, () => { }, jsonSyntaxError);
  137. }
  138. }
  139. function parseKeyValue(cxt, schema) {
  140. const { gen } = cxt;
  141. const key = gen.let("key");
  142. parseString({ ...cxt, data: key });
  143. parseToken(cxt, ":");
  144. parsePropertyValue(cxt, key, schema);
  145. }
  146. function parseDiscriminator(cxt) {
  147. const { gen, data, schema } = cxt;
  148. const { discriminator, mapping } = schema;
  149. parseToken(cxt, "{");
  150. gen.assign(data, (0, codegen_1._) `{}`);
  151. const startPos = gen.const("pos", names_1.default.jsonPos);
  152. const value = gen.let("value");
  153. const tag = gen.let("tag");
  154. tryParseItems(cxt, "}", () => {
  155. const key = gen.let("key");
  156. parseString({ ...cxt, data: key });
  157. parseToken(cxt, ":");
  158. gen.if((0, codegen_1._) `${key} === ${discriminator}`, () => {
  159. parseString({ ...cxt, data: tag });
  160. gen.assign((0, codegen_1._) `${data}[${key}]`, tag);
  161. gen.break();
  162. }, () => parseEmpty({ ...cxt, data: value }) // can be discarded/skipped
  163. );
  164. });
  165. gen.assign(names_1.default.jsonPos, startPos);
  166. gen.if((0, codegen_1._) `${tag} === undefined`);
  167. parsingError(cxt, (0, codegen_1.str) `discriminator tag not found`);
  168. for (const tagValue in mapping) {
  169. gen.elseIf((0, codegen_1._) `${tag} === ${tagValue}`);
  170. parseSchemaProperties({ ...cxt, schema: mapping[tagValue] }, discriminator);
  171. }
  172. gen.else();
  173. parsingError(cxt, (0, codegen_1.str) `discriminator value not in schema`);
  174. gen.endIf();
  175. }
  176. function parseProperties(cxt) {
  177. const { gen, data } = cxt;
  178. parseToken(cxt, "{");
  179. gen.assign(data, (0, codegen_1._) `{}`);
  180. parseSchemaProperties(cxt);
  181. }
  182. function parseSchemaProperties(cxt, discriminator) {
  183. const { gen, schema, data } = cxt;
  184. const { properties, optionalProperties, additionalProperties } = schema;
  185. parseItems(cxt, "}", () => {
  186. const key = gen.let("key");
  187. parseString({ ...cxt, data: key });
  188. parseToken(cxt, ":");
  189. gen.if(false);
  190. parseDefinedProperty(cxt, key, properties);
  191. parseDefinedProperty(cxt, key, optionalProperties);
  192. if (discriminator) {
  193. gen.elseIf((0, codegen_1._) `${key} === ${discriminator}`);
  194. const tag = gen.let("tag");
  195. parseString({ ...cxt, data: tag }); // can be discarded, it is already assigned
  196. }
  197. gen.else();
  198. if (additionalProperties) {
  199. parseEmpty({ ...cxt, data: (0, codegen_1._) `${data}[${key}]` });
  200. }
  201. else {
  202. parsingError(cxt, (0, codegen_1.str) `property ${key} not allowed`);
  203. }
  204. gen.endIf();
  205. });
  206. if (properties) {
  207. const hasProp = (0, code_1.hasPropFunc)(gen);
  208. const allProps = (0, codegen_1.and)(...Object.keys(properties).map((p) => (0, codegen_1._) `${hasProp}.call(${data}, ${p})`));
  209. gen.if((0, codegen_1.not)(allProps), () => parsingError(cxt, (0, codegen_1.str) `missing required properties`));
  210. }
  211. }
  212. function parseDefinedProperty(cxt, key, schemas = {}) {
  213. const { gen } = cxt;
  214. for (const prop in schemas) {
  215. gen.elseIf((0, codegen_1._) `${key} === ${prop}`);
  216. parsePropertyValue(cxt, key, schemas[prop]);
  217. }
  218. }
  219. function parsePropertyValue(cxt, key, schema) {
  220. parseCode({ ...cxt, schema, data: (0, codegen_1._) `${cxt.data}[${key}]` });
  221. }
  222. function parseType(cxt) {
  223. const { gen, schema, data, self } = cxt;
  224. switch (schema.type) {
  225. case "boolean":
  226. parseBoolean(cxt);
  227. break;
  228. case "string":
  229. parseString(cxt);
  230. break;
  231. case "timestamp": {
  232. parseString(cxt);
  233. const vts = (0, util_1.useFunc)(gen, timestamp_1.default);
  234. const { allowDate, parseDate } = self.opts;
  235. const notValid = allowDate ? (0, codegen_1._) `!${vts}(${data}, true)` : (0, codegen_1._) `!${vts}(${data})`;
  236. const fail = parseDate
  237. ? (0, codegen_1.or)(notValid, (0, codegen_1._) `(${data} = new Date(${data}), false)`, (0, codegen_1._) `isNaN(${data}.valueOf())`)
  238. : notValid;
  239. gen.if(fail, () => parsingError(cxt, (0, codegen_1.str) `invalid timestamp`));
  240. break;
  241. }
  242. case "float32":
  243. case "float64":
  244. parseNumber(cxt);
  245. break;
  246. default: {
  247. const t = schema.type;
  248. if (!self.opts.int32range && (t === "int32" || t === "uint32")) {
  249. parseNumber(cxt, 16); // 2 ** 53 - max safe integer
  250. if (t === "uint32") {
  251. gen.if((0, codegen_1._) `${data} < 0`, () => parsingError(cxt, (0, codegen_1.str) `integer out of range`));
  252. }
  253. }
  254. else {
  255. const [min, max, maxDigits] = type_1.intRange[t];
  256. parseNumber(cxt, maxDigits);
  257. gen.if((0, codegen_1._) `${data} < ${min} || ${data} > ${max}`, () => parsingError(cxt, (0, codegen_1.str) `integer out of range`));
  258. }
  259. }
  260. }
  261. }
  262. function parseString(cxt) {
  263. parseToken(cxt, '"');
  264. parseWith(cxt, parseJson_1.parseJsonString);
  265. }
  266. function parseEnum(cxt) {
  267. const { gen, data, schema } = cxt;
  268. const enumSch = schema.enum;
  269. parseToken(cxt, '"');
  270. // TODO loopEnum
  271. gen.if(false);
  272. for (const value of enumSch) {
  273. const valueStr = JSON.stringify(value).slice(1); // remove starting quote
  274. gen.elseIf((0, codegen_1._) `${jsonSlice(valueStr.length)} === ${valueStr}`);
  275. gen.assign(data, (0, codegen_1.str) `${value}`);
  276. gen.add(names_1.default.jsonPos, valueStr.length);
  277. }
  278. gen.else();
  279. jsonSyntaxError(cxt);
  280. gen.endIf();
  281. }
  282. function parseNumber(cxt, maxDigits) {
  283. const { gen } = cxt;
  284. skipWhitespace(cxt);
  285. gen.if((0, codegen_1._) `"-0123456789".indexOf(${jsonSlice(1)}) < 0`, () => jsonSyntaxError(cxt), () => parseWith(cxt, parseJson_1.parseJsonNumber, maxDigits));
  286. }
  287. function parseBooleanToken(bool, fail) {
  288. return (cxt) => {
  289. const { gen, data } = cxt;
  290. tryParseToken(cxt, `${bool}`, () => fail(cxt), () => gen.assign(data, bool));
  291. };
  292. }
  293. function parseRef(cxt) {
  294. const { gen, self, definitions, schema, schemaEnv } = cxt;
  295. const { ref } = schema;
  296. const refSchema = definitions[ref];
  297. if (!refSchema)
  298. throw new ref_error_1.default(self.opts.uriResolver, "", ref, `No definition ${ref}`);
  299. if (!(0, ref_1.hasRef)(refSchema))
  300. return parseCode({ ...cxt, schema: refSchema });
  301. const { root } = schemaEnv;
  302. const sch = compileParser.call(self, new __1.SchemaEnv({ schema: refSchema, root }), definitions);
  303. partialParse(cxt, getParser(gen, sch), true);
  304. }
  305. function getParser(gen, sch) {
  306. return sch.parse
  307. ? gen.scopeValue("parse", { ref: sch.parse })
  308. : (0, codegen_1._) `${gen.scopeValue("wrapper", { ref: sch })}.parse`;
  309. }
  310. function parseEmpty(cxt) {
  311. parseWith(cxt, parseJson_1.parseJson);
  312. }
  313. function parseWith(cxt, parseFunc, args) {
  314. partialParse(cxt, (0, util_1.useFunc)(cxt.gen, parseFunc), args);
  315. }
  316. function partialParse(cxt, parseFunc, args) {
  317. const { gen, data } = cxt;
  318. gen.assign(data, (0, codegen_1._) `${parseFunc}(${names_1.default.json}, ${names_1.default.jsonPos}${args ? (0, codegen_1._) `, ${args}` : codegen_1.nil})`);
  319. gen.assign(names_1.default.jsonPos, (0, codegen_1._) `${parseFunc}.position`);
  320. gen.if((0, codegen_1._) `${data} === undefined`, () => parsingError(cxt, (0, codegen_1._) `${parseFunc}.message`));
  321. }
  322. function parseToken(cxt, tok) {
  323. tryParseToken(cxt, tok, jsonSyntaxError);
  324. }
  325. function tryParseToken(cxt, tok, fail, success) {
  326. const { gen } = cxt;
  327. const n = tok.length;
  328. skipWhitespace(cxt);
  329. gen.if((0, codegen_1._) `${jsonSlice(n)} === ${tok}`, () => {
  330. gen.add(names_1.default.jsonPos, n);
  331. success === null || success === void 0 ? void 0 : success(cxt);
  332. }, () => fail(cxt));
  333. }
  334. function skipWhitespace({ gen, char: c }) {
  335. 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}++;`);
  336. }
  337. function jsonSlice(len) {
  338. return len === 1
  339. ? (0, codegen_1._) `${names_1.default.json}[${names_1.default.jsonPos}]`
  340. : (0, codegen_1._) `${names_1.default.json}.slice(${names_1.default.jsonPos}, ${names_1.default.jsonPos}+${len})`;
  341. }
  342. function jsonSyntaxError(cxt) {
  343. parsingError(cxt, (0, codegen_1._) `"unexpected token " + ${names_1.default.json}[${names_1.default.jsonPos}]`);
  344. }
  345. function parsingError({ gen, parseName }, msg) {
  346. gen.assign((0, codegen_1._) `${parseName}.message`, msg);
  347. gen.assign((0, codegen_1._) `${parseName}.position`, names_1.default.jsonPos);
  348. gen.return(undef);
  349. }
  350. //# sourceMappingURL=parse.js.map