From c70fa5f58f5ca2dc010f00caee19027069a09131 Mon Sep 17 00:00:00 2001 From: Dave Gauer Date: Sun, 31 Jan 2021 17:48:34 -0500 Subject: [PATCH] Adding exs 27-32 --- 27_defer.zig | 25 +++++++++++++++++++++ 28_defer2.zig | 29 ++++++++++++++++++++++++ 29_errdefer.zig | 60 +++++++++++++++++++++++++++++++++++++++++++++++++ 30_switch.zig | 55 +++++++++++++++++++++++++++++++++++++++++++++ 31_switch2.zig | 42 ++++++++++++++++++++++++++++++++++ 32_iferror.zig | 49 ++++++++++++++++++++++++++++++++++++++++ README.md | 7 +++--- ziglings | 9 +++++++- 8 files changed, 271 insertions(+), 5 deletions(-) create mode 100644 27_defer.zig create mode 100644 28_defer2.zig create mode 100644 29_errdefer.zig create mode 100644 30_switch.zig create mode 100644 31_switch2.zig create mode 100644 32_iferror.zig diff --git a/27_defer.zig b/27_defer.zig new file mode 100644 index 0000000..b41e2af --- /dev/null +++ b/27_defer.zig @@ -0,0 +1,25 @@ +// +// You can assign some code to run _after_ a block of code exits by +// deferring it with a "defer" statement: +// +// { +// defer runLater(); +// runNow(); +// } +// +// In the example above, runLater() will run when the block ({...}) +// is finished. So the code above will run in the following order: +// +// runNow(); +// runLater(); +// +// This feature seems strange at first, but we'll see how it could be +// useful in the next exercise. +const std = @import("std"); + +pub fn main() void { + // Without changing anything else, please add a 'defer' statement + // to this code so that our program prints "One Two\n": + std.debug.print("Two\n", .{}); + std.debug.print("One ", .{}); +} diff --git a/28_defer2.zig b/28_defer2.zig new file mode 100644 index 0000000..5c991da --- /dev/null +++ b/28_defer2.zig @@ -0,0 +1,29 @@ +// +// Now that you know how "defer" works, let's do something more +// interesting with it. +// +const std = @import("std"); + +pub fn main() void { + const animals = [_]u8{ 'g', 'c', 'd', 'd', 'g', 'z' }; + + for (animals) |a| printAnimal(a); + + + std.debug.print("done.\n", .{}); +} + +// This function is _supposed_ to print an animal name in parentheses +// like "(Goat) ", but we somehow need to print the end parenthesis +// even though this function can return in four different places! +fn printAnimal(animal: u8) void { + std.debug.print("(", .{}); + + std.debug.print(") ", .{}); // <---- how!? + + if (animal == 'g'){ std.debug.print("Goat", .{}); return; } + if (animal == 'c'){ std.debug.print("Cat", .{}); return; } + if (animal == 'd'){ std.debug.print("Dog", .{}); return; } + + std.debug.print("Unknown", .{}); +} diff --git a/29_errdefer.zig b/29_errdefer.zig new file mode 100644 index 0000000..cd2158d --- /dev/null +++ b/29_errdefer.zig @@ -0,0 +1,60 @@ +// +// Another common problem is a block of code that could exit in multiple +// places due to an error - but that needs to run do something before it +// exits (typically to clean up after itself). +// +// An "errdefer" is a defer that only runs if the block exits with an error: +// +// { +// errdefer cleanup(); +// try canFail(); +// } +// +// The cleanup() function is called ONLY if the "try" statement returns an +// error produced by canFail(). +// +const std = @import("std"); + +// +var counter: u32 = 0; + +const MyErr = error{ GetFail, IncFail }; + +pub fn main() void { + // We simply quit the entire program if we fail to get a number: + var a: u32 = makeNumber() catch return; + var b: u32 = makeNumber() catch return; + + std.debug.print("Numbers: {}, {}\n", .{a,b}); +} + +fn makeNumber() MyErr!u32 { + std.debug.print("Getting number...", .{}); + + // Please make the "failed" message print ONLY if the makeNumber() + // function exits with an error: + std.debug.print("failed!\n", .{}); + + var num = try getNumber(); // <-- This could fail! + + num = try increaseNumber(num); // <-- This could ALSO fail! + + std.debug.print("got {}. ", .{num}); + + return num; +} + +fn getNumber() MyErr!u32 { + // I _could_ fail...but I don't! + return 4; +} + +fn increaseNumber(n: u32) MyErr!u32 { + // I fail after the first time you run me! + if (counter > 0) return MyErr.IncFail; + + // Sneaky, weird global stuff. + counter += 1; + + return n + 1; +} diff --git a/30_switch.zig b/30_switch.zig new file mode 100644 index 0000000..b10ad14 --- /dev/null +++ b/30_switch.zig @@ -0,0 +1,55 @@ +// +// The "switch" statement lets you match the possible values of an +// expression and perform a different action for each. +// +// This switch: +// +// switch (players) { +// 1 => startOnePlayerGame(), +// 2 => startTwoPlayerGame(), +// else => { +// alert(); +// return GameError.TooManyPlayers; +// } +// } +// +// Is equivalent to this if/else: +// +// if (players == 1) startOnePlayerGame(); +// else if (players == 2) startTwoPlayerGame(); +// else { +// alert(); +// return GameError.TooManyPlayers; +// } +// +// +// +const std = @import("std"); + +pub fn main() void { + const lang_chars = [_]u8{ 26, 9, 7, 42 }; + + for (lang_chars) |c| { + switch (c) { + 1 => std.debug.print("A", .{}), + 2 => std.debug.print("B", .{}), + 3 => std.debug.print("C", .{}), + 4 => std.debug.print("D", .{}), + 5 => std.debug.print("E", .{}), + 6 => std.debug.print("F", .{}), + 7 => std.debug.print("G", .{}), + 8 => std.debug.print("H", .{}), + 9 => std.debug.print("I", .{}), + 10 => std.debug.print("J", .{}), + // ... we don't need everything in between ... + 25 => std.debug.print("Y", .{}), + 26 => std.debug.print("Z", .{}), + // Switch statements must be "exhaustive" (there must be a + // match for every possible value). Please add an "else" + // to this switch to print a question mark "?" when c is + // not one of the existing matches. + } + } + + std.debug.print("\n", .{}); +} diff --git a/31_switch2.zig b/31_switch2.zig new file mode 100644 index 0000000..138b809 --- /dev/null +++ b/31_switch2.zig @@ -0,0 +1,42 @@ +// +// What's really nice is that you can use a switch statement as an +// expression to return a value. +// +// var a = switch (x) { +// 1 => 9, +// 2 => 16, +// 3 => 7, +// ... +// } +// +const std = @import("std"); + +pub fn main() void { + const lang_chars = [_]u8{ 26, 9, 7, 42 }; + + for (lang_chars) |c| { + var real_char: u8 = switch (c) { + 1 => 'A', + 2 => 'B', + 3 => 'C', + 4 => 'D', + 5 => 'E', + 6 => 'F', + 7 => 'G', + 8 => 'H', + 9 => 'I', + 10 => 'J', + // ... + 25 => 'Y', + 26 => 'Z', + // As in the last exercise, please add the "else" clause + // and this time, have it return an exclamation mark "!". + }; + + std.debug.print("{c}", .{real_char}); + // Note: "{c}" forces print() to display the value as a character. + // Can you guess what happens if you remove the "c"? Try it! + } + + std.debug.print("\n", .{}); +} diff --git a/32_iferror.zig b/32_iferror.zig new file mode 100644 index 0000000..ed92e94 --- /dev/null +++ b/32_iferror.zig @@ -0,0 +1,49 @@ +// +// Let's revisit the very first error exercise. This time, we're going to +// look at a special error-handling type of the "if" statement. +// +// if (foo) |value| { +// +// // foo was NOT an error; value is the non-error value of foo +// +// } else |err| { +// +// // foo WAS an error; err is the error value of foo +// +// } +// +// We'll take it even further and use a switch statement to handle +// the error types. +// +const MyNumberError = error{ + TooBig, + TooSmall, +}; + +const std = @import("std"); + +pub fn main() void { + var nums = [_]u8{2,3,4,5,6}; + + for (nums) |num| { + std.debug.print("{}", .{num}); + + var n = numberMaybeFail(num); + if (n) |value| { + std.debug.print("=4. ", .{}); + } else |err| switch (err) { + MyNumberError.TooBig => std.debug.print(">4. ", .{}), + // Please add a match for TooSmall here and have it print: "<4. " + } + } + + std.debug.print("\n", .{}); +} + +// This time we'll have numberMaybeFail() return an error union rather +// than a straight error. +fn numberMaybeFail(n: u8) MyNumberError!u8 { + if(n > 4) return MyNumberError.TooBig; + if(n < 4) return MyNumberError.TooSmall; + return n; +} diff --git a/README.md b/README.md index fe7c628..d5f52aa 100644 --- a/README.md +++ b/README.md @@ -66,10 +66,9 @@ Planned exercises: * [x] While * [x] For * [x] Functions -* [ ] Errors -* [ ] Defer -* [ ] Switch -* [ ] Runtime safety +* [x] Errors +* [x] Defer +* [x] Switch * [ ] Unreachable * [ ] Pointers * [ ] Pointer sized integers diff --git a/ziglings b/ziglings index 26e2ee3..8570f28 100755 --- a/ziglings +++ b/ziglings @@ -54,7 +54,7 @@ function check_it { fi # Wildcards to be lenient with anything AROUND the correct output - if [[ "$result" == *$correct_output* ]] + if [[ "$result" == *"$correct_output"* ]] then printf "${fmt_yay}** PASSED **${fmt_off}\n" else @@ -94,6 +94,13 @@ check_it 23_errors3.zig "a=64, b=22" check_it 24_errors4.zig "a=20, b=14, c=10" check_it 25_errors5.zig "a=0, b=19, c=0" check_it 26_hello2.zig "Hello world" "Try using a try!" +check_it 27_defer.zig "One Two" +check_it 28_defer2.zig "(Goat) (Cat) (Dog) (Dog) (Goat) (Unknown) done." +check_it 29_errdefer.zig "Getting number...got 5. Getting number...failed!" +check_it 30_switch.zig "ZIG?" +check_it 31_switch2.zig "ZIG!" +check_it 32_iferror.zig "2<4. 3<4. 4=4. 5>4. 6>4." "Seriously, what's the deal with fours?" +#check_it 33_quiz4.zig "foo" "Can you make this work?" echo echo " __ __ _ "