import {CompilerError} from '../CompilerError';
import {
PrunedReactiveScopeBlock,
ReactiveFunction,
ReactiveScope,
ReactiveScopeBlock,
ReactiveScopeDependency,
ReactiveStatement,
ReactiveTerminal,
ReactiveValue,
} from '../HIR/HIR';
import {
printFunction,
printIdentifier,
printInstructionValue,
printPlace,
printType,
} from '../HIR/PrintHIR';
import {assertExhaustive} from '../Utils/utils';
export function printReactiveFunctionWithOutlined(
fn: ReactiveFunction,
): string {
const writer = new Writer();
writeReactiveFunction(fn, writer);
for (const outlined of fn.env.getOutlinedFunctions()) {
writer.writeLine('\nfunction ' + printFunction(outlined.fn));
}
return writer.complete();
}
export function printReactiveFunction(fn: ReactiveFunction): string {
const writer = new Writer();
writeReactiveFunction(fn, writer);
return writer.complete();
}
function writeReactiveFunction(fn: ReactiveFunction, writer: Writer): void {
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('}');
}
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(token => `${token.optional ? '?.' : '.'}${token.property}`).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--;
}
}