import { Pattern, NamedPattern, Schema, Input } from "./meta"; import * as M from './meta'; import { Annotated, Bytes, Dictionary, Fold, fold, preserves, Record, Tuple, Value } from "@preserves/core"; import { Formatter, parens, seq, Item, opseq, block, commas, brackets, anglebrackets, braces } 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, schema: Schema, preservesModule = '@preserves/core'): string { const literals = new Dictionary(); const types: Array = []; const predicates: Array = []; let temps: Array = []; function environmentWith(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) { const s = '___' + ( v.asPreservesText() .replace('_', '__') .replace('*', '_STAR_') ); varname = M.isValidToken(s, true) ? s : '__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.___tuple_STAR_: 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( 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`))), ' & _.Dictionary<_.Value>')); 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: switch (typeof p[0]) { case 'boolean': case 'number': case 'string': case 'symbol': return `${v} === ${literal(p[0])}`; default: 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.___tuple_STAR_: 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; } function fieldName(np: NamedPattern, index: number): string { return (np.label === M.___named) ? np[0].description! : `_field${index}`; } function fieldEntry(np: NamedPattern, index: number): Item { return seq(JSON.stringify(fieldName(np, index)), ': ', typeFor(unname(np))); } for (const [name0, pattern] of Schema._.definitions(schema)) { const name = name0 as symbol; temps = []; const recognizer = walk('v', pattern); if (pattern.label === M.___rec && pattern[0].label === M.___lit && pattern[1].label === M.___tuple) { types.push( seq(`export const ${name.description!} = _.Record.makeConstructor<`, braces(... pattern[1][0].map(fieldEntry)), `>()(${literal(pattern[0][0])}, `, JSON.stringify(pattern[1][0].map(fieldName)), `);`)); } 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(); 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`)); } f.newline(); types.forEach(t => { f.write(t); f.newline(); 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): 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, Tuple>, any>, k: Fold): Item { return seq(`_.Record`, parens(k(r.label), brackets(... r.map(k)))); }, array(a: Array>, k: Fold): Item { return brackets(... a.map(k)); }, set(s: Set, k: Fold): Item { return seq('new _.Set', parens(brackets(... Array.from(s).map(k)))); }, dictionary(d: Dictionary, any>, k: Fold): Item { return seq('new _.Dictionary', parens(brackets(... Array.from(d).map(([kk,vv]) => brackets(k(kk), k(vv)))))); }, annotated(a: Annotated, k: Fold): Item { return seq('_.annotate', parens(k(a.item), ... a.annotations.map(k))); }, pointer(t: any, _k: Fold): Item { throw new Error(preserves`Cannot emit source code for construction of pointer ${t}`); }, }); }