refactor: extract pure codegen into shared module, remove duplication
- Create codegen/index.js with pure branchDescendants, emitBranch, generateCallWithError, and generateCFile — zero DOM dependencies - blocks-proc.js adapters delegate to CodeGen with browser instanceof check - run.js uses the same CodeGen module instead of duplicating block classes with their own branchDescendants/emitBranch logic - Load codegen/index.js in browser before blocks-proc.js
This commit is contained in:
+13
-44
@@ -168,49 +168,27 @@ function _specDataType(spec) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Collects all blocks reachable from the given root blocks via wcNextFlow,
|
* Adapter: wraps CodeGen.branchDescendants with the browser's instanceof check.
|
||||||
* excluding the interior of decision blocks (decisions handle their own branching).
|
|
||||||
* Used by WCProcCall._generateCallWithError to compute shared vs exclusive branches.
|
|
||||||
* @param {HTMLElement[]} roots
|
* @param {HTMLElement[]} roots
|
||||||
* @returns {Set<HTMLElement>}
|
* @returns {Set<HTMLElement>}
|
||||||
*/
|
*/
|
||||||
function _branchDescendants(roots) {
|
function _branchDescendants(roots) {
|
||||||
const d = new Set(), q = [...roots];
|
return CodeGen.branchDescendants(roots, function (b) {
|
||||||
for (const el of q) {
|
return b instanceof WCDecision;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Emits C code for a branch by walking from root blocks, stopping at shared blocks
|
* Adapter: wraps CodeGen.emitBranch with the browser's instanceof check.
|
||||||
* and decision blocks (which emit their own branching). Uses visited sets to prevent
|
|
||||||
* double-emission when shared code is later emitted separately.
|
|
||||||
* @param {HTMLElement[]} roots
|
* @param {HTMLElement[]} roots
|
||||||
* @param {Set<HTMLElement>} stop blocks to exclude (shared between branches)
|
* @param {Set<HTMLElement>} stop
|
||||||
* @param {Set<HTMLElement>} visited blocks already emitted
|
* @param {Set<HTMLElement>} visited
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
function _emitBranch(roots, stop, visited) {
|
function _emitBranch(roots, stop, visited) {
|
||||||
const v = new Set(visited);
|
return CodeGen.emitBranch(roots, stop, visited, function (b) {
|
||||||
let c = "";
|
return b instanceof WCDecision;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 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. */
|
||||||
@@ -605,18 +583,9 @@ class WCProcCall extends WCBlock {
|
|||||||
.filter(p => p.fromHook !== this._el.errHook)
|
.filter(p => p.fromHook !== this._el.errHook)
|
||||||
.map(p => p.toElement);
|
.map(p => p.toElement);
|
||||||
|
|
||||||
const errAll = _branchDescendants(errTargets);
|
return CodeGen.generateCallWithError(ifHeader, errTargets, okTargets, function (b) {
|
||||||
const okAll = _branchDescendants(okTargets);
|
return b instanceof WCDecision;
|
||||||
const shared = new Set([...errAll].filter(el => okAll.has(el)));
|
});
|
||||||
|
|
||||||
const errCode = _emitBranch(errTargets, shared, new Set());
|
|
||||||
const okCode = _emitBranch(okTargets, shared, new Set());
|
|
||||||
const sharedCode = _emitBranch([...shared], new Set(), new Set());
|
|
||||||
|
|
||||||
let out = `${ifHeader} {\n${errCode}}\n`;
|
|
||||||
if (okCode) out += okCode;
|
|
||||||
if (sharedCode) out += sharedCode;
|
|
||||||
return out;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
saveData() {
|
saveData() {
|
||||||
|
|||||||
@@ -0,0 +1,150 @@
|
|||||||
|
// ── Pure C code generation — no DOM dependencies ─────────────────────────
|
||||||
|
//
|
||||||
|
// These functions operate on generic block objects with the interface:
|
||||||
|
// { generateOwnCode(): string,
|
||||||
|
// generateCode(visited: Set): string,
|
||||||
|
// wcNextFlow?: [{ toElement: block }],
|
||||||
|
// nextFlow?: [{ toElement: block }] }
|
||||||
|
//
|
||||||
|
// Browser DOM blocks use wcNextFlow; test/mock blocks use nextFlow.
|
||||||
|
// Decision blocks must expose isDecision = true.
|
||||||
|
|
||||||
|
(function () {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
var CodeGen = {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Collects all blocks reachable from the given roots via nextFlow,
|
||||||
|
* excluding the interior of decision blocks (they handle their own
|
||||||
|
* branching). Returns a Set of block references.
|
||||||
|
* @param {Array} roots
|
||||||
|
* @param {function(block): 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;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emits C code for a branch by walking from root blocks, stopping at
|
||||||
|
* blocks in the stop set and at decision boundaries. Each visited block
|
||||||
|
* is added to the visited set to prevent double-emission.
|
||||||
|
* @param {Array} roots
|
||||||
|
* @param {Set} stop blocks to exclude (shared between branches)
|
||||||
|
* @param {Set} visited blocks already emitted
|
||||||
|
* @param {function(block): boolean} [isDecision]
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
CodeGen.emitBranch = function (roots, stop, visited, isDecision) {
|
||||||
|
isDecision =
|
||||||
|
isDecision ||
|
||||||
|
function (b) {
|
||||||
|
return b.isDecision;
|
||||||
|
};
|
||||||
|
var v = new Set(visited);
|
||||||
|
var parts = [];
|
||||||
|
|
||||||
|
function walk(el) {
|
||||||
|
if (v.has(el) || (stop && stop.has(el))) return;
|
||||||
|
v.add(el);
|
||||||
|
if (isDecision(el)) {
|
||||||
|
parts.push(el.generateCode(v));
|
||||||
|
} else {
|
||||||
|
parts.push(el.generateOwnCode());
|
||||||
|
var flow = el.wcNextFlow || el.nextFlow || [];
|
||||||
|
for (var j = 0; j < flow.length; j++) {
|
||||||
|
walk(flow[j].toElement);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < roots.length; i++) walk(roots[i]);
|
||||||
|
return parts.join("");
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates the error-path branch for a proc call: computes shared blocks
|
||||||
|
* between error and success paths, emits error-exclusive code inside `if`,
|
||||||
|
* then success-exclusive and shared code after it (no else).
|
||||||
|
* @param {string} ifHeader e.g. 'if (func() != 0)' or 'if ((r=f()) != 0)'
|
||||||
|
* @param {Array} errTargets root blocks reachable from the error hook
|
||||||
|
* @param {Array} okTargets root blocks reachable from the success hook
|
||||||
|
* @param {function(block): boolean} [isDecision]
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
CodeGen.generateCallWithError = function (
|
||||||
|
ifHeader,
|
||||||
|
errTargets,
|
||||||
|
okTargets,
|
||||||
|
isDecision
|
||||||
|
) {
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
|
||||||
|
var errCode = CodeGen.emitBranch(errTargets, shared, new Set(), isDecision);
|
||||||
|
var okCode = CodeGen.emitBranch(okTargets, shared, new Set(), isDecision);
|
||||||
|
var sharedCode = CodeGen.emitBranch(
|
||||||
|
Array.from(shared),
|
||||||
|
new Set(),
|
||||||
|
new Set(),
|
||||||
|
isDecision
|
||||||
|
);
|
||||||
|
|
||||||
|
var out = ifHeader + " {\n" + errCode + "}\n";
|
||||||
|
if (okCode) out += okCode;
|
||||||
|
if (sharedCode) out += sharedCode;
|
||||||
|
return out;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assembles a complete C source file from a program's entry block,
|
||||||
|
* include directives, and variable declarations.
|
||||||
|
* @param {Object} entryBlock the first block in the flow (e.g. START)
|
||||||
|
* @param {string[]} includes #include directives
|
||||||
|
* @param {string} declarations variable declarations (already indented)
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
CodeGen.generateCFile = function (entryBlock, includes, declarations) {
|
||||||
|
var lines = ["// --- WiseCode\u00ae ---"];
|
||||||
|
for (var i = 0; i < includes.length; i++) lines.push(includes[i]);
|
||||||
|
lines.push("");
|
||||||
|
lines.push("int main() {");
|
||||||
|
if (declarations) lines.push(declarations);
|
||||||
|
var body = entryBlock.generateCode(new Set());
|
||||||
|
lines.push(body);
|
||||||
|
lines.push("}");
|
||||||
|
lines.push("// --- END ---");
|
||||||
|
return lines.join("\n");
|
||||||
|
};
|
||||||
|
|
||||||
|
// ── Export ──────────────────────────────────────────────────────────
|
||||||
|
if (typeof module !== "undefined" && module.exports) {
|
||||||
|
module.exports = CodeGen;
|
||||||
|
}
|
||||||
|
if (typeof window !== "undefined") {
|
||||||
|
window.CodeGen = CodeGen;
|
||||||
|
}
|
||||||
|
})();
|
||||||
@@ -521,6 +521,7 @@
|
|||||||
<script src="wcblock.js"></script>
|
<script src="wcblock.js"></script>
|
||||||
<script src="blocks-algo.js"></script>
|
<script src="blocks-algo.js"></script>
|
||||||
<script src="blocks-data.js"></script>
|
<script src="blocks-data.js"></script>
|
||||||
|
<script src="codegen/index.js"></script>
|
||||||
<script src="blocks-proc.js"></script>
|
<script src="blocks-proc.js"></script>
|
||||||
<script src="wcprogram.js"></script>
|
<script src="wcprogram.js"></script>
|
||||||
<script src="persist.js"></script>
|
<script src="persist.js"></script>
|
||||||
|
|||||||
+200
-269
@@ -3,324 +3,258 @@
|
|||||||
* C compilation test runner.
|
* C compilation test runner.
|
||||||
*
|
*
|
||||||
* Generates C source files matching each template's expected output,
|
* Generates C source files matching each template's expected output,
|
||||||
* then runs `make` to compile them with `gcc -Wall -Wextra -Werror -fsyntax-only`.
|
* then runs `make` to compile them with `gcc -Wall -Wextra -fsyntax-only`.
|
||||||
*
|
*
|
||||||
* Code generation is not hardcoded — each source is built from block-like
|
* Block data objects are created manually (matching the browser template
|
||||||
* data structures walked by the same algorithm the browser templates use.
|
* 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");
|
||||||
const path = require("path");
|
const path = require("path");
|
||||||
const { execSync } = require("child_process");
|
const { execSync } = require("child_process");
|
||||||
|
const CodeGen = require("../../codegen/index.js");
|
||||||
|
|
||||||
const OUTDIR = "/tmp/wisecode-compile";
|
const OUTDIR = "/tmp/wisecode-compile";
|
||||||
const MKFILE = path.join(__dirname, "Makefile");
|
const MKFILE = path.join(__dirname, "Makefile");
|
||||||
|
|
||||||
// ── Branch analysis (mirrors blocks-proc.js _branchDescendants / _emitBranch) ──
|
// ── Block data helpers ────────────────────────────────────────────────
|
||||||
|
// Plain objects matching the interface CodeGen expects:
|
||||||
|
// { generateOwnCode(), generateCode(visited), nextFlow, isDecision }
|
||||||
|
|
||||||
function branchDescendants(roots) {
|
function startBlock(id) {
|
||||||
const d = new Set(), q = [...roots];
|
return {
|
||||||
for (const el of q) {
|
id: id,
|
||||||
if (d.has(el)) continue;
|
isDecision: false,
|
||||||
d.add(el);
|
nextFlow: [],
|
||||||
if (el.type === "decision") continue;
|
generateOwnCode: function () {
|
||||||
for (const p of (el.nextFlow || [])) {
|
return "";
|
||||||
if (!d.has(p.toElement)) q.push(p.toElement);
|
},
|
||||||
}
|
generateCode: function (visited) {
|
||||||
}
|
if (visited.has(this)) return "";
|
||||||
return d;
|
visited.add(this);
|
||||||
}
|
var c = this.generateOwnCode();
|
||||||
|
for (var i = 0; i < this.nextFlow.length; i++) {
|
||||||
function emitBranch(roots, stop, visited) {
|
c += this.nextFlow[i].toElement.generateCode(visited);
|
||||||
const v = new Set(visited);
|
}
|
||||||
const lines = [];
|
return c;
|
||||||
const walk = (el) => {
|
},
|
||||||
if (v.has(el) || stop.has(el)) return;
|
|
||||||
v.add(el);
|
|
||||||
if (el.type === "decision") {
|
|
||||||
lines.push(el.generateCode(v));
|
|
||||||
} else {
|
|
||||||
lines.push(el.generateOwnCode());
|
|
||||||
for (const p of (el.nextFlow || [])) walk(p.toElement);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
for (const el of roots) walk(el);
|
|
||||||
return lines.join("\n");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Block classes (simplified, match browser code gen) ────────────────
|
function endBlock(id, exitCode) {
|
||||||
|
var code = exitCode === 0 ? "\n return 0;\n" : "\n exit(" + exitCode + ");\n";
|
||||||
class Block {
|
return {
|
||||||
constructor(id, type, nextFlow = []) {
|
id: id,
|
||||||
this.id = id;
|
isDecision: false,
|
||||||
this.type = type;
|
nextFlow: [],
|
||||||
this.nextFlow = nextFlow;
|
generateOwnCode: function () {
|
||||||
}
|
return code;
|
||||||
generateCode(visited) {
|
},
|
||||||
if (visited.has(this)) return "";
|
generateCode: function (visited) {
|
||||||
visited.add(this);
|
if (visited.has(this)) return "";
|
||||||
const own = this.generateOwnCode();
|
visited.add(this);
|
||||||
const next = this.generateNextCode(visited);
|
return this.generateOwnCode();
|
||||||
return own + next;
|
},
|
||||||
}
|
};
|
||||||
generateNextCode(visited) {
|
|
||||||
let c = "";
|
|
||||||
for (const p of this.nextFlow) {
|
|
||||||
c += p.toElement.generateCode(visited);
|
|
||||||
}
|
|
||||||
return c;
|
|
||||||
}
|
|
||||||
generateOwnCode() { return ""; }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class Start extends Block {
|
function wrap(targets) {
|
||||||
generateOwnCode() { return ""; }
|
return (targets || []).map(function (t) {
|
||||||
|
return { toElement: t };
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
class End extends Block {
|
function procCallBlock(id, procName, inputs, returnStyle, returnValueName, okTargets, errTargets) {
|
||||||
constructor(id, code = 0) {
|
var call = procName + "(" + inputs.join(", ") + ")";
|
||||||
super(id, "end");
|
var self = {
|
||||||
this.exitCode = code;
|
id: id,
|
||||||
}
|
isDecision: false,
|
||||||
generateOwnCode() {
|
nextFlow: wrap(okTargets),
|
||||||
if (this.exitCode === 0) return "\n return 0;\n";
|
errTargets: errTargets || [],
|
||||||
return `\n exit(${this.exitCode});\n`;
|
generateOwnCode: function () {
|
||||||
}
|
if (self.errTargets.length === 0) return "\n " + call + ";\n";
|
||||||
}
|
var ifHeader;
|
||||||
|
if (returnStyle === "value" && returnValueName) {
|
||||||
class Decision extends Block {
|
ifHeader = " if ((" + returnValueName + " = " + call + ") != 0)";
|
||||||
constructor(id, yesTargets = [], noTargets = []) {
|
|
||||||
super(id, "decision");
|
|
||||||
this.yesTargets = yesTargets;
|
|
||||||
this.noTargets = noTargets;
|
|
||||||
}
|
|
||||||
get nextFlow() {
|
|
||||||
return [...this.yesTargets, ...this.noTargets];
|
|
||||||
}
|
|
||||||
_branchDescendants(roots) {
|
|
||||||
const d = new Set(), q = [...roots];
|
|
||||||
for (const el of q) {
|
|
||||||
if (d.has(el)) continue;
|
|
||||||
d.add(el);
|
|
||||||
if (el instanceof Decision) continue;
|
|
||||||
for (const p of el.nextFlow) {
|
|
||||||
if (!d.has(p.toElement)) q.push(p.toElement);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return d;
|
|
||||||
}
|
|
||||||
_emitBranch(roots, stop, visited) {
|
|
||||||
const v = new Set(visited);
|
|
||||||
const lines = [];
|
|
||||||
const walk = (el) => {
|
|
||||||
if (v.has(el) || stop.has(el)) return;
|
|
||||||
v.add(el);
|
|
||||||
if (el instanceof Decision) {
|
|
||||||
lines.push(el.generateCode(v));
|
|
||||||
} else {
|
} else {
|
||||||
lines.push(el.generateOwnCode());
|
ifHeader = " if (" + call + " != 0)";
|
||||||
for (const p of el.nextFlow) walk(p.toElement);
|
|
||||||
}
|
}
|
||||||
};
|
var okTargets = self.nextFlow.map(function (p) {
|
||||||
for (const el of roots) walk(el);
|
return p.toElement;
|
||||||
return lines.join("\n");
|
});
|
||||||
}
|
return CodeGen.generateCallWithError(
|
||||||
generateCode(visited = new Set()) {
|
"\n" + ifHeader,
|
||||||
if (visited.has(this)) return "";
|
self.errTargets,
|
||||||
visited.add(this);
|
okTargets,
|
||||||
if (this.yesTargets.length === 0 && this.noTargets.length === 0) return "";
|
function (b) {
|
||||||
const yesAll = this._branchDescendants(this.yesTargets);
|
return b.isDecision;
|
||||||
const noAll = this._branchDescendants(this.noTargets);
|
}
|
||||||
const shared = new Set([...yesAll].filter(el => noAll.has(el) && el !== this));
|
);
|
||||||
const yesCode = this._emitBranch(this.yesTargets, shared, visited);
|
},
|
||||||
const noCode = this._emitBranch(this.noTargets, shared, visited);
|
generateCode: function (visited) {
|
||||||
const sharedCode = this._emitBranch([...shared], new Set(), visited);
|
if (visited.has(this)) return "";
|
||||||
for (const el of yesAll) visited.add(el);
|
visited.add(this);
|
||||||
for (const el of noAll) visited.add(el);
|
var own = this.generateOwnCode();
|
||||||
let out = "";
|
if (self.errTargets.length > 0) return own;
|
||||||
if (yesCode || noCode) {
|
for (var i = 0; i < this.nextFlow.length; i++) {
|
||||||
if (yesCode) out += `\nif (cond) {\n${yesCode}}`;
|
own += this.nextFlow[i].toElement.generateCode(visited);
|
||||||
if (noCode) {
|
|
||||||
out += yesCode
|
|
||||||
? ` else {\n${noCode}}`
|
|
||||||
: `\nif (!(cond)) {\n${noCode}}`;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
out += sharedCode;
|
|
||||||
return out ? `\n${out}\n` : "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class ProcCall extends Block {
|
|
||||||
constructor(id, procName, inputs, returnStyle, returnValueName, nextFlow = [], errTargets = []) {
|
|
||||||
super(id, "call", nextFlow);
|
|
||||||
this.procName = procName;
|
|
||||||
this.inputs = inputs;
|
|
||||||
this.returnStyle = returnStyle;
|
|
||||||
this.returnValueName = returnValueName;
|
|
||||||
this.errTargets = errTargets;
|
|
||||||
}
|
|
||||||
generateCode(visited = new Set()) {
|
|
||||||
if (visited.has(this)) return "";
|
|
||||||
visited.add(this);
|
|
||||||
const own = this.generateOwnCode();
|
|
||||||
if (this.errTargets.length > 0) {
|
|
||||||
return own;
|
return own;
|
||||||
}
|
},
|
||||||
return own + this.generateNextCode(visited);
|
};
|
||||||
}
|
return self;
|
||||||
generateOwnCode() {
|
|
||||||
const call = `${this.procName}(${this.inputs.join(", ")})`;
|
|
||||||
if (this.errTargets.length === 0) return `\n ${call};\n`;
|
|
||||||
|
|
||||||
let ifHeader;
|
|
||||||
if (this.returnStyle === "value" && this.returnValueName) {
|
|
||||||
ifHeader = ` if ((${this.returnValueName} = ${call}) != 0)`;
|
|
||||||
} else {
|
|
||||||
ifHeader = ` if (${call} != 0)`;
|
|
||||||
}
|
|
||||||
return this._generateCallWithError(ifHeader, this.errTargets);
|
|
||||||
}
|
|
||||||
_generateCallWithError(ifHeader, errTargets) {
|
|
||||||
const okTargets = this.nextFlow.map(p => p.toElement);
|
|
||||||
const errAll = branchDescendants(errTargets);
|
|
||||||
const okAll = branchDescendants(okTargets);
|
|
||||||
const shared = new Set([...errAll].filter(b => okAll.has(b)));
|
|
||||||
const errCode = emitBranch(errTargets, shared, new Set());
|
|
||||||
const okCode = emitBranch(okTargets, shared, new Set());
|
|
||||||
const sharedCode = emitBranch([...shared], new Set(), new Set());
|
|
||||||
let out = `\n${ifHeader} {\n${errCode}}\n`;
|
|
||||||
if (okCode) out += okCode;
|
|
||||||
if (sharedCode) out += sharedCode;
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class Assign extends Block {
|
function assignBlock(id, target, value) {
|
||||||
constructor(id, target, value) {
|
var code = "\n " + target + " = " + value + ";\n";
|
||||||
super(id, "assign");
|
return {
|
||||||
this.target = target;
|
id: id,
|
||||||
this.value = value;
|
isDecision: false,
|
||||||
}
|
nextFlow: [],
|
||||||
generateOwnCode() {
|
generateOwnCode: function () {
|
||||||
return `\n ${this.target} = ${this.value};\n`;
|
return code;
|
||||||
}
|
},
|
||||||
|
generateCode: function (visited) {
|
||||||
|
if (visited.has(this)) return "";
|
||||||
|
visited.add(this);
|
||||||
|
var c = this.generateOwnCode();
|
||||||
|
for (var i = 0; i < this.nextFlow.length; i++) {
|
||||||
|
c += this.nextFlow[i].toElement.generateCode(visited);
|
||||||
|
}
|
||||||
|
return c;
|
||||||
|
},
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Template generators ────────────────────────────────────────────────
|
function link(from, to) {
|
||||||
|
from.nextFlow.push({ toElement: to });
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Template builders ──────────────────────────────────────────────────
|
||||||
|
|
||||||
function buildConsoleTemplate() {
|
function buildConsoleTemplate() {
|
||||||
const start = new Start("start");
|
var start = startBlock("start");
|
||||||
const end = new End("end", 0);
|
var end = endBlock("end", 0);
|
||||||
const printf = new ProcCall("printf", "printf", ['"Hello, World!\\n"'], "value", "result");
|
var errEnd = endBlock("errEnd", 1);
|
||||||
const errEnd = new End("errEnd", 1);
|
var printf = procCallBlock(
|
||||||
printf.errTargets = [errEnd];
|
"printf",
|
||||||
|
"printf",
|
||||||
start.nextFlow.push({ toElement: printf });
|
['"Hello, World!\\n"'],
|
||||||
printf.nextFlow.push({ toElement: end });
|
"value",
|
||||||
|
"result",
|
||||||
return { start, vars: ["int result;"], hasStdio: true, hasStdlib: true };
|
[end],
|
||||||
|
[errEnd]
|
||||||
|
);
|
||||||
|
link(start, printf);
|
||||||
|
return {
|
||||||
|
entry: start,
|
||||||
|
includes: ["#include <stdio.h>", "#include <stdlib.h>"],
|
||||||
|
declarations: " int result;",
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildErrorSimpleTemplate() {
|
function buildErrorSimpleTemplate() {
|
||||||
const start = new Start("start");
|
var start = startBlock("start");
|
||||||
const end = new End("end", 0);
|
var end = endBlock("end", 0);
|
||||||
const call = new ProcCall("init", "func", [], "error", null);
|
var errEnd = endBlock("errEnd", 1);
|
||||||
const errEnd = new End("errEnd", 1);
|
var call = procCallBlock("init", "func", [], "error", null, [end], [errEnd]);
|
||||||
call.errTargets = [errEnd];
|
link(start, call);
|
||||||
start.nextFlow.push({ toElement: call });
|
return {
|
||||||
call.nextFlow.push({ toElement: end });
|
entry: start,
|
||||||
return { start, vars: [], hasStdlib: true };
|
includes: ["#include <stdlib.h>"],
|
||||||
}
|
declarations: "",
|
||||||
|
proto: ["int func(void);"],
|
||||||
function buildErrorSimpleTemplate() {
|
};
|
||||||
const start = new Start("start");
|
|
||||||
const end = new End("end", 0);
|
|
||||||
const call = new ProcCall("init", "func", [], "error", null);
|
|
||||||
const errEnd = new End("errEnd", 1);
|
|
||||||
call.errTargets = [errEnd];
|
|
||||||
start.nextFlow.push({ toElement: call });
|
|
||||||
call.nextFlow.push({ toElement: end });
|
|
||||||
return { start, vars: [], hasStdlib: true, proto: ["int func(void);"] };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildErrorMergeTemplate() {
|
function buildErrorMergeTemplate() {
|
||||||
const start = new Start("start");
|
var start = startBlock("start");
|
||||||
const end = new End("end", 0);
|
var end = endBlock("end", 0);
|
||||||
const call = new ProcCall("init", "func", [], "error", null);
|
var logged = assignBlock("log", "logged", "1");
|
||||||
const errLog = new Assign("log_err", "logged", "1");
|
var done = assignBlock("done", "done", "1");
|
||||||
const shared = new Assign("shared_done", "done", "1");
|
var call = procCallBlock("init", "func", [], "error", null, [done], [logged]);
|
||||||
|
link(logged, done);
|
||||||
call.errTargets = [errLog];
|
link(done, end);
|
||||||
start.nextFlow.push({ toElement: call });
|
link(start, call);
|
||||||
errLog.nextFlow.push({ toElement: shared });
|
return {
|
||||||
call.nextFlow.push({ toElement: shared });
|
entry: start,
|
||||||
shared.nextFlow.push({ toElement: end });
|
includes: ["#include <stdlib.h>"],
|
||||||
|
declarations: " int logged;\n int done;",
|
||||||
return { start, vars: ["int logged;", "int done;"],
|
proto: ["int func(void);"],
|
||||||
hasStdlib: true, proto: ["int func(void);"] };
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function errorSequentialTemplate() {
|
function errorSequentialTemplate() {
|
||||||
const start = new Start("start");
|
var start = startBlock("start");
|
||||||
const end = new End("end", 0);
|
var end = endBlock("end", 0);
|
||||||
const call1 = new ProcCall("a", "init_a", [], "error", null);
|
var errEnd1 = endBlock("err1", 1);
|
||||||
const call2 = new ProcCall("b", "init_b", [], "error", null);
|
var errEnd2 = endBlock("err2", 2);
|
||||||
const errEnd1 = new End("err1", 1);
|
var call1 = procCallBlock("a", "init_a", [], "error", null, null, [errEnd1]);
|
||||||
const errEnd2 = new End("err2", 2);
|
var call2 = procCallBlock("b", "init_b", [], "error", null, [end], [errEnd2]);
|
||||||
call1.errTargets = [errEnd1];
|
link(start, call1);
|
||||||
call2.errTargets = [errEnd2];
|
link(call1, call2);
|
||||||
start.nextFlow.push({ toElement: call1 });
|
return {
|
||||||
call1.nextFlow.push({ toElement: call2 });
|
entry: start,
|
||||||
call2.nextFlow.push({ toElement: end });
|
includes: ["#include <stdlib.h>"],
|
||||||
return { start, vars: [], hasStdlib: true, proto: ["int init_a(void);", "int init_b(void);"] };
|
declarations: "",
|
||||||
|
proto: ["int init_a(void);", "int init_b(void);"],
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildErrorValueTemplate() {
|
function buildErrorValueTemplate() {
|
||||||
const start = new Start("start");
|
var start = startBlock("start");
|
||||||
const end = new End("end", 0);
|
var end = endBlock("end", 0);
|
||||||
const call = new ProcCall("open", "open_file", ['"data.txt"', "0"], "value", "fd");
|
var errEnd = endBlock("errEnd", 1);
|
||||||
const errEnd = new End("errEnd", 1);
|
var call = procCallBlock(
|
||||||
call.errTargets = [errEnd];
|
"open",
|
||||||
start.nextFlow.push({ toElement: call });
|
"open_file",
|
||||||
call.nextFlow.push({ toElement: end });
|
['"data.txt"', "0"],
|
||||||
return { start, vars: ["int fd;"], hasStdlib: true, proto: ["int open_file(const char *path, int flags);"] };
|
"value",
|
||||||
|
"fd",
|
||||||
|
[end],
|
||||||
|
[errEnd]
|
||||||
|
);
|
||||||
|
link(start, call);
|
||||||
|
return {
|
||||||
|
entry: start,
|
||||||
|
includes: ["#include <stdlib.h>"],
|
||||||
|
declarations: " int fd;",
|
||||||
|
proto: ["int open_file(const char *path, int flags);"],
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function generateSource({ start, vars, hasStdio, hasStdlib, proto }) {
|
// ── Source generation ──────────────────────────────────────────────────
|
||||||
const includes = [];
|
|
||||||
if (hasStdio) includes.push("#include <stdio.h>");
|
function generateSource(opts) {
|
||||||
if (hasStdlib) includes.push("#include <stdlib.h>");
|
var parts = ["// --- WiseCode\u00ae ---"];
|
||||||
const body = start.generateCode(new Set());
|
for (var i = 0; i < opts.includes.length; i++) parts.push(opts.includes[i]);
|
||||||
const code = [
|
if (opts.proto) {
|
||||||
"// --- WiseCode\u00ae ---",
|
for (var i = 0; i < opts.proto.length; i++) parts.push(opts.proto[i]);
|
||||||
...includes,
|
}
|
||||||
...(proto ? proto : []),
|
parts.push("");
|
||||||
"",
|
parts.push("int main() {");
|
||||||
"int main() {",
|
if (opts.declarations) parts.push(opts.declarations);
|
||||||
...(vars ? vars.map(v => " " + v) : []),
|
parts.push(opts.entry.generateCode(new Set()));
|
||||||
body,
|
parts.push("}");
|
||||||
"}",
|
parts.push("// --- END ---");
|
||||||
"// --- END ---",
|
return parts.join("\n");
|
||||||
].join("\n");
|
|
||||||
return code;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Main ────────────────────────────────────────────────────────────────
|
// ── Main ────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
function writeSource(label, source) {
|
function writeSource(label, source) {
|
||||||
fs.mkdirSync(OUTDIR, { recursive: true });
|
fs.mkdirSync(OUTDIR, { recursive: true });
|
||||||
const file = path.join(OUTDIR, label.replace(/[^a-z0-9]/gi, "_") + ".c");
|
var file = path.join(OUTDIR, label.replace(/[^a-z0-9]/gi, "_") + ".c");
|
||||||
/* Show a short preview */
|
console.log(" GEN " + path.basename(file) + " (" + source.split("\n").length + " lines)");
|
||||||
const firstLines = source.split("\n").slice(0, 4).join("\n");
|
|
||||||
console.log(` GEN ${path.basename(file)} (${source.split("\n").length} lines)`);
|
|
||||||
fs.writeFileSync(file, source, "utf8");
|
fs.writeFileSync(file, source, "utf8");
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log("\nGenerating C compilation tests...\n");
|
console.log("\nGenerating C compilation tests...\n");
|
||||||
|
|
||||||
// Clear old files
|
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()));
|
||||||
@@ -331,7 +265,7 @@ 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) {
|
||||||
@@ -343,9 +277,6 @@ try {
|
|||||||
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");
|
||||||
// Future: generate SDL3 template C source and run `make sdl3`
|
|
||||||
// writeSource("test_sdl3", generateSDL3Source());
|
|
||||||
// execSync("make ... sdl3", ...);
|
|
||||||
console.log(" (SDL3 generation not yet implemented; skipped)\n");
|
console.log(" (SDL3 generation not yet implemented; skipped)\n");
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
console.log("\nSDL3 headers not found (skipping SDL3 compilation test).\n");
|
console.log("\nSDL3 headers not found (skipping SDL3 compilation test).\n");
|
||||||
|
|||||||
Reference in New Issue
Block a user