diff options
29 files changed, 403 insertions, 144 deletions
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..ebeda5b --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,25 @@ +name: CI +on: + pull_request: + branches: [ main ] + +defaults: + run: + shell: bash + +jobs: + compat: + runs-on: ubuntu-latest + strategy: + matrix: + zig: [ 0.6.0, 0.7.0, 0.8.0, 0.9.0, 0.10.0 ] + steps: + - uses: actions/checkout@v2 + + - name: Setup Zig + uses: goto-bus-stop/setup-zig@v2 + with: + version: ${{ matrix.zig }} + + - name: Check compatibility with old Zig compilers + run: ci/compat.sh @@ -1,6 +1,7 @@ const std = @import("std"); const builtin = @import("builtin"); const compat = @import("src/compat.zig"); +const ipc = @import("src/ipc.zig"); const tests = @import("test/tests.zig"); const Build = compat.Build; @@ -693,6 +694,9 @@ const ZiglingStep = struct { builder: *Build, use_healed: bool, + result_messages: []const u8 = "", + result_error_bundle: std.zig.ErrorBundle = std.zig.ErrorBundle.empty, + pub fn create(builder: *Build, exercise: Exercise, use_healed: bool) *@This() { const self = builder.allocator.create(@This()) catch unreachable; self.* = .{ @@ -728,6 +732,7 @@ const ZiglingStep = struct { const exe_file = try self.doCompile(prog_node); + resetLine(); print("Checking {s}...\n", .{self.exercise.main_file}); const cwd = self.builder.build_root.path.?; @@ -832,6 +837,8 @@ const ZiglingStep = struct { const argv = zig_args.items; var code: u8 = undefined; const file_name = self.eval(argv, &code, prog_node) catch |err| { + self.printErrors(); + switch (err) { error.FileNotFound => { print("{s}{s}: Unable to spawn the following command: file not found{s}\n", .{ red_text, self.exercise.main_file, reset_text }); @@ -848,11 +855,21 @@ const ZiglingStep = struct { for (argv) |v| print("{s} ", .{v}); print("\n", .{}); }, + error.ZigIPCError => { + print("{s}{s}: The following command failed to communicate the compilation result:{s}\n", .{ + red_text, + self.exercise.main_file, + reset_text, + }); + for (argv) |v| print("{s} ", .{v}); + print("\n", .{}); + }, else => {}, } return err; }; + self.printErrors(); return file_name; } @@ -882,8 +899,8 @@ const ZiglingStep = struct { }); defer poller.deinit(); - try sendMessage(child.stdin.?, .update); - try sendMessage(child.stdin.?, .exit); + try ipc.sendMessage(child.stdin.?, .update); + try ipc.sendMessage(child.stdin.?, .exit); const Header = std.zig.Server.Message.Header; var result: ?[]const u8 = null; @@ -911,31 +928,7 @@ const ZiglingStep = struct { return error.ZigVersionMismatch; }, .error_bundle => { - const EbHdr = std.zig.Server.Message.ErrorBundle; - const eb_hdr = @ptrCast(*align(1) const EbHdr, body); - const extra_bytes = - body[@sizeOf(EbHdr)..][0 .. @sizeOf(u32) * eb_hdr.extra_len]; - const string_bytes = - body[@sizeOf(EbHdr) + extra_bytes.len ..][0..eb_hdr.string_bytes_len]; - // TODO: use @ptrCast when the compiler supports it - const unaligned_extra = std.mem.bytesAsSlice(u32, extra_bytes); - const extra_array = try allocator.alloc(u32, unaligned_extra.len); - // TODO: use @memcpy when it supports slices - for (extra_array, unaligned_extra) |*dst, src| dst.* = src; - const error_bundle: std.zig.ErrorBundle = .{ - .string_bytes = try allocator.dupe(u8, string_bytes), - .extra = extra_array, - }; - - // Print the compiler error bundle now. - // TODO: use the same ttyconf from the builder. - const ttyconf: std.debug.TTY.Config = if (use_color_escapes) - .escape_codes - else - .no_color; - error_bundle.renderToStdErr( - .{ .ttyconf = ttyconf }, - ); + self.result_error_bundle = try ipc.parseErrorBundle(allocator, body); }, .progress => { node_name.clearRetainingCapacity(); @@ -943,13 +936,8 @@ const ZiglingStep = struct { sub_prog_node.setName(node_name.items); }, .emit_bin_path => { - const EbpHdr = std.zig.Server.Message.EmitBinPath; - - // TODO: add cache support? - //const ebp_hdr = @ptrCast(*align(1) const EbpHdr, body); - //s.result_cached = ebp_hdr.flags.cache_hit; - - result = try allocator.dupe(u8, body[@sizeOf(EbpHdr)..]); + const emit_bin = try ipc.parseEmitBinPath(allocator, body); + result = emit_bin.path; }, else => {}, // ignore other messages } @@ -959,9 +947,7 @@ const ZiglingStep = struct { const stderr = poller.fifo(.stderr); if (stderr.readableLength() > 0) { - // Print the additional log and verbose messages now. - const messages = try stderr.toOwnedSlice(); - print("{s}\n", .{messages}); + self.result_messages = try stderr.toOwnedSlice(); } // Send EOF to stdin. @@ -987,14 +973,30 @@ const ZiglingStep = struct { return result orelse return error.ZigIPCError; } + + fn printErrors(self: *ZiglingStep) void { + resetLine(); + + // Print the additional log and verbose messages. + // TODO: use colors? + if (self.result_messages.len > 0) print("{s}", .{self.result_messages}); + + // Print the compiler errors. + // TODO: use the same ttyconf from the builder. + const ttyconf: std.debug.TTY.Config = if (use_color_escapes) + .escape_codes + else + .no_color; + if (self.result_error_bundle.errorMessageCount() > 0) { + self.result_error_bundle.renderToStdErr(.{ .ttyconf = ttyconf }); + } + } }; -fn sendMessage(file: std.fs.File, tag: std.zig.Client.Message.Tag) !void { - const header: std.zig.Client.Message.Header = .{ - .tag = tag, - .bytes_len = 0, - }; - try file.writeAll(std.mem.asBytes(&header)); +// Clear the entire line and move the cursor to column zero. +// Used for clearing the compiler and build_runner progress messages. +fn resetLine() void { + if (use_color_escapes) print("{s}", .{"\x1b[2K\r"}); } // Print a message to stderr. diff --git a/ci/compat.sh b/ci/compat.sh new file mode 100755 index 0000000..f5895fd --- /dev/null +++ b/ci/compat.sh @@ -0,0 +1,25 @@ +#!/bin/bash +# This script checks that `zig build` will return an useful error message when +# the Zig compiler is not compatible, instead of failing due to a syntax error. +# +# This script should be run on an UNIX system. + +zig_version=$(zig version) + +zig build -Dn=1 -Dhealed &> /dev/null 2>&1 +zig_ret=$? + +if [ "$zig_ret" -eq 0 ]; then + printf "zig %s unexpectedly succeeded\n" "$zig_version" + exit 1 +fi + +zig_error=$(zig build -Dn=1 -Dhealed 2>&1) + +echo "$zig_error" | grep -q "it looks like your version of zig is too old" +zig_ret=$? + +if [ "$zig_ret" -ne 0 ]; then + printf "zig %s is not compatible\n" "$zig_version" + exit 1 +fi diff --git a/exercises/058_quiz7.zig b/exercises/058_quiz7.zig index 3069710..1ceac5a 100644 --- a/exercises/058_quiz7.zig +++ b/exercises/058_quiz7.zig @@ -107,7 +107,7 @@ const Path = struct { const a_paths = [_]Path{ Path{ .from = &a, // from: Archer's Point - .to = &b, // to: Bridge + .to = &b, // to: Bridge .dist = 2, }, }; @@ -115,12 +115,12 @@ const a_paths = [_]Path{ const b_paths = [_]Path{ Path{ .from = &b, // from: Bridge - .to = &a, // to: Archer's Point + .to = &a, // to: Archer's Point .dist = 2, }, Path{ .from = &b, // from: Bridge - .to = &d, // to: Dogwood Grove + .to = &d, // to: Dogwood Grove .dist = 1, }, }; @@ -128,12 +128,12 @@ const b_paths = [_]Path{ const c_paths = [_]Path{ Path{ .from = &c, // from: Cottage - .to = &d, // to: Dogwood Grove + .to = &d, // to: Dogwood Grove .dist = 3, }, Path{ .from = &c, // from: Cottage - .to = &e, // to: East Pond + .to = &e, // to: East Pond .dist = 2, }, }; @@ -141,17 +141,17 @@ const c_paths = [_]Path{ const d_paths = [_]Path{ Path{ .from = &d, // from: Dogwood Grove - .to = &b, // to: Bridge + .to = &b, // to: Bridge .dist = 1, }, Path{ .from = &d, // from: Dogwood Grove - .to = &c, // to: Cottage + .to = &c, // to: Cottage .dist = 3, }, Path{ .from = &d, // from: Dogwood Grove - .to = &f, // to: Fox Pond + .to = &f, // to: Fox Pond .dist = 7, }, }; @@ -159,20 +159,20 @@ const d_paths = [_]Path{ const e_paths = [_]Path{ Path{ .from = &e, // from: East Pond - .to = &c, // to: Cottage + .to = &c, // to: Cottage .dist = 2, }, Path{ .from = &e, // from: East Pond - .to = &f, // to: Fox Pond - .dist = 1, // (one-way down a short waterfall!) + .to = &f, // to: Fox Pond + .dist = 1, // (one-way down a short waterfall!) }, }; const f_paths = [_]Path{ Path{ .from = &f, // from: Fox Pond - .to = &d, // to: Dogwood Grove + .to = &d, // to: Dogwood Grove .dist = 7, }, }; @@ -355,8 +355,8 @@ pub fn main() void { // Here's where the hermit decides where he would like to go. Once // you get the program working, try some different Places on the // map! - const start = &a; // Archer's Point - const destination = &f; // Fox Pond + const start = &a; // Archer's Point + const destination = &f; // Fox Pond // Store each Path array as a slice in each Place. As mentioned // above, we needed to delay making these references to avoid diff --git a/exercises/059_integers.zig b/exercises/059_integers.zig index 5a6295d..a497efa 100644 --- a/exercises/059_integers.zig +++ b/exercises/059_integers.zig @@ -18,10 +18,10 @@ const print = @import("std").debug.print; pub fn main() void { - var zig = [_]u8 { - 0o131, // octal + var zig = [_]u8{ + 0o131, // octal 0b1101000, // binary - 0x66, // hex + 0x66, // hex }; print("{s} is cool.\n", .{zig}); diff --git a/exercises/069_comptime4.zig b/exercises/069_comptime4.zig index 004a42c..f4c8bbb 100644 --- a/exercises/069_comptime4.zig +++ b/exercises/069_comptime4.zig @@ -16,7 +16,7 @@ const print = @import("std").debug.print; pub fn main() void { // Here we declare arrays of three different types and sizes // at compile time from a function call. Neat! - const s1 = makeSequence(u8, 3); // creates a [3]u8 + const s1 = makeSequence(u8, 3); // creates a [3]u8 const s2 = makeSequence(u32, 5); // creates a [5]u32 const s3 = makeSequence(i64, 7); // creates a [7]i64 diff --git a/exercises/075_quiz8.zig b/exercises/075_quiz8.zig index c2dbc1a..d54864c 100644 --- a/exercises/075_quiz8.zig +++ b/exercises/075_quiz8.zig @@ -151,8 +151,8 @@ const HermitsNotebook = struct { }; pub fn main() void { - const start = &a; // Archer's Point - const destination = &f; // Fox Pond + const start = &a; // Archer's Point + const destination = &f; // Fox Pond // We could either have this: // diff --git a/exercises/083_anonymous_lists.zig b/exercises/083_anonymous_lists.zig index 838d40e..daaeaff 100644 --- a/exercises/083_anonymous_lists.zig +++ b/exercises/083_anonymous_lists.zig @@ -18,8 +18,8 @@ pub fn main() void { // // Don't change this part: // - // = .{'h', 'e', 'l', 'l', 'o'}; + // = .{ 'h', 'e', 'l', 'l', 'o' }; // - const hello = .{'h', 'e', 'l', 'l', 'o'}; + const hello = .{ 'h', 'e', 'l', 'l', 'o' }; print("I say {s}!\n", .{hello}); } diff --git a/exercises/092_interfaces.zig b/exercises/092_interfaces.zig index 43f1119..5ac5768 100644 --- a/exercises/092_interfaces.zig +++ b/exercises/092_interfaces.zig @@ -99,7 +99,7 @@ pub fn main() !void { var my_insects = [_]Insect{ Insect{ .ant = Ant{ .still_alive = true } }, Insect{ .bee = Bee{ .flowers_visited = 17 } }, - Insect{ .grasshopper = Grasshopper{ .distance_hopped = 32 }, }, + Insect{ .grasshopper = Grasshopper{ .distance_hopped = 32 } }, }; std.debug.print("Daily Insect Report:\n", .{}); diff --git a/patches/eowyn.sh b/patches/eowyn.sh index a30a67a..a036acf 100755 --- a/patches/eowyn.sh +++ b/patches/eowyn.sh @@ -42,5 +42,8 @@ do fi done +# Check the healed exercises formatting. +zig fmt --check patches/healed + # Test the healed exercises. May the compiler have mercy upon us. zig build -Dhealed diff --git a/patches/patches/024_errors4.patch b/patches/patches/024_errors4.patch index 5996a99..48e0821 100644 --- a/patches/patches/024_errors4.patch +++ b/patches/patches/024_errors4.patch @@ -5,6 +5,6 @@ > if (err == MyNumberError.TooSmall) { > return 10; > } -> +> > return err; > }; diff --git a/patches/patches/026_hello2.patch b/patches/patches/026_hello2.patch index 0065da5..e97a7b5 100644 --- a/patches/patches/026_hello2.patch +++ b/patches/patches/026_hello2.patch @@ -1,4 +1,4 @@ -22c22 +26c26 < stdout.print("Hello world!\n", .{}); --- > try stdout.print("Hello world!\n", .{}); diff --git a/patches/patches/029_errdefer.patch b/patches/patches/029_errdefer.patch index f93c56f..aa42279 100644 --- a/patches/patches/029_errdefer.patch +++ b/patches/patches/029_errdefer.patch @@ -1,4 +1,4 @@ -35c34 +35c35 < std.debug.print("failed!\n", .{}); --- > errdefer std.debug.print("failed!\n", .{}); diff --git a/patches/patches/040_pointers2.patch b/patches/patches/040_pointers2.patch index a69cb20..87291c3 100644 --- a/patches/patches/040_pointers2.patch +++ b/patches/patches/040_pointers2.patch @@ -1,4 +1,4 @@ -24c24 +26c26 < const b: *u8 = &a; // fix this! --- > const b: *const u8 = &a; // fix this! diff --git a/patches/patches/046_optionals2.patch b/patches/patches/046_optionals2.patch index 8437cdc..89fa604 100644 --- a/patches/patches/046_optionals2.patch +++ b/patches/patches/046_optionals2.patch @@ -1,8 +1,8 @@ -24c12 +24c24 < tail: *Elephant = null, // Hmm... tail needs something... --- > tail: ?*Elephant = null, // <---- make this optional! -54c42 +54c54 < if (e.tail == null) ???; --- > if (e.tail == null) break; diff --git a/patches/patches/050_no_value.patch b/patches/patches/050_no_value.patch index 79db0a3..7ea4e9e 100644 --- a/patches/patches/050_no_value.patch +++ b/patches/patches/050_no_value.patch @@ -6,7 +6,7 @@ < var first_line2: Err!*const [21]u8 = ???; --- > var first_line2: Err!*const [21]u8 = Err.Cthulhu; -79,80c79,80 +80,81c80,81 < fn printSecondLine() ??? { < var second_line2: ?*const [18]u8 = ???; --- diff --git a/patches/patches/059_integers.patch b/patches/patches/059_integers.patch index 50a89a0..7075ebe 100644 --- a/patches/patches/059_integers.patch +++ b/patches/patches/059_integers.patch @@ -1,8 +1,8 @@ 22,24c22,24 -< 0o131, // octal +< 0o131, // octal < 0b1101000, // binary -< 0x66, // hex +< 0x66, // hex --- -> 0o132, // octal +> 0o132, // octal > 0b1101001, // binary -> 0x67, // hex +> 0x67, // hex diff --git a/patches/patches/064_builtins.patch b/patches/patches/064_builtins.patch index ebe313d..8a154a4 100644 --- a/patches/patches/064_builtins.patch +++ b/patches/patches/064_builtins.patch @@ -1,8 +1,8 @@ -72c72 +67c67 < const expected_result: u8 = ???; --- > const expected_result: u8 = 0b00010010; -88c88 +82c82 < const tupni: u8 = @bitReverse(input, tupni); --- > const tupni: u8 = @bitReverse(input); diff --git a/patches/patches/065_builtins2.patch b/patches/patches/065_builtins2.patch index 89fb55f..1b085ef 100644 --- a/patches/patches/065_builtins2.patch +++ b/patches/patches/065_builtins2.patch @@ -1,39 +1,20 @@ ---- exercises/065_builtins2.zig -+++ answers/065_builtins2.zig -@@ -58,7 +58,7 @@ - // Oops! We cannot leave the 'me' and 'myself' fields - // undefined. Please set them here: - narcissus.me = &narcissus; -- narcissus.??? = ???; -+ narcissus.myself = &narcissus; - - // This determines a "peer type" from three separate - // references (they just happen to all be the same object). -@@ -70,7 +70,7 @@ - // - // The fix for this is very subtle, but it makes a big - // difference! -- const Type2 = narcissus.fetchTheMostBeautifulType(); -+ const Type2 = Narcissus.fetchTheMostBeautifulType(); - - // Now we print a pithy statement about Narcissus. - print("A {s} loves all {s}es. ", .{ -@@ -109,15 +109,15 @@ - // Please complete these 'if' statements so that the field - // name will not be printed if the field is of type 'void' - // (which is a zero-bit type that takes up no space at all!): -- if (fields[0].??? != void) { -+ if (fields[0].type != void) { - print(" {s}", .{@typeInfo(Narcissus).Struct.fields[0].name}); - } - -- if (fields[1].??? != void) { -+ if (fields[1].type != void) { - print(" {s}", .{@typeInfo(Narcissus).Struct.fields[1].name}); - } - -- if (fields[2].??? != void) { -+ if (fields[2].type != void) { - print(" {s}", .{@typeInfo(Narcissus).Struct.fields[2].name}); - } - +61c61 +< narcissus.??? = ???; +--- +> narcissus.myself = &narcissus; +73c73 +< const Type2 = narcissus.fetchTheMostBeautifulType(); +--- +> const Type2 = Narcissus.fetchTheMostBeautifulType(); +112c112 +< if (fields[0].??? != void) { +--- +> if (fields[0].type != void) { +116c116 +< if (fields[1].??? != void) { +--- +> if (fields[1].type != void) { +120c120 +< if (fields[2].??? != void) { +--- +> if (fields[2].type != void) { diff --git a/patches/patches/066_comptime.patch b/patches/patches/066_comptime.patch index 4828ad6..e7130c2 100644 --- a/patches/patches/066_comptime.patch +++ b/patches/patches/066_comptime.patch @@ -1,4 +1,4 @@ -64,65c64,65 +65,66c65,66 < var var_int = 12345; < var var_float = 987.654; --- diff --git a/patches/patches/076_sentinels.patch b/patches/patches/076_sentinels.patch index 3cf4877..55c4c96 100644 --- a/patches/patches/076_sentinels.patch +++ b/patches/patches/076_sentinels.patch @@ -1,8 +1,8 @@ -85c84 +85c85 < for (???) |s| { --- > for (my_seq) |s| { -97c96 +97c97 < while (??? != my_sentinel) { --- > while (my_seq[i] != my_sentinel) { diff --git a/patches/patches/080_anonymous_structs.patch b/patches/patches/080_anonymous_structs.patch index 6df1890..a94e7b8 100644 --- a/patches/patches/080_anonymous_structs.patch +++ b/patches/patches/080_anonymous_structs.patch @@ -1,18 +1,8 @@ ---- exercises/080_anonymous_structs.zig -+++ answers/080_anonymous_structs.zig -@@ -48,13 +48,13 @@ - // * circle1 should hold i32 integers - // * circle2 should hold f32 floats - // -- var circle1 = ??? { -+ var circle1 = Circle(i32){ - .center_x = 25, - .center_y = 70, - .radius = 15, - }; - -- var circle2 = ??? { -+ var circle2 = Circle(f32){ - .center_x = 25.234, - .center_y = 70.999, - .radius = 15.714, +51c51 +< var circle1 = ??? { +--- +> var circle1 = Circle(i32){ +57c57 +< var circle2 = ??? { +--- +> var circle2 = Circle(f32){ diff --git a/patches/patches/083_anonymous_lists.patch b/patches/patches/083_anonymous_lists.patch index b981909..94b594b 100644 --- a/patches/patches/083_anonymous_lists.patch +++ b/patches/patches/083_anonymous_lists.patch @@ -1,4 +1,4 @@ 23c23 -< const hello = .{'h', 'e', 'l', 'l', 'o'}; +< const hello = .{ 'h', 'e', 'l', 'l', 'o' }; --- -> const hello: [5]u8 = .{'h', 'e', 'l', 'l', 'o'}; +> const hello: [5]u8 = .{ 'h', 'e', 'l', 'l', 'o' }; diff --git a/patches/patches/096_memory_allocation.patch b/patches/patches/096_memory_allocation.patch index 5398ce5..fd990b0 100644 --- a/patches/patches/096_memory_allocation.patch +++ b/patches/patches/096_memory_allocation.patch @@ -1,4 +1,4 @@ -65c65 +66c66 < var avg: []f64 = ???; --- > var avg: []f64 = try allocator.alloc(f64, arr.len); diff --git a/patches/patches/097_bit_manipulation.patch b/patches/patches/097_bit_manipulation.patch index e216309..2afe3da 100644 --- a/patches/patches/097_bit_manipulation.patch +++ b/patches/patches/097_bit_manipulation.patch @@ -1,4 +1,4 @@ -82c82 +83c83 < ???; --- > x ^= y; diff --git a/patches/patches/098_bit_manipulation2.patch b/patches/patches/098_bit_manipulation2.patch index 1b5df35..8becd34 100644 --- a/patches/patches/098_bit_manipulation2.patch +++ b/patches/patches/098_bit_manipulation2.patch @@ -1,4 +1,4 @@ -62c62 +63c63 < return bits == 0x..???; --- > return bits == 0x3ffffff; diff --git a/src/ipc.zig b/src/ipc.zig new file mode 100644 index 0000000..9eaaa13 --- /dev/null +++ b/src/ipc.zig @@ -0,0 +1,68 @@ +/// Client side support for Zig IPC. +const std = @import("std"); +const debug = std.debug; +const fs = std.fs; +const mem = std.mem; + +const Allocator = mem.Allocator; +const Client = std.zig.Client; +const ErrorBundle = std.zig.ErrorBundle; +const Server = std.zig.Server; + +/// This data structure must be kept in sync with zig.Server.Message.EmitBinPath. +const EmitBinPath = struct { + flags: Flags, + path: []const u8, + + pub const Flags = Server.Message.EmitBinPath.Flags; + + pub fn deinit(self: *EmitBinPath, allocator: Allocator) void { + allocator.free(self.path); + self.* = undefined; + } +}; + +pub fn parseErrorBundle(allocator: Allocator, data: []const u8) !ErrorBundle { + const EbHdr = Server.Message.ErrorBundle; + const eb_hdr = @ptrCast(*align(1) const EbHdr, data); + const extra_bytes = + data[@sizeOf(EbHdr)..][0 .. @sizeOf(u32) * eb_hdr.extra_len]; + const string_bytes = + data[@sizeOf(EbHdr) + extra_bytes.len ..][0..eb_hdr.string_bytes_len]; + + // TODO: use @ptrCast when the compiler supports it + const unaligned_extra = std.mem.bytesAsSlice(u32, extra_bytes); + const extra_array = try allocator.alloc(u32, unaligned_extra.len); + // TODO: use @memcpy when it supports slices + // + // Don't use the "multi-object for loop" syntax, in + // order to avoid a syntax error with old Zig compilers. + var i: usize = 0; + while (i < extra_array.len) : (i += 1) { + extra_array[i] = unaligned_extra[i]; + } + + return .{ + .string_bytes = try allocator.dupe(u8, string_bytes), + .extra = extra_array, + }; +} + +pub fn parseEmitBinPath(allocator: Allocator, data: []const u8) !EmitBinPath { + const EbpHdr = Server.Message.EmitBinPath; + const ebp_hdr = @ptrCast(*align(1) const EbpHdr, data); + const path = try allocator.dupe(u8, data[@sizeOf(EbpHdr)..]); + + return .{ + .flags = ebp_hdr.flags, + .path = path, + }; +} + +pub fn sendMessage(file: fs.File, tag: Client.Message.Tag) !void { + const header: Client.Message.Header = .{ + .tag = tag, + .bytes_len = 0, + }; + try file.writeAll(mem.asBytes(&header)); +} diff --git a/tools/check-exercises.py b/tools/check-exercises.py new file mode 100755 index 0000000..fa0b5cb --- /dev/null +++ b/tools/check-exercises.py @@ -0,0 +1,97 @@ +#!/usr/bin/env python + +import difflib +import io +import os +import os.path +import subprocess +import sys + + +IGNORE = subprocess.DEVNULL +PIPE = subprocess.PIPE + +EXERCISES_PATH = "exercises" +HEALED_PATH = "patches/healed" +PATCHES_PATH = "patches/patches" + + +# Heals all the exercises. +def heal(): + maketree(HEALED_PATH) + + with os.scandir(EXERCISES_PATH) as it: + for entry in it: + name = entry.name + + original_path = entry.path + patch_path = os.path.join(PATCHES_PATH, patch_name(name)) + output_path = os.path.join(HEALED_PATH, name) + + patch(original_path, patch_path, output_path) + + +# Yields all the healed exercises that are not correctly formatted. +def check_healed(): + term = subprocess.run( + ["zig", "fmt", "--check", HEALED_PATH], stdout=PIPE, text=True + ) + if term.stdout == "" and term.returncode != 0: + term.check_returncode() + + stream = io.StringIO(term.stdout) + for line in stream: + yield line.strip() + + +def main(): + heal() + + # Show the unified diff between the original example and the correctly + # formatted one. + for i, original in enumerate(check_healed()): + if i > 0: + print() + + name = os.path.basename(original) + print(f"checking exercise {name}...\n") + + from_file = open(original) + to_file = zig_fmt_file(original) + + diff = difflib.unified_diff( + from_file.readlines(), to_file.readlines(), name, name + "-fmt" + ) + sys.stderr.writelines(diff) + + +def maketree(path): + return os.makedirs(path, exist_ok=True) + + +# Returns path with the patch extension. +def patch_name(path): + name, _ = os.path.splitext(path) + + return name + ".patch" + + +# Applies patch to original, and write the file to output. +def patch(original, patch, output): + subprocess.run( + ["patch", "-i", patch, "-o", output, original], stdout=IGNORE, check=True + ) + + +# Formats the Zig file at path, and returns the possibly reformatted file as a +# file object. +def zig_fmt_file(path): + with open(path) as stdin: + term = subprocess.run( + ["zig", "fmt", "--stdin"], stdin=stdin, stdout=PIPE, check=True, text=True + ) + + return io.StringIO(term.stdout) + + +main() diff --git a/tools/update-patches.py b/tools/update-patches.py new file mode 100755 index 0000000..76a1c46 --- /dev/null +++ b/tools/update-patches.py @@ -0,0 +1,68 @@ +#!/usr/bin/env python + +import os +import os.path +import subprocess + + +IGNORE = subprocess.DEVNULL + +EXERCISES_PATH = "exercises" +ANSWERS_PATH = "answers" +PATCHES_PATH = "patches/patches" + + +# Heals all the exercises. +def heal(): + maketree(ANSWERS_PATH) + + with os.scandir(EXERCISES_PATH) as it: + for entry in it: + name = entry.name + + original_path = entry.path + patch_path = os.path.join(PATCHES_PATH, patch_name(name)) + output_path = os.path.join(ANSWERS_PATH, name) + + patch(original_path, patch_path, output_path) + + +def main(): + heal() + + with os.scandir(EXERCISES_PATH) as it: + for entry in it: + name = entry.name + + broken_path = entry.path + healed_path = os.path.join(ANSWERS_PATH, name) + patch_path = os.path.join(PATCHES_PATH, patch_name(name)) + + with open(patch_path, "w") as file: + term = subprocess.run( + ["diff", broken_path, healed_path], + stdout=file, + text=True, + ) + assert term.returncode == 1 + + +def maketree(path): + return os.makedirs(path, exist_ok=True) + + +# Returns path with the patch extension. +def patch_name(path): + name, _ = os.path.splitext(path) + + return name + ".patch" + + +# Applies patch to original, and write the file to output. +def patch(original, patch, output): + subprocess.run( + ["patch", "-i", patch, "-o", output, original], stdout=IGNORE, check=True + ) + + +main() |