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.
		
		
		
		
		
			
		
			
				
					412 lines
				
				12 KiB
			
		
		
			
		
	
	
					412 lines
				
				12 KiB
			| 
								 
											3 years ago
										 
									 | 
							
								import type Ajv from "../../core"
							 | 
						||
| 
								 | 
							
								import type {SchemaObject} from "../../types"
							 | 
						||
| 
								 | 
							
								import {jtdForms, JTDForm, SchemaObjectMap} from "./types"
							 | 
						||
| 
								 | 
							
								import {SchemaEnv, getCompilingSchema} from ".."
							 | 
						||
| 
								 | 
							
								import {_, str, and, or, nil, not, CodeGen, Code, Name, SafeExpr} from "../codegen"
							 | 
						||
| 
								 | 
							
								import MissingRefError from "../ref_error"
							 | 
						||
| 
								 | 
							
								import N from "../names"
							 | 
						||
| 
								 | 
							
								import {hasPropFunc} from "../../vocabularies/code"
							 | 
						||
| 
								 | 
							
								import {hasRef} from "../../vocabularies/jtd/ref"
							 | 
						||
| 
								 | 
							
								import {intRange, IntType} from "../../vocabularies/jtd/type"
							 | 
						||
| 
								 | 
							
								import {parseJson, parseJsonNumber, parseJsonString} from "../../runtime/parseJson"
							 | 
						||
| 
								 | 
							
								import {useFunc} from "../util"
							 | 
						||
