Compare commits

..

No commits in common. "main" and "v0.11.0" have entirely different histories.

90 changed files with 458 additions and 1870 deletions

View File

@ -1,5 +0,0 @@
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.

7
.gitignore vendored
View File

@ -1,9 +1,4 @@
/.zig-cache/
/zig-cache/
/zig-out/
/answers/
/patches/healed/
/output/
.progress.txt
# Leave this in here for older zig versions
/zig-cache/

View File

@ -1,9 +0,0 @@
steps:
- name: eowyn
image: ziglings/ziglang:latest
pull: true
commands:
- sh ./patches/eowyn.sh
when:
event: [pull_request, push, cron]
cron: daily*

11
.woodpecker/eowyn.yml Normal file
View File

@ -0,0 +1,11 @@
steps:
eowyn:
image: ziglings/ziglang
commands:
- sh ./patches/eowyn.sh
when:
events:
- push
- pull-requests
- cron
cron: "Daily"

View File

@ -40,7 +40,6 @@ fit for one reason or another.
## Platforms and Zig Versions
Because it uses the Zig build system, Ziglings should work
wherever Zig does.
@ -55,12 +54,13 @@ Ziglings. Please file an issue...or make a pull request!
## Formatting
All exercises should conform to `zig fmt`.
All exercises should conform to `zig fmt`. I often forget to do
this.
## Pull Request Workflow
Ziglings uses the "standard" Codeberg workflow as guided by the Web
Ziglings uses the "standard" Github workflow as guided by the Web
interface. Specifically:
* Fork this repository
@ -71,20 +71,11 @@ interface. Specifically:
`git push origin my-branch`
* Create a pull request from your branch to `ziglings/main`
* Your faithful Ziglings maintainers will take a look at your
request ASAP (we don't talk about May-July, LOL)
request ASAP (we don't talk about May-July 2022, LOL)
* Once the changes are reviewed, your request will be merged and
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/`

View File

@ -1,6 +1,6 @@
MIT License
Copyright (c) 2021 Dave Gauer, Chris Boesch
Copyright (c) 2021 Dave Gauer
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@ -1,4 +1,12 @@
# Ziglings
# ⚠️ Ziglings has moved from GitHub to Codeberg!
You are looking at the current Ziglings repo if you are viewing
this at https://codeberg.org/ziglings/exercises/
You can also use the handy URL https://ziglings.org to get here!
***
Welcome to Ziglings! This project contains a series of tiny
broken programs (and one nasty surprise). By fixing them, you'll
@ -10,10 +18,11 @@ 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 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.
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.
## Intended Audience
@ -30,7 +39,6 @@ for more detail:
* 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)
Also, the [Zig community](https://github.com/ziglang/zig/wiki/Community)
is incredibly friendly and helpful!
@ -45,20 +53,20 @@ Verify the installation and build number of `zig` like so:
```
$ zig version
0.15.0-dev.xxxx+xxxxxxxxx
0.12.0-dev.xxxx+xxxxxxxxx
```
Clone this repository with Git:
```
git clone https://codeberg.org/ziglings/exercises.git ziglings
cd ziglings
$ git clone https://ziglings.org
$ cd ziglings.org
```
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
@ -67,14 +75,11 @@ reading these.
## A Note About Versions
**Hint:** To check out Ziglings for a stable release of Zig, you can use
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.14.1`, but Ziglings needs a dev build with
pre-release version "0.15.0" and a build number at least as high
stable release was `0.11.0`, but Ziglings needs a dev build with
pre-release version "0.12.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
@ -87,20 +92,7 @@ that if you update one, you may need to also update the other.
### Version Changes
Version-0.15.0-dev.1092
* *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)
* *2024-03-21* zig 0.12.0-dev.3397 - rename std.os to std.posix - see [#5019](https://github.com/ziglang/zig/issues/5019)
* *2024-03-14* zig 0.12.0-dev.3302 - changes in `std.fmt` - floating-point formatting implementation - see [#19229](https://github.com/ziglang/zig/pull/19229)
* *2024-02-05* zig 0.12.0-dev.2618 - changes in `build system` - from `Step.zig_exe` to `Step.graph.zig_exe` - see [#18778](https://github.com/ziglang/zig/issues/18778)
* *2024-01-05* zig 0.12.0-dev.2043 - rename of `std.Build.FileSource` to `std.Build.LazyPath` - see [#16353](https://github.com/ziglang/zig/issues/16353)
* *2023-10-24* zig 0.12.0-dev.1243 - changes in `std.ChildProcess`: renamed exec to run - see [#5853](https://github.com/ziglang/zig/issues/5853)
Version-0.11.0-dev.4246+71dfce31b
* *2023-06-26* zig 0.11.0-dev.4246 - changes in compile step (now it can be null)
* *2023-06-26* zig 0.11.0-dev.3853 - removal of destination type from all cast builtins
* *2023-06-20* zig 0.11.0-dev.3747 - `@enumToInt` is now `@intFromEnum` and `@intToFloat` is now `@floatFromInt`
@ -131,18 +123,6 @@ 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:
```
zig build -Drandom
```
You can also run without checking for correctness:
```
@ -174,11 +154,6 @@ 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.
@ -234,17 +209,13 @@ Zig Core Language
* [X] Interfaces
* [X] Bit manipulation
* [X] Working with C
* [ ] Opaque types (anyopaque)
* [X] Threading
* [x] Labeled switch
* [x] Vector operations (SIMD)
* [ ] Interfaces part 2
Zig Standard Library
* [X] String formatting
* [X] Testing
* [X] Tokenization
* [X] File handling
## Contributing

265
build.zig
View File

@ -15,7 +15,7 @@ const print = std.debug.print;
// 1) Getting Started
// 2) Version Changes
comptime {
const required_zig = "0.15.0-dev.1092";
const required_zig = "0.11.0-dev.4246";
const current_zig = builtin.zig_version;
const min_zig = std.SemanticVersion.parse(required_zig) catch unreachable;
if (current_zig.order(min_zig) == .lt) {
@ -103,8 +103,6 @@ const Mode = enum {
normal,
/// Named build mode: `zig build -Dn=n`
named,
/// Random build mode: `zig build -Drandom`
random,
};
pub const logo =
@ -120,24 +118,23 @@ pub const logo =
\\
;
const progress_filename = ".progress.txt";
pub fn build(b: *Build) !void {
if (!validate_exercises()) std.process.exit(2);
if (!validate_exercises()) std.os.exit(2);
use_color_escapes = false;
if (std.fs.File.stderr().supportsAnsiEscapeCodes()) {
if (std.io.getStdErr().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));
const GetStdHandle = std.os.windows.kernel32.GetStdHandle;
const GetConsoleMode = std.os.windows.kernel32.GetConsoleMode;
const SetConsoleMode = std.os.windows.kernel32.SetConsoleMode;
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 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,9 +158,6 @@ pub fn build(b: *Build) !void {
false;
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|
@ -178,7 +172,7 @@ pub fn build(b: *Build) !void {
// Named build mode: verifies a single exercise.
if (n == 0 or n > exercises.len - 1) {
print("unknown exercise number: {}\n", .{n});
std.process.exit(2);
std.os.exit(2);
}
const ex = exercises[n - 1];
@ -197,108 +191,17 @@ pub fn build(b: *Build) !void {
return;
}
if (rand) |_| {
// Random build mode: verifies one random exercise.
// 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: {
var seed: u64 = undefined;
try std.posix.getrandom(std.mem.asBytes(&seed));
break :blk seed;
});
const rnd = prng.random();
const ex = exercises[rnd.intRangeLessThan(usize, 0, exercises.len)];
print("random exercise: {s}\n", .{ex.main_file});
const zigling_step = b.step(
"random",
b.fmt("Check the solution of {s}", .{ex.main_file}),
);
b.default_step = zigling_step;
zigling_step.dependOn(&header_step.step);
const verify_step = ZiglingStep.create(b, ex, work_path, .random);
verify_step.step.dependOn(&header_step.step);
zigling_step.dependOn(&verify_step.step);
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;
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);
const verify_stepn = ZiglingStep.create(b, ex, work_path, .normal);
verify_stepn.step.dependOn(prev_step);
prev_step = &verify_stepn.step;
}
prev_step = &verify_stepn.step;
}
ziglings_step.dependOn(prev_step);
@ -341,10 +244,10 @@ const ZiglingStep = struct {
return self;
}
fn make(step: *Step, options: Step.MakeOptions) !void {
fn make(step: *Step, prog_node: *std.Progress.Node) !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));
const self = @fieldParentPtr(ZiglingStep, "step", step);
if (self.exercise.skip) {
print("Skipping {s}\n\n", .{self.exercise.main_file});
@ -352,40 +255,40 @@ const ZiglingStep = struct {
return;
}
const exe_path = self.compile(options.progress_node) catch {
const exe_path = self.compile(prog_node) catch {
self.printErrors();
if (self.exercise.hint) |hint|
print("\n{s}Ziglings hint: {s}{s}", .{ bold_text, hint, reset_text });
self.help();
std.process.exit(2);
std.os.exit(2);
};
self.run(exe_path, options.progress_node) catch {
self.run(exe_path.?, prog_node) catch {
self.printErrors();
if (self.exercise.hint) |hint|
print("\n{s}Ziglings hint: {s}{s}", .{ bold_text, hint, reset_text });
self.help();
std.process.exit(2);
std.os.exit(2);
};
// Print possible warning/debug messages.
self.printErrors();
}
fn run(self: *ZiglingStep, exe_path: []const u8, _: std.Progress.Node) !void {
fn run(self: *ZiglingStep, exe_path: []const u8, _: *std.Progress.Node) !void {
resetLine();
print("Checking: {s}\n", .{self.exercise.main_file});
print("Checking {s}...\n", .{self.exercise.main_file});
const b = self.step.owner;
// Allow up to 1 MB of stdout capture.
const max_output_bytes = 1 * 1024 * 1024;
const result = Child.run(.{
var result = Child.exec(.{
.allocator = b.allocator,
.argv = &.{exe_path},
.cwd = b.build_root.path.?,
@ -403,7 +306,7 @@ const ZiglingStep = struct {
}
}
fn check_output(self: *ZiglingStep, result: Child.RunResult) !void {
fn check_output(self: *ZiglingStep, result: Child.ExecResult) !void {
const b = self.step.owner;
// Make sure it exited cleanly.
@ -449,22 +352,10 @@ 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 });
}
fn check_test(self: *ZiglingStep, result: Child.RunResult) !void {
fn check_test(self: *ZiglingStep, result: Child.ExecResult) !void {
switch (result.term) {
.Exited => |code| {
if (code != 0) {
@ -484,8 +375,8 @@ const ZiglingStep = struct {
print("{s}PASSED{s}\n\n", .{ green_text, reset_text });
}
fn compile(self: *ZiglingStep, prog_node: std.Progress.Node) ![]const u8 {
print("Compiling: {s}\n", .{self.exercise.main_file});
fn compile(self: *ZiglingStep, prog_node: *std.Progress.Node) !?[]const u8 {
print("Compiling {s}...\n", .{self.exercise.main_file});
const b = self.step.owner;
const exercise_path = self.exercise.main_file;
@ -495,7 +386,7 @@ const ZiglingStep = struct {
var zig_args = std.ArrayList([]const u8).init(b.allocator);
defer zig_args.deinit();
zig_args.append(b.graph.zig_exe) catch @panic("OOM");
zig_args.append(b.zig_exe) catch @panic("OOM");
const cmd = switch (self.exercise.kind) {
.exe => "build-exe",
@ -515,21 +406,7 @@ const ZiglingStep = struct {
zig_args.append("--listen=-") catch @panic("OOM");
//
// 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);
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;
return try self.step.evalZigProcess(zig_args.items, prog_node);
}
fn help(self: *ZiglingStep) void {
@ -540,7 +417,6 @@ const ZiglingStep = struct {
const cmd = switch (self.mode) {
.normal => "zig build",
.named => b.fmt("zig build -Dn={s}", .{key}),
.random => "zig build -Drandom",
};
print("\n{s}Edit exercises/{s} and run '{s}' again.{s}\n", .{
@ -583,7 +459,7 @@ fn resetLine() void {
pub fn trimLines(allocator: std.mem.Allocator, buf: []const u8) ![]const u8 {
var list = try std.ArrayList(u8).initCapacity(allocator, buf.len);
var iter = std.mem.splitSequence(u8, buf, " \n");
var iter = std.mem.split(u8, buf, " \n");
while (iter.next()) |line| {
// TODO: trimming CR characters is probably not necessary.
const data = std.mem.trimRight(u8, line, " \r");
@ -618,8 +494,9 @@ const PrintStep = struct {
return self;
}
fn make(step: *Step, _: Step.MakeOptions) !void {
const self: *PrintStep = @alignCast(@fieldParentPtr("step", step));
fn make(step: *Step, _: *std.Progress.Node) !void {
const self = @fieldParentPtr(PrintStep, "step", step);
print("{s}", .{self.message});
}
};
@ -645,7 +522,7 @@ fn validate_exercises() bool {
return false;
}
var iter = std.mem.splitScalar(u8, ex.output, '\n');
var iter = std.mem.split(u8, ex.output, "\n");
while (iter.next()) |line| {
const output = std.mem.trimRight(u8, line, " \r");
if (output.len != line.len) {
@ -962,7 +839,7 @@ const exercises = [_]Exercise{
},
.{
.main_file = "060_floats.zig",
.output = "Shuttle liftoff weight: 2032 metric tons",
.output = "Shuttle liftoff weight: 1995796kg",
},
.{
.main_file = "061_coercions.zig",
@ -1028,7 +905,6 @@ const exercises = [_]Exercise{
.{
.main_file = "074_comptime9.zig",
.output = "My llama value is 2.",
.skip = true,
},
.{
.main_file = "075_quiz8.zig",
@ -1063,7 +939,7 @@ const exercises = [_]Exercise{
.{
.main_file = "082_anonymous_structs3.zig",
.output =
\\"0"(bool):true "1"(bool):false "2"(i32):42 "3"(f32):3.141592
\\"0"(bool):true "1"(bool):false "2"(i32):42 "3"(f32):3.14159202e+00
,
.hint = "This one is a challenge! But you have everything you need.",
},
@ -1145,7 +1021,7 @@ const exercises = [_]Exercise{
},
.{
.main_file = "097_bit_manipulation.zig",
.output = "x = 1011; y = 1101",
.output = "x = 0; y = 1",
},
.{
.main_file = "098_bit_manipulation2.zig",
@ -1227,77 +1103,6 @@ const exercises = [_]Exercise{
\\This little poem has 15 words!
,
},
.{
.main_file = "104_threading.zig",
.output =
\\Starting work...
\\thread 1: started.
\\thread 2: started.
\\thread 3: started.
\\Some weird stuff, after starting the threads.
\\thread 2: finished.
\\thread 1: finished.
\\thread 3: finished.
\\Zig is cool!
,
},
.{
.main_file = "105_threading2.zig",
.output = "PI ≈ 3.14159265",
},
.{
.main_file = "106_files.zig",
.output = "Successfully wrote 18 bytes.",
},
.{
.main_file = "107_files2.zig",
.output =
\\AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
\\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 =

25
ci/compat.sh Executable file
View File

@ -0,0 +1,25 @@
#!/bin/bash
# This script checks that `zig build` will return an useful error message when
# the Zig compiler is not compatible, instead of failing due to a syntax error.
#
# This script should be run on an UNIX system.
zig_version=$(zig version)
zig build -Dn=1 -Dhealed &> /dev/null 2>&1
zig_ret=$?
if [ "$zig_ret" -eq 0 ]; then
printf "zig %s unexpectedly succeeded\n" "$zig_version"
exit 1
fi
zig_error=$(zig build -Dn=1 -Dhealed 2>&1)
echo "$zig_error" | grep -q "it looks like your version of zig is too old"
zig_ret=$?
if [ "$zig_ret" -ne 0 ]; then
printf "zig %s is not compatible\n" "$zig_version"
exit 1
fi

View File

@ -26,7 +26,7 @@ fn addFive(n: u32) MyNumberError!u32 {
// This function needs to return any error which might come back from detect().
// Please use a "try" statement rather than a "catch".
//
const x = detect(n);
var x = detect(n);
return x + 5;
}

View File

@ -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.
var stdout = std.fs.File.stdout().writer(&.{});
const stdout = std.io.getStdOut().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.interface.print("Hello world!\n", .{});
stdout.print("Hello world!\n", .{});
}

View File

@ -21,8 +21,8 @@ const MyErr = error{ GetFail, IncFail };
pub fn main() void {
// We simply quit the entire program if we fail to get a number:
const a: u32 = makeNumber() catch return;
const b: u32 = makeNumber() catch return;
var a: u32 = makeNumber() catch return;
var b: u32 = makeNumber() catch return;
std.debug.print("Numbers: {}, {}\n", .{ a, b });
}

View File

@ -17,7 +17,7 @@
//
// if (foo) |value| {
// ...
// } else |err| switch (err) {
// } else |err| switch(err) {
// ...
// }
//

View File

@ -10,11 +10,11 @@ const std = @import("std");
const NumError = error{IllegalNumber};
pub fn main() void {
var stdout = std.fs.File.stdout().writer(&.{});
const stdout = std.io.getStdOut().writer();
const my_num: u32 = getNumber();
try stdout.interface.print("my_num={}\n", .{my_num});
try stdout.print("my_num={}\n", .{my_num});
}
// This function is obviously weird and non-functional. But you will not be changing it for this quiz.

View File

@ -24,7 +24,7 @@ const std = @import("std");
pub fn main() void {
var num1: u8 = 5;
const num1_pointer: *u8 = &num1;
var num1_pointer: *u8 = &num1;
var num2: u8 = undefined;

View File

@ -5,7 +5,7 @@
// linked to the first elephant. This is because we had NO CONCEPT
// of a tail that didn't point to another elephant!
//
// We also introduce the handy `.?` shortcut:
// We also introduce the handy ".?" shortcut:
//
// const foo = bar.?;
//
@ -13,8 +13,7 @@
//
// const foo = bar orelse unreachable;
//
// Check out where we use this shortcut below to change control flow
// based on if an optional value exists.
// See if you can find where we use this shortcut below.
//
// Now let's make those elephant tails optional!
//
@ -32,25 +31,14 @@ pub fn main() void {
var elephantC = Elephant{ .letter = 'C' };
// Link the elephants so that each tail "points" to the next.
linkElephants(&elephantA, &elephantB);
linkElephants(&elephantB, &elephantC);
// `linkElephants` will stop the program if you try and link an
// elephant that doesn't exist! Uncomment and see what happens.
// const missingElephant: ?*Elephant = null;
// linkElephants(&elephantC, missingElephant);
elephantA.tail = &elephantB;
elephantB.tail = &elephantC;
visitElephants(&elephantA);
std.debug.print("\n", .{});
}
// 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.?;
}
// This function visits all elephants once, starting with the
// first elephant and following the tails to the next elephant.
fn visitElephants(first_elephant: *Elephant) void {
@ -63,9 +51,8 @@ fn visitElephants(first_elephant: *Elephant) void {
// We should stop once we encounter a tail that
// does NOT point to another element. What can
// we put here to make that happen?
if (e.tail == null) ???;
// HINT: We want something similar to what `.?` does,
// but instead of ending the program, we want to exit the loop...
e = e.tail ???
e = e.tail.?;
}
}

View File

@ -40,7 +40,7 @@
// Okay, you're armed.
//
// Now, please zap the alien structs until they're all gone or
// the Earth will be doomed!
// Earth will be doomed!
//
const std = @import("std");

View File

@ -24,7 +24,7 @@ const Elephant = struct {
pub fn print(self: *Elephant) void {
// Prints elephant letter and [v]isited
const v: u8 = if (self.visited) 'v' else ' ';
var v: u8 = if (self.visited) 'v' else ' ';
std.debug.print("{u}{u} ", .{ self.letter, v });
}
};

View File

@ -37,7 +37,7 @@ const Elephant = struct {
pub fn print(self: *Elephant) void {
// Prints elephant letter and [v]isited
const v: u8 = if (self.visited) 'v' else ' ';
var v: u8 = if (self.visited) 'v' else ' ';
std.debug.print("{u}{u} ", .{ self.letter, v });
}
};

View File

@ -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

View File

@ -141,20 +141,9 @@ pub fn main() void {
//
// Moving along...
//
// When arguments are passed to a function,
// they are ALWAYS passed as constants within the function,
// regardless of how they were declared in the calling function.
//
// Example:
// fn foo(arg: u8) void {
// arg = 42; // Error, 'arg' is const!
// }
//
// fn bar() void {
// var arg: u8 = 12;
// foo(arg);
// ...
// }
// Passing arguments to functions is pretty much exactly like
// making an assignment to a const (since Zig enforces that ALL
// function parameters are const).
//
// Knowing this, see if you can make levelUp() work as expected -
// it should add the specified amount to the supplied character's

View File

@ -53,8 +53,8 @@ const AntOrBee = enum { a, b };
pub fn main() void {
// We'll just make one bee and one ant to test them out:
const ant = Insect{ .still_alive = true };
const bee = Insect{ .flowers_visited = 15 };
var ant = Insect{ .still_alive = true };
var bee = Insect{ .flowers_visited = 15 };
std.debug.print("Insect report! ", .{});

View File

@ -38,8 +38,8 @@ const Insect = union(InsectStat) {
};
pub fn main() void {
const ant = Insect{ .still_alive = true };
const bee = Insect{ .flowers_visited = 16 };
var ant = Insect{ .still_alive = true };
var bee = Insect{ .flowers_visited = 16 };
std.debug.print("Insect report! ", .{});

View File

@ -21,8 +21,8 @@ const Insect = union(InsectStat) {
};
pub fn main() void {
const ant = Insect{ .still_alive = true };
const bee = Insect{ .flowers_visited = 17 };
var ant = Insect{ .still_alive = true };
var bee = Insect{ .flowers_visited = 17 };
std.debug.print("Insect report! ", .{});

View File

@ -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 each value as
// in a switch statement. Please capture both values as
// 'p' so the print statements work!
.place => print("{s}", .{p.name}),
.path => print("--{}->", .{p.dist}),
@ -273,7 +273,7 @@ const HermitsNotebook = struct {
// distance) than the one we'd noted before. If it is, we
// overwrite the old entry with the new one.
fn checkNote(self: *HermitsNotebook, note: NotebookEntry) void {
const existing_entry = self.getEntry(note.place);
var existing_entry = self.getEntry(note.place);
if (existing_entry == null) {
self.entries[self.end_of_entries] = note;
@ -386,7 +386,7 @@ pub fn main() void {
// "start" entry we just added) until we run out, at which point
// we'll have checked every reachable Place.
while (notebook.hasNextEntry()) {
const place_entry = notebook.getNextEntry();
var place_entry = notebook.getNextEntry();
// For every Path that leads FROM the current Place, create a
// new note (in the form of a NotebookEntry) with the

View File

@ -2,12 +2,12 @@
// Zig lets you express integer literals in several convenient
// formats. These are all the same value:
//
// const a1: u8 = 65; // decimal
// const a2: u8 = 0x41; // hexadecimal
// const a3: u8 = 0o101; // octal
// const a4: u8 = 0b1000001; // binary
// const a5: u8 = 'A'; // ASCII code point literal
// const a6: u16 = '\u{0041}'; // Unicode code points can take up to 21 bits
// const a1: u8 = 65; // decimal
// const a2: u8 = 0x41; // hexadecimal
// const a3: u8 = 0o101; // octal
// const a4: u8 = 0b1000001; // binary
// const a5: u8 = 'A'; // ASCII code point literal
// const a6: u16 = 'Ȁ'; // Unicode code points can take up to 21 bits
//
// You can also place underscores in numbers to aid readability:
//

View File

@ -39,16 +39,16 @@ const print = @import("std").debug.print;
pub fn main() void {
// The approximate weight of the Space Shuttle upon liftoff
// (including boosters and fuel tank) was 4,480,000 lb.
// (including boosters and fuel tank) was 2,200 tons.
//
// 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;
// We'll convert this weight from tons to kilograms at a
// conversion of 907.18kg to the ton.
const shuttle_weight: f16 = 907.18 * 2200;
// 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} metric tons\n", .{shuttle_weight});
print("Shuttle liftoff weight: {d:.0}kg\n", .{shuttle_weight});
}
// Floating further:

View File

@ -58,7 +58,7 @@ pub fn main() void {
// There is a difference between
// - a value, that overflowed at some point and is now corrupted
// - a single operation that overflows and maybe causes subsequent errors
// In practice we usually notice the overflowed value first and have to work
// In practise we usually notice the overflowed value first and have to work
// our way backwards to the operation that caused the overflow.
//
// If there was no overflow at all while adding 5 to a, what value would

View File

@ -94,35 +94,31 @@ 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: [:0]const u8,
// name: []const u8,
// type: type,
// default_value_ptr: ?*const anyopaque,
// default_value: anytype,
// 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}", .{fields[0].name});
print(" {s}", .{@typeInfo(Narcissus).Struct.fields[0].name});
}
if (fields[1].??? != void) {
print(" {s}", .{fields[1].name});
print(" {s}", .{@typeInfo(Narcissus).Struct.fields[1].name});
}
if (fields[2].??? != void) {
print(" {s}", .{fields[2].name});
print(" {s}", .{@typeInfo(Narcissus).Struct.fields[2].name});
}
// Yuck, look at all that repeated code above! I don't know
@ -140,16 +136,14 @@ 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, 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 ".")
// 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).
//
// 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 {
const indexOf = @import("std").mem.indexOf;
// Turn "065_builtins2.Narcissus" into "Narcissus"
const name = @typeName(myType);
return name[indexOf(u8, name, ".").? + 1 ..];
// Turn '065_builtins2.Narcissus' into 'Narcissus'
return @typeName(myType)[14..];
}

View File

@ -38,16 +38,16 @@ pub fn main() void {
var count = 0;
count += 1;
const a1: [count]u8 = .{'A'} ** count;
var a1: [count]u8 = .{'A'} ** count;
count += 1;
const a2: [count]u8 = .{'B'} ** count;
var a2: [count]u8 = .{'B'} ** count;
count += 1;
const a3: [count]u8 = .{'C'} ** count;
var a3: [count]u8 = .{'C'} ** count;
count += 1;
const a4: [count]u8 = .{'D'} ** count;
var a4: [count]u8 = .{'D'} ** count;
print("{s} {s} {s} {s}\n", .{ a1, a2, a3, a4 });

View File

@ -83,19 +83,19 @@ const DuctError = error{UnmatchedDiameters};
pub fn main() void {
// This is a real duck!
const ducky1 = Duck{
var ducky1 = Duck{
.eggs = 0,
.loudness = 3,
};
// This is not a real duck, but it has quack() and waddle()
// abilities, so it's still a "duck".
const ducky2 = RubberDuck{
var ducky2 = RubberDuck{
.in_bath = false,
};
// This is not even remotely a duck.
const ducky3 = Duct{
var ducky3 = Duct{
.diameter = 17,
.length = 165,
.galvanized = true,

View File

@ -7,7 +7,7 @@
// doing this work.
//
// An 'inline for' is performed at compile time, allowing you to
// programmatically loop through a series of items in situations
// programatically 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) {

View File

@ -39,7 +39,7 @@ pub fn main() void {
// This gets the digit from the "instruction". Can you
// figure out why we subtract '0' from it?
const digit = instructions[i + 1] - '0';
comptime var digit = instructions[i + 1] - '0';
// This 'switch' statement contains the actual work done
// at runtime. At first, this doesn't seem exciting...

View File

@ -110,7 +110,7 @@ const HermitsNotebook = struct {
}
fn checkNote(self: *HermitsNotebook, note: NotebookEntry) void {
const existing_entry = self.getEntry(note.place);
var existing_entry = self.getEntry(note.place);
if (existing_entry == null) {
self.entries[self.end_of_entries] = note;
@ -180,7 +180,7 @@ pub fn main() void {
notebook.checkNote(working_note);
while (notebook.hasNextEntry()) {
const place_entry = notebook.getNextEntry();
var place_entry = notebook.getNextEntry();
for (place_entry.place.paths) |*path| {
working_note = NotebookEntry{

View File

@ -46,7 +46,7 @@ pub fn main() void {
var nums = [_:0]u32{ 1, 2, 3, 4, 5, 6 };
// And here's a zero-terminated many-item pointer:
const ptr: [*:0]u32 = &nums;
var ptr: [*:0]u32 = &nums;
// For fun, let's replace the value at position 3 with the
// sentinel value 0. This seems kind of naughty.
@ -74,11 +74,11 @@ pub fn main() void {
fn printSequence(my_seq: anytype) void {
const my_typeinfo = @typeInfo(@TypeOf(my_seq));
// The TypeInfo contained in my_typeinfo is a union. We use
// a switch to handle printing the Array or Pointer fields,
// The TypeInfo contained in my_type is a union. We use 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:", .{});

View File

@ -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 "<filename>.Bar()".
// * The value of @typeName(MyBar) is "<filename>.Bar()".
// * The value of @typeName(@TypeOf(bar)) is "<filename>.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 "<filename>.<function>__struct_<nnn>".
// of @typeName(struct {}) is "struct:<position in source>".
//
const print = @import("std").debug.print;
@ -48,13 +48,13 @@ pub fn main() void {
// * circle1 should hold i32 integers
// * circle2 should hold f32 floats
//
const circle1 = ??? {
var circle1 = ??? {
.center_x = 25,
.center_y = 70,
.radius = 15,
};
const circle2 = ??? {
var circle2 = ??? {
.center_x = 25.234,
.center_y = 70.999,
.radius = 15.714,

View File

@ -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,15 +95,13 @@ fn printTuple(tuple: anytype) void {
// Each 'field' in this loop is one of these:
//
// pub const StructField = struct {
// name: [:0]const u8,
// name: []const u8,
// type: type,
// default_value_ptr: ?*const anyopaque,
// default_value: anytype,
// 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)

View File

@ -96,7 +96,7 @@ const Insect = union(enum) {
};
pub fn main() !void {
const my_insects = [_]Insect{
var my_insects = [_]Insect{
Insect{ .ant = Ant{ .still_alive = true } },
Insect{ .bee = Bee{ .flowers_visited = 17 } },
Insect{ .grasshopper = Grasshopper{ .distance_hopped = 32 } },

View File

@ -40,7 +40,7 @@
// our well-known "import" for Zig
const std = @import("std");
// and here the new import for C
// and here the new the import for C
const c = @cImport({
@cInclude("unistd.h");
});

View File

@ -1,26 +1,19 @@
//
// Often, C functions are used where no equivalent Zig function exists
// yet. Okay, that's getting less and less. ;-)
//
// Since the integration of a C function is very simple, as already
// yet. Since the integration of a C function is very simple, as already
// seen in the last exercise, it naturally offers itself to use the
// very large variety of C functions for our own programs.
// As an example:
//
// Let's say we have a given angle of 765.2 degrees. If we want to
// normalize that, it means that we have to subtract X * 360 degrees
// to get the correct angle.
// How could we do that? A good method is to use the modulo function.
// But if we write "765.2 % 360", it only works with float values
// that are known at compile time.
// In Zig, we would use @mod(a, b) instead.
//
// Let us now assume that we cannot do this in Zig, but only with
// a C function from the standard library. In the library "math",
// there is a function called "fmod"; the "f" stands for floating
// and means that we can solve modulo for real numbers. With this
// function, it should be possible to normalize our angle.
// Let's go.
// to get the correct angle. How could we do that? A good method is
// to use the modulo function. But if we write "765.2 % 360", it won't
// work, because the standard modulo function works only with integer
// values. In the C library "math", there is a function called "fmod";
// the "f" stands for floating and means that we can solve modulo for
// real numbers. With this function, it should be possible to normalize
// our angle. Let's go.
const std = @import("std");

View File

@ -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 in loops are only supported in 'for' loops.
// At the moment, ranges are only supported in 'for' loops.
//
// Perhaps you recall Exercise 13? We were printing a numeric
// sequence like so:

View File

@ -30,9 +30,9 @@
// std.debug.print("slice_ptr={*}\n", .{slice_ptr});
// }
// Instead of a simple integer or a slice with a constant size,
// this program requires allocating a slice that is the same size
// as an input array.
// Instead of a simple integer or a constant sized slice, this
// program requires a slice to be allocated that is the same size as
// an input array.
// Given a series of numbers, take the running average. In other
// words, each item N should contain the average of the last N
@ -52,7 +52,7 @@ fn runningAverage(arr: []const f64, avg: []f64) void {
pub fn main() !void {
// pretend this was defined by reading in user input
const arr: []const f64 = &[_]f64{ 0.3, 0.2, 0.1, 0.1, 0.4 };
var arr: []const f64 = &[_]f64{ 0.3, 0.2, 0.1, 0.1, 0.4 };
// initialize the allocator
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
@ -64,7 +64,7 @@ pub fn main() !void {
const allocator = arena.allocator();
// allocate memory for this array
const avg: []f64 = ???;
var avg: []f64 = ???;
runningAverage(arr, avg);
std.debug.print("Running Average: ", .{});

View File

@ -1,5 +1,5 @@
//
// Bit manipulation is a very powerful tool, also from Zig.
// Bit manipulations is a very powerful tool just also from Zig.
// Since the dawn of the computer age, numerous algorithms have been
// developed that solve tasks solely by moving, setting, or logically
// combining bits.
@ -8,10 +8,10 @@
// functions where possible. And it is often possible with calculations
// based on integers.
//
// At first glance, it is often not easy to understand what exactly these
// Often it is not easy to understand at first glance what exactly these
// algorithms do when only "numbers" in memory areas change outwardly.
// However, it should never be forgotten that the numbers only represent
// the interpretation of the bit sequences.
// But it must never be forgotten that the numbers only represent the
// interpretation of the bit sequences.
//
// Quasi the reversed case we have otherwise, namely that we represent
// numbers in bit sequences.
@ -20,8 +20,8 @@
//
// Zig provides all the necessary functions to change the bits inside
// a variable. It is distinguished whether the bit change leads to an
// overflow or not. The details are in the Zig documentation in section
// "Table of Operators".
// overflow or not.The details are in the Zig documentation in section
// 10.1 "Table of Operators".
//
// Here are some examples of how the bits of variables can be changed:
//
@ -71,9 +71,9 @@ const print = std.debug.print;
pub fn main() !void {
// Let us use 1101 and 1011 as values for x and y
var x: u8 = 0b1101;
var y: u8 = 0b1011;
// As in the example above, we use 1 and 0 as values for x and y
var x: u8 = 1;
var y: u8 = 0;
// 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 = {b}; y = {b}\n", .{ x, y });
print("x = {d}; y = {d}\n", .{ x, y });
}
// This variable swap takes advantage of the fact that the value resulting

View File

@ -1,5 +1,5 @@
//
// Another useful application for bit manipulation is setting bits as flags.
// Another useful practice for bit manipulation is setting bits as flags.
// This is especially useful when processing lists of something and storing
// the states of the entries, e.g. a list of numbers and for each prime
// number a flag is set.
@ -19,9 +19,9 @@
// For example, you could take an array of bool and set the value to 'true'
// for each letter in the order of the alphabet (a=0; b=1; etc.) found in
// the sentence. However, this is neither memory efficient nor particularly
// fast. Instead we choose a simpler approach that is very similar in principle:
// We define a variable with at least 26 bits (e.g. u32) and set the bit for
// each letter that is found in the corresponding position.
// fast. Instead we take a simpler way, very similar in principle, we define
// a variable with at least 26 bits (e.g. u32) and also set the bit for each
// letter found at the corresponding position.
//
// Zig provides functions for this in the standard library, but we prefer to
// solve it without these extras, after all we want to learn something.
@ -32,24 +32,24 @@ 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 {
// first we check if the string has at least 26 characters
if (str.len < 26) return false;
// we use a 32 bit variable of which we need 26 bits
// we uses a 32 bit variable of which we need 26 bits
var bits: u32 = 0;
// 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:
// since the letters in the ASCII table start at 65
// since the letters in the ASCI table start at 65
// and are numbered sequentially, we simply subtract the
// first letter (in this case the 'a') from the character
// found, and thus get the position of the desired bit

View File

@ -16,13 +16,13 @@
// 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#L33
// https://github.com/ziglang/zig/blob/master/lib/std/fmt.zig#L29
//
// Zig already has a very nice selection of formatting options.
// These can be used in different ways, but generally to convert
// numerical values into various text representations. The results
// can be used for direct output to a terminal or stored for
// later use or written to a file. The latter is useful when
// These can be used in different ways, but typically to convert
// numerical values into various text representations. The
// results can be used for direct output to a terminal or stored
// for later use or written to a file. The latter is useful when
// large amounts of data are to be processed by other programs.
//
// In Ziglings, we are concerned with the output to the console.
@ -45,7 +45,7 @@
// output. Escape sequences can also be written one after the
// other, e.g. "\n\n" will cause two line feeds.
//
// By the way, the result of these escape sequences is passed
// By the way, the result of these escape sequences are passed
// directly to the terminal program. Other than translating them
// into control codes, escape sequences have nothing to do with
// Zig. Zig knows nothing about "line feeds" or "tabs" or
@ -60,7 +60,7 @@
// variety of formatting instructions. It's basically a tiny
// language of its own. Here's a numeric example:
//
// print("Catch-0x{x:0>4}.", .{twenty_two});
// print("Catch-{x:0>4}.", .{twenty_two});
//
// This formatting instruction outputs a hexadecimal number with
// leading zeros:
@ -95,7 +95,7 @@
// ...
//
// Without string formatting, this would be a more challenging
// assignment because the number of digits in the numbers varies
// assignment because the number of digits in the numbers vary
// from 1 to 3. But formatting can help us with that.
//
const std = @import("std");

View File

@ -41,12 +41,12 @@ pub fn main() void {
for (hex_nums, ???) |hn, ???| {
if (hn != dn) {
print("Uh oh! Found a mismatch: {d} vs {d}\n", .{ hn, dn });
std.debug.print("Uh oh! Found a mismatch: {d} vs {d}\n", .{ hn, dn });
return;
}
}
print("Arrays match!\n", .{});
std.debug.print("Arrays match!\n", .{});
}
//
// You are perhaps wondering what happens if one of the two lists

View File

@ -1,9 +1,9 @@
//
// The functionality of the standard library is becoming increasingly
// important in Zig. First of all, it is helpful to take a look at how
// important in Zig. On the one hand, it is helpful to look at how
// the individual functions are implemented. Because this is wonderfully
// suitable as a template for your own functions. In addition these
// standard functions are part of the basic configuration of Zig.
// suitable as a template for your own functions. On the other hand,
// these standard functions are part of the basic equipment of Zig.
//
// This means that they are always available on every system.
// Therefore it is worthwhile to deal with them also in Ziglings.
@ -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");

View File

@ -1,134 +0,0 @@
//
// Whenever there is a lot to calculate, the question arises as to how
// tasks can be carried out simultaneously. We have already learned about
// one possibility, namely asynchronous processes, in Exercises 84-91.
//
// However, the computing power of the processor is only distributed to
// the started and running tasks, which always reaches its limits when
// pure computing power is called up.
//
// For example, in blockchains based on proof of work, the miners have
// to find a nonce for a certain character string so that the first m bits
// in the hash of the character string and the nonce are zeros.
// As the miner who can solve the task first receives the reward, everyone
// tries to complete the calculations as quickly as possible.
//
// This is where multithreading comes into play, where tasks are actually
// distributed across several cores of the CPU or GPU, which then really
// means a multiplication of performance.
//
// The following diagram roughly illustrates the difference between the
// various types of process execution.
// The 'Overall Time' column is intended to illustrate how the time is
// affected if, instead of one core as in synchronous and asynchronous
// processing, a second core now helps to complete the work in multithreading.
//
// In the ideal case shown, execution takes only half the time compared
// to the synchronous single thread. And even asynchronous processing
// is only slightly faster in comparison.
//
//
// Synchronous Asynchronous
// Processing Processing Multithreading
//
// Thread 1 Thread 1 Thread 1 Thread 2
// Overall Time
//
//
// T T T T
// a a a a
// s s s s
// k k k k
//
// 1 1 1 3
//
// 5 Sec
//
// Blocking T T T
// a a a
// s s s 8 Sec
// k k k
// T
// a 2 2 4
// s
// k 10 Sec
//
// 1 T
// a
// s
// k
// T
// a 1
// s
// k
//
// 2
//
//
//
//
//
// The diagram was modeled on the one in a blog in which the differences
// between asynchronous processing and multithreading are explained in detail:
// https://blog.devgenius.io/multi-threading-vs-asynchronous-programming-what-is-the-difference-3ebfe1179a5
//
// Our exercise is essentially about clarifying the approach in Zig and
// therefore we try to keep it as simple as possible.
// Multithreading in itself is already difficult enough. ;-)
//
const std = @import("std");
pub fn main() !void {
// This is where the preparatory work takes place
// before the parallel processing begins.
std.debug.print("Starting work...\n", .{});
// These curly brackets are very important, they are necessary
// to enclose the area where the threads are called.
// Without these brackets, the program would not wait for the
// end of the threads and they would continue to run beyond the
// end of the program.
{
// Now we start the first thread, with the number as parameter
const handle = try std.Thread.spawn(.{}, thread_function, .{1});
// Waits for the thread to complete,
// then deallocates any resources created on `spawn()`.
defer handle.join();
// Second thread
const handle2 = try std.Thread.spawn(.{}, thread_function, .{-4}); // that can't be right?
defer handle2.join();
// Third thread
const handle3 = try std.Thread.spawn(.{}, thread_function, .{3});
defer ??? // <-- something is missing
// After the threads have been started,
// they run in parallel and we can still do some work in between.
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
// the threads have run through, if this has not yet been the case.
std.debug.print("Zig is cool!\n", .{});
}
// 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.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.Thread.sleep(work_time * std.time.ns_per_s);
std.debug.print("thread {d}: {s}\n", .{ num, "finished." });
}
// This is the easiest way to run threads in parallel.
// In general, however, more management effort is required,
// e.g. by setting up a pool and allowing the threads to communicate
// with each other using semaphores.
//
// But that's a topic for another exercise.

View File

@ -1,107 +0,0 @@
//
// Now that we are familiar with the principles of multi-threading,
// let's boldly venture into a practical example from mathematics.
// We will determine the circle number PI with sufficient accuracy.
//
// There are different methods for this, and some of them are several
// hundred years old. For us, the dusty procedures are surprisingly well
// suited to our exercise. Because the mathematicians of the time didn't
// have fancy computers with which we can calculate something like this
// in seconds today.
// Whereby, of course, it depends on the accuracy, i.e. how many digits
// after the decimal point we are interested in.
// But these old procedures can still be tackled with paper and pencil,
// which is why they are easier for us to understand.
// At least for me. ;-)
//
// So let's take a mental leap back a few years.
// Around 1672 (if you want to know and read about it in detail, you can
// do so on Wikipedia, for example), various mathematicians once again
// discovered a method of approaching the circle number PI.
// 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 Leibniz in 1682 in the journal
// "Acta Eruditorum".
// 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:
//
// 4 4 4 4 4
// PI = --- - --- + --- - --- + --- ...
// 1 3 5 7 9
//
// As you can clearly see, the series starts with the whole number 4 and
// approaches the circle number by subtracting and adding smaller and
// smaller parts of 4. Pretty much everyone has learned PI = 3.14 at school,
// but very few people remember other digits, and this is rarely necessary
// 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
// to calculate for which accuracy?
//
// The answer is chewing, to get 8 digits after the decimal point we need
// 1,000,000,000 partial values. And for each additional digit we have to
// add a zero.
// Even fast computers - and I mean really fast computers - get a bit warmer
// on the CPU when it comes to really many digits. But the 8 digits are
// enough for us for now, because we want to understand the principle and
// nothing more, right?
//
// 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
// round.
// But wait! The partial values are alternately added and subtracted.
// This could also be achieved with one loop, but not very elegantly.
// It also makes sense to split this between two CPUs, one calculates
// the positive values and the other the negative values. And so we can
// simply start two threads and add everything up at the end and we're
// done.
// We just have to remember that if only the positive or negative values
// are calculated, the distances are twice as large, i.e. 4.
//
// So that the whole thing has a real learning effect, the first thread
// call is specified and you have to make the second.
// But don't worry, it will work out. :-)
//
const std = @import("std");
pub fn main() !void {
const count = 1_000_000_000;
var pi_plus: f64 = 0;
var pi_minus: f64 = 0;
{
// First thread to calculate the plus numbers.
const handle1 = try std.Thread.spawn(.{}, thread_pi, .{ &pi_plus, 5, count });
defer handle1.join();
// Second thread to calculate the minus numbers.
???
}
// Here we add up the results.
std.debug.print("PI ≈ {d:.8}\n", .{4 + pi_plus - pi_minus});
}
fn thread_pi(pi: *f64, begin: u64, end: u64) !void {
var n: u64 = begin;
while (n < end) : (n += 4) {
pi.* += 4 / @as(f64, @floatFromInt(n));
}
}
// If you wish, you can increase the number of loop passes, which
// improves the number of digits.
//
// But be careful:
// In order for parallel processing to really show its strengths,
// the compiler must be given the "-O ReleaseFast" flag when it
// is created. Otherwise the debug functions slow down the speed
// to such an extent that seconds become minutes during execution.
//
// And you should remove the formatting restriction in "print",
// otherwise you will not be able to see the additional digits.

View File

@ -1,92 +0,0 @@
//
// Until now, we've only been printing our output in the console,
// which is good enough for fighting aliens and hermit bookkeeping.
//
// However, many other tasks 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.
//
// 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
//
// 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
const std = @import("std");
pub fn main() !void {
// first we get the current working directory
const cwd: std.fs.Dir = std.fs.cwd();
// then we'll try to make a new directory /output/
// to store our output files.
cwd.makeDir("output") catch |e| switch (e) {
// there is a 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
// by doing nothing
//
// we want to catch error.PathAlreadyExists and do nothing
??? => {},
// 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...
// opening a directory might fail!
// what should we do here?
var output_dir: std.fs.Dir = cwd.openDir("output", .{});
defer output_dir.close();
// we try to open the file `zigling.txt`,
// and propagate any error up
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
// 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...
file.close();
// 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
// 2. print the content of the file in the console with the following command
// >> cat ./output/zigling.txt
//
//
// More on Creating files
//
// notice in:
// ... try output_dir.createFile("zigling.txt", .{});
// ^^^
// we passed this anonymous struct to the function call
//
// this is the struct `CreateFlag` with default fields
// {
// read: bool = false,
// truncate: bool = true,
// exclusive: bool = false,
// lock: Lock = .none,
// lock_nonblocking: bool = false,
// mode: Mode = default_mode
// }
//
// 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:
// 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?

View File

@ -1,52 +0,0 @@
//
// Prerequisite :
// - exercise/106_files.zig, or
// - 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.
//
// I am assuming that you've created the appropriate files for this to work.
//
// 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 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
const std = @import("std");
pub fn main() !void {
// Get the current working directory
const cwd = std.fs.cwd();
// try to open ./output assuming you did your 106_files exercise
var output_dir = try cwd.openDir("output", .{});
defer output_dir.close();
// try to open the file
const file = try output_dir.openFile("zigling.txt", .{});
defer file.close();
// 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;
// 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?
// https://ziglang.org/documentation/master/std/#std.fs.File
// 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.
// 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
});
}

View File

@ -1,79 +0,0 @@
//
// 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", .{});
}

View File

@ -1,147 +0,0 @@
// 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});
}

View File

@ -1,484 +0,0 @@
// ----------------------------------------------------------------------------
// 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();
}

View File

@ -12,12 +12,6 @@
# 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.
@ -29,12 +23,9 @@ fi
# Which version we have?
echo "Zig version" $(zig version)
echo "Eowyn version 25.1.9, let's try our magic power."
echo "Eowyn version 23.10.5.1, 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
@ -63,6 +54,3 @@ 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

View File

@ -1,11 +1,11 @@
--- exercises/025_errors5.zig 2023-11-21 14:22:48.159250165 +0100
+++ answers/025_errors5.zig 2023-11-21 14:25:01.338277886 +0100
--- exercises/025_errors5.zig 2023-10-03 22:15:22.122241138 +0200
+++ answers/025_errors5.zig 2023-10-05 20:04:06.952764946 +0200
@@ -26,7 +26,7 @@
// This function needs to return any error which might come back from detect().
// Please use a "try" statement rather than a "catch".
//
- const x = detect(n);
+ const x = try detect(n);
- var x = detect(n);
+ var x = try detect(n);
return x + 5;
}

View File

@ -1,9 +1,9 @@
--- exercises/026_hello2.zig 2025-07-22 09:55:51.337832401 +0200
+++ answers/026_hello2.zig 2025-07-22 10:00:11.233348058 +0200
--- exercises/026_hello2.zig 2023-10-03 22:15:22.122241138 +0200
+++ answers/026_hello2.zig 2023-10-05 20:04:06.959431737 +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.interface.print("Hello world!\n", .{});
+ try stdout.interface.print("Hello world!\n", .{});
- stdout.print("Hello world!\n", .{});
+ try stdout.print("Hello world!\n", .{});
}

View File

@ -1,15 +1,15 @@
--- exercises/034_quiz4.zig 2025-07-22 09:55:51.337832401 +0200
+++ answers/034_quiz4.zig 2025-07-22 10:05:08.320323184 +0200
--- exercises/034_quiz4.zig 2023-10-03 22:15:22.122241138 +0200
+++ answers/034_quiz4.zig 2023-10-05 20:04:06.996099091 +0200
@@ -9,10 +9,10 @@
const NumError = error{IllegalNumber};
-pub fn main() void {
+pub fn main() !void {
var stdout = std.fs.File.stdout().writer(&.{});
const stdout = std.io.getStdOut().writer();
- const my_num: u32 = getNumber();
+ const my_num: u32 = try getNumber();
try stdout.interface.print("my_num={}\n", .{my_num});
try stdout.print("my_num={}\n", .{my_num});
}

View File

@ -1,19 +1,20 @@
--- 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 @@
--- exercises/046_optionals2.zig 2023-10-03 22:15:22.122241138 +0200
+++ answers/046_optionals2.zig 2023-10-05 20:04:07.049433424 +0200
@@ -21,7 +21,7 @@
const Elephant = struct {
letter: u8,
- tail: *Elephant = null, // Hmm... tail needs something...
+ tail: ?*Elephant = null, // Hmm... tail needs something...
+ tail: ?*Elephant = null, // <---- make this optional!
visited: bool = false,
};
@@ -66,6 +66,6 @@
@@ -51,7 +51,7 @@
// We should stop once we encounter a tail that
// does NOT point to another element. What can
// we put here to make that happen?
- if (e.tail == null) ???;
+ if (e.tail == null) break;
// HINT: We want something similar to what `.?` does,
// but instead of ending the program, we want to exit the loop...
- e = e.tail ???
+ e = e.tail orelse break;
e = e.tail.?;
}
}

View File

@ -1,5 +1,5 @@
--- exercises/051_values.zig 2024-03-14 23:25:42.695020607 +0100
+++ answers/051_values.zig 2024-03-14 23:28:34.525109174 +0100
--- exercises/051_values.zig 2023-10-03 22:15:22.122241138 +0200
+++ answers/051_values.zig 2023-10-05 20:04:07.072767194 +0200
@@ -87,7 +87,7 @@
// Let's assign the std.debug.print function to a const named
// "print" so that we can use this new name later!
@ -9,7 +9,7 @@
// Now let's look at assigning and pointing to values in Zig.
//
@@ -163,13 +163,13 @@
@@ -152,13 +152,13 @@
print("XP before:{}, ", .{glorp.experience});
// Fix 1 of 2 goes here:

View File

@ -1,8 +1,8 @@
--- exercises/058_quiz7.zig 2024-10-28 09:06:49.448505460 +0100
+++ answers/058_quiz7.zig 2024-10-28 09:35:14.631932322 +0100
--- exercises/058_quiz7.zig 2023-10-03 22:15:22.125574535 +0200
+++ answers/058_quiz7.zig 2023-10-05 20:04:07.106101152 +0200
@@ -192,8 +192,8 @@
// Oops! The hermit forgot how to capture the union values
// in a switch statement. Please capture each value as
// in a switch statement. Please capture both values as
// 'p' so the print statements work!
- .place => print("{s}", .{p.name}),
- .path => print("--{}->", .{p.dist}),

View File

@ -1,11 +1,11 @@
--- exercises/060_floats.zig 2025-03-03 20:23:40.255443963 +0400
+++ answers/060_floats.zig 2025-03-03 20:29:58.554854977 +0400
--- exercises/060_floats.zig 2023-10-03 22:15:22.125574535 +0200
+++ answers/060_floats.zig 2023-10-05 20:04:07.112767942 +0200
@@ -43,7 +43,7 @@
//
// 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;
// We'll convert this weight from tons to kilograms at a
// conversion of 907.18kg to the ton.
- const shuttle_weight: f16 = 907.18 * 2200;
+ const shuttle_weight: f32 = 907.18 * 2200.0;
// By default, float values are formatted in scientific
// notation. Try experimenting with '{d}' and '{d:.3}' to see

View File

@ -1,5 +1,5 @@
--- exercises/065_builtins2.zig 2025-06-17 13:58:07.857258167 +0200
+++ answers/065_builtins2.zig 2025-06-17 13:56:36.630415938 +0200
--- exercises/065_builtins2.zig 2023-10-03 22:15:22.125574535 +0200
+++ answers/065_builtins2.zig 2023-10-05 20:04:07.136101712 +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. ", .{
@@ -113,15 +113,15 @@
@@ -109,15 +109,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}", .{fields[0].name});
print(" {s}", .{@typeInfo(Narcissus).Struct.fields[0].name});
}
- if (fields[1].??? != void) {
+ if (fields[1].type != void) {
print(" {s}", .{fields[1].name});
print(" {s}", .{@typeInfo(Narcissus).Struct.fields[1].name});
}
- if (fields[2].??? != void) {
+ if (fields[2].type != void) {
print(" {s}", .{fields[2].name});
print(" {s}", .{@typeInfo(Narcissus).Struct.fields[2].name});
}

View File

@ -1,5 +1,5 @@
--- exercises/067_comptime2.zig 2023-11-21 14:36:12.080295365 +0100
+++ answers/067_comptime2.zig 2023-11-21 15:11:50.814098876 +0100
--- exercises/067_comptime2.zig 2023-10-03 22:15:22.125574535 +0200
+++ answers/067_comptime2.zig 2023-10-05 20:04:07.146101899 +0200
@@ -35,7 +35,7 @@
// In this contrived example, we've decided to allocate some
// arrays using a variable count! But something's missing...
@ -8,4 +8,4 @@
+ comptime var count = 0;
count += 1;
const a1: [count]u8 = .{'A'} ** count;
var a1: [count]u8 = .{'A'} ** count;

View File

@ -1,8 +1,8 @@
--- exercises/071_comptime6.zig 2024-09-02 19:21:50.250454978 +0200
+++ answers/071_comptime6.zig 2024-09-02 19:21:23.553250563 +0200
--- exercises/071_comptime6.zig 2023-10-03 22:15:22.125574535 +0200
+++ answers/071_comptime6.zig 2023-10-05 20:04:07.162768879 +0200
@@ -40,7 +40,7 @@
const fields = @typeInfo(Narcissus).@"struct".fields;
const fields = @typeInfo(Narcissus).Struct.fields;
- ??? {
+ inline for (fields) |field| {

View File

@ -1,5 +1,5 @@
--- exercises/075_quiz8.zig 2023-11-21 14:48:15.440702720 +0100
+++ answers/075_quiz8.zig 2023-11-21 14:50:23.453311616 +0100
--- exercises/075_quiz8.zig 2023-10-03 22:15:22.125574535 +0200
+++ answers/075_quiz8.zig 2023-10-05 20:04:07.182769252 +0200
@@ -49,7 +49,11 @@
//
// Please fill in the body of this function!

View File

@ -1,5 +1,5 @@
--- exercises/076_sentinels.zig 2024-09-02 19:27:04.336781039 +0200
+++ answers/076_sentinels.zig 2024-09-02 19:26:15.709134934 +0200
--- exercises/076_sentinels.zig 2023-10-03 22:15:22.125574535 +0200
+++ answers/076_sentinels.zig 2023-10-05 20:04:07.186102649 +0200
@@ -82,7 +82,7 @@
print("Array:", .{});

View File

@ -1,18 +1,18 @@
--- exercises/080_anonymous_structs.zig 2023-11-21 14:52:54.312749682 +0100
+++ answers/080_anonymous_structs.zig 2023-11-21 14:52:43.909225238 +0100
--- exercises/080_anonymous_structs.zig 2023-10-03 22:15:22.125574535 +0200
+++ answers/080_anonymous_structs.zig 2023-10-05 20:04:07.202769626 +0200
@@ -48,13 +48,13 @@
// * circle1 should hold i32 integers
// * circle2 should hold f32 floats
//
- const circle1 = ??? {
+ const circle1 = Circle(i32){
- var circle1 = ??? {
+ var circle1 = Circle(i32){
.center_x = 25,
.center_y = 70,
.radius = 15,
};
- const circle2 = ??? {
+ const circle2 = Circle(f32){
- var circle2 = ??? {
+ var circle2 = Circle(f32){
.center_x = 25.234,
.center_y = 70.999,
.radius = 15.714,

View File

@ -1,11 +1,11 @@
--- 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
--- 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
@@ -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:
@@ -119,9 +119,9 @@
@@ -117,9 +117,9 @@
//
// The first field should print as: "0"(bool):true
print("\"{s}\"({any}):{any} ", .{

View File

@ -0,0 +1,11 @@
--- 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 {

View File

@ -0,0 +1,10 @@
--- 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 {

View File

@ -0,0 +1,16 @@
--- 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", .{});
}

View File

@ -0,0 +1,21 @@
--- 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 {}
}
}

View File

@ -0,0 +1,11 @@
--- 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});
}

View File

@ -0,0 +1,13 @@
--- 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 });
}

View File

@ -0,0 +1,11 @@
--- 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});
}

View File

@ -0,0 +1,26 @@
--- 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", .{});
}

View File

@ -1,6 +1,6 @@
--- exercises/094_c_math.zig 2024-02-28 12:50:35.789939935 +0100
+++ answers/094_c_math.zig 2024-02-28 12:53:57.910309471 +0100
@@ -26,7 +26,7 @@
--- exercises/094_c_math.zig 2023-10-22 14:00:02.909379696 +0200
+++ answers/094_c_math.zig 2023-10-22 14:02:46.709025235 +0200
@@ -19,7 +19,7 @@
const c = @cImport({
// What do we need here?

View File

@ -1,11 +1,11 @@
--- exercises/096_memory_allocation.zig 2023-11-21 14:55:33.805678390 +0100
+++ answers/096_memory_allocation.zig 2023-11-21 14:56:00.236163484 +0100
--- exercises/096_memory_allocation.zig 2023-10-03 22:15:22.125574535 +0200
+++ answers/096_memory_allocation.zig 2023-10-05 20:04:07.276104333 +0200
@@ -64,7 +64,7 @@
const allocator = arena.allocator();
// allocate memory for this array
- const avg: []f64 = ???;
+ const avg: []f64 = try allocator.alloc(f64, arr.len);
- var avg: []f64 = ???;
+ var avg: []f64 = try allocator.alloc(f64, arr.len);
runningAverage(arr, avg);
std.debug.print("Running Average: ", .{});

View File

@ -1,5 +1,5 @@
--- 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
--- 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
@@ -80,7 +80,7 @@
y ^= x;
@ -7,5 +7,5 @@
- ???;
+ x ^= y;
print("x = {b}; y = {b}\n", .{ x, y });
print("x = {d}; y = {d}\n", .{ x, y });
}

View File

@ -1,5 +1,5 @@
--- exercises/099_formatting.zig 2024-11-07 21:45:10.459123650 +0100
+++ answers/099_formatting.zig 2024-11-07 21:43:55.154345991 +0100
--- exercises/099_formatting.zig 2023-10-03 22:15:22.125574535 +0200
+++ answers/099_formatting.zig 2023-10-05 20:04:07.292771311 +0200
@@ -131,7 +131,7 @@
for (0..size) |b| {
// What formatting is needed here to make our columns

View File

@ -7,5 +7,5 @@
- for (hex_nums, ???) |hn, ???| {
+ for (hex_nums, dec_nums) |hn, dn| {
if (hn != dn) {
print("Uh oh! Found a mismatch: {d} vs {d}\n", .{ hn, dn });
std.debug.print("Uh oh! Found a mismatch: {d} vs {d}\n", .{ hn, dn });
return;

View File

@ -1,17 +0,0 @@
--- exercises/104_threading.zig 2024-04-10 19:12:29.878856370 +0200
+++ answers/104_threading.zig 2024-04-10 19:11:22.304265713 +0200
@@ -97,12 +97,12 @@
defer handle.join();
// Second thread
- const handle2 = try std.Thread.spawn(.{}, thread_function, .{-4}); // that can't be right?
+ const handle2 = try std.Thread.spawn(.{}, thread_function, .{2});
defer handle2.join();
// Third thread
const handle3 = try std.Thread.spawn(.{}, thread_function, .{3});
- defer ??? // <-- something is missing
+ defer handle3.join();
// After the threads have been started,
// they run in parallel and we can still do some work in between.

View File

@ -1,13 +0,0 @@
--- exercises/105_threading2.zig 2024-03-23 16:35:14.754540802 +0100
+++ answers/105_threading2.zig 2024-03-23 16:38:00.577539733 +0100
@@ -81,8 +81,8 @@
defer handle1.join();
// Second thread to calculate the minus numbers.
- ???
-
+ const handle2 = try std.Thread.spawn(.{}, thread_pi, .{ &pi_minus, 3, count });
+ defer handle2.join();
}
// Here we add up the results.
std.debug.print("PI ≈ {d:.8}\n", .{4 + pi_plus - pi_minus});

View File

@ -1,29 +0,0 @@
--- 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
- ??? => {},
+ error.PathAlreadyExists => {},
// if there's any other unexpected error we just propagate it through
else => return e,
};
@@ -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", .{});
+ var output_dir: std.fs.Dir = try cwd.openDir("output", .{});
defer output_dir.close();
// we try to open the file `zigling.txt`,
@@ -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...
- file.close();
+ defer file.close();
// you are not allowed to move these two lines above the file closing line!
const byte_written = try file.write("It's zigling time!");

View File

@ -1,26 +0,0 @@
--- 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});
@@ -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 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?
std.debug.print("Successfully Read {d} bytes: {s}\n", .{
bytes_read,
- content, // change this line only
+ content[0..bytes_read], // change this line only
});
}

View File

@ -1,18 +0,0 @@
--- 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", .{});
}

View File

@ -1,13 +0,0 @@
--- 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;
}

View File

@ -1,56 +0,0 @@
--- 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();

View File

@ -8,8 +8,8 @@ const mem = std.mem;
const Allocator = std.mem.Allocator;
const Child = std.process.Child;
const Build = std.Build;
const LazyPath = std.Build.LazyPath;
const Build = std.build;
const FileSource = std.Build.FileSource;
const Reader = fs.File.Reader;
const RunStep = std.Build.RunStep;
const Step = Build.Step;
@ -33,7 +33,7 @@ pub fn addCliTests(b: *std.Build, exercises: []const Exercise) *Step {
const n = ex.number();
const cmd = b.addSystemCommand(&.{
b.graph.zig_exe,
b.zig_exe,
"build",
"-Dhealed",
b.fmt("-Dhealed-path={s}", .{tmp_path}),
@ -50,7 +50,7 @@ pub fn addCliTests(b: *std.Build, exercises: []const Exercise) *Step {
case_step.dependOn(&verify.step);
}
const cleanup = b.addRemoveDirTree(.{ .src_path = .{ .owner = b, .sub_path = tmp_path } });
const cleanup = b.addRemoveDirTree(tmp_path);
cleanup.step.dependOn(case_step);
step.dependOn(&cleanup.step);
@ -69,7 +69,7 @@ pub fn addCliTests(b: *std.Build, exercises: []const Exercise) *Step {
// TODO: when an exercise is modified, the cache is not invalidated.
const cmd = b.addSystemCommand(&.{
b.graph.zig_exe,
b.zig_exe,
"build",
"-Dhealed",
b.fmt("-Dhealed-path={s}", .{tmp_path}),
@ -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(.{ .src_path = .{ .owner = b, .sub_path = tmp_path } });
const cleanup = b.addRemoveDirTree(tmp_path);
cleanup.step.dependOn(&verify.step);
step.dependOn(&cleanup.step);
@ -99,7 +99,7 @@ pub fn addCliTests(b: *std.Build, exercises: []const Exercise) *Step {
const n = ex.number();
const cmd = b.addSystemCommand(&.{
b.graph.zig_exe,
b.zig_exe,
"build",
b.fmt("-Dn={}", .{n}),
});
@ -132,9 +132,9 @@ fn createCase(b: *Build, name: []const u8) *Step {
const CheckNamedStep = struct {
step: Step,
exercise: Exercise,
stderr: LazyPath,
stderr: FileSource,
pub fn create(owner: *Build, exercise: Exercise, stderr: LazyPath) *CheckNamedStep {
pub fn create(owner: *Build, exercise: Exercise, stderr: FileSource) *CheckNamedStep {
const self = owner.allocator.create(CheckNamedStep) catch @panic("OOM");
self.* = .{
.step = Step.init(.{
@ -150,9 +150,9 @@ const CheckNamedStep = struct {
return self;
}
fn make(step: *Step, _: Step.MakeOptions) !void {
fn make(step: *Step, _: *std.Progress.Node) !void {
const b = step.owner;
const self: *CheckNamedStep = @alignCast(@fieldParentPtr("step", step));
const self = @fieldParentPtr(CheckNamedStep, "step", step);
const ex = self.exercise;
const stderr_file = try fs.cwd().openFile(
@ -161,7 +161,7 @@ const CheckNamedStep = struct {
);
defer stderr_file.close();
var stderr = stderr_file.readerStreaming(&.{});
const stderr = stderr_file.reader();
{
// 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);
}
};
@ -180,12 +180,12 @@ const CheckNamedStep = struct {
const CheckStep = struct {
step: Step,
exercises: []const Exercise,
stderr: LazyPath,
stderr: FileSource,
pub fn create(
owner: *Build,
exercises: []const Exercise,
stderr: LazyPath,
stderr: FileSource,
) *CheckStep {
const self = owner.allocator.create(CheckStep) catch @panic("OOM");
self.* = .{
@ -202,9 +202,9 @@ const CheckStep = struct {
return self;
}
fn make(step: *Step, _: Step.MakeOptions) !void {
fn make(step: *Step, _: *std.Progress.Node) !void {
const b = step.owner;
const self: *CheckStep = @alignCast(@fieldParentPtr("step", step));
const self = @fieldParentPtr(CheckStep, "step", step);
const exercises = self.exercises;
const stderr_file = try fs.cwd().openFile(
@ -213,7 +213,7 @@ const CheckStep = struct {
);
defer stderr_file.close();
var stderr = stderr_file.readerStreaming(&.{});
const stderr = stderr_file.reader();
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,9 +297,12 @@ fn check(
}
}
fn readLine(reader: *fs.File.Reader, buf: []u8) !?[]const u8 {
try reader.interface.readSliceAll(buf);
return mem.trimRight(u8, buf, " \r\n");
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;
}
/// Fails with a custom error message.
@ -322,9 +325,9 @@ const FailStep = struct {
return self;
}
fn make(step: *Step, _: Step.MakeOptions) !void {
fn make(step: *Step, _: *std.Progress.Node) !void {
const b = step.owner;
const self: *FailStep = @alignCast(@fieldParentPtr("step", step));
const self = @fieldParentPtr(FailStep, "step", step);
try step.result_error_msgs.append(b.allocator, self.error_msg);
return error.MakeFailed;
@ -365,9 +368,9 @@ const HealStep = struct {
return self;
}
fn make(step: *Step, _: Step.MakeOptions) !void {
fn make(step: *Step, _: *std.Progress.Node) !void {
const b = step.owner;
const self: *HealStep = @alignCast(@fieldParentPtr("step", step));
const self = @fieldParentPtr(HealStep, "step", step);
return heal(b.allocator, self.exercises, self.work_path);
}
@ -402,8 +405,7 @@ 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 rand_hex64 = std.fmt.hex(rand_int);
const tmp_dir_sub_path = "tmp" ++ fs.path.sep_str ++ rand_hex64;
const tmp_dir_sub_path = "tmp" ++ fs.path.sep_str ++ Build.hex64(rand_int);
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);