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.
195 lines
7.2 KiB
JavaScript
195 lines
7.2 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* C compilation test runner — flat DFA code generation.
|
|
*
|
|
* Block data objects are minimal: generateOwnCode() + optional generateFlowCode().
|
|
* The shared CodeGen.emitFlat walks the graph, emitting labels + gotos.
|
|
*/
|
|
|
|
const fs = require("fs");
|
|
const path = require("path");
|
|
const { execSync } = require("child_process");
|
|
const CodeGen = require("../../codegen/index.js");
|
|
|
|
const OUTDIR = path.join(__dirname, "out");
|
|
const MKFILE = path.join(__dirname, "Makefile");
|
|
|
|
// ── Block data helpers ────────────────────────────────────────────────
|
|
|
|
function startBlock(id) {
|
|
return {
|
|
id: id,
|
|
isDecision: false,
|
|
nextFlow: [],
|
|
generateOwnCode: function () { return ""; },
|
|
};
|
|
}
|
|
|
|
function endBlock(id, exitCode) {
|
|
var code = exitCode === 0 ? " return 0;\n" : " exit(" + exitCode + ");\n";
|
|
return {
|
|
id: id,
|
|
isDecision: false,
|
|
nextFlow: [],
|
|
generateOwnCode: function () { return code; },
|
|
};
|
|
}
|
|
|
|
function procCallBlock(id, procName, inputs, returnStyle, returnValueName, okTargets, errTargets) {
|
|
var call = procName + "(" + inputs.join(", ") + ")";
|
|
var self = {
|
|
id: id,
|
|
isDecision: false,
|
|
nextFlow: (okTargets || []).concat(errTargets || []).map(function (t) { return { toElement: t }; }),
|
|
errTargets: errTargets || [],
|
|
generateOwnCode: function () {
|
|
if (returnStyle === "error" && self.errTargets.length > 0) return "";
|
|
if (returnStyle === "value" && returnValueName && self.errTargets.length > 0) {
|
|
return " " + returnValueName + " = " + call + ";\n";
|
|
}
|
|
return " " + call + ";\n";
|
|
},
|
|
generateFlowCode: function () {
|
|
if (self.errTargets.length === 0) {
|
|
return self.nextFlow.map(function (p) { return " goto " + p.toElement.id + ";\n"; }).join("");
|
|
}
|
|
var errT = self.errTargets[0];
|
|
var okT = null;
|
|
for (var i = 0; i < self.nextFlow.length; i++) {
|
|
var t = self.nextFlow[i].toElement;
|
|
if (self.errTargets.indexOf(t) === -1) { okT = t; break; }
|
|
}
|
|
var out = "";
|
|
if (returnStyle === "error") {
|
|
out += " if (" + call + " != 0) goto " + errT.id + ";\n";
|
|
} else if (returnStyle === "value" && returnValueName) {
|
|
out += " if (" + returnValueName + " != 0) goto " + errT.id + ";\n";
|
|
}
|
|
if (okT) out += " goto " + okT.id + ";\n";
|
|
return out;
|
|
},
|
|
};
|
|
return self;
|
|
}
|
|
|
|
function assignBlock(id, target, value) {
|
|
return {
|
|
id: id,
|
|
isDecision: false,
|
|
nextFlow: [],
|
|
generateOwnCode: function () { return " " + target + " = " + value + ";\n"; },
|
|
};
|
|
}
|
|
|
|
function link(from, to) {
|
|
from.nextFlow.push({ toElement: to });
|
|
}
|
|
|
|
// ── Template builders ──────────────────────────────────────────────────
|
|
|
|
function buildConsoleTemplate() {
|
|
var start = startBlock("start");
|
|
var end = endBlock("end", 0);
|
|
var errEnd = endBlock("errEnd", 1);
|
|
var printf = procCallBlock("printf", "printf", ['"Hello, World!\\n"'], "value", "result", [end], [errEnd]);
|
|
link(start, printf);
|
|
return { entry: start, includes: ["#include <stdio.h>", "#include <stdlib.h>"], declarations: " int result;" };
|
|
}
|
|
|
|
function buildErrorSimpleTemplate() {
|
|
var start = startBlock("start");
|
|
var end = endBlock("end", 0);
|
|
var errEnd = endBlock("errEnd", 1);
|
|
var call = procCallBlock("init", "func", [], "error", null, [end], [errEnd]);
|
|
link(start, call);
|
|
return { entry: start, includes: ["#include <stdlib.h>"], declarations: "", proto: ["int func(void);"] };
|
|
}
|
|
|
|
function buildErrorMergeTemplate() {
|
|
var start = startBlock("start");
|
|
var end = endBlock("end", 0);
|
|
var logged = assignBlock("log", "logged", "1");
|
|
var done = assignBlock("done", "done", "1");
|
|
var call = procCallBlock("init", "func", [], "error", null, [done], [logged]);
|
|
link(logged, done);
|
|
link(done, end);
|
|
link(start, call);
|
|
return { entry: start, includes: ["#include <stdlib.h>"], declarations: " int logged;\n int done;", proto: ["int func(void);"] };
|
|
}
|
|
|
|
function errorSequentialTemplate() {
|
|
var start = startBlock("start");
|
|
var end = endBlock("end", 0);
|
|
var errEnd1 = endBlock("err1", 1);
|
|
var errEnd2 = endBlock("err2", 2);
|
|
var call1 = procCallBlock("a", "init_a", [], "error", null, null, [errEnd1]);
|
|
var call2 = procCallBlock("b", "init_b", [], "error", null, [end], [errEnd2]);
|
|
link(start, call1);
|
|
link(call1, call2);
|
|
return { entry: start, includes: ["#include <stdlib.h>"], declarations: "", proto: ["int init_a(void);", "int init_b(void);"] };
|
|
}
|
|
|
|
function buildErrorValueTemplate() {
|
|
var start = startBlock("start");
|
|
var end = endBlock("end", 0);
|
|
var errEnd = endBlock("errEnd", 1);
|
|
var call = procCallBlock("open", "open_file", ['"data.txt"', "0"], "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);"] };
|
|
}
|
|
|
|
// ── Source generation ──────────────────────────────────────────────────
|
|
|
|
function generateSource(opts) {
|
|
var parts = ["// --- WiseCode\u00ae ---"];
|
|
for (var i = 0; i < opts.includes.length; i++) parts.push(opts.includes[i]);
|
|
if (opts.proto) {
|
|
for (var i = 0; i < opts.proto.length; i++) parts.push(opts.proto[i]);
|
|
}
|
|
parts.push("");
|
|
parts.push("int main() {");
|
|
if (opts.declarations) parts.push(opts.declarations);
|
|
parts.push(CodeGen.emitFlat(opts.entry));
|
|
parts.push("}");
|
|
parts.push("// --- END ---");
|
|
return parts.join("\n");
|
|
}
|
|
|
|
// ── Main ────────────────────────────────────────────────────────────────
|
|
|
|
function writeSource(label, source) {
|
|
fs.mkdirSync(OUTDIR, { recursive: true });
|
|
var file = path.join(OUTDIR, label.replace(/[^a-z0-9]/gi, "_") + ".c");
|
|
console.log(" GEN " + path.basename(file) + " (" + source.split("\n").length + " lines)");
|
|
fs.writeFileSync(file, source, "utf8");
|
|
}
|
|
|
|
console.log("\nGenerating C compilation tests...\n");
|
|
|
|
try { execSync("rm -f " + OUTDIR + "/*.c", { stdio: "pipe" }); } catch (_) {}
|
|
|
|
writeSource("test_console", generateSource(buildConsoleTemplate()));
|
|
writeSource("test_error_simple", generateSource(buildErrorSimpleTemplate()));
|
|
writeSource("test_error_merge", generateSource(buildErrorMergeTemplate()));
|
|
writeSource("test_error_sequential", generateSource(errorSequentialTemplate()));
|
|
writeSource("test_error_value", generateSource(buildErrorValueTemplate()));
|
|
|
|
console.log("\nRunning make...\n");
|
|
|
|
try {
|
|
execSync('make -C "' + OUTDIR + '" -f "' + MKFILE + '" OUTDIR="' + OUTDIR + '" all 2>&1', { stdio: "inherit" });
|
|
} catch (e) {
|
|
console.error("\n\u2717 Compilation failed.\n");
|
|
process.exit(1);
|
|
}
|
|
|
|
try {
|
|
execSync("pkg-config --exists sdl3", { stdio: "pipe" });
|
|
console.log("\nSDL3 headers found, generating SDL3 test...\n");
|
|
console.log(" (SDL3 generation not yet implemented; skipped)\n");
|
|
} catch (_) {
|
|
console.log("\nSDL3 headers not found (skipping SDL3 compilation test).\n");
|
|
}
|
|
|
|
console.log("\u2713 All compilation tests passed.\n");
|