Files
avcf/wcprogram.js
T
adrien 35959784de 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.
2026-06-23 03:21:22 +02:00

314 lines
13 KiB
JavaScript

// ── WCProgram — root container + block registration ──────────────────────────
//
// WCProgram is the root custom element that owns the flowchart workspace.
// Unlike other blocks, WCProgram extends HTMLElement directly — not WCBlock —
// because the program container itself is not draggable or linkable.
//
// This file also contains the block registry and registration calls.
// Must be loaded AFTER all block class definitions exist in the page.
//
// Load order dependencies:
// All block class files must be loaded first:
// wcblock.js → blocks-algo.js → blocks-data.js → blocks-proc.js
// helpers.js → toCName
// dom.js → $id
//
// This file is loaded last among the block group, before persist.js and app.js.
// ═══════════════════════════════════════════════════════════════════════════════
// WCProgram — root container
// ═══════════════════════════════════════════════════════════════════════════════
/** Root container custom element; owns the flowchart workspace, block creation, and code generation. */
class WCProgram extends HTMLElement {
constructor() {
super();
this.counter = 0;
}
build() {
const startTmpl = $id("wc-start");
const endTmpl = $id("wc-end");
if (!startTmpl || !endTmpl) {
showError("Missing wc-start or wc-end template elements");
return;
}
const startFrag = document.importNode(startTmpl.content, true);
const endFrag = document.importNode(endTmpl.content, true);
const startEl = startFrag.firstElementChild;
const endEl = endFrag.firstElementChild;
if (!startEl || !endEl) {
showError("wc-start or wc-end template has no content");
return;
}
startEl.id = "main-start";
endEl.id = "main-end";
this.appendChild(startFrag);
this.appendChild(endFrag);
const pw = this.parentElement ? this.parentElement.offsetWidth : 600;
const startW = startEl.offsetWidth || 80;
startEl.style.left = Math.max(20, (pw - startW) / 2) + "px";
startEl.style.top = "20px";
const endW = endEl.offsetWidth || 200;
endEl.style.left = Math.max(20, (pw - endW) / 2) + "px";
endEl.style.top = (startEl.offsetTop + startEl.offsetHeight + 60) + "px";
}
makeNode(tag) {
const info = BLOCK_REGISTRY[tag];
if (!info) return null;
const tmpl = document.querySelector(info.template);
if (!tmpl) { showError("Missing template: " + info.template); return null; }
const content = tmpl.content;
if (!content || !content.firstElementChild) { showError("Empty template: " + info.template); return null; }
this.counter++;
const frag = document.importNode(content, true);
const el = frag.firstElementChild;
el.id = info.prefix + this.counter;
return el;
}
newNode(tag, parent) {
const info = BLOCK_REGISTRY[tag];
if (!info) return null;
const el = this.makeNode(tag);
if (!el) return null;
(parent || (info.getParent ? info.getParent() : this)).appendChild(el);
return el;
}
newDecisionNode() { return this.newNode("wc-decision"); }
newExpressionNode() { return this.newNode("wc-expression"); }
newVarRefNode() { return this.newNode("wc-varref"); }
newProcCallNode() { return this.newNode("wc-proccall"); }
newUnitNode() {
const el = this.newNode("wc-unit", $id("data-area"));
if (el) refreshAll();
return el;
}
newQuantityNode() {
const el = this.newNode("wc-quantity", $id("data-area"));
if (el) { refreshQuantityOptions(); refreshDefaultUnitOptions(); }
return el;
}
newBooleanNode() {
const el = this.newNode("wc-boolean", $id("variable-area"));
if (el) refreshVarRefOptions();
return el;
}
newScalarNode() {
const el = this.newNode("wc-scalar", $id("variable-area"));
if (el) { refreshUnitOptions(); refreshVarRefOptions(); }
return el;
}
newStringNode() {
const el = this.newNode("wc-string", $id("variable-area"));
if (el) refreshVarRefOptions();
return el;
}
newArrayNode() {
const el = this.newNode("wc-array", $id("variable-area"));
if (el) refreshVarRefOptions();
return el;
}
newVectorNode() {
const el = this.newNode("wc-vector", $id("variable-area"));
if (el) { refreshUnitOptions(); refreshVarRefOptions(); }
return el;
}
newProcedureNode(libName) {
const libPane = libName
? document.querySelector(`.lib-pane[data-lib-name="${libName}"]`)
: document.querySelector(".lib-pane");
if (!libPane) return null;
const el = this.newNode("wc-procedure", libPane.querySelector(".procedures-area"));
return el;
}
_gatherUsedTypes() {
const usedQuantities = new Set(), usedUnits = new Set();
const varArea = $id("variable-area");
if (!varArea) return { usedQuantities, usedUnits };
for (const d of varArea.querySelectorAll("wc-scalar, wc-vector")) {
const t = d.querySelector(".var-type");
if (t) { const v = t.value; if (v) usedQuantities.add(v); }
const u = d.querySelector(".var-unit");
if (u) { const v = u.value; if (v) usedUnits.add(v); }
}
return { usedQuantities, usedUnits };
}
_generateTypeDefs(usedQuantities) {
const dataArea = $id("data-area");
if (!dataArea) return "";
return Array.from(
dataArea.querySelectorAll("wc-quantity")
).filter(q => {
const el = q.querySelector(".quantity-name");
return el && usedQuantities.has(el.value || "unnamed");
})
.map(q => {
const el = q.querySelector(".quantity-name");
const name = el ? (el.value || "unnamed") : "unnamed";
return `typedef double ${name};`;
}).join("\n");
}
_generateUnitDefs(usedUnits) {
const dataArea = $id("data-area");
if (!dataArea) return "";
return Array.from(
dataArea.querySelectorAll("wc-unit")
).filter(u => {
const el = u.querySelector(".unit-suffix");
return el && usedUnits.has(el.value);
})
.map(u => {
const typeEl = u.querySelector(".unit-type");
const suffixEl = u.querySelector(".unit-suffix");
const fullnameEl = u.querySelector(".unit-fullname");
const siEl = u.querySelector(".unit-si");
const type = typeEl ? typeEl.value || "unknown" : "unknown";
const suffix = suffixEl ? suffixEl.value || "" : "";
const fullname = fullnameEl ? fullnameEl.value || "" : "";
const si = siEl ? siEl.value || "1" : "1";
return `// unit: ${fullname} (${suffix}) = 1 ${type} * ${si}`;
}).join("\n");
}
_generateVariableDeclarations() {
const varArea = $id("variable-area");
if (!varArea) return { boolDefs: "", scalarDefs: "", vectorDefs: "", stringDefs: "", arrayDefs: "" };
const boolDefs = Array.from(varArea.querySelectorAll("wc-boolean")).map(d => d.generateCode()).join("\n");
const scalarDefs = Array.from(varArea.querySelectorAll("wc-scalar")).map(d => d.generateCode()).join("\n");
const vectorDefs = Array.from(varArea.querySelectorAll("wc-vector")).map(d => d.generateCode()).join("\n");
const stringDefs = Array.from(varArea.querySelectorAll("wc-string")).map(d => d.generateCode()).join("\n");
const arrayDefs = Array.from(varArea.querySelectorAll("wc-array")).map(d => d.generateCode()).join("\n");
return { boolDefs, scalarDefs, vectorDefs, stringDefs, arrayDefs };
}
_generateIncludes(proccalls, hasStrings) {
const libNames = new Set();
for (const p of proccalls) {
const qname = p._selectedProc;
if (qname) {
const lib = qname.split("/")[0];
if (lib) libNames.add(lib);
}
}
let includes = Array.from(libNames).map(l => {
const path = INCLUDE_MAP[l] || (l + ".h");
return `#include <${path}>`;
}).join("\n");
if (hasStrings) {
const hdr = "#include <stdlib.h>\n#include <string.h>";
includes = includes ? includes + "\n" + hdr : hdr;
}
return includes;
}
generateCode() {
const outputEl = $id("c-code");
if (!outputEl) { showError("Missing #c-code output element"); return; }
const { usedQuantities, usedUnits } = this._gatherUsedTypes();
const typeDefs = this._generateTypeDefs(usedQuantities);
const unitDefs = this._generateUnitDefs(usedUnits);
const { boolDefs, scalarDefs, vectorDefs, stringDefs, arrayDefs } = this._generateVariableDeclarations();
const hasStrings = $id("variable-area").querySelectorAll("wc-string").length > 0;
const stringStruct = hasStrings
? "typedef struct { char *data; long int length; } string;\n\n"
: "";
const firstChild = this.firstElementChild;
const body = firstChild && typeof CodeGen.emitFlat === "function"
? CodeGen.emitFlat(firstChild)
: "\n// (no flowchart body)\n";
const proccalls = this.querySelectorAll("wc-proccall");
const includes = this._generateIncludes(proccalls, hasStrings);
const dataSection = (boolDefs || scalarDefs || vectorDefs || stringDefs || arrayDefs)
? [boolDefs, scalarDefs, vectorDefs, stringDefs, arrayDefs]
.filter(Boolean).map(s => s + "\n").join("")
: "";
const hasReturnOrExit =
body.includes("\n return ") || body.includes("\n exit(");
const raw =
"// --- WiseCode\u00ae ---\n" +
(includes ? includes + "\n\n" : "") +
"int main() {\n" +
(typeDefs ? typeDefs + "\n\n" : "") +
(unitDefs ? unitDefs + "\n\n" : "") +
stringStruct + dataSection + body +
(hasReturnOrExit ? "" : "return 0;\n") +
"}\n// --- END ---";
outputEl.textContent = raw;
if (typeof hljs !== "undefined") hljs.highlightElement(outputEl);
outputEl.parentElement.dataset.language = "c";
}
}
/**
* Maps library names to their C #include paths.
* Libraries not listed here use the default `<{name}.h>` convention.
*/
const INCLUDE_MAP = {
"sdl3": "SDL3/SDL.h",
"glfw": "GLFW/glfw3.h",
"gl": "GL/gl.h",
};
// ═══════════════════════════════════════════════════════════════════════════════
// Block registry
// ═══════════════════════════════════════════════════════════════════════════════
/** @type {Object<string,{cls:typeof WCBlock, template:string, prefix:string, getParent:(()=>HTMLElement)|null}>} */
const BLOCK_REGISTRY = {};
/** @type {Object<string,string>} Maps toolbar action names to block tag names. */
const BLOCK_ACTIONS = {};
/**
* Registers a custom element class and adds it to the block registry and action map.
* @param {string} tag Custom element tag (e.g. "wc-decision")
* @param {typeof WCBlock} cls Class constructor
* @param {string} prefix ID prefix for auto-generated block IDs
* @param {string|null} creatable Action name for toolbar buttons, or null if not toolbar-creatable
* @param {(()=>HTMLElement)|null} getParent Function returning the parent container, or null for algorithm pane
*/
function registerBlock(tag, cls, prefix, creatable, getParent) {
customElements.define(tag, cls);
BLOCK_REGISTRY[tag] = { cls, template: "#" + tag, prefix: prefix || tag.replace("wc-", ""), getParent };
if (creatable) BLOCK_ACTIONS[creatable] = tag;
}
registerBlock("wc-start", WCStart, "start", null, null);
registerBlock("wc-end", WCEnd, "end", "end", null);
registerBlock("wc-program", WCProgram, "program", null, null);
registerBlock("wc-decision", WCDecision, "decision", "decision", null);
registerBlock("wc-boolean", WCBoolean, "bool", "boolean", () => $id("variable-area"));
registerBlock("wc-scalar", WCScalar, "scalar", "scalar", () => $id("variable-area"));
registerBlock("wc-string", WCString, "str", "string", () => $id("variable-area"));
registerBlock("wc-vector", WCVector, "vec", "vector", () => $id("variable-area"));
registerBlock("wc-array", WCArray, "arr", "array", () => $id("variable-area"));
registerBlock("wc-varref", WCVarRef, "varref", "varref", null);
registerBlock("wc-expression", WCExpression, "expr", "expression", null);
registerBlock("wc-quantity", WCQuantity, "quantity", "quantity", () => $id("data-area"));
registerBlock("wc-unit", WCUnit, "unit", "unit", () => $id("data-area"));
registerBlock("wc-procedure", WCProcedure, "proc", null, null);
registerBlock("wc-proccall", WCProcCall, "proccall", "proccall", null);
registerBlock("wc-assign", WCAssign, "assign", "assign", null);
/** @type {MutationObserver} Watches SVG connections for changes and refreshes assign-block value visibility. */
const _connObserver = new MutationObserver(() => {
for (const el of document.querySelectorAll("wc-assign")) el._updateValueVisibility();
});
const _connSvg = document.getElementById("connections");
if (_connSvg) _connObserver.observe(_connSvg, { childList: true, subtree: true });