Files
avcf/blocks-algo.js
T

376 lines
12 KiB
JavaScript

// ── Algorithm-tab block definitions ────────────────────────────────────────
//
// Blocks that live on the Algorithm (flowchart) canvas:
// WCStart, WCEnd, WCOutput, WCDecision, WCAssign, WCExpression, WCVarRef
//
// Each extends WCBlock (from wcblock.js) and provides generateOwnCode().
//
// Load order dependencies:
// wcblock.js → WCBlock base class
// helpers.js → findDataSource, resolveSourceName, escapeString, toCName,
// commentPrefix, refreshVarRefOptions
// dom.js → $id
// error.js → showError
// ── WCStart — program entry point ──────────────────────────────────────────
class WCStart extends WCBlock {
delete() { showError("Cannot delete the start block."); }
}
// ── WCEnd — program exit ───────────────────────────────────────────────────
class WCEnd extends WCBlock {
delete() { showError("Cannot delete the end block."); }
generateOwnCode() { return ""; }
}
// ── WCOutput — printf statement ────────────────────────────────────────────
class WCOutput extends WCBlock {
connectedCallback() {
super.connectedCallback();
this._el.textarea = this.querySelector("textarea");
}
saveData() { return { textarea: this._el.textarea.value }; }
restoreData(d) { this.querySelector("textarea").value = d.textarea || ""; }
generateOwnCode() {
const msg = escapeString(this._el.textarea.value || "");
return `\nprintf("${msg}");\n`;
}
}
// ── WCDecision — conditional branch ────────────────────────────────────────
class WCDecision extends WCBlock {
connectedCallback() {
super.connectedCallback();
this._el.boolIn = this.querySelector(".decision-data-in");
}
_getBoolName(block) {
return resolveSourceName(block) || "flag";
}
_branchDescendants(roots) {
const d = new Set(), q = [...roots];
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) {
const v = new Set(visited);
let c = "";
const walk = el => {
if (v.has(el) || stop.has(el)) return;
v.add(el);
if (el instanceof WCDecision) {
c += el.generateCode(v);
} else {
c += el.generateOwnCode();
for (const p of el.wcNextFlow) walk(p.toElement);
}
};
for (const el of roots) walk(el);
return c;
}
generateCode(visited = new Set()) {
const yesTargets = [], noTargets = [];
for (const path of this.wcNextFlow) {
const hook = path.fromHook;
if (!hook || hook.id === "hook-out1") {
yesTargets.push(path.toElement);
} else if (hook.id === "hook-out0") {
noTargets.push(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);
const noCode = this._emitBranch(noTargets, shared, visited);
const sharedCode = this._emitBranch([...shared], new Set(), visited);
for (const el of yesAll) visited.add(el);
for (const el of noAll) visited.add(el);
const source = findDataSource(this._el.boolIn);
const cond = source ? this._getBoolName(source) : "0";
let 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 ? `\n${out}\n` : "";
}
}
// ── WCAssign — variable assignment block ───────────────────────────────────
class WCAssign extends WCBlock {
connectedCallback() {
super.connectedCallback();
this._el.target = this.querySelector(".assign-target");
this._el.value = this.querySelector(".assign-value");
this._el.dataIn = this.querySelector(".assign-row .hook");
this._el.indexArea = this.querySelector(".assign-index-area");
this._el.indexInput = this.querySelector(".assign-index");
this._el.indexHook = this.querySelector(".assign-index-area .hook");
this._el.target.addEventListener("change", () => this._syncType());
setTimeout(() => {
refreshVarRefOptions();
this._syncType();
this._updateValueVisibility();
}, 0);
}
_updateValueVisibility() {
const hasConn = findDataSource(this._el.dataIn) !== null;
this._el.value.classList.toggle("hidden", hasConn);
const hasIdxConn = findDataSource(this._el.indexHook) !== null;
this._el.indexInput.classList.toggle("hidden", hasIdxConn);
}
_syncType() {
const id = this._el.target.value;
const target = id ? $id(id) : null;
const type = target ? target.dataType : "scalar";
this._el.dataIn.dataset.type = type;
this._el.dataIn.title = "expects " + type;
this._el.indexArea.style.display = type === "array" ? "flex" : "none";
}
_getSourceVarName(block) {
return resolveSourceName(block) || "x";
}
_genStringLiteral(name, raw) {
const escaped = escapeString(raw);
const len = raw.length;
return "\n" + [
`free(${name}.data);`,
`${name}.length = ${len};`,
`${name}.data = malloc(${len} + 1);`,
`memcpy(${name}.data, "${escaped}", ${len});`,
`${name}.data[${len}] = 0;`,
].join("\n") + "\n";
}
_genStringCopy(dst, src) {
return "\n" + [
`free(${dst}.data);`,
`${dst}.length = ${src}.length;`,
`${dst}.data = malloc(${src}.length + 1);`,
`memcpy(${dst}.data, ${src}.data, ${src}.length);`,
`${dst}.data[${dst}.length] = 0;`,
].join("\n") + "\n";
}
saveData() { return { target: this._el.target.value, value: this._el.value.value, index: this._el.indexInput.value }; }
restoreData(d) {
const tgt = this.querySelector(".assign-target");
tgt.dataset.restoredValue = d.target || "";
tgt.value = d.target || "";
this.querySelector(".assign-value").value = d.value || "";
this.querySelector(".assign-index").value = d.index || "0";
this._syncType();
this._updateValueVisibility();
}
generateOwnCode() {
const targetId = this._el.target.value;
if (!targetId) return "";
const target = $id(targetId);
if (!target) return "";
const targetName = resolveSourceName(target) || "x";
const targetType = target.dataType || "scalar";
const source = findDataSource(this._el.dataIn);
if (targetType === "array") {
const idxSource = findDataSource(this._el.indexHook);
const idxExpr = idxSource ? this._getSourceVarName(idxSource) : (this._el.indexInput.value || "0");
if (!source) {
const raw = this._el.value.value || "0";
return `\n${targetName}[${idxExpr}] = ${raw};\n`;
}
const srcName = this._getSourceVarName(source);
return `\n${targetName}[${idxExpr}] = ${srcName};\n`;
}
if (!source) {
const raw = this._el.value.value || "0";
if (targetType === "string") return this._genStringLiteral(targetName, raw);
return `\n${targetName} = ${raw};\n`;
}
const srcName = this._getSourceVarName(source);
if (targetType === "string" && source.dataType === "string") {
return this._genStringCopy(targetName, srcName);
}
return `\n${targetName} = ${srcName};\n`;
}
}
// ── WCVarRef — read-only variable reference with data output hook ──────────
class WCVarRef extends WCBlock {
connectedCallback() {
super.connectedCallback();
this._el.select = this.querySelector(".varref-select");
this._el.select.addEventListener("change", () => {
this._syncType();
});
}
_syncType() {
const id = this._el.select.value;
const target = id ? $id(id) : null;
const hook = this.querySelector(".hook-out");
hook.dataset.type = target ? target.dataType : "scalar";
}
saveData() { return { selected: this._el.select.value }; }
restoreData(d) {
const sel = this.querySelector(".varref-select");
sel.dataset.restoredValue = d.selected || "";
sel.value = d.selected || "";
const id = sel.value;
const target = id ? $id(id) : null;
const hook = this.querySelector(".hook-out");
hook.dataset.type = target ? target.dataType : "scalar";
}
generateOwnCode() { return ""; }
}
// ── WCExpression — mathematical expression with MathJax rendering & data hooks ──
class WCExpression extends WCBlock {
get dataType() {
const el = this._el?.latex || this.querySelector(".expr-latex");
const latex = el?.value || "";
return /[<>]|==|!=|\\(?:ge|le|ne|neq|geq|leq|land|lor|wedge|vee|neg|lnot)\b/.test(latex) ? "boolean" : "scalar";
}
connectedCallback() {
super.connectedCallback();
this._el.name = this.querySelector(".expr-name");
this._el.latex = this.querySelector(".expr-latex");
this._el.rendered = this.querySelector(".expr-rendered");
this._el.varhooks = this.querySelector(".expr-varhooks");
this._el.latex.addEventListener("input", () => this._renderFormula());
setTimeout(() => this._renderFormula(), 0);
}
saveData() { return { name: this._el.name.value, latex: this._el.latex.value }; }
restoreData(d) {
this._el.name.value = d.name || "";
this._el.latex.value = d.latex || "";
this._renderFormula();
}
_parseVariables(latex) {
const tokens = latex
.replace(/\\[a-zA-Z]+/g, "")
.replace(/[{}\[\]()]/g, " ")
.replace(/[\d+*\/^_=,;:!?<>|~`@#\$%&'-]/g, " ")
.split(/\s+/)
.filter(t => t.length > 0 && t.length <= 8)
.filter(t => ![
"sin","cos","tan","cot","sec","csc","arcsin","arccos","arctan",
"log","ln","lg","exp","sqrt","lim","inf","sum","int","prod","det",
"gcd","lcm","max","min","arg","mod","Re","Im","deg","Pr","var",
"alpha","beta","gamma","delta","epsilon","zeta","eta","theta",
"iota","kappa","lambda","mu","nu","xi","omicron","rho","sigma",
"tau","upsilon","phi","chi","psi","omega","varepsilon","vartheta",
"varpi","varphi","varrho","varsigma","varUpsilon"
].includes(t.toLowerCase()));
return [...new Set(tokens)];
}
_rebuildVarHooks(vars) {
this._el.varhooks.innerHTML = "";
for (const v of vars) {
const hook = document.createElement("button");
hook.className = "hook";
hook.id = this.id + "-vh-" + v;
hook.title = v;
hook.textContent = v;
hook.dataset.direction = "in";
hook.dataset.linkType = "data";
hook.dataset.type = "scalar";
hook.dataset.exit = "left";
this._setupHook(hook);
this._el.varhooks.appendChild(hook);
}
}
_renderFormula() {
const latex = this._el.latex.value;
const outHook = this.querySelector(".hook-out");
if (outHook) outHook.dataset.type = this.dataType;
this._el.rendered.innerHTML = "";
const vars = latex.trim() ? this._parseVariables(latex) : [];
this._rebuildVarHooks(vars);
if (!latex.trim()) return;
if (window.MathJax && MathJax.typesetPromise) {
const wrapper = document.createElement("div");
wrapper.textContent = "$$" + latex + "$$";
const typeset = () => {
MathJax.typesetPromise([wrapper]).then(() => {
this._el.rendered.innerHTML = wrapper.innerHTML;
}).catch(() => {
this._el.rendered.textContent = "\u26A0 error";
});
};
if (MathJax.startup && MathJax.startup.promise && MathJax.startup.promise.then) {
MathJax.startup.promise.then(typeset);
} else {
typeset();
}
} else {
this._el.rendered.textContent = latex;
}
}
generateCode(visited = new Set()) {
if (visited.has(this)) return "";
visited.add(this);
return this.generateOwnCode();
}
generateOwnCode() {
const name = this._el.name.value || "res";
const formula = this._el.latex.value || "0";
const type = this.dataType === "boolean" ? "int" : "double";
return `${type} ${name} = ${formula};`;
}
}