diff --git a/implementations/javascript/packages/schema/src/compiler.ts b/implementations/javascript/packages/schema/src/compiler.ts index b3b5ca7..45d0ae3 100644 --- a/implementations/javascript/packages/schema/src/compiler.ts +++ b/implementations/javascript/packages/schema/src/compiler.ts @@ -3,7 +3,7 @@ import * as M from "./meta"; import { CompilerOptions, ModuleContext } from "./compiler/context"; import { brackets, Formatter, Item, parens, seq } from "./compiler/block"; import { typeForDefinition } from "./compiler/type"; -import { decoderFor } from "./compiler/decoder"; +// import { decoderFor } from "./compiler/decoder"; import { converterForDefinition } from "./compiler/converter"; export function compile(env: M.Environment, schema: M.Schema, options: CompilerOptions = {}): string { @@ -21,7 +21,8 @@ export function compile(env: M.Environment, schema: M.Schema, options: CompilerO ? '() => { throw new _.DecodeError("Pointers forbidden"); }' : seq(`(d: _.TypedDecoder<_ptr>) => `, ctx.block(() => [ seq(`let result`), - ... decoderFor(ctx, pointerName, 'result'), + seq(`/* TODO */`), + // ... decoderFor(ctx, pointerName, 'result'), seq(`return result`)]))), `;`)); @@ -35,7 +36,7 @@ export function compile(env: M.Environment, schema: M.Schema, options: CompilerO mod.defineFunction(ctx => seq(`export function as${name.description!}`, - '(v: any): ', name.description!, ' ', + '(v: _val): ', name.description!, ' ', ctx.block(() => [ seq(`let result = to${name.description!}(v)`), seq(`if (result === void 0) `, @@ -44,17 +45,18 @@ export function compile(env: M.Environment, schema: M.Schema, options: CompilerO mod.defineFunction(ctx => seq(`export function to${name.description!}`, - '(v: any): ', name.description!, ' | undefined ', - ctx.block(() => [seq(`let result`), + '(v: _val): undefined | ', name.description!, ' ', + ctx.block(() => [seq(`let result: undefined | `, name.description!), ... converterForDefinition(ctx, def, 'v', 'result'), seq(`return result`)]))); - mod.defineFunction(ctx => - seq(`export function decode${name.description!}`, - `(d: _.TypedDecoder<_ptr>): `, name.description!, ` | undefined `, - ctx.block(() => [seq(`let result`), - ... decoderFor(ctx, def, 'result'), - seq(`return result`)]))); + // mod.defineFunction(ctx => + // seq(`export function decode${name.description!}`, + // `(d: _.TypedDecoder<_ptr>): undefined | `, name.description!, + // ctx.block(() => [seq(`let result: undefined | `, name.description!), + // seq(`/* TODO */`), + // // ... decoderFor(ctx, def, 'result'), + // seq(`return result`)]))); } const f = new Formatter(); diff --git a/implementations/javascript/packages/schema/src/compiler/block.ts b/implementations/javascript/packages/schema/src/compiler/block.ts index 4461408..12c4f55 100644 --- a/implementations/javascript/packages/schema/src/compiler/block.ts +++ b/implementations/javascript/packages/schema/src/compiler/block.ts @@ -1,7 +1,9 @@ export type Item = Emittable | string; +export const DEFAULT_WIDTH = 80; + export class Formatter { - width = 80; + width = DEFAULT_WIDTH; indentDelta = ' '; currentIndent = '\n'; buffer: Array = []; @@ -42,15 +44,21 @@ export class Formatter { } } -export abstract class Emittable { - abstract writeOn(f: Formatter): void; +export function formatItems(i: Item[], width = DEFAULT_WIDTH): string { + const f = new Formatter(); + f.width = width; + i.forEach(i => f.write(i)); + return f.toString(); } -export class Sequence extends Emittable { +export interface Emittable { + writeOn(f: Formatter): void; +} + +export class Sequence implements Emittable { items: Array; constructor(items: Array) { - super(); if (items.some(i => i === void 0)) throw new Error('aiee'); this.items = items; } @@ -158,6 +166,12 @@ export const opseq = (zero: string, op: string, ... items: Item[]) => export const brackets = (... items: Item[]) => new Brackets(items); export const anglebrackets = (... items: Item[]) => new AngleBrackets(items); export const braces = (... items: Item[]) => new Braces(items); -export const block = (... items: Item[]) => new Block(items); +export const block = (... items: Item[]) => { + if (items.length === 1 && items[0] instanceof Block) { + return items[0]; + } else { + return new Block(items); + } +} export const fnblock = (... items: Item[]) => seq('((() => ', block(... items), ')())'); -export const keyvalue = (k: Item, v: Item) => seq(k, ': ', v); +export const keyvalue = (k: string, v: Item) => seq(JSON.stringify(k), ': ', v); diff --git a/implementations/javascript/packages/schema/src/compiler/context.ts b/implementations/javascript/packages/schema/src/compiler/context.ts index 36743be..e0e8a48 100644 --- a/implementations/javascript/packages/schema/src/compiler/context.ts +++ b/implementations/javascript/packages/schema/src/compiler/context.ts @@ -1,7 +1,7 @@ import { Dictionary, KeyedSet, Position } from "@preserves/core"; import { refPosition } from "../reader"; import * as M from "../meta"; -import { block, commas, Item, seq } from "./block"; +import { block, braces, commas, formatItems, Item, keyvalue, seq } from "./block"; export interface CompilerOptions { preservesModule?: string; @@ -9,6 +9,11 @@ export interface CompilerOptions { warn?(message: string, pos: Position | null): void; } +export interface Capture { + fieldName: string; + sourceExpr: string; +} + export class ModuleContext { readonly env: M.Environment; readonly schema: M.Schema; @@ -61,32 +66,68 @@ export class FunctionContext { readonly mod: ModuleContext; tempCounter = 0; - temps: string[] = []; + temps: Map = new Map(); + captures: Capture[] = []; + variantName: string | undefined = void 0; constructor(mod: ModuleContext) { this.mod = mod; } - gentemp(): string { - const varname = '_tmp' + this.tempCounter++; - this.temps.push(varname); - return varname; + gentempname(): string { + return '_tmp' + this.tempCounter++; } - gentemps(n: number): string[] { - const temps = []; - while (temps.length < n) temps.push(this.gentemp()); - return temps; + gentemp(... vartypePieces: Item[]): string { + const vartype = vartypePieces.length === 0 ? '_val | undefined' : seq(... vartypePieces); + const typestr = formatItems([vartype], Infinity); + const varname = this.gentempname(); + let e = this.temps.get(typestr); + if (e === void 0) { + e = { type: vartype, names: [] }; + this.temps.set(typestr, e); + } + e.names.push(varname); + return varname; } block(f: () => Item[]): Item { const oldTemps = this.temps; - this.temps = []; + this.temps = new Map(); const items = f(); const ts = this.temps; this.temps = oldTemps; return block( - ... ts.length > 0 ? [seq(`let `, commas(... ts), ': any')] : [], + ... Array.from(ts).map(([_typestr, { type, names }]) => + seq(`let `, commas(... names), `: `, type)), ... items); } + + withCapture(fieldName: string | undefined, + sourceExpr: string, + ks: (sourceExpr: string) => Item[], + withCheck = true): Item + { + if (fieldName !== void 0) this.captures.push({ fieldName, sourceExpr }); + const result = withCheck + ? seq(`if (${sourceExpr} !== void 0) `, this.block(() => ks(sourceExpr))) + : block(... ks(sourceExpr)); + if (fieldName !== void 0) this.captures.pop(); + return result; + } + + buildCapturedCompound(dest: string): Item { + return seq(`${dest} = `, braces( + ... variantInitFor(this.variantName), + ... this.captures.map(({ fieldName, sourceExpr }) => + keyvalue(fieldName, sourceExpr)))); + } +} + +export function variantInitFor(variantName: string | undefined) : Item[] { + return variantName === void 0 ? [] : [variantFor(variantName)]; +} + +export function variantFor(variantName: string): Item { + return keyvalue('_variant', JSON.stringify(variantName)); } diff --git a/implementations/javascript/packages/schema/src/compiler/converter.ts b/implementations/javascript/packages/schema/src/compiler/converter.ts index 155788a..b4519db 100644 --- a/implementations/javascript/packages/schema/src/compiler/converter.ts +++ b/implementations/javascript/packages/schema/src/compiler/converter.ts @@ -1,44 +1,8 @@ import { FunctionContext } from "./context"; import * as M from '../meta'; -import { block, braces, Item, keyvalue, seq } from "./block"; -import { typeFor, variantFor, variantInitFor } from "./type"; +import { block, Item, seq } from "./block"; +import { typeFor } from "./type"; import { refPosition } from "../reader"; -import { stringify } from "@preserves/core"; - -function converterForTuple(ctx: FunctionContext, - ps: M.NamedPattern[], - src: string, - dest: string, - variantName: string | undefined, - recordFields: boolean, - variablePattern: M.NamedSimplePattern | undefined): Item[] -{ - const temps = ctx.gentemps(ps.length); - - function loop(i: number): Item[] { - if (i < ps.length) { - return [...converterFor(ctx, M.unname(ps[i]), `${src}[${i}]`, temps[i]), - seq(`if (${temps[i]} !== void 0) `, ctx.block(() => loop(i + 1)))]; - } else { - if (variablePattern === void 0) { - return [seq(`${dest} = `, braces( - ... variantInitFor(variantName), - ... ps.flatMap((pp, i) => converterField(pp, temps[i]))))]; - } else { - return [ps.length > 0 ? `let vN = ${src}.slice(${ps.length})` : `let vN = ${src}`, - converterForArray(ctx, M.unname(variablePattern), 'vN', dest, false)]; - } - } - } - - const lengthCheck = variablePattern === void 0 - ? seq(`${src}.length === ${ps.length}`) - : seq(`${src}.length >= ${ps.length}`); - - return recordFields - ? loop(0) - : [seq(`if (_.Array.isArray(${src}) && `, lengthCheck, `) `, ctx.block(() => loop(0)))]; -} export function converterForDefinition( ctx: FunctionContext, @@ -48,65 +12,98 @@ export function converterForDefinition( { if (p.label === M.$or) { const alts = p[0]; - switch (alts.length) { - case 0: return []; // assume dest is already void 0 - case 1: return converterForAlternative(ctx, alts[0][1], src, dest, alts[0][0]); - default: { - function loop(i: number): Item[] { - return [ - ... converterForAlternative(ctx, alts[i][1], src, dest, alts[i][0]), - ... (i < alts.length - 1) - ? [seq(`if (${dest} === void 0) `, ctx.block(() => loop(i + 1)))] - : []]; - } - return loop(0); - } + function loop(i: number): Item[] { + ctx.variantName = alts[i][0]; + return [... converterForAlternative(ctx, alts[i][1], src, dest), + ... ((i < alts.length - 1) + ? [seq(`if (${dest} === void 0) `, ctx.block(() => loop(i + 1)))] + : [])]; } + return alts.length === 0 ? [] : loop(0); } else { - return converterForAlternative(ctx, p, src, dest, void 0); + ctx.variantName = void 0; + return converterForAlternative(ctx, p, src, dest); } } -function converterForAlternative(ctx: FunctionContext, p: M.Alternative, src: string, dest: string, variantName: string | undefined): Item[] { +function converterForAlternative( + ctx: FunctionContext, + p: M.Alternative, + src: string, + dest: string): Item[] +{ if (p.label === M.$and) { - switch (p[0].length) { - case 0: return [`${dest} = ${src}`]; - case 1: { - return converterFor(ctx, M.unname(p[0][0]), src, dest, variantName); + const alts = p[0]; + function loop(i: number): Item[] { + return (i < alts.length) + ? converterFor(ctx, alts[i], src, () => loop(i + 1)) + : [ctx.buildCapturedCompound(dest)]; + } + return alts.length === 0 ? [seq(`${dest} = ${src}`)] : loop(0); + } else { + return converterFor(ctx, p, src, simpleValue => { + if (simpleValue === void 0) { + return [ctx.buildCapturedCompound(dest)]; + } else if (ctx.variantName !== void 0) { + return [ctx.withCapture('value', + simpleValue, + () => [ctx.buildCapturedCompound(dest)], + false)]; + } else { + return [`${dest} = ${simpleValue}`]; } - default: { - const alts = p[0]; - const temps = ctx.gentemps(alts.length); - function loop(i: number): Item[] { - return (i < temps.length) - ? [...converterFor(ctx, M.unname(alts[i]), src, temps[i]), - seq(`if (${temps[i]} !== void 0) `, ctx.block(() => loop(i + 1)))] - : [seq(`${dest} = `, braces( - ... variantInitFor(variantName), - ... alts.flatMap((pp, i) => converterField(pp, temps[i]))))]; - } - return loop(0); + }); + } +} + +function converterForTuple(ctx: FunctionContext, + ps: M.NamedPattern[], + src: string, + recordFields: boolean, + variablePattern: M.NamedSimplePattern | undefined, + k: () => Item[]): Item[] +{ + function loop(i: number): Item[] { + if (i < ps.length) { + return converterFor(ctx, ps[i], `${src}[${i}]`, () => loop(i + 1)); + } else { + if (variablePattern === void 0) { + return k(); + } else { + const vN = ctx.gentemp(`Array<_val>`); + return [ps.length > 0 ? `${vN} = ${src}.slice(${ps.length})` : `${vN} = ${src}`, + converterForArray(ctx, variablePattern, vN, false, k)]; } } - } else { - return converterFor(ctx, p, src, dest, variantName); } + + const lengthCheck = variablePattern === void 0 + ? seq(` && ${src}.length === ${ps.length}`) + : ((ps.length === 0) ? '' : seq(` && ${src}.length >= ${ps.length}`)); + + return recordFields + ? loop(0) + : [seq(`if (_.Array.isArray(${src})`, lengthCheck, `) `, ctx.block(() => loop(0)))]; } function converterForArray(ctx: FunctionContext, - arrayType: M.SimplePattern, + arrayType: M.NamedSimplePattern, src: string, - dest: string, - checkArray: boolean): Item + checkArray: boolean, + k: (dest: string) => Item[]): Item { - const postCheck = () => [ - seq(`let r: Array<`, typeFor(ctx.mod, arrayType), `> | undefined = []`), - seq(`for (const v of ${src}) `, ctx.block(() => [ - seq(`let vv`), - ... converterFor(ctx, arrayType, 'v', 'vv'), - seq(`if (vv === void 0) { r = void 0; break; }`), - seq(`r.push(vv)`)])), - seq(`${dest} = r`)]; + const postCheck = () => { + const r = ctx.gentemp( + `Array<`, typeFor(ctx.mod, M.unname(arrayType)), `> | undefined`); + const v = ctx.gentempname(); + return [ + seq(`${r} = []`), + seq(`for (const ${v} of ${src}) `, ctx.block(() => [ + ... converterFor(ctx, arrayType, v, vv => [`${r}.push(${vv})`, `continue`]), + seq(`${r} = void 0`), + seq(`break`)])), + ctx.withCapture(M.nameFor(arrayType), r, k)]; + }; return (checkArray ? seq(`if (_.Array.isArray(${src})) `, ctx.block(postCheck)) : block(... postCheck())); @@ -114,67 +111,61 @@ function converterForArray(ctx: FunctionContext, function converterFor( ctx: FunctionContext, - p: M.Pattern, + np: M.NamedPattern, src: string, - dest: string, - variantName?: string, + ks: (dest: string | undefined) => Item[], recordFields = false): Item[] { - let converterItem: Item[]; - const unlabeled = variantName === void 0 ? dest : ctx.gentemp(); + let p = M.unname(np); + let maybeName = M.nameFor(np); if (M.isSimplePattern(p)) { - converterItem = converterForSimple(ctx, p, src, unlabeled); + const dest = ctx.gentemp(typeFor(ctx.mod, p), ` | undefined`); + return [... converterForSimple(ctx, p, src, dest), + ctx.withCapture(maybeName, dest, ks)]; } else { switch (p.label) { - case M.$setof: - // assume dest is already void 0 - converterItem = [ - seq(`if (_.Set.isSet(${src})) `, ctx.block(() => [ - seq(`let r: `, typeFor(ctx.mod, p), ` | undefined = new _.KeyedSet()`), - seq(`for (const v of ${src}) `, ctx.block(() => [ - seq(`let vv`), - ... converterFor(ctx, p[0], 'v', 'vv'), - seq(`if (vv === void 0) { r = void 0; break; }`), - seq(`r.add(vv)`)])), - seq(`${unlabeled} = r`)]))]; - break; - case M.$dictof: - // assume dest is already void 0 - converterItem = [ - seq(`if (_.Dictionary.isDictionary(${src})) `, ctx.block(() => [ - seq(`let r: `, typeFor(ctx.mod, p), ` | undefined = new _.KeyedDictionary()`), - seq(`for (const [k, v] of ${src}) `, ctx.block(() => [ - seq(`let kk`), - ... converterFor(ctx, p[0], 'k', 'kk'), - seq(`if (kk === void 0) { r = void 0; break; }`), - seq(`let vv`), - ... converterFor(ctx, p[1], 'v', 'vv'), - seq(`if (vv === void 0) { r = void 0; break; }`), - seq(`r.set(kk, vv)`)])), - seq(`${unlabeled} = r`)]))]; - break; + case M.$setof: { + const setPattern = p[0]; + const r = ctx.gentemp(typeFor(ctx.mod, p), ` | undefined`); + const v = ctx.gentempname(); + return [ + seq(`if (_.Set.isSet<_ptr>(${src})) `, ctx.block(() => [ + seq(`${r} = new _.KeyedSet()`), + seq(`for (const ${v} of ${src}) `, ctx.block(() => [ + ... converterFor(ctx, setPattern, v, vv => + [`${r}.add(${vv})`, `continue`]), + seq(`${r} = void 0`), + seq(`break`)])), + ctx.withCapture(maybeName, r, ks)]))]; + } + case M.$dictof: { + const keyPattern = p[0]; + const valPattern = p[1]; + const r = ctx.gentemp(typeFor(ctx.mod, p), ` | undefined`); + const v = ctx.gentempname(); + const k = ctx.gentempname(); + return [ + seq(`if (_.Dictionary.isDictionary<_ptr>(${src})) `, ctx.block(() => [ + seq(`${r} = new _.KeyedDictionary()`), + seq(`for (const [${k}, ${v}] of ${src}) `, ctx.block(() => [ + ... converterFor(ctx, keyPattern, k, kk => + converterFor(ctx, valPattern, v, vv => + [`${r}.set(${kk}, ${vv})`, `continue`])), + seq(`${r} = void 0`), + seq(`break`)])), + ctx.withCapture(maybeName, r, ks)]))]; + } default: { const arrayType = M.simpleArray(p); if (arrayType === void 0) { - return converterForCompound(ctx, p, src, dest, variantName, recordFields); + return converterForCompound(ctx, p, src, recordFields, () => ks(void 0)); } else { - converterItem = [ - converterForArray(ctx, arrayType, src, unlabeled, !recordFields)]; - break; + return [converterForArray(ctx, arrayType, src, !recordFields, ks)]; } } } } - - if (variantName === void 0) { - return converterItem; - } else { - return [... converterItem, - seq(`if (${unlabeled} !== void 0) ${dest} = `, braces( - variantFor(variantName), - keyvalue('value', unlabeled)))]; - } } function converterForSimple( @@ -198,11 +189,11 @@ function converterForSimple( return [seq(`${dest} = `, test, ` ? ${src} : void 0`)]; } case M.$lit: - return [`${dest} = _.is(${src}, ${ctx.mod.literal(p[0])}) ? ${src} : void 0`]; + return [`${dest} = _.is(${src}, ${ctx.mod.literal(p[0])}) ? ${ctx.mod.literal(p[0])} : void 0`]; case M.$ref: return M.lookup(refPosition(p), p, ctx.mod.env, (_p) => [`${dest} = to${p[1].description!}(${src})`], - (p) => converterForAlternative(ctx, p, src, dest, void 0), + (p) => converterForAlternative(ctx, p, src, dest), (modId, modPath,_p) => { ctx.mod.imports.add([modId, modPath]); return [`${dest} = ${modId}.decode${p[1].description!}(${src})`]; @@ -219,74 +210,39 @@ function converterForCompound( ctx: FunctionContext, p: M.CompoundPattern, src: string, - dest: string, - variantName: string | undefined, - recordFields: boolean): Item[] + recordFields: boolean, + ks: () => Item[]): Item[] { switch (p.label) { case M.$rec: - // assume dest is already void 0 - return [seq(`if (_.Record.isRecord(${src})) `, ctx.block(() => { - const label = ctx.gentemp(); - return [...converterFor(ctx, M.unname(p[0]), `${src}.label`, label), - seq(`if (${label} !== void 0) `, ctx.block(() => { - const fs = ctx.gentemp(); - return [...converterFor(ctx, M.unname(p[1]), src, fs, void 0, true), - seq(`if (${fs} !== void 0) ${dest} = `, - braces(... variantInitFor(variantName), - ... converterField(p[0], label), - `... ${fs}`))]; - }))]; - }))]; + return [seq(`if (_.Record.isRecord<_val, _.Tuple<_val>, _ptr>(${src})) `, ctx.block(() => + converterFor(ctx, p[0], `${src}.label`, () => + converterFor(ctx, p[1], src, ks, true))))]; case M.$tuple: - // assume dest is already void 0 - return converterForTuple(ctx, p[0], src, dest, variantName, recordFields, void 0); + return converterForTuple(ctx, p[0], src, recordFields, void 0, ks); case M.$tuple_STAR_: - // assume dest is already void 0 - return converterForTuple(ctx, p[0], src, dest, variantName, recordFields, p[1]); + return converterForTuple(ctx, p[0], src, recordFields, p[1], ks); case M.$setof: case M.$dictof: throw new Error('Internal error: setof and dictof are handled in converterFor()'); case M.$dict: { const entries = Array.from(p[0]); - const temps = ctx.gentemps(entries.length); function loop(i: number): Item[] { if (i < entries.length) { const [k, n] = entries[i]; const tmpSrc = ctx.gentemp(); - return [ - seq(`if ((${tmpSrc} = ${src}.get(${ctx.mod.literal(k)})) !== void 0) `, - ctx.block(() => [ - ...converterFor(ctx, M.unname(n), tmpSrc, temps[i]), - seq(`if (${temps[i]} !== void 0) `, ctx.block(() => - loop(i + 1)))]))]; + return [seq(`if ((${tmpSrc} = ${src}.get(${ctx.mod.literal(k)})) !== void 0) `, + ctx.block(() => + converterFor(ctx, M.addNameIfAbsent(n, k), tmpSrc, () => + loop(i + 1))))]; } else { - return [ - seq(`${dest} = `, braces( - ... variantInitFor(variantName), - ... entries.flatMap(([k, n], i) => converterField(n, temps[i], k))))]; + return ks(); } } - return [seq(`if (_.Dictionary.isDictionary(${src})) `, ctx.block(() => loop(0)))]; + return [seq(`if (_.Dictionary.isDictionary<_ptr>(${src})) `, ctx.block(() => loop(0)))]; } default: ((_p: never) => {})(p); throw new Error("Unreachable"); } } - -function converterField(n: M.NamedPattern, src: string, k?: M.Input): Item[] { - if (n.label === M.$named) { - return [keyvalue(stringify(n[0]), src)]; - } - if (k !== void 0) { - const s = M.namelike(k); - if (s !== void 0) { - return [keyvalue(JSON.stringify(s), src)]; - } - } - if (M.isCompoundPattern(n)) { - return [`... ${src}`]; - } - return []; -} diff --git a/implementations/javascript/packages/schema/src/compiler/decoder.ts b/implementations/javascript/packages/schema/src/compiler/decoder.ts index c4ec381..8878336 100644 --- a/implementations/javascript/packages/schema/src/compiler/decoder.ts +++ b/implementations/javascript/packages/schema/src/compiler/decoder.ts @@ -1,9 +1,9 @@ +/* import { FunctionContext } from "./context"; import * as M from '../meta'; import { anglebrackets, block, brackets, Item, opseq, parens, seq } from "./block"; import { typeFor } from './type'; import { refPosition } from "../reader"; -import { predicateFor } from "./predicate"; function decodeCompound(ctx: FunctionContext, p: M.Pattern, @@ -206,3 +206,4 @@ export function decoderFor(ctx: FunctionContext, p: M.Definition, dest: string, throw new Error("Unreachable"); } } +*/ diff --git a/implementations/javascript/packages/schema/src/compiler/predicate.ts b/implementations/javascript/packages/schema/src/compiler/predicate.ts deleted file mode 100644 index 706e811..0000000 --- a/implementations/javascript/packages/schema/src/compiler/predicate.ts +++ /dev/null @@ -1,98 +0,0 @@ -import { refPosition } from '../reader'; -import * as M from '../meta'; -import { block, fnblock, Item, opseq, parens, seq } from './block'; -import { FunctionContext } from './context'; - -export function predicateFor(ctx: FunctionContext, v: string, p: M.Definition, recordOkAsTuple = false): Item -{ - switch (p.label) { - case M.$atom: - switch (p[0]) { - case M.$Boolean: return `typeof ${v} === 'boolean'`; - case M.$Float: return `_.Float.isSingle(${v})`; - case M.$Double: return `_.Float.isDouble(${v})`; - case M.$SignedInteger: return `typeof ${v} === 'number'`; - case M.$String: return `typeof ${v} === 'string'`; - case M.$ByteString: return `_.Bytes.isBytes(${v})`; - case M.$Symbol: return `typeof ${v} === 'symbol'`; - } - case M.$lit: - return `_.is(${v}, ${ctx.mod.literal(p[0])})`; - case M.$ref: - return M.lookup(refPosition(p), p, ctx.mod.env, - (_p) => `is${M.Ref._.name(p).description!}(${v})`, - (pp) => predicateFor(ctx, v, pp), - (modId, modPath, _p) => { - ctx.mod.imports.add([modId, modPath]); - return `${modId}.is${M.Ref._.name(p).description!}(${v})`; - }); - case M.$or: { - const alts = p[0]; - const recs = alts.map(p => ctx.mod.derefPattern(p)); - if (recs.length > 1 && recs.every(pp => pp.label === M.$rec)) { - return seq( - `_.Record.isRecord<_val, _.Tuple<_val>, _ptr>(${v}) && `, - parens(opseq('false', ' || ', - ... recs.map(r => - (r.label !== M.$rec) ? '' : parens(seq( - predicateFor(ctx, `${v}.label`, M.unname(r[0])), - ' && ', - predicateFor(ctx, v, M.unname(r[1]), true))))))); - } else { - return opseq('false', ' || ', ... p[0].map(pp => predicateFor(ctx, v, pp[1]))); - } - } - case M.$and: - return opseq('true', ' && ', ...p[0].map(pp => predicateFor(ctx, v, M.unname(pp)))); - case M.$pointer: - return `_.isPointer(${v})`; - case M.$rec: - return opseq('true', ' && ', - `_.Record.isRecord<_val, _.Tuple<_val>, _ptr>(${v})`, - predicateFor(ctx, `${v}.label`, M.unname(p[0])), - predicateFor(ctx, v, M.unname(p[1]), true)); - case M.$tuple: - return opseq('true', ' && ', - ... (recordOkAsTuple ? [] - : [`_.Array.isArray(${v})`, `!_.Record.isRecord<_val, _.Tuple<_val>, _ptr>(${v})`]), - `(${v}.length === ${p[0].length})`, - ...p[0].map((pp, i) => predicateFor(ctx, `${v}[${i}]`, M.unname(pp)))); - case M.$tuple_STAR_: - return opseq('true', ' && ', - ... (recordOkAsTuple ? [] - : [`_.Array.isArray(${v})`, `!_.Record.isRecord<_val, _.Tuple<_val>, _ptr>(${v})`]), - `(${v}.length >= ${p[0].length})`, - seq(p[0].length > 0 ? `${v}.slice(${p[0].length})` : v, - `.every(v => `, - parens(predicateFor(ctx, 'v', M.unname(p[1]))), - `)`), - ...p[0].map((pp, i) => predicateFor(ctx, `${v}[${i}]`, M.unname(pp)))); - case M.$setof: - return opseq('true', ' && ', - `_.Set.isSet<_val>(${v})`, - fnblock( - seq(`for (const vv of ${v}) `, block( - seq('if (!(', predicateFor(ctx, 'vv', p[0]), ')) return false'))), - seq('return true'))); - case M.$dictof: - return opseq('true', ' && ', - `_.Dictionary.isDictionary<_ptr>(${v})`, - fnblock( - seq(`for (const e of ${v}) `, block( - seq('if (!(', predicateFor(ctx, 'e[0]', p[0]), ')) return false'), - seq('if (!(', predicateFor(ctx, 'e[1]', p[1]), ')) return false'))), - seq('return true'))); - case M.$dict: - return opseq('true', ' && ', - `_.Dictionary.isDictionary<_ptr>(${v})`, - ... Array.from(p[0]).map(([k, vp]) => { - const tmp = ctx.gentemp(); - return parens(seq( - `(${tmp} = ${v}.get(${ctx.mod.literal(k)})) !== void 0 && `, - predicateFor(ctx, tmp, M.unname(vp)))); - })); - default: - ((_p: never) => {})(p); - throw new Error("Unreachable"); - } -} diff --git a/implementations/javascript/packages/schema/src/compiler/type.ts b/implementations/javascript/packages/schema/src/compiler/type.ts index ff017fd..8e62cd9 100644 --- a/implementations/javascript/packages/schema/src/compiler/type.ts +++ b/implementations/javascript/packages/schema/src/compiler/type.ts @@ -1,8 +1,7 @@ import { refPosition } from "../reader"; import * as M from "../meta"; import { anglebrackets, braces, Item, keyvalue, opseq, seq } from "./block"; -import { ModuleContext } from "./context"; -import { stringify } from "@preserves/core"; +import { ModuleContext, variantFor, variantInitFor } from "./context"; export function typeFor(mod: ModuleContext, p: M.Pattern, variantName?: string): Item { let typeItem: Item; @@ -71,7 +70,7 @@ function typeForSimple(mod: ModuleContext, p: M.SimplePattern): Item { function typeField(mod: ModuleContext, n: M.NamedPattern): Item[] { return (n.label === M.$named) - ? [keyvalue(stringify(n[0]), typeForSimple(mod, n[1]))] + ? [keyvalue(n[0].description!, typeForSimple(mod, n[1]))] : (M.isCompoundPattern(n) ? typeForCompound(mod, n) : []); @@ -87,7 +86,7 @@ function typeForCompound(mod: ModuleContext, p: M.CompoundPattern): Item[] { const n = p[1]; return [... p[0].flatMap(pp => typeField(mod, pp)), ... ((n.label === M.$named) - ? [keyvalue(stringify(n[0]), + ? [keyvalue(n[0].description!, seq('Array<', typeForSimple(mod, n[1]), '>'))] : [])]; } @@ -101,7 +100,7 @@ function typeForCompound(mod: ModuleContext, p: M.CompoundPattern): Item[] { } else { const s = M.namelike(k); if (s !== void 0) { - return [keyvalue(JSON.stringify(s), typeForSimple(mod, n))]; + return [keyvalue(s, typeForSimple(mod, n))]; } else { return []; } @@ -121,14 +120,6 @@ export function typeForDefinition(mod: ModuleContext, d: M.Definition): Item { } } -export function variantInitFor(variantName: string | undefined) : Item[] { - return variantName === void 0 ? [] : [variantFor(variantName)]; -} - -export function variantFor(variantName: string): Item { - return keyvalue('_variant', JSON.stringify(variantName)); -} - function typeForAlternative(mod: ModuleContext, a: M.Alternative, variantName: string | undefined): Item { if (a.label === M.$and) { return opseq('_val', ' & ', diff --git a/implementations/javascript/packages/schema/src/meta.ts b/implementations/javascript/packages/schema/src/meta.ts index 1a8b618..5067533 100644 --- a/implementations/javascript/packages/schema/src/meta.ts +++ b/implementations/javascript/packages/schema/src/meta.ts @@ -83,6 +83,25 @@ export function unname( return (p.label === M.$named) ? p[1] : p; } +export function nameFor( + p: M.NamedSimplePattern_ | R): string | undefined +{ + return (p.label === M.$named) ? stringify(p[0]) : void 0; +} + +export function addNameIfAbsent(p: M.NamedSimplePattern, k: Input): M.NamedSimplePattern { + if (p.label === M.$named) { + return p; + } else { + const s = namelike(k); + if (s !== void 0) { + return M.NamedSimplePattern_(Symbol.for(s), p); + } else { + return p; + } + } +} + // Simple arrays at toplevel for convenience // export function simpleArray(p: M.CompoundPattern): M.SimplePattern | undefined { diff --git a/schema/schema.prs b/schema/schema.prs index 394bff4..1cc15fa 100644 --- a/schema/schema.prs +++ b/schema/schema.prs @@ -19,7 +19,7 @@ Definitions = { symbol: Definition ...:... }. ; and the empty pattern is Definition = / Alternative . -NamedAlternative = [@variant string @alternative Alternative]. +NamedAlternative = [@variantLabel string @alternative Alternative]. ; Pattern & Pattern & ... ; and the universal pattern, "any", is