| 
								 | 
							
								import validTimestamp from "../../runtime/timestamp"
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								type GenParse = (cxt: ParseCxt) => void
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								const genParse: {[F in JTDForm]: GenParse} = {
							 | 
						||
| 
								 | 
							
								  elements: parseElements,
							 | 
						||
| 
								 | 
							
								  values: parseValues,
							 | 
						||
| 
								 | 
							
								  discriminator: parseDiscriminator,
							 | 
						||
| 
								 | 
							
								  properties: parseProperties,
							 | 
						||
| 
								 | 
							
								  optionalProperties: parseProperties,
							 | 
						||
| 
								 | 
							
								  enum: parseEnum,
							 | 
						||
| 
								 | 
							
								  type: parseType,
							 | 
						||
| 
								 | 
							
								  ref: parseRef,
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								interface ParseCxt {
							 | 
						||
| 
								 | 
							
								  readonly gen: CodeGen
							 | 
						||
| 
								 | 
							
								  readonly self: Ajv // current Ajv instance
							 | 
						||
| 
								 | 
							
								  readonly schemaEnv: SchemaEnv
							 | 
						||
| 
								 | 
							
								  readonly definitions: SchemaObjectMap
							 | 
						||
| 
								 | 
							
								  schema: SchemaObject
							 | 
						||
| 
								 | 
							
								  data: Code
							 | 
						||
| 
								 | 
							
								  parseName: Name
							 | 
						||
| 
								 | 
							
								  char: Name
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export default function compileParser(
							 | 
						||
| 
								 | 
							
								  this: Ajv,
							 | 
						||
| 
								 | 
							
								  sch: SchemaEnv,
							 | 
						||
| 
								 | 
							
								  definitions: SchemaObjectMap
							 | 
						||
| 
								 | 
							
								): SchemaEnv {
							 | 
						||
| 
								 | 
							
								  const _sch = getCompilingSchema.call(this, sch)
							 | 
						||
| 
								 | 
							
								  if (_sch) return _sch
							 | 
						||
| 
								 | 
							
								  const {es5, lines} = this.opts.code
							 | 
						||
| 
								 | 
							
								  const {ownProperties} = this.opts
							 | 
						||
| 
								 | 
							
								  const gen = new CodeGen(this.scope, {es5, lines, ownProperties})
							 | 
						||
| 
								 | 
							
								  const parseName = gen.scopeName("parse")
							 | 
						||
| 
								 | 
							
								  const cxt: ParseCxt = {
							 | 
						||
| 
								 | 
							
								    self: this,
							 | 
						||
| 
								 | 
							
								    gen,
							 | 
						||
| 
								 | 
							
								    schema: sch.schema as SchemaObject,
							 | 
						||
| 
								 | 
							
								    schemaEnv: sch,
							 | 
						||
| 
								 | 
							
								    definitions,
							 | 
						||
| 
								 | 
							
								    data: N.data,
							 | 
						||
| 
								 | 
							
								    parseName,
							 | 
						||
| 
								 | 
							
								    char: gen.name("c"),
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  let sourceCode: string | undefined
							 | 
						||
| 
								 | 
							
								  try {
							 | 
						||
| 
								 | 
							
								    this._compilations.add(sch)
							 | 
						||
| 
								 | 
							
								    sch.parseName = parseName
							 | 
						||
| 
								 | 
							
								    parserFunction(cxt)
							 | 
						||
| 
								 | 
							
								    gen.optimize(this.opts.code.optimize)
							 | 
						||
| 
								 | 
							
								    const parseFuncCode = gen.toString()
							 | 
						||
| 
								 | 
							
								    sourceCode = `${gen.scopeRefs(N.scope)}return ${parseFuncCode}`
							 | 
						||
| 
								 | 
							
								    const makeParse = new Function(`${N.scope}`, sourceCode)
							 | 
						||
| 
								 | 
							
								    const parse: (json: string) => unknown = 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
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								const undef = _`undefined`
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function parserFunction(cxt: ParseCxt): void {
							 | 
						||
| 
								 | 
							
								  const {gen, parseName, char} = cxt
							 | 
						||
| 
								 | 
							
								  gen.func(parseName, _`${N.json}, ${N.jsonPos}, ${N.jsonPart}`, false, () => {
							 | 
						||
| 
								 | 
							
								    gen.let(N.data)
							 | 
						||
| 
								 | 
							
								    gen.let(char)
							 | 
						||
| 
								 | 
							
								    gen.assign(_`${parseName}.message`, undef)
							 | 
						||
| 
								 | 
							
								    gen.assign(_`${parseName}.position`, undef)
							 | 
						||
| 
								 | 
							
								    gen.assign(N.jsonPos, _`${N.jsonPos} || 0`)
							 | 
						||
| 
								 | 
							
								    gen.const(N.jsonLen, _`${N.json}.length`)
							 | 
						||
| 
								 | 
							
								    parseCode(cxt)
							 | 
						||
| 
								 | 
							
								    skipWhitespace(cxt)
							 | 
						||
| 
								 | 
							
								    gen.if(N.jsonPart, () => {
							 | 
						||
| 
								 | 
							
								      gen.assign(_`${parseName}.position`, N.jsonPos)
							 | 
						||
| 
								 | 
							
								      gen.return(N.data)
							 | 
						||
| 
								 | 
							
								    })
							 | 
						||
| 
								 | 
							
								    gen.if(_`${N.jsonPos} === ${N.jsonLen}`, () => gen.return(N.data))
							 | 
						||
| 
								 | 
							
								    jsonSyntaxError(cxt)
							 | 
						||
| 
								 | 
							
								  })
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function parseCode(cxt: ParseCxt): void {
							 | 
						||
| 
								 | 
							
								  let form: JTDForm | undefined
							 | 
						||
| 
								 | 
							
								  for (const key of 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: ParseCxt, parseForm: GenParse): void {
							 | 
						||
| 
								 | 
							
								  const {gen, schema, data} = cxt
							 | 
						||
| 
								 | 
							
								  if (!schema.nullable) return parseForm(cxt)
							 | 
						||
| 
								 | 
							
								  tryParseToken(cxt, "null", parseForm, () => gen.assign(data, null))
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function parseElements(cxt: ParseCxt): void {
							 | 
						||
| 
								 | 
							
								  const {gen, schema, data} = cxt
							 | 
						||
| 
								 | 
							
								  parseToken(cxt, "[")
							 | 
						||
| 
								 | 
							
								  const ix = gen.let("i", 0)
							 | 
						||
| 
								 | 
							
								  gen.assign(data, _`[]`)
							 | 
						||
| 
								 | 
							
								  parseItems(cxt, "]", () => {
							 | 
						||
| 
								 | 
							
								    const el = gen.let("el")
							 | 
						||
| 
								 | 
							
								    parseCode({...cxt, schema: schema.elements, data: el})
							 | 
						||
| 
								 | 
							
								    gen.assign(_`${data}[${ix}++]`, el)
							 | 
						||
| 
								 | 
							
								  })
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function parseValues(cxt: ParseCxt): void {
							 | 
						||
| 
								 | 
							
								  const {gen, schema, data} = cxt
							 | 
						||
| 
								 | 
							
								  parseToken(cxt, "{")
							 | 
						||
| 
								 | 
							
								  gen.assign(data, _`{}`)
							 | 
						||
| 
								 | 
							
								  parseItems(cxt, "}", () => parseKeyValue(cxt, schema.values))
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function parseItems(cxt: ParseCxt, endToken: string, block: () => void): void {
							 | 
						||
| 
								 | 
							
								  tryParseItems(cxt, endToken, block)
							 | 
						||
| 
								 | 
							
								  parseToken(cxt, endToken)
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function tryParseItems(cxt: ParseCxt, endToken: string, block: () => void): void {
							 | 
						||
| 
								 | 
							
								  const {gen} = cxt
							 | 
						||
| 
								 | 
							
								  gen.for(_`;${N.jsonPos}<${N.jsonLen} && ${jsonSlice(1)}!==${endToken};`, () => {
							 | 
						||
| 
								 | 
							
								    block()
							 | 
						||
| 
								 | 
							
								    tryParseToken(cxt, ",", () => gen.break(), hasItem)
							 | 
						||
| 
								 | 
							
								  })
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  function hasItem(): void {
							 | 
						||
| 
								 | 
							
								    tryParseToken(cxt, endToken, () => {}, jsonSyntaxError)
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function parseKeyValue(cxt: ParseCxt, schema: SchemaObject): void {
							 | 
						||
| 
								 | 
							
								  const {gen} = cxt
							 | 
						||
| 
								 | 
							
								  const key = gen.let("key")
							 | 
						||
| 
								 | 
							
								  parseString({...cxt, data: key})
							 | 
						||
| 
								 | 
							
								  parseToken(cxt, ":")
							 | 
						||
| 
								 | 
							
								  parsePropertyValue(cxt, key, schema)
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function parseDiscriminator(cxt: ParseCxt): void {
							 | 
						||
| 
								 | 
							
								  const {gen, data, schema} = cxt
							 | 
						||
| 
								 | 
							
								  const {discriminator, mapping} = schema
							 | 
						||
| 
								 | 
							
								  parseToken(cxt, "{")
							 | 
						||
| 
								 | 
							
								  gen.assign(data, _`{}`)
							 | 
						||
| 
								 | 
							
								  const startPos = gen.const("pos", N.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(
							 | 
						||
| 
								 | 
							
								      _`${key} === ${discriminator}`,
							 | 
						||
| 
								 | 
							
								      () => {
							 | 
						||
| 
								 | 
							
								        parseString({...cxt, data: tag})
							 | 
						||
| 
								 | 
							
								        gen.assign(_`${data}[${key}]`, tag)
							 | 
						||
| 
								 | 
							
								        gen.break()
							 | 
						||
| 
								 | 
							
								      },
							 | 
						||
| 
								 | 
							
								      () => parseEmpty({...cxt, data: value}) // can be discarded/skipped
							 | 
						||
| 
								 | 
							
								    )
							 | 
						||
| 
								 | 
							
								  })
							 | 
						||
| 
								 | 
							
								  gen.assign(N.jsonPos, startPos)
							 | 
						||
| 
								 | 
							
								  gen.if(_`${tag} === undefined`)
							 | 
						||
| 
								 | 
							
								  parsingError(cxt, str`discriminator tag not found`)
							 | 
						||
| 
								 | 
							
								  for (const tagValue in mapping) {
							 | 
						||
| 
								 | 
							
								    gen.elseIf(_`${tag} === ${tagValue}`)
							 | 
						||
| 
								 | 
							
								    parseSchemaProperties({...cxt, schema: mapping[tagValue]}, discriminator)
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  gen.else()
							 | 
						||
| 
								 | 
							
								  parsingError(cxt, str`discriminator value not in schema`)
							 | 
						||
| 
								 | 
							
								  gen.endIf()
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function parseProperties(cxt: ParseCxt): void {
							 | 
						||
| 
								 | 
							
								  const {gen, data} = cxt
							 | 
						||
| 
								 | 
							
								  parseToken(cxt, "{")
							 | 
						||
| 
								 | 
							
								  gen.assign(data, _`{}`)
							 | 
						||
| 
								 | 
							
								  parseSchemaProperties(cxt)
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function parseSchemaProperties(cxt: ParseCxt, discriminator?: string): void {
							 | 
						||
| 
								 | 
							
								  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(_`${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: _`${data}[${key}]`})
							 | 
						||
| 
								 | 
							
								    } else {
							 | 
						||
| 
								 | 
							
								      parsingError(cxt, str`property ${key} not allowed`)
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    gen.endIf()
							 | 
						||
| 
								 | 
							
								  })
							 | 
						||
| 
								 | 
							
								  if (properties) {
							 | 
						||
| 
								 | 
							
								    const hasProp = hasPropFunc(gen)
							 | 
						||
| 
								 | 
							
								    const allProps: Code = and(
							 | 
						||
| 
								 | 
							
								      ...Object.keys(properties).map((p): Code => _`${hasProp}.call(${data}, ${p})`)
							 | 
						||
| 
								 | 
							
								    )
							 | 
						||
| 
								 | 
							
								    gen.if(not(allProps), () => parsingError(cxt, str`missing required properties`))
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function parseDefinedProperty(cxt: ParseCxt, key: Name, schemas: SchemaObjectMap = {}): void {
							 | 
						||
| 
								 | 
							
								  const {gen} = cxt
							 | 
						||
| 
								 | 
							
								  for (const prop in schemas) {
							 | 
						||
| 
								 | 
							
								    gen.elseIf(_`${key} === ${prop}`)
							 | 
						||
| 
								 | 
							
								    parsePropertyValue(cxt, key, schemas[prop] as SchemaObject)
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function parsePropertyValue(cxt: ParseCxt, key: Name, schema: SchemaObject): void {
							 | 
						||
| 
								 | 
							
								  parseCode({...cxt, schema, data: _`${cxt.data}[${key}]`})
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function parseType(cxt: ParseCxt): void {
							 | 
						||
| 
								 | 
							
								  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 = useFunc(gen, validTimestamp)
							 | 
						||
| 
								 | 
							
								      const {allowDate, parseDate} = self.opts
							 | 
						||
| 
								 | 
							
								      const notValid = allowDate ? _`!${vts}(${data}, true)` : _`!${vts}(${data})`
							 | 
						||
| 
								 | 
							
								      const fail: Code = parseDate
							 | 
						||
| 
								 | 
							
								        ? or(notValid, _`(${data} = new Date(${data}), false)`, _`isNaN(${data}.valueOf())`)
							 | 
						||
| 
								 | 
							
								        : notValid
							 | 
						||
| 
								 | 
							
								      gen.if(fail, () => parsingError(cxt, str`invalid timestamp`))
							 | 
						||
| 
								 | 
							
								      break
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    case "float32":
							 | 
						||
| 
								 | 
							
								    case "float64":
							 | 
						||
| 
								 | 
							
								      parseNumber(cxt)
							 | 
						||
| 
								 | 
							
								      break
							 | 
						||
| 
								 | 
							
								    default: {
							 | 
						||
| 
								 | 
							
								      const t = schema.type as IntType
							 | 
						||
| 
								 | 
							
								      if (!self.opts.int32range && (t === "int32" || t === "uint32")) {
							 | 
						||
| 
								 | 
							
								        parseNumber(cxt, 16) // 2 ** 53 - max safe integer
							 | 
						||
| 
								 | 
							
								        if (t === "uint32") {
							 | 
						||
| 
								 | 
							
								          gen.if(_`${data} < 0`, () => parsingError(cxt, str`integer out of range`))
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								      } else {
							 | 
						||
| 
								 | 
							
								        const [min, max, maxDigits] = intRange[t]
							 | 
						||
| 
								 | 
							
								        parseNumber(cxt, maxDigits)
							 | 
						||
| 
								 | 
							
								        gen.if(_`${data} < ${min} || ${data} > ${max}`, () =>
							 | 
						||
| 
								 | 
							
								          parsingError(cxt, str`integer out of range`)
							 | 
						||
| 
								 | 
							
								        )
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function parseString(cxt: ParseCxt): void {
							 | 
						||
| 
								 | 
							
								  parseToken(cxt, '"')
							 | 
						||
| 
								 | 
							
								  parseWith(cxt, parseJsonString)
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function parseEnum(cxt: ParseCxt): void {
							 | 
						||
| 
								 | 
							
								  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(_`${jsonSlice(valueStr.length)} === ${valueStr}`)
							 | 
						||
| 
								 | 
							
								    gen.assign(data, str`${value}`)
							 | 
						||
| 
								 | 
							
								    gen.add(N.jsonPos, valueStr.length)
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  gen.else()
							 | 
						||
| 
								 | 
							
								  jsonSyntaxError(cxt)
							 | 
						||
| 
								 | 
							
								  gen.endIf()
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function parseNumber(cxt: ParseCxt, maxDigits?: number): void {
							 | 
						||
| 
								 | 
							
								  const {gen} = cxt
							 | 
						||
| 
								 | 
							
								  skipWhitespace(cxt)
							 | 
						||
| 
								 | 
							
								  gen.if(
							 | 
						||
| 
								 | 
							
								    _`"-0123456789".indexOf(${jsonSlice(1)}) < 0`,
							 | 
						||
| 
								 | 
							
								    () => jsonSyntaxError(cxt),
							 | 
						||
| 
								 | 
							
								    () => parseWith(cxt, parseJsonNumber, maxDigits)
							 | 
						||
| 
								 | 
							
								  )
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function parseBooleanToken(bool: boolean, fail: GenParse): GenParse {
							 | 
						||
| 
								 | 
							
								  return (cxt) => {
							 | 
						||
| 
								 | 
							
								    const {gen, data} = cxt
							 | 
						||
| 
								 | 
							
								    tryParseToken(
							 | 
						||
| 
								 | 
							
								      cxt,
							 | 
						||
| 
								 | 
							
								      `${bool}`,
							 | 
						||
| 
								 | 
							
								      () => fail(cxt),
							 | 
						||
| 
								 | 
							
								      () => gen.assign(data, bool)
							 | 
						||
| 
								 | 
							
								    )
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function parseRef(cxt: ParseCxt): void {
							 | 
						||
| 
								 | 
							
								  const {gen, self, definitions, schema, schemaEnv} = cxt
							 | 
						||
| 
								 | 
							
								  const {ref} = schema
							 | 
						||
| 
								 | 
							
								  const refSchema = definitions[ref]
							 | 
						||
| 
								 | 
							
								  if (!refSchema) throw new MissingRefError(self.opts.uriResolver, "", ref, `No definition ${ref}`)
							 | 
						||
| 
								 | 
							
								  if (!hasRef(refSchema)) return parseCode({...cxt, schema: refSchema})
							 | 
						||
| 
								 | 
							
								  const {root} = schemaEnv
							 | 
						||
| 
								 | 
							
								  const sch = compileParser.call(self, new SchemaEnv({schema: refSchema, root}), definitions)
							 | 
						||
| 
								 | 
							
								  partialParse(cxt, getParser(gen, sch), true)
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function getParser(gen: CodeGen, sch: SchemaEnv): Code {
							 | 
						||
| 
								 | 
							
								  return sch.parse
							 | 
						||
| 
								 | 
							
								    ? gen.scopeValue("parse", {ref: sch.parse})
							 | 
						||
| 
								 | 
							
								    : _`${gen.scopeValue("wrapper", {ref: sch})}.parse`
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function parseEmpty(cxt: ParseCxt): void {
							 | 
						||
| 
								 | 
							
								  parseWith(cxt, parseJson)
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function parseWith(cxt: ParseCxt, parseFunc: {code: string}, args?: SafeExpr): void {
							 | 
						||
| 
								 | 
							
								  partialParse(cxt, useFunc(cxt.gen, parseFunc), args)
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function partialParse(cxt: ParseCxt, parseFunc: Name, args?: SafeExpr): void {
							 | 
						||
| 
								 | 
							
								  const {gen, data} = cxt
							 | 
						||
| 
								 | 
							
								  gen.assign(data, _`${parseFunc}(${N.json}, ${N.jsonPos}${args ? _`, ${args}` : nil})`)
							 | 
						||
| 
								 | 
							
								  gen.assign(N.jsonPos, _`${parseFunc}.position`)
							 | 
						||
| 
								 | 
							
								  gen.if(_`${data} === undefined`, () => parsingError(cxt, _`${parseFunc}.message`))
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function parseToken(cxt: ParseCxt, tok: string): void {
							 | 
						||
| 
								 | 
							
								  tryParseToken(cxt, tok, jsonSyntaxError)
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function tryParseToken(cxt: ParseCxt, tok: string, fail: GenParse, success?: GenParse): void {
							 | 
						||
| 
								 | 
							
								  const {gen} = cxt
							 | 
						||
| 
								 | 
							
								  const n = tok.length
							 | 
						||
| 
								 | 
							
								  skipWhitespace(cxt)
							 | 
						||
| 
								 | 
							
								  gen.if(
							 | 
						||
| 
								 | 
							
								    _`${jsonSlice(n)} === ${tok}`,
							 | 
						||
| 
								 | 
							
								    () => {
							 | 
						||
| 
								 | 
							
								      gen.add(N.jsonPos, n)
							 | 
						||
| 
								 | 
							
								      success?.(cxt)
							 | 
						||
| 
								 | 
							
								    },
							 | 
						||
| 
								 | 
							
								    () => fail(cxt)
							 | 
						||
| 
								 | 
							
								  )
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function skipWhitespace({gen, char: c}: ParseCxt): void {
							 | 
						||
| 
								 | 
							
								  gen.code(
							 | 
						||
| 
								 | 
							
								    _`while((${c}=${N.json}[${N.jsonPos}],${c}===" "||${c}==="\\n"||${c}==="\\r"||${c}==="\\t"))${N.jsonPos}++;`
							 | 
						||
| 
								 | 
							
								  )
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function jsonSlice(len: number | Name): Code {
							 | 
						||
| 
								 | 
							
								  return len === 1
							 | 
						||
| 
								 | 
							
								    ? _`${N.json}[${N.jsonPos}]`
							 | 
						||
| 
								 | 
							
								    : _`${N.json}.slice(${N.jsonPos}, ${N.jsonPos}+${len})`
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function jsonSyntaxError(cxt: ParseCxt): void {
							 | 
						||
| 
								 | 
							
								  parsingError(cxt, _`"unexpected token " + ${N.json}[${N.jsonPos}]`)
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function parsingError({gen, parseName}: ParseCxt, msg: Code): void {
							 | 
						||
| 
								 | 
							
								  gen.assign(_`${parseName}.message`, msg)
							 | 
						||
| 
								 | 
							
								  gen.assign(_`${parseName}.position`, N.jsonPos)
							 | 
						||
| 
								 | 
							
								  gen.return(undef)
							 | 
						||
| 
								 | 
							
								}
							 |