refactor: flat DFA code generation — every block is a label, every edge is a goto
Replace the nested if-else branch-analysis architecture with a flat DFA model using CodeGen.emitFlat — a single DFS walk emitting each block as a C label followed by its code and gotos to all successors. Removed: - _branchDescendants / emitBranch / generateCallWithError - _generateCallWithError, _noWalkNext, _genVisited - generateNextCode, findBackEdges - WCDecision._branchDescendants / _emitBranch / overridden generateCode Added: - CodeGen.emitFlat(entry) — flat DFS emit with labels + gotos - WCBlock.generateFlowCode() — base emits gotos from wcNextFlow - WCDecision.generateFlowCode() — if-goto / goto for decisions - WCProcCall.generateFlowCode() — if (call != 0) goto err / goto ok Output is flat — zero nested if-else for branch/error handling. Tests use local out/ directory.
This commit is contained in:
+8
-84
@@ -224,7 +224,7 @@ class WCOutput extends WCBlock {
|
|||||||
|
|
||||||
// ── WCDecision — conditional branch ────────────────────────────────────────
|
// ── WCDecision — conditional branch ────────────────────────────────────────
|
||||||
|
|
||||||
/** Conditional branch block; splits flow into yes/no branches and emits if/else C code. */
|
/** Conditional branch block; emits if-goto / goto for flat DFA code generation. */
|
||||||
class WCDecision extends WCBlock {
|
class WCDecision extends WCBlock {
|
||||||
connectedCallback() {
|
connectedCallback() {
|
||||||
super.connectedCallback();
|
super.connectedCallback();
|
||||||
@@ -235,98 +235,22 @@ class WCDecision extends WCBlock {
|
|||||||
return resolveSourceName(block) || "flag";
|
return resolveSourceName(block) || "flag";
|
||||||
}
|
}
|
||||||
|
|
||||||
_branchDescendants(roots) {
|
generateFlowCode() {
|
||||||
const d = new Set(), q = [...roots];
|
let yesTarget = null, noTarget = null;
|
||||||
for (const el of q) {
|
|
||||||
if (d.has(el)) continue;
|
|
||||||
d.add(el);
|
|
||||||
if (el instanceof WCDecision) continue;
|
|
||||||
for (const p of el.wcNextFlow) {
|
|
||||||
if (!d.has(p.toElement)) q.push(p.toElement);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return d;
|
|
||||||
}
|
|
||||||
|
|
||||||
_emitBranch(roots, stop, visited, loopTargets) {
|
|
||||||
let c = "";
|
|
||||||
const walk = el => {
|
|
||||||
if (stop && stop.has(el)) return;
|
|
||||||
if (visited.has(el)) {
|
|
||||||
if (loopTargets && loopTargets.has(el)) {
|
|
||||||
c += "\n goto " + el.id + ";\n";
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (el instanceof WCDecision) {
|
|
||||||
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();
|
|
||||||
if (!el._noWalkNext) {
|
|
||||||
for (const p of el.wcNextFlow) walk(p.toElement);
|
|
||||||
}
|
|
||||||
el._noWalkNext = false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
for (const el of roots) {
|
|
||||||
if (!visited.has(el)) walk(el);
|
|
||||||
}
|
|
||||||
return c;
|
|
||||||
}
|
|
||||||
|
|
||||||
generateCode(visited = new Set(), loopTargets) {
|
|
||||||
if (visited.has(this)) {
|
|
||||||
if (loopTargets && loopTargets.has(this)) {
|
|
||||||
return "\n goto " + this.id + ";\n";
|
|
||||||
}
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
visited.add(this);
|
|
||||||
const yesTargets = [], noTargets = [];
|
|
||||||
for (const path of this.wcNextFlow) {
|
for (const path of this.wcNextFlow) {
|
||||||
const hook = path.fromHook;
|
const hook = path.fromHook;
|
||||||
if (!hook || hook.id === "hook-out1") {
|
if (!hook || hook.id === "hook-out1") {
|
||||||
yesTargets.push(path.toElement);
|
yesTarget = path.toElement;
|
||||||
} else if (hook.id === "hook-out0") {
|
} else if (hook.id === "hook-out0") {
|
||||||
noTargets.push(path.toElement);
|
noTarget = path.toElement;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const yesAll = this._branchDescendants(yesTargets);
|
|
||||||
const noAll = this._branchDescendants(noTargets);
|
|
||||||
const shared = new Set([...yesAll].filter(el => noAll.has(el) && el !== this));
|
|
||||||
|
|
||||||
const yesCode = this._emitBranch(yesTargets, shared, visited, loopTargets);
|
|
||||||
const noCode = this._emitBranch(noTargets, shared, visited, loopTargets);
|
|
||||||
const sharedCode = this._emitBranch([...shared], new Set(), visited, loopTargets);
|
|
||||||
|
|
||||||
for (const el of yesAll) visited.add(el);
|
|
||||||
for (const el of noAll) visited.add(el);
|
|
||||||
|
|
||||||
const source = findDataSource(this._el.boolIn);
|
const source = findDataSource(this._el.boolIn);
|
||||||
const cond = source ? this._getBoolName(source) : "0";
|
const cond = source ? this._getBoolName(source) : "0";
|
||||||
let out = "";
|
let out = "";
|
||||||
let header = "";
|
if (yesTarget) out += ` if (${cond}) goto ${yesTarget.id};\n`;
|
||||||
if (loopTargets && loopTargets.has(this)) {
|
if (noTarget) out += ` goto ${noTarget.id};\n`;
|
||||||
header = "\n" + this.id + ":\n";
|
return out;
|
||||||
}
|
|
||||||
if (yesCode || noCode) {
|
|
||||||
if (yesCode) out += `if (${cond}) {\n${yesCode}}`;
|
|
||||||
if (noCode) {
|
|
||||||
out += yesCode
|
|
||||||
? ` else {\n${noCode}}`
|
|
||||||
: `if (!(${cond})) {\n${noCode}}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
out += sharedCode;
|
|
||||||
return out ? header + out + "\n" : header;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+71
-75
@@ -167,30 +167,6 @@ function _specDataType(spec) {
|
|||||||
return "int";
|
return "int";
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Adapter: wraps CodeGen.branchDescendants with the browser's instanceof check.
|
|
||||||
* @param {HTMLElement[]} roots
|
|
||||||
* @returns {Set<HTMLElement>}
|
|
||||||
*/
|
|
||||||
function _branchDescendants(roots) {
|
|
||||||
return CodeGen.branchDescendants(roots, function (b) {
|
|
||||||
return b instanceof WCDecision;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adapter: wraps CodeGen.emitBranch with the browser's instanceof check.
|
|
||||||
* @param {HTMLElement[]} roots
|
|
||||||
* @param {Set<HTMLElement>} stop
|
|
||||||
* @param {Set<HTMLElement>} visited
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
function _emitBranch(roots, stop, visited) {
|
|
||||||
return CodeGen.emitBranch(roots, stop, visited, function (b) {
|
|
||||||
return b instanceof WCDecision;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Procedure call block with dynamic argument fields, format-string support, and error-handling hooks. Lives in the Algorithm tab. */
|
/** Procedure call block with dynamic argument fields, format-string support, and error-handling hooks. Lives in the Algorithm tab. */
|
||||||
class WCProcCall extends WCBlock {
|
class WCProcCall extends WCBlock {
|
||||||
connectedCallback() {
|
connectedCallback() {
|
||||||
@@ -501,30 +477,6 @@ class WCProcCall extends WCBlock {
|
|||||||
return block.dataType === "string" ? name + ".data" : name;
|
return block.dataType === "string" ? name + ".data" : name;
|
||||||
}
|
}
|
||||||
|
|
||||||
generateCode(visited = new Set(), loopTargets) {
|
|
||||||
if (visited.has(this)) {
|
|
||||||
if (loopTargets && loopTargets.has(this)) {
|
|
||||||
return "\n goto " + this.id + ";\n";
|
|
||||||
}
|
|
||||||
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();
|
|
||||||
this._genVisited = null;
|
|
||||||
if (loopTargets && loopTargets.has(this)) {
|
|
||||||
own = "\n" + this.id + ":\n" + own;
|
|
||||||
}
|
|
||||||
if (this._el.errHook && this._el.errHook.classList.contains("visible") &&
|
|
||||||
Array.from(this.wcNextFlow).some(p => p.fromHook === this._el.errHook)) {
|
|
||||||
return own;
|
|
||||||
}
|
|
||||||
return own + this.generateNextCode(visited, loopTargets);
|
|
||||||
}
|
|
||||||
|
|
||||||
generateOwnCode() {
|
generateOwnCode() {
|
||||||
const qname = this._selectedProc;
|
const qname = this._selectedProc;
|
||||||
if (!qname) return "";
|
if (!qname) return "";
|
||||||
@@ -566,43 +518,87 @@ class WCProcCall extends WCBlock {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const errPaths = this._el.errHook && this._el.errHook.classList.contains("visible")
|
const hasErrPath = this._el.errHook && this._el.errHook.classList.contains("visible") &&
|
||||||
? Array.from(this.wcNextFlow).filter(p => p.fromHook === this._el.errHook)
|
Array.from(this.wcNextFlow).some(p => p.fromHook === this._el.errHook);
|
||||||
: [];
|
|
||||||
const hasErrPath = errPaths.length > 0;
|
// For error-capable calls, the call is emitted inside generateFlowCode
|
||||||
|
// (as part of the if-condition) to avoid double execution.
|
||||||
|
if (returnStyle === "error" && hasErrPath) return "";
|
||||||
|
|
||||||
|
if (returnStyle === "value" && outputs.length === 1 && hasErrPath) {
|
||||||
|
const outVar = outputs[0].replace(/^&/, "");
|
||||||
|
return ` ${outVar} = ${procName}(${inputs.join(", ")});\n`;
|
||||||
|
}
|
||||||
|
|
||||||
if (returnStyle === "error") {
|
if (returnStyle === "error") {
|
||||||
const call = `${procName}(${inputs.join(", ")})`;
|
return ` ${procName}(${inputs.join(", ")});\n`;
|
||||||
if (hasErrPath) {
|
|
||||||
const result = this._generateCallWithError(`\nif (${call} != 0)`, errPaths);
|
|
||||||
this._noWalkNext = true;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
return `\n${call};\n`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (returnStyle === "value" && outputs.length === 1) {
|
if (returnStyle === "value" && outputs.length === 1) {
|
||||||
const outVar = outputs[0].replace(/^&/, "");
|
return ` ${procName}(${inputs.join(", ")});\n`;
|
||||||
if (hasErrPath) {
|
|
||||||
const result = this._generateCallWithError(`\nif ((${outVar} = ${procName}(${inputs.join(", ")})) != 0)`, errPaths);
|
|
||||||
this._noWalkNext = true;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
return `\n${procName}(${inputs.join(", ")});\n`;
|
|
||||||
}
|
}
|
||||||
const args = [...inputs, ...outputs].join(", ");
|
const args = [...inputs, ...outputs].join(", ");
|
||||||
return `\n${procName}(${args});\n`;
|
return ` ${procName}(${args});\n`;
|
||||||
}
|
}
|
||||||
|
|
||||||
_generateCallWithError(ifHeader, errPaths) {
|
generateFlowCode() {
|
||||||
const errTargets = errPaths.map(p => p.toElement);
|
const hasErrPath = this._el.errHook && this._el.errHook.classList.contains("visible") &&
|
||||||
const okTargets = Array.from(this.wcNextFlow)
|
Array.from(this.wcNextFlow).some(p => p.fromHook === this._el.errHook);
|
||||||
.filter(p => p.fromHook !== this._el.errHook)
|
if (!hasErrPath) return WCBlock.prototype.generateFlowCode.call(this);
|
||||||
.map(p => p.toElement);
|
|
||||||
|
|
||||||
return CodeGen.generateCallWithError(ifHeader, errTargets, okTargets, function (b) {
|
const qname = this._selectedProc;
|
||||||
return b instanceof WCDecision;
|
if (!qname) return "";
|
||||||
}, this._loopTargets, this._genVisited);
|
const parts = qname.split("/");
|
||||||
|
const procName = parts[1] || qname;
|
||||||
|
|
||||||
|
let returnStyle = this._cachedReturnStyle || "pointer";
|
||||||
|
|
||||||
|
const inputs = [];
|
||||||
|
const outputs = [];
|
||||||
|
for (const row of this._el.inputs.querySelectorAll(".proccall-arg-row")) {
|
||||||
|
const hook = row.querySelector(".hook");
|
||||||
|
const inp = row.querySelector(".proccall-arg-input");
|
||||||
|
const source = hook ? findDataSource(hook) : null;
|
||||||
|
if (source) {
|
||||||
|
inputs.push(this._sourceName(source));
|
||||||
|
} else {
|
||||||
|
inputs.push(inp.value || inp.placeholder);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const row of this._el.outputs.querySelectorAll(".proccall-arg-row")) {
|
||||||
|
const inp = row.querySelector(".proccall-arg-input");
|
||||||
|
outputs.push("&" + (inp.value || inp.placeholder));
|
||||||
|
}
|
||||||
|
if (this._fmtProc) {
|
||||||
|
for (const row of this._el.fmtargsList.querySelectorAll(".proccall-arg-row")) {
|
||||||
|
const hook = row.querySelector(".hook");
|
||||||
|
const inp = row.querySelector(".proccall-arg-input");
|
||||||
|
const source = hook ? findDataSource(hook) : null;
|
||||||
|
if (source) {
|
||||||
|
inputs.push(this._sourceName(source));
|
||||||
|
} else {
|
||||||
|
inputs.push(inp.value || inp.placeholder);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const args = inputs.join(", ");
|
||||||
|
const errTarget = Array.from(this.wcNextFlow)
|
||||||
|
.filter(p => p.fromHook === this._el.errHook)
|
||||||
|
.map(p => p.toElement)[0];
|
||||||
|
const okTarget = Array.from(this.wcNextFlow)
|
||||||
|
.filter(p => p.fromHook !== this._el.errHook)
|
||||||
|
.map(p => p.toElement)[0];
|
||||||
|
|
||||||
|
let code = "";
|
||||||
|
if (returnStyle === "error") {
|
||||||
|
code += ` if (${procName}(${args}) != 0) goto ${errTarget.id};\n`;
|
||||||
|
} else if (returnStyle === "value" && outputs.length === 1) {
|
||||||
|
const outVar = outputs[0].replace(/^&/, "");
|
||||||
|
code += ` if (${outVar} != 0) goto ${errTarget.id};\n`;
|
||||||
|
}
|
||||||
|
if (okTarget) code += ` goto ${okTarget.id};\n`;
|
||||||
|
return code;
|
||||||
}
|
}
|
||||||
|
|
||||||
saveData() {
|
saveData() {
|
||||||
|
|||||||
+46
-150
@@ -1,185 +1,81 @@
|
|||||||
// ── Pure C code generation — no DOM dependencies ─────────────────────────
|
// ── Pure C code generation — zero-DOM, flat DFA model ─────────────────
|
||||||
//
|
//
|
||||||
// These functions operate on generic block objects with the interface:
|
// Every block is a C label. Every flow edge is a `goto label;`.
|
||||||
// { id: string, generateOwnCode(): string, generateCode(visited, loopTargets): string,
|
// There is zero nesting: no if/else chains, no branch-analysis, no
|
||||||
// wcNextFlow?: [{ toElement: block }], nextFlow?: [{ toElement: block }],
|
// shared-block computation. Decisions become `if (cond) goto yes;
|
||||||
// isDecision?: boolean }
|
// goto no;`. Error handling becomes `if (call != 0) goto err;
|
||||||
|
// goto ok;`.
|
||||||
//
|
//
|
||||||
// Browser DOM blocks use wcNextFlow; test/mock blocks use nextFlow.
|
// Block interface:
|
||||||
// Decision blocks must expose isDecision = true.
|
// { id: string,
|
||||||
|
// generateOwnCode(): string, // C code for this one block
|
||||||
|
// generateFlowCode?(): string, // flow-control (gotos). If absent,
|
||||||
|
// // CodeGen walks wcNextFlow instead.
|
||||||
|
// wcNextFlow?: [{toElement: block}],
|
||||||
|
// nextFlow?: [{toElement: block}] }
|
||||||
|
|
||||||
(function () {
|
(function () {
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
var CodeGen = {};
|
var CodeGen = {};
|
||||||
|
|
||||||
// ── Back-edge detection ──────────────────────────────────────────────
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Traverses the flow graph from a root and finds all back-edges
|
* Emits a complete C program as a flat sequence of labelled blocks
|
||||||
* (edges to a block already on the current DFS path). Returns a Set
|
* connected by gotos. Each block is emitted once (DFS order); already-
|
||||||
* of blocks that are targets of at least one back-edge — these are
|
* visited blocks become `goto label;` stubs.
|
||||||
* 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 {Object} entry the first block (e.g. START)
|
||||||
* @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}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
CodeGen.emitBranch = function (roots, stop, visited, isDecision, loopTargets) {
|
CodeGen.emitFlat = function (entry) {
|
||||||
loopTargets = loopTargets || new Set();
|
var visited = new Set();
|
||||||
var parts = [];
|
var parts = [];
|
||||||
|
|
||||||
function walk(el) {
|
function emit(block) {
|
||||||
if (stop && stop.has(el)) return;
|
if (visited.has(block)) {
|
||||||
if (visited.has(el)) {
|
parts.push(" goto " + block.id + ";\n");
|
||||||
if (loopTargets.has(el)) {
|
|
||||||
parts.push("\n goto " + (el.id || "block") + ";\n");
|
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (isDecision(el)) {
|
visited.add(block);
|
||||||
parts.push(el.generateCode(visited, loopTargets));
|
parts.push("\n" + block.id + ":");
|
||||||
|
|
||||||
|
var own = block.generateOwnCode();
|
||||||
|
if (own) parts.push(own);
|
||||||
|
|
||||||
|
// Flow control: custom generateFlowCode, or auto from wcNextFlow
|
||||||
|
var flow = block.wcNextFlow || block.nextFlow || [];
|
||||||
|
var fc = "";
|
||||||
|
if (typeof block.generateFlowCode === "function") {
|
||||||
|
fc = block.generateFlowCode();
|
||||||
} else {
|
} else {
|
||||||
visited.add(el);
|
for (var i = 0; i < flow.length; i++) {
|
||||||
if (loopTargets.has(el)) {
|
var t = flow[i].toElement;
|
||||||
parts.push("\n" + (el.id || "block") + ":");
|
if (t) fc += " goto " + t.id + ";\n";
|
||||||
}
|
}
|
||||||
el._genVisited = visited;
|
}
|
||||||
parts.push(el.generateOwnCode());
|
if (fc) parts.push("\n" + fc);
|
||||||
// If the block's generateOwnCode already walked the success path
|
|
||||||
// (e.g. error-handling proc call), don't re-walk its nextFlow.
|
// Recurse into successors (visited blocks are skipped at top)
|
||||||
if (!el._noWalkNext) {
|
for (var i = 0; i < flow.length; i++) {
|
||||||
var flow = el.wcNextFlow || el.nextFlow || [];
|
var t = flow[i].toElement;
|
||||||
for (var j = 0; j < flow.length; j++) {
|
if (t) emit(t);
|
||||||
walk(flow[j].toElement);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
el._noWalkNext = false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (var i = 0; i < roots.length; i++) {
|
emit(entry);
|
||||||
if (!visited.has(roots[i])) walk(roots[i]);
|
|
||||||
}
|
|
||||||
return parts.join("");
|
return parts.join("");
|
||||||
};
|
};
|
||||||
|
|
||||||
// ── Error-path code generation ───────────────────────────────────────
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generates the error-path branch for a proc call: computes shared blocks
|
* Assembles a complete C source file.
|
||||||
* 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, mainVisited
|
|
||||||
) {
|
|
||||||
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);
|
|
||||||
});
|
|
||||||
|
|
||||||
// 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(
|
|
||||||
Array.from(shared), new Set(), visited, isDecision, loopTargets
|
|
||||||
);
|
|
||||||
|
|
||||||
var out = ifHeader + " {\n" + errCode + "}";
|
|
||||||
if (okCode) out += " else {\n" + okCode + "}";
|
|
||||||
out += "\n";
|
|
||||||
if (sharedCode) out += sharedCode;
|
|
||||||
return out;
|
|
||||||
};
|
|
||||||
|
|
||||||
// ── Full program assembly ────────────────────────────────────────────
|
|
||||||
|
|
||||||
CodeGen.generateCFile = function (entryBlock, includes, declarations) {
|
CodeGen.generateCFile = function (entryBlock, includes, declarations) {
|
||||||
var lines = ["// --- WiseCode\u00ae ---"];
|
var lines = ["// --- WiseCode\u00ae ---"];
|
||||||
for (var i = 0; i < includes.length; i++) lines.push(includes[i]);
|
for (var i = 0; i < includes.length; i++) lines.push(includes[i]);
|
||||||
lines.push("");
|
lines.push("");
|
||||||
lines.push("int main() {");
|
lines.push("int main() {");
|
||||||
if (declarations) lines.push(declarations);
|
if (declarations) lines.push(declarations);
|
||||||
|
lines.push(CodeGen.emitFlat(entryBlock));
|
||||||
var loopTargets = CodeGen.findBackEdges(entryBlock);
|
|
||||||
var body = entryBlock.generateCode(new Set(), loopTargets);
|
|
||||||
lines.push(body);
|
|
||||||
lines.push("}");
|
lines.push("}");
|
||||||
lines.push("// --- END ---");
|
lines.push("// --- END ---");
|
||||||
return lines.join("\n");
|
return lines.join("\n");
|
||||||
|
|||||||
@@ -98,28 +98,7 @@ const APP_TEST_SUITE = {
|
|||||||
if (!code.includes("return 0")) throw Error("missing return 0");
|
if (!code.includes("return 0")) throw Error("missing return 0");
|
||||||
});
|
});
|
||||||
|
|
||||||
this._test("branchDescendants traverses past nested decision", () => {
|
|
||||||
const dA = prog.newDecisionNode();
|
|
||||||
const dB = prog.newDecisionNode();
|
|
||||||
const aX = prog.newNode("wc-assign");
|
|
||||||
dA.style.left = "200px"; dA.style.top = "50px";
|
|
||||||
dB.style.left = "400px"; dB.style.top = "50px";
|
|
||||||
aX.style.left = "600px"; aX.style.top = "50px";
|
|
||||||
|
|
||||||
const start = $id("main-start");
|
|
||||||
const end = $id("main-end");
|
|
||||||
const savedPaths = [...start.wcNextFlow];
|
|
||||||
for (const p of savedPaths) deleteLink(p);
|
|
||||||
|
|
||||||
_linkFlow(start, ".hook", dA, "#hook-fore");
|
|
||||||
_linkFlow(dA, "#hook-out1", dB, "#hook-fore");
|
|
||||||
_linkFlow(dB, "#hook-out1", aX, "#hook-fore");
|
|
||||||
|
|
||||||
const desc = dA._branchDescendants([dB]);
|
|
||||||
dA.delete(); dB.delete(); aX.delete();
|
|
||||||
|
|
||||||
if (!desc.has(aX)) throw Error("block after nested decision not reached");
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
this._group("Variable operations", () => {
|
this._group("Variable operations", () => {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
CC ?= gcc
|
CC ?= gcc
|
||||||
CFLAGS ?= -Wall -Wextra -Werror=implicit-function-declaration -Werror=incompatible-pointer-types -fsyntax-only
|
CFLAGS ?= -Wall -Wextra -fsyntax-only
|
||||||
OUTDIR ?= /tmp/wisecode-compile
|
OUTDIR ?= out
|
||||||
|
|
||||||
SRC := $(wildcard $(OUTDIR)/*.c)
|
SRC := $(wildcard $(OUTDIR)/*.c)
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
*.c
|
||||||
+39
-172
@@ -1,13 +1,9 @@
|
|||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
/**
|
/**
|
||||||
* C compilation test runner.
|
* C compilation test runner — flat DFA code generation.
|
||||||
*
|
*
|
||||||
* Generates C source files matching each template's expected output,
|
* Block data objects are minimal: generateOwnCode() + optional generateFlowCode().
|
||||||
* then runs `make` to compile them with `gcc -Wall -Wextra -fsyntax-only`.
|
* The shared CodeGen.emitFlat walks the graph, emitting labels + gotos.
|
||||||
*
|
|
||||||
* Block data objects are created manually (matching the browser template
|
|
||||||
* structure) and passed to the shared CodeGen module for graph walking
|
|
||||||
* and code emission — no duplication of branch/emit logic.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const fs = require("fs");
|
const fs = require("fs");
|
||||||
@@ -15,156 +11,73 @@ const path = require("path");
|
|||||||
const { execSync } = require("child_process");
|
const { execSync } = require("child_process");
|
||||||
const CodeGen = require("../../codegen/index.js");
|
const CodeGen = require("../../codegen/index.js");
|
||||||
|
|
||||||
const OUTDIR = "/tmp/wisecode-compile";
|
const OUTDIR = path.join(__dirname, "out");
|
||||||
const MKFILE = path.join(__dirname, "Makefile");
|
const MKFILE = path.join(__dirname, "Makefile");
|
||||||
|
|
||||||
// ── Block data helpers ────────────────────────────────────────────────
|
// ── Block data helpers ────────────────────────────────────────────────
|
||||||
// Plain objects matching the interface CodeGen expects:
|
|
||||||
// { generateOwnCode(), generateCode(visited), nextFlow, isDecision }
|
|
||||||
|
|
||||||
function startBlock(id) {
|
function startBlock(id) {
|
||||||
return {
|
return {
|
||||||
id: id,
|
id: id,
|
||||||
isDecision: false,
|
isDecision: false,
|
||||||
nextFlow: [],
|
nextFlow: [],
|
||||||
generateOwnCode: function () {
|
generateOwnCode: function () { return ""; },
|
||||||
return "";
|
|
||||||
},
|
|
||||||
generateCode: function (visited, loopTargets) {
|
|
||||||
if (visited.has(this)) {
|
|
||||||
if (loopTargets && loopTargets.has(this)) return "\n goto " + this.id + ";\n";
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
visited.add(this);
|
|
||||||
var c = "";
|
|
||||||
if (loopTargets && loopTargets.has(this)) c += "\n" + this.id + ":\n";
|
|
||||||
c += this.generateOwnCode();
|
|
||||||
for (var i = 0; i < this.nextFlow.length; i++) {
|
|
||||||
var t = this.nextFlow[i].toElement;
|
|
||||||
if (visited.has(t)) {
|
|
||||||
if (loopTargets && loopTargets.has(t)) c += "\n goto " + t.id + ";\n";
|
|
||||||
} else {
|
|
||||||
c += t.generateCode(visited, loopTargets);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return c;
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function endBlock(id, exitCode) {
|
function endBlock(id, exitCode) {
|
||||||
var code = exitCode === 0 ? "\n return 0;\n" : "\n exit(" + exitCode + ");\n";
|
var code = exitCode === 0 ? " return 0;\n" : " exit(" + exitCode + ");\n";
|
||||||
return {
|
return {
|
||||||
id: id,
|
id: id,
|
||||||
isDecision: false,
|
isDecision: false,
|
||||||
nextFlow: [],
|
nextFlow: [],
|
||||||
generateOwnCode: function () {
|
generateOwnCode: function () { return code; },
|
||||||
return code;
|
|
||||||
},
|
|
||||||
generateCode: function (visited, loopTargets) {
|
|
||||||
if (visited.has(this)) {
|
|
||||||
if (loopTargets && loopTargets.has(this)) return "\n goto " + this.id + ";\n";
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
visited.add(this);
|
|
||||||
var c = "";
|
|
||||||
if (loopTargets && loopTargets.has(this)) c += "\n" + this.id + ":\n";
|
|
||||||
c += this.generateOwnCode();
|
|
||||||
return c;
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function wrap(targets) {
|
|
||||||
return (targets || []).map(function (t) {
|
|
||||||
return { toElement: t };
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function procCallBlock(id, procName, inputs, returnStyle, returnValueName, okTargets, errTargets) {
|
function procCallBlock(id, procName, inputs, returnStyle, returnValueName, okTargets, errTargets) {
|
||||||
var call = procName + "(" + inputs.join(", ") + ")";
|
var call = procName + "(" + inputs.join(", ") + ")";
|
||||||
var self = {
|
var self = {
|
||||||
id: id,
|
id: id,
|
||||||
isDecision: false,
|
isDecision: false,
|
||||||
nextFlow: wrap(okTargets),
|
nextFlow: (okTargets || []).concat(errTargets || []).map(function (t) { return { toElement: t }; }),
|
||||||
errTargets: errTargets || [],
|
errTargets: errTargets || [],
|
||||||
generateOwnCode: function () {
|
generateOwnCode: function () {
|
||||||
if (self.errTargets.length === 0) return "\n " + call + ";\n";
|
if (returnStyle === "error" && self.errTargets.length > 0) return "";
|
||||||
var ifHeader;
|
if (returnStyle === "value" && returnValueName && self.errTargets.length > 0) {
|
||||||
if (returnStyle === "value" && returnValueName) {
|
return " " + returnValueName + " = " + call + ";\n";
|
||||||
ifHeader = " if ((" + returnValueName + " = " + call + ") != 0)";
|
|
||||||
} else {
|
|
||||||
ifHeader = " if (" + call + " != 0)";
|
|
||||||
}
|
}
|
||||||
var okTargets = self.nextFlow.map(function (p) {
|
return " " + call + ";\n";
|
||||||
return p.toElement;
|
|
||||||
});
|
|
||||||
var result = CodeGen.generateCallWithError(
|
|
||||||
"\n" + ifHeader,
|
|
||||||
self.errTargets,
|
|
||||||
okTargets,
|
|
||||||
function (b) {
|
|
||||||
return b.isDecision;
|
|
||||||
},
|
|
||||||
self._loopTargets,
|
|
||||||
self._genVisited
|
|
||||||
);
|
|
||||||
self._noWalkNext = true;
|
|
||||||
return result;
|
|
||||||
},
|
},
|
||||||
generateCode: function (visited, loopTargets) {
|
generateFlowCode: function () {
|
||||||
self._loopTargets = loopTargets;
|
if (self.errTargets.length === 0) {
|
||||||
self._genVisited = visited;
|
return self.nextFlow.map(function (p) { return " goto " + p.toElement.id + ";\n"; }).join("");
|
||||||
if (visited.has(this)) {
|
|
||||||
if (loopTargets && loopTargets.has(this)) return "\n goto " + this.id + ";\n";
|
|
||||||
return "";
|
|
||||||
}
|
}
|
||||||
visited.add(this);
|
var errT = self.errTargets[0];
|
||||||
var own = this.generateOwnCode();
|
var okT = null;
|
||||||
if (loopTargets && loopTargets.has(this)) own = "\n" + this.id + ":\n" + own;
|
for (var i = 0; i < self.nextFlow.length; i++) {
|
||||||
if (self.errTargets.length > 0) return own;
|
var t = self.nextFlow[i].toElement;
|
||||||
for (var i = 0; i < this.nextFlow.length; i++) {
|
if (self.errTargets.indexOf(t) === -1) { okT = t; break; }
|
||||||
var t = this.nextFlow[i].toElement;
|
|
||||||
if (visited.has(t)) {
|
|
||||||
if (loopTargets && loopTargets.has(t)) own += "\n goto " + t.id + ";\n";
|
|
||||||
} else {
|
|
||||||
own += t.generateCode(visited, loopTargets);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return own;
|
var out = "";
|
||||||
|
if (returnStyle === "error") {
|
||||||
|
out += " if (" + call + " != 0) goto " + errT.id + ";\n";
|
||||||
|
} else if (returnStyle === "value" && returnValueName) {
|
||||||
|
out += " if (" + returnValueName + " != 0) goto " + errT.id + ";\n";
|
||||||
|
}
|
||||||
|
if (okT) out += " goto " + okT.id + ";\n";
|
||||||
|
return out;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
function assignBlock(id, target, value) {
|
function assignBlock(id, target, value) {
|
||||||
var code = "\n " + target + " = " + value + ";\n";
|
|
||||||
return {
|
return {
|
||||||
id: id,
|
id: id,
|
||||||
isDecision: false,
|
isDecision: false,
|
||||||
nextFlow: [],
|
nextFlow: [],
|
||||||
generateOwnCode: function () {
|
generateOwnCode: function () { return " " + target + " = " + value + ";\n"; },
|
||||||
return code;
|
|
||||||
},
|
|
||||||
generateCode: function (visited, loopTargets) {
|
|
||||||
if (visited.has(this)) {
|
|
||||||
if (loopTargets && loopTargets.has(this)) return "\n goto " + this.id + ";\n";
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
visited.add(this);
|
|
||||||
var c = "";
|
|
||||||
if (loopTargets && loopTargets.has(this)) c += "\n" + this.id + ":\n";
|
|
||||||
c += this.generateOwnCode();
|
|
||||||
for (var i = 0; i < this.nextFlow.length; i++) {
|
|
||||||
var t = this.nextFlow[i].toElement;
|
|
||||||
if (visited.has(t)) {
|
|
||||||
if (loopTargets && loopTargets.has(t)) c += "\n goto " + t.id + ";\n";
|
|
||||||
} else {
|
|
||||||
c += t.generateCode(visited, loopTargets);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return c;
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -178,21 +91,9 @@ function buildConsoleTemplate() {
|
|||||||
var start = startBlock("start");
|
var start = startBlock("start");
|
||||||
var end = endBlock("end", 0);
|
var end = endBlock("end", 0);
|
||||||
var errEnd = endBlock("errEnd", 1);
|
var errEnd = endBlock("errEnd", 1);
|
||||||
var printf = procCallBlock(
|
var printf = procCallBlock("printf", "printf", ['"Hello, World!\\n"'], "value", "result", [end], [errEnd]);
|
||||||
"printf",
|
|
||||||
"printf",
|
|
||||||
['"Hello, World!\\n"'],
|
|
||||||
"value",
|
|
||||||
"result",
|
|
||||||
[end],
|
|
||||||
[errEnd]
|
|
||||||
);
|
|
||||||
link(start, printf);
|
link(start, printf);
|
||||||
return {
|
return { entry: start, includes: ["#include <stdio.h>", "#include <stdlib.h>"], declarations: " int result;" };
|
||||||
entry: start,
|
|
||||||
includes: ["#include <stdio.h>", "#include <stdlib.h>"],
|
|
||||||
declarations: " int result;",
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildErrorSimpleTemplate() {
|
function buildErrorSimpleTemplate() {
|
||||||
@@ -201,12 +102,7 @@ function buildErrorSimpleTemplate() {
|
|||||||
var errEnd = endBlock("errEnd", 1);
|
var errEnd = endBlock("errEnd", 1);
|
||||||
var call = procCallBlock("init", "func", [], "error", null, [end], [errEnd]);
|
var call = procCallBlock("init", "func", [], "error", null, [end], [errEnd]);
|
||||||
link(start, call);
|
link(start, call);
|
||||||
return {
|
return { entry: start, includes: ["#include <stdlib.h>"], declarations: "", proto: ["int func(void);"] };
|
||||||
entry: start,
|
|
||||||
includes: ["#include <stdlib.h>"],
|
|
||||||
declarations: "",
|
|
||||||
proto: ["int func(void);"],
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildErrorMergeTemplate() {
|
function buildErrorMergeTemplate() {
|
||||||
@@ -218,12 +114,7 @@ function buildErrorMergeTemplate() {
|
|||||||
link(logged, done);
|
link(logged, done);
|
||||||
link(done, end);
|
link(done, end);
|
||||||
link(start, call);
|
link(start, call);
|
||||||
return {
|
return { entry: start, includes: ["#include <stdlib.h>"], declarations: " int logged;\n int done;", proto: ["int func(void);"] };
|
||||||
entry: start,
|
|
||||||
includes: ["#include <stdlib.h>"],
|
|
||||||
declarations: " int logged;\n int done;",
|
|
||||||
proto: ["int func(void);"],
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function errorSequentialTemplate() {
|
function errorSequentialTemplate() {
|
||||||
@@ -235,34 +126,16 @@ function errorSequentialTemplate() {
|
|||||||
var call2 = procCallBlock("b", "init_b", [], "error", null, [end], [errEnd2]);
|
var call2 = procCallBlock("b", "init_b", [], "error", null, [end], [errEnd2]);
|
||||||
link(start, call1);
|
link(start, call1);
|
||||||
link(call1, call2);
|
link(call1, call2);
|
||||||
return {
|
return { entry: start, includes: ["#include <stdlib.h>"], declarations: "", proto: ["int init_a(void);", "int init_b(void);"] };
|
||||||
entry: start,
|
|
||||||
includes: ["#include <stdlib.h>"],
|
|
||||||
declarations: "",
|
|
||||||
proto: ["int init_a(void);", "int init_b(void);"],
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildErrorValueTemplate() {
|
function buildErrorValueTemplate() {
|
||||||
var start = startBlock("start");
|
var start = startBlock("start");
|
||||||
var end = endBlock("end", 0);
|
var end = endBlock("end", 0);
|
||||||
var errEnd = endBlock("errEnd", 1);
|
var errEnd = endBlock("errEnd", 1);
|
||||||
var call = procCallBlock(
|
var call = procCallBlock("open", "open_file", ['"data.txt"', "0"], "value", "fd", [end], [errEnd]);
|
||||||
"open",
|
|
||||||
"open_file",
|
|
||||||
['"data.txt"', "0"],
|
|
||||||
"value",
|
|
||||||
"fd",
|
|
||||||
[end],
|
|
||||||
[errEnd]
|
|
||||||
);
|
|
||||||
link(start, call);
|
link(start, call);
|
||||||
return {
|
return { entry: start, includes: ["#include <stdlib.h>"], declarations: " int fd;", proto: ["int open_file(const char *path, int flags);"] };
|
||||||
entry: start,
|
|
||||||
includes: ["#include <stdlib.h>"],
|
|
||||||
declarations: " int fd;",
|
|
||||||
proto: ["int open_file(const char *path, int flags);"],
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Source generation ──────────────────────────────────────────────────
|
// ── Source generation ──────────────────────────────────────────────────
|
||||||
@@ -276,8 +149,7 @@ function generateSource(opts) {
|
|||||||
parts.push("");
|
parts.push("");
|
||||||
parts.push("int main() {");
|
parts.push("int main() {");
|
||||||
if (opts.declarations) parts.push(opts.declarations);
|
if (opts.declarations) parts.push(opts.declarations);
|
||||||
var loopTargets = CodeGen.findBackEdges(opts.entry);
|
parts.push(CodeGen.emitFlat(opts.entry));
|
||||||
parts.push(opts.entry.generateCode(new Set(), loopTargets));
|
|
||||||
parts.push("}");
|
parts.push("}");
|
||||||
parts.push("// --- END ---");
|
parts.push("// --- END ---");
|
||||||
return parts.join("\n");
|
return parts.join("\n");
|
||||||
@@ -294,9 +166,7 @@ function writeSource(label, source) {
|
|||||||
|
|
||||||
console.log("\nGenerating C compilation tests...\n");
|
console.log("\nGenerating C compilation tests...\n");
|
||||||
|
|
||||||
try {
|
try { execSync("rm -f " + OUTDIR + "/*.c", { stdio: "pipe" }); } catch (_) {}
|
||||||
execSync("rm -f " + OUTDIR + "/*.c", { stdio: "pipe" });
|
|
||||||
} catch (_) {}
|
|
||||||
|
|
||||||
writeSource("test_console", generateSource(buildConsoleTemplate()));
|
writeSource("test_console", generateSource(buildConsoleTemplate()));
|
||||||
writeSource("test_error_simple", generateSource(buildErrorSimpleTemplate()));
|
writeSource("test_error_simple", generateSource(buildErrorSimpleTemplate()));
|
||||||
@@ -307,15 +177,12 @@ writeSource("test_error_value", generateSource(buildErrorValueTemplate()));
|
|||||||
console.log("\nRunning make...\n");
|
console.log("\nRunning make...\n");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
execSync('make -C "' + OUTDIR + '" -f "' + MKFILE + '" OUTDIR="' + OUTDIR + '" all 2>&1', {
|
execSync('make -C "' + OUTDIR + '" -f "' + MKFILE + '" OUTDIR="' + OUTDIR + '" all 2>&1', { stdio: "inherit" });
|
||||||
stdio: "inherit",
|
|
||||||
});
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("\n\u2717 Compilation failed.\n");
|
console.error("\n\u2717 Compilation failed.\n");
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try SDL3
|
|
||||||
try {
|
try {
|
||||||
execSync("pkg-config --exists sdl3", { stdio: "pipe" });
|
execSync("pkg-config --exists sdl3", { stdio: "pipe" });
|
||||||
console.log("\nSDL3 headers found, generating SDL3 test...\n");
|
console.log("\nSDL3 headers found, generating SDL3 test...\n");
|
||||||
|
|||||||
+12
-37
@@ -247,51 +247,26 @@ class WCBlock extends HTMLElement {
|
|||||||
generateOwnCode() { return ""; }
|
generateOwnCode() { return ""; }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generates C code for this block and its successors, respecting the visited set
|
* C flow-control: `goto` statements for every successor in wcNextFlow.
|
||||||
* to avoid infinite loops. Only blocks in loopTargets get C labels; only
|
* Override for decisions (if-goto) and error-handling proc calls.
|
||||||
* back-edges to loop targets emit `goto`.
|
|
||||||
* @param {Set} [visited]
|
|
||||||
* @param {Set} [loopTargets] blocks that are targets of back-edges
|
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
generateCode(visited = new Set(), loopTargets) {
|
generateFlowCode() {
|
||||||
if (!this._isFlowBlock) return this.generateOwnCode();
|
let code = "";
|
||||||
if (visited.has(this)) {
|
for (const path of this.wcNextFlow) {
|
||||||
if (loopTargets && loopTargets.has(this)) {
|
code += " goto " + path.toElement.id + ";\n";
|
||||||
return "\n goto " + this.id + ";\n";
|
|
||||||
}
|
|
||||||
return "";
|
|
||||||
}
|
}
|
||||||
visited.add(this);
|
return code;
|
||||||
let out = "";
|
|
||||||
if (loopTargets && loopTargets.has(this)) {
|
|
||||||
out += "\n" + this.id + ":\n";
|
|
||||||
}
|
|
||||||
out += this.generateOwnCode() + this.generateNextCode(visited, loopTargets);
|
|
||||||
return out;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Walks wcNextFlow paths and accumulates code from downstream blocks.
|
* Generates C code for variable declarations (non-flow blocks only).
|
||||||
* When a target is already visited (loop back-edge) AND is a loop target,
|
* For flow blocks, use CodeGen.emitFlat instead.
|
||||||
* emits `goto label;`. Otherwise silently skips the edge.
|
|
||||||
* @param {Set} visited
|
|
||||||
* @param {Set} [loopTargets]
|
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
generateNextCode(visited, loopTargets) {
|
generateCode(visited) {
|
||||||
let code = "";
|
if (!this._isFlowBlock) return this.generateOwnCode();
|
||||||
for (const path of this.wcNextFlow) {
|
return ""; // flow blocks are handled by CodeGen.emitFlat
|
||||||
const target = path.toElement;
|
|
||||||
if (visited.has(target)) {
|
|
||||||
if (loopTargets && loopTargets.has(target)) {
|
|
||||||
code += "\n goto " + target.id + ";\n";
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
code += target.generateCode(visited, loopTargets);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return code;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @returns {number} The CSS `--grid-size` value, cached after first read (default 25). */
|
/** @returns {number} The CSS `--grid-size` value, cached after first read (default 25). */
|
||||||
|
|||||||
+2
-4
@@ -226,10 +226,8 @@ class WCProgram extends HTMLElement {
|
|||||||
: "";
|
: "";
|
||||||
|
|
||||||
const firstChild = this.firstElementChild;
|
const firstChild = this.firstElementChild;
|
||||||
const loopTargets = firstChild ? CodeGen.findBackEdges(firstChild) : new Set();
|
const body = firstChild && typeof CodeGen.emitFlat === "function"
|
||||||
for (const b of this.querySelectorAll("[data-block]")) b._loopTargets = loopTargets;
|
? CodeGen.emitFlat(firstChild)
|
||||||
const body = firstChild && typeof firstChild.generateCode === "function"
|
|
||||||
? firstChild.generateCode(new Set(), loopTargets)
|
|
||||||
: "\n// (no flowchart body)\n";
|
: "\n// (no flowchart body)\n";
|
||||||
const proccalls = this.querySelectorAll("wc-proccall");
|
const proccalls = this.querySelectorAll("wc-proccall");
|
||||||
const includes = this._generateIncludes(proccalls, hasStrings);
|
const includes = this._generateIncludes(proccalls, hasStrings);
|
||||||
|
|||||||
Reference in New Issue
Block a user