243 lines
10 KiB
JavaScript
243 lines
10 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
|
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|
|
|
class WCProgram extends HTMLElement {
|
|
constructor() {
|
|
super();
|
|
this.counter = 0;
|
|
}
|
|
|
|
build() {
|
|
const startFrag = document.importNode(
|
|
$id("wc-start").content, true
|
|
);
|
|
const endFrag = document.importNode(
|
|
$id("wc-end").content, true
|
|
);
|
|
startFrag.firstElementChild.id = "main-start";
|
|
endFrag.firstElementChild.id = "main-end";
|
|
this.appendChild(startFrag);
|
|
this.appendChild(endFrag);
|
|
}
|
|
|
|
makeNode(tag) {
|
|
const info = BLOCK_REGISTRY[tag];
|
|
if (!info) return null;
|
|
this.counter++;
|
|
const frag = document.importNode(document.querySelector(info.template).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"); }
|
|
newOutputNode() { return this.newNode("wc-output"); }
|
|
newProcCallNode() { const el = this.newNode("wc-proccall"); if (el) refreshProcOptions(); return el; }
|
|
|
|
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;
|
|
}
|
|
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"));
|
|
if (el) refreshProcOptions();
|
|
return el;
|
|
}
|
|
|
|
_gatherUsedTypes() {
|
|
const usedQuantities = new Set(), usedUnits = new Set();
|
|
for (const d of $id("variable-area").querySelectorAll("wc-scalar")) {
|
|
const t = d.querySelector(".var-type").value;
|
|
if (t) usedQuantities.add(t);
|
|
const u = d.querySelector(".var-unit").value;
|
|
if (u) usedUnits.add(u);
|
|
}
|
|
return { usedQuantities, usedUnits };
|
|
}
|
|
|
|
_generateTypeDefs(usedQuantities) {
|
|
return Array.from(
|
|
$id("data-area").querySelectorAll("wc-quantity")
|
|
).filter(q => usedQuantities.has(q.querySelector(".quantity-name").value || "unnamed"))
|
|
.map(q => {
|
|
const name = q.querySelector(".quantity-name").value || "unnamed";
|
|
return `typedef double ${name};`;
|
|
}).join("\n");
|
|
}
|
|
|
|
_generateUnitDefs(usedUnits) {
|
|
return Array.from(
|
|
$id("data-area").querySelectorAll("wc-unit")
|
|
).filter(u => usedUnits.has(u.querySelector(".unit-suffix").value))
|
|
.map(u => {
|
|
const type = u.querySelector(".unit-type").value || "unknown";
|
|
const suffix = u.querySelector(".unit-suffix").value || "";
|
|
const fullname = u.querySelector(".unit-fullname").value || "";
|
|
const si = u.querySelector(".unit-si").value || "1";
|
|
return `// unit: ${fullname} (${suffix}) = 1 ${type} * ${si}`;
|
|
}).join("\n");
|
|
}
|
|
|
|
_generateVariableDeclarations() {
|
|
const boolDefs = Array.from($id("variable-area").querySelectorAll("wc-boolean")).map(d => d.generateCode()).join("\n");
|
|
const scalarDefs = Array.from($id("variable-area").querySelectorAll("wc-scalar")).map(d => d.generateCode()).join("\n");
|
|
const stringDefs = Array.from($id("variable-area").querySelectorAll("wc-string")).map(d => d.generateCode()).join("\n");
|
|
const arrayDefs = Array.from($id("variable-area").querySelectorAll("wc-array")).map(d => d.generateCode()).join("\n");
|
|
const exprDefs = Array.from(this.querySelectorAll("wc-expression")).map(d => d.generateCode()).join("\n");
|
|
return { boolDefs, scalarDefs, stringDefs, arrayDefs, exprDefs };
|
|
}
|
|
|
|
_generateIncludes(proccalls, hasStrings) {
|
|
const libNames = new Set();
|
|
for (const p of proccalls) {
|
|
const qname = p.querySelector(".proccall-select").value;
|
|
if (qname) {
|
|
const lib = qname.split("/")[0];
|
|
if (lib) libNames.add(lib);
|
|
}
|
|
}
|
|
let includes = Array.from(libNames).map(l => `#include <${l}.h>`).join("\n");
|
|
if (hasStrings) {
|
|
const hdr = "#include <stdlib.h>\n#include <string.h>";
|
|
includes = includes ? includes + "\n" + hdr : hdr;
|
|
}
|
|
return includes;
|
|
}
|
|
|
|
generateCode() {
|
|
const { usedQuantities, usedUnits } = this._gatherUsedTypes();
|
|
|
|
const typeDefs = this._generateTypeDefs(usedQuantities);
|
|
const unitDefs = this._generateUnitDefs(usedUnits);
|
|
const { boolDefs, scalarDefs, stringDefs, arrayDefs, exprDefs } = 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 body = this.firstElementChild.generateCode(new Set());
|
|
const proccalls = this.querySelectorAll("wc-proccall");
|
|
const includes = this._generateIncludes(proccalls, hasStrings);
|
|
|
|
const stringNames = Array.from($id("variable-area").querySelectorAll("wc-string")).map(
|
|
d => toCName(d.querySelector(".var-name").value) || "s"
|
|
);
|
|
const freeSection = hasStrings
|
|
? "\n" + stringNames.map(n => `free(${n}.data);`).join("\n") + "\n"
|
|
: "";
|
|
|
|
const dataSection = (boolDefs || scalarDefs || stringDefs || arrayDefs || exprDefs)
|
|
? [boolDefs, scalarDefs, stringDefs, arrayDefs, exprDefs]
|
|
.filter(Boolean).map(s => s + "\n").join("")
|
|
: "";
|
|
|
|
$id("c-code").value =
|
|
"// --- WiseCode\u00ae ---\n" +
|
|
(includes ? includes + "\n\n" : "") +
|
|
"int main() {\n" +
|
|
(typeDefs ? typeDefs + "\n\n" : "") +
|
|
(unitDefs ? unitDefs + "\n\n" : "") +
|
|
stringStruct + dataSection + body + freeSection +
|
|
"return 0;\n" +
|
|
"}\n// --- END ---";
|
|
}
|
|
}
|
|
|
|
|
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|
// Block registry
|
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|
|
|
const BLOCK_REGISTRY = {};
|
|
const BLOCK_ACTIONS = {};
|
|
|
|
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", null, null);
|
|
registerBlock("wc-program", WCProgram, "program", null, null);
|
|
registerBlock("wc-output", WCOutput, "output", "output", 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-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);
|
|
|
|
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 });
|