massive update, probably broken
[dotfiles/.git] / .config / coc / extensions / node_modules / coc-prettier / node_modules / table / node_modules / ajv / lib / compile / jtd / parse.ts
1 import type Ajv from "../../core"
2 import type {SchemaObject} from "../../types"
3 import {jtdForms, JTDForm, SchemaObjectMap} from "./types"
4 import {SchemaEnv, getCompilingSchema} from ".."
5 import {_, str, and, or, nil, not, CodeGen, Code, Name, SafeExpr} from "../codegen"
6 import MissingRefError from "../ref_error"
7 import N from "../names"
8 import {hasPropFunc} from "../../vocabularies/code"
9 import {hasRef} from "../../vocabularies/jtd/ref"
10 import {intRange, IntType} from "../../vocabularies/jtd/type"
11 import {parseJson, parseJsonNumber, parseJsonString} from "../../runtime/parseJson"
12 import {useFunc} from "../util"
13 import validTimestamp from "../../runtime/timestamp"
14
15 type GenParse = (cxt: ParseCxt) => void
16
17 const 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
28 interface 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
39 export 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
84 const undef = _`undefined`
85
86 function 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
106 function 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
118 const parseBoolean = parseBooleanToken(true, parseBooleanToken(false, jsonSyntaxError))
119
120 function 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
126 function 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
138 function 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
145 function parseItems(cxt: ParseCxt, endToken: string, block: () => void): void {
146   tryParseItems(cxt, endToken, block)
147   parseToken(cxt, endToken)
148 }
149
150 function 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
162 function 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
170 function 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
204 function parseProperties(cxt: ParseCxt): void {
205   const {gen, data} = cxt
206   parseToken(cxt, "{")
207   gen.assign(data, _`{}`)
208   parseSchemaProperties(cxt)
209 }
210
211 function 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
243 function 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
251 function parsePropertyValue(cxt: ParseCxt, key: Name, schema: SchemaObject): void {
252   parseCode({...cxt, schema, data: _`${cxt.data}[${key}]`})
253 }
254
255 function 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
297 function parseString(cxt: ParseCxt): void {
298   parseToken(cxt, '"')
299   parseWith(cxt, parseJsonString)
300 }
301
302 function 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
319 function 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
329 function 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
341 function 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
352 function 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
358 function parseEmpty(cxt: ParseCxt): void {
359   parseWith(cxt, parseJson)
360 }
361
362 function parseWith(cxt: ParseCxt, parseFunc: {code: string}, args?: SafeExpr): void {
363   partialParse(cxt, useFunc(cxt.gen, parseFunc), args)
364 }
365
366 function 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
373 function parseToken(cxt: ParseCxt, tok: string): void {
374   tryParseToken(cxt, tok, jsonSyntaxError)
375 }
376
377 function 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
391 function 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
397 function 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
403 function jsonSyntaxError(cxt: ParseCxt): void {
404   parsingError(cxt, _`"unexpected token " + ${N.json}[${N.jsonPos}]`)
405 }
406
407 function 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 }