commit db7cd9f02a0e7d80f5637cf6322542536fc94033
parent 807ba37e68c8ca4c4589ccf8a80b849725eb4501
Author: walther chen <walther.chen@gmail.com>
Date:   Fri, 14 Feb 2025 09:27:37 +0700

c3 opt0, turnt tests

Diffstat:
M.gitignore | 1+
MREADME.md | 22++++++++++++++++++++++
AWerkfile | 42++++++++++++++++++++++++++++++++++++++++++
Ac3/brainz.c3 | 59+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ac3/opt0.c3 | 81+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mjustfile | 7+++++--
Atests/hello-world.brainz | 1+
Atests/hello-world.out | 1+
Atests/turnt.toml | 1+
9 files changed, 213 insertions(+), 2 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -1,2 +1,3 @@ zig-* .zig-* +target diff --git a/README.md b/README.md @@ -64,3 +64,25 @@ https://discord.com/channels/605571803288698900/605572581046747136/9500329363994 >there is also aggressive dead-code elimination on comptime-chosen paths. That is to say, if the condition of an if statement/expression is known at comptime (even if the resulting expression is a runtime one), the code of the expression on false will be eliminated, and the contents of the expression of the else clause will be eliminated on true. There's probably doc for it, will link if found. + +## Porting zig to c3 + +``` +brainz lynt| ❯ c3c --version +C3 Compiler Version: 0.6.7 (Pre-release, Feb 13 2025 09:30:57) +Installed directory: /home/hwchen/c3dev/c3c/build/ +Git Hash: 27f09ca8884f852fa331c94c9d326c23e3597128 +Backends: LLVM +LLVM version: 18.1.8 +LLVM default target: x86_64-pc-linux-gnu +``` + +Currently ported just the unoptimized version, but already have some notes +. + +- using turnt for snapshot testing, this is already simpler for setting up tests (you don't have to set up writers and readers in unit test framework, or worry about setting up stubs to try to get almost-e2e-testing), but it's also better because you can use the same test suit across different implementations easily. +- `anytype` in Zig is really unhelpful, it requires more descriptive arg names to have any idea what kind of type it is at all. +- I guess I do find `const` and `var` to be line-noise. I'm uncertain how important having these are, but definitely doesn't matter for small hobby projects. +- Zig tendency to chain is actually pretty annoying. I guess this happens in Rust too. Plus the tendency to use a full module prefix, starting from std (probably in part because importing names is such a pain. And I remember shadowing from module names being a pain too). +- I guess that this means that recursive imports in c3 are pretty convenient, but I still have a lot of mixed feelings about them, not sure how it'd work out in a larger project. +- named params with defaults in c3 feel much better. diff --git a/Werkfile b/Werkfile @@ -0,0 +1,42 @@ +config default = "build" + +let profile = "debug" + +# Pick cflags based on the build profile +let cflags = profile | match { + "debug" => ["-O0"] + "release" => ["-O3"] + "asan-address" => ["-O1", "--sanitize=address"] + "%" => "" +} + +build "brainz-c3" { + from glob "c3/*.c3" + run "c3c compile {cflags*} <in*> -o <out>" +} + +build "testrun" { + from glob "c3*.c3" + # Compiles and runs, ok for now because running tests is fast + run "c3c compile-test --no-run <in*> -o <out>" +} + +task build { + build "brainz-c3" +} + +task test { + let test_exe = "testrun" + build test_exe + run "<test_exe>" +} + +# `werk test -Dturnt-args=-v` +# --save +# --diff +let turnt-args = [] +task turnt { + build "brainz-c3" + let test_inputs = glob "tests/*.brainz" + run "turnt {turnt-args*} <test_inputs*>" +} diff --git a/c3/brainz.c3 b/c3/brainz.c3 @@ -0,0 +1,59 @@ +module brainz; +import std::collections; +import std::io; + +const int MEMORY_SIZE = 30000; + +fn int main(String[] args) { + if (args.len < 2) { + io::eprintn("Please provide source path"); + } + String source_path = args[1]; + char[]! src = file::load_new(source_path); + if (catch err = src) { + io::eprintfn("Error reading source file"); + return 1; + } + + Program! program = parse(src); + if (catch err = program) { + io::eprintfn("Error parsing program: %s", err); + return 1; + } + + char[] memory = mem::new_array(char, 30000); + char opt_level = 0; + switch (opt_level) { + default: + if (catch err = opt0::interpret(program, memory)) { + io::eprintfn("Error running program: %s", err); + return 1; + } + } + return 0; +} + +def Program = char[]; + +fn Program! parse(char[] src, Allocator alloc = allocator::heap()) { + @pool(alloc) { + List(<char>) instructions; + instructions.temp_init(); + + foreach (c : src) { + switch (c) { + case '>': + case '<': + case '+': + case '-': + case '.': + case ',': + case '[': + case ']': + instructions.push(c); + default: {}; + } + } + return instructions.to_new_array(alloc); + }; +} diff --git a/c3/opt0.c3 b/c3/opt0.c3 @@ -0,0 +1,81 @@ +module brainz::opt0; +import std::io; + +fn void! interpret( + Program program, + char[] memory, + InStream rdr = io::stdin(), + OutStream wtr = io::stdout(), +) { + char[] instructions = program; + uint pc; + uint dataptr; + + while (pc < instructions.len) { + char instruction = instructions[pc]; + + switch (instruction) { + case '>': dataptr += 1; + case '<': dataptr -= 1; + case '+': memory[dataptr] += 1; + case '-': memory[dataptr] -= 1; + case ',': memory[dataptr] = rdr.read_byte()!; + case '.': wtr.write_byte(memory[dataptr])!; + case '[': + // jumps to next matching ']' if curr_data == 0 + if (memory[dataptr] != 0) { + break; + } + + uint bracket_nesting = 1; + uint saved_pc = pc; // used for error message only + + while (bracket_nesting != 0 && pc < instructions.len - 1) { + pc += 1; + + if (instructions[pc] == ']') { + bracket_nesting -= 1; + } else if (instructions[pc] == '[') { + bracket_nesting += 1; + } + } + + if (bracket_nesting != 0) { + io::eprintfn("unmatched '[' at pc=%d", saved_pc); + return InterpretError.UNMATCHED_LBRACKET?; + } + case ']': + // jumps to previous matching ']' if curr data != 0 + if (memory[dataptr] == 0) { + break; + } + + uint bracket_nesting = 1; + uint saved_pc = pc; // used for error message only + + while (bracket_nesting != 0 && pc > 0) { + pc -= 1; + + if (instructions[pc] == '[') { + bracket_nesting -= 1; + } else if (instructions[pc] == ']') { + bracket_nesting += 1; + } + } + + if (bracket_nesting != 0) { + io::eprintfn("unmatched ']' at pc=%d", saved_pc); + return InterpretError.UNMATCHED_RBRACKET?; + } + default: + return InterpretError.UNREACHABLE_CHAR?; + } + pc += 1; + } +} + +fault InterpretError { + UNMATCHED_LBRACKET, + UNMATCHED_RBRACKET, + UNREACHABLE_CHAR, +} diff --git a/justfile b/justfile @@ -1,3 +1,6 @@ -benchmark-factor bin: +bench-factor: + werk build -Dprofile=release && \ zig build -Doptimize=ReleaseFast && \ - hyperfine 'echo 179424691 | zig-out/bin/og fixtures/factor.bf' 'echo 179424691 | zig-out/bin/{{bin}} fixtures/factor.bf' + hyperfine \ + 'echo 179424691 | zig-out/bin/og fixtures/factor.bf' \ + 'echo 179424691 | target/brainz-c3 fixtures/factor.bf' diff --git a/tests/hello-world.brainz b/tests/hello-world.brainz @@ -0,0 +1 @@ +++++++++[>++++[>++>+++>+++>+<<<<-]>+>+>->>+[<]<-]>>.>---.+++++++..+++.>>.<-.<.+++.------.--------.>>+.>++. diff --git a/tests/hello-world.out b/tests/hello-world.out @@ -0,0 +1 @@ +Hello World! diff --git a/tests/turnt.toml b/tests/turnt.toml @@ -0,0 +1 @@ +command = "../target/brainz-c3 {filename}"