From 6ad9774189fbd64b2f2c9519f4513ab34b0c3809 Mon Sep 17 00:00:00 2001 From: Dave Gauer Date: Fri, 12 Mar 2021 18:59:46 -0500 Subject: "999 is enough for anybody" triple-zero padding (#18) When I hit 999 exercises, I will finally have reached the ultimate state of soteriological release and no more exercises will be needed. The cycle will be complete. All that will be left is perfect quietude, freedom, and highest happiness. --- exercises/001_hello.zig | 21 +++++ exercises/002_std.zig | 24 ++++++ exercises/003_assignment.zig | 51 +++++++++++ exercises/004_arrays.zig | 52 ++++++++++++ exercises/005_arrays2.zig | 47 +++++++++++ exercises/006_strings.zig | 48 +++++++++++ exercises/007_strings2.zig | 24 ++++++ exercises/008_quiz.zig | 34 ++++++++ exercises/009_if.zig | 32 +++++++ exercises/010_if2.zig | 16 ++++ exercises/011_while.zig | 34 ++++++++ exercises/012_while2.zig | 35 ++++++++ exercises/013_while3.zig | 33 ++++++++ exercises/014_while4.zig | 26 ++++++ exercises/015_for.zig | 27 ++++++ exercises/016_for2.zig | 33 ++++++++ exercises/017_quiz2.zig | 28 ++++++ exercises/018_functions.zig | 33 ++++++++ exercises/019_functions2.zig | 30 +++++++ exercises/01_hello.zig | 21 ----- exercises/020_quiz3.zig | 43 ++++++++++ exercises/021_errors.zig | 46 ++++++++++ exercises/022_errors2.zig | 29 +++++++ exercises/023_errors3.zig | 28 ++++++ exercises/024_errors4.zig | 69 +++++++++++++++ exercises/025_errors5.zig | 38 +++++++++ exercises/026_hello2.zig | 23 +++++ exercises/027_defer.zig | 25 ++++++ exercises/028_defer2.zig | 37 ++++++++ exercises/029_errdefer.zig | 59 +++++++++++++ exercises/02_std.zig | 24 ------ exercises/030_switch.zig | 53 ++++++++++++ exercises/031_switch2.zig | 42 +++++++++ exercises/032_unreachable.zig | 44 ++++++++++ exercises/033_iferror.zig | 49 +++++++++++ exercises/034_quiz4.zig | 24 ++++++ exercises/035_enums.zig | 55 ++++++++++++ exercises/036_enums2.zig | 61 ++++++++++++++ exercises/037_structs.zig | 59 +++++++++++++ exercises/038_structs2.zig | 52 ++++++++++++ exercises/039_pointers.zig | 36 ++++++++ exercises/03_assignment.zig | 51 ----------- exercises/040_pointers2.zig | 27 ++++++ exercises/041_pointers3.zig | 41 +++++++++ exercises/042_pointers4.zig | 32 +++++++ exercises/043_pointers5.zig | 82 ++++++++++++++++++ exercises/044_quiz5.zig | 47 +++++++++++ exercises/045_optionals.zig | 51 +++++++++++ exercises/046_optionals2.zig | 58 +++++++++++++ exercises/047_methods.zig | 94 +++++++++++++++++++++ exercises/048_methods2.zig | 71 ++++++++++++++++ exercises/049_quiz6.zig | 91 ++++++++++++++++++++ exercises/04_arrays.zig | 52 ------------ exercises/050_no_value.zig | 84 ++++++++++++++++++ exercises/051_values.zig | 187 +++++++++++++++++++++++++++++++++++++++++ exercises/052_slices.zig | 49 +++++++++++ exercises/053_slices2.zig | 35 ++++++++ exercises/054_manypointers.zig | 55 ++++++++++++ exercises/055_unions.zig | 76 +++++++++++++++++ exercises/056_unions2.zig | 64 ++++++++++++++ exercises/057_unions3.zig | 54 ++++++++++++ exercises/05_arrays2.zig | 47 ----------- exercises/06_strings.zig | 48 ----------- exercises/07_strings2.zig | 24 ------ exercises/08_quiz.zig | 34 -------- exercises/09_if.zig | 32 ------- exercises/10_if2.zig | 16 ---- exercises/11_while.zig | 34 -------- exercises/12_while2.zig | 35 -------- exercises/13_while3.zig | 33 -------- exercises/14_while4.zig | 26 ------ exercises/15_for.zig | 27 ------ exercises/16_for2.zig | 33 -------- exercises/17_quiz2.zig | 28 ------ exercises/18_functions.zig | 33 -------- exercises/19_functions2.zig | 30 ------- exercises/20_quiz3.zig | 43 ---------- exercises/21_errors.zig | 46 ---------- exercises/22_errors2.zig | 29 ------- exercises/23_errors3.zig | 28 ------ exercises/24_errors4.zig | 69 --------------- exercises/25_errors5.zig | 38 --------- exercises/26_hello2.zig | 23 ----- exercises/27_defer.zig | 25 ------ exercises/28_defer2.zig | 37 -------- exercises/29_errdefer.zig | 59 ------------- exercises/30_switch.zig | 53 ------------ exercises/31_switch2.zig | 42 --------- exercises/32_unreachable.zig | 44 ---------- exercises/33_iferror.zig | 49 ----------- exercises/34_quiz4.zig | 24 ------ exercises/35_enums.zig | 55 ------------ exercises/36_enums2.zig | 61 -------------- exercises/37_structs.zig | 59 ------------- exercises/38_structs2.zig | 52 ------------ exercises/39_pointers.zig | 36 -------- exercises/40_pointers2.zig | 27 ------ exercises/41_pointers3.zig | 41 --------- exercises/42_pointers4.zig | 32 ------- exercises/43_pointers5.zig | 82 ------------------ exercises/44_quiz5.zig | 47 ----------- exercises/45_optionals.zig | 51 ----------- exercises/46_optionals2.zig | 58 ------------- exercises/47_methods.zig | 94 --------------------- exercises/48_methods2.zig | 71 ---------------- exercises/49_quiz6.zig | 91 -------------------- exercises/50_no_value.zig | 84 ------------------ exercises/51_values.zig | 187 ----------------------------------------- exercises/52_slices.zig | 49 ----------- exercises/53_slices2.zig | 35 -------- exercises/54_manypointers.zig | 55 ------------ exercises/55_unions.zig | 76 ----------------- exercises/56_unions2.zig | 64 -------------- exercises/57_unions3.zig | 54 ------------ 114 files changed, 2698 insertions(+), 2698 deletions(-) create mode 100644 exercises/001_hello.zig create mode 100644 exercises/002_std.zig create mode 100644 exercises/003_assignment.zig create mode 100644 exercises/004_arrays.zig create mode 100644 exercises/005_arrays2.zig create mode 100644 exercises/006_strings.zig create mode 100644 exercises/007_strings2.zig create mode 100644 exercises/008_quiz.zig create mode 100644 exercises/009_if.zig create mode 100644 exercises/010_if2.zig create mode 100644 exercises/011_while.zig create mode 100644 exercises/012_while2.zig create mode 100644 exercises/013_while3.zig create mode 100644 exercises/014_while4.zig create mode 100644 exercises/015_for.zig create mode 100644 exercises/016_for2.zig create mode 100644 exercises/017_quiz2.zig create mode 100644 exercises/018_functions.zig create mode 100644 exercises/019_functions2.zig delete mode 100644 exercises/01_hello.zig create mode 100644 exercises/020_quiz3.zig create mode 100644 exercises/021_errors.zig create mode 100644 exercises/022_errors2.zig create mode 100644 exercises/023_errors3.zig create mode 100644 exercises/024_errors4.zig create mode 100644 exercises/025_errors5.zig create mode 100644 exercises/026_hello2.zig create mode 100644 exercises/027_defer.zig create mode 100644 exercises/028_defer2.zig create mode 100644 exercises/029_errdefer.zig delete mode 100644 exercises/02_std.zig create mode 100644 exercises/030_switch.zig create mode 100644 exercises/031_switch2.zig create mode 100644 exercises/032_unreachable.zig create mode 100644 exercises/033_iferror.zig create mode 100644 exercises/034_quiz4.zig create mode 100644 exercises/035_enums.zig create mode 100644 exercises/036_enums2.zig create mode 100644 exercises/037_structs.zig create mode 100644 exercises/038_structs2.zig create mode 100644 exercises/039_pointers.zig delete mode 100644 exercises/03_assignment.zig create mode 100644 exercises/040_pointers2.zig create mode 100644 exercises/041_pointers3.zig create mode 100644 exercises/042_pointers4.zig create mode 100644 exercises/043_pointers5.zig create mode 100644 exercises/044_quiz5.zig create mode 100644 exercises/045_optionals.zig create mode 100644 exercises/046_optionals2.zig create mode 100644 exercises/047_methods.zig create mode 100644 exercises/048_methods2.zig create mode 100644 exercises/049_quiz6.zig delete mode 100644 exercises/04_arrays.zig create mode 100644 exercises/050_no_value.zig create mode 100644 exercises/051_values.zig create mode 100644 exercises/052_slices.zig create mode 100644 exercises/053_slices2.zig create mode 100644 exercises/054_manypointers.zig create mode 100644 exercises/055_unions.zig create mode 100644 exercises/056_unions2.zig create mode 100644 exercises/057_unions3.zig delete mode 100644 exercises/05_arrays2.zig delete mode 100644 exercises/06_strings.zig delete mode 100644 exercises/07_strings2.zig delete mode 100644 exercises/08_quiz.zig delete mode 100644 exercises/09_if.zig delete mode 100644 exercises/10_if2.zig delete mode 100644 exercises/11_while.zig delete mode 100644 exercises/12_while2.zig delete mode 100644 exercises/13_while3.zig delete mode 100644 exercises/14_while4.zig delete mode 100644 exercises/15_for.zig delete mode 100644 exercises/16_for2.zig delete mode 100644 exercises/17_quiz2.zig delete mode 100644 exercises/18_functions.zig delete mode 100644 exercises/19_functions2.zig delete mode 100644 exercises/20_quiz3.zig delete mode 100644 exercises/21_errors.zig delete mode 100644 exercises/22_errors2.zig delete mode 100644 exercises/23_errors3.zig delete mode 100644 exercises/24_errors4.zig delete mode 100644 exercises/25_errors5.zig delete mode 100644 exercises/26_hello2.zig delete mode 100644 exercises/27_defer.zig delete mode 100644 exercises/28_defer2.zig delete mode 100644 exercises/29_errdefer.zig delete mode 100644 exercises/30_switch.zig delete mode 100644 exercises/31_switch2.zig delete mode 100644 exercises/32_unreachable.zig delete mode 100644 exercises/33_iferror.zig delete mode 100644 exercises/34_quiz4.zig delete mode 100644 exercises/35_enums.zig delete mode 100644 exercises/36_enums2.zig delete mode 100644 exercises/37_structs.zig delete mode 100644 exercises/38_structs2.zig delete mode 100644 exercises/39_pointers.zig delete mode 100644 exercises/40_pointers2.zig delete mode 100644 exercises/41_pointers3.zig delete mode 100644 exercises/42_pointers4.zig delete mode 100644 exercises/43_pointers5.zig delete mode 100644 exercises/44_quiz5.zig delete mode 100644 exercises/45_optionals.zig delete mode 100644 exercises/46_optionals2.zig delete mode 100644 exercises/47_methods.zig delete mode 100644 exercises/48_methods2.zig delete mode 100644 exercises/49_quiz6.zig delete mode 100644 exercises/50_no_value.zig delete mode 100644 exercises/51_values.zig delete mode 100644 exercises/52_slices.zig delete mode 100644 exercises/53_slices2.zig delete mode 100644 exercises/54_manypointers.zig delete mode 100644 exercises/55_unions.zig delete mode 100644 exercises/56_unions2.zig delete mode 100644 exercises/57_unions3.zig (limited to 'exercises') diff --git a/exercises/001_hello.zig b/exercises/001_hello.zig new file mode 100644 index 0000000..d2093c7 --- /dev/null +++ b/exercises/001_hello.zig @@ -0,0 +1,21 @@ +// +// Oh no! This program is supposed to print "Hello world!" but it needs +// your help! +// +// +// Zig functions are private by default but the main() function should +// be public. +// +// A function is declared public with the "pub" statement like so: +// +// pub fn foo() void { +// ... +// } +// +// Try to fix the program and run `ziglings` to see if it works! +// +const std = @import("std"); + +fn main() void { + std.debug.print("Hello world!\n", .{}); +} diff --git a/exercises/002_std.zig b/exercises/002_std.zig new file mode 100644 index 0000000..50059e1 --- /dev/null +++ b/exercises/002_std.zig @@ -0,0 +1,24 @@ +// +// Oops! This program is supposed to print a line like our Hello World +// example. But we forgot how to import the Zig Standard Library. +// +// The @import() function is built into Zig. It returns a value which +// represents the imported code. It's a good idea to store the import as +// a constant value with the same name as the import: +// +// const foo = @import("foo"); +// +// Please complete the import below: +// + +??? = @import("std"); + +pub fn main() void { + std.debug.print("Standard Library.\n", .{}); +} + +// For the curious: Imports must be declared as constants because they +// can only be used at compile time rather than run time. Zig evaluates +// constant values at compile time. Don't worry, we'll cover imports +// in detail later. +// See also this answer: https://stackoverflow.com/a/62567550/695615 diff --git a/exercises/003_assignment.zig b/exercises/003_assignment.zig new file mode 100644 index 0000000..6a4364b --- /dev/null +++ b/exercises/003_assignment.zig @@ -0,0 +1,51 @@ +// +// It seems we got a little carried away making everything "const u8"! +// +// "const" values cannot change. +// "u" types are "unsigned" and cannot store negative values. +// "8" means the type is 8 bits in size. +// +// Example: foo cannot change (it is CONSTant) +// bar can change (it is VARiable): +// +// const foo: u8 = 20; +// var bar: u8 = 20; +// +// Example: foo cannot be negative and can hold 0 to 255 +// bar CAN be negative and can hold −128 to 127 +// +// const foo: u8 = 20; +// const bar: i8 = -20; +// +// Example: foo can hold 8 bits (0 to 255) +// bar can hold 16 bits (0 to 65,535) +// +// const foo: u8 = 20; +// const bar: u16 = 2000; +// +// You can do just about any combination of these that you can think of: +// +// u32 can hold 0 to 4,294,967,295 +// i64 can hold −9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 +// +// Please fix this program so that the types can hold the desired values +// and the errors go away! +// +const std = @import("std"); + +pub fn main() void { + const n: u8 = 50; + n = n + 5; + + const pi: u8 = 314159; + + const negative_eleven: u8 = -11; + + // There are no errors in the next line, just explanation: + // Perhaps you noticed before that the print function takes two + // parameters. Now it will make more sense: the first parameter + // is a string. The string may contain placeholders '{}', and the + // second parameter is an "anonymous list literal" (don't worry + // about this for now!) with the values to be printed. + std.debug.print("{} {} {}\n", .{ n, pi, negative_eleven }); +} diff --git a/exercises/004_arrays.zig b/exercises/004_arrays.zig new file mode 100644 index 0000000..88fcc78 --- /dev/null +++ b/exercises/004_arrays.zig @@ -0,0 +1,52 @@ +// +// Let's learn some array basics. Arrays are declared with: +// +// var foo: [3]u32 = [3]u32{ 42, 108, 5423 }; +// +// When Zig can infer the size of the array, you can use '_' for the +// size. You can also let Zig infer the type of the value so the +// declaration is much less verbose. +// +// var foo = [_]u32{ 42, 108, 5423 }; +// +// Get values of an array using array[index] notation: +// +// const bar = foo[2]; // 5423 +// +// Set values of an array using array[index] notation: +// +// foo[2] = 16; +// +// Get the length of an array using the len property: +// +// const length = foo.len; +// +const std = @import("std"); + +pub fn main() void { + // (Problem 1) + // This "const" is going to cause a problem later - can you see what it is? + // How do we fix it? + const some_primes = [_]u8{ 1, 3, 5, 7, 11, 13, 17, 19 }; + + // Individual values can be set with '[]' notation. + // Example: This line changes the first prime to 2 (which is correct): + some_primes[0] = 2; + + // Individual values can also be accessed with '[]' notation. + // Example: This line stores the first prime in "first": + const first = some_primes[0]; + + // (Problem 2) + // Looks like we need to complete this expression. Use the example + // above to set "fourth" to the fourth element of the some_primes array: + const fourth = some_primes[???]; + + // (Problem 3) + // Use the len property to get the length of the array: + const length = some_primes.???; + + std.debug.print("First: {}, Fourth: {}, Length: {}\n", .{ + first, fourth, length, + }); +} diff --git a/exercises/005_arrays2.zig b/exercises/005_arrays2.zig new file mode 100644 index 0000000..9282a31 --- /dev/null +++ b/exercises/005_arrays2.zig @@ -0,0 +1,47 @@ +// +// Zig has some fun array operators. +// +// You can use '++' to concatenate two arrays: +// +// const a = [_]u8{ 1,2 }; +// const b = [_]u8{ 3,4 }; +// const c = a ++ b ++ [_]u8{ 5 }; // equals 1 2 3 4 5 +// +// You can use '**' to repeat an array: +// +// const d = [_]u8{ 1,2,3 } ** 2; // equals 1 2 3 1 2 3 +// +const std = @import("std"); + +pub fn main() void { + const le = [_]u8{ 1, 3 }; + const et = [_]u8{ 3, 7 }; + + // (Problem 1) + // Please set this array concatenating the two arrays above. + // It should result in: 1 3 3 7 + const leet = ???; + + // (Problem 2) + // Please set this array to using repetition. + // It should result in: 1 0 0 1 1 0 0 1 1 0 0 1 + const bit_pattern = [_]u8{ ??? } ** 3; + + // Okay, that's all of the problems. Let's see the results. + // + // We could print these arrays with leet[0], leet[1],...but let's + // have a little preview of Zig "for" loops instead: + std.debug.print("LEET: ", .{}); + + for (leet) |*n| { + std.debug.print("{}", .{n.*}); + } + + std.debug.print(", Bits: ", .{}); + + for (bit_pattern) |*n| { + std.debug.print("{}", .{n.*}); + } + + std.debug.print("\n", .{}); +} diff --git a/exercises/006_strings.zig b/exercises/006_strings.zig new file mode 100644 index 0000000..6258816 --- /dev/null +++ b/exercises/006_strings.zig @@ -0,0 +1,48 @@ +// +// Now that we've learned about arrays, we can talk about strings. +// +// We've already seen Zig string literals: "Hello world.\n" +// +// Zig stores strings as arrays of bytes. +// +// const foo = "Hello"; +// +// Is the same as: +// +// const foo = [_]u8{ 'H', 'e', 'l', 'l', 'o' }; +// +const std = @import("std"); + +pub fn main() void { + const ziggy = "stardust"; + + // (Problem 1) + // Use array square bracket syntax to get the letter 'd' from + // the string "stardust" above. + const d: u8 = ziggy[???]; + + // (Problem 2) + // Use the array repeat '**' operator to make "ha ha ha ". + const laugh = "ha " ???; + + // (Problem 3) + // Use the array concatenation '++' operator to make "Major Tom". + // (You'll need to add a space as well!) + const major = "Major"; + const tom = "Tom"; + const major_tom = major ??? tom; + + // That's all the problems. Let's see our results: + std.debug.print("d={u} {s}{s}\n", .{ d, laugh, major_tom }); + // Keen eyes will notice that we've put 'u' and 's' inside the '{}' + // placeholders in the format string above. This tells the + // print() function to format the values as a UTF-8 character and + // UTF-8 strings respectively. If we didn't do this, we'd see '100', + // which is the decimal number corresponding with the 'd' character + // in UTF-8. (And an error in the case of the strings.) + // + // While we're on this subject, 'c' (ASCII encoded character) + // would work in place for 'u' because the first 128 characters + // of UTF-8 are the same as ASCII! + // +} diff --git a/exercises/007_strings2.zig b/exercises/007_strings2.zig new file mode 100644 index 0000000..6350be1 --- /dev/null +++ b/exercises/007_strings2.zig @@ -0,0 +1,24 @@ +// +// Here's a fun one: Zig has multi-line strings! +// +// To make a multi-line string, put '\\' at the beginning of each +// line just like a code comment but with backslashes instead: +// +// const two_lines = +// \\Line One +// \\Line Two +// ; +// +// See if you can make this program print some song lyrics. +// +const std = @import("std"); + +pub fn main() void { + const lyrics = + Ziggy played guitar + Jamming good with Andrew Kelley + And the Spiders from Mars + ; + + std.debug.print("{s}\n", .{lyrics}); +} diff --git a/exercises/008_quiz.zig b/exercises/008_quiz.zig new file mode 100644 index 0000000..eda66b8 --- /dev/null +++ b/exercises/008_quiz.zig @@ -0,0 +1,34 @@ +// +// Quiz time! Let's see if you can fix this whole program. +// +// This is meant to be challenging. +// +// Let the compiler tell you what's wrong. +// +// Start at the top. +// +const std = @import("std"); + +pub fn main() void { + // What is this nonsense? :-) + const letters = "YZhifg"; + + const x: u8 = 1; + + // This is something you haven't seen before: declaring an array + // without putting anything in it. There is no error here: + var lang: [3]u8 = undefined; + + // The following lines attempt to put 'Z', 'i', and 'g' into the + // 'lang' array we just created. + lang[0] = letters[x]; + + x = 3; + lang[???] = letters[x]; + + x = ???; + lang[2] = letters[???]; + + // We want to "Program in Zig!" of course: + std.debug.print("Program in {s}!\n", .{lang}); +} diff --git a/exercises/009_if.zig b/exercises/009_if.zig new file mode 100644 index 0000000..284563d --- /dev/null +++ b/exercises/009_if.zig @@ -0,0 +1,32 @@ +// +// Now we get into the fun stuff, starting with the 'if' statement! +// +// if (true) { +// ... +// } else { +// ... +// } +// +// Zig has the "usual" comparison operators such as: +// +// a == b means "a equals b" +// a < b means "a is less than b" +// a != b means "a does not equal b" +// +// The important thing about Zig's "if" is that it *only* accepts +// boolean values. It won't coerce numbers or other types of data +// to true and false. +// +const std = @import("std"); + +pub fn main() void { + const foo = 1; + + // Please fix this condition: + if (foo) { + // We want our program to print this message! + std.debug.print("Foo is 1!\n", .{}); + } else { + std.debug.print("Foo is not 1!\n", .{}); + } +} diff --git a/exercises/010_if2.zig b/exercises/010_if2.zig new file mode 100644 index 0000000..d0c8cac --- /dev/null +++ b/exercises/010_if2.zig @@ -0,0 +1,16 @@ +// +// If statements are also valid expressions: +// +// var foo: u8 = if (a) 2 else 3; +// +const std = @import("std"); + +pub fn main() void { + var discount = true; + + // Please use an if...else expression to set "price". + // If discount is true, the price should be $17, otherwise $20: + var price: u8 = if ???; + + std.debug.print("With the discount, the price is ${}.\n", .{price}); +} diff --git a/exercises/011_while.zig b/exercises/011_while.zig new file mode 100644 index 0000000..674d904 --- /dev/null +++ b/exercises/011_while.zig @@ -0,0 +1,34 @@ +// +// Zig 'while' statements create a loop that runs while the +// condition is true. This runs once (at most): +// +// while (condition) { +// condition = false; +// } +// +// Remember that the condition must be a boolean value and +// that we can get a boolean value from conditional operators +// such as: +// +// a == b means "a equals b" +// a < b means "a is less than b" +// a > b means "a is greater than b" +// a != b means "a does not equal b" +// +const std = @import("std"); + +pub fn main() void { + var n: u32 = 2; + + // Please use a condition that is true UNTIL "n" reaches 1024: + while (???) { + // Print the current number + std.debug.print("{} ", .{n}); + + // Set n to n multiplied by 2 + n *= 2; + } + + // Once the above is correct, this will print "n=1024" + std.debug.print("n={}\n", .{n}); +} diff --git a/exercises/012_while2.zig b/exercises/012_while2.zig new file mode 100644 index 0000000..ef53ea0 --- /dev/null +++ b/exercises/012_while2.zig @@ -0,0 +1,35 @@ +// +// Zig 'while' statements can have an optional 'continue expression' +// which runs every time the while loop continues (either at the +// end of the loop or when an explicit 'continue' is invoked - we'll +// try those out next): +// +// while (condition) : (continue expression) { +// ... +// } +// +// Example: +// +// var foo = 2; +// while (foo<10) : (foo+=2) { +// // Do something with even numbers less than 10... +// } +// +// See if you can re-write the last exercise using a continue +// expression: +// +const std = @import("std"); + +pub fn main() void { + var n: u32 = 2; + + // Please set the continue expression so that we get the desired + // results in the print statement below. + while (n < 1000) : ??? { + // Print the current number + std.debug.print("{} ", .{n}); + } + + // As in the last exercise, we want this to result in "n=1024" + std.debug.print("n={}\n", .{n}); +} diff --git a/exercises/013_while3.zig b/exercises/013_while3.zig new file mode 100644 index 0000000..4cccf62 --- /dev/null +++ b/exercises/013_while3.zig @@ -0,0 +1,33 @@ +// +// The last two exercises were functionally identical. Continue +// expressions really show their utility when used with 'continue' +// statements! +// +// Example: +// +// while (condition) : (continue expression) { +// +// if (other condition) continue; +// +// } +// +// The "continue expression" executes every time the loop restarts +// whether the "continue" statement happens or not. +// +const std = @import("std"); + +pub fn main() void { + var n: u32 = 1; + + // I want to print every number between 1 and 20 that is NOT + // divisible by 3 or 5. + while (n <= 20) : (n += 1) { + // The '%' symbol is the "modulo" operator and it + // returns the remainder after division. + if (n % 3 == 0) ???; + if (n % 5 == 0) ???; + std.debug.print("{} ", .{n}); + } + + std.debug.print("\n", .{}); +} diff --git a/exercises/014_while4.zig b/exercises/014_while4.zig new file mode 100644 index 0000000..7b2714e --- /dev/null +++ b/exercises/014_while4.zig @@ -0,0 +1,26 @@ +// +// You can force a loop to exit immediately with a "break" statement: +// +// while (condition) : (continue expression) { +// +// if (other condition) break; +// +// } +// +// Continue expressions do NOT execute when a while loop stops +// because of a break! +// +const std = @import("std"); + +pub fn main() void { + var n: u32 = 1; + + // Oh dear! This while loop will go forever!? + // Please fix this so the print statement below gives the desired output. + while (true) : (n += 1) { + if (???) ???; + } + + // Result: we want n=4 + std.debug.print("n={}\n", .{n}); +} diff --git a/exercises/015_for.zig b/exercises/015_for.zig new file mode 100644 index 0000000..2ce930e --- /dev/null +++ b/exercises/015_for.zig @@ -0,0 +1,27 @@ +// +// Behold the 'for' loop! It lets you execute code for each +// member of an array: +// +// for (items) |item| { +// +// // Do something with item +// +// } +// +const std = @import("std"); + +pub fn main() void { + const story = [_]u8{ 'h', 'h', 's', 'n', 'h' }; + + std.debug.print("A Dramatic Story: ", .{}); + + for (???) |???| { + if (scene == 'h') std.debug.print(":-) ", .{}); + if (scene == 's') std.debug.print(":-( ", .{}); + if (scene == 'n') std.debug.print(":-| ", .{}); + } + + std.debug.print("The End.\n", .{}); +} +// Note that "for" loops also work on things called "slices" +// which we'll see later. diff --git a/exercises/016_for2.zig b/exercises/016_for2.zig new file mode 100644 index 0000000..0a62a1a --- /dev/null +++ b/exercises/016_for2.zig @@ -0,0 +1,33 @@ +// +// For loops also let you store the "index" of the iteration - a +// number starting with 0 that counts up with each iteration: +// +// for (items) |item, index| { +// +// // Do something with item and index +// +// } +// +// You can name "item" and "index" anything you want. "i" is a popular +// shortening of "index". The item name is often the singular form of +// the items you're looping through. +// +const std = @import("std"); + +pub fn main() void { + // Let's store the bits of binary number 1101 in + // 'little-endian' order (least significant byte first): + const bits = [_]u8{ 1, 0, 1, 1 }; + var value: u32 = 0; + + // Now we'll convert the binary bits to a number value by adding + // the value of the place as a power of two for each bit. + // + // See if you can figure out the missing piece: + for (bits) |bit, ???| { + var place_value = std.math.pow(u32, 2, @intCast(u32, i)); + value += place_value * bit; + } + + std.debug.print("The value of bits '1101': {}.\n", .{value}); +} diff --git a/exercises/017_quiz2.zig b/exercises/017_quiz2.zig new file mode 100644 index 0000000..7de7010 --- /dev/null +++ b/exercises/017_quiz2.zig @@ -0,0 +1,28 @@ +// +// Quiz time again! Let's see if you can solve the famous "Fizz Buzz"! +// +// "Players take turns to count incrementally, replacing +// any number divisible by three with the word "fizz", +// and any number divisible by five with the word "buzz". +// - From https://en.wikipedia.org/wiki/Fizz_buzz +// +// Let's go from 1 to 16. This has been started for you, but there's +// some problems. :-( +// +const std = import standard library; + +function main() void { + var i: u8 = 1; + var stop_at: u8 = 16; + + // What kind of loop is this? A 'for' or a 'while'? + ??? (i <= stop_at) : (i += 1) { + if (i % 3 == 0) std.debug.print("Fizz", .{}); + if (i % 5 == 0) std.debug.print("Buzz", .{}); + if (!(i % 3 == 0) and !(i % 5 == 0)) { + std.debug.print("{}", .{???}); + } + std.debug.print(", ", .{}); + } + std.debug.print("\n", .{}); +} diff --git a/exercises/018_functions.zig b/exercises/018_functions.zig new file mode 100644 index 0000000..51be2cd --- /dev/null +++ b/exercises/018_functions.zig @@ -0,0 +1,33 @@ +// +// Functions! We've already seen a lot of one called "main()". Now let's try +// writing one of our own: +// +// fn foo(n: u8) u8 { +// return n + 1; +// } +// +// The foo() function above takes a number "n" and returns a number that is +// larger by one. +// +// If your function doesn't take any parameters and doesn't return anything, +// it would be defined like main(): +// +// fn foo() void { } +// +const std = @import("std"); + +pub fn main() void { + // The new function deepThought() should return the number 42. See below. + const answer: u8 = deepThought(); + + std.debug.print("Answer to the Ultimate Question: {}\n", .{answer}); +} + +// Please define the deepThought() function below. +// +// We're just missing a couple things. One thing we're NOT missing is the +// keyword "pub", which is not needed here. Can you guess why? +// +??? deepThought() ??? { + return 42; // Number courtesy Douglas Adams +} diff --git a/exercises/019_functions2.zig b/exercises/019_functions2.zig new file mode 100644 index 0000000..00f33c5 --- /dev/null +++ b/exercises/019_functions2.zig @@ -0,0 +1,30 @@ +// +// Now let's create a function that takes a parameter. Here's an +// example that takes two parameters. As you can see, parameters +// are declared just like any other types ("name": "type"): +// +// fn myFunction(number: u8, is_lucky: bool) { +// ... +// } +// +const std = @import("std"); + +pub fn main() void { + std.debug.print("Powers of two: {} {} {} {}\n", .{ + twoToThe(1), + twoToThe(2), + twoToThe(3), + twoToThe(4), + }); +} + +// Please give this function the correct input parameter(s). +// You'll need to figure out the parameter name and type that we're +// expecting. The output type has already been specified for you. +// +fn twoToThe(???) u32 { + return std.math.pow(u32, 2, my_number); + // std.math.pow(type, a, b) takes a numeric type and two numbers + // of that type and returns "a to the power of b" as that same + // numeric type. +} diff --git a/exercises/01_hello.zig b/exercises/01_hello.zig deleted file mode 100644 index d2093c7..0000000 --- a/exercises/01_hello.zig +++ /dev/null @@ -1,21 +0,0 @@ -// -// Oh no! This program is supposed to print "Hello world!" but it needs -// your help! -// -// -// Zig functions are private by default but the main() function should -// be public. -// -// A function is declared public with the "pub" statement like so: -// -// pub fn foo() void { -// ... -// } -// -// Try to fix the program and run `ziglings` to see if it works! -// -const std = @import("std"); - -fn main() void { - std.debug.print("Hello world!\n", .{}); -} diff --git a/exercises/020_quiz3.zig b/exercises/020_quiz3.zig new file mode 100644 index 0000000..651af8c --- /dev/null +++ b/exercises/020_quiz3.zig @@ -0,0 +1,43 @@ +// +// Let's see if we can make use of some of things we've learned so far. +// We'll create two functions: one that contains a "for" loop and one +// that contains a "while" loop. +// +// Both of these are simply labeled "loop" below. +// +const std = @import("std"); + +pub fn main() void { + const my_numbers = [4]u16{ 5, 6, 7, 8 }; + + printPowersOfTwo(my_numbers); + std.debug.print("\n", .{}); +} + +// You won't see this every day: a function that takes an array with +// exactly four u16 numbers. This is not how you would normally pass +// an array to a function. We'll learn about slices and pointers in +// a little while. For now, we're using what we know. +// +// This function prints, but does not return anything. +// +fn printPowersOfTwo(numbers: [4]u16) ??? { + loop (numbers) |n| { + std.debug.print("{} ", .{twoToThe(n)}); + } +} + +// This function bears a striking resemblance to twoToThe() in the last +// exercise. But don't be fooled! This one does the math without the aid +// of the standard library! +// +fn twoToThe(number: u16) ??? { + var n: u16 = 0; + var total: u16 = 1; + + loop (n < number) : (n += 1) { + total *= 2; + } + + return ???; +} diff --git a/exercises/021_errors.zig b/exercises/021_errors.zig new file mode 100644 index 0000000..cbb5ac8 --- /dev/null +++ b/exercises/021_errors.zig @@ -0,0 +1,46 @@ +// +// Believe it or not, sometimes things go wrong in programs. +// +// In Zig, an error is a value. Errors are named so we can identify +// things that can go wrong. Errors are created in "error sets", which +// are just a collection of named errors. +// +// We have the start of an error set, but we're missing the condition +// "TooSmall". Please add it where needed! +const MyNumberError = error{ + TooBig, + ???, + TooFour, +}; + +const std = @import("std"); + +pub fn main() void { + var nums = [_]u8{ 2, 3, 4, 5, 6 }; + + for (nums) |n| { + std.debug.print("{}", .{n}); + + const number_error = numberFail(n); + + if (number_error == MyNumberError.TooBig) { + std.debug.print(">4. ", .{}); + } + if (???) { + std.debug.print("<4. ", .{}); + } + if (number_error == MyNumberError.TooFour) { + std.debug.print("=4. ", .{}); + } + } + + std.debug.print("\n", .{}); +} + +// Notice how this function can return any member of the MyNumberError +// error set. +fn numberFail(n: u8) MyNumberError { + if (n > 4) return MyNumberError.TooBig; + if (n < 4) return MyNumberError.TooSmall; // <---- this one is free! + return MyNumberError.TooFour; +} diff --git a/exercises/022_errors2.zig b/exercises/022_errors2.zig new file mode 100644 index 0000000..fa0eafa --- /dev/null +++ b/exercises/022_errors2.zig @@ -0,0 +1,29 @@ +// +// A common case for errors is a situation where we're expecting to +// have a value OR something has gone wrong. Take this example: +// +// var text: Text = getText('foo.txt'); +// +// What happens if getText() can't find 'foo.txt'? How do we express +// this in Zig? +// +// Zig lets us make what's called an "error union" which is a value +// which could either be a regular value OR an error from a set: +// +// var text: MyErrorSet!Text = getText('foo.txt'); +// +// For now, let's just see if we can try making an error union! +// +const std = @import("std"); + +const MyNumberError = error{TooSmall}; + +pub fn main() void { + var my_number: ??? = 5; + + // Looks like my_number will need to either store a number OR + // an error. Can you set the type correctly above? + my_number = MyNumberError.TooSmall; + + std.debug.print("I compiled!", .{}); +} diff --git a/exercises/023_errors3.zig b/exercises/023_errors3.zig new file mode 100644 index 0000000..a465737 --- /dev/null +++ b/exercises/023_errors3.zig @@ -0,0 +1,28 @@ +// +// One way to deal with error unions is to "catch" any error and +// replace it with a default value. +// +// foo = canFail() catch 6; +// +// If canFail() fails, foo will equal 6. +// +const std = @import("std"); + +const MyNumberError = error{TooSmall}; + +pub fn main() void { + var a: u32 = addTwenty(44) catch 22; + var b: u32 = addTwenty(4) ??? 22; + + std.debug.print("a={}, b={}", .{ a, b }); +} + +// Please provide the return type from this function. +// Hint: it'll be an error union. +fn addTwenty(n: u32) ??? { + if (n < 5) { + return MyNumberError.TooSmall; + } else { + return n + 20; + } +} diff --git a/exercises/024_errors4.zig b/exercises/024_errors4.zig new file mode 100644 index 0000000..560b129 --- /dev/null +++ b/exercises/024_errors4.zig @@ -0,0 +1,69 @@ +// +// Using `catch` to replace an error with a default value is a bit +// of a blunt instrument since it doesn't matter what the error is. +// +// Catch lets us capture the error value and perform additional +// actions with this form: +// +// canFail() catch |err| { +// if (err == FishError.TunaMalfunction) { +// ... +// } +// }; +// +const std = @import("std"); + +const MyNumberError = error{ + TooSmall, + TooBig, +}; + +pub fn main() void { + // The "catch 0" below is just our way of dealing with the fact + // that makeJustRight() returns a error union (for now). + var a: u32 = makeJustRight(44) catch 0; + var b: u32 = makeJustRight(14) catch 0; + var c: u32 = makeJustRight(4) catch 0; + + std.debug.print("a={}, b={}, c={}", .{ a, b, c }); +} + +// In this silly example we've split the responsibility of making +// a number just right into four (!) functions: +// +// makeJustRight() Calls fixTooBig(), cannot fix any errors. +// fixTooBig() Calls fixTooSmall(), fixes TooBig errors. +// fixTooSmall() Calls detectProblems(), fixes TooSmall errors. +// detectProblems() Returns the number or an error. +// +fn makeJustRight(n: u32) MyNumberError!u32 { + return fixTooBig(n) catch |err| { + return err; + }; +} + +fn fixTooBig(n: u32) MyNumberError!u32 { + return fixTooSmall(n) catch |err| { + if (err == MyNumberError.TooBig) { + return 20; + } + + return err; + }; +} + +fn fixTooSmall(n: u32) MyNumberError!u32 { + // Oh dear, this is missing a lot! But don't worry, it's nearly + // identical to fixTooBig() above. + // + // If we get a TooSmall error, we should return 10. + // If we get any other error, we should return that error. + // Otherwise, we return the u32 number. + return detectProblems(n) ??? +} + +fn detectProblems(n: u32) MyNumberError!u32 { + if (n < 10) return MyNumberError.TooSmall; + if (n > 20) return MyNumberError.TooBig; + return n; +} diff --git a/exercises/025_errors5.zig b/exercises/025_errors5.zig new file mode 100644 index 0000000..5119dcf --- /dev/null +++ b/exercises/025_errors5.zig @@ -0,0 +1,38 @@ +// +// Zig has a handy "try" shortcut for this common error handling pattern: +// +// canFail() catch |err| return err; +// +// which can be more compactly written as: +// +// try canFail(); +// +const std = @import("std"); + +const MyNumberError = error{ + TooSmall, + TooBig, +}; + +pub fn main() void { + var a: u32 = addFive(44) catch 0; + var b: u32 = addFive(14) catch 0; + var c: u32 = addFive(4) catch 0; + + std.debug.print("a={}, b={}, c={}", .{ a, b, c }); +} + +fn addFive(n: u32) MyNumberError!u32 { + // This function needs to return any error which might come back from detect(). + // Please use a "try" statement rather than a "catch". + // + var x = detect(n); + + return x + 5; +} + +fn detect(n: u32) MyNumberError!u32 { + if (n < 10) return MyNumberError.TooSmall; + if (n > 20) return MyNumberError.TooBig; + return n; +} diff --git a/exercises/026_hello2.zig b/exercises/026_hello2.zig new file mode 100644 index 0000000..237d27c --- /dev/null +++ b/exercises/026_hello2.zig @@ -0,0 +1,23 @@ +// +// Great news! Now we know enough to understand a "real" Hello World +// program in Zig - one that uses the system Standard Out resource...which +// can fail! +// +const std = @import("std"); + +// Take note that this main() definition now returns "!void" rather +// than just "void". Since there's no specific error type, this means +// that Zig will infer the error type. This is appropriate in the case +// of main(), but can have consequences elsewhere. +pub fn main() !void { + + // We get a Writer for Standard Out so we can print() to it. + const stdout = std.io.getStdOut().writer(); + + // Unlike std.debug.print(), the Standard Out writer can fail + // with an error. We don't care _what_ the error is, we want + // to be able to pass it up as a return value of main(). + // + // We just learned of a single statement which can accomplish this. + stdout.print("Hello world!\n", .{}); +} diff --git a/exercises/027_defer.zig b/exercises/027_defer.zig new file mode 100644 index 0000000..b41e2af --- /dev/null +++ b/exercises/027_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/exercises/028_defer2.zig b/exercises/028_defer2.zig new file mode 100644 index 0000000..6943012 --- /dev/null +++ b/exercises/028_defer2.zig @@ -0,0 +1,37 @@ +// +// 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/exercises/029_errdefer.zig b/exercises/029_errdefer.zig new file mode 100644 index 0000000..f43c738 --- /dev/null +++ b/exercises/029_errdefer.zig @@ -0,0 +1,59 @@ +// +// 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/exercises/02_std.zig b/exercises/02_std.zig deleted file mode 100644 index 50059e1..0000000 --- a/exercises/02_std.zig +++ /dev/null @@ -1,24 +0,0 @@ -// -// Oops! This program is supposed to print a line like our Hello World -// example. But we forgot how to import the Zig Standard Library. -// -// The @import() function is built into Zig. It returns a value which -// represents the imported code. It's a good idea to store the import as -// a constant value with the same name as the import: -// -// const foo = @import("foo"); -// -// Please complete the import below: -// - -??? = @import("std"); - -pub fn main() void { - std.debug.print("Standard Library.\n", .{}); -} - -// For the curious: Imports must be declared as constants because they -// can only be used at compile time rather than run time. Zig evaluates -// constant values at compile time. Don't worry, we'll cover imports -// in detail later. -// See also this answer: https://stackoverflow.com/a/62567550/695615 diff --git a/exercises/030_switch.zig b/exercises/030_switch.zig new file mode 100644 index 0000000..cb983f5 --- /dev/null +++ b/exercises/030_switch.zig @@ -0,0 +1,53 @@ +// +// 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/exercises/031_switch2.zig b/exercises/031_switch2.zig new file mode 100644 index 0000000..b7680b4 --- /dev/null +++ b/exercises/031_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/exercises/032_unreachable.zig b/exercises/032_unreachable.zig new file mode 100644 index 0000000..ffc35a4 --- /dev/null +++ b/exercises/032_unreachable.zig @@ -0,0 +1,44 @@ +// +// Zig has an "unreachable" statement. Use it when you want to tell the +// compiler that a branch of code should never be executed and that the +// mere act of reaching it is an error. +// +// if (true) { +// ... +// } else { +// unreachable; +// } +// +// Here we've made a little virtual machine that performs mathematical +// operations on a single numeric value. It looks great but there's one +// little problem: the switch statement doesn't cover every possible +// value of a u8 number! +// +// WE know there are only three operations but Zig doesn't. Use the +// unreachable statement to make the switch complete. Or ELSE. :-) +// +const std = @import("std"); + +pub fn main() void { + const operations = [_]u8{ 1, 1, 1, 3, 2, 2 }; + + var current_value: u32 = 0; + + for (operations) |op| { + switch (op) { + 1 => { + current_value += 1; + }, + 2 => { + current_value -= 1; + }, + 3 => { + current_value *= current_value; + }, + } + + std.debug.print("{} ", .{current_value}); + } + + std.debug.print("\n", .{}); +} diff --git a/exercises/033_iferror.zig b/exercises/033_iferror.zig new file mode 100644 index 0000000..67777a9 --- /dev/null +++ b/exercises/033_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/exercises/034_quiz4.zig b/exercises/034_quiz4.zig new file mode 100644 index 0000000..6b0e3fc --- /dev/null +++ b/exercises/034_quiz4.zig @@ -0,0 +1,24 @@ +// +// Quiz time. See if you can make this program work! +// +// Solve this any way you like, just be sure the output is: +// +// my_num=42 +// +const std = @import("std"); + +const NumError = error{IllegalNumber}; + +pub fn main() void { + const stdout = std.io.getStdOut().writer(); + + const my_num: u32 = getNumber(); + + try stdout.print("my_num={}\n", .{my_num}); +} + +// Just don't modify this function. It's "perfect" the way it is. :-) +fn getNumber() NumError!u32 { + if (false) return NumError.IllegalNumber; + return 42; +} diff --git a/exercises/035_enums.zig b/exercises/035_enums.zig new file mode 100644 index 0000000..1825f52 --- /dev/null +++ b/exercises/035_enums.zig @@ -0,0 +1,55 @@ +// +// Remember that little mathematical virtual machine we made using the +// "unreachable" statement? Well, there were two problems with the +// way we were using op codes: +// +// 1. Having to remember op codes by number is no good. +// 2. We had to use "unreachable" because Zig had no way of knowing +// how many valid op codes there were. +// +// An "enum" is a Zig construct that lets you give names to numeric +// values and store them in a set. They look a lot like error sets: +// +// const Fruit = enum{ apple, pear, orange }; +// +// const my_fruit = Fruit.apple; +// +// Let's use an enum in place of the numbers we were using in the +// previous version! +// +const std = @import("std"); + +// Please complete the enum! +const Ops = enum { ??? }; + +pub fn main() void { + const operations = [_]Ops{ + Ops.inc, + Ops.inc, + Ops.inc, + Ops.pow, + Ops.dec, + Ops.dec, + }; + + var current_value: u32 = 0; + + for (operations) |op| { + switch (op) { + Ops.inc => { + current_value += 1; + }, + Ops.dec => { + current_value -= 1; + }, + Ops.pow => { + current_value *= current_value; + }, + // No "else" needed! Why is that? + } + + std.debug.print("{} ", .{current_value}); + } + + std.debug.print("\n", .{}); +} diff --git a/exercises/036_enums2.zig b/exercises/036_enums2.zig new file mode 100644 index 0000000..0ddc4a5 --- /dev/null +++ b/exercises/036_enums2.zig @@ -0,0 +1,61 @@ +// +// Enums are really just a set of numbers. You can leave the +// numbering up to the compiler, or you can assign them +// explicitly. You can even specify the numeric type used. +// +// const Stuff = enum(u8){ foo = 16 }; +// +// You can get the integer out with a built-in function: +// +// var my_stuff: u8 = @enumToInt(Stuff.foo); +// +// Note how that built-in function starts with "@" just like the +// @import() function we've been using. +// +const std = @import("std"); + +// Zig lets us write integers in hexadecimal format: +// +// 0xf (is the value 15 in hex) +// +// Web browsers let us specify colors using a hexadecimal +// number where each byte represents the brightness of the +// Red, Green, or Blue component (RGB) where two hex digits +// are one byte with a value range of 0-255: +// +// #RRGGBB +// +// Please define and use a pure blue value Color: +const Color = enum(u32) { + red = 0xff0000, + green = 0x00ff00, + blue = ???, +}; + +pub fn main() void { + // Remember Zig's multi-line strings? Here they are again. + // Also, check out this cool format string: + // + // {x:0>6} + // ^ + // x type ('x' is lower-case hexadecimal) + // : separator (needed for format syntax) + // 0 padding character (default is ' ') + // > alignment ('>' aligns right) + // 6 width (use padding to force width) + // + // Please add this formatting to the blue value. + // (Even better, experiment without it, or try parts of it + // to see what prints!) + std.debug.print( + \\

+ \\ Red + \\ Green + \\ Blue + \\

+ , .{ + @enumToInt(Color.red), + @enumToInt(Color.green), + @enumToInt(???), // Oops! We're missing something! + }); +} diff --git a/exercises/037_structs.zig b/exercises/037_structs.zig new file mode 100644 index 0000000..8082248 --- /dev/null +++ b/exercises/037_structs.zig @@ -0,0 +1,59 @@ +// +// Being able to group values together lets us turn this: +// +// point1_x = 3; +// point1_y = 16; +// point1_z = 27; +// point2_x = 7; +// point2_y = 13; +// point2_z = 34; +// +// into this: +// +// point1 = Point{ .x=3, .y=16, .z=27 }; +// point2 = Point{ .x=7, .y=13, .z=34 }; +// +// The Point above is an example of a "struct" (short for "structure"). +// Here's how it could have been defined: +// +// const Point = struct{ x: u32, y: u32, z: u32 }; +// +// Let's store something fun with a struct: a roleplaying character! +// +const std = @import("std"); + +// We'll use an enum to specify the character class. +const Class = enum { + wizard, + thief, + bard, + warrior, +}; + +// Please add a new property to this struct called "health" and make +// it a u8 integer type. +const Character = struct { + class: Class, + gold: u32, + experience: u32, +}; + +pub fn main() void { + // Please initialize Glorp with 100 health. + var glorp_the_wise = Character{ + .class = Class.wizard, + .gold = 20, + .experience = 10, + }; + + // Glorp gains some gold. + glorp_the_wise.gold += 5; + + // Ouch! Glorp takes a punch! + glorp_the_wise.health -= 10; + + std.debug.print("Your wizard has {} health and {} gold.", .{ + glorp_the_wise.health, + glorp_the_wise.gold, + }); +} diff --git a/exercises/038_structs2.zig b/exercises/038_structs2.zig new file mode 100644 index 0000000..b0db022 --- /dev/null +++ b/exercises/038_structs2.zig @@ -0,0 +1,52 @@ +// +// Grouping values in structs is not merely convenient. It also allows +// us to treat the values as a single item when storing them, passing +// them to functions, etc. +// +// This exercise demonstrates how we can store structs in an array and +// how doing so lets us print them all (both) using a loop. +// +const std = @import("std"); + +const Class = enum { + wizard, + thief, + bard, + warrior, +}; + +const Character = struct { + class: Class, + gold: u32, + health: u8, + experience: u32, +}; + +pub fn main() void { + var chars: [2]Character = undefined; + + // Glorp the Wise + chars[0] = Character{ + .class = Class.wizard, + .gold = 20, + .health = 100, + .experience = 10, + }; + + // Please add "Zump the Loud" with the following properties: + // + // class bard + // gold 10 + // health 100 + // experience 20 + // + // Feel free to run this program without adding Zump. What does + // it do and why? + + // Printing all RPG characters in a loop: + for (chars) |c, num| { + std.debug.print("Character {} - G:{} H:{} XP:{}\n", .{ + num + 1, c.gold, c.health, c.experience, + }); + } +} diff --git a/exercises/039_pointers.zig b/exercises/039_pointers.zig new file mode 100644 index 0000000..d545525 --- /dev/null +++ b/exercises/039_pointers.zig @@ -0,0 +1,36 @@ +// +// Check this out: +// +// var foo: u8 = 5; // foo is 5 +// var bar: *u8 = &foo; // bar is a pointer +// +// What is a pointer? It's a reference to a value. In this example +// bar is a reference to the memory space that currently contains the +// value 5. +// +// A cheatsheet given the above declarations: +// +// u8 the type of a u8 value +// foo the value 5 +// *u8 the type of a pointer to a u8 value +// &foo a reference to foo +// bar a pointer to the value at foo +// bar.* the value 5 (the dereferenced value "at" bar) +// +// We'll see why pointers are useful in a moment. For now, see if you +// can make this example work! +// +const std = @import("std"); + +pub fn main() void { + var num1: u8 = 5; + var num1_pointer: *u8 = &num1; + + var num2: u8 = undefined; + + // Please make num2 equal 5 using num1_pointer! + // (See the "cheatsheet" above for ideas.) + num2 = ???; + + std.debug.print("num1: {}, num2: {}\n", .{ num1, num2 }); +} diff --git a/exercises/03_assignment.zig b/exercises/03_assignment.zig deleted file mode 100644 index 6a4364b..0000000 --- a/exercises/03_assignment.zig +++ /dev/null @@ -1,51 +0,0 @@ -// -// It seems we got a little carried away making everything "const u8"! -// -// "const" values cannot change. -// "u" types are "unsigned" and cannot store negative values. -// "8" means the type is 8 bits in size. -// -// Example: foo cannot change (it is CONSTant) -// bar can change (it is VARiable): -// -// const foo: u8 = 20; -// var bar: u8 = 20; -// -// Example: foo cannot be negative and can hold 0 to 255 -// bar CAN be negative and can hold −128 to 127 -// -// const foo: u8 = 20; -// const bar: i8 = -20; -// -// Example: foo can hold 8 bits (0 to 255) -// bar can hold 16 bits (0 to 65,535) -// -// const foo: u8 = 20; -// const bar: u16 = 2000; -// -// You can do just about any combination of these that you can think of: -// -// u32 can hold 0 to 4,294,967,295 -// i64 can hold −9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 -// -// Please fix this program so that the types can hold the desired values -// and the errors go away! -// -const std = @import("std"); - -pub fn main() void { - const n: u8 = 50; - n = n + 5; - - const pi: u8 = 314159; - - const negative_eleven: u8 = -11; - - // There are no errors in the next line, just explanation: - // Perhaps you noticed before that the print function takes two - // parameters. Now it will make more sense: the first parameter - // is a string. The string may contain placeholders '{}', and the - // second parameter is an "anonymous list literal" (don't worry - // about this for now!) with the values to be printed. - std.debug.print("{} {} {}\n", .{ n, pi, negative_eleven }); -} diff --git a/exercises/040_pointers2.zig b/exercises/040_pointers2.zig new file mode 100644 index 0000000..43dd2c3 --- /dev/null +++ b/exercises/040_pointers2.zig @@ -0,0 +1,27 @@ +// +// It's important to note that variable pointers and constant pointers +// are different types. +// +// Given: +// +// var foo: u8 = 5; +// const bar: u8 = 5; +// +// Then: +// +// &foo is of type "*u8" +// &bar is of type "*const u8" +// +// You can always make a constant pointer to a variable, but you cannot +// make a variable pointer to a constant. This sounds like a logic puzzle, +// but it just means that once data is declared immutable, you can't +// coerce it to a mutable type. It's a safety thing (to prevent mistakes). +// +const std = @import("std"); + +pub fn main() void { + const a: u8 = 12; + const b: *u8 = &a; // fix this! + + std.debug.print("a: {}, b: {}\n", .{ a, b.* }); +} diff --git a/exercises/041_pointers3.zig b/exercises/041_pointers3.zig new file mode 100644 index 0000000..9e2bcc6 --- /dev/null +++ b/exercises/041_pointers3.zig @@ -0,0 +1,41 @@ +// +// The tricky part is that the pointer's mutability (var vs const) refers +// to the ability to change what the pointer POINTS TO, not the ability +// to change the VALUE at that location! +// +// const locked: u8 = 5; +// var unlocked: u8 = 10; +// +// const p1: *const u8 = &locked; +// var p2: *const u8 = &locked; +// +// Both p1 and p2 point to constant values which cannot change. However, +// p2 can be changed to point to something else and p1 cannot! +// +// const p3: *u8 = &unlocked; +// var p4: *u8 = &unlocked; +// const p5: *const u8 = &unlocked; +// var p6: *const u8 = &unlocked; +// +// Here p3 and p4 can both be used to change the value they point to but +// p3 cannot point at anything else. +// What's interesting is that p5 and p6 act like p1 and p2, but point to +// the value at "unlocked". This is what we mean when we say that we can +// make a constant reference to any value! +// +const std = @import("std"); + +pub fn main() void { + var foo: u8 = 5; + var bar: u8 = 10; + + // Please define pointer "p" so that it can point to EITHER foo or + // bar AND change the value it points to! + ??? p: ??? = undefined; + + p = &foo; + p.* += 1; + p = &bar; + p.* += 1; + std.debug.print("foo={}, bar={}\n", .{ foo, bar }); +} diff --git a/exercises/042_pointers4.zig b/exercises/042_pointers4.zig new file mode 100644 index 0000000..261dbc1 --- /dev/null +++ b/exercises/042_pointers4.zig @@ -0,0 +1,32 @@ +// +// Now let's use pointers to do something we haven't been +// able to do before: pass a value by reference to a function! +// +const std = @import("std"); + +pub fn main() void { + var num: u8 = 1; + var more_nums = [_]u8{ 1, 1, 1, 1 }; + + // Let's pass a reference to num to our function and print it: + makeFive(&num); + std.debug.print("num: {}, ", .{num}); + + // Now something interesting. Let's pass a reference to a + // specific array value: + makeFive(&more_nums[2]); + + // And print the array: + std.debug.print("more_nums: ", .{}); + for (more_nums) |n| { + std.debug.print("{} ", .{n}); + } + + std.debug.print("\n", .{}); +} + +// This function should take a reference to a u8 value and set it +// to 5. +fn makeFive(x: *u8) void { + ??? = 5; // fix me! +} diff --git a/exercises/043_pointers5.zig b/exercises/043_pointers5.zig new file mode 100644 index 0000000..cb94189 --- /dev/null +++ b/exercises/043_pointers5.zig @@ -0,0 +1,82 @@ +// +// Passing integer pointers around is generally not something you're going +// to do. Integers are cheap to copy. +// +// But you know what IS useful? Pointers to structs: +// +// const Vertex = struct{ x: u32, y: u32, z: u32 }; +// +// var v1 = Vertex{ .x=3, .y=2, .z=5 }; +// +// var pv: *Vertex = &v1; // <-- a pointer to our struct +// +// Note that you don't need to dereference the "pv" pointer to access +// the struct's fields: +// +// YES: pv.x +// NO: pv.*.x +// +// We can write functions that take pointer arguments: +// +// fn foo(v: *Vertex) void { +// v.x += 2; +// v.y += 3; +// v.z += 7; +// } +// +// And pass references to them: +// +// foo(&v1); +// +// +// Let's revisit our RPG example and make a printCharacter() function +// that takes a Character pointer. +// +const std = @import("std"); + +const Class = enum { + wizard, + thief, + bard, + warrior, +}; + +const Character = struct { + class: Class, + gold: u32, + health: u8 = 100, // <--- You can also provide fields a default value! + experience: u32, +}; + +pub fn main() void { + var glorp = Character{ + .class = Class.wizard, + .gold = 10, + .experience = 20, + }; + + // FIX ME! + // Please pass our Character "glorp" to printCharacter(): + printCharacter(???); +} + +// Note how this function's "c" parameter is a pointer to a Character struct. +fn printCharacter(c: *Character) void { + + // Here's something you haven't seen before: when switching an enum, you + // don't have to write the full enum name. Zig understands that ".wizard" + // means "Class.wizard" when we switch on a Class enum value: + const class_name = switch (c.class) { + .wizard => "Wizard", + .thief => "Thief", + .bard => "Bard", + .warrior => "Warrior", + }; + + std.debug.print("{s} (G:{} H:{} XP:{})", .{ + class_name, + c.gold, + c.health, + c.experience, + }); +} diff --git a/exercises/044_quiz5.zig b/exercises/044_quiz5.zig new file mode 100644 index 0000000..8a0d88c --- /dev/null +++ b/exercises/044_quiz5.zig @@ -0,0 +1,47 @@ +// +// "Elephants walking +// Along the trails +// +// Are holding hands +// By holding tails." +// +// from Holding Hands +// by Lenore M. Link +// +const std = @import("std"); + +const Elephant = struct { + letter: u8, + tail: *Elephant = undefined, + visited: bool = false, +}; + +pub fn main() void { + var elephantA = Elephant{ .letter = 'A' }; + // (Please add Elephant B here!) + var elephantC = Elephant{ .letter = 'C' }; + + // Link the elephants so that each tail "points" to the next elephant. + // They make a circle: A->B->C->A... + elephantA.tail = &elephantB; + // (Please link Elephant B's tail to Elephant C here!) + elephantC.tail = &elephantA; + + visitElephants(&elephantA); + + std.debug.print("\n", .{}); +} + +// This function visits all elephants once, starting with the +// first elephant and following the tails to the next elephant. +// If we did not "mark" the elephants as visited (by setting +// visited=true), then this would loop infinitely! +fn visitElephants(first_elephant: *Elephant) void { + var e = first_elephant; + + while (!e.visited) { + std.debug.print("Elephant {u}. ", .{e.letter}); + e.visited = true; + e = e.tail; + } +} diff --git a/exercises/045_optionals.zig b/exercises/045_optionals.zig new file mode 100644 index 0000000..1327e4c --- /dev/null +++ b/exercises/045_optionals.zig @@ -0,0 +1,51 @@ +// +// Sometimes you know that a variable might hold a value or +// it might not. Zig has a neat way of expressing this idea +// called Optionals. An optional type just has a '?' like this: +// +// var foo: ?u32 = 10; +// +// Now foo can store a u32 integer OR null (a value storing +// the cosmic horror of a value NOT EXISTING!) +// +// foo = null; +// +// if (foo == null) beginScreaming(); +// +// Before we can use the optional value as the non-null type +// (a u32 integer in this case), we need to guarantee that it +// isn't null. One way to do this is to THREATEN IT with the +// "orelse" statement. +// +// var bar = foo orelse 2; +// +// Here, bar will either equal the u32 integer value stored in +// foo, or it will equal 2 if foo was null. +// +const std = @import("std"); + +pub fn main() void { + const result = deepThought(); + + // Please threaten the result so that answer is either the + // integer value from deepThought() OR the number 42: + var answer: u8 = result; + + std.debug.print("The Ultimate Answer: {}.\n", .{answer}); +} + +fn deepThought() ?u8 { + // It seems Deep Thought's output has declined in quality. + // But we'll leave this as-is. Sorry Deep Thought. + return null; +} +// Blast from the past: +// +// Optionals are a lot like error union types which can either +// hold a value or an error. Likewise, the orelse statement is +// like the catch statement used to "unwrap" a value or supply +// a default value: +// +// var maybe_bad: Error!u32 = Error.Evil; +// var number: u32 = maybe_bad catch 0; +// diff --git a/exercises/046_optionals2.zig b/exercises/046_optionals2.zig new file mode 100644 index 0000000..d3f65bb --- /dev/null +++ b/exercises/046_optionals2.zig @@ -0,0 +1,58 @@ +// +// Now that we have optional types, we can apply them to structs. +// The last time we checked in with our elephants, we had to link +// all three of them together in a "circle" so that the last tail +// linked to the first elephant. This is because we had NO CONCEPT +// of a tail that didn't point to another elephant! +// +// We also introduce the handy ".?" shortcut: +// +// const foo = bar.?; +// +// is the same as +// +// const foo = bar orelse unreachable; +// +// See if you can find where we use this shortcut below. +// +// Now let's make those elephant tails optional! +// +const std = @import("std"); + +const Elephant = struct { + letter: u8, + tail: *Elephant = null, // Hmm... tail needs something... + visited: bool = false, +}; + +pub fn main() void { + var elephantA = Elephant{ .letter = 'A' }; + var elephantB = Elephant{ .letter = 'B' }; + var elephantC = Elephant{ .letter = 'C' }; + + // Link the elephants so that each tail "points" to the next. + elephantA.tail = &elephantB; + elephantB.tail = &elephantC; + + visitElephants(&elephantA); + + std.debug.print("\n", .{}); +} + +// This function visits all elephants once, starting with the +// first elephant and following the tails to the next elephant. +fn visitElephants(first_elephant: *Elephant) void { + var e = first_elephant; + + while (!e.visited) { + std.debug.print("Elephant {u}. ", .{e.letter}); + e.visited = true; + + // We should stop once we encounter a tail that + // does NOT point to another element. What can + // we put here to make that happen? + if (e.tail == null) ???; + + e = e.tail.?; + } +} diff --git a/exercises/047_methods.zig b/exercises/047_methods.zig new file mode 100644 index 0000000..c8e5c17 --- /dev/null +++ b/exercises/047_methods.zig @@ -0,0 +1,94 @@ +// +// Help! Evil alien creatures have hidden eggs all over the Earth +// and they're starting to hatch! +// +// Before you jump into battle, you'll need to know four things: +// +// 1. You can attach functions to structs: +// +// const Foo = struct{ +// pub fn hello() void { +// std.debug.print("Foo says hello!\n", .{}); +// } +// } +// +// 2. A function that is a member of a struct is a "method" and is +// called with the "dot syntax" like so: +// +// Foo.hello(); +// +// 3. The NEAT feature of methods is the special parameter named +// "self" that takes an instance of that type of struct: +// +// const Bar = struct{ +// number: u32, +// +// pub fn printMe(self: *Bar) void { +// std.debug.print("{}\n", .{self.number}); +// } +// } +// +// 4. Now when you call the method on an INSTANCE of that struct +// with the "dot syntax", the instance will be automatically +// passed as the "self" parameter: +// +// const my_bar = Bar{ .number = 2000 }; +// my_bar.printMe(); // prints "2000" +// +// Okay, you're armed. +// +// Now, please zap the alien structs until they're all gone or +// Earth will be doomed! +// +const std = @import("std"); + +// Look at this hideous Alien struct. Know your enemy! +const Alien = struct { + health: u8, + + // We hate this method: + pub fn hatch(strength: u8) Alien { + return Alien{ + .health = strength * 5, + }; + } + + // We love this method: + pub fn zap(self: *Alien, damage: u8) void { + self.health -= if (damage >= self.health) self.health else damage; + } +}; + +pub fn main() void { + // Look at all of these aliens of various strengths! + var aliens = [_]Alien{ + Alien.hatch(2), + Alien.hatch(1), + Alien.hatch(3), + Alien.hatch(3), + Alien.hatch(5), + Alien.hatch(3), + }; + + var aliens_alive = aliens.len; + var heat_ray_strength: u8 = 7; // We've been given a heat ray weapon. + + // We'll keep checking to see if we've killed all the aliens yet. + while (aliens_alive > 0) { + aliens_alive = 0; + + // Loop through every alien... + for (aliens) |*alien| { + + // *** Zap the Alien Here! *** + ???.zap(heat_ray_strength); + + // If the alien's health is still above 0, it's still alive. + if (alien.health > 0) aliens_alive += 1; + } + + std.debug.print("{} aliens. ", .{aliens_alive}); + } + + std.debug.print("Earth is saved!\n", .{}); +} diff --git a/exercises/048_methods2.zig b/exercises/048_methods2.zig new file mode 100644 index 0000000..f97710d --- /dev/null +++ b/exercises/048_methods2.zig @@ -0,0 +1,71 @@ +// +// Now that we've seen how methods work, let's see if we can help +// our elephants out a bit more with some Elephant methods. +// +const std = @import("std"); + +const Elephant = struct { + letter: u8, + tail: ?*Elephant = null, + visited: bool = false, + + // New Elephant methods! + pub fn getTail(self: *Elephant) *Elephant { + return self.tail.?; // Remember, this is means "orelse unreachable" + } + + pub fn hasTail(self: *Elephant) bool { + return (self.tail != null); + } + + pub fn visit(self: *Elephant) void { + self.visited = true; + } + + pub fn print(self: *Elephant) void { + // Prints elephant letter and [v]isited + var v: u8 = if (self.visited) 'v' else ' '; + std.debug.print("{u}{u} ", .{ self.letter, v }); + } +}; + +pub fn main() void { + var elephantA = Elephant{ .letter = 'A' }; + var elephantB = Elephant{ .letter = 'B' }; + var elephantC = Elephant{ .letter = 'C' }; + + // Link the elephants so that each tail "points" to the next. + elephantA.tail = &elephantB; + elephantB.tail = &elephantC; + + visitElephants(&elephantA); + + std.debug.print("\n", .{}); +} + +// This function visits all elephants once, starting with the +// first elephant and following the tails to the next elephant. +fn visitElephants(first_elephant: *Elephant) void { + var e = first_elephant; + + while (true) { + e.print(); + e.visit(); + + // Get the next elephant or stop. + if (e.hasTail()) { + e = e.???; // Which method do we want here? + } else { + break; + } + } +} + +// Bonus: Zig's enums can also have methods! Can you find +// one in the wild? If you can, mention it along with your +// name or alias in a comment below this one and make a +// pull request on GitHub for a piece of eternal Ziglings +// glory. The first five (5) PRs will be accepted! +// +// 1) drforester - I found one in the Zig source: +// https://github.com/ziglang/zig/blob/041212a41cfaf029dc3eb9740467b721c76f406c/src/Compilation.zig#L2495 diff --git a/exercises/049_quiz6.zig b/exercises/049_quiz6.zig new file mode 100644 index 0000000..a1a1dec --- /dev/null +++ b/exercises/049_quiz6.zig @@ -0,0 +1,91 @@ +// +// "Trunks and tails +// Are handy things" + +// from Holding Hands +// by Lenore M. Link +// +// Now that we have tails all figured out, can you implement trunks? +// +const std = @import("std"); + +const Elephant = struct { + letter: u8, + tail: ?*Elephant = null, + trunk: ?*Elephant = null, + visited: bool = false, + + // Elephant tail methods! + pub fn getTail(self: *Elephant) *Elephant { + return self.tail.?; // Remember, this is means "orelse unreachable" + } + + pub fn hasTail(self: *Elephant) bool { + return (self.tail != null); + } + + // Your Elephant trunk methods go here! + // --------------------------------------------------- + + ??? + + // --------------------------------------------------- + + pub fn visit(self: *Elephant) void { + self.visited = true; + } + + pub fn print(self: *Elephant) void { + // Prints elephant letter and [v]isited + var v: u8 = if (self.visited) 'v' else ' '; + std.debug.print("{u}{u} ", .{ self.letter, v }); + } +}; + +pub fn main() void { + var elephantA = Elephant{ .letter = 'A' }; + var elephantB = Elephant{ .letter = 'B' }; + var elephantC = Elephant{ .letter = 'C' }; + + // Link the elephants so that each tail "points" to the next. + elephantA.tail = &elephantB; + elephantB.tail = &elephantC; + + // And link the elephants so that each trunk "points" to the previous. + elephantB.trunk = &elephantA; + elephantC.trunk = &elephantB; + + visitElephants(&elephantA); + + std.debug.print("\n", .{}); +} + +// This function visits all elephants twice, tails to trunks. +fn visitElephants(first_elephant: *Elephant) void { + var e = first_elephant; + + // Follow the tails! + while (true) { + e.print(); + e.visit(); + + // Get the next elephant or stop. + if (e.hasTail()) { + e = e.getTail(); + } else { + break; + } + } + + // Follow the trunks! + while (true) { + e.print(); + + // Get the previous elephant or stop. + if (e.hasTrunk()) { + e = e.getTrunk(); + } else { + break; + } + } +} diff --git a/exercises/04_arrays.zig b/exercises/04_arrays.zig deleted file mode 100644 index 88fcc78..0000000 --- a/exercises/04_arrays.zig +++ /dev/null @@ -1,52 +0,0 @@ -// -// Let's learn some array basics. Arrays are declared with: -// -// var foo: [3]u32 = [3]u32{ 42, 108, 5423 }; -// -// When Zig can infer the size of the array, you can use '_' for the -// size. You can also let Zig infer the type of the value so the -// declaration is much less verbose. -// -// var foo = [_]u32{ 42, 108, 5423 }; -// -// Get values of an array using array[index] notation: -// -// const bar = foo[2]; // 5423 -// -// Set values of an array using array[index] notation: -// -// foo[2] = 16; -// -// Get the length of an array using the len property: -// -// const length = foo.len; -// -const std = @import("std"); - -pub fn main() void { - // (Problem 1) - // This "const" is going to cause a problem later - can you see what it is? - // How do we fix it? - const some_primes = [_]u8{ 1, 3, 5, 7, 11, 13, 17, 19 }; - - // Individual values can be set with '[]' notation. - // Example: This line changes the first prime to 2 (which is correct): - some_primes[0] = 2; - - // Individual values can also be accessed with '[]' notation. - // Example: This line stores the first prime in "first": - const first = some_primes[0]; - - // (Problem 2) - // Looks like we need to complete this expression. Use the example - // above to set "fourth" to the fourth element of the some_primes array: - const fourth = some_primes[???]; - - // (Problem 3) - // Use the len property to get the length of the array: - const length = some_primes.???; - - std.debug.print("First: {}, Fourth: {}, Length: {}\n", .{ - first, fourth, length, - }); -} diff --git a/exercises/050_no_value.zig b/exercises/050_no_value.zig new file mode 100644 index 0000000..8708d2d --- /dev/null +++ b/exercises/050_no_value.zig @@ -0,0 +1,84 @@ +// +// "We live on a placid island of ignorance in the midst +// of black seas of infinity, and it was not meant that +// we should voyage far." +// +// from The Call of Cthulhu +// by H. P. Lovecraft +// +// Zig has at least four ways of expressing "no value": +// +// * undefined +// +// var foo: u8 = undefined; +// +// "undefined" should not be thought of as a value, but as a way +// of telling the compiler that you are not assigning a value +// _yet_. Any type may be set to undefined, but attempting +// to read or use that value is _always_ a mistake. +// +// * null +// +// var foo: ?u8 = null; +// +// The "null" primitive value _is_ a value that means "no value". +// This is typically used with optional types as with the ?u8 +// shown above. When foo equals null, that's not a value of type +// u8. It means there is _no value_ of type u8 in foo at all! +// +// * error +// +// var foo: MyError!u8 = BadError; +// +// Errors are _very_ similar to nulls. They _are_ a value, but +// they usually indicate that the "real value" you were looking +// for does not exist. Instead, you have an error. The example +// error union type of MyError!u8 means that foo either holds +// a u8 value OR an error. There is _no value_ of type u8 in foo +// when it's set to an error! +// +// * void +// +// var foo: void = {}; +// +// "void" is a _type_, not a value. It is the most popular of the +// Zero Bit Types (those types which take up absolutely no space +// and have only a semantic value. When compiled to executable +// code, zero bit types generate no code at all. The above example +// shows a variable foo of type void which is assigned the value +// of an empty expression. It's much more common to see void as +// the return type of a function that returns nothing. +// +// Zig has all of these ways of expressing different types of "no value" +// because they each serve a purpose. Briefly: +// +// * undefined - there is no value YET, this cannot be read YET +// * null - there is an explicit value of "no value" +// * errors - there is no value because something went wrong +// * void - there will NEVER be a value stored here +// +// Please use the correct "no value" for each ??? to make this program +// print out a cursed quote from the Necronomicon. ...If you dare. +// +const std = @import("std"); + +const Err = error{Cthulhu}; + +pub fn main() void { + var first_line1: *const [16]u8 = ???; + first_line1 = "That is not dead"; + + var first_line2: Err!*const [21]u8 = ???; + first_line2 = "which can eternal lie"; + + std.debug.print("{s} {s} / ", .{ first_line1, first_line2 }); + + printSecondLine(); +} + +fn printSecondLine() ??? { + var second_line2: ?*const [18]u8 = ???; + second_line2 = "even death may die"; + + std.debug.print("And with strange aeons {s}.\n", .{second_line2.?}); +} diff --git a/exercises/051_values.zig b/exercises/051_values.zig new file mode 100644 index 0000000..dd68d3b --- /dev/null +++ b/exercises/051_values.zig @@ -0,0 +1,187 @@ +// +// If you thought the last exercise was a deep dive, hold onto your +// hat because we are about to descend into the computer's molten +// core. +// +// (Shouting) DOWN HERE, THE BITS AND BYTES FLOW FROM RAM TO THE CPU +// LIKE A HOT, DENSE FLUID. THE FORCES ARE INCREDIBLE. BUT HOW DOES +// ALL OF THIS RELATE TO THE DATA IN OUR ZIG PROGRAMS? LET'S HEAD +// BACK UP TO THE TEXT EDITOR AND FIND OUT. +// +// Ah, that's better. Now we can look at some familiar Zig code. +// +// @import() adds the imported code to your own. In this case, code +// from the standard library is added to your program and compiled +// with it. All of this will be loaded into RAM when it runs. Oh, and +// that thing we name "const std"? That's a struct! + +const std = @import("std"); + +// Remember our old RPG Character struct? A struct is really just a +// very convenient way to deal with memory. These fields (gold, +// health, experience) are all values of a particular size. Add them +// together and you have the size of the struct as a whole. + +const Character = struct { + gold: u32 = 0, + health: u8 = 100, + experience: u32 = 0, +}; + +// Here we create a character called "the_narrator" that is a constant +// (immutable) instance of a Character struct. It is stored in your +// program as data, and like the instruction code, it is loaded into +// RAM when your program runs. The relative location of this data in +// memory is hard-coded and neither the address nor the value changes. + +const the_narrator = Character{ + .gold = 12, + .health = 99, + .experience = 9000, +}; + +// This "global_wizard" character is very similar. The address for +// this data won't change, but the data itself can since this is a var +// and not a const. + +var global_wizard = Character{}; + +// A function is instruction code at a particular address. Function +// parameters in Zig are always immutable. They are stored in "the +// stack". A stack is a type of data structure and "the stack" is a +// specific bit of RAM reserved for your program. The CPU has special +// support for adding and removing things from "the stack", so it is +// an extremely efficient place for memory storage. +// +// Also, when a function executes, the input arguments are often +// loaded into the beating heart of the CPU itself in registers. +// +// Our main() function here has no input parameters, but it will have +// a stack entry (called a "frame"). + +pub fn main() void { + + // Here, the "glorp" character will be allocated on the stack + // because each instance of glorp is mutable and therefore unique + // to the invocation of this function. + + var glorp = Character{ + .gold = 30, + }; + + // However, this "skull_farmer" character will be put in the + // global immutable data even though it's defined in a function. + // Since it's immutable, all invocations of the function can share + // this one value. + + const skull_farmer = Character{}; + + // The "reward_xp" value is interesting. It's a constant value, so + // it could go with other global data. But being such a small + // value, it may also simply be inlined as a literal value in your + // instruction code where it is used. It's up to the compiler. + + const reward_xp: u32 = 200; + + // Now let's circle back around to that "std" struct we imported + // at the top. Since it's just a regular Zig value once it's + // imported, we can also assign new names for its fields and + // declarations. "debug" refers to another struct and "print" is a + // public function namespaced within THAT struct. + // + // Let's assign the std.debug.print function to a const named + // "print" so that we can use this new name later! + + const print = ???; + + // Now let's look at assigning and pointing to values in Zig. + // + // We'll try three different ways of making a new name to access + // our glorp Character and change one of its values. + // + // "glorp_access1" is incorrectly named! We asked Zig to set aside + // memory for another Character struct. So when we assign glorp to + // glorp_access1 here, we're actually assigning all of the fields + // to make a copy! Now we have two separate characters. + // + // You don't need to fix this. But notice what gets printed in + // your program's output for this one compared to the other two + // assignments below! + + var glorp_access1: Character = glorp; + glorp_access1.gold = 111; + print("1:{}!. ", .{glorp.gold == glorp_access1.gold}); + + // NOTE: + // + // If we tried to do this with a const Character instead of a + // var, changing the gold field would give us a compiler error + // because const values are immutable! + // + // "glorp_access2" will do what we want. It points to the original + // glorp's address. Also remember that we get one implicit + // dereference with struct fields, so accessing the "gold" field + // from glorp_access2 looks just like accessing it from glorp + // itself. + + var glorp_access2: *Character = &glorp; + glorp_access2.gold = 222; + print("2:{}!. ", .{glorp.gold == glorp_access2.gold}); + + // "glorp_access3" is interesting. It's also a pointer, but it's a + // const. Won't that disallow changing the gold value? No! As you + // may recall from our earlier pointer experiments, a constant + // pointer can't change what it's POINTING AT, but the value at + // the address it points to is still mutable! So we CAN change it. + + const glorp_access3: *Character = &glorp; + glorp_access3.gold = 333; + print("3:{}!. ", .{glorp.gold == glorp_access3.gold}); + + // NOTE: + // + // If we tried to do this with a *const Character pointer, + // that would NOT work and we would get a compiler error + // because the VALUE becomes immutable! + // + // Moving along... + // + // Passing arguments to functions is pretty much exactly like + // making an assignment to a const (since Zig enforces that ALL + // function parameters are const). + // + // Knowing that, see if you can make levelUp() work as expected - + // it should add the specified amount to the supplied character's + // experience points: + + print("XP before:{}, ", .{glorp.experience}); + + levelUp(glorp, reward_xp); + + print("after:{}.\n", .{glorp.experience}); +} + +fn levelUp(character_access: Character, xp: u32) void { + character_access.experience += xp; +} + +// And there's more! +// +// Data segments (allocated at compile time) and "the stack" +// (allocated at run time) aren't the only places where program data +// can be stored in memory. They're just the most efficient. Sometimes +// we don't know how much memory our program will need until the +// program is running. Also, there is a limit to the size of stack +// memory allotted to programs (often set by your operating system). +// For these occasions, we have "the heap". +// +// You can use as much heap memory as you like (within physical +// limitations, of course), but it's much less efficient to manage +// because there is no built-in CPU support for adding and removing +// items as we have with the stack. Also, depending on the type of +// allocation, your program MAY have to do expensive work to manage +// the use of heap memory. We'll learn about heap allocators later. +// +// Whew! This has been a lot of information. You'll be pleased to know +// that the next exercise gets us back to learning Zig language +// features we can use right away to do more things! diff --git a/exercises/052_slices.zig b/exercises/052_slices.zig new file mode 100644 index 0000000..98177cd --- /dev/null +++ b/exercises/052_slices.zig @@ -0,0 +1,49 @@ +// +// We've seen that passing arrays around can be awkward. Perhaps you +// remember a particularly horrendous function definition from quiz3? +// This function can only take arrays that are exactly 4 items long! +// +// fn printPowersOfTwo(numbers: [4]u16) void { ... } +// +// That's the trouble with arrays - their size is part of the data +// type and must be hard-coded into every usage of that type. This +// digits array is a [10]u8 forever and ever: +// +// var digits = [10]u8{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; +// +// Thankfully, Zig has slices, which let you dynamically point to a +// start item and provide a length. Here are slices of our digit +// array: +// +// const foo = digits[0..1]; // 0 +// const bar = digits[3..9]; // 3 4 5 6 7 8 +// const all = digits[0..]; // 0 1 2 3 4 5 6 7 8 9 +// +// As you can see, a slice [x..y] defines a first item by index x and +// a length y (where y-1 is the index of the last item). Leaving y off +// gives you the rest of the items. +// +// Notice that the type of a slice on an array of u8 items is []u8. +// +const std = @import("std"); + +pub fn main() void { + var cards = [8]u8{ 'A', '4', 'K', '8', '5', '2', 'Q', 'J' }; + + // Please put the first 4 cards in hand1 and the rest in hand2. + const hand1: []u8 = cards[???]; + const hand2: []u8 = cards[???]; + + std.debug.print("Hand1: ", .{}); + printHand(hand1); + + std.debug.print("Hand2: ", .{}); + printHand(hand2); +} + +// Please lend this function a hand. A u8 slice hand, that is. +fn printHand(hand: ???) void { + for (hand) |h| { + std.debug.print("{u} ", .{h}); + } +} diff --git a/exercises/053_slices2.zig b/exercises/053_slices2.zig new file mode 100644 index 0000000..2456d86 --- /dev/null +++ b/exercises/053_slices2.zig @@ -0,0 +1,35 @@ +// +// You are perhaps tempted to try slices on strings? They're arrays of +// u8 characters after all, right? Slices on strings work great. +// There's just one catch: don't forget that Zig string literals are +// immutable (const) values. So we need to change the type of slice +// from: +// +// var foo: []u8 = "foobar"[0..3]; +// +// to: +// +// var foo: []const u8 = "foobar"[0..3]; +// +// See if you can fix this Zero Wing-inspired phrase descrambler: +const std = @import("std"); + +pub fn main() void { + const scrambled = "great base for all your justice are belong to us"; + + const base1: []u8 = scrambled[15..23]; + const base2: []u8 = scrambled[6..10]; + const base3: []u8 = scrambled[32..]; + printPhrase(base1, base2, base3); + + const justice1: []u8 = scrambled[11..14]; + const justice2: []u8 = scrambled[0..5]; + const justice3: []u8 = scrambled[24..31]; + printPhrase(justice1, justice2, justice3); + + std.debug.print("\n", .{}); +} + +fn printPhrase(part1: []u8, part2: []u8, part3: []u8) void { + std.debug.print("'{s} {s} {s}.' ", .{part1, part2, part3}); +} diff --git a/exercises/054_manypointers.zig b/exercises/054_manypointers.zig new file mode 100644 index 0000000..23f2ae5 --- /dev/null +++ b/exercises/054_manypointers.zig @@ -0,0 +1,55 @@ +// +// You can also make pointers to multiple items without using a slice. +// +// var foo: [4]u8 = [4]u8{ 1, 2, 3, 4 }; +// var foo_slice: []u8 = foo[0..]; +// var foo_ptr: [*]u8 = &foo; +// +// The difference between foo_slice and foo_ptr is that the slice has +// a known length. The pointer doesn't. It is up to YOU to keep track +// of the number of u8s foo_ptr points to! +// +const std = @import("std"); + +pub fn main() void { + // Take a good look at the array type to which we're coercing + // the zen12 string (the REAL nature of strings will be + // revealed when we've learned some additional features): + const zen12: *const [21]u8 = "Memory is a resource."; + // + // It would also have been valid to coerce to a slice: + // const zen12: []const u8 = "..."; + // + // Now let's turn this into a "many pointer": + const zen_manyptr: [*]const u8 = zen12; + + // It's okay to access zen_manyptr just like an array or slice as + // long as you keep track of the length yourself! + // + // A "string" in Zig is a pointer to an array of const u8 values + // or a slice of const u8 values, into one, as we saw above). So, + // we could treat a "many pointer" of const u8 a string as long + // as we can CONVERT IT TO A SLICE. (Hint: we do know the length!) + // + // Please fix this line so the print below statement can print it: + const zen12_string: []const u8 = zen_manyptr; + + // Here's the moment of truth! + std.debug.print("{s}\n", .{zen12_string}); +} +// +// Are all of these pointer types starting to get confusing? +// +// FREE ZIG POINTER CHEATSHEET! (Using u8 as the example type.) +// +---------------+----------------------------------------------+ +// | u8 | one u8 | +// | *u8 | pointer to one u8 | +// | [2]u8 | two u8s | +// | [*]u8 | pointer to unknown number of u8s | +// | [2]const u8 | two immutable u8s | +// | [*]const u8 | pointer to unknown number of immutable u8s | +// | *[2]u8 | pointer to an array of 2 u8s | +// | *const [2]u8 | pointer to an immutable array of 2 u8s | +// | []u8 | slice of u8s | +// | []const u8 | slice of immutable u8s | +// +---------------+----------------------------------------------+ diff --git a/exercises/055_unions.zig b/exercises/055_unions.zig new file mode 100644 index 0000000..5e08aa1 --- /dev/null +++ b/exercises/055_unions.zig @@ -0,0 +1,76 @@ +// +// A union lets you store different types and sizes of data at +// the same memory address. How is this possible? The compiler +// sets aside enough memory for the largest thing you might want +// to store. +// +// In this example, an instance of Foo always takes up u64 of +// space memory even if you're currently storing a u8. +// +// const Foo = union { +// small: u8, +// medium: u32, +// large: u64, +// }; +// +// The syntax looks just like a struct, but a Foo can only hold a +// small OR a medium OR a large value. Once a field becomes +// active, the other inactive fields cannot be accessed. To +// change active fields, assign a whole new instance: +// +// var f = Foo{ .small = 5 }; +// f.small += 5; // OKAY +// f.medium = 5432; // ERROR! +// f = Foo{ .medium = 5432 }; // OKAY +// +// Unions can save space in memory because they let you "re-use" +// a space in memory. They also provide a sort of primitive +// polymorphism. Here fooBar() can take a Foo no matter what size +// of unsigned integer it holds: +// +// fn fooBar(f: Foo) void { ... } +// +// Oh, but how does fooBar() know which field is active? Zig has +// a neat way of keeping track, but for now, we'll just have to +// do it manually. +// +// Let's see if we can get this program working! +// +const std = @import("std"); + +// We've just started writing a simple ecosystem simulation. +// Insects will be represented by either bees or ants. Bees store +// the number of flowers they've visited that day and ants just +// store whether or not they're still alive. +const Insect = union { + flowers_visited: u16, + still_alive: bool, +}; + +// Since we need to specify the type of insect, we'll use an +// enum (remember those?). +const AntOrBee = enum { a, b }; + +pub fn main() void { + // We'll just make one bee and one ant to test them out: + var ant = Insect{ .still_alive = true }; + var bee = Insect{ .flowers_visited = 15 }; + + std.debug.print("Insect report! ", .{}); + + // Oops! We've made a mistake here. + printInsect(ant, AntOrBee.c); + printInsect(bee, AntOrBee.c); + + std.debug.print("\n", .{}); +} + +// Eccentric Doctor Zoraptera says that we can only use one +// function to print our insects. Doctor Z is small and sometimes +// inscrutable but we do not question her. +fn printInsect(insect: Insect, what_it_is: AntOrBee) void { + switch (what_it_is) { + .a => std.debug.print("Ant alive is: {}. ", .{insect.still_alive}), + .b => std.debug.print("Bee visited {} flowers. ", .{insect.flowers_visited}), + } +} diff --git a/exercises/056_unions2.zig b/exercises/056_unions2.zig new file mode 100644 index 0000000..e4294db --- /dev/null +++ b/exercises/056_unions2.zig @@ -0,0 +1,64 @@ +// +// It is really quite inconvenient having to manually keep track +// of the active field in our union, isn't it? +// +// Thankfully, Zig also has "tagged unions", which allow us to +// store an enum value within our union representing which field +// is active. +// +// const FooTag = enum{ small, medium, large }; +// +// const Foo = union(FooTag) { +// small: u8, +// medium: u32, +// large: u64, +// }; +// +// Now we can use a switch directly on the union to act on the +// active field: +// +// var f = Foo{ .small = 10 }; +// +// switch (f) { +// .small => |my_small| do_something(my_small), +// .medium => |my_medium| do_something(my_medium), +// .large => |my_large| do_something(my_large), +// } +// +// Let's make our Insects use a tagged union (Doctor Zoraptera +// approves). +// +const std = @import("std"); + +const InsectStat = enum { flowers_visited, still_alive }; + +const Insect = union(InsectStat) { + flowers_visited: u16, + still_alive: bool, +}; + +pub fn main() void { + var ant = Insect{ .still_alive = true }; + var bee = Insect{ .flowers_visited = 16 }; + + std.debug.print("Insect report! ", .{}); + + // Could it really be as simple as just passing the union? + printInsect(???); + printInsect(???); + + std.debug.print("\n", .{}); +} + +fn printInsect(insect: Insect) void { + switch (???) { + .still_alive => |a| std.debug.print("Ant alive is: {}. ", .{a}), + .flowers_visited => |f| std.debug.print("Bee visited {} flowers. ", .{f}), + } +} + +// By the way, did unions remind you of optional values and errors? +// Optional values are basically "null unions" and errors use "error +// union types". Now we can add our own unions to the mix to handle +// whatever situations we might encounter: +// union(Tag) { value: u32, toxic_ooze: void } diff --git a/exercises/057_unions3.zig b/exercises/057_unions3.zig new file mode 100644 index 0000000..142180f --- /dev/null +++ b/exercises/057_unions3.zig @@ -0,0 +1,54 @@ +// +// With tagged unions, it gets EVEN BETTER! If you don't have a +// need for a separate enum, you can define an inferred enum with +// your union all in one place. Just use the 'enum' keyword in +// place of the tag type: +// +// const Foo = union(enum) { +// small: u8, +// medium: u32, +// large: u64, +// }; +// +// Let's convert Insect. Doctor Zoraptera has already deleted the +// explicit InsectStat enum for you! +// +const std = @import("std"); + +const Insect = union(InsectStat) { + flowers_visited: u16, + still_alive: bool, +}; + +pub fn main() void { + var ant = Insect{ .still_alive = true }; + var bee = Insect{ .flowers_visited = 17 }; + + std.debug.print("Insect report! ", .{}); + + printInsect(ant); + printInsect(bee); + + std.debug.print("\n", .{}); +} + +fn printInsect(insect: Insect) void { + switch (insect) { + .still_alive => |a| std.debug.print("Ant alive is: {}. ", .{a}), + .flowers_visited => |f| std.debug.print("Bee visited {} flowers. ", .{f}), + } +} + +// Inferred enums are neat, representing the tip of the iceberg +// in the relationship between enums and unions. You can actually +// coerce a union TO an enum (which gives you the active field +// from the union as an enum). What's even wilder is that you can +// coerce an enum to a union! But don't get too excited, that +// only works when the union type is one of those weird zero-bit +// types like void! +// +// Tagged unions, as with most ideas in computer science, have a +// long history going back to the 1960s. However, they're only +// recently becoming mainstream, particularly in system-level +// programming languages. You might have also seen them called +// "variants", "sum types", or even "enums"! diff --git a/exercises/05_arrays2.zig b/exercises/05_arrays2.zig deleted file mode 100644 index 9282a31..0000000 --- a/exercises/05_arrays2.zig +++ /dev/null @@ -1,47 +0,0 @@ -// -// Zig has some fun array operators. -// -// You can use '++' to concatenate two arrays: -// -// const a = [_]u8{ 1,2 }; -// const b = [_]u8{ 3,4 }; -// const c = a ++ b ++ [_]u8{ 5 }; // equals 1 2 3 4 5 -// -// You can use '**' to repeat an array: -// -// const d = [_]u8{ 1,2,3 } ** 2; // equals 1 2 3 1 2 3 -// -const std = @import("std"); - -pub fn main() void { - const le = [_]u8{ 1, 3 }; - const et = [_]u8{ 3, 7 }; - - // (Problem 1) - // Please set this array concatenating the two arrays above. - // It should result in: 1 3 3 7 - const leet = ???; - - // (Problem 2) - // Please set this array to using repetition. - // It should result in: 1 0 0 1 1 0 0 1 1 0 0 1 - const bit_pattern = [_]u8{ ??? } ** 3; - - // Okay, that's all of the problems. Let's see the results. - // - // We could print these arrays with leet[0], leet[1],...but let's - // have a little preview of Zig "for" loops instead: - std.debug.print("LEET: ", .{}); - - for (leet) |*n| { - std.debug.print("{}", .{n.*}); - } - - std.debug.print(", Bits: ", .{}); - - for (bit_pattern) |*n| { - std.debug.print("{}", .{n.*}); - } - - std.debug.print("\n", .{}); -} diff --git a/exercises/06_strings.zig b/exercises/06_strings.zig deleted file mode 100644 index 6258816..0000000 --- a/exercises/06_strings.zig +++ /dev/null @@ -1,48 +0,0 @@ -// -// Now that we've learned about arrays, we can talk about strings. -// -// We've already seen Zig string literals: "Hello world.\n" -// -// Zig stores strings as arrays of bytes. -// -// const foo = "Hello"; -// -// Is the same as: -// -// const foo = [_]u8{ 'H', 'e', 'l', 'l', 'o' }; -// -const std = @import("std"); - -pub fn main() void { - const ziggy = "stardust"; - - // (Problem 1) - // Use array square bracket syntax to get the letter 'd' from - // the string "stardust" above. - const d: u8 = ziggy[???]; - - // (Problem 2) - // Use the array repeat '**' operator to make "ha ha ha ". - const laugh = "ha " ???; - - // (Problem 3) - // Use the array concatenation '++' operator to make "Major Tom". - // (You'll need to add a space as well!) - const major = "Major"; - const tom = "Tom"; - const major_tom = major ??? tom; - - // That's all the problems. Let's see our results: - std.debug.print("d={u} {s}{s}\n", .{ d, laugh, major_tom }); - // Keen eyes will notice that we've put 'u' and 's' inside the '{}' - // placeholders in the format string above. This tells the - // print() function to format the values as a UTF-8 character and - // UTF-8 strings respectively. If we didn't do this, we'd see '100', - // which is the decimal number corresponding with the 'd' character - // in UTF-8. (And an error in the case of the strings.) - // - // While we're on this subject, 'c' (ASCII encoded character) - // would work in place for 'u' because the first 128 characters - // of UTF-8 are the same as ASCII! - // -} diff --git a/exercises/07_strings2.zig b/exercises/07_strings2.zig deleted file mode 100644 index 6350be1..0000000 --- a/exercises/07_strings2.zig +++ /dev/null @@ -1,24 +0,0 @@ -// -// Here's a fun one: Zig has multi-line strings! -// -// To make a multi-line string, put '\\' at the beginning of each -// line just like a code comment but with backslashes instead: -// -// const two_lines = -// \\Line One -// \\Line Two -// ; -// -// See if you can make this program print some song lyrics. -// -const std = @import("std"); - -pub fn main() void { - const lyrics = - Ziggy played guitar - Jamming good with Andrew Kelley - And the Spiders from Mars - ; - - std.debug.print("{s}\n", .{lyrics}); -} diff --git a/exercises/08_quiz.zig b/exercises/08_quiz.zig deleted file mode 100644 index eda66b8..0000000 --- a/exercises/08_quiz.zig +++ /dev/null @@ -1,34 +0,0 @@ -// -// Quiz time! Let's see if you can fix this whole program. -// -// This is meant to be challenging. -// -// Let the compiler tell you what's wrong. -// -// Start at the top. -// -const std = @import("std"); - -pub fn main() void { - // What is this nonsense? :-) - const letters = "YZhifg"; - - const x: u8 = 1; - - // This is something you haven't seen before: declaring an array - // without putting anything in it. There is no error here: - var lang: [3]u8 = undefined; - - // The following lines attempt to put 'Z', 'i', and 'g' into the - // 'lang' array we just created. - lang[0] = letters[x]; - - x = 3; - lang[???] = letters[x]; - - x = ???; - lang[2] = letters[???]; - - // We want to "Program in Zig!" of course: - std.debug.print("Program in {s}!\n", .{lang}); -} diff --git a/exercises/09_if.zig b/exercises/09_if.zig deleted file mode 100644 index 284563d..0000000 --- a/exercises/09_if.zig +++ /dev/null @@ -1,32 +0,0 @@ -// -// Now we get into the fun stuff, starting with the 'if' statement! -// -// if (true) { -// ... -// } else { -// ... -// } -// -// Zig has the "usual" comparison operators such as: -// -// a == b means "a equals b" -// a < b means "a is less than b" -// a != b means "a does not equal b" -// -// The important thing about Zig's "if" is that it *only* accepts -// boolean values. It won't coerce numbers or other types of data -// to true and false. -// -const std = @import("std"); - -pub fn main() void { - const foo = 1; - - // Please fix this condition: - if (foo) { - // We want our program to print this message! - std.debug.print("Foo is 1!\n", .{}); - } else { - std.debug.print("Foo is not 1!\n", .{}); - } -} diff --git a/exercises/10_if2.zig b/exercises/10_if2.zig deleted file mode 100644 index d0c8cac..0000000 --- a/exercises/10_if2.zig +++ /dev/null @@ -1,16 +0,0 @@ -// -// If statements are also valid expressions: -// -// var foo: u8 = if (a) 2 else 3; -// -const std = @import("std"); - -pub fn main() void { - var discount = true; - - // Please use an if...else expression to set "price". - // If discount is true, the price should be $17, otherwise $20: - var price: u8 = if ???; - - std.debug.print("With the discount, the price is ${}.\n", .{price}); -} diff --git a/exercises/11_while.zig b/exercises/11_while.zig deleted file mode 100644 index 674d904..0000000 --- a/exercises/11_while.zig +++ /dev/null @@ -1,34 +0,0 @@ -// -// Zig 'while' statements create a loop that runs while the -// condition is true. This runs once (at most): -// -// while (condition) { -// condition = false; -// } -// -// Remember that the condition must be a boolean value and -// that we can get a boolean value from conditional operators -// such as: -// -// a == b means "a equals b" -// a < b means "a is less than b" -// a > b means "a is greater than b" -// a != b means "a does not equal b" -// -const std = @import("std"); - -pub fn main() void { - var n: u32 = 2; - - // Please use a condition that is true UNTIL "n" reaches 1024: - while (???) { - // Print the current number - std.debug.print("{} ", .{n}); - - // Set n to n multiplied by 2 - n *= 2; - } - - // Once the above is correct, this will print "n=1024" - std.debug.print("n={}\n", .{n}); -} diff --git a/exercises/12_while2.zig b/exercises/12_while2.zig deleted file mode 100644 index ef53ea0..0000000 --- a/exercises/12_while2.zig +++ /dev/null @@ -1,35 +0,0 @@ -// -// Zig 'while' statements can have an optional 'continue expression' -// which runs every time the while loop continues (either at the -// end of the loop or when an explicit 'continue' is invoked - we'll -// try those out next): -// -// while (condition) : (continue expression) { -// ... -// } -// -// Example: -// -// var foo = 2; -// while (foo<10) : (foo+=2) { -// // Do something with even numbers less than 10... -// } -// -// See if you can re-write the last exercise using a continue -// expression: -// -const std = @import("std"); - -pub fn main() void { - var n: u32 = 2; - - // Please set the continue expression so that we get the desired - // results in the print statement below. - while (n < 1000) : ??? { - // Print the current number - std.debug.print("{} ", .{n}); - } - - // As in the last exercise, we want this to result in "n=1024" - std.debug.print("n={}\n", .{n}); -} diff --git a/exercises/13_while3.zig b/exercises/13_while3.zig deleted file mode 100644 index 4cccf62..0000000 --- a/exercises/13_while3.zig +++ /dev/null @@ -1,33 +0,0 @@ -// -// The last two exercises were functionally identical. Continue -// expressions really show their utility when used with 'continue' -// statements! -// -// Example: -// -// while (condition) : (continue expression) { -// -// if (other condition) continue; -// -// } -// -// The "continue expression" executes every time the loop restarts -// whether the "continue" statement happens or not. -// -const std = @import("std"); - -pub fn main() void { - var n: u32 = 1; - - // I want to print every number between 1 and 20 that is NOT - // divisible by 3 or 5. - while (n <= 20) : (n += 1) { - // The '%' symbol is the "modulo" operator and it - // returns the remainder after division. - if (n % 3 == 0) ???; - if (n % 5 == 0) ???; - std.debug.print("{} ", .{n}); - } - - std.debug.print("\n", .{}); -} diff --git a/exercises/14_while4.zig b/exercises/14_while4.zig deleted file mode 100644 index 7b2714e..0000000 --- a/exercises/14_while4.zig +++ /dev/null @@ -1,26 +0,0 @@ -// -// You can force a loop to exit immediately with a "break" statement: -// -// while (condition) : (continue expression) { -// -// if (other condition) break; -// -// } -// -// Continue expressions do NOT execute when a while loop stops -// because of a break! -// -const std = @import("std"); - -pub fn main() void { - var n: u32 = 1; - - // Oh dear! This while loop will go forever!? - // Please fix this so the print statement below gives the desired output. - while (true) : (n += 1) { - if (???) ???; - } - - // Result: we want n=4 - std.debug.print("n={}\n", .{n}); -} diff --git a/exercises/15_for.zig b/exercises/15_for.zig deleted file mode 100644 index 2ce930e..0000000 --- a/exercises/15_for.zig +++ /dev/null @@ -1,27 +0,0 @@ -// -// Behold the 'for' loop! It lets you execute code for each -// member of an array: -// -// for (items) |item| { -// -// // Do something with item -// -// } -// -const std = @import("std"); - -pub fn main() void { - const story = [_]u8{ 'h', 'h', 's', 'n', 'h' }; - - std.debug.print("A Dramatic Story: ", .{}); - - for (???) |???| { - if (scene == 'h') std.debug.print(":-) ", .{}); - if (scene == 's') std.debug.print(":-( ", .{}); - if (scene == 'n') std.debug.print(":-| ", .{}); - } - - std.debug.print("The End.\n", .{}); -} -// Note that "for" loops also work on things called "slices" -// which we'll see later. diff --git a/exercises/16_for2.zig b/exercises/16_for2.zig deleted file mode 100644 index 0a62a1a..0000000 --- a/exercises/16_for2.zig +++ /dev/null @@ -1,33 +0,0 @@ -// -// For loops also let you store the "index" of the iteration - a -// number starting with 0 that counts up with each iteration: -// -// for (items) |item, index| { -// -// // Do something with item and index -// -// } -// -// You can name "item" and "index" anything you want. "i" is a popular -// shortening of "index". The item name is often the singular form of -// the items you're looping through. -// -const std = @import("std"); - -pub fn main() void { - // Let's store the bits of binary number 1101 in - // 'little-endian' order (least significant byte first): - const bits = [_]u8{ 1, 0, 1, 1 }; - var value: u32 = 0; - - // Now we'll convert the binary bits to a number value by adding - // the value of the place as a power of two for each bit. - // - // See if you can figure out the missing piece: - for (bits) |bit, ???| { - var place_value = std.math.pow(u32, 2, @intCast(u32, i)); - value += place_value * bit; - } - - std.debug.print("The value of bits '1101': {}.\n", .{value}); -} diff --git a/exercises/17_quiz2.zig b/exercises/17_quiz2.zig deleted file mode 100644 index 7de7010..0000000 --- a/exercises/17_quiz2.zig +++ /dev/null @@ -1,28 +0,0 @@ -// -// Quiz time again! Let's see if you can solve the famous "Fizz Buzz"! -// -// "Players take turns to count incrementally, replacing -// any number divisible by three with the word "fizz", -// and any number divisible by five with the word "buzz". -// - From https://en.wikipedia.org/wiki/Fizz_buzz -// -// Let's go from 1 to 16. This has been started for you, but there's -// some problems. :-( -// -const std = import standard library; - -function main() void { - var i: u8 = 1; - var stop_at: u8 = 16; - - // What kind of loop is this? A 'for' or a 'while'? - ??? (i <= stop_at) : (i += 1) { - if (i % 3 == 0) std.debug.print("Fizz", .{}); - if (i % 5 == 0) std.debug.print("Buzz", .{}); - if (!(i % 3 == 0) and !(i % 5 == 0)) { - std.debug.print("{}", .{???}); - } - std.debug.print(", ", .{}); - } - std.debug.print("\n", .{}); -} diff --git a/exercises/18_functions.zig b/exercises/18_functions.zig deleted file mode 100644 index 51be2cd..0000000 --- a/exercises/18_functions.zig +++ /dev/null @@ -1,33 +0,0 @@ -// -// Functions! We've already seen a lot of one called "main()". Now let's try -// writing one of our own: -// -// fn foo(n: u8) u8 { -// return n + 1; -// } -// -// The foo() function above takes a number "n" and returns a number that is -// larger by one. -// -// If your function doesn't take any parameters and doesn't return anything, -// it would be defined like main(): -// -// fn foo() void { } -// -const std = @import("std"); - -pub fn main() void { - // The new function deepThought() should return the number 42. See below. - const answer: u8 = deepThought(); - - std.debug.print("Answer to the Ultimate Question: {}\n", .{answer}); -} - -// Please define the deepThought() function below. -// -// We're just missing a couple things. One thing we're NOT missing is the -// keyword "pub", which is not needed here. Can you guess why? -// -??? deepThought() ??? { - return 42; // Number courtesy Douglas Adams -} diff --git a/exercises/19_functions2.zig b/exercises/19_functions2.zig deleted file mode 100644 index 00f33c5..0000000 --- a/exercises/19_functions2.zig +++ /dev/null @@ -1,30 +0,0 @@ -// -// Now let's create a function that takes a parameter. Here's an -// example that takes two parameters. As you can see, parameters -// are declared just like any other types ("name": "type"): -// -// fn myFunction(number: u8, is_lucky: bool) { -// ... -// } -// -const std = @import("std"); - -pub fn main() void { - std.debug.print("Powers of two: {} {} {} {}\n", .{ - twoToThe(1), - twoToThe(2), - twoToThe(3), - twoToThe(4), - }); -} - -// Please give this function the correct input parameter(s). -// You'll need to figure out the parameter name and type that we're -// expecting. The output type has already been specified for you. -// -fn twoToThe(???) u32 { - return std.math.pow(u32, 2, my_number); - // std.math.pow(type, a, b) takes a numeric type and two numbers - // of that type and returns "a to the power of b" as that same - // numeric type. -} diff --git a/exercises/20_quiz3.zig b/exercises/20_quiz3.zig deleted file mode 100644 index 651af8c..0000000 --- a/exercises/20_quiz3.zig +++ /dev/null @@ -1,43 +0,0 @@ -// -// Let's see if we can make use of some of things we've learned so far. -// We'll create two functions: one that contains a "for" loop and one -// that contains a "while" loop. -// -// Both of these are simply labeled "loop" below. -// -const std = @import("std"); - -pub fn main() void { - const my_numbers = [4]u16{ 5, 6, 7, 8 }; - - printPowersOfTwo(my_numbers); - std.debug.print("\n", .{}); -} - -// You won't see this every day: a function that takes an array with -// exactly four u16 numbers. This is not how you would normally pass -// an array to a function. We'll learn about slices and pointers in -// a little while. For now, we're using what we know. -// -// This function prints, but does not return anything. -// -fn printPowersOfTwo(numbers: [4]u16) ??? { - loop (numbers) |n| { - std.debug.print("{} ", .{twoToThe(n)}); - } -} - -// This function bears a striking resemblance to twoToThe() in the last -// exercise. But don't be fooled! This one does the math without the aid -// of the standard library! -// -fn twoToThe(number: u16) ??? { - var n: u16 = 0; - var total: u16 = 1; - - loop (n < number) : (n += 1) { - total *= 2; - } - - return ???; -} diff --git a/exercises/21_errors.zig b/exercises/21_errors.zig deleted file mode 100644 index cbb5ac8..0000000 --- a/exercises/21_errors.zig +++ /dev/null @@ -1,46 +0,0 @@ -// -// Believe it or not, sometimes things go wrong in programs. -// -// In Zig, an error is a value. Errors are named so we can identify -// things that can go wrong. Errors are created in "error sets", which -// are just a collection of named errors. -// -// We have the start of an error set, but we're missing the condition -// "TooSmall". Please add it where needed! -const MyNumberError = error{ - TooBig, - ???, - TooFour, -}; - -const std = @import("std"); - -pub fn main() void { - var nums = [_]u8{ 2, 3, 4, 5, 6 }; - - for (nums) |n| { - std.debug.print("{}", .{n}); - - const number_error = numberFail(n); - - if (number_error == MyNumberError.TooBig) { - std.debug.print(">4. ", .{}); - } - if (???) { - std.debug.print("<4. ", .{}); - } - if (number_error == MyNumberError.TooFour) { - std.debug.print("=4. ", .{}); - } - } - - std.debug.print("\n", .{}); -} - -// Notice how this function can return any member of the MyNumberError -// error set. -fn numberFail(n: u8) MyNumberError { - if (n > 4) return MyNumberError.TooBig; - if (n < 4) return MyNumberError.TooSmall; // <---- this one is free! - return MyNumberError.TooFour; -} diff --git a/exercises/22_errors2.zig b/exercises/22_errors2.zig deleted file mode 100644 index fa0eafa..0000000 --- a/exercises/22_errors2.zig +++ /dev/null @@ -1,29 +0,0 @@ -// -// A common case for errors is a situation where we're expecting to -// have a value OR something has gone wrong. Take this example: -// -// var text: Text = getText('foo.txt'); -// -// What happens if getText() can't find 'foo.txt'? How do we express -// this in Zig? -// -// Zig lets us make what's called an "error union" which is a value -// which could either be a regular value OR an error from a set: -// -// var text: MyErrorSet!Text = getText('foo.txt'); -// -// For now, let's just see if we can try making an error union! -// -const std = @import("std"); - -const MyNumberError = error{TooSmall}; - -pub fn main() void { - var my_number: ??? = 5; - - // Looks like my_number will need to either store a number OR - // an error. Can you set the type correctly above? - my_number = MyNumberError.TooSmall; - - std.debug.print("I compiled!", .{}); -} diff --git a/exercises/23_errors3.zig b/exercises/23_errors3.zig deleted file mode 100644 index a465737..0000000 --- a/exercises/23_errors3.zig +++ /dev/null @@ -1,28 +0,0 @@ -// -// One way to deal with error unions is to "catch" any error and -// replace it with a default value. -// -// foo = canFail() catch 6; -// -// If canFail() fails, foo will equal 6. -// -const std = @import("std"); - -const MyNumberError = error{TooSmall}; - -pub fn main() void { - var a: u32 = addTwenty(44) catch 22; - var b: u32 = addTwenty(4) ??? 22; - - std.debug.print("a={}, b={}", .{ a, b }); -} - -// Please provide the return type from this function. -// Hint: it'll be an error union. -fn addTwenty(n: u32) ??? { - if (n < 5) { - return MyNumberError.TooSmall; - } else { - return n + 20; - } -} diff --git a/exercises/24_errors4.zig b/exercises/24_errors4.zig deleted file mode 100644 index 560b129..0000000 --- a/exercises/24_errors4.zig +++ /dev/null @@ -1,69 +0,0 @@ -// -// Using `catch` to replace an error with a default value is a bit -// of a blunt instrument since it doesn't matter what the error is. -// -// Catch lets us capture the error value and perform additional -// actions with this form: -// -// canFail() catch |err| { -// if (err == FishError.TunaMalfunction) { -// ... -// } -// }; -// -const std = @import("std"); - -const MyNumberError = error{ - TooSmall, - TooBig, -}; - -pub fn main() void { - // The "catch 0" below is just our way of dealing with the fact - // that makeJustRight() returns a error union (for now). - var a: u32 = makeJustRight(44) catch 0; - var b: u32 = makeJustRight(14) catch 0; - var c: u32 = makeJustRight(4) catch 0; - - std.debug.print("a={}, b={}, c={}", .{ a, b, c }); -} - -// In this silly example we've split the responsibility of making -// a number just right into four (!) functions: -// -// makeJustRight() Calls fixTooBig(), cannot fix any errors. -// fixTooBig() Calls fixTooSmall(), fixes TooBig errors. -// fixTooSmall() Calls detectProblems(), fixes TooSmall errors. -// detectProblems() Returns the number or an error. -// -fn makeJustRight(n: u32) MyNumberError!u32 { - return fixTooBig(n) catch |err| { - return err; - }; -} - -fn fixTooBig(n: u32) MyNumberError!u32 { - return fixTooSmall(n) catch |err| { - if (err == MyNumberError.TooBig) { - return 20; - } - - return err; - }; -} - -fn fixTooSmall(n: u32) MyNumberError!u32 { - // Oh dear, this is missing a lot! But don't worry, it's nearly - // identical to fixTooBig() above. - // - // If we get a TooSmall error, we should return 10. - // If we get any other error, we should return that error. - // Otherwise, we return the u32 number. - return detectProblems(n) ??? -} - -fn detectProblems(n: u32) MyNumberError!u32 { - if (n < 10) return MyNumberError.TooSmall; - if (n > 20) return MyNumberError.TooBig; - return n; -} diff --git a/exercises/25_errors5.zig b/exercises/25_errors5.zig deleted file mode 100644 index 5119dcf..0000000 --- a/exercises/25_errors5.zig +++ /dev/null @@ -1,38 +0,0 @@ -// -// Zig has a handy "try" shortcut for this common error handling pattern: -// -// canFail() catch |err| return err; -// -// which can be more compactly written as: -// -// try canFail(); -// -const std = @import("std"); - -const MyNumberError = error{ - TooSmall, - TooBig, -}; - -pub fn main() void { - var a: u32 = addFive(44) catch 0; - var b: u32 = addFive(14) catch 0; - var c: u32 = addFive(4) catch 0; - - std.debug.print("a={}, b={}, c={}", .{ a, b, c }); -} - -fn addFive(n: u32) MyNumberError!u32 { - // This function needs to return any error which might come back from detect(). - // Please use a "try" statement rather than a "catch". - // - var x = detect(n); - - return x + 5; -} - -fn detect(n: u32) MyNumberError!u32 { - if (n < 10) return MyNumberError.TooSmall; - if (n > 20) return MyNumberError.TooBig; - return n; -} diff --git a/exercises/26_hello2.zig b/exercises/26_hello2.zig deleted file mode 100644 index 237d27c..0000000 --- a/exercises/26_hello2.zig +++ /dev/null @@ -1,23 +0,0 @@ -// -// Great news! Now we know enough to understand a "real" Hello World -// program in Zig - one that uses the system Standard Out resource...which -// can fail! -// -const std = @import("std"); - -// Take note that this main() definition now returns "!void" rather -// than just "void". Since there's no specific error type, this means -// that Zig will infer the error type. This is appropriate in the case -// of main(), but can have consequences elsewhere. -pub fn main() !void { - - // We get a Writer for Standard Out so we can print() to it. - const stdout = std.io.getStdOut().writer(); - - // Unlike std.debug.print(), the Standard Out writer can fail - // with an error. We don't care _what_ the error is, we want - // to be able to pass it up as a return value of main(). - // - // We just learned of a single statement which can accomplish this. - stdout.print("Hello world!\n", .{}); -} diff --git a/exercises/27_defer.zig b/exercises/27_defer.zig deleted file mode 100644 index b41e2af..0000000 --- a/exercises/27_defer.zig +++ /dev/null @@ -1,25 +0,0 @@ -// -// 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/exercises/28_defer2.zig b/exercises/28_defer2.zig deleted file mode 100644 index 6943012..0000000 --- a/exercises/28_defer2.zig +++ /dev/null @@ -1,37 +0,0 @@ -// -// 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/exercises/29_errdefer.zig b/exercises/29_errdefer.zig deleted file mode 100644 index f43c738..0000000 --- a/exercises/29_errdefer.zig +++ /dev/null @@ -1,59 +0,0 @@ -// -// 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/exercises/30_switch.zig b/exercises/30_switch.zig deleted file mode 100644 index cb983f5..0000000 --- a/exercises/30_switch.zig +++ /dev/null @@ -1,53 +0,0 @@ -// -// 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/exercises/31_switch2.zig b/exercises/31_switch2.zig deleted file mode 100644 index b7680b4..0000000 --- a/exercises/31_switch2.zig +++ /dev/null @@ -1,42 +0,0 @@ -// -// 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/exercises/32_unreachable.zig b/exercises/32_unreachable.zig deleted file mode 100644 index ffc35a4..0000000 --- a/exercises/32_unreachable.zig +++ /dev/null @@ -1,44 +0,0 @@ -// -// Zig has an "unreachable" statement. Use it when you want to tell the -// compiler that a branch of code should never be executed and that the -// mere act of reaching it is an error. -// -// if (true) { -// ... -// } else { -// unreachable; -// } -// -// Here we've made a little virtual machine that performs mathematical -// operations on a single numeric value. It looks great but there's one -// little problem: the switch statement doesn't cover every possible -// value of a u8 number! -// -// WE know there are only three operations but Zig doesn't. Use the -// unreachable statement to make the switch complete. Or ELSE. :-) -// -const std = @import("std"); - -pub fn main() void { - const operations = [_]u8{ 1, 1, 1, 3, 2, 2 }; - - var current_value: u32 = 0; - - for (operations) |op| { - switch (op) { - 1 => { - current_value += 1; - }, - 2 => { - current_value -= 1; - }, - 3 => { - current_value *= current_value; - }, - } - - std.debug.print("{} ", .{current_value}); - } - - std.debug.print("\n", .{}); -} diff --git a/exercises/33_iferror.zig b/exercises/33_iferror.zig deleted file mode 100644 index 67777a9..0000000 --- a/exercises/33_iferror.zig +++ /dev/null @@ -1,49 +0,0 @@ -// -// 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/exercises/34_quiz4.zig b/exercises/34_quiz4.zig deleted file mode 100644 index 6b0e3fc..0000000 --- a/exercises/34_quiz4.zig +++ /dev/null @@ -1,24 +0,0 @@ -// -// Quiz time. See if you can make this program work! -// -// Solve this any way you like, just be sure the output is: -// -// my_num=42 -// -const std = @import("std"); - -const NumError = error{IllegalNumber}; - -pub fn main() void { - const stdout = std.io.getStdOut().writer(); - - const my_num: u32 = getNumber(); - - try stdout.print("my_num={}\n", .{my_num}); -} - -// Just don't modify this function. It's "perfect" the way it is. :-) -fn getNumber() NumError!u32 { - if (false) return NumError.IllegalNumber; - return 42; -} diff --git a/exercises/35_enums.zig b/exercises/35_enums.zig deleted file mode 100644 index 1825f52..0000000 --- a/exercises/35_enums.zig +++ /dev/null @@ -1,55 +0,0 @@ -// -// Remember that little mathematical virtual machine we made using the -// "unreachable" statement? Well, there were two problems with the -// way we were using op codes: -// -// 1. Having to remember op codes by number is no good. -// 2. We had to use "unreachable" because Zig had no way of knowing -// how many valid op codes there were. -// -// An "enum" is a Zig construct that lets you give names to numeric -// values and store them in a set. They look a lot like error sets: -// -// const Fruit = enum{ apple, pear, orange }; -// -// const my_fruit = Fruit.apple; -// -// Let's use an enum in place of the numbers we were using in the -// previous version! -// -const std = @import("std"); - -// Please complete the enum! -const Ops = enum { ??? }; - -pub fn main() void { - const operations = [_]Ops{ - Ops.inc, - Ops.inc, - Ops.inc, - Ops.pow, - Ops.dec, - Ops.dec, - }; - - var current_value: u32 = 0; - - for (operations) |op| { - switch (op) { - Ops.inc => { - current_value += 1; - }, - Ops.dec => { - current_value -= 1; - }, - Ops.pow => { - current_value *= current_value; - }, - // No "else" needed! Why is that? - } - - std.debug.print("{} ", .{current_value}); - } - - std.debug.print("\n", .{}); -} diff --git a/exercises/36_enums2.zig b/exercises/36_enums2.zig deleted file mode 100644 index 0ddc4a5..0000000 --- a/exercises/36_enums2.zig +++ /dev/null @@ -1,61 +0,0 @@ -// -// Enums are really just a set of numbers. You can leave the -// numbering up to the compiler, or you can assign them -// explicitly. You can even specify the numeric type used. -// -// const Stuff = enum(u8){ foo = 16 }; -// -// You can get the integer out with a built-in function: -// -// var my_stuff: u8 = @enumToInt(Stuff.foo); -// -// Note how that built-in function starts with "@" just like the -// @import() function we've been using. -// -const std = @import("std"); - -// Zig lets us write integers in hexadecimal format: -// -// 0xf (is the value 15 in hex) -// -// Web browsers let us specify colors using a hexadecimal -// number where each byte represents the brightness of the -// Red, Green, or Blue component (RGB) where two hex digits -// are one byte with a value range of 0-255: -// -// #RRGGBB -// -// Please define and use a pure blue value Color: -const Color = enum(u32) { - red = 0xff0000, - green = 0x00ff00, - blue = ???, -}; - -pub fn main() void { - // Remember Zig's multi-line strings? Here they are again. - // Also, check out this cool format string: - // - // {x:0>6} - // ^ - // x type ('x' is lower-case hexadecimal) - // : separator (needed for format syntax) - // 0 padding character (default is ' ') - // > alignment ('>' aligns right) - // 6 width (use padding to force width) - // - // Please add this formatting to the blue value. - // (Even better, experiment without it, or try parts of it - // to see what prints!) - std.debug.print( - \\

- \\ Red - \\ Green - \\ Blue - \\

- , .{ - @enumToInt(Color.red), - @enumToInt(Color.green), - @enumToInt(???), // Oops! We're missing something! - }); -} diff --git a/exercises/37_structs.zig b/exercises/37_structs.zig deleted file mode 100644 index 8082248..0000000 --- a/exercises/37_structs.zig +++ /dev/null @@ -1,59 +0,0 @@ -// -// Being able to group values together lets us turn this: -// -// point1_x = 3; -// point1_y = 16; -// point1_z = 27; -// point2_x = 7; -// point2_y = 13; -// point2_z = 34; -// -// into this: -// -// point1 = Point{ .x=3, .y=16, .z=27 }; -// point2 = Point{ .x=7, .y=13, .z=34 }; -// -// The Point above is an example of a "struct" (short for "structure"). -// Here's how it could have been defined: -// -// const Point = struct{ x: u32, y: u32, z: u32 }; -// -// Let's store something fun with a struct: a roleplaying character! -// -const std = @import("std"); - -// We'll use an enum to specify the character class. -const Class = enum { - wizard, - thief, - bard, - warrior, -}; - -// Please add a new property to this struct called "health" and make -// it a u8 integer type. -const Character = struct { - class: Class, - gold: u32, - experience: u32, -}; - -pub fn main() void { - // Please initialize Glorp with 100 health. - var glorp_the_wise = Character{ - .class = Class.wizard, - .gold = 20, - .experience = 10, - }; - - // Glorp gains some gold. - glorp_the_wise.gold += 5; - - // Ouch! Glorp takes a punch! - glorp_the_wise.health -= 10; - - std.debug.print("Your wizard has {} health and {} gold.", .{ - glorp_the_wise.health, - glorp_the_wise.gold, - }); -} diff --git a/exercises/38_structs2.zig b/exercises/38_structs2.zig deleted file mode 100644 index b0db022..0000000 --- a/exercises/38_structs2.zig +++ /dev/null @@ -1,52 +0,0 @@ -// -// Grouping values in structs is not merely convenient. It also allows -// us to treat the values as a single item when storing them, passing -// them to functions, etc. -// -// This exercise demonstrates how we can store structs in an array and -// how doing so lets us print them all (both) using a loop. -// -const std = @import("std"); - -const Class = enum { - wizard, - thief, - bard, - warrior, -}; - -const Character = struct { - class: Class, - gold: u32, - health: u8, - experience: u32, -}; - -pub fn main() void { - var chars: [2]Character = undefined; - - // Glorp the Wise - chars[0] = Character{ - .class = Class.wizard, - .gold = 20, - .health = 100, - .experience = 10, - }; - - // Please add "Zump the Loud" with the following properties: - // - // class bard - // gold 10 - // health 100 - // experience 20 - // - // Feel free to run this program without adding Zump. What does - // it do and why? - - // Printing all RPG characters in a loop: - for (chars) |c, num| { - std.debug.print("Character {} - G:{} H:{} XP:{}\n", .{ - num + 1, c.gold, c.health, c.experience, - }); - } -} diff --git a/exercises/39_pointers.zig b/exercises/39_pointers.zig deleted file mode 100644 index d545525..0000000 --- a/exercises/39_pointers.zig +++ /dev/null @@ -1,36 +0,0 @@ -// -// Check this out: -// -// var foo: u8 = 5; // foo is 5 -// var bar: *u8 = &foo; // bar is a pointer -// -// What is a pointer? It's a reference to a value. In this example -// bar is a reference to the memory space that currently contains the -// value 5. -// -// A cheatsheet given the above declarations: -// -// u8 the type of a u8 value -// foo the value 5 -// *u8 the type of a pointer to a u8 value -// &foo a reference to foo -// bar a pointer to the value at foo -// bar.* the value 5 (the dereferenced value "at" bar) -// -// We'll see why pointers are useful in a moment. For now, see if you -// can make this example work! -// -const std = @import("std"); - -pub fn main() void { - var num1: u8 = 5; - var num1_pointer: *u8 = &num1; - - var num2: u8 = undefined; - - // Please make num2 equal 5 using num1_pointer! - // (See the "cheatsheet" above for ideas.) - num2 = ???; - - std.debug.print("num1: {}, num2: {}\n", .{ num1, num2 }); -} diff --git a/exercises/40_pointers2.zig b/exercises/40_pointers2.zig deleted file mode 100644 index 43dd2c3..0000000 --- a/exercises/40_pointers2.zig +++ /dev/null @@ -1,27 +0,0 @@ -// -// It's important to note that variable pointers and constant pointers -// are different types. -// -// Given: -// -// var foo: u8 = 5; -// const bar: u8 = 5; -// -// Then: -// -// &foo is of type "*u8" -// &bar is of type "*const u8" -// -// You can always make a constant pointer to a variable, but you cannot -// make a variable pointer to a constant. This sounds like a logic puzzle, -// but it just means that once data is declared immutable, you can't -// coerce it to a mutable type. It's a safety thing (to prevent mistakes). -// -const std = @import("std"); - -pub fn main() void { - const a: u8 = 12; - const b: *u8 = &a; // fix this! - - std.debug.print("a: {}, b: {}\n", .{ a, b.* }); -} diff --git a/exercises/41_pointers3.zig b/exercises/41_pointers3.zig deleted file mode 100644 index 9e2bcc6..0000000 --- a/exercises/41_pointers3.zig +++ /dev/null @@ -1,41 +0,0 @@ -// -// The tricky part is that the pointer's mutability (var vs const) refers -// to the ability to change what the pointer POINTS TO, not the ability -// to change the VALUE at that location! -// -// const locked: u8 = 5; -// var unlocked: u8 = 10; -// -// const p1: *const u8 = &locked; -// var p2: *const u8 = &locked; -// -// Both p1 and p2 point to constant values which cannot change. However, -// p2 can be changed to point to something else and p1 cannot! -// -// const p3: *u8 = &unlocked; -// var p4: *u8 = &unlocked; -// const p5: *const u8 = &unlocked; -// var p6: *const u8 = &unlocked; -// -// Here p3 and p4 can both be used to change the value they point to but -// p3 cannot point at anything else. -// What's interesting is that p5 and p6 act like p1 and p2, but point to -// the value at "unlocked". This is what we mean when we say that we can -// make a constant reference to any value! -// -const std = @import("std"); - -pub fn main() void { - var foo: u8 = 5; - var bar: u8 = 10; - - // Please define pointer "p" so that it can point to EITHER foo or - // bar AND change the value it points to! - ??? p: ??? = undefined; - - p = &foo; - p.* += 1; - p = &bar; - p.* += 1; - std.debug.print("foo={}, bar={}\n", .{ foo, bar }); -} diff --git a/exercises/42_pointers4.zig b/exercises/42_pointers4.zig deleted file mode 100644 index 261dbc1..0000000 --- a/exercises/42_pointers4.zig +++ /dev/null @@ -1,32 +0,0 @@ -// -// Now let's use pointers to do something we haven't been -// able to do before: pass a value by reference to a function! -// -const std = @import("std"); - -pub fn main() void { - var num: u8 = 1; - var more_nums = [_]u8{ 1, 1, 1, 1 }; - - // Let's pass a reference to num to our function and print it: - makeFive(&num); - std.debug.print("num: {}, ", .{num}); - - // Now something interesting. Let's pass a reference to a - // specific array value: - makeFive(&more_nums[2]); - - // And print the array: - std.debug.print("more_nums: ", .{}); - for (more_nums) |n| { - std.debug.print("{} ", .{n}); - } - - std.debug.print("\n", .{}); -} - -// This function should take a reference to a u8 value and set it -// to 5. -fn makeFive(x: *u8) void { - ??? = 5; // fix me! -} diff --git a/exercises/43_pointers5.zig b/exercises/43_pointers5.zig deleted file mode 100644 index cb94189..0000000 --- a/exercises/43_pointers5.zig +++ /dev/null @@ -1,82 +0,0 @@ -// -// Passing integer pointers around is generally not something you're going -// to do. Integers are cheap to copy. -// -// But you know what IS useful? Pointers to structs: -// -// const Vertex = struct{ x: u32, y: u32, z: u32 }; -// -// var v1 = Vertex{ .x=3, .y=2, .z=5 }; -// -// var pv: *Vertex = &v1; // <-- a pointer to our struct -// -// Note that you don't need to dereference the "pv" pointer to access -// the struct's fields: -// -// YES: pv.x -// NO: pv.*.x -// -// We can write functions that take pointer arguments: -// -// fn foo(v: *Vertex) void { -// v.x += 2; -// v.y += 3; -// v.z += 7; -// } -// -// And pass references to them: -// -// foo(&v1); -// -// -// Let's revisit our RPG example and make a printCharacter() function -// that takes a Character pointer. -// -const std = @import("std"); - -const Class = enum { - wizard, - thief, - bard, - warrior, -}; - -const Character = struct { - class: Class, - gold: u32, - health: u8 = 100, // <--- You can also provide fields a default value! - experience: u32, -}; - -pub fn main() void { - var glorp = Character{ - .class = Class.wizard, - .gold = 10, - .experience = 20, - }; - - // FIX ME! - // Please pass our Character "glorp" to printCharacter(): - printCharacter(???); -} - -// Note how this function's "c" parameter is a pointer to a Character struct. -fn printCharacter(c: *Character) void { - - // Here's something you haven't seen before: when switching an enum, you - // don't have to write the full enum name. Zig understands that ".wizard" - // means "Class.wizard" when we switch on a Class enum value: - const class_name = switch (c.class) { - .wizard => "Wizard", - .thief => "Thief", - .bard => "Bard", - .warrior => "Warrior", - }; - - std.debug.print("{s} (G:{} H:{} XP:{})", .{ - class_name, - c.gold, - c.health, - c.experience, - }); -} diff --git a/exercises/44_quiz5.zig b/exercises/44_quiz5.zig deleted file mode 100644 index 8a0d88c..0000000 --- a/exercises/44_quiz5.zig +++ /dev/null @@ -1,47 +0,0 @@ -// -// "Elephants walking -// Along the trails -// -// Are holding hands -// By holding tails." -// -// from Holding Hands -// by Lenore M. Link -// -const std = @import("std"); - -const Elephant = struct { - letter: u8, - tail: *Elephant = undefined, - visited: bool = false, -}; - -pub fn main() void { - var elephantA = Elephant{ .letter = 'A' }; - // (Please add Elephant B here!) - var elephantC = Elephant{ .letter = 'C' }; - - // Link the elephants so that each tail "points" to the next elephant. - // They make a circle: A->B->C->A... - elephantA.tail = &elephantB; - // (Please link Elephant B's tail to Elephant C here!) - elephantC.tail = &elephantA; - - visitElephants(&elephantA); - - std.debug.print("\n", .{}); -} - -// This function visits all elephants once, starting with the -// first elephant and following the tails to the next elephant. -// If we did not "mark" the elephants as visited (by setting -// visited=true), then this would loop infinitely! -fn visitElephants(first_elephant: *Elephant) void { - var e = first_elephant; - - while (!e.visited) { - std.debug.print("Elephant {u}. ", .{e.letter}); - e.visited = true; - e = e.tail; - } -} diff --git a/exercises/45_optionals.zig b/exercises/45_optionals.zig deleted file mode 100644 index 1327e4c..0000000 --- a/exercises/45_optionals.zig +++ /dev/null @@ -1,51 +0,0 @@ -// -// Sometimes you know that a variable might hold a value or -// it might not. Zig has a neat way of expressing this idea -// called Optionals. An optional type just has a '?' like this: -// -// var foo: ?u32 = 10; -// -// Now foo can store a u32 integer OR null (a value storing -// the cosmic horror of a value NOT EXISTING!) -// -// foo = null; -// -// if (foo == null) beginScreaming(); -// -// Before we can use the optional value as the non-null type -// (a u32 integer in this case), we need to guarantee that it -// isn't null. One way to do this is to THREATEN IT with the -// "orelse" statement. -// -// var bar = foo orelse 2; -// -// Here, bar will either equal the u32 integer value stored in -// foo, or it will equal 2 if foo was null. -// -const std = @import("std"); - -pub fn main() void { - const result = deepThought(); - - // Please threaten the result so that answer is either the - // integer value from deepThought() OR the number 42: - var answer: u8 = result; - - std.debug.print("The Ultimate Answer: {}.\n", .{answer}); -} - -fn deepThought() ?u8 { - // It seems Deep Thought's output has declined in quality. - // But we'll leave this as-is. Sorry Deep Thought. - return null; -} -// Blast from the past: -// -// Optionals are a lot like error union types which can either -// hold a value or an error. Likewise, the orelse statement is -// like the catch statement used to "unwrap" a value or supply -// a default value: -// -// var maybe_bad: Error!u32 = Error.Evil; -// var number: u32 = maybe_bad catch 0; -// diff --git a/exercises/46_optionals2.zig b/exercises/46_optionals2.zig deleted file mode 100644 index d3f65bb..0000000 --- a/exercises/46_optionals2.zig +++ /dev/null @@ -1,58 +0,0 @@ -// -// Now that we have optional types, we can apply them to structs. -// The last time we checked in with our elephants, we had to link -// all three of them together in a "circle" so that the last tail -// linked to the first elephant. This is because we had NO CONCEPT -// of a tail that didn't point to another elephant! -// -// We also introduce the handy ".?" shortcut: -// -// const foo = bar.?; -// -// is the same as -// -// const foo = bar orelse unreachable; -// -// See if you can find where we use this shortcut below. -// -// Now let's make those elephant tails optional! -// -const std = @import("std"); - -const Elephant = struct { - letter: u8, - tail: *Elephant = null, // Hmm... tail needs something... - visited: bool = false, -}; - -pub fn main() void { - var elephantA = Elephant{ .letter = 'A' }; - var elephantB = Elephant{ .letter = 'B' }; - var elephantC = Elephant{ .letter = 'C' }; - - // Link the elephants so that each tail "points" to the next. - elephantA.tail = &elephantB; - elephantB.tail = &elephantC; - - visitElephants(&elephantA); - - std.debug.print("\n", .{}); -} - -// This function visits all elephants once, starting with the -// first elephant and following the tails to the next elephant. -fn visitElephants(first_elephant: *Elephant) void { - var e = first_elephant; - - while (!e.visited) { - std.debug.print("Elephant {u}. ", .{e.letter}); - e.visited = true; - - // We should stop once we encounter a tail that - // does NOT point to another element. What can - // we put here to make that happen? - if (e.tail == null) ???; - - e = e.tail.?; - } -} diff --git a/exercises/47_methods.zig b/exercises/47_methods.zig deleted file mode 100644 index c8e5c17..0000000 --- a/exercises/47_methods.zig +++ /dev/null @@ -1,94 +0,0 @@ -// -// Help! Evil alien creatures have hidden eggs all over the Earth -// and they're starting to hatch! -// -// Before you jump into battle, you'll need to know four things: -// -// 1. You can attach functions to structs: -// -// const Foo = struct{ -// pub fn hello() void { -// std.debug.print("Foo says hello!\n", .{}); -// } -// } -// -// 2. A function that is a member of a struct is a "method" and is -// called with the "dot syntax" like so: -// -// Foo.hello(); -// -// 3. The NEAT feature of methods is the special parameter named -// "self" that takes an instance of that type of struct: -// -// const Bar = struct{ -// number: u32, -// -// pub fn printMe(self: *Bar) void { -// std.debug.print("{}\n", .{self.number}); -// } -// } -// -// 4. Now when you call the method on an INSTANCE of that struct -// with the "dot syntax", the instance will be automatically -// passed as the "self" parameter: -// -// const my_bar = Bar{ .number = 2000 }; -// my_bar.printMe(); // prints "2000" -// -// Okay, you're armed. -// -// Now, please zap the alien structs until they're all gone or -// Earth will be doomed! -// -const std = @import("std"); - -// Look at this hideous Alien struct. Know your enemy! -const Alien = struct { - health: u8, - - // We hate this method: - pub fn hatch(strength: u8) Alien { - return Alien{ - .health = strength * 5, - }; - } - - // We love this method: - pub fn zap(self: *Alien, damage: u8) void { - self.health -= if (damage >= self.health) self.health else damage; - } -}; - -pub fn main() void { - // Look at all of these aliens of various strengths! - var aliens = [_]Alien{ - Alien.hatch(2), - Alien.hatch(1), - Alien.hatch(3), - Alien.hatch(3), - Alien.hatch(5), - Alien.hatch(3), - }; - - var aliens_alive = aliens.len; - var heat_ray_strength: u8 = 7; // We've been given a heat ray weapon. - - // We'll keep checking to see if we've killed all the aliens yet. - while (aliens_alive > 0) { - aliens_alive = 0; - - // Loop through every alien... - for (aliens) |*alien| { - - // *** Zap the Alien Here! *** - ???.zap(heat_ray_strength); - - // If the alien's health is still above 0, it's still alive. - if (alien.health > 0) aliens_alive += 1; - } - - std.debug.print("{} aliens. ", .{aliens_alive}); - } - - std.debug.print("Earth is saved!\n", .{}); -} diff --git a/exercises/48_methods2.zig b/exercises/48_methods2.zig deleted file mode 100644 index f97710d..0000000 --- a/exercises/48_methods2.zig +++ /dev/null @@ -1,71 +0,0 @@ -// -// Now that we've seen how methods work, let's see if we can help -// our elephants out a bit more with some Elephant methods. -// -const std = @import("std"); - -const Elephant = struct { - letter: u8, - tail: ?*Elephant = null, - visited: bool = false, - - // New Elephant methods! - pub fn getTail(self: *Elephant) *Elephant { - return self.tail.?; // Remember, this is means "orelse unreachable" - } - - pub fn hasTail(self: *Elephant) bool { - return (self.tail != null); - } - - pub fn visit(self: *Elephant) void { - self.visited = true; - } - - pub fn print(self: *Elephant) void { - // Prints elephant letter and [v]isited - var v: u8 = if (self.visited) 'v' else ' '; - std.debug.print("{u}{u} ", .{ self.letter, v }); - } -}; - -pub fn main() void { - var elephantA = Elephant{ .letter = 'A' }; - var elephantB = Elephant{ .letter = 'B' }; - var elephantC = Elephant{ .letter = 'C' }; - - // Link the elephants so that each tail "points" to the next. - elephantA.tail = &elephantB; - elephantB.tail = &elephantC; - - visitElephants(&elephantA); - - std.debug.print("\n", .{}); -} - -// This function visits all elephants once, starting with the -// first elephant and following the tails to the next elephant. -fn visitElephants(first_elephant: *Elephant) void { - var e = first_elephant; - - while (true) { - e.print(); - e.visit(); - - // Get the next elephant or stop. - if (e.hasTail()) { - e = e.???; // Which method do we want here? - } else { - break; - } - } -} - -// Bonus: Zig's enums can also have methods! Can you find -// one in the wild? If you can, mention it along with your -// name or alias in a comment below this one and make a -// pull request on GitHub for a piece of eternal Ziglings -// glory. The first five (5) PRs will be accepted! -// -// 1) drforester - I found one in the Zig source: -// https://github.com/ziglang/zig/blob/041212a41cfaf029dc3eb9740467b721c76f406c/src/Compilation.zig#L2495 diff --git a/exercises/49_quiz6.zig b/exercises/49_quiz6.zig deleted file mode 100644 index a1a1dec..0000000 --- a/exercises/49_quiz6.zig +++ /dev/null @@ -1,91 +0,0 @@ -// -// "Trunks and tails -// Are handy things" - -// from Holding Hands -// by Lenore M. Link -// -// Now that we have tails all figured out, can you implement trunks? -// -const std = @import("std"); - -const Elephant = struct { - letter: u8, - tail: ?*Elephant = null, - trunk: ?*Elephant = null, - visited: bool = false, - - // Elephant tail methods! - pub fn getTail(self: *Elephant) *Elephant { - return self.tail.?; // Remember, this is means "orelse unreachable" - } - - pub fn hasTail(self: *Elephant) bool { - return (self.tail != null); - } - - // Your Elephant trunk methods go here! - // --------------------------------------------------- - - ??? - - // --------------------------------------------------- - - pub fn visit(self: *Elephant) void { - self.visited = true; - } - - pub fn print(self: *Elephant) void { - // Prints elephant letter and [v]isited - var v: u8 = if (self.visited) 'v' else ' '; - std.debug.print("{u}{u} ", .{ self.letter, v }); - } -}; - -pub fn main() void { - var elephantA = Elephant{ .letter = 'A' }; - var elephantB = Elephant{ .letter = 'B' }; - var elephantC = Elephant{ .letter = 'C' }; - - // Link the elephants so that each tail "points" to the next. - elephantA.tail = &elephantB; - elephantB.tail = &elephantC; - - // And link the elephants so that each trunk "points" to the previous. - elephantB.trunk = &elephantA; - elephantC.trunk = &elephantB; - - visitElephants(&elephantA); - - std.debug.print("\n", .{}); -} - -// This function visits all elephants twice, tails to trunks. -fn visitElephants(first_elephant: *Elephant) void { - var e = first_elephant; - - // Follow the tails! - while (true) { - e.print(); - e.visit(); - - // Get the next elephant or stop. - if (e.hasTail()) { - e = e.getTail(); - } else { - break; - } - } - - // Follow the trunks! - while (true) { - e.print(); - - // Get the previous elephant or stop. - if (e.hasTrunk()) { - e = e.getTrunk(); - } else { - break; - } - } -} diff --git a/exercises/50_no_value.zig b/exercises/50_no_value.zig deleted file mode 100644 index 8708d2d..0000000 --- a/exercises/50_no_value.zig +++ /dev/null @@ -1,84 +0,0 @@ -// -// "We live on a placid island of ignorance in the midst -// of black seas of infinity, and it was not meant that -// we should voyage far." -// -// from The Call of Cthulhu -// by H. P. Lovecraft -// -// Zig has at least four ways of expressing "no value": -// -// * undefined -// -// var foo: u8 = undefined; -// -// "undefined" should not be thought of as a value, but as a way -// of telling the compiler that you are not assigning a value -// _yet_. Any type may be set to undefined, but attempting -// to read or use that value is _always_ a mistake. -// -// * null -// -// var foo: ?u8 = null; -// -// The "null" primitive value _is_ a value that means "no value". -// This is typically used with optional types as with the ?u8 -// shown above. When foo equals null, that's not a value of type -// u8. It means there is _no value_ of type u8 in foo at all! -// -// * error -// -// var foo: MyError!u8 = BadError; -// -// Errors are _very_ similar to nulls. They _are_ a value, but -// they usually indicate that the "real value" you were looking -// for does not exist. Instead, you have an error. The example -// error union type of MyError!u8 means that foo either holds -// a u8 value OR an error. There is _no value_ of type u8 in foo -// when it's set to an error! -// -// * void -// -// var foo: void = {}; -// -// "void" is a _type_, not a value. It is the most popular of the -// Zero Bit Types (those types which take up absolutely no space -// and have only a semantic value. When compiled to executable -// code, zero bit types generate no code at all. The above example -// shows a variable foo of type void which is assigned the value -// of an empty expression. It's much more common to see void as -// the return type of a function that returns nothing. -// -// Zig has all of these ways of expressing different types of "no value" -// because they each serve a purpose. Briefly: -// -// * undefined - there is no value YET, this cannot be read YET -// * null - there is an explicit value of "no value" -// * errors - there is no value because something went wrong -// * void - there will NEVER be a value stored here -// -// Please use the correct "no value" for each ??? to make this program -// print out a cursed quote from the Necronomicon. ...If you dare. -// -const std = @import("std"); - -const Err = error{Cthulhu}; - -pub fn main() void { - var first_line1: *const [16]u8 = ???; - first_line1 = "That is not dead"; - - var first_line2: Err!*const [21]u8 = ???; - first_line2 = "which can eternal lie"; - - std.debug.print("{s} {s} / ", .{ first_line1, first_line2 }); - - printSecondLine(); -} - -fn printSecondLine() ??? { - var second_line2: ?*const [18]u8 = ???; - second_line2 = "even death may die"; - - std.debug.print("And with strange aeons {s}.\n", .{second_line2.?}); -} diff --git a/exercises/51_values.zig b/exercises/51_values.zig deleted file mode 100644 index dd68d3b..0000000 --- a/exercises/51_values.zig +++ /dev/null @@ -1,187 +0,0 @@ -// -// If you thought the last exercise was a deep dive, hold onto your -// hat because we are about to descend into the computer's molten -// core. -// -// (Shouting) DOWN HERE, THE BITS AND BYTES FLOW FROM RAM TO THE CPU -// LIKE A HOT, DENSE FLUID. THE FORCES ARE INCREDIBLE. BUT HOW DOES -// ALL OF THIS RELATE TO THE DATA IN OUR ZIG PROGRAMS? LET'S HEAD -// BACK UP TO THE TEXT EDITOR AND FIND OUT. -// -// Ah, that's better. Now we can look at some familiar Zig code. -// -// @import() adds the imported code to your own. In this case, code -// from the standard library is added to your program and compiled -// with it. All of this will be loaded into RAM when it runs. Oh, and -// that thing we name "const std"? That's a struct! - -const std = @import("std"); - -// Remember our old RPG Character struct? A struct is really just a -// very convenient way to deal with memory. These fields (gold, -// health, experience) are all values of a particular size. Add them -// together and you have the size of the struct as a whole. - -const Character = struct { - gold: u32 = 0, - health: u8 = 100, - experience: u32 = 0, -}; - -// Here we create a character called "the_narrator" that is a constant -// (immutable) instance of a Character struct. It is stored in your -// program as data, and like the instruction code, it is loaded into -// RAM when your program runs. The relative location of this data in -// memory is hard-coded and neither the address nor the value changes. - -const the_narrator = Character{ - .gold = 12, - .health = 99, - .experience = 9000, -}; - -// This "global_wizard" character is very similar. The address for -// this data won't change, but the data itself can since this is a var -// and not a const. - -var global_wizard = Character{}; - -// A function is instruction code at a particular address. Function -// parameters in Zig are always immutable. They are stored in "the -// stack". A stack is a type of data structure and "the stack" is a -// specific bit of RAM reserved for your program. The CPU has special -// support for adding and removing things from "the stack", so it is -// an extremely efficient place for memory storage. -// -// Also, when a function executes, the input arguments are often -// loaded into the beating heart of the CPU itself in registers. -// -// Our main() function here has no input parameters, but it will have -// a stack entry (called a "frame"). - -pub fn main() void { - - // Here, the "glorp" character will be allocated on the stack - // because each instance of glorp is mutable and therefore unique - // to the invocation of this function. - - var glorp = Character{ - .gold = 30, - }; - - // However, this "skull_farmer" character will be put in the - // global immutable data even though it's defined in a function. - // Since it's immutable, all invocations of the function can share - // this one value. - - const skull_farmer = Character{}; - - // The "reward_xp" value is interesting. It's a constant value, so - // it could go with other global data. But being such a small - // value, it may also simply be inlined as a literal value in your - // instruction code where it is used. It's up to the compiler. - - const reward_xp: u32 = 200; - - // Now let's circle back around to that "std" struct we imported - // at the top. Since it's just a regular Zig value once it's - // imported, we can also assign new names for its fields and - // declarations. "debug" refers to another struct and "print" is a - // public function namespaced within THAT struct. - // - // Let's assign the std.debug.print function to a const named - // "print" so that we can use this new name later! - - const print = ???; - - // Now let's look at assigning and pointing to values in Zig. - // - // We'll try three different ways of making a new name to access - // our glorp Character and change one of its values. - // - // "glorp_access1" is incorrectly named! We asked Zig to set aside - // memory for another Character struct. So when we assign glorp to - // glorp_access1 here, we're actually assigning all of the fields - // to make a copy! Now we have two separate characters. - // - // You don't need to fix this. But notice what gets printed in - // your program's output for this one compared to the other two - // assignments below! - - var glorp_access1: Character = glorp; - glorp_access1.gold = 111; - print("1:{}!. ", .{glorp.gold == glorp_access1.gold}); - - // NOTE: - // - // If we tried to do this with a const Character instead of a - // var, changing the gold field would give us a compiler error - // because const values are immutable! - // - // "glorp_access2" will do what we want. It points to the original - // glorp's address. Also remember that we get one implicit - // dereference with struct fields, so accessing the "gold" field - // from glorp_access2 looks just like accessing it from glorp - // itself. - - var glorp_access2: *Character = &glorp; - glorp_access2.gold = 222; - print("2:{}!. ", .{glorp.gold == glorp_access2.gold}); - - // "glorp_access3" is interesting. It's also a pointer, but it's a - // const. Won't that disallow changing the gold value? No! As you - // may recall from our earlier pointer experiments, a constant - // pointer can't change what it's POINTING AT, but the value at - // the address it points to is still mutable! So we CAN change it. - - const glorp_access3: *Character = &glorp; - glorp_access3.gold = 333; - print("3:{}!. ", .{glorp.gold == glorp_access3.gold}); - - // NOTE: - // - // If we tried to do this with a *const Character pointer, - // that would NOT work and we would get a compiler error - // because the VALUE becomes immutable! - // - // Moving along... - // - // Passing arguments to functions is pretty much exactly like - // making an assignment to a const (since Zig enforces that ALL - // function parameters are const). - // - // Knowing that, see if you can make levelUp() work as expected - - // it should add the specified amount to the supplied character's - // experience points: - - print("XP before:{}, ", .{glorp.experience}); - - levelUp(glorp, reward_xp); - - print("after:{}.\n", .{glorp.experience}); -} - -fn levelUp(character_access: Character, xp: u32) void { - character_access.experience += xp; -} - -// And there's more! -// -// Data segments (allocated at compile time) and "the stack" -// (allocated at run time) aren't the only places where program data -// can be stored in memory. They're just the most efficient. Sometimes -// we don't know how much memory our program will need until the -// program is running. Also, there is a limit to the size of stack -// memory allotted to programs (often set by your operating system). -// For these occasions, we have "the heap". -// -// You can use as much heap memory as you like (within physical -// limitations, of course), but it's much less efficient to manage -// because there is no built-in CPU support for adding and removing -// items as we have with the stack. Also, depending on the type of -// allocation, your program MAY have to do expensive work to manage -// the use of heap memory. We'll learn about heap allocators later. -// -// Whew! This has been a lot of information. You'll be pleased to know -// that the next exercise gets us back to learning Zig language -// features we can use right away to do more things! diff --git a/exercises/52_slices.zig b/exercises/52_slices.zig deleted file mode 100644 index 98177cd..0000000 --- a/exercises/52_slices.zig +++ /dev/null @@ -1,49 +0,0 @@ -// -// We've seen that passing arrays around can be awkward. Perhaps you -// remember a particularly horrendous function definition from quiz3? -// This function can only take arrays that are exactly 4 items long! -// -// fn printPowersOfTwo(numbers: [4]u16) void { ... } -// -// That's the trouble with arrays - their size is part of the data -// type and must be hard-coded into every usage of that type. This -// digits array is a [10]u8 forever and ever: -// -// var digits = [10]u8{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; -// -// Thankfully, Zig has slices, which let you dynamically point to a -// start item and provide a length. Here are slices of our digit -// array: -// -// const foo = digits[0..1]; // 0 -// const bar = digits[3..9]; // 3 4 5 6 7 8 -// const all = digits[0..]; // 0 1 2 3 4 5 6 7 8 9 -// -// As you can see, a slice [x..y] defines a first item by index x and -// a length y (where y-1 is the index of the last item). Leaving y off -// gives you the rest of the items. -// -// Notice that the type of a slice on an array of u8 items is []u8. -// -const std = @import("std"); - -pub fn main() void { - var cards = [8]u8{ 'A', '4', 'K', '8', '5', '2', 'Q', 'J' }; - - // Please put the first 4 cards in hand1 and the rest in hand2. - const hand1: []u8 = cards[???]; - const hand2: []u8 = cards[???]; - - std.debug.print("Hand1: ", .{}); - printHand(hand1); - - std.debug.print("Hand2: ", .{}); - printHand(hand2); -} - -// Please lend this function a hand. A u8 slice hand, that is. -fn printHand(hand: ???) void { - for (hand) |h| { - std.debug.print("{u} ", .{h}); - } -} diff --git a/exercises/53_slices2.zig b/exercises/53_slices2.zig deleted file mode 100644 index 2456d86..0000000 --- a/exercises/53_slices2.zig +++ /dev/null @@ -1,35 +0,0 @@ -// -// You are perhaps tempted to try slices on strings? They're arrays of -// u8 characters after all, right? Slices on strings work great. -// There's just one catch: don't forget that Zig string literals are -// immutable (const) values. So we need to change the type of slice -// from: -// -// var foo: []u8 = "foobar"[0..3]; -// -// to: -// -// var foo: []const u8 = "foobar"[0..3]; -// -// See if you can fix this Zero Wing-inspired phrase descrambler: -const std = @import("std"); - -pub fn main() void { - const scrambled = "great base for all your justice are belong to us"; - - const base1: []u8 = scrambled[15..23]; - const base2: []u8 = scrambled[6..10]; - const base3: []u8 = scrambled[32..]; - printPhrase(base1, base2, base3); - - const justice1: []u8 = scrambled[11..14]; - const justice2: []u8 = scrambled[0..5]; - const justice3: []u8 = scrambled[24..31]; - printPhrase(justice1, justice2, justice3); - - std.debug.print("\n", .{}); -} - -fn printPhrase(part1: []u8, part2: []u8, part3: []u8) void { - std.debug.print("'{s} {s} {s}.' ", .{part1, part2, part3}); -} diff --git a/exercises/54_manypointers.zig b/exercises/54_manypointers.zig deleted file mode 100644 index 23f2ae5..0000000 --- a/exercises/54_manypointers.zig +++ /dev/null @@ -1,55 +0,0 @@ -// -// You can also make pointers to multiple items without using a slice. -// -// var foo: [4]u8 = [4]u8{ 1, 2, 3, 4 }; -// var foo_slice: []u8 = foo[0..]; -// var foo_ptr: [*]u8 = &foo; -// -// The difference between foo_slice and foo_ptr is that the slice has -// a known length. The pointer doesn't. It is up to YOU to keep track -// of the number of u8s foo_ptr points to! -// -const std = @import("std"); - -pub fn main() void { - // Take a good look at the array type to which we're coercing - // the zen12 string (the REAL nature of strings will be - // revealed when we've learned some additional features): - const zen12: *const [21]u8 = "Memory is a resource."; - // - // It would also have been valid to coerce to a slice: - // const zen12: []const u8 = "..."; - // - // Now let's turn this into a "many pointer": - const zen_manyptr: [*]const u8 = zen12; - - // It's okay to access zen_manyptr just like an array or slice as - // long as you keep track of the length yourself! - // - // A "string" in Zig is a pointer to an array of const u8 values - // or a slice of const u8 values, into one, as we saw above). So, - // we could treat a "many pointer" of const u8 a string as long - // as we can CONVERT IT TO A SLICE. (Hint: we do know the length!) - // - // Please fix this line so the print below statement can print it: - const zen12_string: []const u8 = zen_manyptr; - - // Here's the moment of truth! - std.debug.print("{s}\n", .{zen12_string}); -} -// -// Are all of these pointer types starting to get confusing? -// -// FREE ZIG POINTER CHEATSHEET! (Using u8 as the example type.) -// +---------------+----------------------------------------------+ -// | u8 | one u8 | -// | *u8 | pointer to one u8 | -// | [2]u8 | two u8s | -// | [*]u8 | pointer to unknown number of u8s | -// | [2]const u8 | two immutable u8s | -// | [*]const u8 | pointer to unknown number of immutable u8s | -// | *[2]u8 | pointer to an array of 2 u8s | -// | *const [2]u8 | pointer to an immutable array of 2 u8s | -// | []u8 | slice of u8s | -// | []const u8 | slice of immutable u8s | -// +---------------+----------------------------------------------+ diff --git a/exercises/55_unions.zig b/exercises/55_unions.zig deleted file mode 100644 index 5e08aa1..0000000 --- a/exercises/55_unions.zig +++ /dev/null @@ -1,76 +0,0 @@ -// -// A union lets you store different types and sizes of data at -// the same memory address. How is this possible? The compiler -// sets aside enough memory for the largest thing you might want -// to store. -// -// In this example, an instance of Foo always takes up u64 of -// space memory even if you're currently storing a u8. -// -// const Foo = union { -// small: u8, -// medium: u32, -// large: u64, -// }; -// -// The syntax looks just like a struct, but a Foo can only hold a -// small OR a medium OR a large value. Once a field becomes -// active, the other inactive fields cannot be accessed. To -// change active fields, assign a whole new instance: -// -// var f = Foo{ .small = 5 }; -// f.small += 5; // OKAY -// f.medium = 5432; // ERROR! -// f = Foo{ .medium = 5432 }; // OKAY -// -// Unions can save space in memory because they let you "re-use" -// a space in memory. They also provide a sort of primitive -// polymorphism. Here fooBar() can take a Foo no matter what size -// of unsigned integer it holds: -// -// fn fooBar(f: Foo) void { ... } -// -// Oh, but how does fooBar() know which field is active? Zig has -// a neat way of keeping track, but for now, we'll just have to -// do it manually. -// -// Let's see if we can get this program working! -// -const std = @import("std"); - -// We've just started writing a simple ecosystem simulation. -// Insects will be represented by either bees or ants. Bees store -// the number of flowers they've visited that day and ants just -// store whether or not they're still alive. -const Insect = union { - flowers_visited: u16, - still_alive: bool, -}; - -// Since we need to specify the type of insect, we'll use an -// enum (remember those?). -const AntOrBee = enum { a, b }; - -pub fn main() void { - // We'll just make one bee and one ant to test them out: - var ant = Insect{ .still_alive = true }; - var bee = Insect{ .flowers_visited = 15 }; - - std.debug.print("Insect report! ", .{}); - - // Oops! We've made a mistake here. - printInsect(ant, AntOrBee.c); - printInsect(bee, AntOrBee.c); - - std.debug.print("\n", .{}); -} - -// Eccentric Doctor Zoraptera says that we can only use one -// function to print our insects. Doctor Z is small and sometimes -// inscrutable but we do not question her. -fn printInsect(insect: Insect, what_it_is: AntOrBee) void { - switch (what_it_is) { - .a => std.debug.print("Ant alive is: {}. ", .{insect.still_alive}), - .b => std.debug.print("Bee visited {} flowers. ", .{insect.flowers_visited}), - } -} diff --git a/exercises/56_unions2.zig b/exercises/56_unions2.zig deleted file mode 100644 index e4294db..0000000 --- a/exercises/56_unions2.zig +++ /dev/null @@ -1,64 +0,0 @@ -// -// It is really quite inconvenient having to manually keep track -// of the active field in our union, isn't it? -// -// Thankfully, Zig also has "tagged unions", which allow us to -// store an enum value within our union representing which field -// is active. -// -// const FooTag = enum{ small, medium, large }; -// -// const Foo = union(FooTag) { -// small: u8, -// medium: u32, -// large: u64, -// }; -// -// Now we can use a switch directly on the union to act on the -// active field: -// -// var f = Foo{ .small = 10 }; -// -// switch (f) { -// .small => |my_small| do_something(my_small), -// .medium => |my_medium| do_something(my_medium), -// .large => |my_large| do_something(my_large), -// } -// -// Let's make our Insects use a tagged union (Doctor Zoraptera -// approves). -// -const std = @import("std"); - -const InsectStat = enum { flowers_visited, still_alive }; - -const Insect = union(InsectStat) { - flowers_visited: u16, - still_alive: bool, -}; - -pub fn main() void { - var ant = Insect{ .still_alive = true }; - var bee = Insect{ .flowers_visited = 16 }; - - std.debug.print("Insect report! ", .{}); - - // Could it really be as simple as just passing the union? - printInsect(???); - printInsect(???); - - std.debug.print("\n", .{}); -} - -fn printInsect(insect: Insect) void { - switch (???) { - .still_alive => |a| std.debug.print("Ant alive is: {}. ", .{a}), - .flowers_visited => |f| std.debug.print("Bee visited {} flowers. ", .{f}), - } -} - -// By the way, did unions remind you of optional values and errors? -// Optional values are basically "null unions" and errors use "error -// union types". Now we can add our own unions to the mix to handle -// whatever situations we might encounter: -// union(Tag) { value: u32, toxic_ooze: void } diff --git a/exercises/57_unions3.zig b/exercises/57_unions3.zig deleted file mode 100644 index 142180f..0000000 --- a/exercises/57_unions3.zig +++ /dev/null @@ -1,54 +0,0 @@ -// -// With tagged unions, it gets EVEN BETTER! If you don't have a -// need for a separate enum, you can define an inferred enum with -// your union all in one place. Just use the 'enum' keyword in -// place of the tag type: -// -// const Foo = union(enum) { -// small: u8, -// medium: u32, -// large: u64, -// }; -// -// Let's convert Insect. Doctor Zoraptera has already deleted the -// explicit InsectStat enum for you! -// -const std = @import("std"); - -const Insect = union(InsectStat) { - flowers_visited: u16, - still_alive: bool, -}; - -pub fn main() void { - var ant = Insect{ .still_alive = true }; - var bee = Insect{ .flowers_visited = 17 }; - - std.debug.print("Insect report! ", .{}); - - printInsect(ant); - printInsect(bee); - - std.debug.print("\n", .{}); -} - -fn printInsect(insect: Insect) void { - switch (insect) { - .still_alive => |a| std.debug.print("Ant alive is: {}. ", .{a}), - .flowers_visited => |f| std.debug.print("Bee visited {} flowers. ", .{f}), - } -} - -// Inferred enums are neat, representing the tip of the iceberg -// in the relationship between enums and unions. You can actually -// coerce a union TO an enum (which gives you the active field -// from the union as an enum). What's even wilder is that you can -// coerce an enum to a union! But don't get too excited, that -// only works when the union type is one of those weird zero-bit -// types like void! -// -// Tagged unions, as with most ideas in computer science, have a -// long history going back to the 1960s. However, they're only -// recently becoming mainstream, particularly in system-level -// programming languages. You might have also seen them called -// "variants", "sum types", or even "enums"! -- cgit v1.2.3-ZIG