import {CompilerError} from '..';
import {
BlockId,
GeneratedSource,
HIRFunction,
MutableRange,
Place,
ReactiveScope,
ScopeId,
} from './HIR';
import {
eachInstructionLValue,
eachInstructionOperand,
eachTerminalOperand,
terminalFallthrough,
} from './visitors';
type Block =
| ({
kind: 'ProgramBlockSubtree';
id: BlockId;
} & MutableRange)
| ({
kind: 'Scope';
id: ScopeId;
} & MutableRange);
export function getScopes(fn: HIRFunction): Set<ReactiveScope> {
const scopes: Set<ReactiveScope> = new Set();
function visitPlace(place: Place): void {
const scope = place.identifier.scope;
if (scope != null) {
if (scope.range.start !== scope.range.end) {
scopes.add(scope);
}
}
}
for (const [, block] of fn.body.blocks) {
for (const instr of block.instructions) {
for (const operand of eachInstructionLValue(instr)) {
visitPlace(operand);
}
for (const operand of eachInstructionOperand(instr)) {
visitPlace(operand);
}
}
for (const operand of eachTerminalOperand(block.terminal)) {
visitPlace(operand);
}
}
return scopes;
}
export function rangePreOrderComparator(
a: MutableRange,
b: MutableRange,
): number {
const startDiff = a.start - b.start;
if (startDiff !== 0) return startDiff;
return b.end - a.end;
}
export function recursivelyTraverseItems<T, TContext>(
items: Array<T>,
getRange: (val: T) => MutableRange,
context: TContext,
enter: (val: T, context: TContext) => void,
exit: (val: T, context: TContext) => void,
): void {
items.sort((a, b) => rangePreOrderComparator(getRange(a), getRange(b)));
let activeItems: Array<T> = [];
const ranges = items.map(getRange);
for (let i = 0; i < items.length; i++) {
const curr = items[i];
const currRange = ranges[i];
for (let i = activeItems.length - 1; i >= 0; i--) {
const maybeParent = activeItems[i];
const maybeParentRange = getRange(maybeParent);
const disjoint = currRange.start >= maybeParentRange.end;
const nested = currRange.end <= maybeParentRange.end;
CompilerError.invariant(disjoint || nested, {
reason: 'Invalid nesting in program blocks or scopes',
description: `Items overlap but are not nested: ${maybeParentRange.start}:${maybeParentRange.end}(${currRange.start}:${currRange.end})`,
loc: GeneratedSource,
});
if (disjoint) {
exit(maybeParent, context);
activeItems.length = i;
} else {
break;
}
}
enter(curr, context);
activeItems.push(curr);
}
let curr = activeItems.pop();
while (curr != null) {
exit(curr, context);
curr = activeItems.pop();
}
}
const no_op: () => void = () => {};
export function assertValidBlockNesting(fn: HIRFunction): void {
const scopes = getScopes(fn);
const blocks: Array<Block> = [...scopes].map(scope => ({
kind: 'Scope',
id: scope.id,
...scope.range,
})) as Array<Block>;
for (const [, block] of fn.body.blocks) {
const fallthroughId = terminalFallthrough(block.terminal);
if (fallthroughId != null) {
const fallthrough = fn.body.blocks.get(fallthroughId)!;
const end = fallthrough.instructions[0]?.id ?? fallthrough.terminal.id;
blocks.push({
kind: 'ProgramBlockSubtree',
id: block.id,
start: block.terminal.id,
end,
});
}
}
recursivelyTraverseItems(blocks, block => block, null, no_op, no_op);
}