diff options
author | Nguyễn Gia Phong <mcsinyx@disroot.org> | 2021-02-28 17:18:03 +0700 |
---|---|---|
committer | Nguyễn Gia Phong <mcsinyx@disroot.org> | 2021-02-28 17:18:03 +0700 |
commit | ee9b8fc921f48dc893808e1c9dbfbef321aa362c (patch) | |
tree | 0d0a5247b139ba68d2c2aa2d94e1d631476dbc62 /lang/zig | |
parent | 2b91f9554b326aea138bd8a0acbfaa10d9ad59aa (diff) | |
download | cp-ee9b8fc921f48dc893808e1c9dbfbef321aa362c.tar.gz |
[lang/zig] Learn some Zig
Diffstat (limited to 'lang/zig')
51 files changed, 1936 insertions, 0 deletions
diff --git a/lang/zig/align.zig b/lang/zig/align.zig new file mode 100644 index 0000000..b81740a --- /dev/null +++ b/lang/zig/align.zig @@ -0,0 +1,48 @@ +const arch = std.Target.current.cpu.arch; +const bytesAsSlice = std.mem.bytesAsSlice; +const expect = std.testing.expect; +const sliceAsBytes = std.mem.sliceAsBytes; +const std = @import("std"); + +test "variable alignment" { + var x: i32 = 1234; + const align_of_i32 = @alignOf(@TypeOf(x)); + expect(@TypeOf(&x) == *i32); + expect(*i32 == *align(align_of_i32) i32); + if (arch == .x86_64) + expect(@typeInfo(*i32).Pointer.alignment == 4); +} + +var foo: u8 align(4) = 100; + +test "global variable alignment" { + expect(@typeInfo(@TypeOf(&foo)).Pointer.alignment == 4); + expect(@TypeOf(&foo) == *align(4) u8); + const as_pointer_to_array: *[1]u8 = &foo; + const as_slice: []u8 = as_pointer_to_array; + expect(@TypeOf(as_slice) == []align(4) u8); +} + +fn derp() align(@sizeOf(usize) * 2) i32 { return 1234; } +fn noop1() align(1) void {} +fn noop4() align(4) void {} + +test "function alignment" { + expect(derp() == 1234); + expect(@TypeOf(noop1) == fn() align(1) void); + expect(@TypeOf(noop4) == fn() align(4) void); + noop1(); + noop4(); +} + +test "pointer alignment safety" { + var array align(4) = [_]u32{ 0x11111111, 0x11111111 }; + const bytes = sliceAsBytes(array[0..]); + expect(bar(bytes) == 0x11111111); +} + +fn bar(bytes: []u8) u32 { + const slice4 = bytes[1..5]; + const int_slice = bytesAsSlice(u32, @alignCast(4, slice4)); + return int_slice[0]; +} diff --git a/lang/zig/allowzero.zig b/lang/zig/allowzero.zig new file mode 100644 index 0000000..39233dc --- /dev/null +++ b/lang/zig/allowzero.zig @@ -0,0 +1,7 @@ +const expect = @import("std").testing.expect; + +test "allowzero" { + var zero: usize = 0; + var ptr = @intToPtr(*allowzero i32, zero); + expect(@ptrToInt(ptr) == 0); +} diff --git a/lang/zig/anon-list.zig b/lang/zig/anon-list.zig new file mode 100644 index 0000000..f71bce8 --- /dev/null +++ b/lang/zig/anon-list.zig @@ -0,0 +1,9 @@ +const expect = @import("std").testing.expect; + +test "anonymous list literal syntax" { + var array: [4]u8 = .{11, 22, 33, 44}; + expect(array[0] == 11); + expect(array[1] == 22); + expect(array[2] == 33); + expect(array[3] == 44); +} diff --git a/lang/zig/arrays.zig b/lang/zig/arrays.zig new file mode 100644 index 0000000..d2658c2 --- /dev/null +++ b/lang/zig/arrays.zig @@ -0,0 +1,100 @@ +const expect = @import("std").testing.expect; +const mem = @import("std").mem; + +// array literal +const message = [_]u8{ 'h', 'e', 'l', 'l', 'o' }; + +// get the size of an array +comptime { + expect(message.len == 5); +} + +// A string literal is a pointer to an array literal. +const same_message = "hello"; + +comptime { + expect(mem.eql(u8, &message, same_message)); +} + +test "iterate over an array" { + var sum: usize = 0; + for (message) |byte| + sum += byte; + expect(sum == 'h' + 'e' + 'l' * 2 + 'o'); +} + +// modifiable array +var some_integers: [100]i32 = undefined; + +test "modify an array" { + for (some_integers) |*item, i| + item.* = @intCast(i32, i); + expect(some_integers[10] == 10); + expect(some_integers[99] == 99); +} + +// array concatenation works if the values are known +// at compile time +const part_one = [_]i32{ 1, 2, 3, 4 }; +const part_two = [_]i32{ 5, 6, 7, 8 }; +const all_of_it = part_one ++ part_two; +comptime { + expect(mem.eql(i32, &all_of_it, &[_]i32{ 1, 2, 3, 4, 5, 6, 7, 8 })); +} + +// remember that string literals are arrays +const hello = "hello"; +const world = "world"; +const hello_world = hello ++ " " ++ world; +comptime { + expect(mem.eql(u8, hello_world, "hello world")); +} + +// ** does repeating patterns +const pattern = "ab" ** 3; +comptime { + expect(mem.eql(u8, pattern, "ababab")); +} + +// initialize an array to zero +const all_zero = [_]u16{0} ** 10; + +comptime { + expect(all_zero.len == 10); + expect(all_zero[5] == 0); +} + +// use compile-time code to initialize an array +var fancy_array = init: { + var initial_value: [10]Point = undefined; + for (initial_value) |*pt, i| { + pt.* = Point{ + .x = @intCast(i32, i), + .y = @intCast(i32, i) * 2, + }; + } + break :init initial_value; +}; +const Point = struct { + x: i32, + y: i32, +}; + +test "compile-time array initialization" { + expect(fancy_array[4].x == 4); + expect(fancy_array[4].y == 8); +} + +// call a function to initialize an array +var more_points = [_]Point{makePoint(3)} ** 10; +fn makePoint(x: i32) Point { + return Point{ + .x = x, + .y = x * 2, + }; +} +test "array initialization with function calls" { + expect(more_points[4].x == 3); + expect(more_points[4].y == 6); + expect(more_points.len == 10); +} diff --git a/lang/zig/assign.zig b/lang/zig/assign.zig new file mode 100644 index 0000000..9ce2400 --- /dev/null +++ b/lang/zig/assign.zig @@ -0,0 +1,25 @@ +const x = 1234; +var b: u6 = add(10, a); +const a = add(12, 34); + +fn add(l: u6, r: u6) u6 { + return l + r; +} + +test "var" { + var y: u13 = 5678; + y += 1; + expect(y == 5679); +} + +test "init" { + var z: i1 = undefined; + z = -1; +} + +test "global" { + expect(a == 46); + expect(b == 56); +} + +const expect = @import("std").testing.expect; diff --git a/lang/zig/blocks.zig b/lang/zig/blocks.zig new file mode 100644 index 0000000..5f6f0e8 --- /dev/null +++ b/lang/zig/blocks.zig @@ -0,0 +1,18 @@ +const expect = @import("std").testing.expect; + +test "labeled break from labeled block expression" { + var y: i32 = 123; + + const x = blk: { + y += 1; + break :blk y; + }; + expect(x == y); + expect(y == 124); +} + +test "access variable after block scope" { + { var x: i32 = 1; } + // undeclared + // x += 1; +} diff --git a/lang/zig/c-string.zig b/lang/zig/c-string.zig new file mode 100644 index 0000000..537fa3b --- /dev/null +++ b/lang/zig/c-string.zig @@ -0,0 +1,12 @@ +const std = @import("std"); + +// This is also available as `std.c.printf`. +pub extern "c" fn printf(format: [*:0]const u8, ...) c_int; + +pub fn main() anyerror!void { + _ = printf("Hello, world!\n"); // OK + + const msg = "Hello, world!\n"; + const non_null_terminated_msg: [msg.len]u8 = msg.*; + _ = printf(&non_null_terminated_msg); +} diff --git a/lang/zig/comments.zig b/lang/zig/comments.zig new file mode 100644 index 0000000..3d1c2e2 --- /dev/null +++ b/lang/zig/comments.zig @@ -0,0 +1,11 @@ +const expect = @import("std").testing.expect; + +test "comments" { + // Comments in Zig start with "//" and end at the next LF byte + // (end of line). The below line is a comment, and won't be executed. + + //expect(false); + + const x = true; // another comment + expect(x); +} diff --git a/lang/zig/comptime-vars.zig b/lang/zig/comptime-vars.zig new file mode 100644 index 0000000..c20193d --- /dev/null +++ b/lang/zig/comptime-vars.zig @@ -0,0 +1,16 @@ +const std = @import("std"); +const expect = std.testing.expect; + +test "comptime vars" { + var x: i32 = 1; + comptime var y: i32 = 1; + + x += 1; + y += 1; + + expect(x == 2); + expect(y == 2); + + if (y != 2) + @compileError("wrong y value"); +} diff --git a/lang/zig/defer.zig b/lang/zig/defer.zig new file mode 100644 index 0000000..754b3af --- /dev/null +++ b/lang/zig/defer.zig @@ -0,0 +1,62 @@ +const std = @import("std"); +const expect = std.testing.expect; +const print = std.debug.print; + +// defer will execute an expression at the end of the current scope. +fn deferExample() usize { + var a: usize = 1; + + { + defer a = 2; + a = 1; + } + expect(a == 2); + + a = 5; + return a; +} + +test "defer basics" { + expect(deferExample() == 5); +} + +// If multiple defer statements are specified, they will be executed in +// the reverse order they were run. +fn deferUnwindExample() void { + print("\n", .{}); + + defer print("1 ", .{}); + defer print("2 ", .{}); + // defers are not run if they are never executed. + if (false) { + defer print("3 ", .{}); + } + // somehow this isn't a scope though? + if (true) { + defer print("4 ", .{}); + } +} + +test "defer unwinding" { + deferUnwindExample(); + print("\n", .{}); +} + +// The errdefer keyword is similar to defer, +// but will only execute if the scope returns with an error. +// +// This is especially useful in allowing a function to clean up properly +// on error, and replaces goto error handling tactics as seen in C. +fn deferErrorExample(is_error: bool) !void { + print("\nstart of function\n", .{}); + // This will always be executed on exit + defer print("end of function\n", .{}); + errdefer print("encountered an error!\n", .{}); + if (is_error) + return error.DeferError; +} + +test "errdefer unwinding" { + deferErrorExample(false) catch {}; + deferErrorExample(true) catch {}; +} diff --git a/lang/zig/enum-literal.zig b/lang/zig/enum-literal.zig new file mode 100644 index 0000000..587cfb2 --- /dev/null +++ b/lang/zig/enum-literal.zig @@ -0,0 +1,35 @@ +const expect = @import("std").testing.expect; + +const Color = enum { auto, off, on }; + +test "enum literals" { + const color1: Color = .auto; + const color2 = Color.auto; + expect(color1 == color2); + expect(color1 == .auto); +} + +test "switch using enum literals" { + expect(switch (Color.on) { + .auto => false, + .on => true, + .off => false, + }); +} + +const Number = enum(u8) { one, two, three, _ }; + +test "switch on non-exhaustive enum" { + const number = Number.one; + expect(switch (number) { + .one => true, + .two, + .three => false, + _ => false, + }); + const is_one = switch (number) { + .one => true, + else => false, + }; + expect(is_one); +} diff --git a/lang/zig/enums-extern.zig b/lang/zig/enums-extern.zig new file mode 100644 index 0000000..58610f1 --- /dev/null +++ b/lang/zig/enums-extern.zig @@ -0,0 +1,2 @@ +const Foo = extern enum { a, b, c }; +export fn entry(foo: Foo) void { } diff --git a/lang/zig/enums-packed.zig b/lang/zig/enums-packed.zig new file mode 100644 index 0000000..b5baaab --- /dev/null +++ b/lang/zig/enums-packed.zig @@ -0,0 +1,7 @@ +const expect = @import("std").testing.expect; + +test "packed enum" { + // Warranty enum size. + const Number = packed enum(u2) { one, two, three }; + expect(@sizeOf(Number) == @sizeOf(u8)); +} diff --git a/lang/zig/enums.zig b/lang/zig/enums.zig new file mode 100644 index 0000000..4b6a7a8 --- /dev/null +++ b/lang/zig/enums.zig @@ -0,0 +1,86 @@ +const std = @import("std"); +const expect = std.testing.expect; +const eql = std.mem.eql; + +// Declare an enum. +const Type = enum { ok, not_ok }; + +// Declare a specific instance of the enum variant. +const c = Type.ok; + +test "enum ordinal value" { + // If you want access to the ordinal value of an enum, + // you can specify the tag type. + const Value = enum(u2) { zero, one, two }; + const Value1 = enum { zero, one, two }; + + // Now you can cast between u2 and Value. + // The ordinal value starts from 0, counting up for each member. + expect(@enumToInt(Value.zero) == 0); + expect(@enumToInt(Value.one) == 1); + expect(@enumToInt(Value.two) == 2); + + // Tag type needs not be explicitly specified: + expect(@enumToInt(Value1.zero) == 0); + expect(@enumToInt(Value1.one) == 1); + expect(@enumToInt(Value1.two) == 2); +} + +// You can override the ordinal value for an enum. +const Value2 = enum(u32) { hundred = 100, thousand = 1000, million = 1000000 }; +test "set enum ordinal value" { + expect(@enumToInt(Value2.hundred) == 100); + expect(@enumToInt(Value2.thousand) == 1000); + expect(@enumToInt(Value2.million) == 1000000); +} + +// Enums can have methods, the same as structs and unions. +// Enum methods are not special, they are only namespaced +// functions that you can call with dot syntax. +const Suit = enum { + clubs, + spades, + diamonds, + hearts, + + pub fn isClubs(self: Suit) bool { + return self == Suit.clubs; + } +}; +test "enum method" { + const p = Suit.spades; + expect(!p.isClubs()); +} + +// An enum variant of different types can be switched upon. +const Foo = enum { + string, + number, + none, +}; +test "enum variant switch" { + const p = Foo.number; + const what_is_it = switch (p) { + Foo.string => "this is a string", + Foo.number => "this is a number", + Foo.none => "this is a none", + }; + expect(eql(u8, what_is_it, "this is a number")); +} + +// @TagType can be used to access the integer tag type of an enum. +const Small = enum { one, two, three, four }; +test "@TagType" { + expect(@TagType(Small) == u2); +} + +// @typeInfo tells us the field count and the fields names: +test "@typeInfo" { + expect(@typeInfo(Small).Enum.fields.len == 4); + expect(eql(u8, @typeInfo(Small).Enum.fields[1].name, "two")); +} + +// @tagName gives a []const u8 representation of an enum value: +test "@tagName" { + expect(eql(u8, @tagName(Small.three), "three")); +} diff --git a/lang/zig/errdefer.zig b/lang/zig/errdefer.zig new file mode 100644 index 0000000..812b0f7 --- /dev/null +++ b/lang/zig/errdefer.zig @@ -0,0 +1,17 @@ +fn createFoo(param: i32) !Foo { + const foo = try tryToAllocateFoo(); + // now we have allocated foo. we need to free it if the function fails. + // but we want to return it if the function succeeds. + errdefer deallocateFoo(foo); + + const tmp_buf = allocateTmpBuffer() orelse return error.OutOfMemory; + // tmp_buf is truly a temporary resource, and we for sure want to clean it up + // before this block leaves scope + defer deallocateTmpBuffer(tmp_buf); + + if (param > 1337) return error.InvalidParam; + + // here the errdefer will not run since we're returning success from the function. + // but the defer will run! + return foo; +} diff --git a/lang/zig/error-set.zig b/lang/zig/error-set.zig new file mode 100644 index 0000000..60b01aa --- /dev/null +++ b/lang/zig/error-set.zig @@ -0,0 +1,22 @@ +const expect = @import("std").testing.expect; +const FileOpenError = error { AccessDenied, OutOfMemory, FileNotFound }; +const AllocationError = error { OutOfMemory }; + +fn foo(err: AllocationError) FileOpenError { + return err; +} + +test "coerce subset to superset" { + const err = foo(AllocationError.OutOfMemory); + expect(err == FileOpenError.OutOfMemory); + expect(err == AllocationError.OutOfMemory); + expect(err == error.OutOfMemory); +} + +fn bar(err: FileOpenError) AllocationError { + return err; +} + +test "coerce superset to subset" { + bar(FileOpenError.OutOfMemory) catch {}; +} diff --git a/lang/zig/error-trace.zig b/lang/zig/error-trace.zig new file mode 100644 index 0000000..822f235 --- /dev/null +++ b/lang/zig/error-trace.zig @@ -0,0 +1,44 @@ +test "error return traces" { + try foo(12); +} + +fn foo(x: i32) !void { + if (x >= 5) { + try bar(); + } else { + try bang2(); + } +} + +fn bar() !void { + if (baz()) { + try quux(); + } else |err| switch (err) { + error.FileNotFound => try hello(), + else => try another(), + } +} + +fn baz() !void { + try bang1(); +} + +fn quux() !void { + try bang2(); +} + +fn hello() !void { + try bang2(); +} + +fn another() !void { + try bang1(); +} + +fn bang1() !void { + return error.FileNotFound; +} + +fn bang2() !void { + return error.PermissionDenied; +} diff --git a/lang/zig/error-union.zig b/lang/zig/error-union.zig new file mode 100644 index 0000000..8ec75b2 --- /dev/null +++ b/lang/zig/error-union.zig @@ -0,0 +1,115 @@ +const std = @import("std"); +const maxInt = std.math.maxInt; +const expect = std.testing.expect; + +fn charToDigit(c: u8) u8 { + return switch (c) { + '0'...'9' => c - '0', + 'A'...'Z' => c - 'A' + 10, + 'a'...'z' => c - 'a' + 10, + else => maxInt(u8), + }; +} + +const ParseError = error{ InvalidChar, Overflow }; + +pub fn parseU64(buf: []const u8, radix: u8) ParseError!u64 { + var x: u64 = 0; + + return for (buf) |c| { + const digit = charToDigit(c); + if (digit >= radix) + break error.InvalidChar; + // x *= radix + if (@mulWithOverflow(u64, x, radix, &x)) + break error.Overflow; + // x += digit + if (@addWithOverflow(u64, x, digit, &x)) + break error.Overflow; + } else x; +} + +test "parse u64" { + const result = try parseU64("1234", 10); + expect(result == 1234); +} + +fn doAThing(str: []u8) !void { + const number = parseU64(str, 10) catch |err| return err; + // The same as + const number = try parseU64(str, 10); +} + +test "error switch" { + if (parseU64("42069", 10)) |number| { + expect(number == 42069); + } else |err| switch (err) { + error.Overflow => { + // handle overflow... + }, + // we promise that InvalidChar won't happen + // (or crash in debug mode if it does) + error.InvalidChar => unreachable, + } +} + +test "error union" { + var foo: anyerror!i32 = undefined; + + // Use compile-time reflection to access the type of an error union: + comptime expect(@typeInfo(@TypeOf(foo)).ErrorUnion.payload == i32); + comptime expect(@typeInfo(@TypeOf(foo)).ErrorUnion.error_set == anyerror); + + // Coerce values + foo = 1234; + foo = error.SomeError; +} + +const A = error{ + NotDir, + + /// A doc comment + PathNotFound, +}; +const B = error{ + OutOfMemory, + + /// B doc comment + PathNotFound, +}; + +const C = A || B; + +fn bar() C!void { + return error.NotDir; +} + +test "merge error sets" { + if (bar()) { + @panic("unexpected"); + } else |err| switch (err) { + error.OutOfMemory => @panic("unexpected"), + error.PathNotFound => @panic("unexpected"), + error.NotDir => {}, + } +} + +const Error = error { Overflow }; + +// With an inferred error set +pub fn add_inferred(comptime T: type, a: T, b: T) !T { + var answer: T = undefined; + return if (@addWithOverflow(T, a, b, &answer)) error.Overflow else answer; +} + +// With an explicit error set +pub fn add_explicit(comptime T: type, a: T, b: T) Error!T { + var answer: T = undefined; + return if (@addWithOverflow(T, a, b, &answer)) error.Overflow else answer; +} + +test "inferred error set" { + if (add_inferred(u8, 255, 1)) |_| unreachable else |err| switch (err) { + error.Overflow => {}, // ok + } +} diff --git a/lang/zig/fn.zig b/lang/zig/fn.zig new file mode 100644 index 0000000..0fb3002 --- /dev/null +++ b/lang/zig/fn.zig @@ -0,0 +1,78 @@ +const expect = @import("std").testing.expect; + +// Functions are declared like this +fn add(a: i8, b: i8) i8 { + return if (a == 0) b else a + b; +} + +// The export specifier makes a function externally visible in the generated +// object file, and makes it use the C ABI. +export fn sub(a: i8, b: i8) i8 { return a - b; } + +// The extern specifier is used to declare a function that will be resolved +// at link time, when linking statically, or at runtime, when linking +// dynamically. +extern "c" fn atan2(a: f64, b: f64) f64; + +// The @setCold builtin tells the optimizer that a function is rarely called. +fn abort() noreturn { + @setCold(true); + while (true) {} +} + +// The naked calling convention makes a function not have any function prologue +// or epilogue. This can be useful when integrating with assembly. +fn _start() callconv(.Naked) noreturn { + abort(); +} + +// The inline specifier forces a function to be inlined at all call sites. +// If the function cannot be inlined, it is a compile-time error. +inline fn shiftLeftOne(a: u32) u32 { + return a << 1; +} + +// The pub specifier allows the function to be visible when importing. +// Another file can use @import and call sub2 +pub fn sub2(a: i8, b: i8) i8 { return a - b; } + +// Functions can be used as values and are equivalent to pointers. +fn do_op(fn_call: fn (a: i8, b: i8) i8, op1: i8, op2: i8) i8 { + return fn_call(op1, op2); +} + +test "function" { + expect(do_op(add, 5, 6) == 11); + expect(do_op(sub2, 5, 6) == -1); +} + +const Point = struct { x: i32, y: i32 }; + +fn foo(point: Point) i32 { + // Here, `point` could be a reference, or a copy. The function body + // can ignore the difference and treat it as a value. Be very careful + // taking the address of the parameter - it should be treated as if + // the address will become invalid when the function returns. + return point.x + point.y; +} + +test "pass struct to function" { + expect(foo(Point{ .x = 1, .y = 2 }) == 3); +} + +fn addFortyTwo(x: anytype) @TypeOf(x) { + return x + 42; +} + +test "fn type inference" { + expect(addFortyTwo(1) == 43); + expect(@TypeOf(addFortyTwo(1)) == comptime_int); + const y: i64 = 2; + expect(addFortyTwo(y) == 44); + expect(@TypeOf(addFortyTwo(y)) == i64); +} + +test "fn reflection" { + expect(@typeInfo(@TypeOf(expect)).Fn.return_type.? == void); + expect(@typeInfo(@TypeOf(expect)).Fn.is_var_args == false); +} diff --git a/lang/zig/for.zig b/lang/zig/for.zig new file mode 100644 index 0000000..e816fcc --- /dev/null +++ b/lang/zig/for.zig @@ -0,0 +1,100 @@ +const expect = @import("std").testing.expect; + +test "for basics" { + const items = [_]i32{ 4, 5, 3, 4, 0 }; + var sum: i32 = 0; + + // For loops iterate over slices and arrays. + for (items) |value| { + // Break and continue are supported. + if (value == 0) + continue; + sum += value; + } + expect(sum == 16); + + // To iterate over a portion of a slice, reslice. + for (items[0..1]) |value| + sum += value; + expect(sum == 20); + + // To access the index of iteration, specify a second capture value. + // This is zero-indexed. + var sum2: i32 = 0; + for (items) |value, i| { + expect(@TypeOf(i) == usize); + sum2 += @intCast(i32, i); + } + expect(sum2 == 10); +} + +test "for reference" { + var items = [_]i32{ 3, 4, 2 }; + + // Iterate over the slice by reference by + // specifying that the capture value is a pointer. + for (items) |*value| + value.* += 1; + + expect(items[0] == 4); + expect(items[1] == 5); + expect(items[2] == 3); +} + +test "for else" { + // For allows an else attached to it, the same as a while loop. + var items = [_]?i32{ 3, 4, null, 5 }; + + // For loops can also be used as expressions. + // Similar to while loops, when you break from a for loop, + // the else branch is not evaluated. + var sum: i32 = 0; + const result = for (items) |value| { + if (value != null) + sum += value.?; + } else blk: { + expect(sum == 12); + break :blk sum; + }; + expect(result == 12); +} + +test "nested break" { + var count: usize = 0; + outer: for ([_]i32{ 1, 2, 3, 4, 5 }) |_| { + for ([_]i32{ 1, 2, 3, 4, 5 }) |_| { + count += 1; + break :outer; + } + } + expect(count == 1); +} + +test "nested continue" { + var count: usize = 0; + outer: for ([_]i32{ 1, 2, 3, 4, 5, 6, 7, 8 }) |_| { + for ([_]i32{ 1, 2, 3, 4, 5 }) |_| { + count += 1; + continue :outer; + } + } + expect(count == 8); +} + +fn typeNameLength(comptime T: type) usize { + return @typeName(T).len; +} + +test "inline for loop" { + var sum: usize = 0; + inline for ([_]i32{ 2, 4, 6 }) |i| { + const T = switch (i) { + 2 => f32, + 4 => i8, + 6 => bool, + else => unreachable, + }; + sum += typeNameLength(T); + } + expect(sum == 9); +} diff --git a/lang/zig/hello-again.zig b/lang/zig/hello-again.zig new file mode 100644 index 0000000..1ef4af6 --- /dev/null +++ b/lang/zig/hello-again.zig @@ -0,0 +1,5 @@ +const print = @import("std").debug.print; + +pub fn main() void { + print("Hello, {s}!\n", .{"world"}); +} diff --git a/lang/zig/hello.zig b/lang/zig/hello.zig new file mode 100644 index 0000000..56b2066 --- /dev/null +++ b/lang/zig/hello.zig @@ -0,0 +1,6 @@ +const std = @import("std"); + +pub fn main() !void { + const stdout = std.io.getStdOut().writer(); + try stdout.print("Hello, {}!\n", .{"world"}); +} diff --git a/lang/zig/if.zig b/lang/zig/if.zig new file mode 100644 index 0000000..99de2b5 --- /dev/null +++ b/lang/zig/if.zig @@ -0,0 +1,141 @@ +// If expressions have three uses, corresponding to the three types: +// * bool +// * ?T +// * anyerror!T + +const expect = @import("std").testing.expect; + +test "if expression" { + // If expressions are used instead of a ternary expression. + const a: u32 = 5; + const b: u32 = 4; + const result = if (a != b) 47 else 3089; + expect(result == 47); +} + +test "if boolean" { + // If expressions test boolean conditions. + const a: u32 = 5; + const b: u32 = 4; + if (a != b) { + expect(true); + } else if (a == 9) { + unreachable; + } else { + unreachable; + } +} + +// If expressions test for null. +test "if optional" { + if (@as(?u32, 0)) |value| { + expect(value == 0); + } else { + unreachable; + } + + const b: ?u32 = null; + if (b) |value| { + unreachable; + } else { + expect(true); + } + // To test against null only, use the binary equality operator. + if (b == null) + expect(true); + + // Access the value by reference using a pointer capture. + var c: ?u32 = 3; + if (c) |*value| + value.* = 2; + // The else is not required. + if (c) |value| + expect(value == 2); +} + +// If expressions test for errors. Note the |err| capture on the else. +test "if error union" { + const a: anyerror!u32 = 0; + if (a) |value| { + expect(value == 0); + } else |err| { + unreachable; + } + + const b: anyerror!u32 = error.BadValue; + if (b) |value| { + unreachable; + } else |err| { + expect(err == error.BadValue); + } + + // The else and |err| capture is strictly required. + if (a) |value| { + expect(value == 0); + } else |_| {} + + // To check only the error value, use an empty block expression. + if (b) |_| {} else |err| { + expect(err == error.BadValue); + } + + // Access the value by reference using a pointer capture. + var c: anyerror!u32 = 3; + if (c) |*value| { + value.* = 9; + } else |err| { + unreachable; + } + + if (c) |value| { + expect(value == 9); + } else |err| { + unreachable; + } +} + +// If expressions test for errors before unwrapping optionals. +// The |optional_value| capture's type is ?u32. +test "if error union with optional" { + const a: anyerror!?u32 = 0; + if (a) |optional_value| { + expect(optional_value.? == 0); + } else |err| { + unreachable; + } + + const b: anyerror!?u32 = null; + if (b) |optional_value| { + expect(optional_value == null); + } else |err| { + unreachable; + } + if (b) |*optional_value| { + if (optional_value.*) |_| + unreachable; + } else |err| { + unreachable; + } + + const c: anyerror!?u32 = error.BadValue; + if (c) |optional_value| { + unreachable; + } else |err| { + expect(err == error.BadValue); + } + + // Access the value by reference by using a pointer capture each time. + var d: anyerror!?u32 = 3; + if (d) |*optional_value| { + if (optional_value.*) |*value| { + value.* = 9; + } + } else |err| { + unreachable; + } + if (d) |optional_value| { + expect(optional_value.? == 9); + } else |err| { + unreachable; + } +} diff --git a/lang/zig/infer-list-literal.zig b/lang/zig/infer-list-literal.zig new file mode 100644 index 0000000..ab5c770 --- /dev/null +++ b/lang/zig/infer-list-literal.zig @@ -0,0 +1,13 @@ +const expect = @import("std").testing.expect; + +test "fully anonymous list literal" { + dump(.{ @as(u32, 1234), @as(f64, 12.34), true, "hi"}); +} + +fn dump(args: anytype) void { + expect(args.@"0" == 1234); + expect(args.@"1" == 12.34); + expect(args.@"2"); + expect(args.@"3"[0] == 'h'); + expect(args.@"3"[1] == 'i'); +} diff --git a/lang/zig/multidim.zig b/lang/zig/multidim.zig new file mode 100644 index 0000000..bf30c80 --- /dev/null +++ b/lang/zig/multidim.zig @@ -0,0 +1,20 @@ +const expect = @import("std").testing.expect; + +const mat4x4 = [4][4]f32{ + [_]f32{ 1.0, 0.0, 0.0, 0.0 }, + [_]f32{ 0.0, 1.0, 0.0, 1.0 }, + [_]f32{ 0.0, 0.0, 1.0, 0.0 }, + [_]f32{ 0.0, 0.0, 0.0, 1.0 }, +}; + +test "multidimensional arrays" { + // Access the 2D array by indexing the outer array, + // and then the inner array. + expect(mat4x4[1][1] == 1.0); + + // Here we iterate with for loops. + for (mat4x4) |row, row_index| + for (row) |cell, column_index| + if (row_index == column_index) + expect(cell == 1.0); +} diff --git a/lang/zig/namespaced-global.zig b/lang/zig/namespaced-global.zig new file mode 100644 index 0000000..2d06ced --- /dev/null +++ b/lang/zig/namespaced-global.zig @@ -0,0 +1,15 @@ +const std = @import("std"); +const expect = std.testing.expect; + +test "namespaced global variable" { + expect(foo() == 1235); + expect(foo() == 1236); +} + +fn foo() i32 { + const S = struct { + var x: i32 = 1234; + }; + S.x += 1; + return S.x; +} diff --git a/lang/zig/noreturn.zig b/lang/zig/noreturn.zig new file mode 100644 index 0000000..6f88de1 --- /dev/null +++ b/lang/zig/noreturn.zig @@ -0,0 +1,10 @@ +const expect = @import("std").testing.expect; + +fn foo(condition: bool, b: u32) void { + const a = if (condition) b else return; + @panic("do something with a"); +} + +test "noreturn" { + foo(false, 1); +} diff --git a/lang/zig/opaque.zig b/lang/zig/opaque.zig new file mode 100644 index 0000000..803d19b --- /dev/null +++ b/lang/zig/opaque.zig @@ -0,0 +1,12 @@ +// Stuff for C-compat +const Derp = opaque {}; +const Wat = opaque {}; + +extern fn bar(d: *Derp) void; +fn foo(w: *Wat) callconv(.C) void { + bar(w); +} + +test "call foo" { + foo(undefined); +} diff --git a/lang/zig/pointers.zig b/lang/zig/pointers.zig new file mode 100644 index 0000000..92c0d9d --- /dev/null +++ b/lang/zig/pointers.zig @@ -0,0 +1,93 @@ +const std = @import("std"); +const expect = std.testing.expect; +const bytesAsSlice = std.mem.bytesAsSlice; + +test "address of syntax" { + // Get the address of a variable: + const x: i32 = 1234; + const x_ptr = &x; + + // Dereference a pointer: + expect(x_ptr.* == 1234); + + // When you get the address of a const variable, + // you get a const pointer to a single item. + expect(@TypeOf(x_ptr) == *const i32); + + // If you want to mutate the value, + // you'd need an address of a mutable variable: + var y: i32 = 5678; + const y_ptr = &y; + expect(@TypeOf(y_ptr) == *i32); + y_ptr.* += 1; + expect(y_ptr.* == 5679); +} + +test "pointer array access" { + // Taking an address of an individual element gives a + // pointer to a single item. This kind of pointer + // does not support pointer arithmetic. + var array = [_]u8{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; + const ptr = &array[2]; + expect(@TypeOf(ptr) == *u8); + + expect(array[2] == 3); + ptr.* += 1; + expect(array[2] == 4); +} + +test "pointer slicing" { + var array = [_]u8{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; + const slice = array[2..4]; + expect(slice.len == 2); + + expect(array[3] == 4); + slice[1] += 1; + expect(array[3] == 5); +} + +test "comptime pointers" { + comptime { + var x: i32 = 1; + const ptr = &x; + ptr.* += 1; + x += 1; + expect(ptr.* == 3); + } +} + +test "@ptrToInt and @intToPtr" { + const ptr = @intToPtr(*i32, 0xdeadbee0); + const addr = @ptrToInt(ptr); + expect(@TypeOf(addr) == usize); + expect(addr == 0xdeadbee0); +} + +test "comptime @intToPtr" { + comptime { + const ptr = @intToPtr(*i32, 0xdeadbee0); + const addr = @ptrToInt(ptr); + expect(@TypeOf(addr) == usize); + expect(addr == 0xdeadbee0); + } +} + +test "volatile" { + const mmio_ptr = @intToPtr(*volatile u8, 0x12345678); + expect(@TypeOf(mmio_ptr) == *volatile u8); +} + +test "pointer casting" { + const bytes align(@alignOf(u32)) = [_]u8{ 0x12 } ** 4; + const u32_ptr = @ptrCast(*const u32, &bytes); + expect(u32_ptr.* == 0x12121212); + + // Even this example is contrived, + // there are better ways to do the above than pointer casting. + // For example, using a slice narrowing cast: + const u32_value = bytesAsSlice(u32, bytes[0..])[0]; + expect(u32_value == 0x12121212); + + // And even another way, the most straightforward way to do it: + expect(@bitCast(u32, bytes) == 0x12121212); +} diff --git a/lang/zig/sentinel-terminated.zig b/lang/zig/sentinel-terminated.zig new file mode 100644 index 0000000..dd9775c --- /dev/null +++ b/lang/zig/sentinel-terminated.zig @@ -0,0 +1,10 @@ +const std = @import("std"); +const expect = std.testing.expect; + +test "null terminated array" { + const array = [_:0]u8 {1, 2, 3, 4}; + + expect(@TypeOf(array) == [4:0]u8); + expect(array.len == 4); + expect(array[4] == 0); +} diff --git a/lang/zig/shadow.zig b/lang/zig/shadow.zig new file mode 100644 index 0000000..9a37722 --- /dev/null +++ b/lang/zig/shadow.zig @@ -0,0 +1,10 @@ +test "separate scopes" { + { const pi = 3.14; } + { var pi: bool = true; } +} + +test "inside test block" { + // That's right, no shadowing. + const pi = 3.14; + { var pi: i32 = 1234; } +} diff --git a/lang/zig/slices.zig b/lang/zig/slices.zig new file mode 100644 index 0000000..8356c26 --- /dev/null +++ b/lang/zig/slices.zig @@ -0,0 +1,77 @@ +const bufPrint = std.fmt.bufPrint; +const eql = std.mem.eql; +const expect = std.testing.expect; +const std = @import("std"); + +test "basic slices" { + var array = [_]i32{ 1, 2, 3, 4 }; + // A slice is a pointer and a length. The difference between an array and + // a slice is that the array's length is part of the type and known at + // compile-time, whereas the slice's length is known at runtime. + // Both can be accessed with the `len` field. + var known_at_runtime_zero: usize = 0; + const slice = array[known_at_runtime_zero..array.len]; + expect(&slice[0] == &array[0]); + expect(slice.len == array.len); + + // Using the address-of operator on a slice gives a pointer to a single + // item, while using the `ptr` field gives an unknown length pointer. + expect(@TypeOf(slice.ptr) == [*]i32); + expect(@TypeOf(&slice[0]) == *i32); + expect(@ptrToInt(slice.ptr) == @ptrToInt(&slice[0])); + + // Slices have array bounds checking. If you try to access something out + // of bounds, you'll get a safety check failure: + // slice[10] += 1; + + // Note that `slice.ptr` does not invoke safety checking, while `&slice[0]` + // asserts that the slice has len >= 1. +} + +test "using slices for strings" { + // Zig has no concept of strings. String literals are const pointers to + // arrays of u8, and by convention parameters that are "strings" are + // expected to be UTF-8 encoded slices of u8. + // Here we coerce [5]u8 to []const u8 + const hello: []const u8 = "hello"; + const world: []const u8 = "世界"; + + var all_together: [100]u8 = undefined; + // String concatenation example. + const hello_world = try bufPrint( + all_together[0..], "{} {}", .{ hello, world }); + + // Generally, you can use UTF-8 and not worry about whether something is a + // string. If you don't need to deal with individual characters, no need + // to decode. + expect(eql(u8, hello_world, "hello 世界")); +} + +test "slice pointer" { + var array: [10]u8 = undefined; + const ptr = &array; + + // You can use slicing syntax to convert a pointer into a slice: + const slice = ptr[0..5]; + slice[2] = 3; + expect(slice[2] == 3); + // The slice is mutable because we sliced a mutable pointer. + // Furthermore, it is actually a pointer to an array, since the start + // and end indexes were both comptime-known. + expect(@TypeOf(slice) == *[5]u8); + + // You can also slice a slice: + const slice2 = slice[2..3]; + expect(slice2.len == 1); + expect(slice2[0] == 3); +} + +test "null terminated slice" { + const string = "hello"; + const slice: [:0]const u8 = string; + expect(@TypeOf(string) == *const [5:0]u8); + expect(@TypeOf(slice) == [:0]const u8); + + expect(slice.len == 5); + expect(slice[5] == 0); +} diff --git a/lang/zig/string.zig b/lang/zig/string.zig new file mode 100644 index 0000000..af6bdc1 --- /dev/null +++ b/lang/zig/string.zig @@ -0,0 +1,14 @@ +const expect = @import("std").testing.expect; +const mem = @import("std").mem; + +test "string literals" { + const bytes = "hello"; + expect(@TypeOf(bytes) == *const [5:0]u8); + expect(bytes.len == 5); + expect(bytes[1] == 'e'); + expect(bytes[5] == 0); + expect('e' == '\x65'); + expect('\u{1f4a9}' == 128169); + expect('💯' == 128175); + expect(mem.eql(u8, "hello", "h\x65llo")); +} diff --git a/lang/zig/struct-anon.zig b/lang/zig/struct-anon.zig new file mode 100644 index 0000000..a666bd0 --- /dev/null +++ b/lang/zig/struct-anon.zig @@ -0,0 +1,20 @@ +const expect = @import("std").testing.expect; + +test "fully anonymous struct" { + dump(.{ + .int = @as(u32, 1234), + .float = @as(f64, 12.34), + .b = true, + .s = "hi", + }); +} + +fn dump(args: anytype) void { + expect(args.int == 1234); + expect(args.float == 12.34); + expect(args.b); + expect(args.s[0] == 'h'); + expect(args.s[1] == 'i'); + // Type is well infered, i.e. the following fail at comp time: + // expect(args.a); +} diff --git a/lang/zig/struct-default.zig b/lang/zig/struct-default.zig new file mode 100644 index 0000000..941d201 --- /dev/null +++ b/lang/zig/struct-default.zig @@ -0,0 +1,11 @@ +const expect = @import("std").testing.expect; + +const Foo = struct { + a: i32 = 1234, + b: i32, +}; + +test "default struct initialization fields" { + const x = Foo{ .b = 5 }; + expect(x.a + x.b == 1239); +} diff --git a/lang/zig/struct-name.zig b/lang/zig/struct-name.zig new file mode 100644 index 0000000..8c3affc --- /dev/null +++ b/lang/zig/struct-name.zig @@ -0,0 +1,14 @@ +const print = @import("std").debug.print; + +pub fn main() void { + const Foo = struct {}; + print("variable: {}\n", .{@typeName(Foo)}); + print("anonymous: {}\n", .{@typeName(struct {})}); + print("function: {}\n", .{@typeName(List(i32))}); +} + +fn List(comptime T: type) type { + return struct { + x: T, + }; +} diff --git a/lang/zig/struct-packed.zig b/lang/zig/struct-packed.zig new file mode 100644 index 0000000..f493661 --- /dev/null +++ b/lang/zig/struct-packed.zig @@ -0,0 +1,73 @@ +const std = @import("std"); +const endian = std.builtin.endian; +const expect = std.testing.expect; + +const Full = packed struct { + number: u16, +}; +const Divided = packed struct { + half1: u8, + quarter3: u4, + quarter4: u4, +}; + +fn doTheTest() void { + expect(@sizeOf(Full) == 2); + expect(@sizeOf(Divided) == 2); + var full = Full{ .number = 0x1234 }; + var divided = @bitCast(Divided, full); + switch (endian) { + .Big => { + expect(divided.half1 == 0x12); + expect(divided.quarter3 == 0x3); + expect(divided.quarter4 == 0x4); + }, + .Little => { + expect(divided.half1 == 0x34); + expect(divided.quarter3 == 0x2); + expect(divided.quarter4 == 0x1); + }, + } +} + +test "@bitCast between packed structs" { + doTheTest(); + comptime doTheTest(); +} + +const BitField = packed struct { + a: u3, + b: u3, + c: u2, +}; + +var foo = BitField{ + .a = 1, + .b = 2, + .c = 3, +}; + +fn bar(x: *const u3) u3 { + return x.*; +} + +test "pointer to non-byte-aligned field" { + const ptr = &foo.b; + expect(ptr.* == 2); + // The pointer to a non-byte-aligned field has special properties + // and cannot be passed when a normal pointer is expected: + // expect(bar(ptr) == 2); + // Here's why: + expect(@ptrToInt(&foo.a) == @ptrToInt(&foo.b)); + expect(@ptrToInt(&foo.a) == @ptrToInt(&foo.c)); + + comptime { + expect(@bitOffsetOf(BitField, "a") == 0); + expect(@bitOffsetOf(BitField, "b") == 3); + expect(@bitOffsetOf(BitField, "c") == 6); + + expect(@byteOffsetOf(BitField, "a") == 0); + expect(@byteOffsetOf(BitField, "b") == 0); + expect(@byteOffsetOf(BitField, "c") == 0); + } +} diff --git a/lang/zig/struct-result.zig b/lang/zig/struct-result.zig new file mode 100644 index 0000000..fcd556a --- /dev/null +++ b/lang/zig/struct-result.zig @@ -0,0 +1,7 @@ +const expect = @import("std").testing.expect; +const Point = struct { x: i32, y: i32 }; + +test "anonymous struct literal" { + const pt: Point = .{ .x = 13, .y = 67 }; + expect(pt.x + pt.y == 80); +} diff --git a/lang/zig/structs.zig b/lang/zig/structs.zig new file mode 100644 index 0000000..d90f4ff --- /dev/null +++ b/lang/zig/structs.zig @@ -0,0 +1,133 @@ +// Declare a struct. +// Zig gives no guarantees about the order of fields and the size of +// the struct but the fields are guaranteed to be ABI-aligned. +const Point = struct { + x: f32, + y: f32, +}; + +// Maybe we want to pass it to OpenGL so we want to be particular about +// how the bytes are arranged. +const Point2 = packed struct { + x: f32, + y: f32, +}; + +// Declare an instance of a struct. +const p = Point { + .x = 0.12, + .y = 0.34, +}; + +// Maybe we're not ready to fill out some of the fields. +var p2 = Point { + .x = 0.12, + .y = undefined, +}; + +// Structs can have methods +// Struct methods are not special, they are only namespaced +// functions that you can call with dot syntax. +const Vec3 = struct { + x: f32, + y: f32, + z: f32, + + pub fn init(x: f32, y: f32, z: f32) Vec3 { + return Vec3 { + .x = x, + .y = y, + .z = z, + }; + } + + pub fn dot(self: Vec3, other: Vec3) f32 { + return self.x * other.x + self.y * other.y + self.z * other.z; + } +}; + +const expect = @import("std").testing.expect; +test "dot product" { + const v1 = Vec3.init(1.0, 0.0, 0.0); + const v2 = Vec3.init(0.0, 1.0, 0.0); + expect(v1.dot(v2) == 0.0); + + // Other than being available to call with dot syntax, struct methods are + // not special. You can reference them as any other declaration inside + // the struct: + expect(Vec3.dot(v1, v2) == 0.0); +} + +// Structs can have global declarations. +// Structs can have 0 fields. +const Empty = struct { + pub const PI = 3.14; +}; +test "struct namespaced variable" { + expect(Empty.PI == 3.14); + expect(@sizeOf(Empty) == 0); + + // you can still instantiate an empty struct + const does_nothing = Empty {}; +} + +// struct field order is determined by the compiler for optimal performance. +// however, you can still calculate a struct base pointer given a field pointer: +fn setYBasedOnX(x: *f32, y: f32) void { + const point = @fieldParentPtr(Point, "x", x); + point.y = y; +} +test "field parent pointer" { + var point = Point { + .x = 0.1234, + .y = 0.5678, + }; + setYBasedOnX(&point.x, 0.9); + expect(point.y == 0.9); +} + +// You can return a struct from a function. +// This is how we do generics in Zig: +fn LinkedList(comptime T: type) type { + return struct { + pub const Node = struct { + prev: ?*Node, + next: ?*Node, + data: T, + }; + + first: ?*Node, + last: ?*Node, + len: usize, + }; +} + +test "linked list" { + // Functions called at compile-time are memoized. + // This means you can do this: + expect(LinkedList(i32) == LinkedList(i32)); + + var list = LinkedList(i32) { + .first = null, + .last = null, + .len = 0, + }; + expect(list.len == 0); + + // Since types are first class values you can instantiate the type + // by assigning it to a variable: + const ListOfInts = LinkedList(i32); + expect(ListOfInts == LinkedList(i32)); + + var node = ListOfInts.Node { + .prev = null, + .next = null, + .data = 1234, + }; + var list2 = LinkedList(i32) { + .first = &node, + .last = &node, + .len = 1, + }; + expect(list2.first.?.data == 1234); +} diff --git a/lang/zig/switch-enum-literal.zig b/lang/zig/switch-enum-literal.zig new file mode 100644 index 0000000..550de93 --- /dev/null +++ b/lang/zig/switch-enum-literal.zig @@ -0,0 +1,13 @@ +const std = @import("std"); +const expect = std.testing.expect; + +const Color = enum { auto, off, on }; + +test "enum literals with switch" { + const color = Color.off; + const result = switch (color) { + .auto, .on => false, + .off => true, + }; + expect(result); +} diff --git a/lang/zig/switch-exhaust.zig b/lang/zig/switch-exhaust.zig new file mode 100644 index 0000000..4777f19 --- /dev/null +++ b/lang/zig/switch-exhaust.zig @@ -0,0 +1,9 @@ +const Color = enum { auto, off, on }; + +test "exhaustive switching" { + const color = Color.off; + switch (color) { + Color.auto => {}, + Color.on => {}, + } +} diff --git a/lang/zig/switch.zig b/lang/zig/switch.zig new file mode 100644 index 0000000..7fd3da5 --- /dev/null +++ b/lang/zig/switch.zig @@ -0,0 +1,86 @@ +const std = @import("std"); +const expect = std.testing.expect; +const os = std.Target.current.os.tag; + +test "switch simple" { + const a: u64 = 10; + const zz: u64 = 103; + + // All branches of a switch expression must be able to be coerced + // to a common type. Branches cannot fallthrough. If fallthrough behavior + // is desired, combine the cases and use an if. + const b = switch (a) { + // Multiple cases can be combined via a ',' + 1, 2, 3 => 0, + + // Ranges can be specified using the ... syntax. + // These are inclusive both ends. + 5...100 => 1, + + // Branches can be arbitrarily complex. + 101 => blk: { + const c: u64 = 5; + break :blk c * 2 + 1; + }, + + // Switching on arbitrary expressions is allowed as long as the + // expression is known at compile-time. + zz => zz, + blk: { + const d: u32 = 5; + const e: u32 = 100; + break :blk d + e; + } => 107, + + // The else branch catches everything not already captured. + // Else branches are mandatory unless the entire range of values + // is handled. + else => 9, + }; + + expect(b == 1); +} + +// Switch expressions can be used outside a function: +const os_msg = switch (os) { + .linux => "we found a linux user", + else => "not a linux user", +}; + +// Inside a function, switch statements implicitly are compile-time +// evaluated if the target expression is compile-time known. +test "switch inside function" { + switch (os) { + // On an OS other than fuchsia, block is not even analyzed, + // so this compile error is not triggered. + // On fuchsia this compile error would be triggered. + .fuchsia => @compileError("fuchsia not supported"), + else => {}, + } +} + +test "switch on tagged union" { + const Point = struct { x: u8, y: u8 }; + const Item = union(enum) { a: u32, c: Point, d, e: u32 }; + var a = Item{ .c = Point{ .x = 1, .y = 2 } }; + + // Switching on more complex enums is allowed. + const b = switch (a) { + // A capture group is allowed on a match, and will return the enum + // value matched. If the payload types of both cases are the same + // they can be put into the same switch prong. + Item.a, Item.e => |item| item, + + // A reference to the matched value can be obtained using `*` syntax. + Item.c => |*item| blk: { + item.*.x += 1; + break :blk 6; + }, + + // No else is required if the types cases was exhaustively handled + Item.d => 8, + }; + + expect(b == 6); + expect(a.c.x == 2); +} diff --git a/lang/zig/tls.zig b/lang/zig/tls.zig new file mode 100644 index 0000000..b47b17c --- /dev/null +++ b/lang/zig/tls.zig @@ -0,0 +1,22 @@ +const assert = std.debug.assert; +const std = @import("std"); +const spawn = std.Thread.spawn; + +threadlocal var x: i32 = 1234; + +test "thread local storage" { + const thread1 = try spawn({}, testTls); + defer thread1.wait(); + + const thread2 = try spawn({}, testTls); + defer thread2.wait(); + + // Main thread + testTls({}); +} + +fn testTls(context: void) void { + assert(x == 1234); + x += 1; + assert(x == 1235); +} diff --git a/lang/zig/union-anon.zig b/lang/zig/union-anon.zig new file mode 100644 index 0000000..d26dcae --- /dev/null +++ b/lang/zig/union-anon.zig @@ -0,0 +1,13 @@ +const expect = @import("std").testing.expect; +const Number = union { int: i32, float: f64 }; + +fn makeNumber() Number { + return .{.float = 12.34}; +} + +test "anonymous union literal syntax" { + var i: Number = .{.int = 42}; + var f = makeNumber(); + expect(i.int == 42); + expect(f.float == 12.34); +} diff --git a/lang/zig/union-tag.zig b/lang/zig/union-tag.zig new file mode 100644 index 0000000..6954c73 --- /dev/null +++ b/lang/zig/union-tag.zig @@ -0,0 +1,68 @@ +const eql = std.mem.eql; +const expect = std.testing.expect; +const std = @import("std"); + +const ComplexTypeTag = enum { ok, not_ok }; +const ComplexType = union(ComplexTypeTag) { ok: u8, not_ok: void }; + +test "switch on tagged union" { + const c = ComplexType{ .ok = 42 }; + expect(@as(ComplexTypeTag, c) == ComplexTypeTag.ok); + + switch (c) { + ComplexTypeTag.ok => |value| expect(value == 42), + ComplexTypeTag.not_ok => unreachable, + } +} + +test "@TagType" { + expect(@TagType(ComplexType) == ComplexTypeTag); +} + +test "coerce to enum" { + const c1: ComplexType = ComplexType{ .ok = 42 }; + const c2: ComplexTypeTag = ComplexType.not_ok; + expect(c1 == .ok); + expect(c2 == .not_ok); +} + +test "modify tagged union in switch" { + var c = ComplexType{ .ok = 42 }; + expect(c == .ok); + + switch (c) { + ComplexTypeTag.ok => |*value| value.* += 1, + ComplexTypeTag.not_ok => unreachable, + } + + expect(c.ok == 43); +} + +const Variant = union(enum) { + int: i32, + boolean: bool, + + // void can be omitted when inferring enum tag type. + none, + + fn truthy(self: Variant) bool { + return switch (self) { + Variant.int => |x_int| x_int != 0, + Variant.boolean => |x_bool| x_bool, + Variant.none => false, + }; + } +}; + +test "union method" { + var v1 = Variant{ .int = 420 }; + var v2 = Variant{ .boolean = false }; + + expect(v1.truthy()); + expect(!v2.truthy()); +} + +const Small2 = union(enum) { a: i32, b: bool, c: u8 }; +test "@tagName" { + expect(eql(u8, @tagName(Small2.a), "a")); +} diff --git a/lang/zig/union.zig b/lang/zig/union.zig new file mode 100644 index 0000000..a9445fd --- /dev/null +++ b/lang/zig/union.zig @@ -0,0 +1,15 @@ +const expect = @import("std").testing.expect; +const Payload = union { + int: i64, + float: f64, + boolean: bool, +}; + +test "simple union" { + var payload = Payload{ .int = 1234 }; + expect(payload.int == 1234); + // Nope this is not how it works. + // payload.float = 12.34; + payload = Payload{ .float = 12.34 }; + expect(payload.float == 12.34); +} diff --git a/lang/zig/unreachable.zig b/lang/zig/unreachable.zig new file mode 100644 index 0000000..26463a9 --- /dev/null +++ b/lang/zig/unreachable.zig @@ -0,0 +1,22 @@ +test "basic math" { + const x = 1; + const y = 2; + if (x + y != 3) + unreachable; +} + +fn assert(ok: bool) void { + if (!ok) unreachable; // assertion failure +} + +// This test will fail because we hit unreachable. +test "this will fail" { + assert(false); +} + +// The type of unreachable is noreturn. +test "type of unreachable" { + // However this assertion will still fail because + // evaluating unreachable at compile-time is a compile error. + comptime assert(@TypeOf(unreachable) == noreturn); +} diff --git a/lang/zig/values.zig b/lang/zig/values.zig new file mode 100644 index 0000000..4095da5 --- /dev/null +++ b/lang/zig/values.zig @@ -0,0 +1,54 @@ +// Top-level declarations are order-independent: +const print = std.debug.print; +const std = @import("std"); +const os = std.os; +const assert = std.debug.assert; + +pub fn main() void { + // integers + const one_plus_one: i32 = 1 + 1; + print("1 + 1 = {}\n", .{one_plus_one}); + + // floats + const seven_div_three: f32 = 7.0 / 3.0; + print("7.0 / 3.0 = {}\n", .{seven_div_three}); + + // boolean + print("{}\n{}\n{}\n", .{ + true and false, + true or false, + !true, + }); + + // optional + var optional_value: ?[]const u8 = null; + assert(optional_value == null); + + print("\noptional 1\ntype: {}\nvalue: {}\n", .{ + @typeName(@TypeOf(optional_value)), + optional_value, + }); + + optional_value = "hi"; + assert(optional_value != null); + + print("\noptional 2\ntype: {}\nvalue: {}\n", .{ + @typeName(@TypeOf(optional_value)), + optional_value, + }); + + // error union + var number_or_error: anyerror!i32 = error.ArgNotFound; + + print("\nerror union 1\ntype: {}\nvalue: {}\n", .{ + @typeName(@TypeOf(number_or_error)), + number_or_error, + }); + + number_or_error = 1234; + + print("\nerror union 2\ntype: {}\nvalue: {}\n", .{ + @typeName(@TypeOf(number_or_error)), + number_or_error, + }); +} diff --git a/lang/zig/while-inline.zig b/lang/zig/while-inline.zig new file mode 100644 index 0000000..2af1160 --- /dev/null +++ b/lang/zig/while-inline.zig @@ -0,0 +1,20 @@ +const expect = @import("std").testing.expect; + +fn typeNameLength(comptime T: type) usize { + return @typeName(T).len; +} + +test "inline while loop" { + comptime var i = 0; + var sum: usize = 0; + inline while (i < 3) : (i += 1) { + const T = switch (i) { + 0 => f32, + 1 => i8, + 2 => bool, + else => unreachable, + }; + sum += typeNameLength(T); + } + expect(sum == 9); +} diff --git a/lang/zig/while-unpack.zig b/lang/zig/while-unpack.zig new file mode 100644 index 0000000..d10c385 --- /dev/null +++ b/lang/zig/while-unpack.zig @@ -0,0 +1,43 @@ +const expect = @import("std").testing.expect; + +var numbers_left: u32 = undefined; + +fn eventuallyNullSequence() ?u32 { + return if (numbers_left == 0) null else blk: { + numbers_left -= 1; + break :blk numbers_left; + }; +} + +fn eventuallyErrorSequence() anyerror!u32 { + return if (numbers_left == 0) error.ReachedZero else blk: { + numbers_left -= 1; + break :blk numbers_left; + }; +} + +test "while null capture" { + var sum1: u32 = 0; + numbers_left = 3; + while (eventuallyNullSequence()) |value| + sum1 += value; + expect(sum1 == 3); + + var sum2: u32 = 0; + numbers_left = 3; + while (eventuallyNullSequence()) |value| { + sum2 += value; + } else { + expect(sum2 == 3); + } +} + +test "while error union capture" { + var sum: u32 = 0; + numbers_left = 3; + while (eventuallyErrorSequence()) |value| { + sum += value; + } else |err| { + expect(err == error.ReachedZero); + } +} diff --git a/lang/zig/while.zig b/lang/zig/while.zig new file mode 100644 index 0000000..9fe87e9 --- /dev/null +++ b/lang/zig/while.zig @@ -0,0 +1,73 @@ +const expect = @import("std").testing.expect; + +test "while basic" { + var i: usize = 0; + while (i < 10) + i += 1; + expect(i == 10); +} + +test "while break" { + var i: usize = 0; + while (true) { + if (i == 10) + break; + i += 1; + } + expect(i == 10); +} + +test "while continue" { + var i: usize = 0; + while (true) { + i += 1; + if (i < 10) + continue; + break; + } + expect(i == 10); +} + +test "while loop continue expression" { + var i: usize = 0; + while (i < 10) : (i += 1) {} + expect(i == 10); +} + +test "while loop continue expression, more complicated" { + var i: usize = 1; + var j: usize = 1; + while (i * j < 2000) : ({ i *= 2; j *= 3; }) + expect(i * j < 2000); +} + +test "while else" { + expect(rangeHasNumber(0, 10, 5)); + expect(!rangeHasNumber(0, 10, 15)); +} + +fn rangeHasNumber(begin: usize, end: usize, number: usize) bool { + var i = begin; + return while (i < end) : (i += 1) { + if (i == number) { + break true; + } + } else false; +} + +test "nested break" { + outer: while (true) { + while (true) { + break :outer; + } + } +} + +test "nested continue" { + var i: usize = 0; + outer: while (i < 10) : (i += 1) { + while (true) { + continue :outer; + } + } +} |