Merge branch 'main' into make-threading-mathier

This commit is contained in:
Chris Boesch
2026-06-30 00:09:19 +02:00
18 changed files with 159 additions and 87 deletions
+3 -3
View File
@@ -8,11 +8,11 @@ of Ziglings Maintenance!
Ziglings is intended for programmers of all experience levels. No Ziglings is intended for programmers of all experience levels. No
specific language knowledge is expected. Anyone who can install specific language knowledge is expected. Anyone who can install
the current Zig snapshot, setup a copy of Ziglings, and knows the current Zig snapshot, set up a copy of Ziglings, and knows
common language building blocks (if/then/else, loops, and common language building blocks (if/then/else, loops, and
functions) is ready for Ziglings. functions) is ready for Ziglings.
Zigling's excercises are self-contained. If you can't solve Zigling's exercises are self-contained. If you can't solve
an exercise from the information you've gleaned so far from an exercise from the information you've gleaned so far from
Ziglings, then the exercise probably needs some additional work. Ziglings, then the exercise probably needs some additional work.
Please file an issue! Please file an issue!
@@ -95,7 +95,7 @@ interface. Specifically:
eternal Ziglings contributor glory is yours! eternal Ziglings contributor glory is yours!
## Licence ## License
If you submit your contribution to the repository/project, If you submit your contribution to the repository/project,
you agree that your contribution will be licensed under you agree that your contribution will be licensed under
+1
View File
@@ -81,6 +81,7 @@ pub fn build(b: *Build) !void {
const run = b.addRunArtifact(elrond); const run = b.addRunArtifact(elrond);
run.addArg(b.fmt("--zig={s}", .{b.graph.zig_exe})); run.addArg(b.fmt("--zig={s}", .{b.graph.zig_exe}));
run.addArg(b.fmt("--work-path={s}", .{work_path})); run.addArg(b.fmt("--work-path={s}", .{work_path}));
run.addArg(b.fmt("--root-path={s}", .{b.root.root_dir.path.?}));
if (exno) |n| { if (exno) |n| {
run.addArg(b.fmt("--only={d}", .{n})); run.addArg(b.fmt("--only={d}", .{n}));
+16 -2
View File
@@ -26,8 +26,13 @@ pub fn main() void {
// (Problem 2) // (Problem 2)
// Please set this array using repetition. // Please set this array using repetition.
// It should result in: 1 0 0 1 1 0 0 1 1 0 0 1 // It should result in: 1 0 0 1 1 0 0 1 1 0 0 1
const bit_pattern_unit = [_]u8{ ??? }; const bit_pattern_unit = ???;
const bit_pattern: [3 * bit_pattern_unit.len]u8 = @bitCast(@as([3][bit_pattern_unit.len]u8, @splat(bit_pattern_unit)));
// How long should the bit pattern be?
const len = ???;
// For now, don't worry about the use of SIMD.
const bit_pattern: [len]u8 = std.simd.repeat(len, bit_pattern_unit);
// Okay, that's all of the problems. Let's see the results. // Okay, that's all of the problems. Let's see the results.
// //
@@ -53,3 +58,12 @@ pub fn main() void {
std.debug.print("\n", .{}); std.debug.print("\n", .{});
} }
// For the curious:
//
// The `std.simd.repeat` function takes a target length and a pattern,
// and returns a vector filled with that pattern repeated to the
// desired length.
//
// For example, `repeat(5, [_]u8{1, 2})` will return a vector
// equivalent to `.{1, 2, 1, 2, 1}`.
+1 -1
View File
@@ -1,6 +1,6 @@
// //
// As with integers, you can pass a pointer to a struct when you // As with integers, you can pass a pointer to a struct when you
// will wish to modify that struct. Pointers are also useful when // wish to modify that struct. Pointers are also useful when
// you need to store a reference to a struct (a "link" to it). // you need to store a reference to a struct (a "link" to it).
// //
// const Vertex = struct{ x: u32, y: u32, z: u32 }; // const Vertex = struct{ x: u32, y: u32, z: u32 };
+18
View File
@@ -37,6 +37,24 @@
// self, others use a lowercase version of the type name, but feel // self, others use a lowercase version of the type name, but feel
// free to use whatever is most appropriate. // free to use whatever is most appropriate.
// //
// "But hold on," you say, eyeing a() and b() suspiciously, "why
// does one take 'self' and another take '*self'?" Sharp eye!
//
// It all comes down to a single question: does the function need
// to CHANGE the struct?
//
// * Needs to change it? Take a pointer (*Bar). Without it you'd
// be scribbling on a COPY, and your changes would evaporate the
// instant the function returns. Poof.
// * Only reads it? Plain Bar is just fine.
// (For a big, bulky struct you might still write *const Bar to
// avoid copying it around, but for small ones a copy is cheap.)
//
// You'll see this below: zap() takes 'self: HeatRay' by value
// because it only reads the ray's damage, but it takes the alien
// as '*Alien' because zapping is supposed to HURT - and that means
// changing the alien's health for real, not on a throwaway copy.
//
// Okay, you're armed. // Okay, you're armed.
// //
// Now, please zap the alien structs until they're all gone or // Now, please zap the alien structs until they're all gone or
+3 -3
View File
@@ -47,7 +47,7 @@ const Narcissus = struct {
myself: *Narcissus = undefined, myself: *Narcissus = undefined,
echo: void = undefined, // Alas, poor Echo! echo: void = undefined, // Alas, poor Echo!
fn fetchTheMostBeautifulType() type { fn FetchTheMostBeautifulType() type {
return @This(); return @This();
} }
}; };
@@ -70,7 +70,7 @@ pub fn main() void {
// //
// The fix for this is very subtle, but it makes a big // The fix for this is very subtle, but it makes a big
// difference! // difference!
const Type2 = narcissus.fetchTheMostBeautifulType(); const Type2 = narcissus.FetchTheMostBeautifulType();
// Now we print a pithy statement about Narcissus. // Now we print a pithy statement about Narcissus.
print("A {s} loves all {s}es. ", .{ print("A {s} loves all {s}es. ", .{
@@ -94,7 +94,7 @@ pub fn main() void {
print("He has room in his heart for:", .{}); print("He has room in his heart for:", .{});
// `field_names` is a slice of strings and it holds the names of the struct's 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, // `field_types` is a slice of types and it holds the types of the struct's fields,
// it is guaranteed to be the same length as `field_names` // it is guaranteed to be the same length as `field_names`
const field_names = @typeInfo(Narcissus).@"struct".field_names; const field_names = @typeInfo(Narcissus).@"struct".field_names;
const field_types = @typeInfo(Narcissus).@"struct".field_types; const field_types = @typeInfo(Narcissus).@"struct".field_types;
+7 -8
View File
@@ -1,5 +1,5 @@
// //
// In Exercises 84-91, we learned about Zig's Io interface for // In Exercises 85-94 and quiz 95, we learned about Zig's Io interface for
// concurrent execution: io.async(), Group, Select, and Futures. // concurrent execution: io.async(), Group, Select, and Futures.
// Under the hood, the Threaded backend manages a pool of real // Under the hood, the Threaded backend manages a pool of real
// OS threads for you - including scheduling, cancellation, and // OS threads for you - including scheduling, cancellation, and
@@ -74,11 +74,11 @@ pub fn main() !void {
// before the parallel processing begins. // before the parallel processing begins.
std.debug.print("Starting work...\n", .{}); std.debug.print("Starting work...\n", .{});
// These curly brackets are very important, they are necessary // These curly braces are very important, they are necessary
// to enclose the area where the threads are called. // to enclose the area where the threads are called and joined.
// Without these brackets, the program would not wait for the // With these braces, the program will block and wait for all threads
// end of the threads and they would continue to run beyond the // to finish right at the closing brace of this block, ensuring
// end of the program. // "Zig is cool!" is always printed last.
{ {
// Now we start the first thread, with the number as parameter // Now we start the first thread, with the number as parameter
const handle = try std.Thread.spawn(.{}, thread_function, .{1}); const handle = try std.Thread.spawn(.{}, thread_function, .{1});
@@ -102,8 +102,7 @@ pub fn main() !void {
try io.sleep(std.Io.Duration.fromMilliseconds(400), .awake); try io.sleep(std.Io.Duration.fromMilliseconds(400), .awake);
std.debug.print("Some weird stuff, after starting the threads.\n", .{}); std.debug.print("Some weird stuff, after starting the threads.\n", .{});
} }
// After we have left the closed area, we wait until // The threads are guaranteed to be finished by the time we reach here.
// the threads have run through, if this has not yet been the case.
std.debug.print("Zig is cool!\n", .{}); std.debug.print("Zig is cool!\n", .{});
} }
+1 -1
View File
@@ -46,7 +46,7 @@
// 1,000,000,000 partial values. And for each additional digit we have to // 1,000,000,000 partial values. And for each additional digit we have to
// add a zero. // add a zero.
// Even fast computers - and I mean really fast computers - get a bit warmer // 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 // on the CPU when it comes to a large number of digits. But 8 digits are
// enough for us for now, because we want to understand the principle and // enough for us for now, because we want to understand the principle and
// nothing more, right? // nothing more, right?
// //
+1 -1
View File
@@ -50,7 +50,7 @@ pub fn main(init: std.process.Init) !void {
// wait a minute... // wait a minute...
// opening a directory might fail! // opening a directory might fail!
// what should we do here? // what should we do here?
var output_dir: std.Io.Dir = try cwd.openDir(io, "output", .{}); var output_dir: std.Io.Dir = cwd.openDir(io, "output", .{});
defer output_dir.close(io); defer output_dir.close(io);
// we try to open the file `zigling.txt`, // we try to open the file `zigling.txt`,
+4
View File
@@ -145,3 +145,7 @@ pub fn main() void {
print("Max difference (old fn): {d: >5.3}\n", .{mpd_old}); print("Max difference (old fn): {d: >5.3}\n", .{mpd_old});
print("Max difference (new fn): {d: >5.3}\n", .{mpd_new}); print("Max difference (new fn): {d: >5.3}\n", .{mpd_new});
} }
// Another cool feature of Vectors is repeating patterns.
// Remember the arrays exercise from earlier where we created an array
// by repeating a pattern? See `005_arrays2.zig`.
+1 -1
View File
@@ -14,7 +14,7 @@
// //
// A common activity in microcontroller programming is setting and clearing // A common activity in microcontroller programming is setting and clearing
// bits on input and output pins. This lets you control LEDs, sensors, motors // 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 // and more! In a previous exercise (100_bit_manipulation.zig) you learned how
// to swap two bytes using the ^ (XOR - exclusive or) operator. This quiz will // 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 // 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 // what it's like to control registers in a real microcontroller. Included at
+2 -2
View File
@@ -1,6 +1,6 @@
// //
// We've already learned plenty about bit manipulation using bitwise operations // We've already learned plenty about bit manipulation using bitwise operations
// in exercises 097 and 098 and in quiz 110. The techniques we already know work // in exercises 100 and 101 and in quiz 113. The techniques we already know work
// just fine, but creating masks and shifting individual bits around can become // just fine, but creating masks and shifting individual bits around can become
// quite tedious and unwieldy pretty quickly. // quite tedious and unwieldy pretty quickly.
// What if there was a better, a more convenient way to control individual bits? // What if there was a better, a more convenient way to control individual bits?
@@ -78,7 +78,7 @@ const FLG = packed struct(u8) {
content_checksum: bool, content_checksum: bool,
content_size: bool, content_size: bool,
block_checksum: bool, block_checksum: bool,
block_indepencence: bool, block_independence: bool,
version: u2, version: u2,
}; };
+3 -3
View File
@@ -1,5 +1,5 @@
// //
// We've already learned about switch statements in exercises 030, 031 and 108. // We've already learned about switch statements in exercises 030, 031 and 111.
// They also work with packed containers: // They also work with packed containers:
const S = packed struct(u2) { const S = packed struct(u2) {
@@ -51,11 +51,11 @@ comptime {
// //
// Try to make the float below negative: // Try to make the float below negative:
/// IEEE 754 half precision float // IEEE 754 binary16 floating-point format
const Float = packed union(u16) { const Float = packed union(u16) {
value: f16, value: f16,
bits: packed struct(u16) { bits: packed struct(u16) {
mantissa: u10, significand: u10,
exponent: u5, exponent: u5,
sign: u1, sign: u1,
}, },
+10 -6
View File
@@ -1,6 +1,6 @@
--- exercises/005_arrays2.zig 2026-05-04 16:26:32.778330847 +0200 --- exercises/005_arrays2.zig 2026-06-27 09:03:29.918124920 +0000
+++ answers/005_arrays2.zig 2026-05-04 16:26:13.082917974 +0200 +++ answers/005_arrays2.zig 2026-06-27 09:01:13.162138431 +0000
@@ -21,12 +21,12 @@ @@ -21,15 +21,15 @@
// (Problem 1) // (Problem 1)
// Please set this array concatenating the two arrays above. // Please set this array concatenating the two arrays above.
// It should result in: 1 3 3 7 // It should result in: 1 3 3 7
@@ -10,8 +10,12 @@
// (Problem 2) // (Problem 2)
// Please set this array using repetition. // Please set this array using repetition.
// It should result in: 1 0 0 1 1 0 0 1 1 0 0 1 // It should result in: 1 0 0 1 1 0 0 1 1 0 0 1
- const bit_pattern_unit = [_]u8{ ??? }; - const bit_pattern_unit = ???;
+ const bit_pattern_unit = [_]u8{ 1, 0, 0, 1 }; + 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. // How long should the bit pattern be?
- const len = ???;
+ const len = 3 * bit_pattern_unit.len;
// For now, don't worry about the use of SIMD.
const bit_pattern: [len]u8 = std.simd.repeat(len, bit_pattern_unit);
+3 -3
View File
@@ -1,6 +1,6 @@
--- exercises/047_methods.zig 2023-10-03 22:15:22.122241138 +0200 --- exercises/047_methods.zig 2026-06-21 16:11:59.110876971 +0200
+++ answers/047_methods.zig 2023-10-05 20:04:07.056100214 +0200 +++ answers/047_methods.zig 2026-06-21 16:13:07.066363558 +0200
@@ -88,7 +88,7 @@ @@ -106,7 +106,7 @@
for (&aliens) |*alien| { for (&aliens) |*alien| {
// *** Zap the alien with the heat ray here! *** // *** Zap the alien with the heat ray here! ***
+2 -2
View File
@@ -13,8 +13,8 @@
// //
// The fix for this is very subtle, but it makes a big // The fix for this is very subtle, but it makes a big
// difference! // difference!
- const Type2 = narcissus.fetchTheMostBeautifulType(); - const Type2 = narcissus.FetchTheMostBeautifulType();
+ const Type2 = Narcissus.fetchTheMostBeautifulType(); + const Type2 = Narcissus.FetchTheMostBeautifulType();
// Now we print a pithy statement about Narcissus. // Now we print a pithy statement about Narcissus.
print("A {s} loves all {s}es. ", .{ print("A {s} loves all {s}es. ", .{
+11 -2
View File
@@ -1,5 +1,5 @@
--- exercises/109_files.zig 2026-03-20 19:23:48.874150121 +0100 --- exercises/109_files.zig 2026-06-29 17:08:04.124224900 +0000
+++ answers/109_files.zig 2026-04-02 10:51:15.813831695 +0200 +++ answers/109_files.zig 2026-06-29 17:13:20.696220533 +0000
@@ -41,7 +41,7 @@ @@ -41,7 +41,7 @@
// by doing nothing // by doing nothing
// //
@@ -9,6 +9,15 @@
// if there's any other unexpected error we just propagate it through // if there's any other unexpected error we just propagate it through
else => return e, else => return e,
}; };
@@ -50,7 +50,7 @@
// wait a minute...
// opening a directory might fail!
// what should we do here?
- var output_dir: std.Io.Dir = cwd.openDir(io, "output", .{});
+ var output_dir: std.Io.Dir = try cwd.openDir(io, "output", .{});
defer output_dir.close(io);
// we try to open the file `zigling.txt`,
@@ -61,7 +61,7 @@ @@ -61,7 +61,7 @@
// but here we are not yet done writing to the file // but here we are not yet done writing to the file
// if only there were a keyword in Zig that // if only there were a keyword in Zig that
+72 -49
View File
@@ -19,12 +19,28 @@
const std = @import("std"); const std = @import("std");
const builtin = @import("builtin"); const builtin = @import("builtin");
const Io = std.Io;
const File = Io.File;
const Process = std.process; const Process = std.process;
const Terminal = std.Io.Terminal;
const print = std.debug.print; const print = std.debug.print;
const cutPrefix = std.mem.cutPrefix; const cutPrefix = std.mem.cutPrefix;
const progress_filename = ".progress.txt"; const progress_filename = ".progress.txt";
const no_colors_help =
\\Colors are not available. Check the NO_COLOR environ variable.
\\
\\
;
const windows_colors_help =
\\Windows colors are not supported.
\\Install Windows Terminal or update to a more recent Windows version.
\\
\\
;
pub const logo = pub const logo =
\\ _ _ _ \\ _ _ _
\\ ___(_) __ _| (_)_ __ __ _ ___ \\ ___(_) __ _| (_)_ __ __ _ ___
@@ -109,41 +125,23 @@ const Context = struct {
arena: std.mem.Allocator, arena: std.mem.Allocator,
zig_exe: []const u8, zig_exe: []const u8,
work_path: []const u8, work_path: []const u8,
root_path: []const u8,
}; };
const Error = error{Failed}; const Error = error{Failed};
// Ansi colors. var stderr_term_mode: Terminal.Mode = .no_color;
var use_color_escapes = false;
var red_text: []const u8 = "";
var red_bold_text: []const u8 = "";
var red_dim_text: []const u8 = "";
var green_text: []const u8 = "";
var yellow_text: []const u8 = "";
var bold_text: []const u8 = "";
var reset_text: []const u8 = "";
fn setupColors(io: std.Io) void { // Ansi colors.
use_color_escapes = false; const C = struct {
const stderr = std.Io.File.stderr(); var red: []const u8 = "\x1b[31m";
if (stderr.supportsAnsiEscapeCodes(io)) |ok| { var red_bold: []const u8 = "\x1b[31;1m";
if (ok) use_color_escapes = true; var red_dim: []const u8 = "\x1b[31;2m";
} else |_| {} var green: []const u8 = "\x1b[32m";
if (!use_color_escapes and builtin.os.tag == .windows) { var yellow: []const u8 = "\x1b[33m";
if (stderr.enableAnsiEscapeCodes(io)) { var bold: []const u8 = "\x1b[1m";
use_color_escapes = true; var reset: []const u8 = "\x1b[0m";
} else |_| {} };
}
if (use_color_escapes) {
red_text = "\x1b[31m";
red_bold_text = "\x1b[31;1m";
red_dim_text = "\x1b[31;2m";
green_text = "\x1b[32m";
yellow_text = "\x1b[33m";
bold_text = "\x1b[1m";
reset_text = "\x1b[0m";
}
}
pub fn main(init: std.process.Init) !void { pub fn main(init: std.process.Init) !void {
const io = init.io; const io = init.io;
@@ -152,24 +150,51 @@ pub fn main(init: std.process.Init) !void {
var args_it = try init.minimal.args.iterateAllocator(arena); var args_it = try init.minimal.args.iterateAllocator(arena);
if (!args_it.skip()) @panic("expected self arg"); if (!args_it.skip()) @panic("expected self arg");
setupColors(io); const stderr = File.stderr();
// Setup colors.
const NO_COLOR = try init.minimal.environ.containsUnempty(arena, "NO_COLOR");
const CLICOLOR_FORCE = try init.minimal.environ.containsUnempty(arena, "CLICOLOR_FORCE");
const term_mode = try Terminal.Mode.detect(io, stderr, NO_COLOR, CLICOLOR_FORCE);
stderr_term_mode = term_mode;
// Show help in case colors are not available or not supported.
switch (term_mode) {
.no_color => print(no_colors_help, .{}),
.escape_codes => {},
.windows_api => print(windows_colors_help, .{}),
}
if (term_mode == .no_color) {
C.red = "";
C.red_bold = "";
C.red_dim = "";
C.green = "";
C.yellow = "";
C.bold = "";
C.reset = "";
}
if (!validateExercises()) std.process.exit(1); if (!validateExercises()) std.process.exit(1);
var zig_exe: []const u8 = "zig"; var zig_exe: []const u8 = "zig";
var work_path: []const u8 = "exercises"; var work_path: []const u8 = "exercises";
var root_path: []const u8 = "";
var mode: Mode = .normal; var mode: Mode = .normal;
var only_n: ?usize = null; var only_n: ?usize = null;
var start_n: ?usize = null; var start_n: ?usize = null;
while (args_it.next()) |arg| { while (args_it.next()) |arg| {
if (std.mem.eql(u8, arg, "--logo")) { if (std.mem.eql(u8, arg, "--logo")) {
print("{s}{s}{s}", .{ yellow_text, logo, reset_text }); print("{s}{s}{s}", .{ C.yellow, logo, C.reset });
return; return;
} else if (cutPrefix(u8, arg, "--zig=")) |v| { } else if (cutPrefix(u8, arg, "--zig=")) |v| {
zig_exe = v; zig_exe = v;
} else if (cutPrefix(u8, arg, "--work-path=")) |v| { } else if (cutPrefix(u8, arg, "--work-path=")) |v| {
work_path = v; work_path = v;
} else if (cutPrefix(u8, arg, "--root-path=")) |v| {
root_path = v;
} else if (cutPrefix(u8, arg, "--only=")) |v| { } else if (cutPrefix(u8, arg, "--only=")) |v| {
only_n = std.fmt.parseInt(usize, v, 10) catch { only_n = std.fmt.parseInt(usize, v, 10) catch {
print("invalid --only value: {s}\n", .{v}); print("invalid --only value: {s}\n", .{v});
@@ -197,6 +222,7 @@ pub fn main(init: std.process.Init) !void {
.arena = arena, .arena = arena,
.zig_exe = zig_exe, .zig_exe = zig_exe,
.work_path = work_path, .work_path = work_path,
.root_path = root_path,
}; };
switch (mode) { switch (mode) {
@@ -238,7 +264,7 @@ pub fn main(init: std.process.Init) !void {
} }
} else { } else {
// All solved. // All solved.
print("{s}All exercises completed!{s}\n", .{ green_text, reset_text }); print("{s}All exercises completed!{s}\n", .{ C.green, C.reset });
return; return;
} }
iterateFrom(ctx, start_index) catch std.process.exit(1); iterateFrom(ctx, start_index) catch std.process.exit(1);
@@ -260,7 +286,7 @@ fn runOne(ctx: Context, ex: Exercise, mode: Mode) Error!void {
if (ex.skip) { if (ex.skip) {
print("Skipping {s}", .{ex.main_file}); print("Skipping {s}", .{ex.main_file});
if (ex.skip_hint) |hint| if (ex.skip_hint) |hint|
print("\n{s}Reason: {s}{s}\n", .{ bold_text, hint, reset_text }); print("\n{s}Reason: {s}{s}\n", .{ C.bold, hint, C.reset });
print("\n\n", .{}); print("\n\n", .{});
return; return;
} }
@@ -283,7 +309,7 @@ fn runOne(ctx: Context, ex: Exercise, mode: Mode) Error!void {
fn hintAndHelp(ex: Exercise, mode: Mode) void { fn hintAndHelp(ex: Exercise, mode: Mode) void {
if (ex.hint) |hint| if (ex.hint) |hint|
print("\n{s}{s}Ziglings hint: {s}{s}", .{ bold_text, green_text, hint, reset_text }); print("\n{s}{s}Ziglings hint: {s}{s}", .{ C.bold, C.green, hint, C.reset });
help(ex, mode); help(ex, mode);
} }
@@ -310,7 +336,7 @@ fn runExe(ctx: Context, ex: Exercise) !void {
const arena = ctx.arena; const arena = ctx.arena;
print("Compiling {s}...\n", .{ex.main_file}); print("Compiling {s}...\n", .{ex.main_file});
const path = std.fs.path.join(arena, &.{ ctx.work_path, ex.main_file }) catch @panic("OOM"); const path = std.fs.path.join(arena, &.{ ctx.root_path, ctx.work_path, ex.main_file }) catch @panic("OOM");
var argv = std.ArrayList([]const u8).initCapacity(arena, 8) catch @panic("OOM"); var argv = std.ArrayList([]const u8).initCapacity(arena, 8) catch @panic("OOM");
argv.append(arena, ctx.zig_exe) catch @panic("OOM"); argv.append(arena, ctx.zig_exe) catch @panic("OOM");
@@ -328,7 +354,7 @@ fn runExe(ctx: Context, ex: Exercise) !void {
.stderr_limit = .limited(1024 * 1024), .stderr_limit = .limited(1024 * 1024),
}) catch |err| { }) catch |err| {
print("{s}error:{s} unable to run {s}: {s}\n", .{ print("{s}error:{s} unable to run {s}: {s}\n", .{
red_bold_text, reset_text, ex.main_file, @errorName(err), C.red_bold, C.reset, ex.main_file, @errorName(err),
}); });
return err; return err;
}; };
@@ -344,8 +370,7 @@ fn runTest(ctx: Context, ex: Exercise) !void {
const arena = ctx.arena; const arena = ctx.arena;
print("Compiling {s}...\n", .{ex.main_file}); print("Compiling {s}...\n", .{ex.main_file});
const path = std.fs.path.join(arena, &.{ ctx.work_path, ex.main_file }) catch const path = std.fs.path.join(arena, &.{ ctx.root_path, ctx.work_path, ex.main_file }) catch @panic("OOM");
@panic("OOM");
var argv = std.ArrayList([]const u8).initCapacity(arena, 8) catch @panic("OOM"); var argv = std.ArrayList([]const u8).initCapacity(arena, 8) catch @panic("OOM");
argv.append(arena, ctx.zig_exe) catch @panic("OOM"); argv.append(arena, ctx.zig_exe) catch @panic("OOM");
@@ -362,7 +387,7 @@ fn runTest(ctx: Context, ex: Exercise) !void {
.stderr_limit = .limited(1024 * 1024), .stderr_limit = .limited(1024 * 1024),
}) catch |err| { }) catch |err| {
print("{s}error:{s} unable to run test {s}: {s}\n", .{ print("{s}error:{s} unable to run test {s}: {s}\n", .{
red_bold_text, reset_text, ex.main_file, @errorName(err), C.red_bold, C.reset, ex.main_file, @errorName(err),
}); });
return err; return err;
}; };
@@ -384,7 +409,7 @@ fn checkOutput(io: std.Io, arena: std.mem.Allocator, ex: Exercise, result: Proce
}, },
else => { else => {
print("{s}{s} terminated unexpectedly{s}\n", .{ print("{s}{s} terminated unexpectedly{s}\n", .{
red_bold_text, ex.main_file, reset_text, C.red_bold, ex.main_file, C.reset,
}); });
return Error.Failed; return Error.Failed;
}, },
@@ -419,8 +444,6 @@ fn checkOutput(io: std.Io, arena: std.mem.Allocator, ex: Exercise, result: Proce
} }
if (!std.mem.eql(u8, output, exercise_output)) { if (!std.mem.eql(u8, output, exercise_output)) {
const red = red_bold_text;
const reset = reset_text;
print( print(
\\ \\
\\{s}========= expected this output: =========={s} \\{s}========= expected this output: =========={s}
@@ -428,11 +451,11 @@ fn checkOutput(io: std.Io, arena: std.mem.Allocator, ex: Exercise, result: Proce
\\{s}========= but found: ====================={s} \\{s}========= but found: ====================={s}
\\{s} \\{s}
\\{s}=========================================={s} \\{s}=========================================={s}
++ "\n", .{ red, reset, exercise_output, red, reset, output, red, reset }); ++ "\n", .{ C.red_bold, C.reset, exercise_output, C.red_bold, C.reset, output, C.red_bold, C.reset });
return Error.Failed; return Error.Failed;
} }
print("{s}PASSED:\n{s}{s}\n\n", .{ green_text, output, reset_text }); print("{s}PASSED:\n{s}{s}\n\n", .{ C.green, output, C.reset });
} }
fn checkTest(ex: Exercise, result: Process.RunResult) !void { fn checkTest(ex: Exercise, result: Process.RunResult) !void {
@@ -444,12 +467,12 @@ fn checkTest(ex: Exercise, result: Process.RunResult) !void {
}, },
else => { else => {
print("{s}{s} terminated unexpectedly{s}\n", .{ print("{s}{s} terminated unexpectedly{s}\n", .{
red_bold_text, ex.main_file, reset_text, C.red_bold, ex.main_file, C.reset,
}); });
return Error.Failed; return Error.Failed;
}, },
} }
print("{s}PASSED{s}\n\n", .{ green_text, reset_text }); print("{s}PASSED{s}\n\n", .{ C.green, C.reset });
} }
fn help(ex: Exercise, mode: Mode) void { fn help(ex: Exercise, mode: Mode) void {
@@ -459,12 +482,12 @@ fn help(ex: Exercise, mode: Mode) void {
.random => "zig build -Drandom", .random => "zig build -Drandom",
}; };
print("\n{s}Edit exercises/{s} and run '{s}' again.{s}\n", .{ print("\n{s}Edit exercises/{s} and run '{s}' again.{s}\n", .{
red_bold_text, ex.main_file, cmd, reset_text, C.red_bold, ex.main_file, cmd, C.reset,
}); });
} }
fn resetLine() void { fn resetLine() void {
if (use_color_escapes) print("{s}", .{"\x1b[2K\r"}); if (stderr_term_mode == .escape_codes) print("{s}", .{"\x1b[2K\r"});
} }
// Removes trailing whitespace per line and any trailing LF at the end. // Removes trailing whitespace per line and any trailing LF at the end.