preserves/implementations/javascript/src/schema/compiler.ts

287 lines
12 KiB
TypeScript

import { Pattern, NamedPattern, Schema, Input } from "./meta";
import * as M from './meta';
import { Annotated, Bytes, Dictionary, Fold, fold, preserves, Record, Tuple, Value } from "..";
import { Formatter, parens, seq, Item, opseq, block, commas, brackets, anglebrackets } from "./block";
function fnblock(... items: Item[]): Item {
return seq('((() => ', block(... items), ')())');
}
export type CompileEnvEntry = {
moduleName: string,
modulePath: string,
schema: Schema,
inline: boolean,
};
export function compile(env: Array<CompileEnvEntry>, schema: Schema, preservesModule = 'preserves'): string {
const literals = new Dictionary<string, never>();
const types: Array<Item> = [];
const predicates: Array<Item> = [];
let temps: Array<string> = [];
function environmentWith<R>(name: symbol,
kLocal: () => R,
kOther: (e: CompileEnvEntry, p: Pattern) => R): R {
if (Schema._.definitions(schema).has(name)) {
return kLocal();
}
for (const e of env) {
const p = Schema._.definitions(e.schema).get(name);
if (p !== void 0) {
return kOther(e, p);
}
}
throw new Error(`Undefined reference: ${name.description!}`);
}
function applyPredicate(name: symbol, v: string): Item {
return environmentWith(name,
() => `is${name.description!}(${v})`,
(e, p) => {
if (e.inline) {
return walk(v, p);
} else {
return `${e.moduleName}.is${name.description!}(${v})`;
}
});
}
function gentemp(): string {
const varname = '_tmp' + temps.length;
temps.push(varname);
return varname;
}
function literal(v: Input): Item {
let varname = literals.get(v);
if (varname === void 0) {
varname = '__lit' + literals.size;
literals.set(v, varname);
}
return varname;
}
function typeFor(p: Pattern): Item {
switch (p.label) {
case M.ATOM:
switch (p[0]) {
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`;
}
case M.LIT:
return `(typeof ${literal(p[0])})`;
case M.REF:
return environmentWith(
p[0],
() => p[0].description!,
(e, pp) => {
if (e.inline) {
return typeFor(pp);
} else {
return `${e.moduleName}.${p[0].description!}`;
}
});
case M.OR:
return opseq('never', ' | ', ... p[0].map(pp => typeFor(pp)));
case M.AND:
return opseq('_.Value', ' & ', ... p[0].map(pp => typeFor(pp)));
case M.POINTER:
return `any`; // TODO: what to do here?
case M.REC:
return seq('_.Record', anglebrackets(typeFor(p[0]), typeFor(p[1])));
case M.TUPLE:
return brackets(... p[0].map(pp => typeFor(unname(pp))));
case M.TUPLESTAR:
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])), '>'));
}
case M.SETOF:
return seq('_.KeyedSet<', typeFor(p[0]), '>');
case M.DICTOF:
return seq('_.KeyedDictionary', anglebrackets(typeFor(p[0]), typeFor(p[1])));
case M.DICT:
return parens(seq('_.Dictionary<_.Value> & ', block(
... Array.from(p[0]).map(([k, vp]) =>
seq(`get(k: typeof ${literal(k)}): `, typeFor(vp))))));
default:
((_p: never) => {})(p);
throw new Error("Unreachable");
}
}
function walk(v: string, p: Pattern, 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}, ${literal(p[0])})`;
case M.REF:
return applyPredicate(p[0], v);
case M.OR:
return opseq('false', ' || ', ... p[0].map(pp => walk(v, pp)));
case M.AND:
return opseq('true', ' && ', ... p[0].map(pp => walk(v, pp)));
case M.POINTER:
return `_.isPointer(${v})`;
case M.REC:
return opseq('true', ' && ',
`_.Record.isRecord(${v})`,
walk(`${v}.label`, p[0]),
walk(v, p[1], true));
case M.TUPLE:
return opseq('true', ' && ',
... (recordOkAsTuple ? []
: [`_.Array.isArray(${v})`, `!_.Record.isRecord(${v})`]),
`(${v}.length === ${p[0].length})`,
... p[0].map((pp, i) => walk(`${v}[${i}]`, unname(pp))));
case M.TUPLESTAR:
return opseq('true', ' && ',
... (recordOkAsTuple ? []
: [`_.Array.isArray(${v})`, `!_.Record.isRecord(${v})`]),
`(${v}.length >= ${p[0].length})`,
seq(`${v}.slice(${p[0].length})`,
`.every(v => `,
parens(walk('v', unname(p[1]))),
`)`),
... p[0].map((pp, i) => walk(`${v}[${i}]`, unname(pp))));
case M.SETOF:
return opseq('true', ' && ',
`_.Set.isSet(${v})`,
fnblock(
seq(`for (const vv of ${v}) `, block(
seq('if (!(', walk('vv', p[0]), ')) return false'))),
seq('return true')));
case M.DICTOF:
return opseq('true', ' && ',
`_.Dictionary.isDictionary(${v})`,
fnblock(
seq(`for (const e of ${v}) `, block(
seq('if (!(', walk('e[0]', p[0]), ')) return false'),
seq('if (!(', walk('e[1]', p[1]), ')) return false'))),
seq('return true')));
case M.DICT:
return opseq('true', ' && ',
`_.Dictionary.isDictionary(${v})`,
... Array.from(p[0]).map(([k, vp]) => {
const tmp = gentemp();
return parens(seq(
`(${tmp} = ${v}.get(${literal(k)})) !== void 0 && `,
walk(tmp, vp)));
}));
default:
((_p: never) => {})(p);
throw new Error("Unreachable");
}
}
function unname(p: NamedPattern): Pattern {
return (p.label === M.NAMED) ? p[1] : p;
}
for (const [name0, pattern] of Schema._.definitions(schema)) {
const name = name0 as symbol;
temps = [];
const recognizer = walk('v', pattern);
types.push(
seq(`export type ${name.description!} = `, typeFor(pattern)));
predicates.push(
seq('export function ', `is${name.description!}`,
'(v: any): v is ', name.description!, ' ',
block(
... temps.length > 0 ? [seq('let ', commas(... temps), ': any')] : [],
seq('return ', recognizer))));
}
const f = new Formatter();
f.write(`import * as _ from ${JSON.stringify(preservesModule)};\n`);
f.newline();
for (const [lit, varname] of literals) {
f.write(seq(`const ${varname} = `, sourceCodeFor(lit), `;\n`));
}
f.newline();
types.forEach(t => {
f.write(t);
f.newline();
});
f.newline();
predicates.forEach(p => {
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 {
return seq(`_.Record`, parens(k(r.label), brackets(... r.map(k))));
},
array(a: Array<Value<any>>, k: Fold<any, Item>): Item {
return brackets(... a.map(k));
},
set(s: Set<any>, k: Fold<any, Item>): Item {
return seq('new _.Set', parens(brackets(... Array.from(s).map(k))));
},
dictionary(d: Dictionary<Value<any>, any>, k: Fold<any, Item>): Item {
return seq('new _.Dictionary', parens(brackets(... Array.from(d).map(([kk,vv]) =>
brackets(k(kk), k(vv))))));
},
annotated(a: Annotated<any>, k: Fold<any, Item>): Item {
return seq('_.annotate', parens(k(a.item), ... a.annotations.map(k)));
},
pointer(t: any, _k: Fold<any, Item>): Item {
throw new Error(preserves`Cannot emit source code for construction of pointer ${t}`);
},
});
}
import fs from 'fs';
import { readSchema } from "./reader";
import { Reader } from "../reader";
import { BASE } from "./base";
function main() {
const src = fs.readFileSync(__dirname + '/../../../../schema/schema.txt', 'utf-8');
const sch = readSchema(new Reader<never>(src, { includeAnnotations: true }).readToEnd());
console.log(compile([{ moduleName: 'BASE', modulePath: 'BASE', schema: BASE, inline: true }], sch, '..'));
}
main();