Files
avcf/codegen/index.js
T
adrien 82627a3852 refactor: only emit labels for blocks that are actual back-edge targets
- Add CodeGen.findBackEdges(root) pre-pass — DFS-based back-edge
  detection that finds all blocks targeted by at least one back-edge
- Only those blocks get C labels; only those back-edges emit goto
- emitBranch reverts to generateOwnCode + manual successor walking
  (respects stop set properly for shared blocks)
- generateCode/generateNextCode accept loopTargets parameter;
  visited-but-not-back-edge blocks are silently skipped
- No more -Wunused-label warnings in compiled output
2026-06-23 02:51:17 +02:00

189 lines
6.8 KiB
JavaScript

// ── Pure C code generation — no DOM dependencies ─────────────────────────
//
// These functions operate on generic block objects with the interface:
// { id: string, generateOwnCode(): string, generateCode(visited, loopTargets): string,
// wcNextFlow?: [{ toElement: block }], nextFlow?: [{ toElement: block }],
// isDecision?: boolean }
//
// Browser DOM blocks use wcNextFlow; test/mock blocks use nextFlow.
// Decision blocks must expose isDecision = true.
(function () {
"use strict";
var CodeGen = {};
// ── Back-edge detection ──────────────────────────────────────────────
/**
* Traverses the flow graph from a root and finds all back-edges
* (edges to a block already on the current DFS path). Returns a Set
* of blocks that are targets of at least one back-edge — these are
* the only blocks that need C labels and can be goto targets.
* @param {Object} root
* @param {function(Object): Array} [getNext] returns [{toElement:..}]
* @param {function(Object): boolean} [isDecision]
* @returns {Set}
*/
CodeGen.findBackEdges = function (root, getNext, isDecision) {
isDecision = isDecision || function (b) { return b.isDecision; };
getNext = getNext || function (b) { return b.wcNextFlow || b.nextFlow || []; };
var visited = new Set();
var onPath = new Set();
var loopTargets = new Set();
function dfs(block) {
if (visited.has(block)) return;
visited.add(block);
onPath.add(block);
var flow = getNext(block);
for (var i = 0; i < flow.length; i++) {
var target = flow[i].toElement;
if (!target) continue;
if (onPath.has(target)) {
loopTargets.add(target);
} else if (!visited.has(target)) {
dfs(target);
}
}
onPath["delete"](block);
}
dfs(root);
return loopTargets;
};
// ── Branch analysis ──────────────────────────────────────────────────
/**
* Collects all blocks reachable from the given roots via nextFlow,
* excluding the interior of decision blocks.
* @param {Array} roots
* @param {function(Object): boolean} [isDecision]
* @returns {Set}
*/
CodeGen.branchDescendants = function (roots, isDecision) {
isDecision = isDecision || function (b) { return b.isDecision; };
var d = new Set(), q = roots.slice();
for (var i = 0; i < q.length; i++) {
var el = q[i];
if (d.has(el)) continue;
d.add(el);
if (isDecision(el)) continue;
var flow = el.wcNextFlow || el.nextFlow || [];
for (var j = 0; j < flow.length; j++) {
var target = flow[j].toElement;
if (target && !d.has(target)) q.push(target);
}
}
return d;
};
// ── Branch code emission ─────────────────────────────────────────────
/**
* Emits C code for a branch by walking from root blocks. Only blocks
* in loopTargets get C labels / gotos. The stop set prevents shared
* blocks from being emitted in individual branches.
*
* @param {Array} roots starting blocks for this branch
* @param {Set} stop blocks to exclude (shared between branches)
* @param {Set} visited blocks already emitted (modified in-place)
* @param {function(Object): boolean} isDecision
* @param {Set} loopTargets back-edge targets from findBackEdges
* @returns {string}
*/
CodeGen.emitBranch = function (roots, stop, visited, isDecision, loopTargets) {
loopTargets = loopTargets || new Set();
var parts = [];
function walk(el) {
if (stop && stop.has(el)) return;
if (visited.has(el)) {
if (loopTargets.has(el)) {
parts.push("\n goto " + (el.id || "block") + ";\n");
}
return;
}
visited.add(el);
if (loopTargets.has(el)) {
parts.push("\n" + (el.id || "block") + ":");
}
if (isDecision(el)) {
parts.push(el.generateCode(visited, loopTargets));
} else {
parts.push(el.generateOwnCode());
var flow = el.wcNextFlow || el.nextFlow || [];
for (var j = 0; j < flow.length; j++) {
walk(flow[j].toElement);
}
}
}
for (var i = 0; i < roots.length; i++) {
if (!visited.has(roots[i])) walk(roots[i]);
}
return parts.join("");
};
// ── Error-path code generation ───────────────────────────────────────
/**
* Generates the error-path branch for a proc call: computes shared blocks
* between error and success paths, emits error-exclusive code inside `if`,
* then success-exclusive and shared code after it (no else).
*/
CodeGen.generateCallWithError = function (
ifHeader, errTargets, okTargets, isDecision, loopTargets
) {
loopTargets = loopTargets || new Set();
var errAll = CodeGen.branchDescendants(errTargets, isDecision);
var okAll = CodeGen.branchDescendants(okTargets, isDecision);
var shared = new Set();
errAll.forEach(function (b) {
if (okAll.has(b)) shared.add(b);
});
var visited = new Set();
var errCode = CodeGen.emitBranch(errTargets, shared, visited, isDecision, loopTargets);
var okCode = CodeGen.emitBranch(okTargets, shared, visited, isDecision, loopTargets);
var sharedCode = CodeGen.emitBranch(
Array.from(shared), new Set(), visited, isDecision, loopTargets
);
var out = ifHeader + " {\n" + errCode + "}\n";
if (okCode) out += okCode;
if (sharedCode) out += sharedCode;
return out;
};
// ── Full program assembly ────────────────────────────────────────────
CodeGen.generateCFile = function (entryBlock, includes, declarations) {
var lines = ["// --- WiseCode\u00ae ---"];
for (var i = 0; i < includes.length; i++) lines.push(includes[i]);
lines.push("");
lines.push("int main() {");
if (declarations) lines.push(declarations);
var loopTargets = CodeGen.findBackEdges(entryBlock);
var body = entryBlock.generateCode(new Set(), loopTargets);
lines.push(body);
lines.push("}");
lines.push("// --- END ---");
return lines.join("\n");
};
// ── Export ──────────────────────────────────────────────────────────
if (typeof module !== "undefined" && module.exports) {
module.exports = CodeGen;
}
if (typeof window !== "undefined") {
window.CodeGen = CodeGen;
}
})();