aboutsummaryrefslogtreecommitdiff
path: root/src/main.zig
diff options
context:
space:
mode:
authorMathias Magnusson <mathias@magnusson.space>2025-08-04 23:12:39 +0200
committerMathias Magnusson <mathias@magnusson.space>2025-08-04 23:12:39 +0200
commit392f0c4be0d4c034a4b161337f8f3c5fbf46358a (patch)
tree493cef6315b016e8ec7f9be051be7bb904e16add /src/main.zig
parent22f24043755ed320eff8a121aa1b80ede3b3a37f (diff)
downloadhuginn-392f0c4be0d4c034a4b161337f8f3c5fbf46358a.tar.gz
add a little test runnerHEADmain
Diffstat (limited to 'src/main.zig')
-rw-r--r--src/main.zig96
1 files changed, 74 insertions, 22 deletions
diff --git a/src/main.zig b/src/main.zig
index 205c452..f687fc6 100644
--- a/src/main.zig
+++ b/src/main.zig
@@ -27,7 +27,7 @@ pub fn main() !u8 {
return 1;
};
- const dir = switch (action) {
+ const out_dir = switch (action) {
.run => blk: {
const dir = try std.fmt.allocPrint(allocator, "/tmp/huginn-build-{}", .{std.crypto.random.int(u32)});
try std.fs.makeDirAbsolute(dir);
@@ -47,9 +47,9 @@ pub fn main() !u8 {
std.debug.print("Invalid input file extension. Must be `.hgn`.", .{});
return 1;
}
- break :blk p[0 .. p.len - 4];
+ break :blk std.fs.path.basename(p[0 .. p.len - 4]);
} else "a.out";
- const out_path = try std.fmt.allocPrint(allocator, "{s}/{s}", .{ dir, out_name });
+ const out_path = try std.fmt.allocPrint(allocator, "{s}/{s}", .{ out_dir, out_name });
const out_file = try std.fs.cwd().createFile(out_path, .{ .mode = 0o777 });
const source = try in_file.readToEndAlloc(
@@ -57,25 +57,7 @@ pub fn main() !u8 {
640 * 1024, // ought to be enough for anyone
);
- var lexer: Lexer = .{ .source = source };
- const ast = parse.file(allocator, &lexer) catch |err| {
- std.debug.print(red ++ "parsing error: {}\n" ++ normal, .{err});
- return 1;
- };
- std.debug.print(blue ++ "parse tree:" ++ normal ++ "\n{}\n", .{parse.fmt(ast, source, 0)});
- if (lexer.peek().type != .eof) {
- std.debug.print(red ++ "Unexpected token {}, expected end of file\n" ++ normal, .{lexer.next()});
- return 1;
- }
- const module = compile.compile(allocator, source, ast) catch |err| {
- std.debug.print(red ++ "compilation error: {}\n" ++ normal, .{err});
- return 1;
- };
- std.debug.print(blue ++ "bytecode instructions: " ++ normal ++ "\n{}", .{module});
- const elf = codegen.create_elf(allocator, module) catch |err| {
- std.debug.print(red ++ "code generation error: {}\n" ++ normal, .{err});
- return 1;
- };
+ const elf = try compileSource(allocator, source);
try out_file.writer().writeAll(elf);
if (action == .run) {
std.debug.print(blue ++ "running program:" ++ normal ++ "\n", .{});
@@ -95,6 +77,76 @@ pub fn main() !u8 {
}
}
+fn compileSource(allocator: std.mem.Allocator, source: []const u8) ![]u8 {
+ var lexer: Lexer = .{ .source = source };
+ const ast = parse.file(allocator, &lexer) catch |err| {
+ std.debug.print(red ++ "parsing error: {}\n" ++ normal, .{err});
+ return error.ParsingError;
+ };
+ // std.debug.print(blue ++ "parse tree:" ++ normal ++ "\n{}\n", .{parse.fmt(ast, source, 0)});
+ if (lexer.peek().type != .eof) {
+ std.debug.print(red ++ "Unexpected token {}, expected end of file\n" ++ normal, .{lexer.next()});
+ return error.ParsingError;
+ }
+ const module = compile.compile(allocator, source, ast) catch |err| {
+ std.debug.print(red ++ "compilation error: {}\n" ++ normal, .{err});
+ return error.CompilationError;
+ };
+ // std.debug.print(blue ++ "bytecode instructions: " ++ normal ++ "\n{}", .{module});
+ const elf = codegen.create_elf(allocator, module) catch |err| {
+ std.debug.print(red ++ "code generation error: {}\n" ++ normal, .{err});
+ return error.CodeGenError;
+ };
+ return elf;
+}
+
+test {
+ var arena: std.heap.ArenaAllocator = .init(std.testing.allocator);
+ defer arena.deinit();
+ const allocator = arena.allocator();
+
+ const test_dir = try std.fs.cwd().openDir("tests", .{ .iterate = true });
+ var it = test_dir.iterateAssumeFirstIteration();
+ var tmpdir = std.testing.tmpDir(.{});
+ try tmpdir.dir.setAsCwd();
+ defer tmpdir.cleanup();
+ while (try it.next()) |entry| {
+ if (!std.mem.endsWith(u8, entry.name, ".hgn")) continue;
+ const file = try test_dir.readFileAlloc(allocator, entry.name, 640 * 1024);
+ const basename = entry.name[0 .. entry.name.len - 4];
+ const elf = try compileSource(allocator, file);
+ try tmpdir.dir.writeFile(.{ .sub_path = basename, .data = elf, .flags = .{ .mode = 0o777 } });
+ defer tmpdir.dir.deleteFile(basename) catch {};
+
+ const inout_name = try std.fmt.allocPrint(allocator, "{s}.json", .{basename});
+ const inoutfile = test_dir.openFile(inout_name, .{}) catch |err| switch (err) {
+ error.FileNotFound => continue,
+ else => return err,
+ };
+ var json_reader = std.json.reader(allocator, inoutfile.reader());
+ const cases = try std.json.parseFromTokenSourceLeaky([]struct { stdin: []u8, stdout: []u8 }, allocator, &json_reader, .{});
+ for (cases) |case| {
+ var child: std.process.Child = .init(
+ if (target.cpu.arch == .riscv64 and target.os.tag == .linux)
+ &.{basename}
+ else
+ &.{ "qemu-riscv64", basename },
+ allocator,
+ );
+ child.stdin_behavior = .Pipe;
+ child.stdout_behavior = .Pipe;
+ child.stderr_behavior = .Ignore;
+ try child.spawn();
+ try child.stdin.?.writeAll(case.stdin);
+ const stdout = try child.stdout.?.readToEndAlloc(allocator, 640 * 1024);
+ defer allocator.free(stdout);
+ const term = try child.wait();
+ try std.testing.expectEqualStrings(case.stdout, stdout);
+ try std.testing.expectEqual(@TypeOf(term){ .Exited = 0 }, term);
+ }
+ }
+}
+
fn HashMapFormatter(HashMap: type) type {
return std.fmt.Formatter(struct {
fn formatHashMap(