2021-03-11 09:56:49 +00:00
|
|
|
import { Pattern, NamedPattern, Schema, Input, Environment, Ref, lookup } from "./meta";
|
2021-03-09 14:59:40 +00:00
|
|
|
import * as M from './meta';
|
2021-03-11 16:59:40 +00:00
|
|
|
import { Annotated, Bytes, Dictionary, Fold, fold, KeyedSet, Position, preserves, Record, Set, Tuple, Value } from "@preserves/core";
|
2021-03-09 15:45:57 +00:00
|
|
|
import { Formatter, parens, seq, Item, opseq, block, commas, brackets, anglebrackets, braces } from "./block";
|
2021-03-11 13:43:06 +00:00
|
|
|
import { refPosition } from "./reader";
|
2021-03-09 14:59:40 +00:00
|
|
|
|
2021-03-11 16:59:40 +00:00
|
|
|
export interface CompilerOptions {
|
|
|
|
preservesModule?: string;
|
|
|
|
defaultPointer?: Ref;
|
|
|
|
warn?(message: string, pos: Position | null): void;
|
|
|
|
};
|
|
|
|
|
2021-03-09 14:59:40 +00:00
|
|
|
function fnblock(... items: Item[]): Item {
|
|
|
|
return seq('((() => ', block(... items), ')())');
|
|
|
|
}
|
|
|
|
|
2021-03-11 16:59:40 +00:00
|
|
|
export function compile(env: Environment, schema: Schema, options: CompilerOptions = {}): string {
|
2021-03-09 14:59:40 +00:00
|
|
|
const literals = new Dictionary<string, never>();
|
|
|
|
const types: Array<Item> = [];
|
2021-03-11 08:25:17 +00:00
|
|
|
const functions: Array<Item> = [];
|
2021-03-11 09:56:49 +00:00
|
|
|
const imports = new KeyedSet<[string, string]>();
|
2021-03-09 14:59:40 +00:00
|
|
|
let temps: Array<string> = [];
|
2021-03-14 20:10:47 +00:00
|
|
|
let body: Array<Item> = [];
|
|
|
|
let tempCounter = 0;
|
2021-03-11 16:59:40 +00:00
|
|
|
const pointerName = Schema._.details(schema).get(M.$pointer);
|
2021-03-09 14:59:40 +00:00
|
|
|
|
|
|
|
function gentemp(): string {
|
2021-03-14 20:10:47 +00:00
|
|
|
const varname = '_tmp' + tempCounter++;
|
2021-03-09 14:59:40 +00:00
|
|
|
temps.push(varname);
|
|
|
|
return varname;
|
|
|
|
}
|
|
|
|
|
|
|
|
function literal(v: Input): Item {
|
|
|
|
let varname = literals.get(v);
|
|
|
|
if (varname === void 0) {
|
2021-03-10 22:15:53 +00:00
|
|
|
const s = v.asPreservesText()
|
|
|
|
.replace('_', '__')
|
|
|
|
.replace('*', '_STAR_');
|
|
|
|
varname = M.isValidToken('_' + s, true) ? '$' + s : '__lit' + literals.size;
|
2021-03-09 14:59:40 +00:00
|
|
|
literals.set(v, varname);
|
|
|
|
}
|
|
|
|
return varname;
|
|
|
|
}
|
|
|
|
|
2021-03-14 20:10:47 +00:00
|
|
|
function emit(item: Item): void {
|
|
|
|
body.push(item);
|
|
|
|
}
|
|
|
|
|
|
|
|
function collectBody(f: () => void): Item {
|
|
|
|
const oldTemps = temps;
|
|
|
|
const oldBody = body;
|
|
|
|
temps = []
|
|
|
|
body = [];
|
|
|
|
f();
|
|
|
|
const ts = temps;
|
|
|
|
const result = body;
|
|
|
|
temps = oldTemps;
|
|
|
|
body = oldBody;
|
|
|
|
return block(
|
|
|
|
... ts.length > 0 ? [seq('let ', commas(... ts), ': any')] : [],
|
|
|
|
... result);
|
|
|
|
}
|
|
|
|
|
|
|
|
function accumulateCompound(p: Pattern,
|
|
|
|
kFail: () => Item[],
|
|
|
|
kAcc: (temp: string) => Item[]): Item
|
|
|
|
{
|
|
|
|
const t = gentemp();
|
|
|
|
return seq(`while (!d.closeCompound()) `, collectBody(() => {
|
|
|
|
emit(seq(`${t} = void 0`));
|
|
|
|
decoderFor(p, t);
|
|
|
|
emit(seq(`if (${t} === void 0) `, block(
|
|
|
|
... kFail(),
|
|
|
|
seq(`break`))));
|
|
|
|
kAcc(t).forEach(emit);
|
|
|
|
}));
|
|
|
|
}
|
|
|
|
|
|
|
|
function derefPattern(p: Pattern): Pattern {
|
|
|
|
if (p.label === M.$ref) {
|
|
|
|
return lookup(refPosition(p), p, env,
|
|
|
|
(p) => p,
|
|
|
|
(p) => p,
|
|
|
|
(_mod, _modPath, pp) => pp ?? p);
|
|
|
|
} else {
|
|
|
|
return p;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function decoderForTuple(ps: Pattern[],
|
|
|
|
dest: string,
|
|
|
|
recordFields: boolean,
|
|
|
|
variablePattern: Pattern | undefined): void {
|
|
|
|
const temps = ps.map(gentemp);
|
|
|
|
function loop(i: number) {
|
|
|
|
if (i < ps.length) {
|
|
|
|
decoderFor(ps[i], temps[i]);
|
|
|
|
emit(seq(`if (${temps[i]} !== void 0) `,
|
|
|
|
collectBody(() => loop(i + 1))));
|
|
|
|
} else {
|
|
|
|
if (variablePattern === void 0) {
|
|
|
|
emit(seq(`if (d.closeCompound()) ${dest} = `, brackets(... temps)));
|
|
|
|
} else {
|
|
|
|
emit(block(
|
|
|
|
seq(`let vN: Array<`, typeFor(variablePattern),
|
|
|
|
`> | undefined = `, brackets(... temps)),
|
|
|
|
accumulateCompound(variablePattern,
|
|
|
|
() => [`vN = void 0`],
|
|
|
|
(t) => [`vN.push(${t})`]),
|
|
|
|
seq(`${dest} = vN`)));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (recordFields) {
|
|
|
|
loop(0);
|
|
|
|
} else {
|
|
|
|
emit(seq(`if (d.openSequence()) `, collectBody(() => loop(0))));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function decoderFor(p: Pattern, dest: string, recordFields = false): void {
|
2021-03-12 19:41:35 +00:00
|
|
|
switch (p.label) {
|
|
|
|
case M.$atom:
|
|
|
|
switch (p[0]) {
|
2021-03-14 20:10:47 +00:00
|
|
|
case M.$Boolean: emit(`${dest} = d.nextBoolean()`); break;
|
|
|
|
case M.$Float: emit(`${dest} = d.nextFloat()`); break;
|
|
|
|
case M.$Double: emit(`${dest} = d.nextDouble()`); break;
|
|
|
|
case M.$SignedInteger: emit(`${dest} = d.nextSignedInteger()`); break;
|
|
|
|
case M.$String: emit(`${dest} = d.nextString()`); break;
|
|
|
|
case M.$ByteString: emit(`${dest} = d.nextByteString()`); break;
|
|
|
|
case M.$Symbol: emit(`${dest} = d.nextSymbol()`); break;
|
2021-03-12 19:41:35 +00:00
|
|
|
}
|
2021-03-14 20:10:47 +00:00
|
|
|
break;
|
|
|
|
case M.$lit: {
|
|
|
|
let n: string;
|
2021-03-12 19:41:35 +00:00
|
|
|
switch (typeof p[0]) {
|
2021-03-14 20:10:47 +00:00
|
|
|
case 'boolean': n = `d.nextBoolean()`; break;
|
|
|
|
case 'string': n = `d.nextString()`; break;
|
|
|
|
case 'number': n = `d.nextSignedInteger()`; break;
|
|
|
|
case 'symbol': n = `d.nextSymbol()`; break;
|
|
|
|
default: n = `d.next()`; break;
|
2021-03-12 19:41:35 +00:00
|
|
|
}
|
2021-03-14 20:10:47 +00:00
|
|
|
emit(`${dest} = _.asLiteral(${n}, ${literal(p[0])})`);
|
|
|
|
break;
|
|
|
|
}
|
2021-03-12 19:41:35 +00:00
|
|
|
case M.$ref:
|
2021-03-14 20:10:47 +00:00
|
|
|
lookup(refPosition(p), p, env,
|
|
|
|
(_p) => emit(`${dest} = decode${p[1].description!}(d)`),
|
|
|
|
(p) => decoderFor(p, dest),
|
|
|
|
(mod, modPath,_p) => {
|
|
|
|
imports.add([mod, modPath]);
|
|
|
|
emit(`${dest} = ${mod}.decode${p[1].description!}(d)`);
|
|
|
|
});
|
|
|
|
break;
|
|
|
|
case M.$or: {
|
|
|
|
const alts = p[0];
|
|
|
|
const recs = alts.map(derefPattern);
|
|
|
|
if (recs.length > 1 && recs.every(pp => pp.label === M.$rec)) {
|
|
|
|
// Hoist the record check up.
|
|
|
|
// This is pretty hacky. If we lift the level of
|
|
|
|
// discourse a little, we can do this
|
|
|
|
// automatically and generically...
|
|
|
|
emit(seq(`if (d.openRecord()) `, collectBody(() => {
|
|
|
|
const label = gentemp();
|
|
|
|
emit(seq(`${label} = d.next()`));
|
|
|
|
const mark = gentemp();
|
|
|
|
emit(seq(`${mark} = d.mark()`));
|
|
|
|
function loop(i: number) {
|
|
|
|
const alt = recs[i];
|
|
|
|
if (alt.label !== M.$rec) return; // avoid a cast
|
|
|
|
emit(seq(`if (`, predicateFor(label, alt[0]), `) `, collectBody(() => {
|
|
|
|
const fs = gentemp();
|
|
|
|
decoderFor(alt[1], fs, true);
|
|
|
|
emit(seq(`if (${fs} !== void 0) ${dest} = _.Record`,
|
|
|
|
anglebrackets(typeFor(alt[0]), typeFor(alt[1])),
|
|
|
|
parens(seq(label, ` as any`),
|
|
|
|
seq(fs, ` as any`))));
|
|
|
|
})));
|
|
|
|
if (i < recs.length - 1) {
|
|
|
|
emit(seq(`if (${dest} === void 0) `, collectBody(() => {
|
|
|
|
emit(`d.restoreMark(${mark})`);
|
|
|
|
loop(i + 1);
|
|
|
|
})));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
loop(0);
|
|
|
|
})));
|
|
|
|
} else {
|
|
|
|
switch (alts.length) {
|
|
|
|
case 0: break; // assume dest is already void 0
|
|
|
|
case 1: decoderFor(alts[0], dest); break;
|
|
|
|
default: {
|
|
|
|
const mark = gentemp();
|
|
|
|
emit(`${mark} = d.mark()`);
|
|
|
|
function loop(i: number) {
|
|
|
|
decoderFor(alts[i], dest);
|
|
|
|
if (i < alts.length - 1) {
|
|
|
|
emit(seq(`if (${dest} === void 0) `, collectBody(() => {
|
|
|
|
emit(`d.restoreMark(${mark})`);
|
|
|
|
loop(i + 1);
|
|
|
|
})));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
loop(0);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
2021-03-12 19:41:35 +00:00
|
|
|
case M.$and:
|
|
|
|
switch (p[0].length) {
|
2021-03-14 20:10:47 +00:00
|
|
|
case 0: emit(`${dest} = d.next()`); break;
|
|
|
|
case 1: decoderFor(p[0][0], dest); break;
|
2021-03-12 19:41:35 +00:00
|
|
|
default: {
|
|
|
|
const [pp0, ... ppN] = p[0];
|
2021-03-14 20:10:47 +00:00
|
|
|
decoderFor(pp0, dest);
|
2021-03-12 19:41:35 +00:00
|
|
|
const otherChecks =
|
2021-03-14 20:10:47 +00:00
|
|
|
opseq('true', ' && ', ... ppN.map(pp => predicateFor(dest, pp)));
|
|
|
|
emit(seq(`if (!`, otherChecks, `) ${dest} = void 0`));
|
|
|
|
break;
|
2021-03-12 19:41:35 +00:00
|
|
|
}
|
|
|
|
}
|
2021-03-14 20:10:47 +00:00
|
|
|
break;
|
2021-03-12 19:41:35 +00:00
|
|
|
case M.$pointer:
|
2021-03-14 20:10:47 +00:00
|
|
|
emit(`${dest} = _decodePtr(d)`);
|
|
|
|
break;
|
2021-03-12 19:41:35 +00:00
|
|
|
case M.$rec:
|
2021-03-14 20:10:47 +00:00
|
|
|
// assume dest is already void 0
|
|
|
|
emit(seq(`if (d.openRecord()) `, collectBody(() => {
|
|
|
|
const label = gentemp();
|
|
|
|
decoderFor(p[0], label);
|
|
|
|
emit(seq(`if (${label} !== void 0) `,
|
|
|
|
collectBody(() => {
|
|
|
|
const fs = gentemp();
|
|
|
|
decoderFor(p[1], fs, true);
|
|
|
|
emit(seq(
|
|
|
|
`if (${fs} !== void 0) ${dest} = _.Record`,
|
|
|
|
anglebrackets(typeFor(p[0]), typeFor(p[1])),
|
|
|
|
parens(seq(label, ` as any`),
|
|
|
|
seq(fs, ` as any`))));
|
|
|
|
})));
|
|
|
|
})));
|
|
|
|
break;
|
2021-03-12 19:41:35 +00:00
|
|
|
case M.$tuple:
|
2021-03-14 20:10:47 +00:00
|
|
|
// assume dest is already void 0
|
|
|
|
decoderForTuple(p[0].map(unname), dest, recordFields, void 0);
|
|
|
|
break;
|
2021-03-12 19:41:35 +00:00
|
|
|
case M.$tuple_STAR_:
|
2021-03-14 20:10:47 +00:00
|
|
|
// assume dest is already void 0
|
|
|
|
decoderForTuple(p[0].map(unname), dest, recordFields, unname(p[1]));
|
|
|
|
break;
|
2021-03-12 19:41:35 +00:00
|
|
|
case M.$setof:
|
2021-03-14 20:10:47 +00:00
|
|
|
// assume dest is already void 0
|
|
|
|
emit(seq(`if (d.openSet()) `, collectBody(() => {
|
|
|
|
emit(seq(`let r: `, typeFor(p), ` | undefined = new _.KeyedSet()`));
|
|
|
|
emit(accumulateCompound(p[0],
|
|
|
|
() => [`r = void 0`],
|
|
|
|
(t) => [`r.add(${t})`]));
|
|
|
|
emit(`${dest} = r`);
|
|
|
|
})));
|
|
|
|
break;
|
2021-03-12 19:41:35 +00:00
|
|
|
case M.$dictof:
|
2021-03-14 20:10:47 +00:00
|
|
|
// assume dest is already void 0
|
|
|
|
emit(seq(`if (d.openDictionary()) `, collectBody(() => {
|
|
|
|
emit(seq(`let r: `, typeFor(p), ` | undefined = new _.KeyedDictionary()`));
|
|
|
|
emit(seq(`while (!d.closeCompound()) `, collectBody(() => {
|
|
|
|
emit(seq(`let K: undefined | `, typeFor(p[0]), ` = void 0`));
|
|
|
|
decoderFor(p[0], 'K');
|
|
|
|
emit(seq(`if (K === void 0) { r = void 0; break; }`));
|
|
|
|
emit(seq(`let V: undefined | `, typeFor(p[1]), ` = void 0`));
|
|
|
|
decoderFor(p[1], 'V');
|
|
|
|
emit(seq(`if (V === void 0) { r = void 0; break; }`));
|
|
|
|
emit(seq(`r.set(K, V)`));
|
|
|
|
})));
|
|
|
|
emit(seq(`${dest} = r`));
|
|
|
|
})));
|
|
|
|
break;
|
2021-03-12 19:41:35 +00:00
|
|
|
case M.$dict:
|
2021-03-14 20:10:47 +00:00
|
|
|
emit(seq(`${dest} = d.next()`));
|
|
|
|
emit(seq(
|
|
|
|
`if (${dest} !== void 0 && !(`, predicateFor(dest, p), `)) ${dest} = void 0`));
|
|
|
|
break;
|
2021-03-12 19:41:35 +00:00
|
|
|
default:
|
|
|
|
((_p: never) => {})(p);
|
|
|
|
throw new Error("Unreachable");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-09 14:59:40 +00:00
|
|
|
function typeFor(p: Pattern): Item {
|
|
|
|
switch (p.label) {
|
2021-03-10 22:15:53 +00:00
|
|
|
case M.$atom:
|
2021-03-09 14:59:40 +00:00
|
|
|
switch (p[0]) {
|
2021-03-10 22:15:53 +00:00
|
|
|
case M.$Boolean: return `boolean`;
|
|
|
|
case M.$Float: return `_.SingleFloat`;
|
|
|
|
case M.$Double: return `_.DoubleFloat`;
|
|
|
|
case M.$SignedInteger: return `number`;
|
|
|
|
case M.$String: return `string`;
|
|
|
|
case M.$ByteString: return `_.Bytes`;
|
|
|
|
case M.$Symbol: return `symbol`;
|
2021-03-09 14:59:40 +00:00
|
|
|
}
|
2021-03-10 22:15:53 +00:00
|
|
|
case M.$lit:
|
2021-03-09 14:59:40 +00:00
|
|
|
return `(typeof ${literal(p[0])})`;
|
2021-03-10 22:15:53 +00:00
|
|
|
case M.$ref:
|
2021-03-11 13:43:06 +00:00
|
|
|
return lookup(refPosition(p), p, env,
|
2021-03-11 09:56:49 +00:00
|
|
|
(_p) => p[1].description!,
|
|
|
|
(p) => typeFor(p),
|
|
|
|
(mod, modPath,_p) => {
|
|
|
|
imports.add([mod, modPath]);
|
|
|
|
return `${mod}.${p[1].description!}`;
|
|
|
|
});
|
2021-03-10 22:15:53 +00:00
|
|
|
case M.$or:
|
2021-03-09 14:59:40 +00:00
|
|
|
return opseq('never', ' | ', ... p[0].map(pp => typeFor(pp)));
|
2021-03-10 22:15:53 +00:00
|
|
|
case M.$and:
|
2021-03-11 16:59:40 +00:00
|
|
|
return opseq('_val', ' & ', ... p[0].map(pp => typeFor(pp)));
|
2021-03-10 22:15:53 +00:00
|
|
|
case M.$pointer:
|
2021-03-11 16:59:40 +00:00
|
|
|
return `_ptr`;
|
2021-03-10 22:15:53 +00:00
|
|
|
case M.$rec:
|
2021-03-11 16:59:40 +00:00
|
|
|
return seq('_.Record', anglebrackets(typeFor(p[0]), typeFor(p[1]), '_ptr'));
|
2021-03-10 22:15:53 +00:00
|
|
|
case M.$tuple:
|
2021-03-09 14:59:40 +00:00
|
|
|
return brackets(... p[0].map(pp => typeFor(unname(pp))));
|
2021-03-10 22:15:53 +00:00
|
|
|
case M.$tuple_STAR_:
|
2021-03-09 14:59:40 +00:00
|
|
|
if (p[0].length === 0) {
|
|
|
|
return seq('Array<', typeFor(unname(p[1])), '>');
|
|
|
|
} else {
|
|
|
|
return brackets(... p[0].map(pp => typeFor(unname(pp))),
|
|
|
|
seq('... Array<', typeFor(unname(p[1])), '>'));
|
|
|
|
}
|
2021-03-10 22:15:53 +00:00
|
|
|
case M.$setof:
|
2021-03-11 22:02:18 +00:00
|
|
|
return seq('_.KeyedSet', anglebrackets(typeFor(p[0]), '_ptr'));
|
2021-03-10 22:15:53 +00:00
|
|
|
case M.$dictof:
|
2021-03-11 22:02:18 +00:00
|
|
|
return seq('_.KeyedDictionary', anglebrackets(typeFor(p[0]), typeFor(p[1]), '_ptr'));
|
2021-03-10 22:15:53 +00:00
|
|
|
case M.$dict:
|
2021-03-09 15:45:57 +00:00
|
|
|
return parens(seq(
|
|
|
|
block(
|
|
|
|
... Array.from(p[0]).map(([k, vp]) =>
|
|
|
|
seq(`get(k: typeof ${literal(k)}): `, typeFor(vp))),
|
|
|
|
... Array.from(p[0]).map(([k, _vp]) =>
|
|
|
|
seq(`has(k: typeof ${literal(k)}): true`))),
|
2021-03-11 16:59:40 +00:00
|
|
|
' & _.Dictionary<_val, _ptr>'));
|
2021-03-09 14:59:40 +00:00
|
|
|
default:
|
|
|
|
((_p: never) => {})(p);
|
|
|
|
throw new Error("Unreachable");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-12 10:11:00 +00:00
|
|
|
function predicateFor(v: string, p: Pattern, recordOkAsTuple = false): Item {
|
2021-03-09 14:59:40 +00:00
|
|
|
switch (p.label) {
|
2021-03-10 22:15:53 +00:00
|
|
|
case M.$atom:
|
2021-03-09 14:59:40 +00:00
|
|
|
switch (p[0]) {
|
2021-03-10 22:15:53 +00:00
|
|
|
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'`;
|
2021-03-09 14:59:40 +00:00
|
|
|
}
|
2021-03-10 22:15:53 +00:00
|
|
|
case M.$lit:
|
2021-03-14 20:13:40 +00:00
|
|
|
return `_.is(${v}, ${literal(p[0])})`;
|
2021-03-10 22:15:53 +00:00
|
|
|
case M.$ref:
|
2021-03-12 10:33:49 +00:00
|
|
|
return lookup(refPosition(p), p, env,
|
|
|
|
(_p) => `is${Ref._.name(p).description!}(${v})`,
|
|
|
|
(pp) => predicateFor(v, pp),
|
|
|
|
(mod, modPath, _p) => {
|
|
|
|
imports.add([mod, modPath]);
|
|
|
|
return `${mod}.is${Ref._.name(p).description!}(${v})`;
|
|
|
|
});
|
2021-03-14 20:10:47 +00:00
|
|
|
case M.$or: {
|
|
|
|
const alts = p[0];
|
|
|
|
const recs = alts.map(derefPattern);
|
|
|
|
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(`${v}.label`, r[0]),
|
|
|
|
' && ',
|
|
|
|
predicateFor(v, r[1], true)))))));
|
|
|
|
} else {
|
|
|
|
return opseq('false', ' || ', ... p[0].map(pp => predicateFor(v, pp)));
|
|
|
|
}
|
|
|
|
}
|
2021-03-10 22:15:53 +00:00
|
|
|
case M.$and:
|
2021-03-12 10:11:00 +00:00
|
|
|
return opseq('true', ' && ', ... p[0].map(pp => predicateFor(v, pp)));
|
2021-03-10 22:15:53 +00:00
|
|
|
case M.$pointer:
|
2021-03-09 14:59:40 +00:00
|
|
|
return `_.isPointer(${v})`;
|
2021-03-10 22:15:53 +00:00
|
|
|
case M.$rec:
|
2021-03-09 14:59:40 +00:00
|
|
|
return opseq('true', ' && ',
|
2021-03-11 16:59:40 +00:00
|
|
|
`_.Record.isRecord<_val, _.Tuple<_val>, _ptr>(${v})`,
|
2021-03-12 10:11:00 +00:00
|
|
|
predicateFor(`${v}.label`, p[0]),
|
|
|
|
predicateFor(v, p[1], true));
|
2021-03-10 22:15:53 +00:00
|
|
|
case M.$tuple:
|
2021-03-09 14:59:40 +00:00
|
|
|
return opseq('true', ' && ',
|
|
|
|
... (recordOkAsTuple ? []
|
2021-03-11 16:59:40 +00:00
|
|
|
: [`_.Array.isArray(${v})`, `!_.Record.isRecord<_val, _.Tuple<_val>, _ptr>(${v})`]),
|
2021-03-09 14:59:40 +00:00
|
|
|
`(${v}.length === ${p[0].length})`,
|
2021-03-12 10:11:00 +00:00
|
|
|
... p[0].map((pp, i) => predicateFor(`${v}[${i}]`, unname(pp))));
|
2021-03-10 22:15:53 +00:00
|
|
|
case M.$tuple_STAR_:
|
2021-03-09 14:59:40 +00:00
|
|
|
return opseq('true', ' && ',
|
|
|
|
... (recordOkAsTuple ? []
|
2021-03-11 16:59:40 +00:00
|
|
|
: [`_.Array.isArray(${v})`, `!_.Record.isRecord<_val, _.Tuple<_val>, _ptr>(${v})`]),
|
2021-03-09 14:59:40 +00:00
|
|
|
`(${v}.length >= ${p[0].length})`,
|
2021-03-14 20:10:47 +00:00
|
|
|
seq(p[0].length > 0 ? `${v}.slice(${p[0].length})` : v,
|
2021-03-09 14:59:40 +00:00
|
|
|
`.every(v => `,
|
2021-03-12 10:11:00 +00:00
|
|
|
parens(predicateFor('v', unname(p[1]))),
|
2021-03-09 14:59:40 +00:00
|
|
|
`)`),
|
2021-03-12 10:11:00 +00:00
|
|
|
... p[0].map((pp, i) => predicateFor(`${v}[${i}]`, unname(pp))));
|
2021-03-10 22:15:53 +00:00
|
|
|
case M.$setof:
|
2021-03-09 14:59:40 +00:00
|
|
|
return opseq('true', ' && ',
|
2021-03-11 16:59:40 +00:00
|
|
|
`_.Set.isSet<_val>(${v})`,
|
2021-03-09 14:59:40 +00:00
|
|
|
fnblock(
|
|
|
|
seq(`for (const vv of ${v}) `, block(
|
2021-03-12 10:11:00 +00:00
|
|
|
seq('if (!(', predicateFor('vv', p[0]), ')) return false'))),
|
2021-03-09 14:59:40 +00:00
|
|
|
seq('return true')));
|
2021-03-10 22:15:53 +00:00
|
|
|
case M.$dictof:
|
2021-03-09 14:59:40 +00:00
|
|
|
return opseq('true', ' && ',
|
2021-03-11 16:59:40 +00:00
|
|
|
`_.Dictionary.isDictionary<_val, _ptr>(${v})`,
|
2021-03-09 14:59:40 +00:00
|
|
|
fnblock(
|
|
|
|
seq(`for (const e of ${v}) `, block(
|
2021-03-12 10:11:00 +00:00
|
|
|
seq('if (!(', predicateFor('e[0]', p[0]), ')) return false'),
|
|
|
|
seq('if (!(', predicateFor('e[1]', p[1]), ')) return false'))),
|
2021-03-09 14:59:40 +00:00
|
|
|
seq('return true')));
|
2021-03-10 22:15:53 +00:00
|
|
|
case M.$dict:
|
2021-03-09 14:59:40 +00:00
|
|
|
return opseq('true', ' && ',
|
2021-03-11 16:59:40 +00:00
|
|
|
`_.Dictionary.isDictionary<_val, _ptr>(${v})`,
|
2021-03-09 14:59:40 +00:00
|
|
|
... Array.from(p[0]).map(([k, vp]) => {
|
|
|
|
const tmp = gentemp();
|
|
|
|
return parens(seq(
|
|
|
|
`(${tmp} = ${v}.get(${literal(k)})) !== void 0 && `,
|
2021-03-12 10:11:00 +00:00
|
|
|
predicateFor(tmp, vp)));
|
2021-03-09 14:59:40 +00:00
|
|
|
}));
|
|
|
|
default:
|
|
|
|
((_p: never) => {})(p);
|
|
|
|
throw new Error("Unreachable");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function unname(p: NamedPattern): Pattern {
|
2021-03-10 22:15:53 +00:00
|
|
|
return (p.label === M.$named) ? p[1] : p;
|
2021-03-09 15:45:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
function fieldName(np: NamedPattern, index: number): string {
|
2021-03-10 22:15:53 +00:00
|
|
|
return (np.label === M.$named) ? np[0].description! : `_field${index}`;
|
2021-03-09 15:45:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
function fieldEntry(np: NamedPattern, index: number): Item {
|
|
|
|
return seq(JSON.stringify(fieldName(np, index)), ': ', typeFor(unname(np)));
|
2021-03-09 14:59:40 +00:00
|
|
|
}
|
|
|
|
|
2021-03-10 22:15:53 +00:00
|
|
|
for (const [name0, pattern] of Schema._.details(schema).get(M.$definitions)) {
|
2021-03-09 14:59:40 +00:00
|
|
|
const name = name0 as symbol;
|
|
|
|
temps = [];
|
2021-03-12 10:11:00 +00:00
|
|
|
const recognizer = predicateFor('v', pattern);
|
2021-03-10 22:15:53 +00:00
|
|
|
if (pattern.label === M.$rec &&
|
|
|
|
pattern[0].label === M.$lit &&
|
|
|
|
pattern[1].label === M.$tuple)
|
2021-03-09 15:45:57 +00:00
|
|
|
{
|
|
|
|
types.push(
|
|
|
|
seq(`export const ${name.description!} = _.Record.makeConstructor<`,
|
|
|
|
braces(... pattern[1][0].map(fieldEntry)),
|
2021-03-11 16:59:40 +00:00
|
|
|
`, _ptr>()(${literal(pattern[0][0])}, `,
|
2021-03-09 15:45:57 +00:00
|
|
|
JSON.stringify(pattern[1][0].map(fieldName)), `);`));
|
|
|
|
}
|
2021-03-09 14:59:40 +00:00
|
|
|
types.push(
|
2021-03-09 15:45:57 +00:00
|
|
|
seq(`export type ${name.description!} = `, typeFor(pattern), `;`));
|
2021-03-11 08:25:17 +00:00
|
|
|
functions.push(
|
2021-03-12 19:41:35 +00:00
|
|
|
seq(`export function is${name.description!}`,
|
2021-03-09 14:59:40 +00:00
|
|
|
'(v: any): v is ', name.description!, ' ',
|
|
|
|
block(
|
|
|
|
... temps.length > 0 ? [seq('let ', commas(... temps), ': any')] : [],
|
|
|
|
seq('return ', recognizer))));
|
2021-03-11 08:25:17 +00:00
|
|
|
functions.push(
|
2021-03-12 19:41:35 +00:00
|
|
|
seq(`export function as${name.description!}`,
|
2021-03-11 08:25:17 +00:00
|
|
|
'(v: any): ', name.description!, ' ',
|
|
|
|
block(
|
|
|
|
seq(`if (!is${name.description!}(v)) `,
|
2021-03-11 16:59:40 +00:00
|
|
|
block(`throw new TypeError(\`Invalid ${name.description!}: \${_.stringify(v)}\`)`),
|
2021-03-11 08:25:17 +00:00
|
|
|
' else ',
|
|
|
|
block(`return v`)))));
|
2021-03-12 19:41:35 +00:00
|
|
|
|
|
|
|
functions.push(
|
|
|
|
seq(`export function decode${name.description!}`,
|
|
|
|
`(d: _.TypedDecoder<_ptr>): ${name.description!} | undefined `,
|
2021-03-14 20:10:47 +00:00
|
|
|
collectBody(() => {
|
|
|
|
emit(seq(`let result`));
|
|
|
|
decoderFor(pattern, 'result');
|
|
|
|
emit(seq(`return result`));
|
|
|
|
})));
|
2021-03-09 14:59:40 +00:00
|
|
|
}
|
|
|
|
|
2021-03-11 16:59:40 +00:00
|
|
|
types.push(seq('export type _ptr = ', pointerName === false ? 'never' : typeFor(pointerName), `;`));
|
|
|
|
types.push(`export type _val = _.Value<_ptr>;`);
|
2021-03-12 19:41:35 +00:00
|
|
|
functions.push(seq(`export const _decodePtr = `,
|
|
|
|
(pointerName === false
|
|
|
|
? '() => { throw new _.DecodeError("Pointers forbidden"); }'
|
2021-03-14 20:10:47 +00:00
|
|
|
: seq(`(d: _.TypedDecoder<_ptr>) => `,
|
|
|
|
collectBody(() => {
|
|
|
|
emit(seq(`let result`));
|
|
|
|
decoderFor(pointerName, 'result');
|
|
|
|
emit(seq(`return result`));
|
|
|
|
}))),
|
2021-03-12 19:41:35 +00:00
|
|
|
`;`));
|
2021-03-11 16:59:40 +00:00
|
|
|
|
2021-03-09 14:59:40 +00:00
|
|
|
const f = new Formatter();
|
2021-03-11 16:59:40 +00:00
|
|
|
f.write(`import * as _ from ${JSON.stringify(options.preservesModule ?? '@preserves/core')};\n`);
|
2021-03-11 13:43:06 +00:00
|
|
|
imports.forEach(([identifier, path]) => {
|
|
|
|
f.write(`import * as ${identifier} from ${JSON.stringify(path)};\n`);
|
|
|
|
});
|
2021-03-09 14:59:40 +00:00
|
|
|
f.newline();
|
|
|
|
|
2021-03-09 15:45:57 +00:00
|
|
|
const sortedLiterals = Array.from(literals);
|
|
|
|
sortedLiterals.sort((a, b) => a[1] < b[1] ? -1 : a[1] === b[1] ? 0 : 1);
|
|
|
|
for (const [lit, varname] of sortedLiterals) {
|
|
|
|
f.write(seq(`export const ${varname} = `, sourceCodeFor(lit), `;\n`));
|
2021-03-09 14:59:40 +00:00
|
|
|
}
|
|
|
|
f.newline();
|
|
|
|
|
|
|
|
types.forEach(t => {
|
|
|
|
f.write(t);
|
|
|
|
f.newline();
|
2021-03-09 15:45:57 +00:00
|
|
|
f.newline();
|
2021-03-09 14:59:40 +00:00
|
|
|
});
|
|
|
|
f.newline();
|
|
|
|
|
2021-03-11 08:25:17 +00:00
|
|
|
functions.forEach(p => {
|
2021-03-09 14:59:40 +00:00
|
|
|
f.write(p);
|
|
|
|
f.newline();
|
|
|
|
f.newline();
|
|
|
|
});
|
|
|
|
|
|
|
|
return f.toString();
|
|
|
|
}
|
|
|
|
|
|
|
|
export function stringSource(s: string) {
|
|
|
|
return JSON.stringify(s);
|
|
|
|
}
|
|
|
|
|
|
|
|
export function sourceCodeFor(v: Value<any>): Item {
|
|
|
|
return fold(v, {
|
|
|
|
boolean(b: boolean): Item { return b.toString(); },
|
|
|
|
single(f: number): Item { return f.toString(); },
|
|
|
|
double(f: number): Item { return f.toString(); },
|
|
|
|
integer(i: number): Item { return i.toString(); },
|
|
|
|
string(s: string): Item { return stringSource(s); },
|
|
|
|
bytes(b: Bytes): Item {
|
|
|
|
return seq(`Uint8Array.from(`, brackets(... Array.from(b).map(b => b.toString())), `)`);
|
|
|
|
},
|
|
|
|
symbol(s: symbol): Item { return `Symbol.for(${stringSource(s.description!)})`; },
|
|
|
|
|
|
|
|
record(r: Record<Value<any>, Tuple<Value<any>>, any>, k: Fold<any, Item>): Item {
|
2021-03-11 16:59:40 +00:00
|
|
|
return seq(`_.Record<_val, _.Tuple<_val>, _ptr>`, parens(k(r.label), brackets(... r.map(k))));
|
2021-03-09 14:59:40 +00:00
|
|
|
},
|
|
|
|
array(a: Array<Value<any>>, k: Fold<any, Item>): Item {
|
|
|
|
return brackets(... a.map(k));
|
|
|
|
},
|
|
|
|
set(s: Set<any>, k: Fold<any, Item>): Item {
|
2021-03-11 16:59:40 +00:00
|
|
|
return seq('new _.Set<_val>', parens(brackets(... Array.from(s).map(k))));
|
2021-03-09 14:59:40 +00:00
|
|
|
},
|
|
|
|
dictionary(d: Dictionary<Value<any>, any>, k: Fold<any, Item>): Item {
|
2021-03-11 16:59:40 +00:00
|
|
|
return seq('new _.Dictionary<_val, _ptr>', parens(brackets(... Array.from(d).map(([kk,vv]) =>
|
2021-03-09 14:59:40 +00:00
|
|
|
brackets(k(kk), k(vv))))));
|
|
|
|
},
|
|
|
|
|
|
|
|
annotated(a: Annotated<any>, k: Fold<any, Item>): Item {
|
2021-03-11 16:59:40 +00:00
|
|
|
return seq('_.annotate<_ptr>', parens(k(a.item), ... a.annotations.map(k)));
|
2021-03-09 14:59:40 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
pointer(t: any, _k: Fold<any, Item>): Item {
|
|
|
|
throw new Error(preserves`Cannot emit source code for construction of pointer ${t}`);
|
|
|
|
},
|
|
|
|
});
|
|
|
|
}
|