feat: loop support via goto labels on each block
- 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)
This commit is contained in:
+12
-10
@@ -249,23 +249,25 @@ class WCDecision extends WCBlock {
|
||||
}
|
||||
|
||||
_emitBranch(roots, stop, visited) {
|
||||
const v = new Set(visited);
|
||||
let c = "";
|
||||
const walk = el => {
|
||||
if (v.has(el) || stop.has(el)) return;
|
||||
v.add(el);
|
||||
if (el instanceof WCDecision) {
|
||||
c += el.generateCode(v);
|
||||
} else {
|
||||
c += el.generateOwnCode();
|
||||
for (const p of el.wcNextFlow) walk(p.toElement);
|
||||
if (stop && stop.has(el)) return;
|
||||
if (visited.has(el)) {
|
||||
c += "\n goto " + el.id + ";\n";
|
||||
return;
|
||||
}
|
||||
c += el.generateCode(visited);
|
||||
};
|
||||
for (const el of roots) walk(el);
|
||||
for (const el of roots) {
|
||||
if (!visited.has(el)) walk(el);
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
generateCode(visited = new Set()) {
|
||||
if (visited.has(this)) return "\n goto " + this.id + ";\n";
|
||||
visited.add(this);
|
||||
const header = "\n" + this.id + ":\n";
|
||||
const yesTargets = [], noTargets = [];
|
||||
for (const path of this.wcNextFlow) {
|
||||
const hook = path.fromHook;
|
||||
@@ -299,7 +301,7 @@ class WCDecision extends WCBlock {
|
||||
}
|
||||
}
|
||||
out += sharedCode;
|
||||
return out ? `\n${out}\n` : "";
|
||||
return out ? header + out + "\n" : header;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -502,9 +502,9 @@ class WCProcCall extends WCBlock {
|
||||
}
|
||||
|
||||
generateCode(visited = new Set()) {
|
||||
if (visited.has(this)) return "";
|
||||
if (visited.has(this)) return "\n goto " + this.id + ";\n";
|
||||
visited.add(this);
|
||||
const own = this.generateOwnCode();
|
||||
const own = "\n" + this.id + ":\n" + this.generateOwnCode();
|
||||
if (this._el.errHook && this._el.errHook.classList.contains("visible") &&
|
||||
Array.from(this.wcNextFlow).some(p => p.fromHook === this._el.errHook)) {
|
||||
return own;
|
||||
|
||||
+17
-17
@@ -46,8 +46,9 @@
|
||||
|
||||
/**
|
||||
* Emits C code for a branch by walking from root blocks, stopping at
|
||||
* blocks in the stop set and at decision boundaries. Each visited block
|
||||
* is added to the visited set to prevent double-emission.
|
||||
* 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
|
||||
@@ -60,24 +61,20 @@
|
||||
function (b) {
|
||||
return b.isDecision;
|
||||
};
|
||||
var v = new Set(visited);
|
||||
var parts = [];
|
||||
|
||||
function walk(el) {
|
||||
if (v.has(el) || (stop && stop.has(el))) return;
|
||||
v.add(el);
|
||||
if (isDecision(el)) {
|
||||
parts.push(el.generateCode(v));
|
||||
} else {
|
||||
parts.push(el.generateOwnCode());
|
||||
var flow = el.wcNextFlow || el.nextFlow || [];
|
||||
for (var j = 0; j < flow.length; j++) {
|
||||
walk(flow[j].toElement);
|
||||
}
|
||||
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++) walk(roots[i]);
|
||||
for (var i = 0; i < roots.length; i++) {
|
||||
if (!visited.has(roots[i])) walk(roots[i]);
|
||||
}
|
||||
return parts.join("");
|
||||
};
|
||||
|
||||
@@ -104,12 +101,15 @@
|
||||
if (okAll.has(b)) shared.add(b);
|
||||
});
|
||||
|
||||
var errCode = CodeGen.emitBranch(errTargets, shared, new Set(), isDecision);
|
||||
var okCode = CodeGen.emitBranch(okTargets, shared, new Set(), isDecision);
|
||||
// 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(),
|
||||
new Set(),
|
||||
visited,
|
||||
isDecision
|
||||
);
|
||||
|
||||
|
||||
+26
-11
@@ -31,11 +31,16 @@ function startBlock(id) {
|
||||
return "";
|
||||
},
|
||||
generateCode: function (visited) {
|
||||
if (visited.has(this)) return "";
|
||||
if (visited.has(this)) return "\n goto " + this.id + ";\n";
|
||||
visited.add(this);
|
||||
var c = this.generateOwnCode();
|
||||
var c = "\n" + this.id + ":\n" + this.generateOwnCode();
|
||||
for (var i = 0; i < this.nextFlow.length; i++) {
|
||||
c += this.nextFlow[i].toElement.generateCode(visited);
|
||||
var t = this.nextFlow[i].toElement;
|
||||
if (visited.has(t)) {
|
||||
c += "\n goto " + t.id + ";\n";
|
||||
} else {
|
||||
c += t.generateCode(visited);
|
||||
}
|
||||
}
|
||||
return c;
|
||||
},
|
||||
@@ -52,9 +57,9 @@ function endBlock(id, exitCode) {
|
||||
return code;
|
||||
},
|
||||
generateCode: function (visited) {
|
||||
if (visited.has(this)) return "";
|
||||
if (visited.has(this)) return "\n goto " + this.id + ";\n";
|
||||
visited.add(this);
|
||||
return this.generateOwnCode();
|
||||
return "\n" + this.id + ":\n" + this.generateOwnCode();
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -93,12 +98,17 @@ function procCallBlock(id, procName, inputs, returnStyle, returnValueName, okTar
|
||||
);
|
||||
},
|
||||
generateCode: function (visited) {
|
||||
if (visited.has(this)) return "";
|
||||
if (visited.has(this)) return "\n goto " + this.id + ";\n";
|
||||
visited.add(this);
|
||||
var own = this.generateOwnCode();
|
||||
var own = "\n" + this.id + ":\n" + this.generateOwnCode();
|
||||
if (self.errTargets.length > 0) return own;
|
||||
for (var i = 0; i < this.nextFlow.length; i++) {
|
||||
own += this.nextFlow[i].toElement.generateCode(visited);
|
||||
var t = this.nextFlow[i].toElement;
|
||||
if (visited.has(t)) {
|
||||
own += "\n goto " + t.id + ";\n";
|
||||
} else {
|
||||
own += t.generateCode(visited);
|
||||
}
|
||||
}
|
||||
return own;
|
||||
},
|
||||
@@ -116,11 +126,16 @@ function assignBlock(id, target, value) {
|
||||
return code;
|
||||
},
|
||||
generateCode: function (visited) {
|
||||
if (visited.has(this)) return "";
|
||||
if (visited.has(this)) return "\n goto " + this.id + ";\n";
|
||||
visited.add(this);
|
||||
var c = this.generateOwnCode();
|
||||
var c = "\n" + this.id + ":\n" + this.generateOwnCode();
|
||||
for (var i = 0; i < this.nextFlow.length; i++) {
|
||||
c += this.nextFlow[i].toElement.generateCode(visited);
|
||||
var t = this.nextFlow[i].toElement;
|
||||
if (visited.has(t)) {
|
||||
c += "\n goto " + t.id + ";\n";
|
||||
} else {
|
||||
c += t.generateCode(visited);
|
||||
}
|
||||
}
|
||||
return c;
|
||||
},
|
||||
|
||||
+12
-4
@@ -247,25 +247,33 @@ class WCBlock extends HTMLElement {
|
||||
|
||||
/**
|
||||
* Generates C code for this block and its successors, respecting the visited set
|
||||
* to avoid infinite loops.
|
||||
* to avoid infinite loops. Each block emits a C label (its id) so that loop
|
||||
* back-edges can target it with `goto label;`.
|
||||
* @param {Set} [visited]
|
||||
* @returns {string}
|
||||
*/
|
||||
generateCode(visited = new Set()) {
|
||||
if (visited.has(this)) return "";
|
||||
if (visited.has(this)) return "\n goto " + this.id + ";\n";
|
||||
visited.add(this);
|
||||
return this.generateOwnCode() + this.generateNextCode(visited);
|
||||
return "\n" + this.id + ":\n" + this.generateOwnCode() + this.generateNextCode(visited);
|
||||
}
|
||||
|
||||
/**
|
||||
* Walks wcNextFlow paths and accumulates code from downstream blocks.
|
||||
* When a target is already visited (loop back-edge), emits `goto label;`
|
||||
* instead of silently dropping the path.
|
||||
* @param {Set} visited
|
||||
* @returns {string}
|
||||
*/
|
||||
generateNextCode(visited) {
|
||||
let code = "";
|
||||
for (const path of this.wcNextFlow) {
|
||||
code += path.toElement.generateCode(visited);
|
||||
const target = path.toElement;
|
||||
if (visited.has(target)) {
|
||||
code += "\n goto " + target.id + ";\n";
|
||||
} else {
|
||||
code += target.generateCode(visited);
|
||||
}
|
||||
}
|
||||
return code;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user