- Every block now emits a C label (its id) before its code - generateNextCode emits goto label; for back-edges (visited targets) - _emitBranch delegates to generateCode (not generateOwnCode), so labels are emitted consistently in both main and branch traversals - generateCallWithError threads a shared visited set across all three emitBranch calls, preventing duplicate label emission for shared blocks - emitBranch skips roots already visited by previous roots in the same branch (prevents spurious gotos from shared-block traversal)
151 lines
5.0 KiB
JavaScript
151 lines
5.0 KiB
JavaScript
// ── Pure C code generation — no DOM dependencies ─────────────────────────
|
|
//
|
|
// These functions operate on generic block objects with the interface:
|
|
// { generateOwnCode(): string,
|
|
// generateCode(visited: Set): string,
|
|
// wcNextFlow?: [{ toElement: block }],
|
|
// nextFlow?: [{ toElement: block }] }
|
|
//
|
|
// Browser DOM blocks use wcNextFlow; test/mock blocks use nextFlow.
|
|
// Decision blocks must expose isDecision = true.
|
|
|
|
(function () {
|
|
"use strict";
|
|
|
|
var CodeGen = {};
|
|
|
|
/**
|
|
* Collects all blocks reachable from the given roots via nextFlow,
|
|
* excluding the interior of decision blocks (they handle their own
|
|
* branching). Returns a Set of block references.
|
|
* @param {Array} roots
|
|
* @param {function(block): 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;
|
|
};
|
|
|
|
/**
|
|
* Emits C code for a branch by walking from root blocks, stopping at
|
|
* blocks in the stop set and at decision boundaries. When a loop
|
|
* back-edge encounters an already-visited block, a `goto label;` is
|
|
* emitted instead of silently dropping the edge.
|
|
* @param {Array} roots
|
|
* @param {Set} stop blocks to exclude (shared between branches)
|
|
* @param {Set} visited blocks already emitted
|
|
* @param {function(block): boolean} [isDecision]
|
|
* @returns {string}
|
|
*/
|
|
CodeGen.emitBranch = function (roots, stop, visited, isDecision) {
|
|
isDecision =
|
|
isDecision ||
|
|
function (b) {
|
|
return b.isDecision;
|
|
};
|
|
var parts = [];
|
|
|
|
function walk(el) {
|
|
if (stop && stop.has(el)) return;
|
|
if (visited.has(el)) {
|
|
parts.push("\n goto " + (el.id || "block") + ";\n");
|
|
return;
|
|
}
|
|
parts.push(el.generateCode(visited));
|
|
}
|
|
|
|
for (var i = 0; i < roots.length; i++) {
|
|
if (!visited.has(roots[i])) walk(roots[i]);
|
|
}
|
|
return parts.join("");
|
|
};
|
|
|
|
/**
|
|
* 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).
|
|
* @param {string} ifHeader e.g. 'if (func() != 0)' or 'if ((r=f()) != 0)'
|
|
* @param {Array} errTargets root blocks reachable from the error hook
|
|
* @param {Array} okTargets root blocks reachable from the success hook
|
|
* @param {function(block): boolean} [isDecision]
|
|
* @returns {string}
|
|
*/
|
|
CodeGen.generateCallWithError = function (
|
|
ifHeader,
|
|
errTargets,
|
|
okTargets,
|
|
isDecision
|
|
) {
|
|
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);
|
|
});
|
|
|
|
// Thread a shared visited set through all three emitBranch calls
|
|
// so shared blocks are not re-emitted with duplicate labels.
|
|
var visited = new Set();
|
|
var errCode = CodeGen.emitBranch(errTargets, shared, visited, isDecision);
|
|
var okCode = CodeGen.emitBranch(okTargets, shared, visited, isDecision);
|
|
var sharedCode = CodeGen.emitBranch(
|
|
Array.from(shared),
|
|
new Set(),
|
|
visited,
|
|
isDecision
|
|
);
|
|
|
|
var out = ifHeader + " {\n" + errCode + "}\n";
|
|
if (okCode) out += okCode;
|
|
if (sharedCode) out += sharedCode;
|
|
return out;
|
|
};
|
|
|
|
/**
|
|
* Assembles a complete C source file from a program's entry block,
|
|
* include directives, and variable declarations.
|
|
* @param {Object} entryBlock the first block in the flow (e.g. START)
|
|
* @param {string[]} includes #include directives
|
|
* @param {string} declarations variable declarations (already indented)
|
|
* @returns {string}
|
|
*/
|
|
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 body = entryBlock.generateCode(new Set());
|
|
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;
|
|
}
|
|
})();
|