diff options
author | Mathias Magnusson <mathias@magnusson.space> | 2025-08-04 23:12:39 +0200 |
---|---|---|
committer | Mathias Magnusson <mathias@magnusson.space> | 2025-08-04 23:12:39 +0200 |
commit | 392f0c4be0d4c034a4b161337f8f3c5fbf46358a (patch) | |
tree | 493cef6315b016e8ec7f9be051be7bb904e16add /src/main.zig | |
parent | 22f24043755ed320eff8a121aa1b80ede3b3a37f (diff) | |
download | huginn-392f0c4be0d4c034a4b161337f8f3c5fbf46358a.tar.gz |
Diffstat (limited to 'src/main.zig')
-rw-r--r-- | src/main.zig | 96 |
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( |