62 Commits

Author SHA1 Message Date
Chris Boesch
6aaf048935 Merge pull request 'removed unnecessary shell prompt' (#449) from readme-fixes into main
Reviewed-on: https://codeberg.org/ziglings/exercises/pulls/449
2026-06-04 20:17:48 +02:00
Chris Boesch
febc5e8bd2 removed unnecessary shell prompt 2026-06-04 20:16:35 +02:00
Chris Boesch
b7ff71cb9e Merge pull request 'Fix exercises 65, 71, 82 due to the build system update' (#443) from mark2185/exercises:fix-build-update into main
Reviewed-on: https://codeberg.org/ziglings/exercises/pulls/443
2026-06-03 18:37:25 +02:00
Chris Boesch
a403436fe8 Merge branch 'main' into fix-build-update 2026-06-03 17:39:58 +02:00
Chris Boesch
beeca8d510 Merge pull request 'Elrond has moved to Rivendell (where he lives)' (#447) from rivendell into main
Reviewed-on: https://codeberg.org/ziglings/exercises/pulls/447
2026-06-02 20:26:16 +02:00
Chris Boesch
01ad296114 Elrond has moved to Rivendell (where he lives) 2026-06-02 20:17:48 +02:00
Chris Boesch
9b87962595 Merge pull request 'fixed patch file path' (#446) from contribute-fix into main
Reviewed-on: https://codeberg.org/ziglings/exercises/pulls/446
2026-06-02 20:05:42 +02:00
Chris Boesch
9d6ef1f5ba fixed patch file path 2026-06-02 20:03:18 +02:00
Chris Boesch
fadd2ea4c7 Merge pull request 'fixed minor issues' (#444) from elrond-fixes into main
Reviewed-on: https://codeberg.org/ziglings/exercises/pulls/444
2026-06-02 12:10:11 +02:00
Chris Boesch
e69a865ce3 fixed minor issues 2026-06-02 11:56:34 +02:00
Chris Boesch
f6dda8181a Merge pull request 'Add another defer exercise' (#438) from mark2185/exercises:defer3 into main
Reviewed-on: https://codeberg.org/ziglings/exercises/pulls/438
2026-06-02 08:56:50 +02:00
Luka Markušić
bd55c4ac5a fixup! Add another defer exercise 2026-06-02 06:12:06 +02:00
Luka Markušić
e61bedaa25 Add another defer exercise 2026-06-01 22:32:11 +02:00
Chris Boesch
ac9f96459c Merge pull request 'fix removed array multiplication' (#436) from mark2185/exercises:array_mult into main
Reviewed-on: https://codeberg.org/ziglings/exercises/pulls/436
2026-06-01 20:50:08 +02:00
Luka Markušić
014560c3f5 fix removed array multiplication 2026-06-01 16:05:36 +02:00
Luka Markušić
3b865a0c17 Fix 082_anonymous_structs3.zig because of new build system 2026-06-01 16:00:27 +02:00
Luka Markušić
63c798637c Fix 071_comptime6.zig because of new build system 2026-06-01 15:37:05 +02:00
Luka Markušić
4480762e83 Fix 065_builtins2.zig because of new build system 2026-06-01 15:34:01 +02:00
Chris Boesch
4ce3ed23f3 Merge pull request 'updated version in history' (#439) from build-system-fix into main
Reviewed-on: https://codeberg.org/ziglings/exercises/pulls/439
2026-06-01 00:23:49 +02:00
Chris Boesch
8db82ef84e updated version in history 2026-06-01 00:22:32 +02:00
Chris Boesch
6d23c876d6 Merge pull request 'started fix for new build system' (#437) from build-system-fix into main
Reviewed-on: https://codeberg.org/ziglings/exercises/pulls/437
2026-06-01 00:20:31 +02:00
Chris Boesch
fa36a4520f removed ziglings logic from build file 2026-05-31 23:54:20 +02:00
Chris Boesch
7889d15b84 moved ziglings logic to elrond 2026-05-31 23:48:17 +02:00
Chris Boesch
8a0077e83b Merge pull request 'fix typos' (#435) from mark2185/exercises:main into main
Reviewed-on: https://codeberg.org/ziglings/exercises/pulls/435
2026-05-31 21:12:39 +02:00
Chris Boesch
762d8b8fa5 started fix for new build system 2026-05-31 20:00:50 +02:00
Luka Markušić
7300e87028 fix contributing instructions for patches 2026-05-31 19:10:30 +02:00
Luka Markušić
1c3238d619 fix typos 2026-05-31 19:10:30 +02:00
Chris Boesch
000e9448d5 Merge pull request '051: add a comma' (#429) from xiaolizhi/exercises:comma-fix into main
Reviewed-on: https://codeberg.org/ziglings/exercises/pulls/429
2026-05-25 12:49:37 +02:00
xiaolizhi
f434dbfc01 051: add a comma 2026-05-25 10:37:05 +02:00
Chris Boesch
8468040a8e Merge pull request 'added expalantion for const pointer' (#428) from pointer into main
Reviewed-on: https://codeberg.org/ziglings/exercises/pulls/428
2026-05-22 22:27:15 +02:00
Chris Boesch
1d965491c1 added expalantion for const pointer 2026-05-22 21:58:19 +02:00
Chris Boesch
926fc04d81 Merge pull request 'Instruct user not to worry about sentinel syntax in 065_builtins2' (#425) from nickgrim/exercises:sentinel_warning into main
Reviewed-on: https://codeberg.org/ziglings/exercises/pulls/425
2026-05-18 19:50:18 +02:00
Chris Boesch
6b8bbfd980 Merge pull request 'Minor grammar fix to comment in 005_arrays2' (#426) from nickgrim/exercises:main into main
Reviewed-on: https://codeberg.org/ziglings/exercises/pulls/426
2026-05-18 19:49:52 +02:00
Nick Grimshaw
e96ce4da8e Minor grammar fix to comment in 005_arrays2 2026-05-18 17:38:04 +01:00
Nick Grimshaw
42a417f9a5 Instruct user not to worry about sentinel syntax in 065_builtins2 2026-05-18 17:29:17 +01:00
Chris Boesch
dde51b3126 Merge pull request 'fixed removed array multiplication' (#423) from array_mult into main
Reviewed-on: https://codeberg.org/ziglings/exercises/pulls/423
2026-05-04 17:30:31 +02:00
Chris Boesch
8af3372cf2 fixed removed array multiplication 2026-05-04 17:15:30 +02:00
Chris Boesch
1ba1e301a8 Merge pull request 'adjusted comment to zig 0.16' (#422) from i421 into main
Reviewed-on: https://codeberg.org/ziglings/exercises/pulls/422
2026-05-02 23:08:18 +02:00
Chris Boesch
1c897e1951 adjusted comment to zig 0.16 2026-05-02 19:25:52 +02:00
Chris Boesch
6048e6ef21 Merge pull request 'fixed compileError printing' (#419) from i418 into main
Reviewed-on: https://codeberg.org/ziglings/exercises/pulls/419
2026-04-30 19:53:56 +02:00
Chris Boesch
3a782a96d5 fixed compileError printing 2026-04-30 19:30:27 +02:00
Chris Boesch
4bdcf195b9 Merge pull request 'docs: update wrong comment reference to prior exercise in 110_files2' (#416) from kwyse/exercises:fix-110_files2 into main
Reviewed-on: https://codeberg.org/ziglings/exercises/pulls/416
2026-04-23 10:20:48 +02:00
kwyse
656d6824d9 docs: update wrong reference to prior exercise
The comment should reference exercise 109, not 106.
2026-04-22 22:49:10 +02:00
Chris Boesch
0d9652d867 Merge pull request 'fix: move exercise 100 celebration comment to the right file' (#413) from Frost-Phoenix/exercises:fix_comment into main
Reviewed-on: https://codeberg.org/ziglings/exercises/pulls/413
2026-04-21 22:25:41 +02:00
Chris Boesch
945d9b84b0 Merge branch 'main' into fix_comment 2026-04-21 22:13:57 +02:00
Chris Boesch
c7afea6ef5 Merge pull request 'added skip to the C exercises' (#414) from fixing_cimport into main
Reviewed-on: https://codeberg.org/ziglings/exercises/pulls/414
2026-04-20 20:20:14 +02:00
Chris Boesch
6c2531b824 added skip to the C exercises 2026-04-20 19:38:31 +02:00
Chris Boesch
42db1f05a7 Merge pull request 'docs: fix wrong prerequisite in 110_files2.zig' (#412) from Kercy/exercises:main into main
Reviewed-on: https://codeberg.org/ziglings/exercises/pulls/412
2026-04-20 19:28:22 +02:00
Frost-Phoenix
333ca33e78 fix: move exercise 100 celebration comment to the right file 2026-04-20 17:40:49 +02:00
KercyDing
5eb9a30abe docs: fix wrong prerequisite in 110_files2.zig 2026-04-20 04:22:21 +08:00
Chris Boesch
0603b66cc7 Merge pull request 'fixed readme' (#411) from readme into main
Reviewed-on: https://codeberg.org/ziglings/exercises/pulls/411
2026-04-19 21:29:03 +02:00
Chris Boesch
a79e923768 fixed readme 2026-04-19 21:26:05 +02:00
Chris Boesch
234e829b15 Merge pull request 'fixed readme' (#409) from readme into main
Reviewed-on: https://codeberg.org/ziglings/exercises/pulls/409
2026-04-19 15:01:03 +02:00
Chris Boesch
823b4987f7 fixed readme 2026-04-19 14:49:29 +02:00
Chris Boesch
ca8aee84ad Merge pull request 'Demonstrate the benefit of a mutex' (#408) from MatthijsBlom/ziglings:matthijsblom-patch-1 into main
Reviewed-on: https://codeberg.org/ziglings/exercises/pulls/408
2026-04-19 14:21:24 +02:00
MatthijsBlom
c4cb76ea8e Update patches/patches/091_async7.patch 2026-04-18 23:50:13 +02:00
MatthijsBlom
e0470c4f45 demonstrate the benefit of the mutex 2026-04-18 23:46:59 +02:00
Chris Boesch
f35c9419d2 Merge pull request 'fix: mutex unlock call in async example' (#407) from gabelluardo/ziglings:fix-async91 into main
Reviewed-on: https://codeberg.org/ziglings/exercises/pulls/407
2026-04-18 21:50:49 +02:00
Gabriele Belluardo
656e1ba1e7 fix: mutex unlock call in async example 2026-04-18 11:55:49 +02:00
Chris Boesch
8e2cbf7b5a Merge pull request 'Some deprecated functions removed and a progress bar added.' (#403) from fixes_v016 into main
Reviewed-on: https://codeberg.org/ziglings/exercises/pulls/403
2026-04-15 18:39:30 +02:00
Chris Boesch
d918dbae59 Some deprecated functions removed and a progress bar added. 2026-04-15 18:21:21 +02:00
Chris Boesch
4746ccc1c8 v0.16 2026-04-15 14:33:47 +02:00
40 changed files with 1506 additions and 2078 deletions

View File

@@ -111,7 +111,7 @@ directory.
Every Ziglings exercise contains mistakes on purpose.
To keep our automated tests happy, each exercise also
has a patch in `patches/healed` that “heals” it.
has a patch in `patches/patches` that “heals” it.
When you change an exercise, you will usually need to update
its patch too. Thats where our little helper Gollum comes in:
@@ -119,12 +119,12 @@ its patch too. Thats where our little helper Gollum comes in:
1. In the project root, create a folder called `answers/`
2. Put your solved version of the exercise file in there
3. Back in the root, run:
`./patches/gollum <exercise-number>`<br>
For example: `./patches/gollum 106`
`./patches/gollum.sh <exercise-number>`<br>
For example: `./patches/gollum.sh 106`
This will generate a shiny new patch.
Double-check everything by asking the magical Eowyn:
`./patches/eowyn`<br>
`./patches/eowyn.sh`<br>
If all tests pass: You are done!
Dont forget to commit the patch file.

View File

@@ -44,8 +44,8 @@ in that case, you can download it directly from the [build directory](https://zi
Verify the installation and build number of `zig` like so:
```
$ zig version
0.16.0-dev.xxxx+xxxxxxxxx
zig version
0.17.0-dev.xxxx+xxxxxxxxx
```
Clone this repository with Git:
@@ -73,11 +73,11 @@ 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.15.2`, but Ziglings needs a dev build with
pre-release version "0.16.0" and a build number at least as high
stable release was `0.16`, but Ziglings needs a dev build with
pre-release version "0.17.0-dev" and a build number at least as high
as that shown in the example version check above.
**Hint**: You can find a version change summary at the end of this README.
**Hint**: You can find a summary of breaking changes at the end of this README.
It is likely that you'll download a build which is _greater_ than
the minimum.
@@ -106,12 +106,6 @@ Or let Ziglings pick an exercise for you:
zig build -Drandom
```
You can also run without checking for correctness:
```
zig build -Dn=19 test
```
Or skip the build system entirely and interact directly with the
compiler if you're into that sort of thing:
@@ -119,22 +113,10 @@ compiler if you're into that sort of thing:
zig run exercises/001_hello.zig
```
Calling all wizards: To prepare an executable for debugging,
install it to zig-cache/bin with:
```
zig build -Dn=19 install
```
To get a list of all possible options, run:
```
zig build -Dn=19 -l
install Install 019_functions2.zig to prefix path
uninstall Uninstall 019_functions2.zig from prefix path
test Run 019_functions2.zig without checking output
...
zig build -h
```
To reset the progress (have it run all the exercises that have already been completed):
@@ -211,6 +193,7 @@ Zig Standard Library
### Version Changes
* 2026-05-31 zig 0.17.0-dev.607 - zig build: separate the maker process from the configurer process, see[#35428](https://codeberg.org/ziglang/zig/pulls/35428)
* 2026-03-20 zig 0.16.0-dev.2915 - `GeneralPurposeAllocator` was changed to `DebugAllocator`
* 2026-02-04 zig 0.16.0-dev.2471 - added process.Child.Cwd, see [#31090](https://codeberg.org/ziglang/zig/pulls/31090)
* 2026-01-09 zig 0.16.0-dev.2075 - move randomness API to `std.Io`, see [#30709](https://codeberg.org/ziglang/zig/pulls/30709)

1386
build.zig

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,5 @@
//
// Zig has some fun array operators.
// Zig has one array operator.
//
// You can use '++' to concatenate two arrays:
//
@@ -7,12 +7,8 @@
// const b = [_]u8{ 3,4 };
// const c = a ++ b ++ [_]u8{ 5 }; // equals 1 2 3 4 5
//
// You can use '**' to repeat an array:
//
// const d = [_]u8{ 1,2,3 } ** 2; // equals 1 2 3 1 2 3
//
// Note that both '++' and '**' only operate on arrays while your
// program is _being compiled_. This special time is known in Zig
// Note that '++' only operates on arrays while your program is
// _being compiled_. This special time is known in Zig
// parlance as "comptime" and we'll learn plenty more about that
// later.
//
@@ -30,7 +26,8 @@ pub fn main() void {
// (Problem 2)
// Please set this array using repetition.
// It should result in: 1 0 0 1 1 0 0 1 1 0 0 1
const bit_pattern = [_]u8{ ??? } ** 3;
const bit_pattern_unit = [_]u8{ ??? };
const bit_pattern: [3 * bit_pattern_unit.len]u8 = @bitCast(@as([3][bit_pattern_unit.len]u8, @splat(bit_pattern_unit)));
// Okay, that's all of the problems. Let's see the results.
//

View File

@@ -27,10 +27,6 @@ pub fn main() void {
const d: u8 = ziggy[???];
// (Problem 2)
// Use the array repeat '**' operator to make "ha ha ha ".
const laugh = "ha " ???;
// (Problem 3)
// Use the array concatenation '++' operator to make "Major Tom".
// (You'll need to add a space as well!)
const major = "Major";
@@ -38,7 +34,7 @@ pub fn main() void {
const major_tom = major ??? tom;
// That's all the problems. Let's see our results:
std.debug.print("d={u} {s}{s}\n", .{ d, laugh, major_tom });
std.debug.print("d={u} {s}\n", .{ d, major_tom });
// Keen eyes will notice that we've put 'u' and 's' inside the '{}'
// placeholders in the format string above. This tells the
// print() function to format the values as a UTF-8 character and

View File

@@ -10,6 +10,8 @@ pub fn main() void {
for (animals) |a| printAnimal(a);
std.debug.print("done.\n", .{});
std.debug.print("Answer to everything? {d}\n", .{calculateTheUltimateQuestionOfLife()});
}
// This function is _supposed_ to print an animal name in parentheses
@@ -35,3 +37,24 @@ fn printAnimal(animal: u8) void {
std.debug.print("Unknown", .{});
}
// This function is supposed to calculate the answer to the
// ultimate question of life, the universe, and everything,
// but it needs to be deferred as far in the future as possible,
// in order to gather more data.
//
// When there are multiple defers in a single block, they are executed in reverse order.
// This example might seem silly, but it's important to know when e.g.
// deinitializing containers whose elements need to be deinitialized first.
fn calculateTheUltimateQuestionOfLife() u32 {
var x: u32 = 100;
// Try reordering the statements to get the answer 42
{
defer x = x / 10;
defer x = x + 11;
defer x = x * 2;
}
return x;
}

View File

@@ -27,3 +27,19 @@ pub fn main() void {
std.debug.print("a: {}, b: {}\n", .{ a, b.* });
}
//
// A look into the future:
// When you allocate memory, you store the returned address in
// a const var. The pointer itself never changes — it always
// refers to the same allocation — but you can still read and
// write the data it points to.
//
// Example:
//
// const buf = try allocator.alloc(u8, 1024);
// buf[0] = 42; // fine: the *contents* are mutable
//
// Note:
// Passing this pointer to a function is cheap: it's just an address
// copied on the stack. The caller can work with the data without
// needing to know where it came from or how it was allocated.

View File

@@ -54,7 +54,7 @@ var global_wizard = Character{};
// an extremely efficient place for memory storage.
//
// Also, when a function executes, the input arguments are often
// loaded into the beating heart of the CPU itself in registers.
// loaded into the beating heart of the CPU itself, in registers.
//
// Our main() function here has no input parameters, but it will have
// a stack entry (called a "frame").

View File

@@ -224,11 +224,10 @@ const NotebookEntry = struct {
// +---+----------------+----------------+----------+
//
const HermitsNotebook = struct {
// Remember the array repetition operator `**`? It is no mere
// novelty, it's also a great way to assign multiple items in an
// array without having to list them one by one. Here we use it to
// initialize an array with null values.
entries: [place_count]?NotebookEntry = .{null} ** place_count,
// Remember the array repetition function @splat()? It is a great way
// to assign multiple items in an array without having to list them
// one by one. Here we use it to initialize an array with null values.
entries: [place_count]?NotebookEntry = @splat(null),
// The next entry keeps track of where we are in our "todo" list.
next_entry: u8 = 0,
@@ -409,7 +408,7 @@ pub fn main() void {
// aside memory for the trip and have the hermit's notebook fill
// in the trip from the destination back to the path. Note that
// this is the first time we've actually used the destination!
var trip = [_]?TripItem{null} ** (place_count * 2);
var trip: [place_count * 2]?TripItem = @splat(null);
notebook.getTripTo(trip[0..], destination) catch |err| {
print("Oh no! {}\n", .{err});

View File

@@ -29,8 +29,15 @@
//
// var foo: f16 = 5; // NO ERROR
//
// var foo: u16 = 5; // A literal of a different type
// var bar: f16 = foo; // ERROR
// A runtime value can coerce to a different type,
// as long as the value is losslessly representable:
//
// var foo: u16 = 5;
// var bar: f16 = foo; // NO ERROR (5 fits in f16)
//
// var foo: u16 = 49876;
// var bar: f16 = foo; // ERROR (49876 not representable in f16)
//
//
// Please fix the two float problems with this program and
// display the result as a whole number.

View File

@@ -93,36 +93,25 @@ pub fn main() void {
print("He has room in his heart for:", .{});
// A StructFields array
const fields = @typeInfo(Narcissus).@"struct".fields;
// `field_names` is a slice of strings and it holds the names of the struct's fields
// `field_types` is a slice of strings and it holds the types of the struct's fields,
// it is guaranteed to be the same length as `field_names`
const field_names = @typeInfo(Narcissus).@"struct".field_names;
const field_types = @typeInfo(Narcissus).@"struct".field_types;
// 'fields' is a slice of StructFields. Here's the declaration:
//
// pub const StructField = struct {
// name: [:0]const u8,
// type: type,
// default_value_ptr: ?*const anyopaque,
// is_comptime: bool,
// alignment: comptime_int,
//
// defaultValue() ?sf.type // Function that loads the
// // field's default value from
// // `default_value_ptr`
// };
//
// Please complete these 'if' statements so that the field
// name will not be printed if the field is of type 'void'
// (which is a zero-bit type that takes up no space at all!):
if (fields[0].??? != void) {
print(" {s}", .{fields[0].name});
if (field_???[???] != void) {
print(" {s}", .{field_???[???]});
}
if (fields[1].??? != void) {
print(" {s}", .{fields[1].name});
if (field_???[???] != void) {
print(" {s}", .{field_???[???]});
}
if (fields[2].??? != void) {
print(" {s}", .{fields[2].name});
if (field_???[???] != void) {
print(" {s}", .{field_???[???]});
}
// Yuck, look at all that repeated code above! I don't know

View File

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

View File

@@ -36,13 +36,14 @@ pub fn main() void {
// statement was repeated three times almost verbatim. Yuck!
//
// Please use an 'inline for' to implement the block below
// for each field in the slice 'fields'!
// for each field in the corresponding slices (they're of the same length)!
const fields = @typeInfo(Narcissus).@"struct".fields;
const field_names = @typeInfo(Narcissus).@"struct".field_names;
const field_types = @typeInfo(Narcissus).@"struct".field_types;
??? {
if (field.type != void) {
print(" {s}", .{field.name});
if (field_type != void) {
print(" {s}", .{field_name});
}
}

View File

@@ -1,4 +1,5 @@
const print = @import("std").debug.print;
const std = @import("std");
const print = std.debug.print;
// We're going to (ab)use the power of Zig to make animal hybrid creatures!
// What do you think a GatorMouse would look like? Eek.
@@ -31,7 +32,7 @@ fn makeCreature(comptime count: usize, comptime fmt: []const u8) [count]Animal {
// We return an array of animals representing the creature. (This is why we
// really needed the 'count' parameter. Arrays need a size.)
var animals: [count]Animal = .{undefined} ** count;
var animals: [count]Animal = undefined;
var next_animal: usize = 0;
inline for (fmt) |char| {
@@ -58,7 +59,7 @@ fn makeCreature(comptime count: usize, comptime fmt: []const u8) [count]Animal {
// other animals or is this an error?
'g' => ???,
else => @compileError("No animal starts with '" ++ char ++ "'!"),
else => @compileError(std.fmt.comptimePrint("No animal starts with '{c}'!", .{char})),
},
.l => switch (char) {

View File

@@ -48,9 +48,7 @@ const Path = struct {
// instead.
//
// Please fill in the body of this function!
fn makePath(from: *Place, to: *Place, dist: u8) Path {
}
fn makePath(from: *Place, to: *Place, dist: u8) Path {}
// Using our new function, these path definitions take up considerably less
// space in our program now!
@@ -97,7 +95,7 @@ const NotebookEntry = struct {
};
const HermitsNotebook = struct {
entries: [place_count]?NotebookEntry = .{null} ** place_count,
entries: [place_count]?NotebookEntry = @splat(null),
next_entry: u8 = 0,
end_of_entries: u8 = 0,
@@ -193,7 +191,7 @@ pub fn main() void {
}
}
var trip = [_]?TripItem{null} ** (place_count * 2);
var trip: [place_count * 2]?TripItem = @splat(null);
notebook.getTripTo(trip[0..], destination) catch |err| {
print("Oh no! {}\n", .{err});

View File

@@ -74,36 +74,27 @@ fn printTuple(tuple: anytype) void {
// @typeInfo() - takes a type, returns a TypeInfo union
// with fields specific to that type.
//
// The list of a struct type's fields can be found in
// TypeInfo's @"struct".fields.
// The list of a struct type's field types can be found in
// TypeInfo's @"struct".field_types.
//
// Example:
//
// @typeInfo(Circle).@"struct".fields
// @typeInfo(Circle).@"struct".field_types
//
// This will be an array of StructFields.
const fields = ???;
// This will be an array of field types.
const field_types = ???;
// This will be an array of field names.
const field_names = ???;
// 2. Loop through each field. This must be done at compile
// time.
//
// Hint: remember 'inline' loops?
//
for (fields) |field| {
for (???, ???) |???, ???| {
// 3. Print the field's name, type, and value.
//
// Each 'field' in this loop is one of these:
//
// pub const StructField = struct {
// name: [:0]const u8,
// type: type,
// default_value_ptr: ?*const anyopaque,
// is_comptime: bool,
// alignment: comptime_int,
// };
//
// Note we will learn about 'anyopaque' type later
//
// You'll need this builtin:
//
// @field(lhs: anytype, comptime field_name: []const u8)
@@ -123,8 +114,8 @@ fn printTuple(tuple: anytype) void {
// for declarations. If it's a value, it looks for data.
//
print("\"{s}\"({any}):{any} ", .{
field.???,
field.???,
field_name,
field_type,
???,
});
}

View File

@@ -6,7 +6,7 @@
//
// // In a task:
// try mutex.lock(io); // blocks until lock is acquired
// defer mutex.unlock();
// defer mutex.unlock(io);
// // ... critical section: safe to modify shared data ...
//
// Without the mutex, concurrent tasks could read and write the
@@ -52,6 +52,12 @@ fn increment(io: std.Io, state: *SharedState, times: u32) void {
state.mutex.??? catch return;
defer state.mutex.unlock(); // <-- what's missing here?
// Sleep to give the other tasks a chance to run in the meantime.
// We do this here only to make nondeterminism more visible.
io.sleep(std.Io.Duration.fromMilliseconds(1), .awake) catch {};
// What happens if you neglect to lock the mutex?
state.counter += 1;
}
}

View File

@@ -93,3 +93,12 @@ pub fn main() !void {
//
// For Crypto it is better not to use this, but in sorting algorithms like
// Bubble Sort it works very well.
//
// By the way, congratulations for making it to Exercise 100!
//
// +-------------+
// | Celebration |
// | Area * * * |
// +-------------+
//
// Please keep your celebrating within the area provided.

View File

@@ -51,12 +51,3 @@ pub fn main() void {
//
// You are perhaps wondering what happens if one of the two lists
// is longer than the other? Try it!
//
// By the way, congratulations for making it to Exercise 100!
//
// +-------------+
// | Celebration |
// | Area * * * |
// +-------------+
//
// Please keep your celebrating within the area provided.

View File

@@ -14,7 +14,7 @@
// exercises.
//
// A nice example of this has been published on the Zig homepage,
// replacing the somewhat dusty 'Hello world!
// replacing the somewhat dusty 'Hello world!'
//
// Nothing against 'Hello world!', but it just doesn't do justice
// to the elegance of Zig and that's a pity, if someone takes a short,

View File

@@ -1,6 +1,6 @@
//
// Prerequisite :
// - exercise/106_files.zig, or
// - exercise/109_files.zig, or
// - create a file {project_root}/output/zigling.txt
// with content `It's zigling time!`(18 bytes total)
//
@@ -28,7 +28,7 @@ pub fn main(init: std.process.Init) !void {
// Get the current working directory
const cwd = std.Io.Dir.cwd();
// try to open ./output assuming you did your 106_files exercise
// try to open ./output assuming you did your 109_files exercise
var output_dir = try cwd.openDir(io, "output", .{});
defer output_dir.close(io);
@@ -36,10 +36,10 @@ pub fn main(init: std.process.Init) !void {
const file = try output_dir.openFile(io, "zigling.txt", .{});
defer file.close(io);
// initialize an array of u8 with all letter 'A'
// initialize an array of u8 entirely with the 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;
// do you remember the array repetition function?
var content: ??? = ???('A');
// this should print out : `AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA`
std.debug.print("{s}\n", .{content});

View File

@@ -73,7 +73,7 @@
// 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.
// Defining vectors in Zig is straightforward. No library import is needed.
const v1 = @Vector(3, i32){ 1, 10, 100 };
const v2 = @Vector(3, f32){ 2.0, 3.0, 5.0 };

View File

@@ -1,9 +1,9 @@
//
// We've already learned plenty about bit manipulation using bitwise operations
// in exercices 097 and 098 and in quiz 110. The techniques we already know work
// in exercises 097 and 098 and in quiz 110. The techniques we already know work
// just fine, but creating masks and shifting individual bits around can become
// quite tedious and unwieldy pretty quickly.
// What if there was a better, a more convenient way to control invidivual bits?
// What if there was a better, a more convenient way to control individual bits?
//
// Luckily, Zig has a keyword for exactly this purpose:
//

View File

@@ -1,6 +1,6 @@
--- exercises/005_arrays2.zig 2023-10-03 22:15:22.122241138 +0200
+++ answers/005_arrays2.zig 2023-10-05 20:04:06.862763262 +0200
@@ -25,12 +25,12 @@
--- exercises/005_arrays2.zig 2026-05-04 16:26:32.778330847 +0200
+++ answers/005_arrays2.zig 2026-05-04 16:26:13.082917974 +0200
@@ -21,12 +21,12 @@
// (Problem 1)
// Please set this array concatenating the two arrays above.
// It should result in: 1 3 3 7
@@ -10,8 +10,8 @@
// (Problem 2)
// Please set this array using repetition.
// It should result in: 1 0 0 1 1 0 0 1 1 0 0 1
- const bit_pattern = [_]u8{ ??? } ** 3;
+ const bit_pattern = [_]u8{ 1, 0, 0, 1 } ** 3;
- const bit_pattern_unit = [_]u8{ ??? };
+ const bit_pattern_unit = [_]u8{ 1, 0, 0, 1 };
const bit_pattern: [3 * bit_pattern_unit.len]u8 = @bitCast(@as([3][bit_pattern_unit.len]u8, @splat(bit_pattern_unit)));
// Okay, that's all of the problems. Let's see the results.
//

View File

@@ -1,6 +1,6 @@
--- exercises/006_strings.zig 2023-10-03 22:15:22.122241138 +0200
+++ answers/006_strings.zig 2023-10-05 20:04:06.869430053 +0200
@@ -24,18 +24,18 @@
--- exercises/006_strings.zig 2026-05-04 17:04:31.763821070 +0200
+++ answers/006_strings.zig 2026-05-04 17:02:11.672866263 +0200
@@ -24,14 +24,14 @@
// (Problem 1)
// Use array square bracket syntax to get the letter 'd' from
// the string "stardust" above.
@@ -8,11 +8,6 @@
+ const d: u8 = ziggy[4];
// (Problem 2)
// Use the array repeat '**' operator to make "ha ha ha ".
- const laugh = "ha " ???;
+ const laugh = "ha " ** 3;
// (Problem 3)
// Use the array concatenation '++' operator to make "Major Tom".
// (You'll need to add a space as well!)
const major = "Major";
@@ -21,4 +16,4 @@
+ const major_tom = major ++ " " ++ tom;
// That's all the problems. Let's see our results:
std.debug.print("d={u} {s}{s}\n", .{ d, laugh, major_tom });
std.debug.print("d={u} {s}\n", .{ d, major_tom });

View File

@@ -1,6 +1,6 @@
--- exercises/028_defer2.zig 2023-10-03 22:15:22.122241138 +0200
+++ answers/028_defer2.zig 2023-10-05 20:04:06.966098530 +0200
@@ -18,7 +18,7 @@
--- exercises/028_defer2.zig 2026-06-02 06:08:12.713672612 +0200
+++ answers/028_defer2.zig 2026-06-02 06:08:43.262234023 +0200
@@ -20,7 +20,7 @@
fn printAnimal(animal: u8) void {
std.debug.print("(", .{});
@@ -9,3 +9,15 @@
if (animal == 'g') {
std.debug.print("Goat", .{});
@@ -51,9 +51,9 @@
// Try reordering the statements to get the answer 42
{
- defer x = x / 10;
- defer x = x + 11;
defer x = x * 2;
+ defer x = x + 11;
+ defer x = x / 10;
}
return x;

View File

@@ -1,5 +1,5 @@
--- exercises/040_pointers2.zig 2023-10-03 22:15:22.122241138 +0200
+++ answers/040_pointers2.zig 2023-10-05 20:04:07.022766257 +0200
--- exercises/040_pointers2.zig 2026-05-22 21:57:28.601255748 +0200
+++ answers/040_pointers2.zig 2026-05-22 21:57:27.672235943 +0200
@@ -23,7 +23,7 @@
pub fn main() void {

View File

@@ -1,5 +1,5 @@
--- 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 2026-05-04 16:34:31.692458399 +0200
+++ answers/058_quiz7.zig 2026-05-04 16:34:29.239406323 +0200
@@ -192,8 +192,8 @@
// Oops! The hermit forgot how to capture the union values
// in a switch statement. Please capture each value as
@@ -11,7 +11,7 @@
}
}
};
@@ -255,7 +255,7 @@
@@ -254,7 +254,7 @@
// dereference and optional value "unwrapping" look
// together. Remember that you return the address with the
// "&" operator.
@@ -20,7 +20,7 @@
// Try to make your answer this long:__________;
}
return null;
@@ -309,7 +309,7 @@
@@ -308,7 +308,7 @@
//
// Looks like the hermit forgot something in the return value of
// this function. What could that be?

View File

@@ -1,6 +1,6 @@
--- exercises/060_floats.zig 2026-04-03 14:40:17.582344768 +0200
+++ answers/060_floats.zig 2026-04-03 14:49:26.997886326 +0200
@@ -43,7 +43,7 @@
--- exercises/060_floats.zig 2026-05-02 19:22:46.225370223 +0200
+++ answers/060_floats.zig 2026-05-02 19:22:47.523142218 +0200
@@ -50,7 +50,7 @@
//
// We'll convert this weight from pounds to metric units at a
// conversion of 0.453592 kg to the pound.
@@ -9,7 +9,7 @@
// By default, float values are formatted in standard decimal
// notation. Experiment with '{d}' and '{d:.3}' to see how
@@ -51,7 +51,7 @@
@@ -58,7 +58,7 @@
// scientific notation.
// NOTE: The weight of the shuttle is a huge number, a scientific notation
// may be more appropriate.

View File

@@ -1,5 +1,5 @@
--- exercises/065_builtins2.zig 2026-02-27 13:10:36
+++ answers/065_builtins2.zig 2026-02-27 13:10:52
--- exercises/065_builtins2.zig 2026-06-01 15:33:16.617432671 +0200
+++ answers/065_builtins2.zig 2026-06-01 15:33:31.104018108 +0200
@@ -58,7 +58,7 @@
// Oops! We cannot leave the 'me' and 'myself' fields
// undefined. Please set them here:
@@ -18,22 +18,26 @@
// Now we print a pithy statement about Narcissus.
print("A {s} loves all {s}es. ", .{
@@ -113,15 +113,15 @@
@@ -102,16 +102,16 @@
// 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});
- if (field_???[???] != void) {
- print(" {s}", .{field_???[???]});
+ if (field_types[0] != void) {
+ print(" {s}", .{field_names[0]});
}
- if (fields[1].??? != void) {
+ if (fields[1].type != void) {
print(" {s}", .{fields[1].name});
- if (field_???[???] != void) {
- print(" {s}", .{field_???[???]});
+ if (field_types[1] != void) {
+ print(" {s}", .{field_names[1]});
}
- if (fields[2].??? != void) {
+ if (fields[2].type != void) {
print(" {s}", .{fields[2].name});
- if (field_???[???] != void) {
- print(" {s}", .{field_???[???]});
+ if (field_types[2] != void) {
+ print(" {s}", .{field_names[2]});
}
// Yuck, look at all that repeated code above! I don't know

View File

@@ -1,5 +1,5 @@
--- exercises/067_comptime2.zig 2025-12-07 22:51:24.396031248 +0100
+++ answers/067_comptime2.zig 2025-12-07 22:50:18.721616293 +0100
--- exercises/067_comptime2.zig 2026-05-04 15:38:52.565144012 +0200
+++ answers/067_comptime2.zig 2026-05-04 15:37:20.257213463 +0200
@@ -36,7 +36,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;
const a1: [count]u8 = @splat('A');

View File

@@ -1,11 +1,11 @@
--- exercises/071_comptime6.zig 2024-09-02 19:21:50.250454978 +0200
+++ answers/071_comptime6.zig 2024-09-02 19:21:23.553250563 +0200
@@ -40,7 +40,7 @@
const fields = @typeInfo(Narcissus).@"struct".fields;
--- exercises/071_comptime6.zig 2026-06-01 15:35:27.223400223 +0200
+++ answers/071_comptime6.zig 2026-06-01 15:36:35.349728561 +0200
@@ -41,7 +41,7 @@
const field_names = @typeInfo(Narcissus).@"struct".field_names;
const field_types = @typeInfo(Narcissus).@"struct".field_types;
- ??? {
+ inline for (fields) |field| {
if (field.type != void) {
print(" {s}", .{field.name});
+ inline for (field_names, field_types) |field_name, field_type| {
if (field_type != void) {
print(" {s}", .{field_name});
}

View File

@@ -1,6 +1,6 @@
--- exercises/074_comptime9.zig 2026-04-11 21:35:08.132459373 -0700
+++ answers/074_comptime9.zig 2026-04-12 07:13:37.971010827 -0700
@@ -27,12 +27,12 @@
--- exercises/074_comptime9.zig 2026-05-04 17:11:05.144118157 +0200
+++ answers/074_comptime9.zig 2026-05-04 17:10:36.778519877 +0200
@@ -28,12 +28,12 @@
start, // Ready to start a new animal.
l, // This means we've seen an "l", so if we see an "m", we know it's a Llama.
};
@@ -9,22 +9,22 @@
// We return an array of animals representing the creature. (This is why we
// really needed the 'count' parameter. Arrays need a size.)
var animals: [count]Animal = .{undefined} ** count;
var animals: [count]Animal = undefined;
- var next_animal: usize = 0;
+ comptime var next_animal: usize = 0;
inline for (fmt) |char| {
@@ -56,7 +56,7 @@
@@ -57,7 +57,7 @@
//
// What do you think happens with Gators? Do they join with
// other animals or is this an error?
- 'g' => ???,
+ 'g' => @compileError("Gators refuse to join with other animals."),
else => @compileError("No animal starts with '" ++ char ++ "'!"),
else => @compileError(std.fmt.comptimePrint("No animal starts with '{c}'!", .{char})),
},
@@ -68,7 +68,7 @@
@@ -69,7 +69,7 @@
next_animal += 1;
// Something is missing here. After we finish a Llama, we
// need to be ready to _start_ over with a new animal...

View File

@@ -1,15 +1,17 @@
--- exercises/075_quiz8.zig 2023-11-21 14:48:15.440702720 +0100
+++ answers/075_quiz8.zig 2023-11-21 14:50:23.453311616 +0100
@@ -49,7 +49,11 @@
--- exercises/075_quiz8.zig 2026-05-04 15:51:48.254371574 +0200
+++ answers/075_quiz8.zig 2026-05-04 15:49:28.426445382 +0200
@@ -48,7 +48,13 @@
// instead.
//
// Please fill in the body of this function!
fn makePath(from: *Place, to: *Place, dist: u8) Path {
-
-fn makePath(from: *Place, to: *Place, dist: u8) Path {}
+fn makePath(from: *Place, to: *Place, dist: u8) Path {
+ return Path{
+ .from = from,
+ .to = to,
+ .dist = dist,
+ };
}
+}
// Using our new function, these path definitions take up considerably less
// space in our program now!

View File

@@ -1,32 +1,32 @@
--- exercises/082_anonymous_structs3.zig 2026-02-27 13:05:46
+++ answers/082_anonymous_structs3.zig 2026-02-27 13:07:22
@@ -82,14 +82,14 @@
// @typeInfo(Circle).@"struct".fields
--- exercises/082_anonymous_structs3.zig 2026-06-01 15:59:11.872467805 +0200
+++ answers/082_anonymous_structs3.zig 2026-06-01 15:58:38.004730144 +0200
@@ -82,17 +82,17 @@
// @typeInfo(Circle).@"struct".field_types
//
// This will be an array of StructFields.
- const fields = ???;
+ const fields = @typeInfo(@TypeOf(tuple)).@"struct".fields;
// This will be an array of field types.
- const field_types = ???;
+ const field_types = @typeInfo(@TypeOf(tuple)).@"struct".field_types;
// This will be an array of field names.
- const field_names = ???;
+ const field_names = @typeInfo(@TypeOf(tuple)).@"struct".field_names;
// 2. Loop through each field. This must be done at compile
// time.
//
// Hint: remember 'inline' loops?
//
- for (fields) |field| {
+ inline for (fields) |field| {
- for (???, ???) |???, ???| {
+ inline for (field_types, field_names) |field_type, field_name| {
// 3. Print the field's name, type, and value.
//
// Each 'field' in this loop is one of these:
@@ -123,9 +123,9 @@
// for declarations. If it's a value, it looks for data.
//
// You'll need this builtin:
@@ -116,7 +116,7 @@
print("\"{s}\"({any}):{any} ", .{
- field.???,
- field.???,
field_name,
field_type,
- ???,
+ field.name,
+ field.type,
+ @field(tuple, field.name),
+ @field(tuple, field_name),
});
}
}

View File

@@ -1,5 +1,5 @@
--- exercises/091_async7.zig 2026-04-02 10:50:08.142508099 +0200
+++ answers/091_async7.zig 2026-04-02 10:49:59.629341593 +0200
--- exercises/091_async7.zig 2026-04-18 23:30:40.963951835 +0200
+++ answers/091_async7.zig 2026-04-18 23:33:47.313340585 +0200
@@ -49,8 +49,8 @@
for (0..times) |_| {
// Acquire the lock before modifying shared state.
@@ -9,5 +9,5 @@
+ state.mutex.lock(io) catch return;
+ defer state.mutex.unlock(io);
state.counter += 1;
}
// Sleep to give the other tasks a chance to run in the meantime.
// We do this here only to make nondeterminism more visible.

View File

@@ -1,11 +1,11 @@
--- exercises/110_files2.zig 2026-03-20 19:23:48.874150121 +0100
+++ answers/110_files2.zig 2026-04-02 10:51:15.815831734 +0200
--- exercises/110_files2.zig 2026-05-31 14:54:55.061019159 +0200
+++ answers/110_files2.zig 2026-05-31 14:54:42.655691238 +0200
@@ -39,7 +39,7 @@
// initialize an array of u8 with all letter 'A'
// initialize an array of u8 entirely with the 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;
// do you remember the array repetition function?
- var content: ??? = ???('A');
+ var content: [64]u8 = @splat('A');
// this should print out : `AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA`
std.debug.print("{s}\n", .{content});

1223
rivendell/elrond.zig Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,413 +0,0 @@
const std = @import("std");
const root = @import("../build.zig");
const debug = std.debug;
const fmt = std.fmt;
const mem = std.mem;
const Allocator = std.mem.Allocator;
const Process = std.process;
const Build = std.Build;
const Step = Build.Step;
const RunStep = Build.RunStep;
const LazyPath = Build.LazyPath;
const Exercise = root.Exercise;
pub fn addCliTests(b: *std.Build, exercises: []const Exercise) *Step {
const step = b.step("test-cli", "Test the command line interface");
{
// Test that `zig build -Dhealed -Dn=n` selects the nth exercise.
const case_step = createCase(b, "case-1");
const tmp_path = createTempPath(b) catch |err| {
return fail(step, "unable to make tmp path: {s}\n", .{@errorName(err)});
};
defer deleteTmpPath(b, tmp_path);
const heal_step = HealStep.create(b, exercises, tmp_path);
for (exercises[0 .. exercises.len - 1]) |ex| {
const n = ex.number();
const cmd = b.addSystemCommand(&.{
b.graph.zig_exe,
"build",
"-Dhealed",
b.fmt("-Dhealed-path={s}", .{tmp_path}),
b.fmt("-Dn={}", .{n}),
});
cmd.setName(b.fmt("zig build -Dhealed -Dn={}", .{n}));
cmd.expectExitCode(0);
cmd.step.dependOn(&heal_step.step);
const stderr = cmd.captureStdErr(.{});
const verify = CheckNamedStep.create(b, ex, stderr);
verify.step.dependOn(&cmd.step);
case_step.dependOn(&verify.step);
}
}
{
// Test that `zig build -Dhealed` processes all the exercises in order.
const case_step = createCase(b, "case-2");
const tmp_path = createTempPath(b) catch |err| {
return fail(step, "unable to make tmp path: {s}\n", .{@errorName(err)});
};
defer deleteTmpPath(b, tmp_path);
const heal_step = HealStep.create(b, exercises, tmp_path);
heal_step.step.dependOn(case_step);
// TODO: when an exercise is modified, the cache is not invalidated.
const cmd = b.addSystemCommand(&.{
b.graph.zig_exe,
"build",
"-Dhealed",
b.fmt("-Dhealed-path={s}", .{tmp_path}),
});
cmd.setName("zig build -Dhealed");
cmd.expectExitCode(0);
cmd.step.dependOn(&heal_step.step);
const stderr = cmd.captureStdErr(.{});
const verify = CheckStep.create(b, exercises, stderr);
verify.step.dependOn(&cmd.step);
}
{
// Test that `zig build -Dn=n` prints the hint.
const case_step = createCase(b, "case-3");
for (exercises[0 .. exercises.len - 1]) |ex| {
if (ex.skip) continue;
if (ex.hint) |hint| {
const n = ex.number();
const cmd = b.addSystemCommand(&.{
b.graph.zig_exe,
"build",
b.fmt("-Dn={}", .{n}),
});
cmd.setName(b.fmt("zig build -Dn={}", .{n}));
cmd.expectExitCode(2);
cmd.addCheck(.{ .expect_stderr_match = hint });
case_step.dependOn(&cmd.step);
}
}
step.dependOn(case_step);
}
return step;
}
fn createCase(b: *Build, name: []const u8) *Step {
const case_step = b.allocator.create(Step) catch @panic("OOM");
case_step.* = Step.init(.{
.id = .custom,
.name = name,
.owner = b,
});
return case_step;
}
/// Checks the output of `zig build -Dn=n`.
const CheckNamedStep = struct {
step: Step,
exercise: Exercise,
stderr: LazyPath,
pub fn create(owner: *Build, exercise: Exercise, stderr: LazyPath) *CheckNamedStep {
const self = owner.allocator.create(CheckNamedStep) catch @panic("OOM");
self.* = .{
.step = Step.init(.{
.id = .custom,
.name = "check-named",
.owner = owner,
.makeFn = make,
}),
.exercise = exercise,
.stderr = stderr,
};
return self;
}
fn make(step: *Step, _: Step.MakeOptions) !void {
const b = step.owner;
const io = b.graph.io;
const self: *CheckNamedStep = @alignCast(@fieldParentPtr("step", step));
const ex = self.exercise;
const stderr_file = try std.Io.Dir.cwd().openFile(
io,
self.stderr.getPath(b),
.{ .mode = .read_only },
);
defer stderr_file.close(io);
var stderr = stderr_file.readerStreaming(io, &.{});
{
// Skip the logo.
const nlines = mem.count(u8, root.logo, "\n");
var buf: [80]u8 = undefined;
var lineno: usize = 0;
while (lineno < nlines) : (lineno += 1) {
_ = try readLine(&stderr, &buf);
}
}
try check_output(step, ex, &stderr);
}
};
/// Checks the output of `zig build`.
const CheckStep = struct {
step: Step,
exercises: []const Exercise,
stderr: LazyPath,
pub fn create(
owner: *Build,
exercises: []const Exercise,
stderr: LazyPath,
) *CheckStep {
const self = owner.allocator.create(CheckStep) catch @panic("OOM");
self.* = .{
.step = Step.init(.{
.id = .custom,
.name = "check",
.owner = owner,
.makeFn = make,
}),
.exercises = exercises,
.stderr = stderr,
};
return self;
}
fn make(step: *Step, _: Step.MakeOptions) !void {
const b = step.owner;
const io = b.graph.io;
const self: *CheckStep = @alignCast(@fieldParentPtr("step", step));
const exercises = self.exercises;
const stderr_file = try std.Io.Dir.cwd().openFile(
io,
self.stderr.getPath(b),
.{ .mode = .read_only },
);
defer stderr_file.close(io);
var stderr = stderr_file.readerStreaming(io, &.{});
for (exercises) |ex| {
if (ex.number() == 1) {
// Skip the logo.
const nlines = mem.count(u8, root.logo, "\n");
var buf: [80]u8 = undefined;
var lineno: usize = 0;
while (lineno < nlines) : (lineno += 1) {
_ = try readLine(&stderr, &buf);
}
}
try check_output(step, ex, &stderr);
}
}
};
fn check_output(step: *Step, exercise: Exercise, reader: *std.Io.File.Reader) !void {
const b = step.owner;
var buf: [1024]u8 = undefined;
if (exercise.skip) {
{
const actual = try readLine(reader, &buf) orelse "EOF";
const expect = b.fmt("Skipping {s}", .{exercise.main_file});
try check(step, exercise, expect, actual);
}
{
const actual = try readLine(reader, &buf) orelse "EOF";
try check(step, exercise, "", actual);
}
return;
}
{
const actual = try readLine(reader, &buf) orelse "EOF";
const expect = b.fmt("Compiling {s}...", .{exercise.main_file});
try check(step, exercise, expect, actual);
}
{
const actual = try readLine(reader, &buf) orelse "EOF";
const expect = b.fmt("Checking {s}...", .{exercise.main_file});
try check(step, exercise, expect, actual);
}
{
const actual = try readLine(reader, &buf) orelse "EOF";
const expect = switch (exercise.kind) {
.exe => "PASSED:",
.@"test" => "PASSED",
};
try check(step, exercise, expect, actual);
}
// Skip the exercise output.
const nlines = switch (exercise.kind) {
.exe => 1 + mem.count(u8, exercise.output, "\n") + 1,
.@"test" => 1,
};
var lineno: usize = 0;
while (lineno < nlines) : (lineno += 1) {
_ = try readLine(reader, &buf) orelse @panic("EOF");
}
}
fn check(
step: *Step,
exercise: Exercise,
expect: []const u8,
actual: []const u8,
) !void {
if (!mem.eql(u8, expect, actual)) {
return step.fail("{s}: expected to see \"{s}\", found \"{s}\"", .{
exercise.main_file,
expect,
actual,
});
}
}
fn readLine(reader: *std.Io.File.Reader, buf: []u8) !?[]const u8 {
try reader.interface.readSliceAll(buf);
return mem.trimEnd(u8, buf, " \r\n");
}
/// Fails with a custom error message.
const FailStep = struct {
step: Step,
error_msg: []const u8,
pub fn create(owner: *Build, error_msg: []const u8) *FailStep {
const self = owner.allocator.create(FailStep) catch @panic("OOM");
self.* = .{
.step = Step.init(.{
.id = .custom,
.name = "fail",
.owner = owner,
.makeFn = make,
}),
.error_msg = error_msg,
};
return self;
}
fn make(step: *Step, _: Step.MakeOptions) !void {
const b = step.owner;
const self: *FailStep = @alignCast(@fieldParentPtr("step", step));
try step.result_error_msgs.append(b.allocator, self.error_msg);
return error.MakeFailed;
}
};
/// A variant of `std.Build.Step.fail` that does not return an error so that it
/// can be used in the configuration phase. It returns a FailStep, so that the
/// error will be cleanly handled by the build runner.
fn fail(step: *Step, comptime format: []const u8, args: anytype) *Step {
const b = step.owner;
const fail_step = FailStep.create(b, b.fmt(format, args));
step.dependOn(&fail_step.step);
return step;
}
/// Heals the exercises.
const HealStep = struct {
step: Step,
exercises: []const Exercise,
work_path: []const u8,
pub fn create(owner: *Build, exercises: []const Exercise, work_path: []const u8) *HealStep {
const self = owner.allocator.create(HealStep) catch @panic("OOM");
self.* = .{
.step = Step.init(.{
.id = .custom,
.name = "heal",
.owner = owner,
.makeFn = make,
}),
.exercises = exercises,
.work_path = work_path,
};
return self;
}
fn make(step: *Step, _: Step.MakeOptions) !void {
const b = step.owner;
const self: *HealStep = @alignCast(@fieldParentPtr("step", step));
return heal(b.allocator, self.exercises, self.work_path);
}
};
/// Heals all the exercises.
fn heal(allocator: Allocator, exercises: []const Exercise, work_path: []const u8) !void {
const io = std.Options.debug_io;
const sep = std.Io.Dir.path.sep_str;
const join = std.Io.Dir.path.join;
const exercises_path = "exercises";
const patches_path = "patches" ++ sep ++ "patches";
for (exercises) |ex| {
const name = ex.name();
const file = try join(allocator, &.{ exercises_path, ex.main_file });
const patch = b: {
const patch_name = try fmt.allocPrint(allocator, "{s}.patch", .{name});
break :b try join(allocator, &.{ patches_path, patch_name });
};
const output = try join(allocator, &.{ work_path, ex.main_file });
const argv = &.{ "patch", "-i", patch, "-o", output, "-s", file };
_ = try Process.run(allocator, io, .{ .argv = argv });
}
}
fn createTempPath(b: *Build) ![]const u8 {
const io = b.graph.io;
const rand_int = r: {
var x: u64 = undefined;
io.random(@ptrCast(&x));
break :r x;
};
const tmp_dir_sub_path = "tmp" ++ std.Io.Dir.path.sep_str ++ std.fmt.hex(rand_int);
const result_path = b.cache_root.join(b.allocator, &.{tmp_dir_sub_path}) catch @panic("OOM");
try b.cache_root.handle.createDirPath(io, tmp_dir_sub_path);
return result_path;
}
fn deleteTmpPath(b: *Build, path: []const u8) void {
const io = b.graph.io;
std.Io.Dir.cwd().deleteTree(io, path) catch |err| {
std.log.warn("failed to delete {s}: {t}", .{ path, err });
};
}

View File

@@ -1,108 +0,0 @@
const std = @import("std");
const print = std.debug.print;
const string = []const u8;
const cwd = std.fs.cwd();
const Dir = std.fs.Dir;
const Allocator = std.mem.Allocator;
const EXERCISES_PATH = "exercises";
const HEALED_PATH = "patches/healed";
const TEMP_PATH = "patches/healed/tmp";
const PATCHES_PATH = "patches/patches";
// Heals all the exercises.
fn heal(alloc: Allocator) !void {
try cwd.makePath(HEALED_PATH);
const org_path = try cwd.realpathAlloc(alloc, EXERCISES_PATH);
const patch_path = try cwd.realpathAlloc(alloc, PATCHES_PATH);
const healed_path = try cwd.realpathAlloc(alloc, HEALED_PATH);
var idir = try cwd.openIterableDir(EXERCISES_PATH, Dir.OpenDirOptions{});
defer idir.close();
var it = idir.iterate();
while (try it.next()) |entry| {
// create filenames
const healed_file = try concat(alloc, &.{ healed_path, "/", entry.name });
const patch_file = try concat(alloc, &.{ patch_path, "/", try patch_name(alloc, entry.name) });
// patch file
const result = try std.ChildProcess.exec(.{
.allocator = alloc,
.argv = &.{ "patch", "-i", patch_file, "-o", healed_file, entry.name },
.cwd = org_path,
});
print("{s}", .{result.stderr});
}
}
// Yields all the healed exercises that are not correctly formatted.
fn check_healed(alloc: Allocator) !void {
try cwd.makePath(TEMP_PATH);
const temp_path = try cwd.realpathAlloc(alloc, TEMP_PATH);
const healed_path = try cwd.realpathAlloc(alloc, HEALED_PATH);
var idir = try cwd.openIterableDir(HEALED_PATH, Dir.OpenDirOptions{});
defer idir.close();
var it = idir.iterate();
while (try it.next()) |entry| {
// Check the healed file
const result = try std.ChildProcess.exec(.{
.allocator = alloc,
.argv = &.{ "zig", "fmt", "--check", entry.name },
.cwd = healed_path,
});
// Is there something to fix?
if (result.stdout.len > 0) {
const temp_file = try concat(alloc, &.{ temp_path, "/", entry.name });
const healed_file = try concat(alloc, &.{ healed_path, "/", entry.name });
try std.fs.copyFileAbsolute(healed_file, temp_file, std.fs.CopyFileOptions{});
// Formats the temp file
_ = try std.ChildProcess.exec(.{
.allocator = alloc,
.argv = &.{ "zig", "fmt", entry.name },
.cwd = temp_path,
});
// Show the differences
const diff = try std.ChildProcess.exec(.{
.allocator = alloc,
.argv = &.{ "diff", "-c", healed_file, entry.name },
.cwd = temp_path,
});
print("{s}", .{diff.stdout});
try std.fs.deleteFileAbsolute(temp_file);
}
}
}
fn concat(alloc: Allocator, slices: []const string) !string {
const buf = try std.mem.concat(alloc, u8, slices);
return buf;
}
fn patch_name(alloc: Allocator, path: string) !string {
var filename = path;
const index = std.mem.lastIndexOfScalar(u8, path, '.') orelse return path;
if (index > 0) filename = path[0..index];
return try concat(alloc, &.{ filename, ".patch" });
}
pub fn main() !void {
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
defer arena.deinit();
const alloc = arena.allocator();
try heal(alloc);
try check_healed(alloc);
}