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 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/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()