Factor-out getInputFile

This commit is contained in:
Jack Jackson 2024-12-01 20:10:22 -08:00
parent b7e34b0dd9
commit d4cd8cc346
4 changed files with 152 additions and 2 deletions

85
NOTES.md Normal file
View File

@ -0,0 +1,85 @@
Notes, thoughts, or questions that arose as I implemented the solutions. Hopefully I am able to go back and answer these questions as I keep learning!
# Things I like
* [Continue expressions](https://zig-by-example.com/while)
# Things that I've found missing from this language
I mean, Jesus Christ, right now it seems even worse than GoLang.
* [String concatenation](https://old.reddit.com/r/Zig/comments/bfcsul/concatenating_zig_strings/)
* [String equality](https://nofmal.github.io/zig-with-example/string-handling/#string-equal)
# Questions
## (From Ziglings)
[Problem 40](https://codeberg.org/ziglings/exercises/src/commit/8da60edb82b25ac913033b2f0edb63eea212c0d0/exercises/040_pointers2.zig) says "_You can always make a const pointer to a mutable value (var), but you cannot make a var pointer to an immutable value (const)._" - which, sure, fair enough (I mean, not really, but I'm not going to argue with it...), but that's not what's presented in the problem - the original code is:
```
const a: u8 = 12;
const b: *u8 = &a; // fix this!
```
which is a constant pointer to a _constant_ value - which shouldn't be an issue?
## What's the idiomatic way to run Zig tests?
I've tried - using references like [this](https://zig.guide/build-system/zig-build), [this](https://old.reddit.com/r/Zig/comments/y65qa6/how_to_test_every_file_in_a_simple_zig_project/), and [this](https://www.openmymind.net/Using-A-Custom-Test-Runner-In-Zig/) - to set up `build.zig` so that I could run `zig build test` and thus run every test in my files, but that didn't work:
```zig
// build.zig
const std = @import("std");
pub fn build(b: *std.Build) void {
const main_tests = b.addTest(.{ .root_source_file = b.path("main.zig") });
const build_mode = b.standardReleaseOptions();
main_tests.setBuildMode(build_mode);
const test_step = b.step("test", "Run library tests");
test_step.dependOn(&main_tests.step);
}
// main.zig
pub const one = @import("solutions/01.zig");
test {
@import("std").testing.refAllDecls(@This());
}
```
As-written, `zig build test` gives:
```
/Users/scubbo/Code/advent-of-code-2024/build.zig:19:25: error: no field or member function named 'standardReleaseOptions' in 'Build'
const build_mode = b.standardReleaseOptions();
```
With that line (and the following one deleted), `zig build test` completes silently, even with a failing test.
And this setup _still_ isn't great, because it's necessary to manually import every file to `main.zig`'s imports.
Hence the `test.sh` workaround script. It's not great, because it will error-out on the first failure (rather than accumulating failures from all files) - but it does the job!
Refer to [here](https://ziglang.org/documentation/master/#Zig-Test) for more info - which I only found after writing that hacky script.
## Why can't a string-literal be passed to a function that accepts a `[]8`?
That is, why is this illegal?
```
fn doIt(string: []u8) []u8 {
return "prefix" + string;
}
const expect = @import("std").testing.expect;
test {
expect(std.mem.eql(u8, doIt("foo"), "prefixfoo"))
}
```
I can fix it by changing the type signature to accept `[]const u8`, but (I think?) that then means that I can't call the function with non-const-length strings - including strings read from files.
[This](https://stackoverflow.com/questions/72736997/how-to-pass-a-c-string-into-a-zig-function-expecting-a-zig-string) link refers to `[]const u8` as a "Zig-style string-slice", but also refers to `[*c]const u8` as a "c_string", so...:shrug:?

View File

@ -1,7 +1,8 @@
const std = @import("std"); const std = @import("std");
const print = std.debug.print; const print = std.debug.print;
const util = @import("util.zig");
pub fn main() !void { pub fn main() !u32 {
var arr1: [1000]u32 = undefined; var arr1: [1000]u32 = undefined;
var arr2: [1000]u32 = undefined; var arr2: [1000]u32 = undefined;
@ -11,7 +12,9 @@ pub fn main() !void {
defer _ = gpa.deinit(); defer _ = gpa.deinit();
const allocator = gpa.allocator(); const allocator = gpa.allocator();
const file = try std.fs.cwd().openFile("../inputs/01/real.txt", .{}); const path = try util.getInputFile("01", true);
std.debug.print("Path is {s}\n", .{path});
const file = try std.fs.cwd().openFile(path, .{});
defer file.close(); defer file.close();
// Wrap the file reader in a buffered reader. // Wrap the file reader in a buffered reader.
@ -61,6 +64,9 @@ pub fn main() !void {
} }
} }
try stdout.print("{}", .{sum}); try stdout.print("{}", .{sum});
// Only used for testing - but later we'll have this return to an actual higher-level `main` function and have
// _that_ do printing.
return sum;
} }
fn parseLineToNumbers(line: []u8) struct { first: u32, second: u32 } { fn parseLineToNumbers(line: []u8) struct { first: u32, second: u32 } {
@ -89,3 +95,12 @@ fn parseLineToNumbers(line: []u8) struct { first: u32, second: u32 } {
} }
return .{ .first = first / 10, .second = second / 10 }; return .{ .first = first / 10, .second = second / 10 };
} }
const expect = std.testing.expect;
test {
const filePath = try util.getInputFile("01", true);
const stdout = std.io.getStdOut().writer();
try stdout.print("{s}\n\n", .{filePath});
try expect(try main() == 11);
}

47
solutions/util.zig Normal file
View File

@ -0,0 +1,47 @@
// Unfortunately I have to put this in `solutions/` even though it more-properly belongs at a higher-level,
// because non-sibling imports require setting up a `build.zig` (https://old.reddit.com/r/Zig/comments/ra5qeo/import_of_file_outside_package_path/)
// and that seems awful.
const std = @import("std");
pub fn getInputFile(problemNumber: []const u8, isTestCase: bool) ![]u8 {
return concatString("inputs/", try concatString(problemNumber, try concatString("/", try concatString(if (isTestCase) "test" else "real", ".txt"))));
}
fn concatString(a: []const u8, b: []const u8) ![]u8 {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
const allocator = gpa.allocator();
// I don't recall where the below came from, but it causes a `[gpa] (err): memory address 0x10383f000 leaked:` when
// kept in, so...dropping it! :P
//
// defer {
// const deinit_status = gpa.deinit();
// //fail test; can't try in defer as defer is executed after we return
// if (deinit_status == .leak) expect(false) catch @panic("TEST FAIL");
// }
return concat(u8, allocator, a, b);
}
// https://www.openmymind.net/Zigs-memcpy-copyForwards-and-copyBackwards/
fn concat(comptime T: type, allocator: std.mem.Allocator, arr1: []const T, arr2: []const T) ![]T {
var combined = try allocator.alloc(T, arr1.len + arr2.len);
@memcpy(combined[0..arr1.len], arr1);
@memcpy(combined[arr1.len..], arr2);
return combined;
}
const expect = @import("std").testing.expect;
test {
const result = try concatString("abc", "def");
try expect(std.mem.eql(u8, result, "abcdef"));
}
test "testGetInputFile" {
const result = try getInputFile("01", true);
try expect(std.mem.eql(u8, result, "inputs/01/test.txt"));
const result1 = try getInputFile("42", false);
try expect(std.mem.eql(u8, result1, "inputs/42/real.txt"));
}

3
test.sh Executable file
View File

@ -0,0 +1,3 @@
#!/bin/bash
find . -name "*.zig" -exec sh -c "echo \"Testing {}\"; zig test {}" \;