// ── 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 }; } _generateLLVMDecls(proccalls) { var seen = new Set(); var lines = []; for (var pi = 0; pi < proccalls.length; pi++) { var qname = proccalls[pi]._selectedProc; if (!qname || seen.has(qname)) continue; seen.add(qname); var decl = LLVM_DECL_MAP[qname]; if (decl) { lines.push(decl); } else { var fn = qname.split("/")[1] || qname; lines.push("declare i32 @" + fn + "(...)"); } } return lines.join("\n"); } _generateIR() { const { usedQuantities, usedUnits } = this._gatherUsedTypes(); const typeDefs = this._generateTypeDefs(usedQuantities); const unitDefs = this._generateUnitDefs(usedUnits); const { boolDefs, scalarDefs, vectorDefs, stringDefs, arrayDefs } = this._generateVariableDeclarations(); CodeGen.resetStringPool(); const firstChild = this.firstElementChild; const body = firstChild && typeof CodeGen.emitFlat === "function" ? CodeGen.emitFlat(firstChild) : "; no flowchart body\n"; const proccalls = this.querySelectorAll("wc-proccall"); const decls = this._generateLLVMDecls(proccalls); const strPool = CodeGen.getStringPool(); const globals = strPool.map(function (_, i) { return CodeGen.stringGlobal(i); }).join("\n"); const dataSection = (boolDefs || scalarDefs || vectorDefs || stringDefs || arrayDefs) ? [boolDefs, scalarDefs, vectorDefs, stringDefs, arrayDefs] .filter(Boolean).map(function (s) { return s + "\n"; }).join("") : ""; return CodeGen.generateIRFile(firstChild, decls, globals, dataSection); } generateAndDownload() { const ir = this._generateIR(); if (!ir) { showError("No IR to download"); return; } const blob = new Blob([ir], { type: "text/plain" }); const url = URL.createObjectURL(blob); const a = document.createElement("a"); a.href = url; a.download = "program.ll"; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); } } /** * Maps library/function names to their LLVM IR `declare` signature. * Key is the proc qname, value is the declare line. * Functions not listed get a generic `declare i32 @name(...)`. */ const LLVM_DECL_MAP = { "stdio/printf": "declare i32 @printf(i8*, ...)", "stdlib/exit": "declare void @exit(i32)", }; // ═══════════════════════════════════════════════════════════════════════════════ // Block registry // ═══════════════════════════════════════════════════════════════════════════════ /** @type {ObjectHTMLElement)|null}>} */ const BLOCK_REGISTRY = {}; /** @type {Object} 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 });