Tidy up core package code and comments
This commit is contained in:
		
							parent
							
								
									a07b56d2d5
								
							
						
					
					
						commit
						ac95993a4b
					
				| @ -1,70 +0,0 @@ | |||||||
| const io = @import("io.zig"); |  | ||||||
| 
 |  | ||||||
| /// |  | ||||||
| /// Number formatting modes supported by [writeInt]. |  | ||||||
| /// |  | ||||||
| pub const Radix = enum { |  | ||||||
|     binary, |  | ||||||
|     tinary, |  | ||||||
|     quaternary, |  | ||||||
|     quinary, |  | ||||||
|     senary, |  | ||||||
|     septenary, |  | ||||||
|     octal, |  | ||||||
|     nonary, |  | ||||||
|     decimal, |  | ||||||
|     undecimal, |  | ||||||
|     duodecimal, |  | ||||||
|     tridecimal, |  | ||||||
|     tetradecimal, |  | ||||||
|     pentadecimal, |  | ||||||
|     hexadecimal, |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| /// |  | ||||||
| /// Writes `value` as a ASCII / UTF-8 encoded integer to `writer`, returning `true` if the full |  | ||||||
| /// sequence was successfully written, otherwise `false`. |  | ||||||
| /// |  | ||||||
| /// The `radix` argument identifies which base system to format `value` as. |  | ||||||
| /// |  | ||||||
| pub fn printInt(writer: io.Writer, radix: Radix, value: anytype) bool { |  | ||||||
|     const Int = @TypeOf(value); |  | ||||||
|     const type_info = @typeInfo(Int); |  | ||||||
| 
 |  | ||||||
|     switch (type_info) { |  | ||||||
|         .Int => { |  | ||||||
|             if (value == 0) return writer.writeByte('0'); |  | ||||||
| 
 |  | ||||||
|             // TODO: Unhardcode this as it will break with large ints. |  | ||||||
|             var buffer = std.mem.zeroes([28]u8); |  | ||||||
|             var buffer_count = @as(usize, 0); |  | ||||||
|             var n1 = value; |  | ||||||
| 
 |  | ||||||
|             if ((type_info.Int.signedness == .signed) and (value < 0)) { |  | ||||||
|                 // Negative value. |  | ||||||
|                 n1 = -value; |  | ||||||
|                 buffer[0] = '-'; |  | ||||||
|                 buffer_count += 1; |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             while (n1 != 0) { |  | ||||||
|                 const base = @enumToInt(radix); |  | ||||||
| 
 |  | ||||||
|                 buffer[buffer_count] = @intCast(u8, (n1 % base) + '0'); |  | ||||||
|                 n1 = (n1 / base); |  | ||||||
|                 buffer_count += 1; |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             for (buffer[0 .. (buffer_count / 2)]) |_, i| |  | ||||||
|                 std.mem.swap(u8, &buffer[i], &buffer[buffer_count - i - 1]); |  | ||||||
| 
 |  | ||||||
|             return (writer.call(.{buffer[0 .. buffer_count]}) == buffer_count); |  | ||||||
|         }, |  | ||||||
| 
 |  | ||||||
|         // Cast comptime int into known-size integer and try again. |  | ||||||
|         .ComptimeInt => return writer. |  | ||||||
|             writeInt(radix, @intCast(std.math.IntFittingRange(value, value), value)), |  | ||||||
| 
 |  | ||||||
|         else => @compileError("value must be of type int"), |  | ||||||
|     } |  | ||||||
| } |  | ||||||
							
								
								
									
										230
									
								
								src/core/io.zig
									
									
									
									
									
								
							
							
						
						
									
										230
									
								
								src/core/io.zig
									
									
									
									
									
								
							| @ -1,11 +1,12 @@ | |||||||
|  | const math = @import("./math.zig"); | ||||||
| const meta = @import("./meta.zig"); | const meta = @import("./meta.zig"); | ||||||
| const stack = @import("./stack.zig"); | const stack = @import("./stack.zig"); | ||||||
| const std = @import("std"); | const testing = @import("./testing.zig"); | ||||||
| 
 | 
 | ||||||
| /// | /// | ||||||
| /// | /// | ||||||
| /// | /// | ||||||
| pub const Allocator = std.mem.Allocator; | pub const Allocator = @import("std").mem.Allocator; | ||||||
| 
 | 
 | ||||||
| /// | /// | ||||||
| /// Closure that captures a reference to readable resources like block devices, memory buffers, | /// Closure that captures a reference to readable resources like block devices, memory buffers, | ||||||
| @ -31,6 +32,26 @@ pub fn Spliterator(comptime Element: type) type { | |||||||
|             return (self.source.len != 0); |             return (self.source.len != 0); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  |         test "Check has data" { | ||||||
|  |             var empty_spliterator = Spliterator(u8){ | ||||||
|  |                 .source = "", | ||||||
|  |                 .delimiter = "/", | ||||||
|  |             }; | ||||||
|  | 
 | ||||||
|  |             try testing.expect(!empty_spliterator.hasNext()); | ||||||
|  | 
 | ||||||
|  |             var stateful_spliterator = Spliterator(u8){ | ||||||
|  |                 .source = "data", | ||||||
|  |                 .delimiter = "/", | ||||||
|  |             }; | ||||||
|  | 
 | ||||||
|  |             try testing.expect(stateful_spliterator.hasNext()); | ||||||
|  | 
 | ||||||
|  |             _ = try stateful_spliterator.next(); | ||||||
|  | 
 | ||||||
|  |             try testing.expect(!stateful_spliterator.hasNext()); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|         /// |         /// | ||||||
|         /// Iterates on `self` and returns the next view of [Spliterator.source] that matches |         /// Iterates on `self` and returns the next view of [Spliterator.source] that matches | ||||||
|         /// [Spliterator.delimiter], or `null` if there is no more data to be processed. |         /// [Spliterator.delimiter], or `null` if there is no more data to be processed. | ||||||
| @ -38,7 +59,11 @@ pub fn Spliterator(comptime Element: type) type { | |||||||
|         pub fn next(self: *Self) ?[]const Element { |         pub fn next(self: *Self) ?[]const Element { | ||||||
|             if (!self.hasNext()) return null; |             if (!self.hasNext()) return null; | ||||||
| 
 | 
 | ||||||
|             if (std.mem.indexOfPos(Element, self.source, 0, self.delimiter)) |index| { |             if (findFirstOf(Element, self.source, self.delimiter, struct { | ||||||
|  |                 fn testEquality(this: Element, that: Element) bool { | ||||||
|  |                     return this == that; | ||||||
|  |                 } | ||||||
|  |             }.testEquality)) |index| { | ||||||
|                 defer self.source = self.source[(index + self.delimiter.len) .. self.source.len]; |                 defer self.source = self.source[(index + self.delimiter.len) .. self.source.len]; | ||||||
| 
 | 
 | ||||||
|                 return self.source[0 .. index]; |                 return self.source[0 .. index]; | ||||||
| @ -48,43 +73,43 @@ pub fn Spliterator(comptime Element: type) type { | |||||||
| 
 | 
 | ||||||
|             return self.source; |             return self.source; | ||||||
|         } |         } | ||||||
|  | 
 | ||||||
|  |         test "Iterate through data" { | ||||||
|  |             // Single-character delimiter. | ||||||
|  |             { | ||||||
|  |                 var spliterator = Spliterator(u8){ | ||||||
|  |                     .source = "single.character.separated.hello.world", | ||||||
|  |                     .delimiter = ".", | ||||||
|  |                 }; | ||||||
|  | 
 | ||||||
|  |                 const components = [_][]const u8{"single", | ||||||
|  |                     "character", "separated", "hello", "world"}; | ||||||
|  | 
 | ||||||
|  |                 var index = @as(usize, 0); | ||||||
|  | 
 | ||||||
|  |                 while (spliterator.next()) |split| : (index += 1) { | ||||||
|  |                     try testing.expect(equals(u8, split, components[index])); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             // Multi-character delimiter. | ||||||
|  |             { | ||||||
|  |                 var spliterator = Spliterator(u8){ | ||||||
|  |                     .source = "finding a needle in a needle stack", | ||||||
|  |                     .delimiter = "needle", | ||||||
|  |                 }; | ||||||
|  | 
 | ||||||
|  |                 const components = [_][]const u8{"finding a ", " in a ", " stack"}; | ||||||
|  |                 var index = @as(usize, 0); | ||||||
|  | 
 | ||||||
|  |                 while (spliterator.next()) |split| : (index += 1) { | ||||||
|  |                     try testing.expect(equals(u8, split, components[index])); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|     }; |     }; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| test "Spliterating text" { |  | ||||||
|     const testing = std.testing; |  | ||||||
| 
 |  | ||||||
|     // Single-character delimiter. |  | ||||||
|     { |  | ||||||
|         var spliterator = Spliterator(u8){ |  | ||||||
|             .source = "single.character.separated.hello.world", |  | ||||||
|             .delimiter = ".", |  | ||||||
|         }; |  | ||||||
| 
 |  | ||||||
|         const components = [_][]const u8{"single", "character", "separated", "hello", "world"}; |  | ||||||
|         var index = @as(usize, 0); |  | ||||||
| 
 |  | ||||||
|         while (spliterator.next()) |split| : (index += 1) { |  | ||||||
|             try testing.expect(equals(u8, split, components[index])); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     // Multi-character delimiter. |  | ||||||
|     { |  | ||||||
|         var spliterator = Spliterator(u8){ |  | ||||||
|             .source = "finding a needle in a needle stack", |  | ||||||
|             .delimiter = "needle", |  | ||||||
|         }; |  | ||||||
| 
 |  | ||||||
|         const components = [_][]const u8{"finding a ", " in a ", " stack"}; |  | ||||||
|         var index = @as(usize, 0); |  | ||||||
| 
 |  | ||||||
|         while (spliterator.next()) |split| : (index += 1) { |  | ||||||
|             try testing.expect(equals(u8, split, components[index])); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| /// | /// | ||||||
| /// Closure that captures a reference to writable resources like block devices, memory buffers, | /// Closure that captures a reference to writable resources like block devices, memory buffers, | ||||||
| /// network sockets, and more. | /// network sockets, and more. | ||||||
| @ -102,7 +127,6 @@ pub fn begins(comptime Element: type, elements: []const Element, with: []const E | |||||||
| 
 | 
 | ||||||
| test "Check memory begins with" { | test "Check memory begins with" { | ||||||
|     const bytes_sequence = &.{69, 42}; |     const bytes_sequence = &.{69, 42}; | ||||||
|     const testing = std.testing; |  | ||||||
| 
 | 
 | ||||||
|     try testing.expect(begins(u8, &.{69, 42, 0, 89}, bytes_sequence)); |     try testing.expect(begins(u8, &.{69, 42, 0, 89}, bytes_sequence)); | ||||||
|     try testing.expect(!begins(u8, &.{69, 89, 42, 0}, bytes_sequence)); |     try testing.expect(!begins(u8, &.{69, 89, 42, 0}, bytes_sequence)); | ||||||
| @ -133,11 +157,9 @@ pub fn bytesOf(pointer: anytype) switch (@typeInfo(@TypeOf(pointer))) { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| test "Bytes of types" { | test "Bytes of types" { | ||||||
|     const testing = std.testing; |  | ||||||
| 
 |  | ||||||
|     var foo: u32 = 10; |     var foo: u32 = 10; | ||||||
| 
 | 
 | ||||||
|     testing.expectEqual(bytesOf(&foo), 0x0a); |     try testing.expect(bytesOf(&foo)[0] == 0x0a); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /// | /// | ||||||
| @ -145,27 +167,38 @@ test "Bytes of types" { | |||||||
| /// sequences, otherwise `0` if they are identical. | /// sequences, otherwise `0` if they are identical. | ||||||
| /// | /// | ||||||
| pub fn compareBytes(this: []const u8, that: []const u8) isize { | pub fn compareBytes(this: []const u8, that: []const u8) isize { | ||||||
|     var cursor: usize = 0; |     const range = math.min(usize, this.len, that.len); | ||||||
|  |     var index: usize = 0; | ||||||
| 
 | 
 | ||||||
|     while (cursor != this.len) : (cursor += 1) { |     while (index < range) : (index += 1) { | ||||||
|         const this_byte = this[cursor]; |         const difference = (this[index] - that[index]); | ||||||
| 
 | 
 | ||||||
|         if (cursor != that.len) return this_byte; |         if (difference != 0) return difference; | ||||||
| 
 |  | ||||||
|         const that_byte = that[cursor]; |  | ||||||
| 
 |  | ||||||
|         if (this_byte != that_byte) return (this_byte - that_byte); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     return 0; |     return (@intCast(isize, this.len) - @intCast(isize, that.len)); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| test "Compare bytes" { | test "Compare bytes" { | ||||||
|     const testing = std.testing; |     try testing.expect(compareBytes(&.{69, 42, 0}, &.{69, 42, 0}) == 0); | ||||||
|  |     try testing.expect(compareBytes(&.{69, 42, 11}, &.{69, 42}) == 1); | ||||||
|  |     try testing.expect(compareBytes(&.{69, 42}, &.{69, 42, 11}) == -1); | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
|     try testing.expectEquals(compareBytes(&.{69, 42, 0}, &.{69, 42, 0}), 0); | /// | ||||||
|     try testing.expectEquals(compareBytes(&.{69, 42, 0}, &.{69, 42}), 42); | /// Copies the contents of `source` into `target` | ||||||
|     try testing.expectEquals(compareBytes(&.{69, 42}, &.{69, 42, 0}), -42); | /// | ||||||
|  | pub fn copy(comptime Element: type, target: []Element, source: []const Element) void { | ||||||
|  |     for (source) |element, index| target[index] = element; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | test "Copy data" { | ||||||
|  |     var buffer = [_]u32{0} ** 20; | ||||||
|  |     const data = [_]u32{3, 20, 8000}; | ||||||
|  | 
 | ||||||
|  |     copy(u32, &buffer, &data); | ||||||
|  | 
 | ||||||
|  |     for (data) |datum, index| try testing.expect(buffer[index] == datum); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /// | /// | ||||||
| @ -175,21 +208,72 @@ test "Compare bytes" { | |||||||
| pub fn equals(comptime Element: type, this: []const Element, that: []const Element) bool { | pub fn equals(comptime Element: type, this: []const Element, that: []const Element) bool { | ||||||
|     if (this.len != that.len) return false; |     if (this.len != that.len) return false; | ||||||
| 
 | 
 | ||||||
|     var i = std.mem.zeroes(usize); |     var index: usize = 0; | ||||||
| 
 | 
 | ||||||
|     while (i < this.len) : (i += 1) if (this[i] != that[i]) return false; |     while (index < this.len) : (index += 1) if (this[index] != that[index]) return false; | ||||||
| 
 | 
 | ||||||
|     return true; |     return true; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| test "Check memory is equals" { | test "Check memory is equal" { | ||||||
|     const bytes_sequence = &.{69, 42, 0}; |     const bytes_sequence = &.{69, 42, 0}; | ||||||
|     const testing = std.testing; |  | ||||||
| 
 | 
 | ||||||
|     try testing.expect(equals(u8, bytes_sequence, bytes_sequence)); |     try testing.expect(equals(u8, bytes_sequence, bytes_sequence)); | ||||||
|     try testing.expect(!equals(u8, bytes_sequence, &.{69, 42})); |     try testing.expect(!equals(u8, bytes_sequence, &.{69, 42})); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | /// | ||||||
|  | /// Searches for the first instance of an `Element` equal to `needle` in `haystack`, returning its | ||||||
|  | /// index or `null` if nothing was found. | ||||||
|  | /// | ||||||
|  | pub fn findFirst(comptime Element: type, haystack: []const Element, | ||||||
|  |     needle: Element, comptime testEquality: fn (Element, Element) bool) ?usize { | ||||||
|  | 
 | ||||||
|  |     for (haystack) |element, index| if (testEquality(element, needle)) return index; | ||||||
|  | 
 | ||||||
|  |     return null; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | test "Find first of element" { | ||||||
|  |     const haystack = &.{"", "", "foo"}; | ||||||
|  | 
 | ||||||
|  |     const testEquality = struct { | ||||||
|  |         fn testEquality(this: []const u8, that: []const u8) bool { | ||||||
|  |             return equals(u8, this, that); | ||||||
|  |         } | ||||||
|  |     }.testEquality; | ||||||
|  | 
 | ||||||
|  |     try testing.expect(findFirst([]const u8, haystack, "foo", testEquality).? == 2); | ||||||
|  |     try testing.expect(findFirst([]const u8, haystack, "bar", testEquality) == null); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /// | ||||||
|  | /// Searches for the first instance of an `Element` sequence equal to the contents of `needle` in | ||||||
|  | /// `haystack`, returning the starting index or `null` if nothing was found. | ||||||
|  | /// | ||||||
|  | pub fn findFirstOf(comptime Element: type, haystack: []const Element, | ||||||
|  |     needle: []const Element, comptime testEquality: fn (Element, Element) bool) ?usize { | ||||||
|  | 
 | ||||||
|  |     var cursor: usize = 0; | ||||||
|  |     const end = (haystack.len - needle.len); | ||||||
|  | 
 | ||||||
|  |     walk_haystack: while (cursor <= end) : (cursor += 1) { | ||||||
|  |         const range = (cursor + needle.len); | ||||||
|  |         var index = cursor; | ||||||
|  | 
 | ||||||
|  |         while (index < range) : (index += 1) | ||||||
|  |             if (testEquality(haystack[index], needle[index])) continue: walk_haystack; | ||||||
|  | 
 | ||||||
|  |         return cursor; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return null; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | test "Find first of sequence" { | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | 
 | ||||||
| /// | /// | ||||||
| /// Returns a deterministic hash code compiled from each byte in `bytes`. | /// Returns a deterministic hash code compiled from each byte in `bytes`. | ||||||
| /// | /// | ||||||
| @ -205,12 +289,30 @@ pub fn hashBytes(bytes: []const u8) usize { | |||||||
| 
 | 
 | ||||||
| test "Hashing bytes" { | test "Hashing bytes" { | ||||||
|     const bytes_sequence = &.{69, 42, 0}; |     const bytes_sequence = &.{69, 42, 0}; | ||||||
|     const testing = std.testing; |  | ||||||
| 
 | 
 | ||||||
|     try testing.expect(hashBytes(bytes_sequence) == hashBytes(bytes_sequence)); |     try testing.expect(hashBytes(bytes_sequence) == hashBytes(bytes_sequence)); | ||||||
|     try testing.expect(hashBytes(bytes_sequence) != hashBytes(&.{69, 42})); |     try testing.expect(hashBytes(bytes_sequence) != hashBytes(&.{69, 42})); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | /// | ||||||
|  | /// Swaps the `Data` in `this` with `that`. | ||||||
|  | /// | ||||||
|  | pub fn swap(comptime Data: type, this: *Data, that: *Data) void { | ||||||
|  |     const temp = this.*; | ||||||
|  |     this.* = that.*; | ||||||
|  |     that.* = temp; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | test "Data swapping" { | ||||||
|  |     var a: u64 = 0; | ||||||
|  |     var b: u64 = 1; | ||||||
|  | 
 | ||||||
|  |     swap(u64, &a, &b); | ||||||
|  | 
 | ||||||
|  |     try testing.expect(a == 1); | ||||||
|  |     try testing.expect(b == 0); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| /// | /// | ||||||
| /// Returns a [Writer] that silently consumes all given data without failure and throws it away. | /// Returns a [Writer] that silently consumes all given data without failure and throws it away. | ||||||
| /// | /// | ||||||
| @ -218,7 +320,7 @@ test "Hashing bytes" { | |||||||
| /// sent somewhere for whatever reason. | /// sent somewhere for whatever reason. | ||||||
| /// | /// | ||||||
| pub fn nullWriter() Writer { | pub fn nullWriter() Writer { | ||||||
|     return Writer.capture(std.mem.zeroes(usize), struct { |     return Writer.capture(@as(usize, 0), struct { | ||||||
|         fn write(_: usize, buffer: []const u8) usize { |         fn write(_: usize, buffer: []const u8) usize { | ||||||
|             return buffer.len; |             return buffer.len; | ||||||
|         } |         } | ||||||
| @ -226,11 +328,7 @@ pub fn nullWriter() Writer { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| test "Null writing" { | test "Null writing" { | ||||||
|     const testing = std.testing; |     const sequence = "foo"; | ||||||
| 
 | 
 | ||||||
|     { |     try testing.expect(nullWriter().call(sequence) == sequence.len); | ||||||
|         const sequence = "foo"; |  | ||||||
| 
 |  | ||||||
|         try testing.expectEqual(nullWriter().apply(sequence), sequence.len); |  | ||||||
|     } |  | ||||||
| } | } | ||||||
|  | |||||||
| @ -5,12 +5,17 @@ | |||||||
| pub const io = @import("./io.zig"); | pub const io = @import("./io.zig"); | ||||||
| 
 | 
 | ||||||
| /// | /// | ||||||
| /// Metaprogramming introspection and generation utilities. | /// Math types and functions with a focus on graphics-specific linear algebra. | ||||||
|  | /// | ||||||
|  | pub const math = @import("./math.zig"); | ||||||
|  | 
 | ||||||
|  | /// | ||||||
|  | /// Metaprogramming introspection and generation. | ||||||
| /// | /// | ||||||
| pub const meta = @import("./meta.zig"); | pub const meta = @import("./meta.zig"); | ||||||
| 
 | 
 | ||||||
| /// | /// | ||||||
| /// Sequential last-in first-out data structures. | /// Sequential, last-in first-out data structures. | ||||||
| /// | /// | ||||||
| pub const stack = @import("./stack.zig"); | pub const stack = @import("./stack.zig"); | ||||||
| 
 | 
 | ||||||
| @ -19,9 +24,22 @@ pub const stack = @import("./stack.zig"); | |||||||
| /// | /// | ||||||
| pub const table = @import("./table.zig"); | pub const table = @import("./table.zig"); | ||||||
| 
 | 
 | ||||||
|  | /// | ||||||
|  | /// Unit testing suite utilities. | ||||||
|  | /// | ||||||
|  | pub const testing = @import("./testing.zig"); | ||||||
|  | 
 | ||||||
|  | /// | ||||||
|  | /// Unicode-encoded string analysis and processing with a focus on UTF-8 encoded text. | ||||||
|  | /// | ||||||
|  | pub const unicode = @import("./unicode.zig"); | ||||||
|  | 
 | ||||||
| test { | test { | ||||||
|     _ = io; |     _ = io; | ||||||
|  |     _ = math; | ||||||
|     _ = meta; |     _ = meta; | ||||||
|     _ = stack; |     _ = stack; | ||||||
|     _ = table; |     _ = table; | ||||||
|  |     _ = testing; | ||||||
|  |     _ = unicode; | ||||||
| } | } | ||||||
|  | |||||||
							
								
								
									
										26
									
								
								src/core/math.zig
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								src/core/math.zig
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,26 @@ | |||||||
|  | 
 | ||||||
|  | pub const IntFittingRange = @import("std").math.IntFittingRange; | ||||||
|  | 
 | ||||||
|  | /// | ||||||
|  | /// Returns the maximum value of `Integer`. | ||||||
|  | /// | ||||||
|  | pub fn maxInt(comptime Integer: type) Integer { | ||||||
|  |     return switch (@typeInfo(Integer)) { | ||||||
|  |         .Int => |info| if (info.bits == 0) 0 else | ||||||
|  |             ((1 << (info.bits - @boolToInt(info.signedness == .signed))) - 1), | ||||||
|  | 
 | ||||||
|  |         else => @compileError("`" ++ @typeName(Integer) ++ "` must be an int"), | ||||||
|  |     }; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /// | ||||||
|  | /// Returns the lowest `Number` value between `this` and `that`. | ||||||
|  | /// | ||||||
|  | pub fn min(comptime Number: type, this: Number, that: Number) Number { | ||||||
|  |     return switch (@typeInfo(Number)) { | ||||||
|  |         .Int, .Float, .ComptimeInt, .ComptimeFloat => if (this < that) this else that, | ||||||
|  | 
 | ||||||
|  |         else => @compileError("`" ++ @typeName(Number) ++ | ||||||
|  |             "` must be an int, float, comptime_int, or comptime_float"), | ||||||
|  |     }; | ||||||
|  | } | ||||||
| @ -1,5 +1,3 @@ | |||||||
| const std = @import("std"); |  | ||||||
| 
 |  | ||||||
| /// | /// | ||||||
| /// Returns the return type of the function type `Fn`. | /// Returns the return type of the function type `Fn`. | ||||||
| /// | /// | ||||||
| @ -11,70 +9,13 @@ pub fn FnReturn(comptime Fn: type) type { | |||||||
|     return type_info.Fn.return_type orelse void; |     return type_info.Fn.return_type orelse void; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /// |  | ||||||
| /// Returns a double-input single-output closure type where `A` represents the first input type, `B` |  | ||||||
| /// represents the second, and `Out` represents the output type, and `captures_size` represents the |  | ||||||
| /// size of the closure context. |  | ||||||
| /// |  | ||||||
| pub fn BiFunction(comptime captures_size: usize, comptime A: type, |  | ||||||
|     comptime B: type, comptime Out: type) type { |  | ||||||
| 
 |  | ||||||
|     return struct { |  | ||||||
|         applyErased: fn (*anyopaque, A, B) Out, |  | ||||||
|         context: [captures_size]u8, |  | ||||||
| 
 |  | ||||||
|         /// |  | ||||||
|         /// Function type. |  | ||||||
|         /// |  | ||||||
|         const Self = @This(); |  | ||||||
| 
 |  | ||||||
|         /// |  | ||||||
|         /// Applies `a` and `b` to `self`, producing a result according to the current context data. |  | ||||||
|         /// |  | ||||||
|         pub fn apply(self: *Self, a: A, b: B) Out { |  | ||||||
|             return self.applyErased(&self.context, a, b); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         /// |  | ||||||
|         /// Creates a new [Self] by capturing the `captures` value as the context and `call` as the |  | ||||||
|         /// as the behavior executed when [apply] or [applyErased] is called. |  | ||||||
|         /// |  | ||||||
|         /// The newly created [Self] is returned. |  | ||||||
|         /// |  | ||||||
|         pub fn capture(captures: anytype, comptime call: fn (@TypeOf(captures), A, B) Out) Self { |  | ||||||
|             const Captures = @TypeOf(captures); |  | ||||||
| 
 |  | ||||||
|             if (@sizeOf(Captures) > captures_size) |  | ||||||
|                 @compileError("`captures` must be smaller than or equal to " ++ |  | ||||||
|                     std.fmt.comptimePrint("{d}", .{captures_size}) ++ " bytes"); |  | ||||||
| 
 |  | ||||||
|             var function = Self{ |  | ||||||
|                 .context = undefined, |  | ||||||
| 
 |  | ||||||
|                 .applyErased = struct { |  | ||||||
|                     fn applyErased(erased: *anyopaque, a: A, b: B) Out { |  | ||||||
|                         return call(if (Captures == void) {} else @ptrCast(*Captures, |  | ||||||
|                             @alignCast(@alignOf(Captures), erased)).*, a, b); |  | ||||||
|                     } |  | ||||||
|                 }.applyErased, |  | ||||||
|             }; |  | ||||||
| 
 |  | ||||||
|             if (captures != {}) { |  | ||||||
|                 @ptrCast(*Captures, @alignCast(@alignOf(Captures), &function.context)).* = captures; |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             return function; |  | ||||||
|         } |  | ||||||
|     }; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| /// | /// | ||||||
| /// Returns a single-input single-output closure type where `In` represents the input type, `Out` | /// Returns a single-input single-output closure type where `In` represents the input type, `Out` | ||||||
| /// represents the output type, and `captures_size` represents the size of the closure context. | /// represents the output type, and `captures_size` represents the size of the closure context. | ||||||
| /// | /// | ||||||
| pub fn Function(comptime captures_size: usize, comptime In: type, comptime Out: type) type { | pub fn Function(comptime captures_size: usize, comptime In: type, comptime Out: type) type { | ||||||
|     return struct { |     return struct { | ||||||
|         applyErased: fn (*anyopaque, In) Out, |         callErased: fn (*anyopaque, In) Out, | ||||||
|         context: [captures_size]u8, |         context: [captures_size]u8, | ||||||
| 
 | 
 | ||||||
|         /// |         /// | ||||||
| @ -83,39 +24,39 @@ pub fn Function(comptime captures_size: usize, comptime In: type, comptime Out: | |||||||
|         const Self = @This(); |         const Self = @This(); | ||||||
| 
 | 
 | ||||||
|         /// |         /// | ||||||
|         /// Applies `input` to `self`, producing a result according to the current context data. |         /// Invokes `self` with `input`, producing a result according to the current context data. | ||||||
|         /// |         /// | ||||||
|         pub fn apply(self: *Self, input: In) Out { |         pub fn call(self: *Self, input: In) Out { | ||||||
|             return self.applyErased(&self.context, input); |             return self.callErased(&self.context, input); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         /// |         /// | ||||||
|         /// Creates a new [Self] by capturing the `captures` value as the context and `call` as the |         /// Creates a new [Self] by capturing the `captures` value as the context and `invoke` as | ||||||
|         /// as the behavior executed when [apply] or [applyErased] is called. |         /// the as the behavior executed when [call] or [callErased] is called. | ||||||
|         /// |         /// | ||||||
|         /// The newly created [Self] is returned. |         /// The newly created [Self] is returned. | ||||||
|         /// |         /// | ||||||
|         pub fn capture(captures: anytype, comptime call: fn (@TypeOf(captures), In) Out) Self { |         pub fn capture(captures: anytype, comptime invoke: fn (@TypeOf(captures), In) Out) Self { | ||||||
|             const Captures = @TypeOf(captures); |             const Captures = @TypeOf(captures); | ||||||
| 
 | 
 | ||||||
|             if (@sizeOf(Captures) > captures_size) |             if (@sizeOf(Captures) > captures_size) | ||||||
|                 @compileError("`captures` must be smaller than or equal to " ++ |                 @compileError("`captures` exceeds the size limit of the capture context"); | ||||||
|                     std.fmt.comptimePrint("{d}", .{captures_size}) ++ " bytes"); |  | ||||||
| 
 | 
 | ||||||
|             const captures_align = @alignOf(Captures); |             const captures_align = @alignOf(Captures); | ||||||
| 
 | 
 | ||||||
|             var function = Self{ |             var function = Self{ | ||||||
|                 .context = undefined, |                 .context = undefined, | ||||||
| 
 | 
 | ||||||
|                 .applyErased = struct { |                 .callErased = struct { | ||||||
|                     fn applyErased(erased: *anyopaque, input: In) Out { |                     fn callErased(erased: *anyopaque, input: In) Out { | ||||||
|                         return call(if (Captures == void) {} else @ptrCast(*Captures, |                         return invoke(if (Captures == void) {} else @ptrCast(*Captures, | ||||||
|                             @alignCast(@alignOf(Captures), erased)).*, input); |                             @alignCast(@alignOf(Captures), erased)).*, input); | ||||||
|                     } |                     } | ||||||
|                 }.applyErased, |                 }.callErased, | ||||||
|             }; |             }; | ||||||
| 
 | 
 | ||||||
|             @ptrCast(*Captures, @alignCast(captures_align, &function.context)).* = captures; |             if (Captures != void) | ||||||
|  |                 @ptrCast(*Captures, @alignCast(captures_align, &function.context)).* = captures; | ||||||
| 
 | 
 | ||||||
|             return function; |             return function; | ||||||
|         } |         } | ||||||
|  | |||||||
| @ -1,5 +1,5 @@ | |||||||
| const io = @import("./io.zig"); | const io = @import("./io.zig"); | ||||||
| const std = @import("std"); | const testing = @import("./testing.zig"); | ||||||
| 
 | 
 | ||||||
| /// | /// | ||||||
| /// Returns a fixed-size stack type of `Element`s. | /// Returns a fixed-size stack type of `Element`s. | ||||||
| @ -15,42 +15,20 @@ pub fn Fixed(comptime Element: type) type { | |||||||
|         const Self = @This(); |         const Self = @This(); | ||||||
| 
 | 
 | ||||||
|         /// |         /// | ||||||
|         /// Wraps `self` and returns it in a [io.Writer] value. |         /// Resets the number of filled items to `0`, otherwise leaving the actual memory contents | ||||||
|         /// |         /// of the buffer untouched until it is later overwritten by following operations on it. | ||||||
|         /// Note that this will raise a compilation error if [Element] is not `u8`. |  | ||||||
|         /// |  | ||||||
|         pub fn writer(self: *Self) io.Writer { |  | ||||||
|             if (Element != u8) @compileError("Cannot coerce fixed stack of type " ++ |  | ||||||
|                 @typeName(Element) ++ " into a Writer"); |  | ||||||
| 
 |  | ||||||
|             return io.Writer.capture(self, struct { |  | ||||||
|                 fn write(stack: *Self, buffer: []const u8) usize { |  | ||||||
|                     stack.pushAll(buffer) catch |err| switch (err) { |  | ||||||
|                         error.OutOfMemory => return 0, |  | ||||||
|                     }; |  | ||||||
| 
 |  | ||||||
|                     return buffer.len; |  | ||||||
|                 } |  | ||||||
|             }.write); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         /// |  | ||||||
|         /// Clears all elements from `self`. |  | ||||||
|         /// |         /// | ||||||
|         pub fn clear(self: *Self) void { |         pub fn clear(self: *Self) void { | ||||||
|             self.filled = 0; |             self.filled = 0; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         /// |         /// | ||||||
|         /// Counts and returns the number of pushed elements in `self`. |         /// If `self` is filled with at least `1` value, it is decremented by `1`, otherwise leaving | ||||||
|  |         /// the actual memory contents of the buffer untouched until it is later overwritten by | ||||||
|  |         /// following operations on it. | ||||||
|         /// |         /// | ||||||
|         pub fn count(self: Self) usize { |         /// The value of the element removed from the list is returned if something existed to be | ||||||
|             return self.filled; |         /// popped, otherwise `null` if it contained no elements. | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         /// |  | ||||||
|         /// Attempts to pop the tail-end of `self`, returning the element value or `null` if the |  | ||||||
|         /// stack is empty. |  | ||||||
|         /// |         /// | ||||||
|         pub fn pop(self: *Self) ?Element { |         pub fn pop(self: *Self) ?Element { | ||||||
|             if (self.filled == 0) return null; |             if (self.filled == 0) return null; | ||||||
| @ -74,40 +52,92 @@ pub fn Fixed(comptime Element: type) type { | |||||||
|         /// Attempts to push all of `elements` into `self`, returning a [FixedPushError] if it |         /// Attempts to push all of `elements` into `self`, returning a [FixedPushError] if it | ||||||
|         /// failed. |         /// failed. | ||||||
|         /// |         /// | ||||||
|         pub fn pushAll(self: *Self, elements: []const u8) PushError!void { |         pub fn pushAll(self: *Self, elements: []const Element) PushError!void { | ||||||
|             const filled = (self.filled + elements.len); |             const filled = (self.filled + elements.len); | ||||||
| 
 | 
 | ||||||
|             if (filled > self.buffer.len) return error.OutOfMemory; |             if (filled > self.buffer.len) return error.OutOfMemory; | ||||||
| 
 | 
 | ||||||
|             std.mem.copy(u8, self.buffer[self.filled ..], elements); |             io.copy(Element, self.buffer[self.filled ..], elements); | ||||||
| 
 | 
 | ||||||
|             self.filled = filled; |             self.filled = filled; | ||||||
|         } |         } | ||||||
|     }; |     }; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | test "Fixed stack of string literals" { | ||||||
|  |     const default_value = ""; | ||||||
|  |     var buffer = [_][]const u8{default_value} ** 4; | ||||||
|  |     var shopping_list = Fixed([]const u8){.buffer = &buffer}; | ||||||
|  | 
 | ||||||
|  |     // Pop empty stack. | ||||||
|  |     { | ||||||
|  |         try testing.expect(shopping_list.pop() == null); | ||||||
|  |         try testing.expect(shopping_list.filled == 0); | ||||||
|  |         try testing.expect(shopping_list.buffer.ptr == &buffer); | ||||||
|  |         try testing.expect(shopping_list.buffer.len == buffer.len); | ||||||
|  | 
 | ||||||
|  |         for (shopping_list.buffer) |item| | ||||||
|  |             try testing.expect(io.equals(u8, item, default_value)); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // Push single element. | ||||||
|  |     { | ||||||
|  |         try shopping_list.push("milk"); | ||||||
|  |         try testing.expect(shopping_list.filled == 1); | ||||||
|  |         try testing.expect(shopping_list.buffer.ptr == &buffer); | ||||||
|  |         try testing.expect(shopping_list.buffer.len == buffer.len); | ||||||
|  |         try testing.expect(io.equals(u8, shopping_list.buffer[0], "milk")); | ||||||
|  | 
 | ||||||
|  |         for (shopping_list.buffer[1 ..]) |item| | ||||||
|  |             try testing.expect(io.equals(u8, item, default_value)); | ||||||
|  | 
 | ||||||
|  |         // TODO: Test stack overflow. | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // Pop single element. | ||||||
|  |     { | ||||||
|  |         try testing.expect(io.equals(u8, shopping_list.pop().?, "milk")); | ||||||
|  |         try testing.expect(shopping_list.filled == 0); | ||||||
|  |         try testing.expect(shopping_list.buffer.ptr == &buffer); | ||||||
|  |         try testing.expect(shopping_list.buffer.len == buffer.len); | ||||||
|  |         try testing.expect(io.equals(u8, shopping_list.buffer[0], "milk")); | ||||||
|  | 
 | ||||||
|  |         for (shopping_list.buffer[1 ..]) |item| | ||||||
|  |             try testing.expect(io.equals(u8, item, default_value)); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // TODO: Multiple elements. | ||||||
|  |     // TODO: Clear elements. | ||||||
|  | } | ||||||
|  | 
 | ||||||
| /// | /// | ||||||
| /// Potential errors that may occur while trying to push one or more elements into a stack. | /// Potential errors that may occur while trying to push one or more elements into a stack. | ||||||
| /// | /// | ||||||
| pub const PushError = std.mem.Allocator.Error; | pub const PushError = io.Allocator.Error; | ||||||
| 
 | 
 | ||||||
| test "Fixed stack manipulation" { | /// | ||||||
|     const testing = std.testing; | /// Returns an [io.Writer] wrapping `fixed_stack`. | ||||||
|     var buffer = std.mem.zeroes([4]u8); | /// | ||||||
|     var stack = Fixed(u8){.buffer = &buffer}; | /// Writing to the returned [io.Writer] will push values to the underlying [Fixed] stack instance | ||||||
|  | /// referenced by `fixed_stack` until it is full. | ||||||
|  | /// | ||||||
|  | pub fn fixedWriter(fixed_stack: *Fixed(u8)) io.Writer { | ||||||
|  |     return io.Writer.capture(fixed_stack, struct { | ||||||
|  |         fn write(stack: *Fixed(u8), buffer: []const u8) usize { | ||||||
|  |             stack.pushAll(buffer) catch |err| switch (err) { | ||||||
|  |                 error.OutOfMemory => return 0, | ||||||
|  |             }; | ||||||
| 
 | 
 | ||||||
|     try testing.expectEqual(stack.count(), 0); |             return buffer.len; | ||||||
|     try testing.expectEqual(stack.pop(), null); |         } | ||||||
|     try stack.push(69); |     }.write); | ||||||
|     try testing.expectEqual(stack.count(), 1); | } | ||||||
|     try testing.expectEqual(stack.pop(), 69); | 
 | ||||||
|     try stack.pushAll(&.{42, 10, 95, 0}); | test "Fixed writer" { | ||||||
|     try testing.expectEqual(stack.count(), 4); |     var buffer = [_]u8{0} ** 4; | ||||||
|     try testing.expectError(PushError.OutOfMemory, stack.push(1)); |     var sequence_stack = Fixed(u8){.buffer = &buffer}; | ||||||
|     try testing.expectError(PushError.OutOfMemory, stack.pushAll(&.{1, 11, 11})); |     const sequence_data = [_]u8{8, 16, 32, 64}; | ||||||
| 
 | 
 | ||||||
|     stack.clear(); |     try testing.expect(fixedWriter(&sequence_stack).call(&sequence_data) == sequence_data.len); | ||||||
| 
 |     try testing.expect(io.equals(u8, sequence_stack.buffer, &sequence_data)); | ||||||
|     try testing.expectEqual(stack.count(), 0); |  | ||||||
|     try testing.expectEqual(stack.writer().apply(&.{0, 0, 0, 0}), 4); |  | ||||||
| } | } | ||||||
|  | |||||||
							
								
								
									
										18
									
								
								src/core/testing.zig
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								src/core/testing.zig
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,18 @@ | |||||||
|  | /// | ||||||
|  | /// [TestError.UnexpectedResult] occurs when a conditional that should have been `true` was actually | ||||||
|  | /// `false`. | ||||||
|  | /// | ||||||
|  | pub const TestError = error { | ||||||
|  |     UnexpectedResult, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /// | ||||||
|  | /// Returns a [TestError] if `ok` is false. | ||||||
|  | /// | ||||||
|  | pub fn expect(ok: bool) TestError!void { | ||||||
|  |     if (!ok) return error.UnexpectedResult; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // TODO: Implement tests. | ||||||
|  | 
 | ||||||
|  | pub const expectError = @import("std").testing.expectError; | ||||||
							
								
								
									
										84
									
								
								src/core/unicode.zig
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										84
									
								
								src/core/unicode.zig
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,84 @@ | |||||||
|  | const io = @import("./io.zig"); | ||||||
|  | const math = @import("./math.zig"); | ||||||
|  | 
 | ||||||
|  | /// | ||||||
|  | /// [PrintError.WriteFailure] occurs when the underlying [io.Writer] implementation failed to write | ||||||
|  | /// the entirety of a the requested print operation. | ||||||
|  | /// | ||||||
|  | pub const PrintError = error { | ||||||
|  |     WriteFailure, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /// | ||||||
|  | /// Number formatting modes supported by [printInt]. | ||||||
|  | /// | ||||||
|  | pub const Radix = enum { | ||||||
|  |     binary, | ||||||
|  |     tinary, | ||||||
|  |     quaternary, | ||||||
|  |     quinary, | ||||||
|  |     senary, | ||||||
|  |     septenary, | ||||||
|  |     octal, | ||||||
|  |     nonary, | ||||||
|  |     decimal, | ||||||
|  |     undecimal, | ||||||
|  |     duodecimal, | ||||||
|  |     tridecimal, | ||||||
|  |     tetradecimal, | ||||||
|  |     pentadecimal, | ||||||
|  |     hexadecimal, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /// | ||||||
|  | /// Writes `value` as a ASCII / UTF-8 encoded integer to `writer`, returning `true` if the full | ||||||
|  | /// sequence was successfully written, otherwise `false`. | ||||||
|  | /// | ||||||
|  | /// The `radix` argument identifies which base system to format `value` as. | ||||||
|  | /// | ||||||
|  | pub fn printInt(writer: io.Writer, radix: Radix, value: anytype) PrintError!void { | ||||||
|  |     const Int = @TypeOf(value); | ||||||
|  | 
 | ||||||
|  |     switch (@typeInfo(Int)) { | ||||||
|  |         .Int => |int_info| { | ||||||
|  |             if (value == 0) return writer.apply("0"); | ||||||
|  | 
 | ||||||
|  |             const base = @enumToInt(radix); | ||||||
|  |             const is_signed = (int_info.signedness == .signed); | ||||||
|  | 
 | ||||||
|  |             var buffer = [_]u8{0} ** (math.ceil(math.log(math. | ||||||
|  |                 maxInt(Int), base)) + @boolToInt(is_signed)); | ||||||
|  | 
 | ||||||
|  |             var buffer_count: usize = 0; | ||||||
|  |             var n1 = value; | ||||||
|  | 
 | ||||||
|  |             if (is_signed and (value < 0)) { | ||||||
|  |                 // Negative value. | ||||||
|  |                 n1 = -value; | ||||||
|  |                 buffer[0] = '-'; | ||||||
|  |                 buffer_count += 1; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             while (n1 != 0) { | ||||||
|  |                 buffer[buffer_count] = @intCast(u8, (n1 % base) + '0'); | ||||||
|  |                 n1 = (n1 / base); | ||||||
|  |                 buffer_count += 1; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             for (buffer[0 .. (buffer_count / 2)]) |_, i| | ||||||
|  |                 io.swap(u8, &buffer[i], &buffer[buffer_count - i - 1]); | ||||||
|  | 
 | ||||||
|  |             if (writer.call(buffer[0 .. buffer_count]) != buffer_count) return error.WriteFailure; | ||||||
|  |         }, | ||||||
|  | 
 | ||||||
|  |         // Cast comptime int into known-size integer and try again. | ||||||
|  |         .ComptimeInt => return printInt(writer, radix, | ||||||
|  |             @intCast(math.IntFittingRange(value, value), value)), | ||||||
|  | 
 | ||||||
|  |         else => @compileError("`value` must be of type int or comptime_int"), | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | test "Print 64-bit signed integer" { | ||||||
|  |     // TODO: implement. | ||||||
|  | } | ||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user