From 392f0c4be0d4c034a4b161337f8f3c5fbf46358a Mon Sep 17 00:00:00 2001 From: Mathias Magnusson Date: Mon, 4 Aug 2025 23:12:39 +0200 Subject: add a little test runner --- fibonacci.hgn | 24 ------------- rec_fib.hgn | 11 ------ recurse.hgn | 11 ------ src/codegen.zig | 3 +- src/compile.zig | 8 ++--- src/main.zig | 96 ++++++++++++++++++++++++++++++++++++++++------------ src/parse.zig | 3 +- ternary.hgn | 4 --- tests/fibonacci.hgn | 23 +++++++++++++ tests/fibonacci.json | 7 ++++ tests/rec_fib.hgn | 11 ++++++ tests/rec_fib.json | 7 ++++ tests/recurse.hgn | 11 ++++++ tests/recurse.json | 5 +++ tests/ternary.hgn | 4 +++ tests/ternary.json | 6 ++++ tests/triangle.hgn | 10 ++++++ tests/triangle.json | 4 +++ triangle.hgn | 10 ------ 19 files changed, 168 insertions(+), 90 deletions(-) delete mode 100644 fibonacci.hgn delete mode 100644 rec_fib.hgn delete mode 100644 recurse.hgn delete mode 100644 ternary.hgn create mode 100644 tests/fibonacci.hgn create mode 100644 tests/fibonacci.json create mode 100644 tests/rec_fib.hgn create mode 100644 tests/rec_fib.json create mode 100644 tests/recurse.hgn create mode 100644 tests/recurse.json create mode 100644 tests/ternary.hgn create mode 100644 tests/ternary.json create mode 100644 tests/triangle.hgn create mode 100644 tests/triangle.json delete mode 100644 triangle.hgn diff --git a/fibonacci.hgn b/fibonacci.hgn deleted file mode 100644 index 3bf49b1..0000000 --- a/fibonacci.hgn +++ /dev/null @@ -1,24 +0,0 @@ -main := proc() { - n := read_int(0) - fib(n) - exit(0) -} - -fib := proc(n) { - a := 0 - b := 1 - i := 0 - while i < n { - p(a) - c := a + b - a = b - b = c - i = i + 1 - } - print(a) -} - -p := proc(x) { - x2 := x - print(x2) -} diff --git a/rec_fib.hgn b/rec_fib.hgn deleted file mode 100644 index cc09993..0000000 --- a/rec_fib.hgn +++ /dev/null @@ -1,11 +0,0 @@ -main := proc() { - n := 10 # read_int(0) - f := fib(n) - print(f) - exit(0) -} - -fib := proc(n) { - if n < 2 { return n } - return fib(n - 2) + fib(n - 1) -} diff --git a/recurse.hgn b/recurse.hgn deleted file mode 100644 index 52b7da4..0000000 --- a/recurse.hgn +++ /dev/null @@ -1,11 +0,0 @@ -main := proc() { - print_up_to(10) - exit(0) -} - -print_up_to := proc(n) { - print(n) - if n > 0 { - print_up_to(n - 1) - } -} diff --git a/src/codegen.zig b/src/codegen.zig index 144e58f..6efb92d 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -1,8 +1,7 @@ const std = @import("std"); const elf = std.elf; const Allocator = std.mem.Allocator; -const root = @import("root"); -const compile = root.compile; +const compile = @import("compile.zig"); const Register = enum(u5) { // zig fmt: off diff --git a/src/compile.zig b/src/compile.zig index 69ac270..1b890b6 100644 --- a/src/compile.zig +++ b/src/compile.zig @@ -1,9 +1,9 @@ const std = @import("std"); const Allocator = std.mem.Allocator; -const root = @import("root"); -const Token = root.Lexer.Token; -const parse = root.parse; -const Location = root.Lexer.Location; +const Lexer = @import("Lexer.zig"); +const Token = Lexer.Token; +const parse = @import("parse.zig"); +const Location = Lexer.Location; const log = std.log.scoped(.compile); 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( diff --git a/src/parse.zig b/src/parse.zig index 4adeffb..47356e8 100644 --- a/src/parse.zig +++ b/src/parse.zig @@ -1,8 +1,7 @@ const std = @import("std"); const Allocator = std.mem.Allocator; -const root = @import("root"); -const Lexer = root.Lexer; +const Lexer = @import("Lexer.zig"); const Token = Lexer.Token; const Location = Lexer.Location; diff --git a/ternary.hgn b/ternary.hgn deleted file mode 100644 index 3740fd8..0000000 --- a/ternary.hgn +++ /dev/null @@ -1,4 +0,0 @@ -main := proc() { - print(if read_int(0) 1 else 2) - if 1 exit(0) -} diff --git a/tests/fibonacci.hgn b/tests/fibonacci.hgn new file mode 100644 index 0000000..f2ea776 --- /dev/null +++ b/tests/fibonacci.hgn @@ -0,0 +1,23 @@ +main := proc() { + n := read_int(0) + fib(n) + exit(0) +} + +fib := proc(n) { + a := 0 + b := 1 + i := 0 + while i < n { + c := a + b + a = b + b = c + i = i + 1 + } + p(a) +} + +p := proc(x) { + x2 := x + print(x2) +} diff --git a/tests/fibonacci.json b/tests/fibonacci.json new file mode 100644 index 0000000..1be12db --- /dev/null +++ b/tests/fibonacci.json @@ -0,0 +1,7 @@ +[ + { "stdin": "0\n", "stdout": "0\n" }, + { "stdin": "1\n", "stdout": "1\n" }, + { "stdin": "10\n", "stdout": "55\n" }, + { "stdin": "20\n", "stdout": "6765\n" }, + { "stdin": "90\n", "stdout": "2880067194370816120\n" } +] diff --git a/tests/rec_fib.hgn b/tests/rec_fib.hgn new file mode 100644 index 0000000..9cbeed8 --- /dev/null +++ b/tests/rec_fib.hgn @@ -0,0 +1,11 @@ +main := proc() { + n := read_int(0) + f := fib(n) + print(f) + exit(0) +} + +fib := proc(n) { + if n < 2 { return n } + return fib(n - 2) + fib(n - 1) +} diff --git a/tests/rec_fib.json b/tests/rec_fib.json new file mode 100644 index 0000000..d7b90c6 --- /dev/null +++ b/tests/rec_fib.json @@ -0,0 +1,7 @@ +[ + { "stdin": "0\n", "stdout": "0\n" }, + { "stdin": "1\n", "stdout": "1\n" }, + { "stdin": "10\n", "stdout": "55\n" }, + { "stdin": "20\n", "stdout": "6765\n" }, + { "stdin": "30\n", "stdout": "832040\n" } +] diff --git a/tests/recurse.hgn b/tests/recurse.hgn new file mode 100644 index 0000000..bf9a0fd --- /dev/null +++ b/tests/recurse.hgn @@ -0,0 +1,11 @@ +main := proc() { + print_up_to(read_int(0)) + exit(0) +} + +print_up_to := proc(n) { + print(n) + if n > 0 { + print_up_to(n - 1) + } +} diff --git a/tests/recurse.json b/tests/recurse.json new file mode 100644 index 0000000..ac3f3e6 --- /dev/null +++ b/tests/recurse.json @@ -0,0 +1,5 @@ +[ + { "stdin": "0\n", "stdout": "0\n" }, + { "stdin": "1\n", "stdout": "1\n0\n" }, + { "stdin": "10\n", "stdout": "10\n9\n8\n7\n6\n5\n4\n3\n2\n1\n0\n" } +] diff --git a/tests/ternary.hgn b/tests/ternary.hgn new file mode 100644 index 0000000..3740fd8 --- /dev/null +++ b/tests/ternary.hgn @@ -0,0 +1,4 @@ +main := proc() { + print(if read_int(0) 1 else 2) + if 1 exit(0) +} diff --git a/tests/ternary.json b/tests/ternary.json new file mode 100644 index 0000000..ec1213c --- /dev/null +++ b/tests/ternary.json @@ -0,0 +1,6 @@ +[ + { "stdin": "0\n", "stdout": "2\n" }, + { "stdin": "1\n", "stdout": "1\n" }, + { "stdin": "2\n", "stdout": "1\n" }, + { "stdin": "3\n", "stdout": "1\n" } +] diff --git a/tests/triangle.hgn b/tests/triangle.hgn new file mode 100644 index 0000000..5459c12 --- /dev/null +++ b/tests/triangle.hgn @@ -0,0 +1,10 @@ +main := proc() { + n := read_int(0) + print(triangle(n)) + exit(0) +} + +triangle := proc(n) { + if n == 0 { return 0 } + return triangle(n - 1) + n +} diff --git a/tests/triangle.json b/tests/triangle.json new file mode 100644 index 0000000..ff1246c --- /dev/null +++ b/tests/triangle.json @@ -0,0 +1,4 @@ +[ + { "stdin": "10\n", "stdout": "55\n" }, + { "stdin": "20\n", "stdout": "210\n" } +] diff --git a/triangle.hgn b/triangle.hgn deleted file mode 100644 index 5459c12..0000000 --- a/triangle.hgn +++ /dev/null @@ -1,10 +0,0 @@ -main := proc() { - n := read_int(0) - print(triangle(n)) - exit(0) -} - -triangle := proc(n) { - if n == 0 { return 0 } - return triangle(n - 1) + n -} -- cgit v1.2.3