import { CompilerError } from "../CompilerError";
import {
PrunedReactiveScopeBlock,
ReactiveFunction,
ReactiveScope,
ReactiveScopeBlock,
ReactiveScopeDependency,
ReactiveStatement,
ReactiveTerminal,
ReactiveValue,
} from "../HIR/HIR";
import {
printIdentifier,
printInstructionValue,
printPlace,
printType,
} from "../HIR/PrintHIR";
import { assertExhaustive } from "../Utils/utils";
export function printReactiveFunction(fn: ReactiveFunction): string {
const writer = new Writer();
writer.writeLine(`function ${fn.id !== null ? fn.id : "<unknown>"}(`);
writer.indented(() => {
for (const param of fn.params) {
if (param.kind === "Identifier") {
writer.writeLine(`${printPlace(param)},`);
} else {
writer.writeLine(`...${printPlace(param.place)},`);
}
}
});
writer.writeLine(") {");
writeReactiveInstructions(writer, fn.body);
writer.writeLine("}");
return writer.complete();
}
export function printReactiveScopeSummary(scope: ReactiveScope): string {
const items = [];
items.push("scope");
items.push(`@${scope.id}`);
items.push(`[${scope.range.start}:${scope.range.end}]`);
items.push(
`dependencies=[${Array.from(scope.dependencies)
.map((dep) => printDependency(dep))
.join(", ")}]`
);
items.push(
`declarations=[${Array.from(scope.declarations)
.map(([, decl]) =>
printIdentifier({ ...decl.identifier, scope: decl.scope })
)
.join(", ")}]`
);
items.push(
`reassignments=[${Array.from(scope.reassignments).map((reassign) =>
printIdentifier(reassign)
)}]`
);
if (scope.earlyReturnValue !== null) {
items.push(
`earlyReturn={id: ${printIdentifier(
scope.earlyReturnValue.value
)}, label: ${scope.earlyReturnValue.label}}}`
);
}
return items.join(" ");
}
export function writeReactiveBlock(
writer: Writer,
block: ReactiveScopeBlock
): void {
writer.writeLine(`${printReactiveScopeSummary(block.scope)} {`);
writeReactiveInstructions(writer, block.instructions);
writer.writeLine("}");
}
export function writePrunedScope(
writer: Writer,
block: PrunedReactiveScopeBlock
): void {
writer.writeLine(`<pruned> ${printReactiveScopeSummary(block.scope)} {`);
writeReactiveInstructions(writer, block.instructions);
writer.writeLine("}");
}
export function printDependency(dependency: ReactiveScopeDependency): string {
const identifier =
printIdentifier(dependency.identifier) +
printType(dependency.identifier.type);
return `${identifier}${dependency.path.map((prop) => `.${prop}`).join("")}`;
}
export function printReactiveInstructions(
instructions: Array<ReactiveStatement>
): string {
const writer = new Writer();
writeReactiveInstructions(writer, instructions);
return writer.complete();
}
export function writeReactiveInstructions(
writer: Writer,
instructions: Array<ReactiveStatement>
): void {
writer.indented(() => {
for (const instr of instructions) {
writeReactiveInstruction(writer, instr);
}
});
}
function writeReactiveInstruction(
writer: Writer,
instr: ReactiveStatement
): void {
switch (instr.kind) {
case "instruction": {
const { instruction } = instr;
const id = `[${instruction.id}]`;
if (instruction.lvalue !== null) {
writer.write(`${id} ${printPlace(instruction.lvalue)} = `);
writeReactiveValue(writer, instruction.value);
writer.newline();
} else {
writer.write(`${id} `);
writeReactiveValue(writer, instruction.value);
writer.newline();
}
break;
}
case "scope": {
writeReactiveBlock(writer, instr);
break;
}
case "pruned-scope": {
writePrunedScope(writer, instr);
break;
}
case "terminal": {
if (instr.label !== null) {
writer.write(`bb${instr.label.id}: `);
}
writeTerminal(writer, instr.terminal);
break;
}
default: {
assertExhaustive(
instr,
`Unexpected terminal kind \`${(instr as any).kind}\``
);
}
}
}
export function printReactiveValue(value: ReactiveValue): string {
const writer = new Writer();
writeReactiveValue(writer, value);
return writer.complete();
}
function writeReactiveValue(writer: Writer, value: ReactiveValue): void {
switch (value.kind) {
case "ConditionalExpression": {
writer.writeLine(`Ternary `);
writer.indented(() => {
writeReactiveValue(writer, value.test);
writer.writeLine(`? `);
writer.indented(() => {
writeReactiveValue(writer, value.consequent);
});
writer.writeLine(`: `);
writer.indented(() => {
writeReactiveValue(writer, value.alternate);
});
});
writer.newline();
break;
}
case "LogicalExpression": {
writer.writeLine(`Logical`);
writer.indented(() => {
writeReactiveValue(writer, value.left);
writer.write(`${value.operator} `);
writeReactiveValue(writer, value.right);
});
writer.newline();
break;
}
case "SequenceExpression": {
writer.writeLine(`Sequence`);
writer.indented(() => {
writer.indented(() => {
value.instructions.forEach((instr) =>
writeReactiveInstruction(writer, {
kind: "instruction",
instruction: instr,
})
);
writer.write(`[${value.id}] `);
writeReactiveValue(writer, value.value);
});
});
writer.newline();
break;
}
case "OptionalExpression": {
writer.append(`OptionalExpression optional=${value.optional}`);
writer.newline();
writer.indented(() => {
writeReactiveValue(writer, value.value);
});
writer.newline();
break;
}
default: {
const printed = printInstructionValue(value);
const lines = printed.split("\n");
if (lines.length === 1) {
writer.writeLine(printed);
} else {
writer.indented(() => {
for (const line of lines) {
writer.writeLine(line);
}
});
}
}
}
}
function writeTerminal(writer: Writer, terminal: ReactiveTerminal): void {
switch (terminal.kind) {
case "break": {
const id = terminal.id !== null ? `[${terminal.id}]` : [];
writer.writeLine(
`${id} break bb${terminal.target} (${terminal.targetKind})`
);
break;
}
case "continue": {
const id = `[${terminal.id}]`;
writer.writeLine(
`${id} continue bb${terminal.target} (${terminal.targetKind})`
);
break;
}
case "do-while": {
writer.writeLine(`[${terminal.id}] do-while {`);
writeReactiveInstructions(writer, terminal.loop);
writer.writeLine("} (");
writer.indented(() => {
writeReactiveValue(writer, terminal.test);
});
writer.writeLine(")");
break;
}
case "while": {
writer.writeLine(`[${terminal.id}] while (`);
writer.indented(() => {
writeReactiveValue(writer, terminal.test);
});
writer.writeLine(") {");
writeReactiveInstructions(writer, terminal.loop);
writer.writeLine("}");
break;
}
case "if": {
const { test, consequent, alternate } = terminal;
writer.writeLine(`[${terminal.id}] if (${printPlace(test)}) {`);
writeReactiveInstructions(writer, consequent);
if (alternate !== null) {
writer.writeLine("} else {");
writeReactiveInstructions(writer, alternate);
}
writer.writeLine("}");
break;
}
case "switch": {
writer.writeLine(
`[${terminal.id}] switch (${printPlace(terminal.test)}) {`
);
writer.indented(() => {
for (const case_ of terminal.cases) {
let prefix =
case_.test !== null ? `case ${printPlace(case_.test)}` : "default";
writer.writeLine(`${prefix}: {`);
writer.indented(() => {
const block = case_.block;
CompilerError.invariant(block != null, {
reason: "Expected case to have a block",
description: null,
loc: case_.test?.loc ?? null,
suggestions: null,
});
writeReactiveInstructions(writer, block);
});
writer.writeLine("}");
}
});
writer.writeLine("}");
break;
}
case "for": {
writer.writeLine(`[${terminal.id}] for (`);
writer.indented(() => {
writeReactiveValue(writer, terminal.init);
writer.writeLine(";");
writeReactiveValue(writer, terminal.test);
writer.writeLine(";");
if (terminal.update !== null) {
writeReactiveValue(writer, terminal.update);
}
});
writer.writeLine(") {");
writeReactiveInstructions(writer, terminal.loop);
writer.writeLine("}");
break;
}
case "for-of": {
writer.writeLine(`[${terminal.id}] for-of (`);
writer.indented(() => {
writeReactiveValue(writer, terminal.init);
writer.writeLine(";");
writeReactiveValue(writer, terminal.test);
});
writer.writeLine(") {");
writeReactiveInstructions(writer, terminal.loop);
writer.writeLine("}");
break;
}
case "for-in": {
writer.writeLine(`[${terminal.id}] for-in (`);
writer.indented(() => {
writeReactiveValue(writer, terminal.init);
});
writer.writeLine(") {");
writeReactiveInstructions(writer, terminal.loop);
writer.writeLine("}");
break;
}
case "throw": {
writer.writeLine(`[${terminal.id}] throw ${printPlace(terminal.value)}`);
break;
}
case "return": {
writer.writeLine(`[${terminal.id}] return ${printPlace(terminal.value)}`);
break;
}
case "label": {
writer.writeLine("{");
writeReactiveInstructions(writer, terminal.block);
writer.writeLine("}");
break;
}
case "try": {
writer.writeLine(`[${terminal.id}] try {`);
writeReactiveInstructions(writer, terminal.block);
writer.write(`} catch `);
if (terminal.handlerBinding !== null) {
writer.writeLine(`(${printPlace(terminal.handlerBinding)}) {`);
} else {
writer.writeLine(`{`);
}
writeReactiveInstructions(writer, terminal.handler);
writer.writeLine("}");
break;
}
default:
assertExhaustive(terminal, `Unhandled terminal ${terminal}`);
}
}
export class Writer {
#out: Array<string> = [];
#line: string;
#depth: number;
constructor({ depth }: { depth: number } = { depth: 0 }) {
this.#depth = Math.max(depth, 0);
this.#line = "";
}
complete(): string {
const line = this.#line.trimEnd();
if (line.length > 0) {
this.#out.push(line);
}
return this.#out.join("\n");
}
append(s: string): void {
this.write(s);
}
newline(): void {
const line = this.#line.trimEnd();
if (line.length > 0) {
this.#out.push(line);
}
this.#line = "";
}
write(s: string): void {
if (this.#line.length === 0 && this.#depth > 0) {
this.#line = " ".repeat(this.#depth);
}
this.#line += s;
}
writeLine(s: string): void {
this.write(s);
this.newline();
}
indented(f: () => void): void {
this.#depth++;
f();
this.#depth--;
}
}