diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json deleted file mode 100644 index beea353..0000000 --- a/.devcontainer/devcontainer.json +++ /dev/null @@ -1,31 +0,0 @@ -// For format details, see https://aka.ms/devcontainer.json. For config options, see the -// README at: https://github.com/devcontainers/templates/tree/main/src/debian -{ - "name": "Ziglings", - // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile - "image": "mcr.microsoft.com/devcontainers/base:bullseye", - "features": { - "ghcr.io/devcontainers-contrib/features/zig:1": { - "version": "master" - } - }, - "customizations": { - "vscode": { - "extensions": [ - "ziglang.vscode-zig" - ] - } - } - - // Features to add to the dev container. More info: https://containers.dev/features. - // "features": {}, - - // Use 'forwardPorts' to make a list of ports inside the container available locally. - // "forwardPorts": [], - - // Configure tool-specific properties. - // "customizations": {}, - - // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. - // "remoteUser": "root" -} diff --git a/.gitea/issue_template.md b/.gitea/issue_template.md new file mode 100644 index 0000000..4af39e3 --- /dev/null +++ b/.gitea/issue_template.md @@ -0,0 +1,5 @@ +Ziglings is a progressive learning series — each exercise builds on previous ones. +Before opening an issue, please ensure you've followed the path and read the instructions carefully. + +Respectful and constructive feedback is always welcome. + diff --git a/.gitignore b/.gitignore index 53a6184..0fa4230 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ /answers/ /patches/healed/ /output/ +.progress.txt # Leave this in here for older zig versions /zig-cache/ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index bb43f67..8d9d6b9 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -40,6 +40,7 @@ fit for one reason or another. ## Platforms and Zig Versions + Because it uses the Zig build system, Ziglings should work wherever Zig does. @@ -75,6 +76,15 @@ interface. Specifically: eternal Ziglings contributor glory is yours! +## Licence + +If you submit your contribution to the repository/project, +you agree that your contribution will be licensed under +the license of this repository/this project. +Please note, it does not change your rights to use your own +contribution for any other purpose. + + ## The Secrets If you want to peek at the secrets, take a look at the `patches/` diff --git a/LICENSE b/LICENSE index bc944dc..56a7408 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2021 Dave Gauer +Copyright (c) 2021 Dave Gauer, Chris Boesch Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 776bafb..fd45a06 100644 --- a/README.md +++ b/README.md @@ -10,11 +10,10 @@ Those broken programs need your help! (You'll also save the planet from evil aliens and help some friendly elephants stick together, which is very sweet of you.) -This project was directly inspired by the brilliant and fun -[rustlings](https://github.com/rust-lang/rustlings) -project for the [Rust](https://www.rust-lang.org/) language. -Indirect inspiration comes from [Ruby Koans](http://rubykoans.com/) -and the Little LISPer/Little Schemer series of books. +This project was initiated by [Dave Gauer](https://ratfactor.com/) and is directly inspired +by the brilliant and fun [rustlings](https://github.com/rust-lang/rustlings) project. +Indirect inspiration comes from [Ruby Koans](http://rubykoans.com/) and the Little LISPer/Little +Schemer series of books. ## Intended Audience @@ -26,10 +25,9 @@ language such as C. Each exercise is self-contained and self-explained. However, you're encouraged to also check out these Zig language resources -for more detail: +for more details: * https://ziglang.org/learn/ -* https://ziglearn.org/ * https://ziglang.org/documentation/master/ * [Zig in Depth! (video series)](https://www.youtube.com/watch?v=MMtvGA1YhW4&list=PLtB7CL7EG7pCw7Xy1SQC53Gl8pI7aDg9t&pp=iAQB) @@ -46,20 +44,20 @@ Verify the installation and build number of `zig` like so: ``` $ zig version -0.14.0-dev.xxxx+xxxxxxxxx +0.16.0-dev.xxxx+xxxxxxxxx ``` Clone this repository with Git: ``` -$ git clone https://ziglings.org -$ cd ziglings.org +git clone https://codeberg.org/ziglings/exercises.git ziglings +cd ziglings ``` Then run `zig build` and follow the instructions to begin! ``` -$ zig build +zig build ``` Note: The output of Ziglings is the unaltered output from the Zig @@ -69,13 +67,13 @@ reading these. ## A Note About Versions **Hint:** To check out Ziglings for a stable release of Zig, you can use -the appropriate tag. +the appropriate tag. The Zig language is under very active development. In order to be current, Ziglings tracks **development** builds of the Zig compiler rather than versioned **release** builds. The last -stable release was `0.13.0`, but Ziglings needs a dev build with -pre-release version "0.14.0" and a build number at least as high +stable release was `0.15.1`, but Ziglings needs a dev build with +pre-release version "0.16.0" and a build number at least as high as that shown in the example version check above. It is likely that you'll download a build which is _greater_ than @@ -88,7 +86,13 @@ that if you update one, you may need to also update the other. ### Version Changes -Version-0.14.0-dev.42 +* *2025-08-15* zig 0.15.0-dev.1519 - changes in array list, see [#24801](https://github.com/ziglang/zig/pull/24801) +* *2025-08-08* zig 0.15.0-dev.1380 - changes in build system, see [#24588](https://github.com/ziglang/zig/pull/24588) +* *2025-07-22* zig 0.15.0-dev.1092 - various changes due to new I/O API, see [#24488](https://github.com/ziglang/zig/pull/24488) +* *2024-09-16* zig 0.14.0-dev.1573 - introduction of labeled switch, see [#21257](https://github.com/ziglang/zig/pull/21257) +* *2024-09-02* zig 0.14.0-dev.1409 - several changes in std.builtin, see [#21225](https://github.com/ziglang/zig/pull/21225) +* *2024-08-04* zig 0.14.0-dev.1224 - several changes in build system, see [#21115](https://github.com/ziglang/zig/pull/21115) +* *2024-08-04* zig 0.14.0-dev.839 - several changes in build system, see [#20580](https://github.com/ziglang/zig/pull/20580), [#20600](https://github.com/ziglang/zig/issues/20600) * *2024-06-17* zig 0.14.0-dev.42 - changes in `std.mem.split and tokenize` - see [#15579](https://github.com/ziglang/zig/pull/15579) * *2024-05-29* zig 0.13.0-dev.339 - rework std.Progress - see [#20059](https://github.com/ziglang/zig/pull/20059) * *2024-03-21* zig 0.12.0-dev.3518 - change to @fieldParentPtr - see [#19470](https://github.com/ziglang/zig/pull/19470) @@ -127,6 +131,12 @@ It can be handy to check just a single exercise: zig build -Dn=19 ``` +Or run all exercises, starting from a specific one: + +``` +zig build -Ds=27 +``` + Or let Ziglings pick an exercise for you: ``` @@ -164,6 +174,11 @@ zig build -Dn=19 -l ... ``` +To reset the progress (have it run all the exercises that have already been completed): +``` +zig build -Dreset +``` + ## What's Covered The primary goal for Ziglings is to cover the core Zig language. @@ -219,7 +234,10 @@ Zig Core Language * [X] Interfaces * [X] Bit manipulation * [X] Working with C +* [ ] Opaque types (anyopaque) * [X] Threading +* [x] Labeled switch +* [x] Vector operations (SIMD) Zig Standard Library diff --git a/build.zig b/build.zig index b40f8bd..7e8bf7a 100644 --- a/build.zig +++ b/build.zig @@ -15,7 +15,7 @@ const print = std.debug.print; // 1) Getting Started // 2) Version Changes comptime { - const required_zig = "0.14.0-dev.42"; + const required_zig = "0.15.0-dev.1519"; const current_zig = builtin.zig_version; const min_zig = std.SemanticVersion.parse(required_zig) catch unreachable; if (current_zig.order(min_zig) == .lt) { @@ -120,23 +120,24 @@ pub const logo = \\ ; +const progress_filename = ".progress.txt"; + pub fn build(b: *Build) !void { if (!validate_exercises()) std.process.exit(2); use_color_escapes = false; - if (std.io.getStdErr().supportsAnsiEscapeCodes()) { + if (std.fs.File.stderr().supportsAnsiEscapeCodes()) { use_color_escapes = true; } else if (builtin.os.tag == .windows) { const w32 = struct { - const WINAPI = std.os.windows.WINAPI; const DWORD = std.os.windows.DWORD; const ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004; const STD_ERROR_HANDLE: DWORD = @bitCast(@as(i32, -12)); - extern "kernel32" fn GetStdHandle(id: DWORD) callconv(WINAPI) ?*anyopaque; - extern "kernel32" fn GetConsoleMode(console: ?*anyopaque, out_mode: *DWORD) callconv(WINAPI) u32; - extern "kernel32" fn SetConsoleMode(console: ?*anyopaque, mode: DWORD) callconv(WINAPI) u32; + const GetStdHandle = std.os.windows.kernel32.GetStdHandle; + const GetConsoleMode = std.os.windows.kernel32.GetConsoleMode; + const SetConsoleMode = std.os.windows.kernel32.SetConsoleMode; }; - const handle = w32.GetStdHandle(w32.STD_ERROR_HANDLE); + const handle = w32.GetStdHandle(w32.STD_ERROR_HANDLE).?; var mode: w32.DWORD = 0; if (w32.GetConsoleMode(handle, &mode) != 0) { mode |= w32.ENABLE_VIRTUAL_TERMINAL_PROCESSING; @@ -161,6 +162,8 @@ pub fn build(b: *Build) !void { const override_healed_path = b.option([]const u8, "healed-path", "Override healed path"); const exno: ?usize = b.option(usize, "n", "Select exercise"); const rand: ?bool = b.option(bool, "random", "Select random exercise"); + const start: ?usize = b.option(usize, "s", "Start at exercise"); + const reset: ?bool = b.option(bool, "reset", "Reset exercise progress"); const sep = std.fs.path.sep_str; const healed_path = if (override_healed_path) |path| @@ -196,7 +199,7 @@ pub fn build(b: *Build) !void { if (rand) |_| { // Random build mode: verifies one random exercise. - // like for 'exno' but chooses a random exersise number. + // like for 'exno' but chooses a random exercise number. print("work in progress: check a random exercise\n", .{}); var prng = std.Random.DefaultPrng.init(blk: { @@ -221,17 +224,81 @@ pub fn build(b: *Build) !void { return; } + if (start) |s| { + if (s == 0 or s > exercises.len - 1) { + print("unknown exercise number: {}\n", .{s}); + std.process.exit(2); + } + const first = exercises[s - 1]; + const ziglings_step = b.step("ziglings", b.fmt("Check ziglings starting with {s}", .{first.main_file})); + b.default_step = ziglings_step; + + var prev_step = &header_step.step; + for (exercises[(s - 1)..]) |ex| { + const verify_stepn = ZiglingStep.create(b, ex, work_path, .normal); + verify_stepn.step.dependOn(prev_step); + + prev_step = &verify_stepn.step; + } + ziglings_step.dependOn(prev_step); + return; + } + + if (reset) |_| { + std.fs.cwd().deleteFile(progress_filename) catch |err| { + switch (err) { + std.fs.Dir.DeleteFileError.FileNotFound => {}, + else => { + print("Unable to remove progress file, Error: {}\n", .{err}); + return err; + }, + } + }; + + print("Progress reset, {s} removed.\n", .{progress_filename}); + std.process.exit(0); + } + // Normal build mode: verifies all exercises according to the recommended // order. const ziglings_step = b.step("ziglings", "Check all ziglings"); b.default_step = ziglings_step; var prev_step = &header_step.step; - for (exercises) |ex| { - const verify_stepn = ZiglingStep.create(b, ex, work_path, .normal); - verify_stepn.step.dependOn(prev_step); - prev_step = &verify_stepn.step; + var starting_exercise: u32 = 0; + + if (std.fs.cwd().openFile(progress_filename, .{})) |progress_file| { + defer progress_file.close(); + + const progress_file_size = try progress_file.getEndPos(); + + var gpa = std.heap.GeneralPurposeAllocator(.{}){}; + defer _ = gpa.deinit(); + const allocator = gpa.allocator(); + const contents = try progress_file.readToEndAlloc(allocator, progress_file_size); + defer allocator.free(contents); + + starting_exercise = try std.fmt.parseInt(u32, contents, 10); + } else |err| { + switch (err) { + std.fs.File.OpenError.FileNotFound => { + // This is fine, may be the first time tests are run or progress have been reset + }, + else => { + print("Unable to open {s}: {}\n", .{ progress_filename, err }); + return err; + }, + } + } + + for (exercises) |ex| { + if (starting_exercise < ex.number()) { + const verify_stepn = ZiglingStep.create(b, ex, work_path, .normal); + verify_stepn.step.dependOn(prev_step); + + prev_step = &verify_stepn.step; + } } ziglings_step.dependOn(prev_step); @@ -274,7 +341,7 @@ const ZiglingStep = struct { return self; } - fn make(step: *Step, prog_node: std.Progress.Node) !void { + fn make(step: *Step, options: Step.MakeOptions) !void { // NOTE: Using exit code 2 will prevent the Zig compiler to print the message: // "error: the following build command failed with exit code 1:..." const self: *ZiglingStep = @alignCast(@fieldParentPtr("step", step)); @@ -285,7 +352,7 @@ const ZiglingStep = struct { return; } - const exe_path = self.compile(prog_node) catch { + const exe_path = self.compile(options.progress_node) catch { self.printErrors(); if (self.exercise.hint) |hint| @@ -295,7 +362,7 @@ const ZiglingStep = struct { std.process.exit(2); }; - self.run(exe_path.?, prog_node) catch { + self.run(exe_path, options.progress_node) catch { self.printErrors(); if (self.exercise.hint) |hint| @@ -382,6 +449,18 @@ const ZiglingStep = struct { , .{ red, reset, exercise_output, red, reset, output, red, reset }); } + const progress = try std.fmt.allocPrint(b.allocator, "{d}", .{self.exercise.number()}); + defer b.allocator.free(progress); + + const file = try std.fs.cwd().createFile( + progress_filename, + .{ .read = true, .truncate = true }, + ); + defer file.close(); + + try file.writeAll(progress); + try file.sync(); + print("{s}PASSED:\n{s}{s}\n\n", .{ green_text, output, reset_text }); } @@ -405,7 +484,7 @@ const ZiglingStep = struct { print("{s}PASSED{s}\n\n", .{ green_text, reset_text }); } - fn compile(self: *ZiglingStep, prog_node: std.Progress.Node) !?[]const u8 { + fn compile(self: *ZiglingStep, prog_node: std.Progress.Node) ![]const u8 { print("Compiling: {s}\n", .{self.exercise.main_file}); const b = self.step.owner; @@ -413,7 +492,7 @@ const ZiglingStep = struct { const path = join(b.allocator, &.{ self.work_path, exercise_path }) catch @panic("OOM"); - var zig_args = std.ArrayList([]const u8).init(b.allocator); + var zig_args = std.array_list.Managed([]const u8).init(b.allocator); defer zig_args.deinit(); zig_args.append(b.graph.zig_exe) catch @panic("OOM"); @@ -429,6 +508,10 @@ const ZiglingStep = struct { zig_args.append("-lc") catch @panic("OOM"); } + if (b.reference_trace) |rt| { + zig_args.append(b.fmt("-freference-trace={}", .{rt})) catch @panic("OOM"); + } + zig_args.append(b.pathFromRoot(path)) catch @panic("OOM"); zig_args.append("--cache-dir") catch @panic("OOM"); @@ -436,7 +519,21 @@ const ZiglingStep = struct { zig_args.append("--listen=-") catch @panic("OOM"); - return try self.step.evalZigProcess(zig_args.items, prog_node); + // + // NOTE: After many changes in zig build system, we need to create the cache path manually. + // See https://github.com/ziglang/zig/pull/21115 + // Maybe there is a better way (in the future). + const exe_dir = try self.step.evalZigProcess(zig_args.items, prog_node, false, null, b.allocator); + const exe_name = switch (self.exercise.kind) { + .exe => self.exercise.name(), + .@"test" => "test", + }; + const sep = std.fs.path.sep_str; + const root_path = exe_dir.?.root_dir.path.?; + const sub_path = exe_dir.?.subPathOrDot(); + const exe_path = b.fmt("{s}{s}{s}{s}{s}", .{ root_path, sep, sub_path, sep, exe_name }); + + return exe_path; } fn help(self: *ZiglingStep) void { @@ -488,17 +585,17 @@ fn resetLine() void { /// Removes trailing whitespace for each line in buf, also ensuring that there /// are no trailing LF characters at the end. pub fn trimLines(allocator: std.mem.Allocator, buf: []const u8) ![]const u8 { - var list = try std.ArrayList(u8).initCapacity(allocator, buf.len); + var list = try std.array_list.Aligned(u8, null).initCapacity(allocator, buf.len); var iter = std.mem.splitSequence(u8, buf, " \n"); while (iter.next()) |line| { // TODO: trimming CR characters is probably not necessary. const data = std.mem.trimRight(u8, line, " \r"); - try list.appendSlice(data); - try list.append('\n'); + try list.appendSlice(allocator, data); + try list.append(allocator, '\n'); } - const result = try list.toOwnedSlice(); // TODO: probably not necessary + const result = try list.toOwnedSlice(allocator); // TODO: probably not necessary // Remove the trailing LF character, that is always present in the exercise // output. @@ -525,7 +622,7 @@ const PrintStep = struct { return self; } - fn make(step: *Step, _: std.Progress.Node) !void { + fn make(step: *Step, _: Step.MakeOptions) !void { const self: *PrintStep = @alignCast(@fieldParentPtr("step", step)); print("{s}", .{self.message}); } @@ -869,7 +966,7 @@ const exercises = [_]Exercise{ }, .{ .main_file = "060_floats.zig", - .output = "Shuttle liftoff weight: 2032092kg", + .output = "Shuttle liftoff weight: 2032 metric tons", }, .{ .main_file = "061_coercions.zig", @@ -935,6 +1032,7 @@ const exercises = [_]Exercise{ .{ .main_file = "074_comptime9.zig", .output = "My llama value is 2.", + .skip = true, }, .{ .main_file = "075_quiz8.zig", @@ -969,7 +1067,7 @@ const exercises = [_]Exercise{ .{ .main_file = "082_anonymous_structs3.zig", .output = - \\"0"(bool):true "1"(bool):false "2"(i32):42 "3"(f32):3.141592e0 + \\"0"(bool):true "1"(bool):false "2"(i32):42 "3"(f32):3.141592 , .hint = "This one is a challenge! But you have everything you need.", }, @@ -1051,7 +1149,7 @@ const exercises = [_]Exercise{ }, .{ .main_file = "097_bit_manipulation.zig", - .output = "x = 0; y = 1", + .output = "x = 1011; y = 1101", }, .{ .main_file = "098_bit_manipulation2.zig", @@ -1162,6 +1260,48 @@ const exercises = [_]Exercise{ \\Successfully Read 18 bytes: It's zigling time! , }, + .{ + .main_file = "108_labeled_switch.zig", + .output = "The pull request has been merged.", + }, + .{ + .main_file = "109_vectors.zig", + .output = + \\Max difference (old fn): 0.014 + \\Max difference (new fn): 0.014 + , + }, + .{ .main_file = "110_quiz9.zig", .output = + \\Toggle pins with XOR on PORTB + \\----------------------------- + \\ 1100 // (initial state of PORTB) + \\^ 0101 // (bitmask) + \\= 1001 + \\ + \\ 1100 // (initial state of PORTB) + \\^ 0011 // (bitmask) + \\= 1111 + \\ + \\Set pins with OR on PORTB + \\------------------------- + \\ 1001 // (initial state of PORTB) + \\| 0100 // (bitmask) + \\= 1101 + \\ + \\ 1001 // (reset state) + \\| 0100 // (bitmask) + \\= 1101 + \\ + \\Clear pins with AND and NOT on PORTB + \\------------------------------------ + \\ 1110 // (initial state of PORTB) + \\& 1011 // (bitmask) + \\= 1010 + \\ + \\ 0111 // (reset state) + \\& 1110 // (bitmask) + \\= 0110 + }, .{ .main_file = "999_the_end.zig", .output = diff --git a/exercises/019_functions2.zig b/exercises/019_functions2.zig index a527ae2..5a76c7b 100644 --- a/exercises/019_functions2.zig +++ b/exercises/019_functions2.zig @@ -3,7 +3,7 @@ // example that takes two parameters. As you can see, parameters // are declared just like any other types ("name": "type"): // -// fn myFunction(number: u8, is_lucky: bool) { +// fn myFunction(number: u8, is_lucky: bool) void { // ... // } // diff --git a/exercises/026_hello2.zig b/exercises/026_hello2.zig index cd59b86..582dba9 100644 --- a/exercises/026_hello2.zig +++ b/exercises/026_hello2.zig @@ -16,12 +16,12 @@ const std = @import("std"); // pub fn main() !void { // We get a Writer for Standard Out so we can print() to it. - const stdout = std.io.getStdOut().writer(); + var stdout = std.fs.File.stdout().writer(&.{}); // Unlike std.debug.print(), the Standard Out writer can fail // with an error. We don't care _what_ the error is, we want // to be able to pass it up as a return value of main(). // // We just learned of a single statement which can accomplish this. - stdout.print("Hello world!\n", .{}); + stdout.interface.print("Hello world!\n", .{}); } diff --git a/exercises/033_iferror.zig b/exercises/033_iferror.zig index 6ba0c61..12da2d2 100644 --- a/exercises/033_iferror.zig +++ b/exercises/033_iferror.zig @@ -17,7 +17,7 @@ // // if (foo) |value| { // ... -// } else |err| switch(err) { +// } else |err| switch (err) { // ... // } // diff --git a/exercises/034_quiz4.zig b/exercises/034_quiz4.zig index 2d843f2..8704397 100644 --- a/exercises/034_quiz4.zig +++ b/exercises/034_quiz4.zig @@ -10,11 +10,11 @@ const std = @import("std"); const NumError = error{IllegalNumber}; pub fn main() void { - const stdout = std.io.getStdOut().writer(); + var stdout = std.fs.File.stdout().writer(&.{}); const my_num: u32 = getNumber(); - try stdout.print("my_num={}\n", .{my_num}); + try stdout.interface.print("my_num={}\n", .{my_num}); } // This function is obviously weird and non-functional. But you will not be changing it for this quiz. diff --git a/exercises/046_optionals2.zig b/exercises/046_optionals2.zig index a5436d9..b5fffbb 100644 --- a/exercises/046_optionals2.zig +++ b/exercises/046_optionals2.zig @@ -48,7 +48,7 @@ pub fn main() void { // If e1 and e2 are valid pointers to elephants, // this function links the elephants so that e1's tail "points" to e2. fn linkElephants(e1: ?*Elephant, e2: ?*Elephant) void { - e1.?.*.tail = e2.?; + e1.?.tail = e2.?; } // This function visits all elephants once, starting with the diff --git a/exercises/050_no_value.zig b/exercises/050_no_value.zig index 8c73ed3..f5365cf 100644 --- a/exercises/050_no_value.zig +++ b/exercises/050_no_value.zig @@ -43,7 +43,7 @@ // // "void" is a _type_, not a value. It is the most popular of the // Zero Bit Types (those types which take up absolutely no space -// and have only a semantic value. When compiled to executable +// and have only a semantic value). When compiled to executable // code, zero bit types generate no code at all. The above example // shows a variable foo of type void which is assigned the value // of an empty expression. It's much more common to see void as diff --git a/exercises/058_quiz7.zig b/exercises/058_quiz7.zig index cf32fc3..fda83fc 100644 --- a/exercises/058_quiz7.zig +++ b/exercises/058_quiz7.zig @@ -190,7 +190,7 @@ const TripItem = union(enum) { fn printMe(self: TripItem) void { switch (self) { // Oops! The hermit forgot how to capture the union values - // in a switch statement. Please capture both values as + // in a switch statement. Please capture each value as // 'p' so the print statements work! .place => print("{s}", .{p.name}), .path => print("--{}->", .{p.dist}), diff --git a/exercises/060_floats.zig b/exercises/060_floats.zig index 6f341ad..b570518 100644 --- a/exercises/060_floats.zig +++ b/exercises/060_floats.zig @@ -41,14 +41,14 @@ pub fn main() void { // The approximate weight of the Space Shuttle upon liftoff // (including boosters and fuel tank) was 4,480,000 lb. // - // We'll convert this weight from pound to kilograms at a - // conversion of 0.453592kg to the pound. - const shuttle_weight: f16 = 0.453592 * 4480e6; + // We'll convert this weight from pounds to metric units at a + // conversion of 0.453592 kg to the pound. + const shuttle_weight: f16 = 0.453592 * 4480e3; // By default, float values are formatted in scientific // notation. Try experimenting with '{d}' and '{d:.3}' to see // how decimal formatting works. - print("Shuttle liftoff weight: {d:.0}kg\n", .{shuttle_weight}); + print("Shuttle liftoff weight: {d:.0} metric tons\n", .{shuttle_weight}); } // Floating further: diff --git a/exercises/065_builtins2.zig b/exercises/065_builtins2.zig index 283aca5..067f07c 100644 --- a/exercises/065_builtins2.zig +++ b/exercises/065_builtins2.zig @@ -94,31 +94,35 @@ pub fn main() void { print("He has room in his heart for:", .{}); // A StructFields array - const fields = @typeInfo(Narcissus).Struct.fields; + const fields = @typeInfo(Narcissus).@"struct".fields; // 'fields' is a slice of StructFields. Here's the declaration: // // pub const StructField = struct { - // name: []const u8, + // name: [:0]const u8, // type: type, - // default_value: anytype, + // default_value_ptr: ?*const anyopaque, // is_comptime: bool, // alignment: comptime_int, + // + // defaultValue() ?sf.type // Function that loads the + // // field's default value from + // // `default_value_ptr` // }; // // Please complete these 'if' statements so that the field // name will not be printed if the field is of type 'void' // (which is a zero-bit type that takes up no space at all!): if (fields[0].??? != void) { - print(" {s}", .{@typeInfo(Narcissus).Struct.fields[0].name}); + print(" {s}", .{fields[0].name}); } if (fields[1].??? != void) { - print(" {s}", .{@typeInfo(Narcissus).Struct.fields[1].name}); + print(" {s}", .{fields[1].name}); } if (fields[2].??? != void) { - print(" {s}", .{@typeInfo(Narcissus).Struct.fields[2].name}); + print(" {s}", .{fields[2].name}); } // Yuck, look at all that repeated code above! I don't know @@ -136,14 +140,16 @@ pub fn main() void { // But a change after Zig 0.10.0 added the source file name to the // type. "Narcissus" became "065_builtins2.Narcissus". // -// To fix this, I've added this function to strip the filename from -// the front of the type name in the dumbest way possible. (It returns -// a slice of the type name starting at character 14 (assuming -// single-byte characters). +// To fix this, we've added this function to strip the filename from +// the front of the type name. (It returns a slice of the type name +// starting at the index + 1 of character ".") // // We'll be seeing @typeName again in Exercise 070. For now, you can // see that it takes a Type and returns a u8 "string". fn maximumNarcissism(myType: anytype) []const u8 { - // Turn '065_builtins2.Narcissus' into 'Narcissus' - return @typeName(myType)[14..]; + const indexOf = @import("std").mem.indexOf; + + // Turn "065_builtins2.Narcissus" into "Narcissus" + const name = @typeName(myType); + return name[indexOf(u8, name, ".").? + 1 ..]; } diff --git a/exercises/071_comptime6.zig b/exercises/071_comptime6.zig index 7723291..9b3e5a2 100644 --- a/exercises/071_comptime6.zig +++ b/exercises/071_comptime6.zig @@ -7,7 +7,7 @@ // doing this work. // // An 'inline for' is performed at compile time, allowing you to -// programatically loop through a series of items in situations +// programmatically loop through a series of items in situations // like those mentioned above where a regular runtime 'for' loop // wouldn't be allowed: // @@ -38,7 +38,7 @@ pub fn main() void { // Please use an 'inline for' to implement the block below // for each field in the slice 'fields'! - const fields = @typeInfo(Narcissus).Struct.fields; + const fields = @typeInfo(Narcissus).@"struct".fields; ??? { if (field.type != void) { diff --git a/exercises/076_sentinels.zig b/exercises/076_sentinels.zig index 357bd95..accb600 100644 --- a/exercises/076_sentinels.zig +++ b/exercises/076_sentinels.zig @@ -78,7 +78,7 @@ fn printSequence(my_seq: anytype) void { // a switch to handle printing the Array or Pointer fields, // depending on which type of my_seq was passed in: switch (my_typeinfo) { - .Array => { + .array => { print("Array:", .{}); // Loop through the items in my_seq. @@ -86,7 +86,7 @@ fn printSequence(my_seq: anytype) void { print("{}", .{s}); } }, - .Pointer => { + .pointer => { // Check this out - it's pretty cool: const my_sentinel = sentinel(@TypeOf(my_seq)); print("Many-item pointer:", .{}); diff --git a/exercises/080_anonymous_structs.zig b/exercises/080_anonymous_structs.zig index 55dedd4..4e3ce84 100644 --- a/exercises/080_anonymous_structs.zig +++ b/exercises/080_anonymous_structs.zig @@ -19,12 +19,12 @@ // const MyBar = Bar(); // store the struct type // const bar = Bar() {}; // create instance of the struct // -// * The value of @typeName(Bar()) is "Bar()". -// * The value of @typeName(MyBar) is "Bar()". -// * The value of @typeName(@TypeOf(bar)) is "Bar()". +// * The value of @typeName(Bar()) is ".Bar()". +// * The value of @typeName(MyBar) is ".Bar()". +// * The value of @typeName(@TypeOf(bar)) is ".Bar()". // // You can also have completely anonymous structs. The value -// of @typeName(struct {}) is "struct:". +// of @typeName(struct {}) is ".__struct_". // const print = @import("std").debug.print; diff --git a/exercises/082_anonymous_structs3.zig b/exercises/082_anonymous_structs3.zig index 6760ff3..469cd66 100644 --- a/exercises/082_anonymous_structs3.zig +++ b/exercises/082_anonymous_structs3.zig @@ -75,11 +75,11 @@ fn printTuple(tuple: anytype) void { // with fields specific to that type. // // The list of a struct type's fields can be found in - // TypeInfo's Struct.fields. + // TypeInfo's @"struct".fields. // // Example: // - // @typeInfo(Circle).Struct.fields + // @typeInfo(Circle).@"struct".fields // // This will be an array of StructFields. const fields = ???; @@ -95,13 +95,15 @@ fn printTuple(tuple: anytype) void { // Each 'field' in this loop is one of these: // // pub const StructField = struct { - // name: []const u8, + // name: [:0]const u8, // type: type, - // default_value: anytype, + // default_value_ptr: ?*const anyopaque, // is_comptime: bool, // alignment: comptime_int, // }; // + // Note we will learn about 'anyopaque' type later + // // You'll need this builtin: // // @field(lhs: anytype, comptime field_name: []const u8) diff --git a/exercises/095_for3.zig b/exercises/095_for3.zig index e4c4662..0d4f42f 100644 --- a/exercises/095_for3.zig +++ b/exercises/095_for3.zig @@ -28,7 +28,7 @@ // 0..10 is a range from 0 to 9 // 1..4 is a range from 1 to 3 // -// At the moment, ranges are only supported in 'for' loops. +// At the moment, ranges in loops are only supported in 'for' loops. // // Perhaps you recall Exercise 13? We were printing a numeric // sequence like so: diff --git a/exercises/097_bit_manipulation.zig b/exercises/097_bit_manipulation.zig index 03fc72d..0e64ad7 100644 --- a/exercises/097_bit_manipulation.zig +++ b/exercises/097_bit_manipulation.zig @@ -71,9 +71,9 @@ const print = std.debug.print; pub fn main() !void { - // As in the example above, we use 1 and 0 as values for x and y - var x: u8 = 1; - var y: u8 = 0; + // Let us use 1101 and 1011 as values for x and y + var x: u8 = 0b1101; + var y: u8 = 0b1011; // Now we swap the values of the two variables by doing xor on them x ^= y; @@ -82,7 +82,7 @@ pub fn main() !void { // What must be written here? ???; - print("x = {d}; y = {d}\n", .{ x, y }); + print("x = {b}; y = {b}\n", .{ x, y }); } // This variable swap takes advantage of the fact that the value resulting diff --git a/exercises/098_bit_manipulation2.zig b/exercises/098_bit_manipulation2.zig index 979b103..8b51265 100644 --- a/exercises/098_bit_manipulation2.zig +++ b/exercises/098_bit_manipulation2.zig @@ -32,7 +32,7 @@ const print = std.debug.print; pub fn main() !void { // let's check the pangram - print("Is this a pangram? {?}!\n", .{isPangram("The quick brown fox jumps over the lazy dog.")}); + print("Is this a pangram? {}!\n", .{isPangram("The quick brown fox jumps over the lazy dog.")}); } fn isPangram(str: []const u8) bool { @@ -45,7 +45,7 @@ fn isPangram(str: []const u8) bool { // loop about all characters in the string for (str) |c| { // if the character is an alphabetical character - if (ascii.isASCII(c) and ascii.isAlphabetic(c)) { + if (ascii.isAscii(c) and ascii.isAlphabetic(c)) { // then we set the bit at the position // // to do this, we use a little trick: diff --git a/exercises/099_formatting.zig b/exercises/099_formatting.zig index 4b64209..ef6b84e 100644 --- a/exercises/099_formatting.zig +++ b/exercises/099_formatting.zig @@ -16,7 +16,7 @@ // Therefore, the comments for the format() function are the only // way to definitively learn how to format strings in Zig: // -// https://github.com/ziglang/zig/blob/master/lib/std/fmt.zig#L29 +// https://github.com/ziglang/zig/blob/master/lib/std/fmt.zig#L33 // // Zig already has a very nice selection of formatting options. // These can be used in different ways, but generally to convert @@ -60,7 +60,7 @@ // variety of formatting instructions. It's basically a tiny // language of its own. Here's a numeric example: // -// print("Catch-{x:0>4}.", .{twenty_two}); +// print("Catch-0x{x:0>4}.", .{twenty_two}); // // This formatting instruction outputs a hexadecimal number with // leading zeros: diff --git a/exercises/100_for4.zig b/exercises/100_for4.zig index e0fa602..c8a1161 100644 --- a/exercises/100_for4.zig +++ b/exercises/100_for4.zig @@ -41,12 +41,12 @@ pub fn main() void { for (hex_nums, ???) |hn, ???| { if (hn != dn) { - std.debug.print("Uh oh! Found a mismatch: {d} vs {d}\n", .{ hn, dn }); + print("Uh oh! Found a mismatch: {d} vs {d}\n", .{ hn, dn }); return; } } - std.debug.print("Arrays match!\n", .{}); + print("Arrays match!\n", .{}); } // // You are perhaps wondering what happens if one of the two lists diff --git a/exercises/102_testing.zig b/exercises/102_testing.zig index 89a0ee8..248f5b5 100644 --- a/exercises/102_testing.zig +++ b/exercises/102_testing.zig @@ -58,7 +58,7 @@ test "add" { // Another way to perform this test // is as follows: - try testing.expectEqual(add(41, 1), 42); + try testing.expectEqual(42, add(41, 1)); // This time a test with the addition // of a negative number: diff --git a/exercises/103_tokenization.zig b/exercises/103_tokenization.zig index eded880..6f71177 100644 --- a/exercises/103_tokenization.zig +++ b/exercises/103_tokenization.zig @@ -119,9 +119,9 @@ // after all we need some practice. Suppose we want to count the words // of this little poem: // -// My name is Ozymandias, King of Kings; -// Look on my Works, ye Mighty, and despair! -// by Percy Bysshe Shelley +// My name is Ozymandias, King of Kings; +// Look on my Works, ye Mighty, and despair! +// by Percy Bysshe Shelley // // const std = @import("std"); diff --git a/exercises/104_threading.zig b/exercises/104_threading.zig index 9c4e216..638769f 100644 --- a/exercises/104_threading.zig +++ b/exercises/104_threading.zig @@ -106,7 +106,7 @@ pub fn main() !void { // After the threads have been started, // they run in parallel and we can still do some work in between. - std.time.sleep(1500 * std.time.ns_per_ms); + std.Thread.sleep(1500 * std.time.ns_per_ms); std.debug.print("Some weird stuff, after starting the threads.\n", .{}); } // After we have left the closed area, we wait until @@ -117,12 +117,12 @@ pub fn main() !void { // This function is started with every thread that we set up. // In our example, we pass the number of the thread as a parameter. fn thread_function(num: usize) !void { - std.time.sleep(200 * num * std.time.ns_per_ms); + std.Thread.sleep(200 * num * std.time.ns_per_ms); std.debug.print("thread {d}: {s}\n", .{ num, "started." }); // This timer simulates the work of the thread. const work_time = 3 * ((5 - num % 3) - 2); - std.time.sleep(work_time * std.time.ns_per_s); + std.Thread.sleep(work_time * std.time.ns_per_s); std.debug.print("thread {d}: {s}\n", .{ num, "finished." }); } diff --git a/exercises/105_threading2.zig b/exercises/105_threading2.zig index 7ca8f5c..374391a 100644 --- a/exercises/105_threading2.zig +++ b/exercises/105_threading2.zig @@ -21,9 +21,9 @@ // There were the Scottish mathematician Gregory and the German // mathematician Leibniz, and even a few hundred years earlier the Indian // mathematician Madhava. All of them independently developed the same -// formula, which was published by Leibnitz in 1682 in the journal +// formula, which was published by Leibniz in 1682 in the journal // "Acta Eruditorum". -// This is why this method has become known as the "Leibnitz series", +// This is why this method has become known as the "Leibniz series", // although the other names are also often used today. // We will not go into the formula and its derivation in detail, but // will deal with the series straight away: @@ -39,7 +39,7 @@ // in practice. Because either you don't need the precision, or you use a // calculator in which the number is stored as a very precise constant. // But at some point this constant was calculated and we are doing the same -// now.The question at this point is, how many partial values do we have +// now. The question at this point is, how many partial values do we have // to calculate for which accuracy? // // The answer is chewing, to get 8 digits after the decimal point we need @@ -50,7 +50,7 @@ // enough for us for now, because we want to understand the principle and // nothing more, right? // -// As we have already discovered, the Leibnitz series is a series with a +// As we have already discovered, the Leibniz series is a series with a // fixed distance of 2 between the individual partial values. This makes // it easy to apply a simple loop to it, because if we start with n = 1 // (which is not necessarily useful now) we always have to add 2 in each diff --git a/exercises/106_files.zig b/exercises/106_files.zig index f5fd1ac..b224508 100644 --- a/exercises/106_files.zig +++ b/exercises/106_files.zig @@ -9,7 +9,7 @@ // by organizing them into directories, which hold files and other directories, // thus creating a tree structure that can be navigated. // -// Fortunately, the Zig standard library provides a simple API for interacting +// Fortunately, the Zig Standard Library provides a simple API for interacting // with the file system, see the detail documentation here: // // https://ziglang.org/documentation/master/std/#std.fs diff --git a/exercises/107_files2.zig b/exercises/107_files2.zig index 45e12f5..d059879 100644 --- a/exercises/107_files2.zig +++ b/exercises/107_files2.zig @@ -12,7 +12,7 @@ // Alright, bud, lean in close. Here's the game plan. // - First, we open the {project_root}/output/ directory // - Secondly, we open file `zigling.txt` in that directory -// - Then, we initalize an array of characters with all letter 'A', and print it +// - Then, we initialize an array of characters with all letter 'A', and print it // - After that, we read the content of the file into the array // - Finally, we print out the content we just read @@ -30,9 +30,9 @@ pub fn main() !void { const file = try output_dir.openFile("zigling.txt", .{}); defer file.close(); - // initalize an array of u8 with all letter 'A' + // initialize an array of u8 with all letter 'A' // we need to pick the size of the array, 64 seems like a good number - // fix the initalization below + // fix the initialization below var content = ['A']*64; // this should print out : `AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA` std.debug.print("{s}\n", .{content}); @@ -40,7 +40,7 @@ pub fn main() !void { // okay, seems like a threat of violence is not the answer in this case // can you go here to find a way to read the content? // https://ziglang.org/documentation/master/std/#std.fs.File - // hint: you might find two answers that are both vaild in this case + // hint: you might find two answers that are both valid in this case const bytes_read = zig_read_the_file_or_i_will_fight_you(&content); // Woah, too screamy. I know you're excited for zigling time but tone it down a bit. diff --git a/exercises/108_labeled_switch.zig b/exercises/108_labeled_switch.zig new file mode 100644 index 0000000..897fcf5 --- /dev/null +++ b/exercises/108_labeled_switch.zig @@ -0,0 +1,79 @@ +// +// You've heard of while loops in exercises 011,012,013 and 014 +// You've also heard of switch expressions in exercises 030 and 31. +// You've also seen how labels can be used in exercise 063. +// +// By combining while loops and switch statements with continue and break statements +// one can create very concise State Machines. +// +// One such example would be: +// +// pub fn main() void { +// var op: u8 = 1; +// while (true) { +// switch (op) { +// 1 => { op = 2; continue; }, +// 2 => { op = 3; continue; }, +// 3 => return, +// else => {}, +// } +// break; +// } +// std.debug.print("This statement cannot be reached\n", .{}); +// } +// +// By combining all we've learned so far, we can now proceed with a labeled switch. +// +// A labeled switch is some extra syntactic sugar, which comes with all sorts of +// candy (performance benefits). Don't believe me? Directly to source https://github.com/ziglang/zig/pull/21367 +// +// Here is the previous excerpt implemented as a labeled switch instead: +// +// pub fn main() void { +// foo: switch (@as(u8, 1)) { +// 1 => continue :foo 2, +// 2 => continue :foo 3, +// 3 => return, +// else => {}, +// } +// std.debug.print("This statement cannot be reached\n", .{}); +// } +// +// The flow of execution on this second case is: +// 1. The switch starts with value '1'; +// 2. The switch evaluates to case '1' which in turn uses the continue statement +// to re-evaluate the labeled switch again, now providing the value '2'; +// 3. In the case '2' we repeat the same pattern as case '1' +// but instead the value to be evaluated is now '3'; +// 4. Finally we get to case '3', where we return from the function as a whole, +// so the debug statement is never executed. +// 5. In this example, since the input does not have clear, exhaustive patterns and +// can essentially be any 'u8' integer, we need to handle all cases not explicitly +// covered by using the 'else => {}' branch as the default case. +// +// +const std = @import("std"); + +const PullRequestState = enum(u8) { + Draft, + InReview, + Approved, + Rejected, + Merged, +}; + +pub fn main() void { + // Oh no, your pull request keeps being rejected, + // how would you fix it? + pr: switch (PullRequestState.Draft) { + PullRequestState.Draft => continue :pr PullRequestState.InReview, + PullRequestState.InReview => continue :pr PullRequestState.Rejected, + PullRequestState.Approved => continue :pr PullRequestState.Merged, + PullRequestState.Rejected => { + std.debug.print("The pull request has been rejected.\n", .{}); + return; + }, + PullRequestState.Merged => break, // Would you know where to break to? + } + std.debug.print("The pull request has been merged.\n", .{}); +} diff --git a/exercises/109_vectors.zig b/exercises/109_vectors.zig new file mode 100644 index 0000000..96892ca --- /dev/null +++ b/exercises/109_vectors.zig @@ -0,0 +1,147 @@ +// So far in Ziglings, we've seen how for loops can be used to +// repeat calculations across an array in several ways. +// +// For loops are generally great for this kind of task, but +// sometimes they don't fully utilize the capabilities of the +// CPU. +// +// Most modern CPUs can execute instructions in which SEVERAL +// calculations are performed WITHIN registers at the SAME TIME. +// These are known as "single instruction, multiple data" (SIMD) +// instructions. SIMD instructions can make code significantly +// more performant. +// +// To see why, imagine we have a program in which we take the +// square root of four (changing) f32 floats. +// +// A simple compiler would take the program and produce machine code +// which calculates each square root sequentially. Most registers on +// modern CPUs have 64 bits, so we could imagine that each float moves +// into a 64-bit register, and the following happens four times: +// +// 32 bits 32 bits +// +-------------------+ +// register | 0 | x | +// +-------------------+ +// +// | +// [SQRT instruction] +// V +// +// +-------------------+ +// | 0 | sqrt(x) | +// +-------------------+ +// +// Notice that half of the register contains blank data to which +// nothing happened. What a waste! What if we were able to use +// that space instead? This is the idea at the core of SIMD. +// +// Most modern CPUs contain specialized registers with at least 128 bits +// for performing SIMD instructions. On a machine with 128-bit SIMD +// registers, a smart compiler would probably NOT issue four sqrt +// instructions as above, but instead pack the floats into a single +// 128-bit register, then execute a single "packed" sqrt +// instruction to do ALL the square root calculations at once. +// +// For example: +// +// +// 32 bits 32 bits 32 bits 32 bits +// +---------------------------------------+ +// register | 4.0 | 9.0 | 25.0 | 49.0 | +// +---------------------------------------+ +// +// | +// [SIMD SQRT instruction] +// V +// +// +---------------------------------------+ +// register | 2.0 | 3.0 | 5.0 | 7.0 | +// +---------------------------------------+ +// +// Pretty cool, right? +// +// Code with SIMD instructions is usually more performant than code +// without SIMD instructions. Zig cares a lot about performance, +// so it has built-in support for SIMD! It has a data structure that +// directly supports SIMD instructions: +// +// +-----------+ +// | Vectors | +// +-----------+ +// +// Operations performed on vectors in Zig will be done in parallel using +// SIMD instructions, whenever possible. +// +// Defining vectors in Zig is straightforwards. No library import is needed. +const v1 = @Vector(3, i32){ 1, 10, 100 }; +const v2 = @Vector(3, f32){ 2.0, 3.0, 5.0 }; + +// Vectors support the same builtin operators as their underlying base types. +const v3 = v1 + v1; // { 2, 20, 200}; +const v4 = v2 * v2; // { 4.0, 9.0, 25.0}; + +// Intrinsics that apply to base types usually extend to vectors. +const v5: @Vector(3, f32) = @floatFromInt(v3); // { 2.0, 20.0, 200.0} +const v6 = v4 - v5; // { 2.0, -11.0, -175.0} +const v7 = @abs(v6); // { 2.0, 11.0, 175.0} + +// We can make constant vectors, and reduce vectors. +const v8: @Vector(4, u8) = @splat(2); // { 2, 2, 2, 2} +const v8_sum = @reduce(.Add, v8); // 8 +const v8_min = @reduce(.Min, v8); // 2 + +// Fixed-length arrays can be automatically assigned to vectors (and vice-versa). +const single_digit_primes = [4]i8{ 2, 3, 5, 7 }; +const prime_vector: @Vector(4, i8) = single_digit_primes; + +// Now let's use vectors to simplify and optimize some code! +// +// Ewa is writing a program in which they frequently want to compare +// two lists of four f32s. Ewa expects the lists to be similar, and +// wants to determine the largest pairwise difference between the lists. +// +// Ewa wrote the following function to figure this out. + +fn calcMaxPairwiseDiffOld(list1: [4]f32, list2: [4]f32) f32 { + var max_diff: f32 = 0; + for (list1, list2) |n1, n2| { + const abs_diff = @abs(n1 - n2); + if (abs_diff > max_diff) { + max_diff = abs_diff; + } + } + return max_diff; +} + +// Ewa heard about vectors in Zig, and started writing a new vector +// version of the function, but has got stuck! +// +// Help Ewa finish the vector version! The examples above should help. + +const Vec4 = @Vector(4, f32); +fn calcMaxPairwiseDiffNew(a: Vec4, b: Vec4) f32 { + const abs_diff_vec = ???; + const max_diff = @reduce(???, abs_diff_vec); + return max_diff; +} + +// Quite the simplification! We could even write the function in one line +// and it would still be readable. +// +// Since the entire function is now expressed in terms of vector operations, +// the Zig compiler will easily be able to compile it down to machine code +// which utilizes the all-powerful SIMD instructions and does a lot of the +// computation in parallel. + +const std = @import("std"); +const print = std.debug.print; + +pub fn main() void { + const l1 = [4]f32{ 3.141, 2.718, 0.577, 1.000 }; + const l2 = [4]f32{ 3.154, 2.707, 0.591, 0.993 }; + const mpd_old = calcMaxPairwiseDiffOld(l1, l2); + const mpd_new = calcMaxPairwiseDiffNew(l1, l2); + print("Max difference (old fn): {d: >5.3}\n", .{mpd_old}); + print("Max difference (new fn): {d: >5.3}\n", .{mpd_new}); +} diff --git a/exercises/110_quiz9.zig b/exercises/110_quiz9.zig new file mode 100644 index 0000000..8f5cb61 --- /dev/null +++ b/exercises/110_quiz9.zig @@ -0,0 +1,484 @@ +// ---------------------------------------------------------------------------- +// Quiz Time: Toggling, Setting, and Clearing Bits +// ---------------------------------------------------------------------------- +// +// Another exciting thing about Zig is its suitability for embedded +// programming. Your Zig code doesn't have to remain on your laptop; you can +// also deploy your code to microcontrollers! This means you can write Zig to +// drive your next robot or greenhouse climate control system! Ready to enter +// the exciting world of embedded programming? Let's get started! +// +// ---------------------------------------------------------------------------- +// Some Background +// ---------------------------------------------------------------------------- +// +// A common activity in microcontroller programming is setting and clearing +// bits on input and output pins. This lets you control LEDs, sensors, motors +// and more! In a previous exercise (097_bit_manipulation.zig) you learned how +// to swap two bytes using the ^ (XOR - exclusive or) operator. This quiz will +// test your knowledge of bit manipulation in Zig while giving you a taste of +// what it's like to control registers in a real microcontroller. Included at +// the end are some helper functions that demonstrate how we might make our +// code a little more readable. +// +// Below is a pinout diagram for the famous ATmega328 AVR microcontroller used +// as the primary microchip on popular microcontroller platforms like the +// Arduino UNO. +// +// ============ PINOUT DIAGRAM FOR ATMEGA328 MICROCONTROLLER ============ +// _____ _____ +// | U | +// (RESET) PC6 --| 1 28 |-- PC5 +// PD0 --| 2 27 |-- PC4 +// PD1 --| 3 26 |-- PC3 +// PD2 --| 4 25 |-- PC2 +// PD3 --| 5 24 |-- PC1 +// PD4 --| 6 23 |-- PC0 +// VCC --| 7 22 |-- GND +// GND --| 8 21 |-- AREF +// |-- PB6 --| 9 20 |-- AVCC +// |-- PB7 --| 10 19 |-- PB5 --| +// | PD5 --| 11 18 |-- PB4 --| +// | PD6 --| 12 17 |-- PB3 --| +// | PD7 --| 13 16 |-- PB2 --| +// |-- PB0 --| 14 15 |-- PB1 --| +// | |___________| | +// \_______________________________/ +// | +// PORTB +// +// Drawing inspiration from this diagram, we'll use the pins for PORTB as our +// mental model for this quiz on bit manipulation. It should be noted that +// in the following problems we are using ordinary variables, one of which we +// have named PORTB, to simulate modifying the bits of real hardware registers. +// But in actual microcontroller code, PORTB would be defined something like +// this: +// pub const PORTB = @as(*volatile u8, @ptrFromInt(0x25)); +// +// This lets the compiler know not to make any optimizations to PORTB so that +// the IO pins are properly mapped to our code. +// +// NOTE : To keep things simple, the following problems are given using type +// u4, so applying the output to PORTB would only affect the lower four pins +// PB0..PB3. Of course, there is nothing to prevent you from swapping the u4 +// with a u8 so you can control all 8 of PORTB's IO pins. + +const std = @import("std"); +const print = std.debug.print; +const testing = std.testing; + +pub fn main() !void { + var PORTB: u4 = 0b0000; // only 4 bits wide for simplicity + + // ------------------------------------------------------------------------ + // Quiz + // ------------------------------------------------------------------------ + + // See if you can solve the following problems. The last two problems throw + // you a bit of a curve ball. Try solving them on your own. If you need + // help, scroll to the bottom of main to see some in depth explanations on + // toggling, setting, and clearing bits in Zig. + + print("Toggle pins with XOR on PORTB\n", .{}); + print("-----------------------------\n", .{}); + PORTB = 0b1100; + print(" {b:0>4} // (initial state of PORTB)\n", .{PORTB}); + print("^ {b:0>4} // (bitmask)\n", .{0b0101}); + PORTB ^= (1 << 1) | (1 << 0); // What's wrong here? + checkAnswer(0b1001, PORTB); + + newline(); + + PORTB = 0b1100; + print(" {b:0>4} // (initial state of PORTB)\n", .{PORTB}); + print("^ {b:0>4} // (bitmask)\n", .{0b0011}); + PORTB ^= (1 << 1) & (1 << 0); // What's wrong here? + checkAnswer(0b1111, PORTB); + + newline(); + + print("Set pins with OR on PORTB\n", .{}); + print("-------------------------\n", .{}); + + PORTB = 0b1001; // reset PORTB + print(" {b:0>4} // (initial state of PORTB)\n", .{PORTB}); + print("| {b:0>4} // (bitmask)\n", .{0b0100}); + PORTB = PORTB ??? (1 << 2); // What's missing here? + checkAnswer(0b1101, PORTB); + + newline(); + + PORTB = 0b1001; // reset PORTB + print(" {b:0>4} // (reset state)\n", .{PORTB}); + print("| {b:0>4} // (bitmask)\n", .{0b0100}); + PORTB ??? (1 << 2); // What's missing here? + checkAnswer(0b1101, PORTB); + + newline(); + + print("Clear pins with AND and NOT on PORTB\n", .{}); + print("------------------------------------\n", .{}); + + PORTB = 0b1110; // reset PORTB + print(" {b:0>4} // (initial state of PORTB)\n", .{PORTB}); + print("& {b:0>4} // (bitmask)\n", .{0b1011}); + PORTB = PORTB & ???@as(u4, 1 << 2); // What character is missing here? + checkAnswer(0b1010, PORTB); + + newline(); + + PORTB = 0b0111; // reset PORTB + print(" {b:0>4} // (reset state)\n", .{PORTB}); + print("& {b:0>4} // (bitmask)\n", .{0b1110}); + PORTB &= ~(1 << 0); // What's missing here? + checkAnswer(0b0110, PORTB); + + newline(); + newline(); +} + +// ************************************************************************ +// IN-DEPTH EXPLANATIONS BELOW +// ************************************************************************ +// +// +// +// +// +// +// +// +// +// +// +// ------------------------------------------------------------------------ +// Toggling bits with XOR: +// ------------------------------------------------------------------------ +// XOR stands for "exclusive or". We can toggle bits with the ^ (XOR) +// bitwise operator, like so: +// +// +// In order to output a 1, the logic of an XOR operation requires that the +// two input bits are of different values. Therefore, 0 ^ 1 and 1 ^ 0 will +// both yield a 1 but 0 ^ 0 and 1 ^ 1 will output 0. XOR's unique behavior +// of outputting a 0 when both inputs are 1s is what makes it different from +// the OR operator; it also gives us the ability to toggle bits by putting +// 1s into our bitmask. +// +// - 1s in our bitmask operand, can be thought of as causing the +// corresponding bits in the other operand to flip to the opposite value. +// - 0s cause no change. +// +// The 0s in our bitmask preserve these values +// -XOR op- ---expanded--- in the output. +// _______________/ +// / / +// 1100 1 1 0 0 +// ^ 0101 0 1 0 1 (bitmask) +// ------ - - - - +// = 1001 1 0 0 1 <- This bit was already cleared. +// \_______\ +// \ +// We can think of these bits having flipped +// because of the presence of 1s in those columns +// of our bitmask. +// +// Now let's take a look at setting bits with the | operator. +// +// +// +// +// +// ------------------------------------------------------------------------ +// Setting bits with OR: +// ------------------------------------------------------------------------ +// We can set bits on PORTB with the | (OR) operator, like so: +// +// var PORTB: u4 = 0b1001; +// PORTB = PORTB | 0b0010; +// print("PORTB: {b:0>4}\n", .{PORTB}); // output: 1011 +// +// -OR op- ---expanded--- +// _ Set only this bit. +// / +// 1001 1 0 0 1 +// | 0010 0 0 1 0 (bitmask) +// ------ - - - - +// = 1011 1 0 1 1 +// \___\_______\ +// \ +// These bits remain untouched because OR-ing with +// a 0 effects no change. +// +// ------------------------------------------------------------------------ +// To create a bitmask like 0b0010 used above: +// +// 1. First, shift the value 1 over one place with the bitwise << (shift +// left) operator as indicated below: +// 1 << 0 -> 0001 +// 1 << 1 -> 0010 <-- Shift 1 one place to the left +// 1 << 2 -> 0100 +// 1 << 3 -> 1000 +// +// This allows us to rewrite the above code like this: +// +// var PORTB: u4 = 0b1001; +// PORTB = PORTB | (1 << 1); +// print("PORTB: {b:0>4}\n", .{PORTB}); // output: 1011 +// +// Finally, as in the C language, Zig allows us to use the |= operator, so +// we can rewrite our code again in an even more compact and idiomatic +// form: PORTB |= (1 << 1) + +// So now we've covered how to toggle and set bits. What about clearing +// them? Well, this is where Zig throws us a curve ball. Don't worry we'll +// go through it step by step. +// +// +// +// +// +// ------------------------------------------------------------------------ +// Clearing bits with AND and NOT: +// ------------------------------------------------------------------------ +// We can clear bits with the & (AND) bitwise operator, like so: + +// PORTB = 0b1110; // reset PORTB +// PORTB = PORTB & 0b1011; +// print("PORTB: {b:0>4}\n", .{PORTB}); // output -> 1010 +// +// - 0s clear bits when used in conjunction with a bitwise AND. +// - 1s do nothing, thus preserving the original bits. +// +// -AND op- ---expanded--- +// __________ Clear only this bit. +// / +// 1110 1 1 1 0 +// & 1011 1 0 1 1 (bitmask) +// ------ - - - - +// = 1010 1 0 1 0 <- This bit was already cleared. +// \_______\ +// \ +// These bits remain untouched because AND-ing with a +// 1 preserves the original bit value whether 0 or 1. +// +// ------------------------------------------------------------------------ +// We can use the ~ (NOT) operator to easily create a bitmask like 1011: +// +// 1. First, shift the value 1 over two places with the bit-wise << (shift +// left) operator as indicated below: +// 1 << 0 -> 0001 +// 1 << 1 -> 0010 +// 1 << 2 -> 0100 <- The 1 has been shifted two places to the left +// 1 << 3 -> 1000 +// +// 2. The second step in creating our bitmask is to invert the bits +// ~0100 -> 1011 +// in C we would write this as: +// ~(1 << 2) -> 1011 +// +// But if we try to compile ~(1 << 2) in Zig, we'll get an error: +// unable to perform binary not operation on type 'comptime_int' +// +// Before Zig can invert our bits, it needs to know the number of +// bits it's being asked to invert. +// +// We do this with the @as (cast as) built-in like this: +// @as(u4, 1 << 2) -> 0100 +// +// Finally, we can invert our new mask by placing the NOT ~ operator +// before our expression, like this: +// ~@as(u4, 1 << 2) -> 1011 +// +// If you are offput by the fact that you can't simply invert bits like +// you can in languages such as C without casting to a particular size +// of integer, you're not alone. However, this is actually another +// instance where Zig is really helpful because it protects you from +// difficult to debug integer overflow bugs that can have you tearing +// your hair out. In the interest of keeping things sane, Zig requires +// you simply to tell it the size of number you are inverting. In the +// words of Andrew Kelley, "If you want to invert the bits of an +// integer, zig has to know how many bits there are." +// +// For more insight into the Zig team's position on why the language +// takes the approach it does with the ~ operator, take a look at +// Andrew's comments on the following github issue: +// https://github.com/ziglang/zig/issues/1382#issuecomment-414459529 +// +// Whew, so after all that what we end up with is: +// PORTB = PORTB & ~@as(u4, 1 << 2); +// +// We can shorten this with the &= combined AND and assignment operator, +// which applies the AND operator on PORTB and then reassigns PORTB. Here's +// what that looks like: +// PORTB &= ~@as(u4, 1 << 2); +// + +// ------------------------------------------------------------------------ +// Conclusion +// ------------------------------------------------------------------------ +// +// While the examples in this quiz have used only 4-bit wide variables, +// working with 8 bits is no different. Here's an example where we set +// every other bit beginning with the two's place: + +// var PORTD: u8 = 0b0000_0000; +// print("PORTD: {b:0>8}\n", .{PORTD}); +// PORTD |= (1 << 1); +// PORTD = setBit(u8, PORTD, 3); +// PORTD |= (1 << 5) | (1 << 7); +// print("PORTD: {b:0>8} // set every other bit\n", .{PORTD}); +// PORTD = ~PORTD; +// print("PORTD: {b:0>8} // bits flipped with NOT (~)\n", .{PORTD}); +// newline(); +// +// // Here we clear every other bit beginning with the two's place. +// +// PORTD = 0b1111_1111; +// print("PORTD: {b:0>8}\n", .{PORTD}); +// PORTD &= ~@as(u8, 1 << 1); +// PORTD = clearBit(u8, PORTD, 3); +// PORTD &= ~@as(u8, (1 << 5) | (1 << 7)); +// print("PORTD: {b:0>8} // clear every other bit\n", .{PORTD}); +// PORTD = ~PORTD; +// print("PORTD: {b:0>8} // bits flipped with NOT (~)\n", .{PORTD}); +// newline(); + +// ---------------------------------------------------------------------------- +// Here are some helper functions for manipulating bits +// ---------------------------------------------------------------------------- + +// Functions for setting, clearing, and toggling a single bit +fn setBit(comptime T: type, byte: T, comptime bit_pos: T) !T { + return byte | (1 << bit_pos); +} + +test "setBit" { + try testing.expectEqual(setBit(u8, 0b0000_0000, 3), 0b0000_1000); +} + +fn clearBit(comptime T: type, byte: T, comptime bit_pos: T) T { + return byte & ~@as(T, (1 << bit_pos)); +} + +test "clearBit" { + try testing.expectEqual(clearBit(u8, 0b1111_1111, 0), 0b1111_1110); +} + +fn toggleBit(comptime T: type, byte: T, comptime bit_pos: T) T { + return byte ^ (1 << bit_pos); +} + +test "toggleBit" { + var byte = toggleBit(u8, 0b0000_0000, 0); + try testing.expectEqual(byte, 0b0000_0001); + byte = toggleBit(u8, byte, 0); + try testing.expectEqual(byte, 0b0000_0000); +} + +// ---------------------------------------------------------------------------- +// Some additional functions for setting, clearing, and toggling multiple bits +// at once with a tuple because, hey, why not? +// ---------------------------------------------------------------------------- +// + +fn createBitmask(comptime T: type, comptime bits: anytype) !T { + comptime var bitmask: T = 0; + inline for (bits) |bit| { + if (bit >= @bitSizeOf(T)) return error.BitPosTooLarge; + if (bit < 0) return error.BitPosTooSmall; + + bitmask |= (1 << bit); + } + return bitmask; +} + +test "creating bitmasks from a tuple" { + try testing.expectEqual(createBitmask(u8, .{0}), 0b0000_0001); + try testing.expectEqual(createBitmask(u8, .{1}), 0b0000_0010); + try testing.expectEqual(createBitmask(u8, .{2}), 0b0000_0100); + try testing.expectEqual(createBitmask(u8, .{3}), 0b0000_1000); + // + try testing.expectEqual(createBitmask(u8, .{ 0, 4 }), 0b0001_0001); + try testing.expectEqual(createBitmask(u8, .{ 1, 5 }), 0b0010_0010); + try testing.expectEqual(createBitmask(u8, .{ 2, 6 }), 0b0100_0100); + try testing.expectEqual(createBitmask(u8, .{ 3, 7 }), 0b1000_1000); + + try testing.expectError(error.BitPosTooLarge, createBitmask(u4, .{4})); +} + +fn setBits(byte: u8, bits: anytype) !u8 { + const bitmask = try createBitmask(u8, bits); + return byte | bitmask; +} + +test "setBits" { + try testing.expectEqual(setBits(0b0000_0000, .{0}), 0b0000_0001); + try testing.expectEqual(setBits(0b0000_0000, .{7}), 0b1000_0000); + + try testing.expectEqual(setBits(0b0000_0000, .{ 0, 1, 2, 3, 4, 5, 6, 7 }), 0b1111_1111); + try testing.expectEqual(setBits(0b1111_1111, .{ 0, 1, 2, 3, 4, 5, 6, 7 }), 0b1111_1111); + + try testing.expectEqual(setBits(0b0000_0000, .{ 2, 3, 4, 5 }), 0b0011_1100); + + try testing.expectError(error.BitPosTooLarge, setBits(0b1111_1111, .{8})); + try testing.expectError(error.BitPosTooSmall, setBits(0b1111_1111, .{-1})); +} + +fn clearBits(comptime byte: u8, comptime bits: anytype) !u8 { + const bitmask: u8 = try createBitmask(u8, bits); + return byte & ~@as(u8, bitmask); +} + +test "clearBits" { + try testing.expectEqual(clearBits(0b1111_1111, .{0}), 0b1111_1110); + try testing.expectEqual(clearBits(0b1111_1111, .{7}), 0b0111_1111); + + try testing.expectEqual(clearBits(0b1111_1111, .{ 0, 1, 2, 3, 4, 5, 6, 7 }), 0b000_0000); + try testing.expectEqual(clearBits(0b0000_0000, .{ 0, 1, 2, 3, 4, 5, 6, 7 }), 0b000_0000); + + try testing.expectEqual(clearBits(0b1111_1111, .{ 0, 1, 6, 7 }), 0b0011_1100); + + try testing.expectError(error.BitPosTooLarge, clearBits(0b1111_1111, .{8})); + try testing.expectError(error.BitPosTooSmall, clearBits(0b1111_1111, .{-1})); +} + +fn toggleBits(comptime byte: u8, comptime bits: anytype) !u8 { + const bitmask = try createBitmask(u8, bits); + return byte ^ bitmask; +} + +test "toggleBits" { + try testing.expectEqual(toggleBits(0b0000_0000, .{0}), 0b0000_0001); + try testing.expectEqual(toggleBits(0b0000_0000, .{7}), 0b1000_0000); + + try testing.expectEqual(toggleBits(0b1111_1111, .{ 0, 1, 2, 3, 4, 5, 6, 7 }), 0b000_0000); + try testing.expectEqual(toggleBits(0b0000_0000, .{ 0, 1, 2, 3, 4, 5, 6, 7 }), 0b1111_1111); + + try testing.expectEqual(toggleBits(0b0000_1111, .{ 0, 1, 2, 3, 4, 5, 6, 7 }), 0b1111_0000); + try testing.expectEqual(toggleBits(0b0000_1111, .{ 0, 1, 2, 3 }), 0b0000_0000); + + try testing.expectEqual(toggleBits(0b0000_0000, .{ 0, 2, 4, 6 }), 0b0101_0101); + + try testing.expectError(error.BitPosTooLarge, toggleBits(0b1111_1111, .{8})); + try testing.expectError(error.BitPosTooSmall, toggleBits(0b1111_1111, .{-1})); +} + +// ---------------------------------------------------------------------------- +// Utility functions +// ---------------------------------------------------------------------------- + +fn newline() void { + print("\n", .{}); +} + +fn checkAnswer(expected: u4, answer: u4) void { + if (expected != answer) { + print("*************************************************************\n", .{}); + print("= {b:0>4} <- INCORRECT! THE EXPECTED OUTPUT IS {b:0>4}\n", .{ answer, expected }); + print("*************************************************************\n", .{}); + } else { + print("= {b:0>4}", .{answer}); + } + newline(); +} diff --git a/patches/eowyn.sh b/patches/eowyn.sh index 8cac450..afc2732 100755 --- a/patches/eowyn.sh +++ b/patches/eowyn.sh @@ -12,6 +12,12 @@ # using the patches in this directory and convey them # to convalesce in the healed directory. # +delete_progress() { + progress_file=".progress.txt" + if [ -f $progress_file ]; then + rm $progress_file + fi +} set -e # We check ourselves before we wreck ourselves. @@ -23,9 +29,12 @@ fi # Which version we have? echo "Zig version" $(zig version) -echo "Eowyn version 23.10.5.1, let's try our magic power." +echo "Eowyn version 25.1.9, let's try our magic power." echo "" +# Remove progress file +delete_progress + # Create directory of healing if it doesn't already exist. mkdir -p patches/healed @@ -54,3 +63,6 @@ zig fmt --check patches/healed # Test the healed exercises. May the compiler have mercy upon us. zig build -Dhealed + +# Remove progress file again +delete_progress diff --git a/patches/patches/026_hello2.patch b/patches/patches/026_hello2.patch index f0224a1..f99cc67 100644 --- a/patches/patches/026_hello2.patch +++ b/patches/patches/026_hello2.patch @@ -1,9 +1,9 @@ ---- exercises/026_hello2.zig 2023-10-03 22:15:22.122241138 +0200 -+++ answers/026_hello2.zig 2023-10-05 20:04:06.959431737 +0200 +--- exercises/026_hello2.zig 2025-07-22 09:55:51.337832401 +0200 ++++ answers/026_hello2.zig 2025-07-22 10:00:11.233348058 +0200 @@ -23,5 +23,5 @@ // to be able to pass it up as a return value of main(). // // We just learned of a single statement which can accomplish this. -- stdout.print("Hello world!\n", .{}); -+ try stdout.print("Hello world!\n", .{}); +- stdout.interface.print("Hello world!\n", .{}); ++ try stdout.interface.print("Hello world!\n", .{}); } diff --git a/patches/patches/034_quiz4.patch b/patches/patches/034_quiz4.patch index 8c0bc0e..15c95fc 100644 --- a/patches/patches/034_quiz4.patch +++ b/patches/patches/034_quiz4.patch @@ -1,15 +1,15 @@ ---- exercises/034_quiz4.zig 2023-10-03 22:15:22.122241138 +0200 -+++ answers/034_quiz4.zig 2023-10-05 20:04:06.996099091 +0200 +--- exercises/034_quiz4.zig 2025-07-22 09:55:51.337832401 +0200 ++++ answers/034_quiz4.zig 2025-07-22 10:05:08.320323184 +0200 @@ -9,10 +9,10 @@ const NumError = error{IllegalNumber}; -pub fn main() void { +pub fn main() !void { - const stdout = std.io.getStdOut().writer(); + var stdout = std.fs.File.stdout().writer(&.{}); - const my_num: u32 = getNumber(); + const my_num: u32 = try getNumber(); - try stdout.print("my_num={}\n", .{my_num}); + try stdout.interface.print("my_num={}\n", .{my_num}); } diff --git a/patches/patches/046_optionals2.patch b/patches/patches/046_optionals2.patch index 9229f05..8fa01a2 100644 --- a/patches/patches/046_optionals2.patch +++ b/patches/patches/046_optionals2.patch @@ -1,5 +1,5 @@ ---- exercises/046_optionals2.zig 2024-06-23 19:43:16 -+++ answers/046_optionals2.zig 2024-06-23 19:42:46 +--- exercises/046_optionals2.zig 2024-11-08 22:46:25.592875338 +0100 ++++ answers/046_optionals2.zig 2024-11-08 22:46:20.699447951 +0100 @@ -22,7 +22,7 @@ const Elephant = struct { diff --git a/patches/patches/058_quiz7.patch b/patches/patches/058_quiz7.patch index 265b9e3..978fbb1 100644 --- a/patches/patches/058_quiz7.patch +++ b/patches/patches/058_quiz7.patch @@ -1,8 +1,8 @@ ---- exercises/058_quiz7.zig 2023-10-03 22:15:22.125574535 +0200 -+++ answers/058_quiz7.zig 2023-10-05 20:04:07.106101152 +0200 +--- exercises/058_quiz7.zig 2024-10-28 09:06:49.448505460 +0100 ++++ answers/058_quiz7.zig 2024-10-28 09:35:14.631932322 +0100 @@ -192,8 +192,8 @@ // Oops! The hermit forgot how to capture the union values - // in a switch statement. Please capture both values as + // in a switch statement. Please capture each value as // 'p' so the print statements work! - .place => print("{s}", .{p.name}), - .path => print("--{}->", .{p.dist}), diff --git a/patches/patches/060_floats.patch b/patches/patches/060_floats.patch index 404654a..9e64c6f 100644 --- a/patches/patches/060_floats.patch +++ b/patches/patches/060_floats.patch @@ -1,11 +1,11 @@ ---- exercises/060_floats.zig 2023-11-06 19:45:03.609687304 +0100 -+++ answers/060_floats.zig 2023-11-06 19:44:49.249419994 +0100 +--- exercises/060_floats.zig 2025-03-03 20:23:40.255443963 +0400 ++++ answers/060_floats.zig 2025-03-03 20:29:58.554854977 +0400 @@ -43,7 +43,7 @@ // - // We'll convert this weight from pound to kilograms at a - // conversion of 0.453592kg to the pound. -- const shuttle_weight: f16 = 0.453592 * 4480e6; -+ const shuttle_weight: f32 = 0.453592 * 4.480e6; + // We'll convert this weight from pounds to metric units at a + // conversion of 0.453592 kg to the pound. +- const shuttle_weight: f16 = 0.453592 * 4480e3; ++ const shuttle_weight: f32 = 0.453592 * 4.480e3; // By default, float values are formatted in scientific // notation. Try experimenting with '{d}' and '{d:.3}' to see diff --git a/patches/patches/065_builtins2.patch b/patches/patches/065_builtins2.patch index 4b0ccd2..ad4192b 100644 --- a/patches/patches/065_builtins2.patch +++ b/patches/patches/065_builtins2.patch @@ -1,5 +1,5 @@ ---- exercises/065_builtins2.zig 2023-10-03 22:15:22.125574535 +0200 -+++ answers/065_builtins2.zig 2023-10-05 20:04:07.136101712 +0200 +--- exercises/065_builtins2.zig 2025-06-17 13:58:07.857258167 +0200 ++++ answers/065_builtins2.zig 2025-06-17 13:56:36.630415938 +0200 @@ -58,7 +58,7 @@ // Oops! We cannot leave the 'me' and 'myself' fields // undefined. Please set them here: @@ -18,22 +18,22 @@ // Now we print a pithy statement about Narcissus. print("A {s} loves all {s}es. ", .{ -@@ -109,15 +109,15 @@ +@@ -113,15 +113,15 @@ // Please complete these 'if' statements so that the field // name will not be printed if the field is of type 'void' // (which is a zero-bit type that takes up no space at all!): - if (fields[0].??? != void) { + if (fields[0].type != void) { - print(" {s}", .{@typeInfo(Narcissus).Struct.fields[0].name}); + print(" {s}", .{fields[0].name}); } - if (fields[1].??? != void) { + if (fields[1].type != void) { - print(" {s}", .{@typeInfo(Narcissus).Struct.fields[1].name}); + print(" {s}", .{fields[1].name}); } - if (fields[2].??? != void) { + if (fields[2].type != void) { - print(" {s}", .{@typeInfo(Narcissus).Struct.fields[2].name}); + print(" {s}", .{fields[2].name}); } diff --git a/patches/patches/071_comptime6.patch b/patches/patches/071_comptime6.patch index 8731344..98fb6e3 100644 --- a/patches/patches/071_comptime6.patch +++ b/patches/patches/071_comptime6.patch @@ -1,8 +1,8 @@ ---- exercises/071_comptime6.zig 2023-10-03 22:15:22.125574535 +0200 -+++ answers/071_comptime6.zig 2023-10-05 20:04:07.162768879 +0200 +--- exercises/071_comptime6.zig 2024-09-02 19:21:50.250454978 +0200 ++++ answers/071_comptime6.zig 2024-09-02 19:21:23.553250563 +0200 @@ -40,7 +40,7 @@ - const fields = @typeInfo(Narcissus).Struct.fields; + const fields = @typeInfo(Narcissus).@"struct".fields; - ??? { + inline for (fields) |field| { diff --git a/patches/patches/076_sentinels.patch b/patches/patches/076_sentinels.patch index cbfae31..7abd01e 100644 --- a/patches/patches/076_sentinels.patch +++ b/patches/patches/076_sentinels.patch @@ -1,5 +1,5 @@ ---- exercises/076_sentinels.zig 2023-10-03 22:15:22.125574535 +0200 -+++ answers/076_sentinels.zig 2023-10-05 20:04:07.186102649 +0200 +--- exercises/076_sentinels.zig 2024-09-02 19:27:04.336781039 +0200 ++++ answers/076_sentinels.zig 2024-09-02 19:26:15.709134934 +0200 @@ -82,7 +82,7 @@ print("Array:", .{}); diff --git a/patches/patches/082_anonymous_structs3.patch b/patches/patches/082_anonymous_structs3.patch index 7beb511..0f71a94 100644 --- a/patches/patches/082_anonymous_structs3.patch +++ b/patches/patches/082_anonymous_structs3.patch @@ -1,11 +1,11 @@ ---- exercises/082_anonymous_structs3.zig 2023-10-03 22:15:22.125574535 +0200 -+++ answers/082_anonymous_structs3.zig 2023-10-05 20:04:07.212769813 +0200 +--- exercises/082_anonymous_structs3.zig 2025-03-14 16:41:17.892873287 +0200 ++++ answers/082_anonymous_structs3.zig 2025-03-14 16:40:56.043829543 +0200 @@ -82,14 +82,14 @@ - // @typeInfo(Circle).Struct.fields + // @typeInfo(Circle).@"struct".fields // // This will be an array of StructFields. - const fields = ???; -+ const fields = @typeInfo(@TypeOf(tuple)).Struct.fields; ++ const fields = @typeInfo(@TypeOf(tuple)).@"struct".fields; // 2. Loop through each field. This must be done at compile // time. @@ -17,7 +17,7 @@ // 3. Print the field's name, type, and value. // // Each 'field' in this loop is one of these: -@@ -117,9 +117,9 @@ +@@ -119,9 +119,9 @@ // // The first field should print as: "0"(bool):true print("\"{s}\"({any}):{any} ", .{ diff --git a/patches/patches/084_async.patch b/patches/patches/084_async.patch deleted file mode 100644 index 11a9da0..0000000 --- a/patches/patches/084_async.patch +++ /dev/null @@ -1,11 +0,0 @@ ---- exercises/084_async.zig 2023-10-03 22:15:22.125574535 +0200 -+++ answers/084_async.zig 2023-10-05 20:04:07.219436606 +0200 -@@ -48,7 +48,7 @@ - pub fn main() void { - // Additional Hint: you can assign things to '_' when you - // don't intend to do anything with them. -- foo(); -+ _ = async foo(); - } - - fn foo() void { diff --git a/patches/patches/085_async2.patch b/patches/patches/085_async2.patch deleted file mode 100644 index ba10b05..0000000 --- a/patches/patches/085_async2.patch +++ /dev/null @@ -1,10 +0,0 @@ ---- exercises/085_async2.zig 2023-10-03 22:15:22.125574535 +0200 -+++ answers/085_async2.zig 2023-10-05 20:04:07.226103397 +0200 -@@ -19,6 +19,7 @@ - - pub fn main() void { - var foo_frame = async foo(); -+ resume foo_frame; - } - - fn foo() void { diff --git a/patches/patches/086_async3.patch b/patches/patches/086_async3.patch deleted file mode 100644 index d80d4a1..0000000 --- a/patches/patches/086_async3.patch +++ /dev/null @@ -1,16 +0,0 @@ ---- exercises/086_async3.zig 2023-10-03 22:15:22.125574535 +0200 -+++ answers/086_async3.zig 2023-10-05 20:04:07.229436793 +0200 -@@ -13,7 +13,12 @@ - const n = 5; - var foo_frame = async foo(n); - -- ??? -+ // Silly solution. You can also use a loop. -+ resume foo_frame; -+ resume foo_frame; -+ resume foo_frame; -+ resume foo_frame; -+ resume foo_frame; - - print("\n", .{}); - } diff --git a/patches/patches/087_async4.patch b/patches/patches/087_async4.patch deleted file mode 100644 index b1c1736..0000000 --- a/patches/patches/087_async4.patch +++ /dev/null @@ -1,21 +0,0 @@ ---- exercises/087_async4.zig 2023-10-03 22:15:22.125574535 +0200 -+++ answers/087_async4.zig 2023-10-05 20:04:07.236103584 +0200 -@@ -16,7 +16,7 @@ - - while (global_counter <= 5) { - print("{} ", .{global_counter}); -- ??? -+ resume foo_frame; - } - - print("\n", .{}); -@@ -24,7 +24,7 @@ - - fn foo() void { - while (true) { -- ??? -- ??? -+ global_counter += 1; -+ suspend {} - } - } diff --git a/patches/patches/088_async5.patch b/patches/patches/088_async5.patch deleted file mode 100644 index b9d5a21..0000000 --- a/patches/patches/088_async5.patch +++ /dev/null @@ -1,11 +0,0 @@ ---- exercises/088_async5.zig 2023-10-03 22:15:22.125574535 +0200 -+++ answers/088_async5.zig 2023-10-05 20:04:07.239436980 +0200 -@@ -36,7 +36,7 @@ - pub fn main() void { - var myframe = async getPageTitle("http://example.com"); - -- var value = ??? -+ var value = await myframe; - - print("{s}\n", .{value}); - } diff --git a/patches/patches/089_async6.patch b/patches/patches/089_async6.patch deleted file mode 100644 index 4a0687e..0000000 --- a/patches/patches/089_async6.patch +++ /dev/null @@ -1,13 +0,0 @@ ---- exercises/089_async6.zig 2023-10-03 22:15:22.125574535 +0200 -+++ answers/089_async6.zig 2023-10-05 20:04:07.242770376 +0200 -@@ -41,8 +41,8 @@ - var com_frame = async getPageTitle("http://example.com"); - var org_frame = async getPageTitle("http://example.org"); - -- var com_title = com_frame; -- var org_title = org_frame; -+ var com_title = await com_frame; -+ var org_title = await org_frame; - - print(".com: {s}, .org: {s}.\n", .{ com_title, org_title }); - } diff --git a/patches/patches/090_async7.patch b/patches/patches/090_async7.patch deleted file mode 100644 index 62ec057..0000000 --- a/patches/patches/090_async7.patch +++ /dev/null @@ -1,11 +0,0 @@ ---- exercises/090_async7.zig 2023-10-03 22:15:22.125574535 +0200 -+++ answers/090_async7.zig 2023-10-05 20:04:07.249437167 +0200 -@@ -29,7 +29,7 @@ - // The main() function can not be async. But we know - // getBeef() will not suspend with this particular - // invocation. Please make this okay: -- var my_beef = getBeef(0); -+ var my_beef = nosuspend getBeef(0); - - print("beef? {X}!\n", .{my_beef}); - } diff --git a/patches/patches/091_async8.patch b/patches/patches/091_async8.patch deleted file mode 100644 index ddd3fce..0000000 --- a/patches/patches/091_async8.patch +++ /dev/null @@ -1,26 +0,0 @@ ---- exercises/091_async8.zig 2023-10-03 22:15:22.125574535 +0200 -+++ answers/091_async8.zig 2023-10-05 20:04:07.252770563 +0200 -@@ -17,7 +17,7 @@ - - var frame = async suspendable(); - -- print("X", .{}); -+ print("D", .{}); - - resume frame; - -@@ -25,11 +25,11 @@ - } - - fn suspendable() void { -- print("X", .{}); -+ print("B", .{}); - - suspend { -- print("X", .{}); -+ print("C", .{}); - } - -- print("X", .{}); -+ print("E", .{}); - } diff --git a/patches/patches/097_bit_manipulation.patch b/patches/patches/097_bit_manipulation.patch index 5303ee1..19ba876 100644 --- a/patches/patches/097_bit_manipulation.patch +++ b/patches/patches/097_bit_manipulation.patch @@ -1,5 +1,5 @@ ---- exercises/097_bit_manipulation.zig 2023-10-03 22:15:22.125574535 +0200 -+++ answers/097_bit_manipulation.zig 2023-10-05 20:04:07.282771124 +0200 +--- exercises/097_bit_manipulation.zig 2025-05-12 21:25:03.395385743 +0200 ++++ answers/097_bit_manipulation.zig 2025-05-12 21:22:57.472986976 +0200 @@ -80,7 +80,7 @@ y ^= x; @@ -7,5 +7,5 @@ - ???; + x ^= y; - print("x = {d}; y = {d}\n", .{ x, y }); + print("x = {b}; y = {b}\n", .{ x, y }); } diff --git a/patches/patches/099_formatting.patch b/patches/patches/099_formatting.patch index 384bf86..a56b556 100644 --- a/patches/patches/099_formatting.patch +++ b/patches/patches/099_formatting.patch @@ -1,5 +1,5 @@ ---- exercises/099_formatting.zig 2023-10-03 22:15:22.125574535 +0200 -+++ answers/099_formatting.zig 2023-10-05 20:04:07.292771311 +0200 +--- exercises/099_formatting.zig 2024-11-07 21:45:10.459123650 +0100 ++++ answers/099_formatting.zig 2024-11-07 21:43:55.154345991 +0100 @@ -131,7 +131,7 @@ for (0..size) |b| { // What formatting is needed here to make our columns diff --git a/patches/patches/100_for4.patch b/patches/patches/100_for4.patch index 3539be2..ad73e9a 100644 --- a/patches/patches/100_for4.patch +++ b/patches/patches/100_for4.patch @@ -7,5 +7,5 @@ - for (hex_nums, ???) |hn, ???| { + for (hex_nums, dec_nums) |hn, dn| { if (hn != dn) { - std.debug.print("Uh oh! Found a mismatch: {d} vs {d}\n", .{ hn, dn }); + print("Uh oh! Found a mismatch: {d} vs {d}\n", .{ hn, dn }); return; diff --git a/patches/patches/106_files.patch b/patches/patches/106_files.patch index 5eb5a19..f40a33a 100644 --- a/patches/patches/106_files.patch +++ b/patches/patches/106_files.patch @@ -1,63 +1,16 @@ ---- exercises/106_files.zig 2024-06-17 10:11:53.651439869 +0200 -+++ answers/106_files.zig 2024-06-17 10:21:50.697337653 +0200 -@@ -1,22 +1,22 @@ - // - // Until now, we've only been printing our output in the console, --// which is good enough for fighting aliens and hermit bookkeeping. -+// which is good enough for fighting alien and hermit bookkeeping. - // --// However, many other tasks require some interaction with the file system, -+// However, many other task require some interaction with the file system, - // which is the underlying structure for organizing files on your computer. - // --// The file system provides a hierarchical structure for storing files --// by organizing them into directories, which hold files and other directories, --// thus creating a tree structure that can be navigated. -+// The File System provide a hierarchical structure for storing files -+// by organizing files into directories, which hold files and other directories, -+// thus creating a tree structure for navigating. - // --// Fortunately, the Zig standard library provides a simple API for interacting --// with the file system, see the detail documentation here: -+// Fortunately, zig standard library provide a simple api for interacting -+// with the file system, see the detail documentation here - // - // https://ziglang.org/documentation/master/std/#std.fs - // --// In this exercise, we'll try to: --// - create a new directory, --// - open a file in the directory, -+// In this exercise, we'll try to -+// - create a new directory -+// - open a file in the directory - // - write to the file. - // - // import std as always -@@ -27,42 +27,42 @@ - const cwd: std.fs.Dir = std.fs.cwd(); - - // then we'll try to make a new directory /output/ -- // to store our output files. -+ // to put our output files. - cwd.makeDir("output") catch |e| switch (e) { -- // there is a chance you might want to run this -+ // there are chance you might want to run this - // program more than once and the path might already -- // have been created, so we'll have to handle this error -+ // been created, so we'll have to handle this error +--- exercises/106_files.zig 2025-03-13 15:26:59.532367792 +0200 ++++ answers/106_files.zig 2025-03-14 22:04:52.243435159 +0200 +@@ -35,7 +35,7 @@ // by doing nothing // // we want to catch error.PathAlreadyExists and do nothing - ??? => {}, -- // if there's any other unexpected error we just propagate it through + error.PathAlreadyExists => {}, -+ // if is any other unexpected error we just propagate it through + // if there's any other unexpected error we just propagate it through else => return e, }; - - // then we'll try to open our freshly created directory -- // wait a minute... -+ // wait a minute +@@ -44,7 +44,7 @@ + // wait a minute... // opening a directory might fail! // what should we do here? - var output_dir: std.fs.Dir = cwd.openDir("output", .{}); @@ -65,36 +18,12 @@ defer output_dir.close(); // we try to open the file `zigling.txt`, -- // and propagate any error up -+ // and propagate the error up if there are any errors - const file: std.fs.File = try output_dir.createFile("zigling.txt", .{}); - // it is a good habit to close a file after you are done with it - // so that other programs can read it and prevent data corruption +@@ -55,7 +55,7 @@ // but here we are not yet done writing to the file -- // if only there were a keyword in Zig that -- // allowed you to "defer" code execution to the end of the scope... + // if only there were a keyword in Zig that + // allowed you to "defer" code execution to the end of the scope... - file.close(); -+ // if only there were a keyword in zig that -+ // allows you "defer" code execute to the end of scope... + defer file.close(); -- // you are not allowed to move these two lines above the file closing line! -+ // !you are not allowed to switch these two lines above the file closing line! + // you are not allowed to move these two lines above the file closing line! const byte_written = try file.write("It's zigling time!"); - std.debug.print("Successfully wrote {d} bytes.\n", .{byte_written}); - } - // to check if you actually write to the file, you can either, --// 1. open the file in your text editor, or -+// 1. open the file on your text editor, or - // 2. print the content of the file in the console with the following command - // >> cat ./output/zigling.txt - // -@@ -86,7 +86,7 @@ - // - // Question: - // - what should you do if you want to also read the file after opening it? --// - go to the documentation of the struct `std.fs.Dir` here: -+// - go to documentation of the struct `std.fs.Dir` here - // https://ziglang.org/documentation/master/std/#std.fs.Dir - // - can you find a function for opening a file? how about deleting a file? - // - what kind of options can you use with those functions? diff --git a/patches/patches/107_files2.patch b/patches/patches/107_files2.patch index ebf8a7c..95aaa0c 100644 --- a/patches/patches/107_files2.patch +++ b/patches/patches/107_files2.patch @@ -1,55 +1,23 @@ ---- exercises/107_files2.zig 2024-06-17 10:11:53.651439869 +0200 -+++ answers/107_files2.zig 2024-06-17 10:21:50.700671057 +0200 -@@ -4,17 +4,17 @@ - // - create a file {project_root}/output/zigling.txt - // with content `It's zigling time!`(18 byte total) - // --// Now there's no point in writing to a file if we don't read from it, am I right? --// Let's write a program to read the content of the file that we just created. -+// Now there no point in writing to a file if we don't read from it am I right? -+// let's write a program to read the content of the file that we just created. - // - // I am assuming that you've created the appropriate files for this to work. - // --// Alright, bud, lean in close. Here's the game plan. -+// Alright, bud, lean in close here's the game plan. - // - First, we open the {project_root}/output/ directory - // - Secondly, we open file `zigling.txt` in that directory --// - Then, we initalize an array of characters with all letter 'A', and print it --// - After that, we read the content of the file into the array --// - Finally, we print out the content we just read -+// - then, we initalize an array of characters with all letter 'A', and print it -+// - After that, we read the content of the file to the array -+// - Finally, we print out the read content - - const std = @import("std"); - -@@ -30,23 +30,23 @@ - const file = try output_dir.openFile("zigling.txt", .{}); - defer file.close(); - -- // initalize an array of u8 with all letter 'A' -- // we need to pick the size of the array, 64 seems like a good number -+ // initalize an array of u8 with all letter 'A'. -+ // we need to pick the size of the array, 64 seems like a good number. - // fix the initalization below +--- exercises/107_files2.zig 2025-03-13 15:26:59.532367792 +0200 ++++ answers/107_files2.zig 2025-03-14 22:08:35.167953736 +0200 +@@ -33,7 +33,7 @@ + // initialize an array of u8 with all letter 'A' + // we need to pick the size of the array, 64 seems like a good number + // fix the initialization below - var content = ['A']*64; + var content = [_]u8{'A'} ** 64; // this should print out : `AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA` std.debug.print("{s}\n", .{content}); - // okay, seems like a threat of violence is not the answer in this case -- // can you go here to find a way to read the content? -+ // can you go here to find a way to read the content ? +@@ -41,12 +41,12 @@ + // can you go here to find a way to read the content? // https://ziglang.org/documentation/master/std/#std.fs.File - // hint: you might find two answers that are both vaild in this case + // hint: you might find two answers that are both valid in this case - const bytes_read = zig_read_the_file_or_i_will_fight_you(&content); + const bytes_read = try file.read(&content); -- // Woah, too screamy. I know you're excited for zigling time but tone it down a bit. -- // Can you print only what we read from the file? -+ // Woah, too screamy, I know you're excited for zigling time but tone it down a bit -+ // Can you print only what we read from the file ? + // Woah, too screamy. I know you're excited for zigling time but tone it down a bit. + // Can you print only what we read from the file? std.debug.print("Successfully Read {d} bytes: {s}\n", .{ bytes_read, - content, // change this line only diff --git a/patches/patches/108_labeled_switch.patch b/patches/patches/108_labeled_switch.patch new file mode 100644 index 0000000..fa9dd67 --- /dev/null +++ b/patches/patches/108_labeled_switch.patch @@ -0,0 +1,18 @@ +--- exercises/108_labeled_switch.zig 2024-09-20 12:09:24.370066539 +0200 ++++ answers/108_labeled_switch.zig 2024-09-20 12:09:06.499711739 +0200 +@@ -65,13 +65,13 @@ + // how would you fix it? + pr: switch (PullRequestState.Draft) { + PullRequestState.Draft => continue :pr PullRequestState.InReview, +- PullRequestState.InReview => continue :pr PullRequestState.Rejected, ++ PullRequestState.InReview => continue :pr PullRequestState.Approved, + PullRequestState.Approved => continue :pr PullRequestState.Merged, + PullRequestState.Rejected => { + std.debug.print("The pull request has been rejected.\n", .{}); + return; + }, +- PullRequestState.Merged => break, // Would you know where to break to? ++ PullRequestState.Merged => break :pr, // Would you know where to break to? + } + std.debug.print("The pull request has been merged.\n", .{}); + } diff --git a/patches/patches/109_vectors.patch b/patches/patches/109_vectors.patch new file mode 100644 index 0000000..bf18cc0 --- /dev/null +++ b/patches/patches/109_vectors.patch @@ -0,0 +1,13 @@ +--- exercises/109_vectors.zig 2024-11-07 14:57:09.673383618 +0100 ++++ answers/109_vectors.zig 2024-11-07 14:22:59.069150138 +0100 +@@ -121,8 +121,8 @@ + + const Vec4 = @Vector(4, f32); + fn calcMaxPairwiseDiffNew(a: Vec4, b: Vec4) f32 { +- const abs_diff_vec = ???; +- const max_diff = @reduce(???, abs_diff_vec); ++ const abs_diff_vec = @abs(a - b); ++ const max_diff = @reduce(.Max, abs_diff_vec); + return max_diff; + } + diff --git a/patches/patches/110_quiz9.patch b/patches/patches/110_quiz9.patch new file mode 100644 index 0000000..9d9b864 --- /dev/null +++ b/patches/patches/110_quiz9.patch @@ -0,0 +1,56 @@ +--- exercises/110_quiz9.zig 2025-02-08 13:19:48.522641785 -0800 ++++ answers/110_quiz9.zig 2025-02-10 17:42:04.525004335 -0800 +@@ -108,7 +108,7 @@ + PORTB = 0b1100; + print(" {b:0>4} // (initial state of PORTB)\n", .{PORTB}); + print("^ {b:0>4} // (bitmask)\n", .{0b0101}); +- PORTB ^= (1 << 1) | (1 << 0); // What's wrong here? ++ PORTB ^= (1 << 2) | (1 << 0); + checkAnswer(0b1001, PORTB); + + newline(); +@@ -116,7 +116,7 @@ + PORTB = 0b1100; + print(" {b:0>4} // (initial state of PORTB)\n", .{PORTB}); + print("^ {b:0>4} // (bitmask)\n", .{0b0011}); +- PORTB ^= (1 << 1) & (1 << 0); // What's wrong here? ++ PORTB ^= (1 << 1) | (1 << 0); + checkAnswer(0b1111, PORTB); + + newline(); +@@ -170,7 +170,7 @@ + PORTB = 0b1001; // reset PORTB + print(" {b:0>4} // (initial state of PORTB)\n", .{PORTB}); + print("| {b:0>4} // (bitmask)\n", .{0b0100}); +- PORTB = PORTB ??? (1 << 2); // What's missing here? ++ PORTB = PORTB | (1 << 2); + checkAnswer(0b1101, PORTB); + + newline(); +@@ -178,7 +178,7 @@ + PORTB = 0b1001; // reset PORTB + print(" {b:0>4} // (reset state)\n", .{PORTB}); + print("| {b:0>4} // (bitmask)\n", .{0b0100}); +- PORTB ??? (1 << 2); // What's missing here? ++ PORTB |= (1 << 2); + checkAnswer(0b1101, PORTB); + + newline(); +@@ -269,7 +269,7 @@ + PORTB = 0b1110; // reset PORTB + print(" {b:0>4} // (initial state of PORTB)\n", .{PORTB}); + print("& {b:0>4} // (bitmask)\n", .{0b1011}); +- PORTB = PORTB & ???@as(u4, 1 << 2); // What character is missing here? ++ PORTB = PORTB & ~@as(u4, 1 << 2); + checkAnswer(0b1010, PORTB); + + newline(); +@@ -277,7 +277,7 @@ + PORTB = 0b0111; // reset PORTB + print(" {b:0>4} // (reset state)\n", .{PORTB}); + print("& {b:0>4} // (bitmask)\n", .{0b1110}); +- PORTB &= ~(1 << 0); // What's missing here? ++ PORTB &= ~@as(u4, 1 << 0); + checkAnswer(0b0110, PORTB); + + newline(); diff --git a/test/tests.zig b/test/tests.zig index 126a1cd..b191610 100644 --- a/test/tests.zig +++ b/test/tests.zig @@ -50,7 +50,7 @@ pub fn addCliTests(b: *std.Build, exercises: []const Exercise) *Step { case_step.dependOn(&verify.step); } - const cleanup = b.addRemoveDirTree(tmp_path); + const cleanup = b.addRemoveDirTree(.{ .src_path = .{ .owner = b, .sub_path = tmp_path } }); cleanup.step.dependOn(case_step); step.dependOn(&cleanup.step); @@ -82,7 +82,7 @@ pub fn addCliTests(b: *std.Build, exercises: []const Exercise) *Step { const verify = CheckStep.create(b, exercises, stderr); verify.step.dependOn(&cmd.step); - const cleanup = b.addRemoveDirTree(tmp_path); + const cleanup = b.addRemoveDirTree(.{ .src_path = .{ .owner = b, .sub_path = tmp_path } }); cleanup.step.dependOn(&verify.step); step.dependOn(&cleanup.step); @@ -150,7 +150,7 @@ const CheckNamedStep = struct { return self; } - fn make(step: *Step, _: std.Progress.Node) !void { + fn make(step: *Step, _: Step.MakeOptions) !void { const b = step.owner; const self: *CheckNamedStep = @alignCast(@fieldParentPtr("step", step)); const ex = self.exercise; @@ -161,7 +161,7 @@ const CheckNamedStep = struct { ); defer stderr_file.close(); - const stderr = stderr_file.reader(); + var stderr = stderr_file.readerStreaming(&.{}); { // Skip the logo. const nlines = mem.count(u8, root.logo, "\n"); @@ -169,10 +169,10 @@ const CheckNamedStep = struct { var lineno: usize = 0; while (lineno < nlines) : (lineno += 1) { - _ = try readLine(stderr, &buf); + _ = try readLine(&stderr, &buf); } } - try check_output(step, ex, stderr); + try check_output(step, ex, &stderr); } }; @@ -202,7 +202,7 @@ const CheckStep = struct { return self; } - fn make(step: *Step, _: std.Progress.Node) !void { + fn make(step: *Step, _: Step.MakeOptions) !void { const b = step.owner; const self: *CheckStep = @alignCast(@fieldParentPtr("step", step)); const exercises = self.exercises; @@ -213,7 +213,7 @@ const CheckStep = struct { ); defer stderr_file.close(); - const stderr = stderr_file.reader(); + var stderr = stderr_file.readerStreaming(&.{}); for (exercises) |ex| { if (ex.number() == 1) { // Skip the logo. @@ -222,15 +222,15 @@ const CheckStep = struct { var lineno: usize = 0; while (lineno < nlines) : (lineno += 1) { - _ = try readLine(stderr, &buf); + _ = try readLine(&stderr, &buf); } } - try check_output(step, ex, stderr); + try check_output(step, ex, &stderr); } } }; -fn check_output(step: *Step, exercise: Exercise, reader: Reader) !void { +fn check_output(step: *Step, exercise: Exercise, reader: *Reader) !void { const b = step.owner; var buf: [1024]u8 = undefined; @@ -297,12 +297,9 @@ fn check( } } -fn readLine(reader: fs.File.Reader, buf: []u8) !?[]const u8 { - if (try reader.readUntilDelimiterOrEof(buf, '\n')) |line| { - return mem.trimRight(u8, line, " \r\n"); - } - - return null; +fn readLine(reader: *fs.File.Reader, buf: []u8) !?[]const u8 { + try reader.interface.readSliceAll(buf); + return mem.trimRight(u8, buf, " \r\n"); } /// Fails with a custom error message. @@ -325,7 +322,7 @@ const FailStep = struct { return self; } - fn make(step: *Step, _: std.Progress.Node) !void { + fn make(step: *Step, _: Step.MakeOptions) !void { const b = step.owner; const self: *FailStep = @alignCast(@fieldParentPtr("step", step)); @@ -368,7 +365,7 @@ const HealStep = struct { return self; } - fn make(step: *Step, _: std.Progress.Node) !void { + fn make(step: *Step, _: Step.MakeOptions) !void { const b = step.owner; const self: *HealStep = @alignCast(@fieldParentPtr("step", step)); @@ -405,7 +402,8 @@ fn heal(allocator: Allocator, exercises: []const Exercise, work_path: []const u8 /// difference that returns an error when the temp path cannot be created. pub fn makeTempPath(b: *Build) ![]const u8 { const rand_int = std.crypto.random.int(u64); - const tmp_dir_sub_path = "tmp" ++ fs.path.sep_str ++ Build.hex64(rand_int); + const rand_hex64 = std.fmt.hex(rand_int); + const tmp_dir_sub_path = "tmp" ++ fs.path.sep_str ++ rand_hex64; const path = b.cache_root.join(b.allocator, &.{tmp_dir_sub_path}) catch @panic("OOM"); try b.cache_root.handle.makePath(tmp_dir_sub_path);