import {
findBlockEnd,
isBlank,
isIfOpen,
nextCodeLine,
} from "./postprocess-shared.js";
const GOTO_LABEL_RE = /^goto\s+(label_0x[\da-f]+);$/i;
const LEAVE_LABEL_RE = /^leave\s+(label_0x[\da-f]+);$/i;
const LABEL_LINE_RE = /^(label_0x[\da-f]+):$/i;
const IF_GOTO_RE = /^if\s+.+\{\s*goto\s+(label_0x[\da-f]+);\s*\}$/i;
const DO_WHILE_END_RE = /^}\s+while\s+(?:!\((.+)\)|(.+))\);?$/;
export function rewriteGotoDoWhile(statements) {
let index = 0;
while (index < statements.length) {
const trimmed = statements[index].trim();
const labelMatch = GOTO_LABEL_RE.exec(trimmed);
if (!labelMatch) {
index++;
continue;
}
const label = labelMatch[1];
const doIdx = nextCodeLine(statements, index);
if (doIdx < 0 || statements[doIdx].trim() !== "do {") {
index++;
continue;
}
const endIdx = findBlockEnd(statements, doIdx);
if (endIdx < 0) {
index++;
continue;
}
const endTrimmed = statements[endIdx].trim();
const condMatch = DO_WHILE_END_RE.exec(endTrimmed);
if (!condMatch) {
index++;
continue;
}
const condition = condMatch[1] ? `!(${condMatch[1]})` : condMatch[2];
const labelLine = `${label}:`;
let labelIdx = -1;
for (let i = doIdx + 1; i < endIdx; i++) {
if (statements[i].trim() === labelLine) {
labelIdx = i;
break;
}
}
if (labelIdx < 0) {
index++;
continue;
}
const setupLines = [];
for (let i = labelIdx + 1; i < endIdx; i++) {
if (!isBlank(statements[i])) setupLines.push(i);
}
statements[index] = "";
statements[doIdx] = `while ${condition} {`;
statements[labelIdx] = "";
if (setupLines.length === 0) {
statements[endIdx] = "}";
} else {
const copies = setupLines.map((i) => statements[i]);
for (let j = 0; j < copies.length; j++) {
statements.splice(doIdx + j, 0, copies[j]);
}
statements[endIdx + copies.length] = "}";
}
index++;
}
}
export function rewriteIfGotoToWhile(statements) {
let index = 0;
while (index < statements.length) {
const trimmed = statements[index].trim();
const labelMatch = LABEL_LINE_RE.exec(trimmed);
if (!labelMatch) {
index++;
continue;
}
const label = labelMatch[1];
let ifIdx = -1;
for (let i = index + 1; i < statements.length; i++) {
const t = statements[i].trim();
if (isIfOpen(t)) {
ifIdx = i;
break;
}
}
if (ifIdx < 0) {
index++;
continue;
}
const endIdx = findBlockEnd(statements, ifIdx);
if (endIdx < 0 || statements[endIdx].trim() !== "}") {
index++;
continue;
}
const gotoTarget = `goto ${label};`;
let gotoIdx = -1;
for (let i = ifIdx + 1; i < endIdx; i++) {
if (statements[i].trim() === gotoTarget) {
gotoIdx = i;
break;
}
}
if (gotoIdx < 0) {
index++;
continue;
}
const setupLines = [];
for (let i = index + 1; i < ifIdx; i++) {
if (!isBlank(statements[i])) setupLines.push(statements[i]);
}
statements[index] = "";
statements[ifIdx] = statements[ifIdx].trim().replace(/^if /, "while ");
statements[gotoIdx] = "";
if (setupLines.length > 0) {
for (let j = 0; j < setupLines.length; j++) {
statements.splice(endIdx + j, 0, setupLines[j]);
}
}
index++;
}
}
export function eliminateFallthroughGotos(statements) {
for (let i = 0; i < statements.length; i++) {
const trimmed = statements[i].trim();
const labelMatch =
GOTO_LABEL_RE.exec(trimmed) ??
LEAVE_LABEL_RE.exec(trimmed) ??
IF_GOTO_RE.exec(trimmed);
if (!labelMatch) continue;
const label = labelMatch[1];
const labelLine = `${label}:`;
let probe = i + 1;
while (probe < statements.length) {
const t = statements[probe].trim();
if (t === "" || t.startsWith("//") || t === "}") {
probe++;
continue;
}
if (t === labelLine) {
statements[i] = "";
}
break;
}
}
}
export function rewriteLabelGotoToLoop(statements) {
const standaloneGotos = new Set();
for (const stmt of statements) {
const t = stmt.trim();
if (t.startsWith("goto label_") && t.endsWith(";")) standaloneGotos.add(t);
}
if (standaloneGotos.size === 0) return;
let index = 0;
while (index < statements.length) {
const trimmed = statements[index].trim();
const labelMatch = LABEL_LINE_RE.exec(trimmed);
if (!labelMatch) {
index++;
continue;
}
const label = labelMatch[1];
const gotoTarget = `goto ${label};`;
if (!standaloneGotos.has(gotoTarget)) {
index++;
continue;
}
let depth = 0;
let gotoIdx = -1;
for (let j = index + 1; j < statements.length; j++) {
const t = statements[j].trim();
if (t === gotoTarget && depth === 0) {
gotoIdx = j;
break;
}
if (t.endsWith("{")) depth++;
if (t === "}" || t.startsWith("} ")) {
depth--;
if (depth < 0) break;
}
}
if (gotoIdx < 0) {
index++;
continue;
}
const labelDecl = `${label}:`;
let hasOtherReference = false;
for (let i = 0; i < statements.length; i++) {
if (i === index || i === gotoIdx) continue;
const t = statements[i].trim();
if (t === labelDecl || t.includes(gotoTarget)) {
hasOtherReference = true;
break;
}
}
if (hasOtherReference) {
index++;
continue;
}
const labelIndent = statements[index].slice(0, statements[index].length - statements[index].trimStart().length);
const gotoIndent = statements[gotoIdx].slice(0, statements[gotoIdx].length - statements[gotoIdx].trimStart().length);
statements[index] = `${labelIndent}loop {`;
statements[gotoIdx] = `${gotoIndent}}`;
index++;
}
}
export function removeOrphanedLabels(statements) {
const referenced = new Set();
for (const stmt of statements) {
const t = stmt.trim();
const gotoM = GOTO_LABEL_RE.exec(t);
if (gotoM) referenced.add(gotoM[1]);
const leaveM = LEAVE_LABEL_RE.exec(t);
if (leaveM) referenced.add(leaveM[1]);
const ifGotoM = IF_GOTO_RE.exec(t);
if (ifGotoM) referenced.add(ifGotoM[1]);
}
for (let i = 0; i < statements.length; i++) {
const m = LABEL_LINE_RE.exec(statements[i].trim());
if (m && !referenced.has(m[1])) {
statements[i] = "";
}
}
}