summaryrefslogtreecommitdiff
path: root/rivendell
diff options
context:
space:
mode:
authorChris Boesch <chrboesch@noreply.codeberg.org>2026-06-02 20:26:16 +0200
committerChris Boesch <chrboesch@noreply.codeberg.org>2026-06-02 20:26:16 +0200
commitbeeca8d510891a272868f447e8193b0fcee7e2fc (patch)
tree00c74f53bddb0406737b50899fc74c989e2fa797 /rivendell
parent9b879625954cc92be02cf98d03fff140de7be23e (diff)
parent01ad2961140ce9ef552532fa56923ec8d3b81f9f (diff)
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
Diffstat (limited to 'rivendell')
-rw-r--r--rivendell/elrond.zig1223
1 files changed, 1223 insertions, 0 deletions
diff --git a/rivendell/elrond.zig b/rivendell/elrond.zig
new file mode 100644
index 0000000..52cf395
--- /dev/null
+++ b/rivendell/elrond.zig
@@ -0,0 +1,1223 @@
+//
+// "You have a long way to go,
+// and the path is not easy."
+// Elrond, The Hobbit or The Fellowship of the Ring
+//
+//
+// In the reworked Zig build system (configurer/maker split) a build Step no
+// longer carries a `makeFn`, and the configure phase is cached and not re-run
+// when build.zig is unchanged. So Elrond is a standalone program that owns
+// the entire Ziglings logic: it holds the exercise list, reads/writes
+// .progress.txt, and iterates through exercises itself -- compiling (via
+// `zig run`), checking output, printing progress and hints.
+// The build only ever launches this one program.
+//
+// Exit codes:
+// 0 all requested exercises passed (or skipped, or --logo)
+// 1 an exercise failed (compile error, output mismatch, runtime error)
+
+const std = @import("std");
+const builtin = @import("builtin");
+
+const Process = std.process;
+const print = std.debug.print;
+
+const progress_filename = ".progress.txt";
+
+pub const logo =
+ \\ _ _ _
+ \\ ___(_) __ _| (_)_ __ __ _ ___
+ \\ |_ | |/ _' | | | '_ \ / _' / __|
+ \\ / /| | (_| | | | | | | (_| \__ \
+ \\ /___|_|\__, |_|_|_| |_|\__, |___/
+ \\ |___/ |___/
+ \\
+ \\ "Look out! Broken programs below!"
+ \\
+ \\
+;
+
+// How Elrond was called.
+const Mode = enum {
+ // `zig build`: iterate from after the last solved exercise to the first unsolved one (or the end).
+ normal,
+ // `zig build -Dn=n`: check exactly one exercise.
+ named,
+ // `zig build -Drandom`: check one random exercise.
+ random,
+ // `zig build -Ds=n`: iterate starting at exercise n.
+ start,
+};
+
+const Kind = enum {
+ // Run the artifact as a normal executable.
+ exe,
+ // Run the artifact as a test.
+ @"test",
+};
+
+pub const Exercise = struct {
+ // main_file must have the format key_name.zig.
+ main_file: []const u8,
+
+ // Desired output. A program passes if its output, excluding trailing whitespace, equals this string.
+ output: []const u8,
+
+ // Optional hint shown if the program does not succeed.
+ hint: ?[]const u8 = null,
+
+ // By default, output is verified against stderr; set to check stdout.
+ check_stdout: bool = false,
+
+ // This exercise uses C functions; compile with libc.
+ link_libc: bool = false,
+
+ // Exercise kind.
+ kind: Kind = .exe,
+
+ // Not supported by the current Zig compiler.
+ skip: bool = false,
+
+ // Why this has been skipped.
+ skip_hint: ?[]const u8 = null,
+
+ timestamp: bool = false,
+
+ // Name of the main file with .zig stripped.
+ pub fn name(self: Exercise) []const u8 {
+ return std.fs.path.stem(self.main_file);
+ }
+
+ // Key of the main file: the string before the '_' with zero padding removed. "001_hello.zig" -> "1".
+ pub fn key(self: Exercise) []const u8 {
+ const end_index = std.mem.indexOfScalar(u8, self.main_file, '_') orelse unreachable;
+ var start_index: usize = 0;
+ while (self.main_file[start_index] == '0') start_index += 1;
+ return self.main_file[start_index..end_index];
+ }
+
+ // Exercise key as an integer.
+ pub fn number(self: Exercise) usize {
+ return std.fmt.parseInt(usize, self.key(), 10) catch unreachable;
+ }
+};
+
+// Ansi colors.
+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 {
+ use_color_escapes = false;
+ const stderr = std.Io.File.stderr();
+ if (stderr.supportsAnsiEscapeCodes(io)) |ok| {
+ if (ok) use_color_escapes = true;
+ } else |_| {}
+ if (!use_color_escapes and builtin.os.tag == .windows) {
+ if (stderr.enableAnsiEscapeCodes(io)) {
+ use_color_escapes = true;
+ } 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 {
+ const io = init.io;
+ const arena = init.arena.allocator();
+
+ const args = try init.minimal.args.toSlice(arena);
+
+ setupColors(io);
+
+ if (!validateExercises()) std.process.exit(1);
+
+ var zig_exe: []const u8 = "zig";
+ var work_path: []const u8 = "exercises";
+ var mode: Mode = .normal;
+ var only_n: ?usize = null;
+ var start_n: ?usize = null;
+
+ for (1..args.len) |n| {
+ const arg = args[n];
+ if (std.mem.eql(u8, arg, "--logo")) {
+ print("{s}{s}{s}", .{ yellow_text, logo, reset_text });
+ return;
+ } else if (prefix(arg, "--zig=")) |v| {
+ zig_exe = v;
+ } else if (prefix(arg, "--work-path=")) |v| {
+ work_path = v;
+ } else if (prefix(arg, "--only=")) |v| {
+ only_n = std.fmt.parseInt(usize, v, 10) catch {
+ print("invalid --only value: {s}\n", .{v});
+ std.process.exit(1);
+ };
+ mode = .named;
+ } else if (prefix(arg, "--start=")) |v| {
+ start_n = std.fmt.parseInt(usize, v, 10) catch {
+ print("invalid --start value: {s}\n", .{v});
+ std.process.exit(1);
+ };
+ mode = .start;
+ } else if (std.mem.eql(u8, arg, "--random")) {
+ mode = .random;
+ } else {
+ print("unknown argument: {s}\n", .{arg});
+ std.process.exit(2);
+ }
+ }
+
+ print("{s}", .{logo});
+
+ const ctx: Context = .{ .io = io, .arena = arena, .zig_exe = zig_exe, .work_path = work_path };
+
+ switch (mode) {
+ .named => {
+ const n = only_n.?;
+ if (n == 0 or n > exercises.len - 1) {
+ print("unknown exercise number: {}\n", .{n});
+ std.process.exit(1);
+ }
+ runOne(ctx, exercises[n - 1], .named) catch std.process.exit(1);
+ },
+ .random => {
+ var prng = std.Random.DefaultPrng.init(blk: {
+ var seed: u64 = undefined;
+ io.random(std.mem.asBytes(&seed));
+ break :blk seed;
+ });
+ const num = prng.random().intRangeLessThan(usize, 0, exercises.len);
+ print("random exercise: {s}\n", .{exercises[num].main_file});
+ runOne(ctx, exercises[num], .random) catch std.process.exit(1);
+ },
+ .start => {
+ const s = start_n.?;
+ if (s == 0 or s > exercises.len - 1) {
+ print("unknown exercise number: {}\n", .{s});
+ std.process.exit(1);
+ }
+ // Iterate from exercise s to the end (or first failure).
+ iterateFrom(ctx, s - 1) catch std.process.exit(1);
+ },
+ .normal => {
+ // Start after the last solved exercise recorded in .progress.txt.
+ const solved = readProgress(io, arena);
+ var start_index: usize = 0;
+ for (exercises, 0..) |ex, idx| {
+ if (solved < ex.number()) {
+ start_index = idx;
+ break;
+ }
+ } else {
+ // All solved.
+ print("{s}All exercises completed!{s}\n", .{ green_text, reset_text });
+ return;
+ }
+ iterateFrom(ctx, start_index) catch std.process.exit(1);
+ },
+ }
+}
+
+fn prefix(arg: []const u8, pre: []const u8) ?[]const u8 {
+ if (std.mem.startsWith(u8, arg, pre)) return arg[pre.len..];
+ return null;
+}
+
+// Shared, read-only run context threaded through the helpers.
+const Context = struct {
+ io: std.Io,
+ arena: std.mem.Allocator,
+ zig_exe: []const u8,
+ work_path: []const u8,
+};
+
+const Error = error{Failed};
+
+// Iterates exercises from `start_index` to the end, stopping at the first failure.
+// Progress is written after each passed exercise.
+fn iterateFrom(ctx: Context, start_index: usize) Error!void {
+ for (exercises[start_index..]) |ex| {
+ try runOne(ctx, ex, .normal);
+ }
+}
+
+// Checks a single exercise: progress bar, compile+run (or test), output
+// verification, progress file update on success, hint on failure.
+fn runOne(ctx: Context, ex: Exercise, mode: Mode) Error!void {
+ if (ex.skip) {
+ print("Skipping {s}", .{ex.main_file});
+ if (ex.skip_hint) |hint|
+ print("\n{s}Reason: {s}{s}\n", .{ bold_text, hint, reset_text });
+ print("\n\n", .{});
+ return;
+ }
+
+ printProgress(ex.number(), exercises.len - 1);
+
+ switch (ex.kind) {
+ .exe => runExe(ctx, ex) catch {
+ hintAndHelp(ex, mode);
+ return Error.Failed;
+ },
+ .@"test" => runTest(ctx, ex) catch {
+ hintAndHelp(ex, mode);
+ return Error.Failed;
+ },
+ }
+
+ writeProgress(ctx.io, ctx.arena, ex.number()) catch {};
+}
+
+fn hintAndHelp(ex: Exercise, mode: Mode) void {
+ if (ex.hint) |hint|
+ print("\n{s}{s}Ziglings hint: {s}{s}", .{ bold_text, green_text, hint, reset_text });
+ help(ex, mode);
+}
+
+fn printProgress(num: usize, max: usize) void {
+ const bar_width = 60;
+ const safe_max = if (max == 0) 1 else max;
+ const filled_len_u64 = (@as(u64, num) * bar_width) / safe_max;
+ const filled_len = @as(u32, @intCast(filled_len_u64));
+
+ var bar_buf: [bar_width]u8 = undefined;
+ for (0..bar_width) |n| {
+ const ord = std.math.order(n, filled_len);
+ bar_buf[n] = switch (ord) {
+ .lt => '#',
+ .eq => '>',
+ .gt => '-',
+ };
+ }
+ print("\rProgress: [{s}] {d}/{d}\n\n", .{ &bar_buf, num, max });
+}
+
+fn runExe(ctx: Context, ex: Exercise) !void {
+ const io = ctx.io;
+ const arena = ctx.arena;
+ print("Compiling {s}...\n", .{ex.main_file});
+
+ const path = std.fs.path.join(arena, &.{ ctx.work_path, ex.main_file }) 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, "run") catch @panic("OOM");
+ if (ex.link_libc) {
+ argv.append(arena, "-lc") catch @panic("OOM");
+ argv.append(arena, "-fllvm") catch @panic("OOM");
+ }
+ argv.append(arena, path) catch @panic("OOM");
+
+ // `zig run` compiles and runs in one step using Zig's own cache.
+ const result = Process.run(arena, io, .{
+ .argv = argv.items,
+ .stdout_limit = .limited(1024 * 1024),
+ .stderr_limit = .limited(1024 * 1024),
+ }) catch |err| {
+ print("{s}error:{s} unable to run {s}: {s}\n", .{
+ red_bold_text, reset_text, ex.main_file, @errorName(err),
+ });
+ return err;
+ };
+
+ resetLine();
+ print("Checking {s}...\n", .{ex.main_file});
+
+ return checkOutput(io, arena, ex, result);
+}
+
+fn runTest(ctx: Context, ex: Exercise) !void {
+ const io = ctx.io;
+ const arena = ctx.arena;
+ print("Compiling {s}...\n", .{ex.main_file});
+
+ const path = std.fs.path.join(arena, &.{ ctx.work_path, ex.main_file }) 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, "test") catch @panic("OOM");
+ if (ex.link_libc) {
+ argv.append(arena, "-lc") catch @panic("OOM");
+ argv.append(arena, "-fllvm") catch @panic("OOM");
+ }
+ argv.append(arena, path) catch @panic("OOM");
+
+ const result = Process.run(arena, io, .{
+ .argv = argv.items,
+ .stdout_limit = .limited(1024 * 1024),
+ .stderr_limit = .limited(1024 * 1024),
+ }) catch |err| {
+ print("{s}error:{s} unable to run test {s}: {s}\n", .{
+ red_bold_text, reset_text, ex.main_file, @errorName(err),
+ });
+ return err;
+ };
+
+ resetLine();
+ print("Checking {s}...\n", .{ex.main_file});
+
+ return checkTest(ex, result);
+}
+
+fn checkOutput(io: std.Io, arena: std.mem.Allocator, ex: Exercise, result: Process.RunResult) !void {
+ switch (result.term) {
+ .exited => |code| if (code != 0) {
+ // `zig run` puts both compile errors and runtime panics on stderr;
+ // show them so the "read the compiler messages above" hint applies.
+ const diag = std.mem.trimEnd(u8, result.stderr, " \r\n");
+ if (diag.len > 0) print("{s}\n", .{diag});
+ return Error.Failed;
+ },
+ else => {
+ print("{s}{s} terminated unexpectedly{s}\n", .{
+ red_bold_text, ex.main_file, reset_text,
+ });
+ return Error.Failed;
+ },
+ }
+
+ const raw_output = if (ex.check_stdout) result.stdout else result.stderr;
+ const output = trimLines(arena, raw_output) catch @panic("OOM");
+
+ var exercise_output = ex.output;
+ if (ex.timestamp) {
+ var ts_buf: [20]u8 = undefined;
+ const ts_slice = output[14..24];
+ const ts_value = try std.fmt.parseInt(i64, ts_slice, 10);
+ const ts_build = std.Io.Timestamp.now(io, .real).toSeconds();
+ const ts_diff = @abs(ts_build - ts_value);
+ const timestamp = std.fmt.bufPrint(
+ &ts_buf,
+ "{}",
+ .{if (ts_diff < 5) ts_value else ts_build},
+ ) catch unreachable;
+
+ var buf: [100]u8 = undefined;
+ const prefix_len = 14;
+ const placeholder_len = 11;
+ @memcpy(buf[0..prefix_len], exercise_output[0..prefix_len]);
+ @memcpy(buf[prefix_len..][0..timestamp.len], timestamp);
+ const suffix = exercise_output[prefix_len + placeholder_len ..];
+ const suffix_dest_start = prefix_len + timestamp.len;
+ @memcpy(buf[suffix_dest_start..][0..suffix.len], suffix);
+ const total_len = prefix_len + timestamp.len + suffix.len;
+ exercise_output = buf[0..total_len];
+ }
+
+ if (!std.mem.eql(u8, output, exercise_output)) {
+ const red = red_bold_text;
+ const reset = reset_text;
+ print(
+ \\
+ \\{s}========= expected this output: =========={s}
+ \\{s}
+ \\{s}========= but found: ====================={s}
+ \\{s}
+ \\{s}=========================================={s}
+ ++ "\n", .{ red, reset, exercise_output, red, reset, output, red, reset });
+ return Error.Failed;
+ }
+
+ print("{s}PASSED:\n{s}{s}\n\n", .{ green_text, output, reset_text });
+}
+
+fn checkTest(ex: Exercise, result: Process.RunResult) !void {
+ switch (result.term) {
+ .exited => |code| if (code != 0) {
+ const stderr = std.mem.trimEnd(u8, result.stderr, " \r\n");
+ print("\n{s}\n", .{stderr});
+ return Error.Failed;
+ },
+ else => {
+ print("{s}{s} terminated unexpectedly{s}\n", .{
+ red_bold_text, ex.main_file, reset_text,
+ });
+ return Error.Failed;
+ },
+ }
+ print("{s}PASSED{s}\n\n", .{ green_text, reset_text });
+}
+
+fn help(ex: Exercise, mode: Mode) void {
+ const cmd = switch (mode) {
+ .normal, .start => "zig build",
+ .named => "zig build -Dn=...",
+ .random => "zig build -Drandom",
+ };
+ print("\n{s}Edit exercises/{s} and run '{s}' again.{s}\n", .{
+ red_bold_text, ex.main_file, cmd, reset_text,
+ });
+}
+
+fn resetLine() void {
+ if (use_color_escapes) print("{s}", .{"\x1b[2K\r"});
+}
+
+// Removes trailing whitespace per line and any trailing LF at the end.
+fn trimLines(arena: std.mem.Allocator, buf: []const u8) ![]const u8 {
+ var list = try std.ArrayList(u8).initCapacity(arena, buf.len);
+
+ var iter = std.mem.splitSequence(u8, buf, " \n");
+ while (iter.next()) |line| {
+ const data = std.mem.trimEnd(u8, line, " \r");
+ try list.appendSlice(arena, data);
+ try list.append(arena, '\n');
+ }
+ const result = try list.toOwnedSlice(arena);
+ return std.mem.trimEnd(u8, result, "\n");
+}
+
+// Reads the last solved exercise number from .progress.txt; 0 if absent.
+fn readProgress(io: std.Io, arena: std.mem.Allocator) u32 {
+ const file = std.Io.Dir.cwd().openFile(io, progress_filename, .{}) catch return 0;
+ defer file.close(io);
+
+ const size = file.length(io) catch return 0;
+ if (size == 0) return 0;
+ const contents = arena.alloc(u8, size) catch return 0;
+ var file_buffer: [1024]u8 = undefined;
+ var reader = file.reader(io, &file_buffer);
+ const n = reader.interface.readSliceShort(contents) catch return 0;
+ const trimmed = std.mem.trim(u8, contents[0..n], " \r\n");
+ return std.fmt.parseInt(u32, trimmed, 10) catch 0;
+}
+
+fn writeProgress(io: std.Io, arena: std.mem.Allocator, number: usize) !void {
+ const progress = try std.fmt.allocPrint(arena, "{d}", .{number});
+ const file = try std.Io.Dir.cwd().createFile(
+ io,
+ progress_filename,
+ .{ .read = true, .truncate = true },
+ );
+ defer file.close(io);
+ try file.writeStreamingAll(io, progress);
+ try file.sync(io);
+}
+
+// Checks that each exercise number (except the last) forms the sequence
+// [1, exercises.len), and that output lines have no trailing whitespace.
+fn validateExercises() bool {
+ var i: usize = 0;
+ for (exercises[0..]) |ex| {
+ const exno = ex.number();
+ const last = 999;
+ i += 1;
+
+ if (exno != i and exno != last) {
+ print("exercise {s} has an incorrect number: expected {}, got {s}\n", .{
+ ex.main_file, i, ex.key(),
+ });
+ return false;
+ }
+
+ var iter = std.mem.splitScalar(u8, ex.output, '\n');
+ while (iter.next()) |line| {
+ const out = std.mem.trimEnd(u8, line, " \r");
+ if (out.len != line.len) {
+ print("exercise {s} output field lines have trailing whitespace\n", .{
+ ex.main_file,
+ });
+ return false;
+ }
+ }
+
+ if (!std.mem.endsWith(u8, ex.main_file, ".zig")) {
+ print("exercise {s} is not a zig source file\n", .{ex.main_file});
+ return false;
+ }
+ }
+ return true;
+}
+
+const exercises = [_]Exercise{
+ .{
+ .main_file = "001_hello.zig",
+ .output = "Hello world!",
+ .hint =
+ \\DON'T PANIC!
+ \\Read the compiler messages above. (Something about 'main'?)
+ \\Open up the source file as noted below and read the comments.
+ \\
+ \\(Hints like these will occasionally show up, but for the
+ \\most part, you'll be taking directions from the Zig
+ \\compiler itself.)
+ \\
+ , // pay attention to the comma
+ },
+ .{
+ .main_file = "002_std.zig",
+ .output = "Standard Library.",
+ },
+ .{
+ .main_file = "003_assignment.zig",
+ .output = "55 314159 -11",
+ .hint = "There are three mistakes in this one!",
+ },
+ .{
+ .main_file = "004_arrays.zig",
+ .output = "First: 2, Fourth: 7, Length: 8",
+ .hint = "There are two things to complete here.",
+ },
+ .{
+ .main_file = "005_arrays2.zig",
+ .output = "LEET: 1337, Bits: 100110011001",
+ .hint = "Fill in the two arrays.",
+ },
+ .{
+ .main_file = "006_strings.zig",
+ .output = "d=d Major Tom",
+ .hint = "Each '???' needs something filled in.",
+ },
+ .{
+ .main_file = "007_strings2.zig",
+ .output =
+ \\Ziggy played guitar
+ \\Jamming good with Andrew Kelley
+ \\And the Spiders from Mars
+ , // pay attention to the comma
+ .hint = "Please fix the lyrics!",
+ },
+ .{
+ .main_file = "008_quiz.zig",
+ .output = "Program in Zig!",
+ .hint = "See if you can fix the program!",
+ },
+ .{
+ .main_file = "009_if.zig",
+ .output = "Foo is 42!",
+ },
+ .{
+ .main_file = "010_if2.zig",
+ .output = "With the discount, the price is $17.",
+ },
+ .{
+ .main_file = "011_while.zig",
+ .output = "2 4 8 16 32 64 128 256 512 n=1024",
+ .hint = "You probably want a 'less than' condition.",
+ },
+ .{
+ .main_file = "012_while2.zig",
+ .output = "2 4 8 16 32 64 128 256 512 n=1024",
+ .hint = "It might help to look back at the previous exercise.",
+ },
+ .{
+ .main_file = "013_while3.zig",
+ .output = "1 2 4 7 8 11 13 14 16 17 19",
+ },
+ .{
+ .main_file = "014_while4.zig",
+ .output = "n=4",
+ },
+ .{
+ .main_file = "015_for.zig",
+ .output = "A Dramatic Story: :-) :-) :-( :-| :-) The End.",
+ },
+ .{
+ .main_file = "016_for2.zig",
+ .output = "The value of bits '1101': 13.",
+ },
+ .{
+ .main_file = "017_quiz2.zig",
+ .output = "1, 2, Fizz, 4, Buzz, Fizz, 7, 8, Fizz, Buzz, 11, Fizz, 13, 14, FizzBuzz, 16,",
+ .hint = "This is a famous game!",
+ },
+ .{
+ .main_file = "018_functions.zig",
+ .output = "Answer to the Ultimate Question: 42",
+ .hint = "Can you help write the function?",
+ },
+ .{
+ .main_file = "019_functions2.zig",
+ .output = "Powers of two: 2 4 8 16",
+ },
+ .{
+ .main_file = "020_quiz3.zig",
+ .output = "32 64 128 256",
+ .hint = "Unexpected pop quiz! Help!",
+ },
+ .{
+ .main_file = "021_errors.zig",
+ .output = "2<4. 3<4. 4=4. 5>4. 6>4.",
+ .hint = "What's the deal with fours?",
+ },
+ .{
+ .main_file = "022_errors2.zig",
+ .output = "I compiled!",
+ .hint = "Get the error union type right to allow this to compile.",
+ },
+ .{
+ .main_file = "023_errors3.zig",
+ .output = "a=64, b=22",
+ },
+ .{
+ .main_file = "024_errors4.zig",
+ .output = "a=20, b=14, c=10",
+ },
+ .{
+ .main_file = "025_errors5.zig",
+ .output = "a=0, b=19, c=0",
+ },
+ .{
+ .main_file = "026_hello2.zig",
+ .output = "Hello world!",
+ .hint = "Try using a try!",
+ .check_stdout = true,
+ },
+ .{
+ .main_file = "027_defer.zig",
+ .output = "One Two",
+ },
+ .{
+ .main_file = "028_defer2.zig",
+ .output =
+ \\(Goat) (Cat) (Dog) (Dog) (Goat) (Unknown) done.
+ \\Answer to everything? 42
+ , // pay attention to the comma
+ },
+ .{
+ .main_file = "029_errdefer.zig",
+ .output = "Getting number...got 5. Getting number...failed!",
+ },
+ .{
+ .main_file = "030_switch.zig",
+ .output = "ZIG?",
+ },
+ .{
+ .main_file = "031_switch2.zig",
+ .output = "ZIG!",
+ },
+ .{
+ .main_file = "032_unreachable.zig",
+ .output = "1 2 3 9 8 7",
+ },
+ .{
+ .main_file = "033_iferror.zig",
+ .output = "2<4. 3<4. 4=4. 5>4. 6>4.",
+ .hint = "Seriously, what's the deal with fours?",
+ },
+ .{
+ .main_file = "034_quiz4.zig",
+ .output = "my_num=42",
+ .hint = "Can you make this work?",
+ .check_stdout = true,
+ },
+ .{
+ .main_file = "035_enums.zig",
+ .output = "1 2 3 9 8 7",
+ .hint = "This problem seems familiar...",
+ },
+ .{
+ .main_file = "036_enums2.zig",
+ .output =
+ \\<p>
+ \\ <span style="color: #ff0000">Red</span>
+ \\ <span style="color: #00ff00">Green</span>
+ \\ <span style="color: #0000ff">Blue</span>
+ \\</p>
+ , // pay attention to the comma
+ .hint = "I'm feeling blue about this.",
+ },
+ .{
+ .main_file = "037_structs.zig",
+ .output = "Your wizard has 90 health and 25 gold.",
+ },
+ .{
+ .main_file = "038_structs2.zig",
+ .output =
+ \\Character 1 - G:20 H:100 XP:10
+ \\Character 2 - G:10 H:100 XP:20
+ , // pay attention to the comma
+ },
+ .{
+ .main_file = "039_pointers.zig",
+ .output = "num1: 5, num2: 5",
+ .hint = "Pointers aren't so bad.",
+ },
+ .{
+ .main_file = "040_pointers2.zig",
+ .output = "a: 12, b: 12",
+ },
+ .{
+ .main_file = "041_pointers3.zig",
+ .output = "foo=6, bar=11",
+ },
+ .{
+ .main_file = "042_pointers4.zig",
+ .output = "num: 5, more_nums: 1 1 5 1",
+ },
+ .{
+ .main_file = "043_pointers5.zig",
+ .output =
+ \\Wizard (G:10 H:100 XP:20)
+ \\ Mentor: Wizard (G:10000 H:100 XP:2340)
+ , // pay attention to the comma
+ },
+ .{
+ .main_file = "044_quiz5.zig",
+ .output = "Elephant A. Elephant B. Elephant C.",
+ .hint = "Oh no! We forgot Elephant B!",
+ },
+ .{
+ .main_file = "045_optionals.zig",
+ .output = "The Ultimate Answer: 42.",
+ },
+ .{
+ .main_file = "046_optionals2.zig",
+ .output = "Elephant A. Elephant B. Elephant C.",
+ .hint = "Elephants again!",
+ },
+ .{
+ .main_file = "047_methods.zig",
+ .output = "5 aliens. 4 aliens. 1 aliens. 0 aliens. Earth is saved!",
+ .hint = "Use the heat ray. And the method!",
+ },
+ .{
+ .main_file = "048_methods2.zig",
+ .output = "A B C",
+ .hint = "This just needs one little fix.",
+ },
+ .{
+ .main_file = "049_quiz6.zig",
+ .output = "A B C Cv Bv Av",
+ .hint = "Now you're writing Zig!",
+ },
+ .{
+ .main_file = "050_no_value.zig",
+ .output = "That is not dead which can eternal lie / And with strange aeons even death may die.",
+ },
+ .{
+ .main_file = "051_values.zig",
+ .output = "1:false!. 2:true!. 3:true!. XP before:0, after:200.",
+ },
+ .{
+ .main_file = "052_slices.zig",
+ .output =
+ \\Hand1: A 4 K 8
+ \\Hand2: 5 2 Q J
+ , // pay attention to the comma
+ },
+ .{
+ .main_file = "053_slices2.zig",
+ .output = "'all your base are belong to us.' 'for great justice.'",
+ },
+ .{
+ .main_file = "054_manypointers.zig",
+ .output = "Memory is a resource.",
+ },
+ .{
+ .main_file = "055_unions.zig",
+ .output = "Insect report! Ant alive is: true. Bee visited 15 flowers.",
+ },
+ .{
+ .main_file = "056_unions2.zig",
+ .output = "Insect report! Ant alive is: true. Bee visited 16 flowers.",
+ },
+ .{
+ .main_file = "057_unions3.zig",
+ .output = "Insect report! Ant alive is: true. Bee visited 17 flowers.",
+ },
+ .{
+ .main_file = "058_quiz7.zig",
+ .output = "Archer's Point--2->Bridge--1->Dogwood Grove--3->Cottage--2->East Pond--1->Fox Pond",
+ .hint = "This is the biggest program we've seen yet. But you can do it!",
+ },
+ .{
+ .main_file = "059_integers.zig",
+ .output = "Zig is cool.",
+ },
+ .{
+ .main_file = "060_floats.zig",
+ .output = "Shuttle liftoff weight: 2.032e3 metric tons",
+ },
+ .{
+ .main_file = "061_coercions.zig",
+ .output = "Letter: A",
+ },
+ .{
+ .main_file = "062_loop_expressions.zig",
+ .output = "Current language: Zig",
+ .hint = "Surely the current language is 'Zig'!",
+ },
+ .{
+ .main_file = "063_labels.zig",
+ .output = "Enjoy your Cheesy Chili!",
+ },
+ .{
+ .main_file = "064_builtins.zig",
+ .output = "1101 + 0101 = 0010 (true). Without overflow: 00010010. Furthermore, 11110000 backwards is 00001111.",
+ },
+ .{
+ .main_file = "065_builtins2.zig",
+ .output = "A Narcissus loves all Narcissuses. He has room in his heart for: me myself.",
+ },
+ .{
+ .main_file = "066_comptime.zig",
+ .output = "Immutable: 12345, 987.654; Mutable: 54321, 456.789; Types: comptime_int, comptime_float, u32, f32",
+ .hint = "It may help to read this one out loud to your favorite stuffed animal until it sinks in completely.",
+ },
+ .{
+ .main_file = "067_comptime2.zig",
+ .output = "A BB CCC DDDD",
+ },
+ .{
+ .main_file = "068_comptime3.zig",
+ .output =
+ \\Minnow (1:32, 4 x 2)
+ \\Shark (1:16, 8 x 5)
+ \\Whale (1:1, 143 x 95)
+ ,
+ },
+ .{
+ .main_file = "069_comptime4.zig",
+ .output = "s1={ 1, 2, 3 }, s2={ 1, 2, 3, 4, 5 }, s3={ 1, 2, 3, 4, 5, 6, 7 }",
+ },
+ .{
+ .main_file = "070_comptime5.zig",
+ .output =
+ \\"Quack." ducky1: true, "Squeek!" ducky2: true, ducky3: false
+ ,
+ .hint = "Have you kept the wizard hat on?",
+ },
+ .{
+ .main_file = "071_comptime6.zig",
+ .output = "Narcissus has room in his heart for: me myself.",
+ },
+ .{
+ .main_file = "072_comptime7.zig",
+ .output = "26",
+ },
+ .{
+ .main_file = "073_comptime8.zig",
+ .output = "My llama value is 25.",
+ },
+ .{
+ .main_file = "074_comptime9.zig",
+ .output = "MouseLlama joins the crew!",
+ },
+ .{
+ .main_file = "075_quiz8.zig",
+ .output = "Archer's Point--2->Bridge--1->Dogwood Grove--3->Cottage--2->East Pond--1->Fox Pond",
+ .hint = "Roll up those sleeves. You get to WRITE some code for this one.",
+ },
+ .{
+ .main_file = "076_sentinels.zig",
+ .output = "Array:123056. Many-item pointer:123.",
+ },
+ .{
+ .main_file = "077_sentinels2.zig",
+ .output = "Weird Data!",
+ },
+ .{
+ .main_file = "078_sentinels3.zig",
+ .output = "Weird Data!",
+ },
+ .{
+ .main_file = "079_quoted_identifiers.zig",
+ .output = "Sweet freedom: 55, false.",
+ .hint = "Help us, Zig Programmer, you're our only hope!",
+ },
+ .{
+ .main_file = "080_anonymous_structs.zig",
+ .output = "[Circle(i32): 25,70,15] [Circle(f32): 25.2,71.0,15.7]",
+ },
+ .{
+ .main_file = "081_anonymous_structs2.zig",
+ .output = "x:205 y:187 radius:12",
+ },
+ .{
+ .main_file = "082_anonymous_structs3.zig",
+ .output =
+ \\"0"(bool):true "1"(bool):false "2"(i32):42 "3"(f32):3.141592
+ , // pay attention to the comma
+ .hint = "This one is a challenge! But you have everything you need.",
+ },
+ .{
+ .main_file = "083_anonymous_lists.zig",
+ .output = "I say hello!",
+ },
+ .{
+ .main_file = "084_interfaces.zig",
+ .output =
+ \\=== Doctor Zoraptera's Insect Report ===
+ \\Ant is alive.
+ \\Bee visited 17 flowers.
+ \\Grasshopper hopped 32 meters.
+ , // pay attention to the comma
+ },
+ .{
+ .main_file = "085_async.zig",
+ .output = "Current time: <timestamp>s since epoch",
+ .timestamp = true,
+ },
+ .{
+ .main_file = "086_async2.zig",
+ .output = "Computing... The answer is: 42",
+ },
+ .{
+ .main_file = "087_async3.zig",
+ .output =
+ \\1 + 2 = 3
+ \\6 * 7 = 42
+ \\Total: 45
+ , // pay attention to the comma
+ },
+ .{
+ .main_file = "088_async4.zig",
+ .output =
+ \\Task 1 done.
+ \\Task 2 done.
+ \\Task 3 done.
+ \\All tasks finished!
+ , // pay attention to the comma
+ },
+ .{
+ .main_file = "089_async5.zig",
+ .output =
+ \\Starting long computation...
+ \\Canceling slow task...
+ \\Task was canceled, cleaning up.
+ \\Task returned: 0
+ , // pay attention to the comma
+ },
+ .{
+ .main_file = "090_async6.zig",
+ .output = "Hare: I'm fast!",
+ },
+ .{
+ .main_file = "091_async7.zig",
+ .output = "Counter: 400",
+ },
+ .{
+ .main_file = "092_async8.zig",
+ .output = "Sum of 1..10 = 55",
+ },
+ .{
+ .main_file = "093_async9.zig",
+ .output = "Worker 1 found signal start over threshold at index 12!",
+ },
+ .{
+ .main_file = "094_async10.zig",
+ .output =
+ \\Starting critical section...
+ \\Critical section completed safely.
+ \\Task result: All data saved.
+ , // pay attention to the comma
+ },
+ .{
+ .main_file = "095_quiz_async.zig",
+ .output =
+ \\=== Doctor Zoraptera's Garden Report ===
+ \\Temperature : 23C
+ \\Humidity : 63%
+ \\Wind : 13 km/h
+ \\Readings : 9
+ \\Bee-friendly conditions! Expect high pollination.
+ , // pay attention to the comma
+ },
+ .{
+ .main_file = "096_hello_c.zig",
+ .output = "Hello C from Zig! - C result is 17 chars written.",
+ .link_libc = true,
+ .skip = true,
+ .skip_hint = "Skipped until we have found a solution for the removed '@cImport'",
+ },
+ .{
+ .main_file = "097_c_math.zig",
+ .output = "The normalized angle of 765.2 degrees is 45.2 degrees.",
+ .link_libc = true,
+ .skip = true,
+ .skip_hint = "Skipped until we have found a solution for the removed '@cImport'",
+ },
+ .{
+ .main_file = "098_for3.zig",
+ .output = "1 2 4 7 8 11 13 14 16 17 19\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15",
+ },
+ .{
+ .main_file = "099_memory_allocation.zig",
+ .output = "Running Average: 0.30 0.25 0.20 0.18 0.22",
+ },
+ .{
+ .main_file = "100_bit_manipulation.zig",
+ .output = "x = 1011; y = 1101",
+ },
+ .{
+ .main_file = "101_bit_manipulation2.zig",
+ .output = "Is this a pangram? true!",
+ },
+ .{
+ .main_file = "102_formatting.zig",
+ .output =
+ \\
+ \\ X | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
+ \\---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
+ \\ 1 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
+ \\
+ \\ 2 | 2 4 6 8 10 12 14 16 18 20 22 24 26 28 30
+ \\
+ \\ 3 | 3 6 9 12 15 18 21 24 27 30 33 36 39 42 45
+ \\
+ \\ 4 | 4 8 12 16 20 24 28 32 36 40 44 48 52 56 60
+ \\
+ \\ 5 | 5 10 15 20 25 30 35 40 45 50 55 60 65 70 75
+ \\
+ \\ 6 | 6 12 18 24 30 36 42 48 54 60 66 72 78 84 90
+ \\
+ \\ 7 | 7 14 21 28 35 42 49 56 63 70 77 84 91 98 105
+ \\
+ \\ 8 | 8 16 24 32 40 48 56 64 72 80 88 96 104 112 120
+ \\
+ \\ 9 | 9 18 27 36 45 54 63 72 81 90 99 108 117 126 135
+ \\
+ \\10 | 10 20 30 40 50 60 70 80 90 100 110 120 130 140 150
+ \\
+ \\11 | 11 22 33 44 55 66 77 88 99 110 121 132 143 154 165
+ \\
+ \\12 | 12 24 36 48 60 72 84 96 108 120 132 144 156 168 180
+ \\
+ \\13 | 13 26 39 52 65 78 91 104 117 130 143 156 169 182 195
+ \\
+ \\14 | 14 28 42 56 70 84 98 112 126 140 154 168 182 196 210
+ \\
+ \\15 | 15 30 45 60 75 90 105 120 135 150 165 180 195 210 225
+ ,
+ },
+ .{
+ .main_file = "103_for4.zig",
+ .output = "Arrays match!",
+ },
+ .{
+ .main_file = "104_for5.zig",
+ .output =
+ \\1. Wizard (Gold: 25, XP: 40)
+ \\2. Bard (Gold: 11, XP: 17)
+ \\3. Bard (Gold: 5, XP: 55)
+ \\4. Warrior (Gold: 7392, XP: 21)
+ , // pay attention to the comma
+ },
+ .{
+ .main_file = "105_testing.zig",
+ .output = "",
+ .kind = .@"test",
+ },
+ .{
+ .main_file = "106_tokenization.zig",
+ .output =
+ \\My
+ \\name
+ \\is
+ \\Ozymandias
+ \\King
+ \\of
+ \\Kings
+ \\Look
+ \\on
+ \\my
+ \\Works
+ \\ye
+ \\Mighty
+ \\and
+ \\despair
+ \\This little poem has 15 words!
+ , // pay attention to the comma
+ },
+ .{
+ .main_file = "107_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!
+ , // pay attention to the comma
+ },
+ .{
+ .main_file = "108_threading2.zig",
+ .output = "PI ≈ 3.14159265",
+ },
+ .{
+ .main_file = "109_files.zig",
+ .output = "Successfully wrote 18 bytes.",
+ },
+ .{
+ .main_file = "110_files2.zig",
+ .output =
+ \\AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ \\Successfully Read 18 bytes: It's zigling time!
+ , // pay attention to the comma
+ },
+ .{
+ .main_file = "111_labeled_switch.zig",
+ .output = "The pull request has been merged.",
+ },
+ .{
+ .main_file = "112_vectors.zig",
+ .output =
+ \\Max difference (old fn): 0.014
+ \\Max difference (new fn): 0.014
+ , // pay attention to the comma
+ },
+ .{ .main_file = "113_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 = "114_packed.zig",
+ .output = "",
+ },
+ .{
+ .main_file = "115_packed2.zig",
+ .output = "",
+ },
+ .{
+ .main_file = "999_the_end.zig",
+ .output =
+ \\
+ \\This is the end for now!
+ \\We hope you had fun and were able to learn a lot, so visit us again when the next exercises are available.
+ , // pay attention to the comma
+ },
+};