Files
avcf/tests/compile/run.js
T
adrien f1eed2d4e0 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
2026-06-23 01:07:08 +02:00

286 lines
8.4 KiB
JavaScript

#!/usr/bin/env node
/**
* C compilation test runner.
*
* Generates C source files matching each template's expected output,
* then runs `make` to compile them with `gcc -Wall -Wextra -fsyntax-only`.
*
* Block data objects are created manually (matching the browser template
* 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 path = require("path");
const { execSync } = require("child_process");
const CodeGen = require("../../codegen/index.js");
const OUTDIR = "/tmp/wisecode-compile";
const MKFILE = path.join(__dirname, "Makefile");
// ── Block data helpers ────────────────────────────────────────────────
// Plain objects matching the interface CodeGen expects:
// { generateOwnCode(), generateCode(visited), nextFlow, isDecision }
function startBlock(id) {
return {
id: id,
isDecision: false,
nextFlow: [],
generateOwnCode: function () {
return "";
},
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;
},
};
}
function endBlock(id, exitCode) {
var code = exitCode === 0 ? "\n return 0;\n" : "\n exit(" + exitCode + ");\n";
return {
id: id,
isDecision: false,
nextFlow: [],
generateOwnCode: function () {
return code;
},
generateCode: function (visited) {
if (visited.has(this)) return "";
visited.add(this);
return this.generateOwnCode();
},
};
}
function wrap(targets) {
return (targets || []).map(function (t) {
return { toElement: t };
});
}
function procCallBlock(id, procName, inputs, returnStyle, returnValueName, okTargets, errTargets) {
var call = procName + "(" + inputs.join(", ") + ")";
var self = {
id: id,
isDecision: false,
nextFlow: wrap(okTargets),
errTargets: errTargets || [],
generateOwnCode: function () {
if (self.errTargets.length === 0) return "\n " + call + ";\n";
var ifHeader;
if (returnStyle === "value" && returnValueName) {
ifHeader = " if ((" + returnValueName + " = " + call + ") != 0)";
} else {
ifHeader = " if (" + call + " != 0)";
}
var okTargets = self.nextFlow.map(function (p) {
return p.toElement;
});
return CodeGen.generateCallWithError(
"\n" + ifHeader,
self.errTargets,
okTargets,
function (b) {
return b.isDecision;
}
);
},
generateCode: function (visited) {
if (visited.has(this)) return "";
visited.add(this);
var own = this.generateOwnCode();
if (self.errTargets.length > 0) return own;
for (var i = 0; i < this.nextFlow.length; i++) {
own += this.nextFlow[i].toElement.generateCode(visited);
}
return own;
},
};
return self;
}
function assignBlock(id, target, value) {
var code = "\n " + target + " = " + value + ";\n";
return {
id: id,
isDecision: false,
nextFlow: [],
generateOwnCode: function () {
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;
},
};
}
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(opts.entry.generateCode(new Set()));
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 SDL3
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");