source: trip-planner-front/node_modules/ajv/lib/compile/jtd/parse.ts@ 8d391a1

Last change on this file since 8d391a1 was 6a3a178, checked in by Ema <ema_spirova@…>, 3 years ago

initial commit

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