import {
collectDerivedWarnings,
rewriteForLoops,
} from "./high-level-control-flow-shared.js";
import { findMnemonicFrom, findTryFrom, tryHandlerTargets } from "./high-level-try-shared.js";
export function createMalformedTryLifter(runtime) {
const { createState, cloneState, executeStraightLine } = runtime;
function liftMalformedTryBlock({
instructions,
manifestMethod,
context,
methodOffset,
tryIndex,
bodyStartIndex,
catchIndex,
finallyIndex,
initialState = null,
}) {
const handlerIndices = [catchIndex, finallyIndex]
.filter((index) => index !== null && index !== undefined)
.sort((left, right) => left - right);
const bodyEndIndex = handlerIndices[0] ?? instructions.length;
if (bodyEndIndex < bodyStartIndex) {
return null;
}
const prefixState =
initialState === null
? createState(manifestMethod, context, methodOffset, instructions)
: cloneState(initialState);
const statementBase = initialState === null ? 0 : prefixState.statements.length;
if (initialState === null) {
executeStraightLine(prefixState, instructions.slice(0, tryIndex));
}
const statements = [...prefixState.statements.slice(statementBase), "try {"];
const branchStates = [];
const tryBodyState = cloneState(prefixState);
const bodySlice = instructions.slice(bodyStartIndex, bodyEndIndex);
const bodyResult = liftPossiblyNestedSlice(
tryBodyState,
bodySlice,
manifestMethod,
context,
methodOffset,
);
statements.push(...bodyResult.statements);
branchStates.push(bodyResult.state);
let catchState = null;
if (catchIndex !== null && catchIndex !== undefined) {
const catchEndIndex = finallyIndex ?? instructions.length;
catchState = cloneState(prefixState);
catchState.stack.push("exception");
executeStraightLine(catchState, instructions.slice(catchIndex, catchEndIndex));
statements.push("} catch {");
statements.push(...catchState.statements.slice(prefixState.statements.length));
branchStates.push(catchState);
}
let finallyState = null;
if (finallyIndex !== null && finallyIndex !== undefined) {
const finallyEndIndex = findMnemonicFrom(instructions, finallyIndex, "ENDFINALLY");
const finallySliceEnd = finallyEndIndex >= 0 ? finallyEndIndex : instructions.length;
const upstream = branchStates.at(-1) ?? tryBodyState;
finallyState = cloneState(upstream);
executeStraightLine(finallyState, instructions.slice(finallyIndex, finallySliceEnd));
statements.push("} finally {");
statements.push(...finallyState.statements.slice(upstream.statements.length));
branchStates.push(finallyState);
}
statements.push("}");
const endState = branchStates.at(-1) ?? tryBodyState;
const result = rewriteForLoops({
statements,
warnings: collectDerivedWarnings(prefixState, ...branchStates),
});
result.state = endState;
return result;
}
function liftPossiblyNestedSlice(state, slice, manifestMethod, context, methodOffset) {
const nestedResult = liftNestedMalformedTrySlice(
state,
slice,
manifestMethod,
context,
methodOffset,
);
if (nestedResult !== null) {
return nestedResult;
}
const statementBase = state.statements.length;
executeStraightLine(state, slice);
return {
statements: state.statements.slice(statementBase),
warnings: state.warnings,
state,
};
}
function liftNestedMalformedTrySlice(state, slice, manifestMethod, context, methodOffset) {
const nestedTryIndex = findTryFrom(slice, 0);
if (nestedTryIndex < 0) {
return null;
}
const statementBase = state.statements.length;
executeStraightLine(state, slice.slice(0, nestedTryIndex));
const nestedInstruction = slice[nestedTryIndex];
const handlerTargets = tryHandlerTargets(nestedInstruction);
if (handlerTargets === null) {
return null;
}
const indexByOffset = new Map();
for (let i = 0; i < slice.length; i++) {
indexByOffset.set(slice[i].offset, i);
}
const { bodyStart, catchTarget, finallyTarget } = handlerTargets;
const bodyStartIndex = indexByOffset.get(bodyStart);
if (bodyStartIndex === undefined) {
return null;
}
const catchIndex = catchTarget === null ? null : indexByOffset.get(catchTarget);
const finallyIndex = finallyTarget === null ? null : indexByOffset.get(finallyTarget);
const hasMissingHandlers =
(catchTarget !== null && catchIndex === undefined) ||
(finallyTarget !== null && finallyIndex === undefined);
const hasNoUsableHandlers = catchTarget === null && finallyTarget === null;
const endtryIndex = findMnemonicFrom(slice, bodyStartIndex, "ENDTRY");
if (!hasNoUsableHandlers && !hasMissingHandlers && endtryIndex >= 0) {
return null;
}
const nestedResult = liftMalformedTryBlock({
instructions: slice,
manifestMethod,
context,
methodOffset,
tryIndex: nestedTryIndex,
bodyStartIndex,
catchIndex,
finallyIndex,
initialState: state,
});
if (nestedResult === null) {
return null;
}
return {
statements: [
...state.statements.slice(statementBase),
...nestedResult.statements,
],
warnings: nestedResult.warnings ?? [],
state: nestedResult.state ?? state,
};
}
return liftMalformedTryBlock;
}