diff --git a/blocks-algo.js b/blocks-algo.js index 144f59e..a9542d0 100644 --- a/blocks-algo.js +++ b/blocks-algo.js @@ -259,15 +259,20 @@ class WCDecision extends WCBlock { return; } if (el instanceof WCDecision) { - // Decisions manage their own visited tracking inside generateCode. c += el.generateCode(visited, loopTargets); } else { visited.add(el); if (loopTargets && loopTargets.has(el)) { c += "\n" + el.id + ":"; } + // Let proc calls know about the visited set so their error-path + // generation uses it instead of a fresh local set. + el._genVisited = visited; c += el.generateOwnCode(); - for (const p of el.wcNextFlow) walk(p.toElement); + if (!el._noWalkNext) { + for (const p of el.wcNextFlow) walk(p.toElement); + } + el._noWalkNext = false; } }; for (const el of roots) { diff --git a/blocks-proc.js b/blocks-proc.js index 12f8e22..f0dfe7b 100644 --- a/blocks-proc.js +++ b/blocks-proc.js @@ -509,10 +509,12 @@ class WCProcCall extends WCBlock { return ""; } visited.add(this); + // Store the main-traversal visited set so _generateCallWithError can + // use it instead of a fresh local set — prevents re-walking blocks + // (like a loop-decision) that are already covered by the main traversal. + this._genVisited = visited; let own = this.generateOwnCode(); - // The PROC CALL itself might be targeted by a back-edge for the success path. - // That's handled by generateNextCode on the predecessor block. - // If this block is a loop target, emit the label before ownCode. + this._genVisited = null; if (loopTargets && loopTargets.has(this)) { own = "\n" + this.id + ":\n" + own; } @@ -572,7 +574,9 @@ class WCProcCall extends WCBlock { if (returnStyle === "error") { const call = `${procName}(${inputs.join(", ")})`; if (hasErrPath) { - return this._generateCallWithError(`\nif (${call} != 0)`, errPaths); + const result = this._generateCallWithError(`\nif (${call} != 0)`, errPaths); + this._noWalkNext = true; + return result; } return `\n${call};\n`; } @@ -580,7 +584,9 @@ class WCProcCall extends WCBlock { if (returnStyle === "value" && outputs.length === 1) { const outVar = outputs[0].replace(/^&/, ""); if (hasErrPath) { - return this._generateCallWithError(`\nif ((${outVar} = ${procName}(${inputs.join(", ")})) != 0)`, errPaths); + const result = this._generateCallWithError(`\nif ((${outVar} = ${procName}(${inputs.join(", ")})) != 0)`, errPaths); + this._noWalkNext = true; + return result; } return `\n${procName}(${inputs.join(", ")});\n`; } @@ -596,7 +602,7 @@ class WCProcCall extends WCBlock { return CodeGen.generateCallWithError(ifHeader, errTargets, okTargets, function (b) { return b instanceof WCDecision; - }, this._loopTargets); + }, this._loopTargets, this._genVisited); } saveData() { diff --git a/codegen/index.js b/codegen/index.js index ac05ef5..1850a77 100644 --- a/codegen/index.js +++ b/codegen/index.js @@ -108,18 +108,23 @@ return; } if (isDecision(el)) { - // Decisions manage their own visited tracking inside generateCode. parts.push(el.generateCode(visited, loopTargets)); } else { visited.add(el); if (loopTargets.has(el)) { parts.push("\n" + (el.id || "block") + ":"); } + el._genVisited = visited; parts.push(el.generateOwnCode()); - var flow = el.wcNextFlow || el.nextFlow || []; - for (var j = 0; j < flow.length; j++) { - walk(flow[j].toElement); + // If the block's generateOwnCode already walked the success path + // (e.g. error-handling proc call), don't re-walk its nextFlow. + if (!el._noWalkNext) { + var flow = el.wcNextFlow || el.nextFlow || []; + for (var j = 0; j < flow.length; j++) { + walk(flow[j].toElement); + } } + el._noWalkNext = false; } } @@ -137,7 +142,7 @@ * then success-exclusive and shared code after it (no else). */ CodeGen.generateCallWithError = function ( - ifHeader, errTargets, okTargets, isDecision, loopTargets + ifHeader, errTargets, okTargets, isDecision, loopTargets, mainVisited ) { loopTargets = loopTargets || new Set(); var errAll = CodeGen.branchDescendants(errTargets, isDecision); @@ -147,7 +152,9 @@ if (okAll.has(b)) shared.add(b); }); - var visited = new Set(); + // Create a local visited seeded from the main set so blocks walked by + // the error/success branches don't pollute the main traversal's set. + var visited = new Set(mainVisited || []); var errCode = CodeGen.emitBranch(errTargets, shared, visited, isDecision, loopTargets); var okCode = CodeGen.emitBranch(okTargets, shared, visited, isDecision, loopTargets); var sharedCode = CodeGen.emitBranch( diff --git a/tests/compile/run.js b/tests/compile/run.js index e841bab..0e64270 100644 --- a/tests/compile/run.js +++ b/tests/compile/run.js @@ -99,18 +99,22 @@ function procCallBlock(id, procName, inputs, returnStyle, returnValueName, okTar var okTargets = self.nextFlow.map(function (p) { return p.toElement; }); - return CodeGen.generateCallWithError( + var result = CodeGen.generateCallWithError( "\n" + ifHeader, self.errTargets, okTargets, function (b) { return b.isDecision; }, - self._loopTargets + self._loopTargets, + self._genVisited ); + self._noWalkNext = true; + return result; }, generateCode: function (visited, loopTargets) { self._loopTargets = loopTargets; + self._genVisited = visited; if (visited.has(this)) { if (loopTargets && loopTargets.has(this)) return "\n goto " + this.id + ";\n"; return "";