Moved exercises to exercises because exercises
This commit is contained in:
parent
cf0920de31
commit
55ad7c32f2
45 changed files with 3 additions and 3 deletions
22
exercises/01_hello.zig
Normal file
22
exercises/01_hello.zig
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
//
|
||||
// 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", .{});
|
||||
}
|
||||
|
||||
24
exercises/02_std.zig
Normal file
24
exercises/02_std.zig
Normal file
|
|
@ -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", .{});
|
||||
}
|
||||
|
||||
// Going deeper: imports must be declared as "constants" (with the 'const'
|
||||
// keyword rather than "variables" (with the 'var' keyword) is that they
|
||||
// can only be used at "compile time" rather than "run time". Zig evaluates
|
||||
// const values at compile time. Don't worry if none of this makes sense
|
||||
// yet! See also this answer: https://stackoverflow.com/a/62567550/695615
|
||||
48
exercises/03_assignment.zig
Normal file
48
exercises/03_assignment.zig
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
//
|
||||
// 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;
|
||||
// var bar: i8 = -20;
|
||||
//
|
||||
// Example: foo can hold 8 bits (0 to 255)
|
||||
// bar can hold 16 bits (0 to 65,535)
|
||||
//
|
||||
// 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});
|
||||
}
|
||||
51
exercises/04_arrays.zig
Normal file
51
exercises/04_arrays.zig
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
//
|
||||
// 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[3]; // 5423
|
||||
//
|
||||
// Set values of an array using array[index] notation:
|
||||
//
|
||||
// foo[3] = 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});
|
||||
}
|
||||
47
exercises/05_arrays2.zig
Normal file
47
exercises/05_arrays2.zig
Normal file
|
|
@ -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", .{});
|
||||
}
|
||||
48
exercises/06_strings.zig
Normal file
48
exercises/06_strings.zig
Normal file
|
|
@ -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} {}{}\n",.{d, laugh, major_tom});
|
||||
//
|
||||
// Keen eyes will notice that we've put a 'u' inside the '{}'
|
||||
// placeholder in the format string above. This tells the
|
||||
// print() function to format the values as a UTF-8 character.
|
||||
// If we didn't do this, we'd see '100', which is the decimal
|
||||
// number corresponding with the 'd' character in UTF-8.
|
||||
//
|
||||
// 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!
|
||||
//
|
||||
}
|
||||
24
exercises/07_strings2.zig
Normal file
24
exercises/07_strings2.zig
Normal file
|
|
@ -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("{}\n",.{lyrics});
|
||||
}
|
||||
34
exercises/08_quiz.zig
Normal file
34
exercises/08_quiz.zig
Normal file
|
|
@ -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 {}!\n", .{lang});
|
||||
}
|
||||
32
exercises/09_if.zig
Normal file
32
exercises/09_if.zig
Normal file
|
|
@ -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 out program to print this message!
|
||||
std.debug.print("Foo is 1!\n", .{});
|
||||
} else {
|
||||
std.debug.print("Foo is not 1!\n", .{});
|
||||
}
|
||||
}
|
||||
25
exercises/10_if2.zig
Normal file
25
exercises/10_if2.zig
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
//
|
||||
// If statements are also valid expressions:
|
||||
//
|
||||
// var foo: u8 = if (a) 2 else 3;
|
||||
//
|
||||
// Note: You'll need to declare a variable type when assigning a value
|
||||
// from a statement like this because the compiler isn't smart enough
|
||||
// to infer the type for you.
|
||||
//
|
||||
// This WON'T work:
|
||||
//
|
||||
// var foo = if (a) 2 else 3; // error!
|
||||
//
|
||||
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 = if ???;
|
||||
|
||||
std.debug.print("With the discount, the price is ${}.\n", .{price});
|
||||
}
|
||||
|
||||
34
exercises/11_while.zig
Normal file
34
exercises/11_while.zig
Normal file
|
|
@ -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});
|
||||
}
|
||||
35
exercises/12_while2.zig
Normal file
35
exercises/12_while2.zig
Normal file
|
|
@ -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});
|
||||
}
|
||||
33
exercises/13_while3.zig
Normal file
33
exercises/13_while3.zig
Normal file
|
|
@ -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", .{});
|
||||
}
|
||||
26
exercises/14_while4.zig
Normal file
26
exercises/14_while4.zig
Normal file
|
|
@ -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});
|
||||
}
|
||||
28
exercises/15_for.zig
Normal file
28
exercises/15_for.zig
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
//
|
||||
// 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.
|
||||
33
exercises/16_for2.zig
Normal file
33
exercises/16_for2.zig
Normal file
|
|
@ -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});
|
||||
}
|
||||
28
exercises/17_quiz2.zig
Normal file
28
exercises/17_quiz2.zig
Normal file
|
|
@ -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",.{});
|
||||
}
|
||||
34
exercises/18_functions.zig
Normal file
34
exercises/18_functions.zig
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
//
|
||||
// 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
|
||||
}
|
||||
31
exercises/19_functions2.zig
Normal file
31
exercises/19_functions2.zig
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
//
|
||||
// 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 an 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.
|
||||
}
|
||||
45
exercises/20_quiz3.zig
Normal file
45
exercises/20_quiz3.zig
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
//
|
||||
// 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 ???;
|
||||
}
|
||||
46
exercises/21_errors.zig
Normal file
46
exercises/21_errors.zig
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
//
|
||||
// Believe it or not, sometimes things to 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;
|
||||
}
|
||||
30
exercises/22_errors2.zig
Normal file
30
exercises/22_errors2.zig
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
//
|
||||
// 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 let's 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!", .{});
|
||||
}
|
||||
|
||||
28
exercises/23_errors3.zig
Normal file
28
exercises/23_errors3.zig
Normal file
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
68
exercises/24_errors4.zig
Normal file
68
exercises/24_errors4.zig
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
//
|
||||
// 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;
|
||||
}
|
||||
|
||||
40
exercises/25_errors5.zig
Normal file
40
exercises/25_errors5.zig
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
//
|
||||
// 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 fix().
|
||||
// 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;
|
||||
}
|
||||
|
||||
23
exercises/26_hello2.zig
Normal file
23
exercises/26_hello2.zig
Normal file
|
|
@ -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", .{});
|
||||
}
|
||||
25
exercises/27_defer.zig
Normal file
25
exercises/27_defer.zig
Normal file
|
|
@ -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 ", .{});
|
||||
}
|
||||
29
exercises/28_defer2.zig
Normal file
29
exercises/28_defer2.zig
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
//
|
||||
// Now that you know how "defer" works, let's do something more
|
||||
// interesting with it.
|
||||
//
|
||||
const std = @import("std");
|
||||
|
||||
pub fn main() void {
|
||||
const animals = [_]u8{ 'g', 'c', 'd', 'd', 'g', 'z' };
|
||||
|
||||
for (animals) |a| printAnimal(a);
|
||||
|
||||
|
||||
std.debug.print("done.\n", .{});
|
||||
}
|
||||
|
||||
// This function is _supposed_ to print an animal name in parentheses
|
||||
// like "(Goat) ", but we somehow need to print the end parenthesis
|
||||
// even though this function can return in four different places!
|
||||
fn printAnimal(animal: u8) void {
|
||||
std.debug.print("(", .{});
|
||||
|
||||
std.debug.print(") ", .{}); // <---- how!?
|
||||
|
||||
if (animal == 'g'){ std.debug.print("Goat", .{}); return; }
|
||||
if (animal == 'c'){ std.debug.print("Cat", .{}); return; }
|
||||
if (animal == 'd'){ std.debug.print("Dog", .{}); return; }
|
||||
|
||||
std.debug.print("Unknown", .{});
|
||||
}
|
||||
60
exercises/29_errdefer.zig
Normal file
60
exercises/29_errdefer.zig
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
//
|
||||
// Another common problem is a block of code that could exit in multiple
|
||||
// places due to an error - but that needs to run do something before it
|
||||
// exits (typically to clean up after itself).
|
||||
//
|
||||
// An "errdefer" is a defer that only runs if the block exits with an error:
|
||||
//
|
||||
// {
|
||||
// errdefer cleanup();
|
||||
// try canFail();
|
||||
// }
|
||||
//
|
||||
// The cleanup() function is called ONLY if the "try" statement returns an
|
||||
// error produced by canFail().
|
||||
//
|
||||
const std = @import("std");
|
||||
|
||||
//
|
||||
var counter: u32 = 0;
|
||||
|
||||
const MyErr = error{ GetFail, IncFail };
|
||||
|
||||
pub fn main() void {
|
||||
// We simply quit the entire program if we fail to get a number:
|
||||
var a: u32 = makeNumber() catch return;
|
||||
var b: u32 = makeNumber() catch return;
|
||||
|
||||
std.debug.print("Numbers: {}, {}\n", .{a,b});
|
||||
}
|
||||
|
||||
fn makeNumber() MyErr!u32 {
|
||||
std.debug.print("Getting number...", .{});
|
||||
|
||||
// Please make the "failed" message print ONLY if the makeNumber()
|
||||
// function exits with an error:
|
||||
std.debug.print("failed!\n", .{});
|
||||
|
||||
var num = try getNumber(); // <-- This could fail!
|
||||
|
||||
num = try increaseNumber(num); // <-- This could ALSO fail!
|
||||
|
||||
std.debug.print("got {}. ", .{num});
|
||||
|
||||
return num;
|
||||
}
|
||||
|
||||
fn getNumber() MyErr!u32 {
|
||||
// I _could_ fail...but I don't!
|
||||
return 4;
|
||||
}
|
||||
|
||||
fn increaseNumber(n: u32) MyErr!u32 {
|
||||
// I fail after the first time you run me!
|
||||
if (counter > 0) return MyErr.IncFail;
|
||||
|
||||
// Sneaky, weird global stuff.
|
||||
counter += 1;
|
||||
|
||||
return n + 1;
|
||||
}
|
||||
55
exercises/30_switch.zig
Normal file
55
exercises/30_switch.zig
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
//
|
||||
// The "switch" statement lets you match the possible values of an
|
||||
// expression and perform a different action for each.
|
||||
//
|
||||
// This switch:
|
||||
//
|
||||
// switch (players) {
|
||||
// 1 => startOnePlayerGame(),
|
||||
// 2 => startTwoPlayerGame(),
|
||||
// else => {
|
||||
// alert();
|
||||
// return GameError.TooManyPlayers;
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// Is equivalent to this if/else:
|
||||
//
|
||||
// if (players == 1) startOnePlayerGame();
|
||||
// else if (players == 2) startTwoPlayerGame();
|
||||
// else {
|
||||
// alert();
|
||||
// return GameError.TooManyPlayers;
|
||||
// }
|
||||
//
|
||||
//
|
||||
//
|
||||
const std = @import("std");
|
||||
|
||||
pub fn main() void {
|
||||
const lang_chars = [_]u8{ 26, 9, 7, 42 };
|
||||
|
||||
for (lang_chars) |c| {
|
||||
switch (c) {
|
||||
1 => std.debug.print("A", .{}),
|
||||
2 => std.debug.print("B", .{}),
|
||||
3 => std.debug.print("C", .{}),
|
||||
4 => std.debug.print("D", .{}),
|
||||
5 => std.debug.print("E", .{}),
|
||||
6 => std.debug.print("F", .{}),
|
||||
7 => std.debug.print("G", .{}),
|
||||
8 => std.debug.print("H", .{}),
|
||||
9 => std.debug.print("I", .{}),
|
||||
10 => std.debug.print("J", .{}),
|
||||
// ... we don't need everything in between ...
|
||||
25 => std.debug.print("Y", .{}),
|
||||
26 => std.debug.print("Z", .{}),
|
||||
// Switch statements must be "exhaustive" (there must be a
|
||||
// match for every possible value). Please add an "else"
|
||||
// to this switch to print a question mark "?" when c is
|
||||
// not one of the existing matches.
|
||||
}
|
||||
}
|
||||
|
||||
std.debug.print("\n", .{});
|
||||
}
|
||||
42
exercises/31_switch2.zig
Normal file
42
exercises/31_switch2.zig
Normal file
|
|
@ -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", .{});
|
||||
}
|
||||
38
exercises/32_unreachable.zig
Normal file
38
exercises/32_unreachable.zig
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
//
|
||||
// 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", .{});
|
||||
}
|
||||
49
exercises/33_iferror.zig
Normal file
49
exercises/33_iferror.zig
Normal file
|
|
@ -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;
|
||||
}
|
||||
24
exercises/34_quiz4.zig
Normal file
24
exercises/34_quiz4.zig
Normal file
|
|
@ -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;
|
||||
}
|
||||
49
exercises/35_enums.zig
Normal file
49
exercises/35_enums.zig
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
//
|
||||
// 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", .{});
|
||||
}
|
||||
61
exercises/36_enums2.zig
Normal file
61
exercises/36_enums2.zig
Normal file
|
|
@ -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 {
|
||||
// Remeber 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(
|
||||
\\<p>
|
||||
\\ <span style="color: #{x:0>6}">Red</span>
|
||||
\\ <span style="color: #{x:0>6}">Green</span>
|
||||
\\ <span style="color: #{}">Blue</span>
|
||||
\\</p>
|
||||
, .{
|
||||
@enumToInt(Color.red),
|
||||
@enumToInt(Color.green),
|
||||
@enumToInt(???), // Oops! We're missing something!
|
||||
});
|
||||
}
|
||||
59
exercises/37_structs.zig
Normal file
59
exercises/37_structs.zig
Normal file
|
|
@ -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, .y=27 };
|
||||
// point2 = Point{ .x=7, .y=13, .y=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
|
||||
});
|
||||
}
|
||||
51
exercises/38_structs2.zig
Normal file
51
exercises/38_structs2.zig
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
//
|
||||
// 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});
|
||||
}
|
||||
}
|
||||
36
exercises/39_pointers.zig
Normal file
36
exercises/39_pointers.zig
Normal file
|
|
@ -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 current 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});
|
||||
}
|
||||
27
exercises/40_pointers2.zig
Normal file
27
exercises/40_pointers2.zig
Normal file
|
|
@ -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.*});
|
||||
}
|
||||
41
exercises/41_pointers3.zig
Normal file
41
exercises/41_pointers3.zig
Normal file
|
|
@ -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});
|
||||
}
|
||||
33
exercises/42_pointers4.zig
Normal file
33
exercises/42_pointers4.zig
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
//
|
||||
// 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!
|
||||
}
|
||||
84
exercises/43_pointers5.zig
Normal file
84
exercises/43_pointers5.zig
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
//
|
||||
// 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,
|
||||
experience: u32,
|
||||
};
|
||||
|
||||
pub fn main() void {
|
||||
var glorp = Character{
|
||||
.class = Class.wizard,
|
||||
.gold = 10,
|
||||
.health = 100,
|
||||
.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,
|
||||
});
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue