type ModeWriter = (doc: Doc, modes: { execution: "sync" | "async" }) => void; export class Doc { args: string[]; /** Bindings the compiled function closes over, by name. */ closed: Record; content: string[] = []; indent = 0; constructor(args: string[] = [], closed: Record = {}) { this.args = args; this.closed = closed; } // the compiler catches a child's throw and keeps writing into this doc, so the indent has to unwind with it indented(fn: (doc: Doc) => void) { this.indent += 1; try { fn(this); } finally { this.indent -= 1; } } write(fn: ModeWriter): void; write(line: string): void; write(arg: any) { if (typeof arg === "function") { (arg as ModeWriter)(this, { execution: "sync" }); (arg as ModeWriter)(this, { execution: "async" }); return; } const content = arg as string; const lines = content.split("\n").filter((x) => x); const minIndent = Math.min(...lines.map((x) => x.length - x.trimStart().length)); const dedented = lines.map((x) => x.slice(minIndent)).map((x) => " ".repeat(this.indent * 2) + x); for (const line of dedented) { this.content.push(line); } } compile(): any { const F = Function; const content = this?.content ?? [``]; const factory = new F( ...Object.keys(this.closed), `return function (${this.args.join(", ")}) {\n${content.join("\n")}\n};` ); return factory(...Object.values(this.closed)) as any; } }