Application Context Implementation #4
|
@ -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"),
|
|
||||||
}
|
|
||||||
}
|
|
172
src/core/io.zig
172
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,12 +73,8 @@ pub fn Spliterator(comptime Element: type) type {
|
||||||
|
|
||||||
return self.source;
|
return self.source;
|
||||||
}
|
}
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
test "Spliterating text" {
|
|
||||||
const testing = std.testing;
|
|
||||||
|
|
||||||
|
test "Iterate through data" {
|
||||||
// Single-character delimiter.
|
// Single-character delimiter.
|
||||||
{
|
{
|
||||||
var spliterator = Spliterator(u8){
|
var spliterator = Spliterator(u8){
|
||||||
|
@ -61,7 +82,9 @@ test "Spliterating text" {
|
||||||
.delimiter = ".",
|
.delimiter = ".",
|
||||||
};
|
};
|
||||||
|
|
||||||
const components = [_][]const u8{"single", "character", "separated", "hello", "world"};
|
const components = [_][]const u8{"single",
|
||||||
|
"character", "separated", "hello", "world"};
|
||||||
|
|
||||||
var index = @as(usize, 0);
|
var index = @as(usize, 0);
|
||||||
|
|
||||||
while (spliterator.next()) |split| : (index += 1) {
|
while (spliterator.next()) |split| : (index += 1) {
|
||||||
|
@ -84,6 +107,8 @@ test "Spliterating text" {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
///
|
///
|
||||||
/// 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,
|
||||||
|
@ -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";
|
const sequence = "foo";
|
||||||
|
|
||||||
try testing.expectEqual(nullWriter().apply(sequence), sequence.len);
|
try testing.expect(nullWriter().call(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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,38 +24,38 @@ 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,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (Captures != void)
|
||||||
@ptrCast(*Captures, @alignCast(captures_align, &function.context)).* = captures;
|
@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);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
|
@ -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…
Reference in New Issue