- 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
286 lines
8.4 KiB
JavaScript
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");
|