Skip to content

Commit

Permalink
refactor: shared function scoped names
Browse files Browse the repository at this point in the history
  • Loading branch information
epoberezkin committed Aug 27, 2020
1 parent 60c4b3c commit 47d172c
Show file tree
Hide file tree
Showing 18 changed files with 145 additions and 116 deletions.
17 changes: 13 additions & 4 deletions lib/compile/codegen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ enum BlockKind {

export type Expression = string | Name | Code

export type Value = string | Name | Code | number | boolean | null

export type Block = string | Name | Code | (() => void)

export class Code {
Expand Down Expand Up @@ -63,6 +65,8 @@ function interpolate(x: TemplateArg): TemplateArg {
return x instanceof Code || typeof x == "number" || typeof x == "boolean" ? x : quoteString(x)
}

const IDENTIFIER = /^[a-z$_][a-z$_0-9]*$/i

export default class CodeGen {
#names: {[key: string]: number} = {}
// TODO make private. Possibly stack?
Expand Down Expand Up @@ -99,15 +103,20 @@ export default class CodeGen {
return this._def(varKinds.var, nameOrPrefix, rhs)
}

assign(name: Expression, rhs: Expression | number | boolean): CodeGen {
assign(name: Expression, rhs: Value): CodeGen {
this.code(`${name} = ${rhs};`)
return this
}

code(c?: Block): CodeGen {
prop(name: Code, key: Expression | number): Code {
name = name instanceof Name ? name : _`(${name})`
return typeof key == "string" && IDENTIFIER.test(key) ? _`${name}.${key}` : _`${name}[${key}]`
}

code(c?: Block | Value): CodeGen {
// TODO optionally strip whitespace
if (typeof c == "function") c()
else if (c) this._out += c + "\n"
else if (c !== undefined) this._out += c + "\n" // TODO fails without line breaks
return this
}

Expand Down Expand Up @@ -166,7 +175,7 @@ export default class CodeGen {
return this
}

return(value: Block): CodeGen {
return(value: Block | Value): CodeGen {
this._out += "return "
this.code(value)
this._out += ";"
Expand Down
42 changes: 23 additions & 19 deletions lib/compile/errors.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {KeywordContext, KeywordErrorDefinition} from "../types"
import {quotedString} from "../vocabularies/util"
import CodeGen, {_, Name} from "./codegen"
import CodeGen, {_, Name, Expression} from "./codegen"
import N from "./names"

export function reportError(
cxt: KeywordContext,
Expand All @@ -21,14 +22,14 @@ export function reportExtraError(cxt: KeywordContext, error: KeywordErrorDefinit
const errObj = errorObjectCode(cxt, error)
addError(gen, errObj)
if (!(compositeRule || allErrors)) {
returnErrors(gen, async, "vErrors")
returnErrors(gen, async, N.vErrors)
}
}

export function resetErrorsCount(gen: CodeGen, errsCount: Name): void {
gen.code(_`errors = ${errsCount};`)
gen.if(_`vErrors !== null`, () =>
gen.if(errsCount, _`vErrors.length = ${errsCount}`, _`vErrors = null`)
gen.assign(N.errors, errsCount)
gen.if(_`${N.vErrors} !== null`, () =>
gen.if(errsCount, _`${N.vErrors}.length = ${errsCount}`, _`${N.vErrors} = null`)
)
}

Expand All @@ -37,13 +38,16 @@ export function extendErrors(
errsCount: Name
): void {
const err = gen.name("err")
gen.for(_`let i=${errsCount}; i<errors; i++`, () => {
gen.const(err, _`vErrors[i]`)
gen.if(_`${err}.dataPath === undefined`, `${err}.dataPath = (dataPath || '') + ${it.errorPath}`)
gen.for(_`let i=${errsCount}; i<${N.errors}; i++`, () => {
gen.const(err, _`${N.vErrors}[i]`)
gen.if(
_`${err}.dataPath === undefined`,
`${err}.dataPath = (${N.dataPath} || '') + ${it.errorPath}`
)
gen.code(_`${err}.schemaPath = ${it.errSchemaPath + "/" + keyword};`)
if (it.opts.verbose) {
gen.code(
`${err}.schema = ${schemaValue};
_`${err}.schema = ${schemaValue};
${err}.data = ${data};`
)
}
Expand All @@ -52,17 +56,17 @@ export function extendErrors(

function addError(gen: CodeGen, errObj: string): void {
const err = gen.const("err", errObj)
gen.if(_`vErrors === null`, _`vErrors = [${err}]`, _`vErrors.push(${err})`)
gen.code(_`errors++;`)
gen.if(_`${N.vErrors} === null`, _`${N.vErrors} = [${err}]`, _`${N.vErrors}.push(${err})`)
gen.code(_`${N.errors}++;`)
}

function returnErrors(gen: CodeGen, async: boolean, errs: string): void {
gen.code(
async
? `throw new ValidationError(${errs});`
: `validate.errors = ${errs};
return false;`
)
function returnErrors(gen: CodeGen, async: boolean, errs: Expression): void {
if (async) {
gen.code(`throw new ValidationError(${errs})`)
} else {
gen.assign(_`${N.validate}.errors`, errs)
gen.return("false")
}
}

function errorObjectCode(cxt: KeywordContext, error: KeywordErrorDefinition): string {
Expand All @@ -78,7 +82,7 @@ function errorObjectCode(cxt: KeywordContext, error: KeywordErrorDefinition): st
// TODO trim whitespace
let out = `{
keyword: "${keyword}",
dataPath: (dataPath || "") + ${errorPath},
dataPath: (${N.dataPath} || "") + ${errorPath},
schemaPath: ${quotedString(errSchemaPath + "/" + keyword)},
params: ${params ? params(cxt) : "{}"},`
if (propertyName) out += `propertyName: ${propertyName},`
Expand Down
15 changes: 7 additions & 8 deletions lib/compile/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import CodeGen, {nil, Expression, Code, Name} from "./codegen"
import CodeGen, {_, nil, Expression} from "./codegen"
import {toQuotedString} from "./util"
import {quotedString} from "../vocabularies/util"
import {validateFunctionCode} from "./validate"
import {validateKeywordSchema} from "./validate/keyword"
import {ErrorObject, KeywordCompilationResult} from "../types"
import N from "./names"

const equal = require("fast-deep-equal")
const ucs2length = require("./ucs2length")
Expand Down Expand Up @@ -109,17 +110,15 @@ function compile(schema, root, localRefs, baseId) {

const gen = new CodeGen()

const data = new Name("data")

validateFunctionCode({
gen,
allErrors: !!opts.allErrors,
data,
parentData: new Name("parentData"),
parentDataProperty: new Name("parentDataProperty"),
dataNames: [data],
data: N.data,
parentData: N.parentData,
parentDataProperty: N.parentDataProperty,
dataNames: [N.data],
dataPathArr: [nil],
topSchemaRef: new Code("validate.schema"),
topSchemaRef: _`${N.validate}.schema`,
async: _schema.$async === true,
schema: _schema,
isRoot,
Expand Down
15 changes: 13 additions & 2 deletions lib/compile/names.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,20 @@
import {Name} from "./codegen"

const names = {
validate: new Name("validate"),
validate: new Name("validate"), // validation function name
// validation function arguments
data: new Name("data"), // data passed to validation function
// args passed from referencing schema
dataPath: new Name("dataPath"),
rootData: new Name("rootData"),
parentData: new Name("parentData"),
parentDataProperty: new Name("parentDataProperty"),
rootData: new Name("rootData"), // data passed to the first/top validation function
// function scoped variables
vErrors: new Name("vErrors"), // null or array of validation errors
errors: new Name("errors"), // counter of validation errors
this: new Name("this"),
// "globals"
self: new Name("self"),
}

export default names
6 changes: 3 additions & 3 deletions lib/compile/util.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {_, Code, Name, Expression} from "./codegen"
import {CompilationContext} from "../types"
import names from "./names"
import N from "./names"

export function checkDataType(
dataType: string,
Expand Down Expand Up @@ -144,13 +144,13 @@ export function getData(
{dataLevel, dataNames, dataPathArr}: CompilationContext
): Expression | number {
let jsonPointer, data
if ($data === "") return names.rootData
if ($data === "") return N.rootData
if ($data[0] === "/") {
if (!JSON_POINTER.test($data)) {
throw new Error("Invalid JSON-pointer: " + $data)
}
jsonPointer = $data
data = names.rootData
data = N.rootData
} else {
const matches = RELATIVE_JSON_POINTER.exec($data)
if (!matches) throw new Error("Invalid JSON-pointer: " + $data)
Expand Down
6 changes: 4 additions & 2 deletions lib/compile/validate/boolSchema.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {KeywordErrorDefinition, CompilationContext, KeywordContext} from "../../types"
import {reportError} from "../errors"
import {_, Name} from "../codegen"
import N from "../names"

const boolError: KeywordErrorDefinition = {
message: "boolean schema is false",
Expand All @@ -11,9 +12,10 @@ export function topBoolOrEmptySchema(it: CompilationContext): void {
if (schema === false) {
falseSchemaError(it, false)
} else if (schema.$async === true) {
gen.code(_`return data;`)
gen.return(N.data)
} else {
gen.code(_`validate.errors = null; return true;`)
gen.assign(_`${N.validate}.errors`, "null")
gen.return(true)
}
}

Expand Down
49 changes: 25 additions & 24 deletions lib/compile/validate/dataType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {toHash, checkDataType, checkDataTypes} from "../util"
import {schemaHasRulesForType} from "./applicability"
import {reportError} from "../errors"
import {getKeywordContext} from "../../keyword"
import {_, Name} from "../codegen"
import {_, str, Name} from "../codegen"

export function getSchemaTypes({schema, opts}: CompilationContext): string[] {
const st: undefined | string | string[] = schema.type
Expand Down Expand Up @@ -59,7 +59,7 @@ export function coerceData(it: CompilationContext, coerceTo: string[]): void {
.if(checkDataType(schema.type, data, opts.strictNumbers), _`${coerced} = ${data}`)
)
}
gen.if(`${coerced} !== undefined`)
gen.if(_`${coerced} !== undefined`)
for (const t of coerceTo) {
if (t in COERCIBLE || (t === "array" && opts.coerceTypes === "array")) {
coerceSpecificType(t)
Expand All @@ -69,53 +69,53 @@ export function coerceData(it: CompilationContext, coerceTo: string[]): void {
reportTypeError(it)
gen.endIf()

gen.if(`${coerced} !== undefined`, () => {
gen.code(`${data} = ${coerced};`)
gen.if(_`${coerced} !== undefined`, () => {
gen.code(_`${data} = ${coerced};`)
assignParentData(it, coerced)
})

function coerceSpecificType(t) {
switch (t) {
case "string":
gen
.elseIf(`${dataType} == "number" || ${dataType} == "boolean"`)
.code(`${coerced} = "" + ${data}`)
.elseIf(`${data} === null`)
.code(`${coerced} = ""`)
.elseIf(_`${dataType} == "number" || ${dataType} == "boolean"`)
.code(_`${coerced} = "" + ${data}`)
.elseIf(_`${data} === null`)
.code(_`${coerced} = ""`)
return
case "number":
gen
.elseIf(
`${dataType} == "boolean" || ${data} === null
|| (${dataType} == "string" && ${data} && ${data} == +${data})`
_`${dataType} == "boolean" || ${data} === null
|| (${dataType} == "string" && ${data} && ${data} == +${data})`
)
.code(`${coerced} = +${data}`)
.code(_`${coerced} = +${data}`)
return
case "integer":
gen
.elseIf(
`${dataType} === "boolean" || ${data} === null
|| (${dataType} === "string" && ${data} && ${data} == +${data} && !(${data} % 1))`
_`${dataType} === "boolean" || ${data} === null
|| (${dataType} === "string" && ${data} && ${data} == +${data} && !(${data} % 1))`
)
.code(`${coerced} = +${data}`)
return
case "boolean":
gen
.elseIf(`${data} === "false" || ${data} === 0 || ${data} === null`)
.code(`${coerced} = false`)
.elseIf(`${data} === "true" || ${data} === 1`)
.code(`${coerced} = true`)
.elseIf(_`${data} === "false" || ${data} === 0 || ${data} === null`)
.code(_`${coerced} = false`)
.elseIf(_`${data} === "true" || ${data} === 1`)
.code(_`${coerced} = true`)
return
case "null":
gen.elseIf(`${data} === "" || ${data} === 0 || ${data} === false`)
gen.code(`${coerced} = null`)
gen.elseIf(_`${data} === "" || ${data} === 0 || ${data} === false`)
gen.code(_`${coerced} = null`)
return

case "array":
gen
.elseIf(
`${dataType} === "string" || ${dataType} === "number"
|| ${dataType} === "boolean" || ${data} === null`
_`${dataType} === "string" || ${dataType} === "number"
|| ${dataType} === "boolean" || ${data} === null`
)
.code(`${coerced} = [${data}]`)
}
Expand All @@ -127,15 +127,16 @@ function assignParentData(
expr: string | Name
): void {
// TODO use gen.property
gen.if(`${parentData} !== undefined`, () =>
gen.if(_`${parentData} !== undefined`, () =>
gen.assign(`${parentData}[${parentDataProperty}]`, expr)
)
}

const typeError: KeywordErrorDefinition = {
message: ({schema}) => `"should be ${Array.isArray(schema) ? schema.join(",") : <string>schema}"`,
message: ({schema}) =>
str`should be ${Array.isArray(schema) ? schema.join(",") : <string>schema}`,
// TODO change: return type as array here
params: ({schema}) => `{type: "${Array.isArray(schema) ? schema.join(",") : <string>schema}"}`,
params: ({schema}) => _`{type: ${Array.isArray(schema) ? schema.join(",") : <string>schema}}`,
}

export function reportTypeError(it: CompilationContext): void {
Expand Down
Loading

0 comments on commit 47d172c

Please sign in to comment.