import {
collectDerivedWarnings,
rewriteForLoops,
} from "./high-level-control-flow-shared.js";
import { createMalformedTryLifter } from "./high-level-try-malformed.js";
import { findMnemonicFrom, findTryFrom, tryHandlerTargets } from "./high-level-try-shared.js";
import { jumpTarget } from "./high-level-utils.js";
export function createTryHelpers(runtime) {
const { createState, cloneState, executeStraightLine } = runtime;
const liftMalformedTryBlock = createMalformedTryLifter(runtime);
function tryLiftSimpleTryBlock(instructions, manifestMethod, context, methodOffset) {
const tryIndex = findTryFrom(instructions, 0);
if (tryIndex < 0) {
return null;
}
const tryInstruction = instructions[tryIndex];
const handlerTargets = tryHandlerTargets(tryInstruction);
if (handlerTargets === null) {
return null;
}
const { bodyStart, catchTarget, finallyTarget } = handlerTargets;
const indexByOffset = new Map();
for (let i = 0; i < instructions.length; i++) {
indexByOffset.set(instructions[i].offset, i);
}
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 endtryGlobalIndex = findMnemonicFrom(instructions, bodyStartIndex, "ENDTRY");
if (hasMissingHandlers || endtryGlobalIndex < 0) {
return liftMalformedTryBlock({
instructions,
manifestMethod,
context,
methodOffset,
tryIndex,
bodyStartIndex,
catchIndex,
finallyIndex,
});
}
let catchSlice = [];
let finallySlice = [];
let resumeSlice = [];
let allowBareTry = false;
if (catchTarget !== null && finallyTarget !== null) {
const finallyEndGlobalIndex = findMnemonicFrom(
instructions,
endtryGlobalIndex + 1,
"ENDFINALLY",
);
catchSlice = instructions.slice(catchIndex, finallyIndex);
if (finallyEndGlobalIndex >= 0) {
finallySlice = instructions.slice(finallyIndex, finallyEndGlobalIndex);
resumeSlice = instructions.slice(finallyEndGlobalIndex + 1);
}
} else if (catchTarget !== null) {
const catchEndGlobalIndex = findMnemonicFrom(
instructions,
endtryGlobalIndex + 1,
"ENDTRY",
);
if (catchEndGlobalIndex >= 0) {
const catchEndInstruction = instructions[catchEndGlobalIndex];
const catchEndTarget = jumpTarget(catchEndInstruction);
if (catchEndTarget !== null && catchEndTarget > catchTarget) {
catchSlice = instructions.slice(catchIndex, indexByOffset.get(catchEndTarget));
resumeSlice = instructions.slice(catchEndGlobalIndex + 1);
}
}
if (
catchSlice.length === 0 &&
instructions[catchIndex]?.opcode.mnemonic === "ENDFINALLY"
) {
allowBareTry = true;
resumeSlice = instructions.slice(catchIndex + 1);
}
} else if (finallyTarget !== null) {
const finallyEndGlobalIndex = findMnemonicFrom(
instructions,
finallyIndex,
"ENDFINALLY",
);
if (finallyEndGlobalIndex >= 0) {
finallySlice = instructions.slice(finallyIndex, finallyEndGlobalIndex);
resumeSlice = instructions.slice(finallyEndGlobalIndex + 1);
}
}
if (catchSlice.length === 0 && finallySlice.length === 0 && !allowBareTry) {
return null;
}
const prefixState = createState(manifestMethod, context, methodOffset, instructions);
executeStraightLine(prefixState, instructions.slice(0, tryIndex));
const tryBodyState = cloneState(prefixState);
executeStraightLine(tryBodyState, instructions.slice(bodyStartIndex, endtryGlobalIndex));
const statements = [...prefixState.statements];
statements.push("try {");
statements.push(...tryBodyState.statements.slice(prefixState.statements.length));
let catchState = null;
let finallyState = null;
let resumeState = null;
if (catchSlice.length > 0) {
catchState = cloneState(prefixState);
catchState.stack.push("exception");
executeStraightLine(catchState, catchSlice);
statements.push("} catch {");
statements.push(...catchState.statements.slice(prefixState.statements.length));
}
if (finallySlice.length > 0) {
finallyState = cloneState(prefixState);
executeStraightLine(finallyState, finallySlice);
statements.push("} finally {");
statements.push(...finallyState.statements.slice(prefixState.statements.length));
}
statements.push("}");
if (resumeSlice.length > 0) {
const upstream = finallyState ?? tryBodyState ?? prefixState;
resumeState = cloneState(upstream);
executeStraightLine(resumeState, resumeSlice);
statements.push(...resumeState.statements.slice(upstream.statements.length));
}
return rewriteForLoops({
statements,
warnings: collectDerivedWarnings(
prefixState,
tryBodyState,
catchState,
finallyState,
resumeState,
),
});
}
return { tryLiftSimpleTryBlock };
}