aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDave Gauer <dave@ratfactor.com>2021-01-31 17:48:34 -0500
committerDave Gauer <dave@ratfactor.com>2021-01-31 17:48:34 -0500
commitc70fa5f58f5ca2dc010f00caee19027069a09131 (patch)
tree9f7d352065d282c15a75aa4c4168216e69a762f8
parent2de8a8c54d7090dd063bed8b6b283c2fcb452e43 (diff)
downloadziglings-c70fa5f58f5ca2dc010f00caee19027069a09131.tar.gz
ziglings-c70fa5f58f5ca2dc010f00caee19027069a09131.tar.bz2
ziglings-c70fa5f58f5ca2dc010f00caee19027069a09131.tar.xz
ziglings-c70fa5f58f5ca2dc010f00caee19027069a09131.zip
Adding exs 27-32
-rw-r--r--27_defer.zig25
-rw-r--r--28_defer2.zig29
-rw-r--r--29_errdefer.zig60
-rw-r--r--30_switch.zig55
-rw-r--r--31_switch2.zig42
-rw-r--r--32_iferror.zig49
-rw-r--r--README.md7
-rwxr-xr-xziglings9
8 files changed, 271 insertions, 5 deletions
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 " __ __ _ "