Application Context Implementation #4

Closed
kayomn wants to merge 93 commits from event-loop-dev into main
8 changed files with 407 additions and 262 deletions
Showing only changes of commit ac95993a4b - Show all commits

View File

@ -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"),
}
}

View File

@ -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;
kayomn marked this conversation as resolved Outdated

Worth mentioning that it performs linear time, making it O(n) time complexity?

Worth mentioning that it performs linear time, making it O(n) time complexity?
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);
}
} }

View File

@ -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
View File

@ -0,0 +1,26 @@
pub const IntFittingRange = @import("std").math.IntFittingRange;
///
kayomn marked this conversation as resolved Outdated

Warrants TODO comment mentioning that this stdlib dependency should be removed in future.

Warrants `TODO` comment mentioning that this stdlib dependency should be removed in future.
/// 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"),
};
}

View File

@ -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;
} }

View File

@ -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" {
kayomn marked this conversation as resolved Outdated

Seeing as FixedStack does not depend on any kind of dynamic allocation directly, does it make sense to make PushError a direct alias of io.MakeError?

Would it make more sense to use BufferOverflow instead of OutOfMemory?

Seeing as `FixedStack` does not depend on any kind of dynamic allocation directly, does it make sense to make `PushError` a direct alias of `io.MakeError`? Would it make more sense to use `BufferOverflow` instead of `OutOfMemory`?
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
View 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
View 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.
}