import { Dictionary, KeyedSet, Position } from "@preserves/core"; import { refPosition } from "../reader"; import * as M from "../meta"; import { block, braces, commas, formatItems, Item, keyvalue, seq } from "./block"; import { ANY_TYPE, FieldType, Type } from "../type"; import { renderType, variantInitFor } from "./rendertype"; export interface CompilerOptions { preservesModule?: string; defaultEmbeddedType?: M.Ref; warn?(message: string, pos: Position | null): void; } export interface Capture { fieldName: string; sourceExpr: string; } export const RECURSION_LIMIT = 128; export class ModuleContext { readonly env: M.Environment; readonly schema: M.Schema; readonly options: CompilerOptions; readonly literals = new Dictionary(); readonly typedefs: Item[] = []; readonly functiondefs: Item[] = []; readonly imports = new KeyedSet<[string, string]>(); constructor(env: M.Environment, schema: M.Schema, options: CompilerOptions) { this.env = env; this.schema = schema; this.options = options; } literal(v: M.Input): Item { let varname = this.literals.get(v); if (varname === void 0) { varname = M.jsId('$' + v.asPreservesText(), () => '__lit' + this.literals.size); this.literals.set(v, varname); } return varname; } derefPattern(p: M.Definition, refCount = 0): M.Definition { if (refCount > RECURSION_LIMIT) { throw new Error('Recursion limit exceeded'); } if (p._variant === 'Pattern' && p.value._variant === 'SimplePattern' && p.value.value._variant === 'Ref') { return M.lookup(refPosition(p.value.value.value), p.value.value.value, this.env, (p) => this.derefPattern(p, refCount + 1), (_modId, _modPath, pp) => this.derefPattern(pp ?? p, refCount + 1)); } else { return p; } } defineType(f: Item): void { this.typedefs.push(f); } defineFunction(f: (ctx: FunctionContext) => Item): void { this.functiondefs.push(f(new FunctionContext(this))); } resolver(): (ref: M.Ref) => FieldType { return (ref) => M.lookup(refPosition(ref), ref, this.env, (_p) => Type.ref(ref.name.description!), (modId, modPath,_p) => { this.imports.add([modId, modPath]); return Type.ref(`${modId}.${ref.name.description!}`); }); } } export class FunctionContext { readonly mod: ModuleContext; tempCounter = 0; temps: Map = new Map(); captures: Capture[] = []; variantName: string | undefined = void 0; constructor(mod: ModuleContext) { this.mod = mod; } gentempname(): string { return '_tmp' + this.tempCounter++; } gentemp(vartype: Type = ANY_TYPE): string { const typeitem = renderType(vartype); const typestr = formatItems([typeitem], Infinity); const varname = this.gentempname(); let e = this.temps.get(typestr); if (e === void 0) { e = { type: typeitem, names: [] }; this.temps.set(typestr, e); } e.names.push(varname); return varname; } block(f: () => Item[]): Item { const oldTemps = this.temps; this.temps = new Map(); const items = f(); const ts = this.temps; this.temps = oldTemps; return block( ... Array.from(ts).map(([_typestr, { type, names }]) => seq(`let `, commas(... names), `: (`, type, `) | undefined`)), ... items); } withCapture( fieldName: string | undefined, sourceExpr: string, ks: (sourceExpr: string) => R): R { if (fieldName !== void 0) this.captures.push({ fieldName, sourceExpr }); const result = ks(sourceExpr); if (fieldName !== void 0) this.captures.pop(); return result; } convertCapture( fieldName: string | undefined, sourceExpr: string, ks: () => Item[]): Item { return this.withCapture(fieldName, sourceExpr, sourceExpr => seq(`if (${sourceExpr} !== void 0) `, this.block(() => ks()))); } buildCapturedCompound(dest: string): Item { const fields = [ ... variantInitFor(this.variantName), ... this.captures.map(({ fieldName, sourceExpr }) => keyvalue(fieldName, sourceExpr)) ]; return seq(`${dest} = `, fields.length === 0 ? `null` : braces(... fields)); } }