Application Context Implementation #4

Closed
kayomn wants to merge 93 commits from event-loop-dev into main
7 changed files with 227 additions and 59 deletions
Showing only changes of commit 4bb86c41bc - Show all commits

View File

@ -13,7 +13,7 @@ pub const Allocation = struct {
};
///
/// Dynamic memory allocation interface.
/// Closure for dynamic memory allocation through the referenced allocator state machine capture.
///
pub const Allocator = meta.Function(Allocation, ?[*]u8);
@ -78,7 +78,7 @@ pub fn Spliterator(comptime Element: type) type {
};
}
test "Spliterator of string literals" {
test "Spliterator(u8)" {
// Empty source.
{
var spliterator = Spliterator(u8){
@ -172,7 +172,7 @@ pub fn bytesOf(pointer: anytype) switch (@typeInfo(@TypeOf(pointer))) {
}
}
test "Bytes of types" {
test "bytesOf" {
var foo: u32 = 10;
try testing.expect(bytesOf(&foo)[0] == 0x0a);
@ -195,7 +195,7 @@ pub fn compareBytes(this: []const u8, that: []const u8) isize {
return (@intCast(isize, this.len) - @intCast(isize, that.len));
}
test "Compare bytes" {
test "compareBytes" {
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);
@ -208,7 +208,7 @@ pub fn copy(comptime Element: type, target: []Element, source: []const Element)
for (source) |element, index| target[index] = element;
}
test "Copy data" {
test "copy" {
var buffer = [_]u32{0} ** 20;
const data = [_]u32{3, 20, 8000};
@ -231,7 +231,7 @@ pub fn equals(comptime Element: type, this: []const Element, that: []const Eleme
return true;
}
test "Check memory is equal" {
test "equals" {
const bytes_sequence = &.{69, 42, 0};
try testing.expect(equals(u8, bytes_sequence, bytes_sequence));
@ -245,7 +245,7 @@ pub fn fill(comptime Element: type, target: []Element, source: Element) void {
for (target) |_, index| target[index] = source;
}
test "Fill data" {
test "fill" {
var buffer = [_]u32{0} ** 8;
fill(u32, &buffer, 1);
@ -265,7 +265,7 @@ pub fn findFirst(comptime Element: type, haystack: []const Element,
return null;
}
test "Find first of element" {
test "findFirst" {
const haystack = &.{"", "", "foo"};
const testEquality = struct {
@ -298,7 +298,7 @@ pub fn findFirstOf(comptime Element: type, haystack: []const Element,
return null;
}
test "Find first of sequence" {
test "findFirstOf" {
const haystack = &.{"foo", "bar", "baz"};
const testEquality = struct {
@ -333,6 +333,16 @@ pub fn free(allocator: Allocator, allocated_memory: anytype) void {
}) != null) unreachable;
}
test "free" {
var buffer = [_]u8{0} ** 4096;
var memory = stack.Fixed(u8){.buffer = &buffer};
const fixed_allocator = stack.fixedAllocator(&memory);
const block_size = 8;
const allocated_block = (try makeMany(u8, fixed_allocator, block_size))[0 .. block_size];
defer free(fixed_allocator, allocated_block);
}
///
/// Returns a deterministic hash code compiled from each byte in `bytes`.
///
@ -346,7 +356,7 @@ pub fn hashBytes(bytes: []const u8) usize {
return hash;
}
test "Hashing bytes" {
test "hashBytes" {
const bytes_sequence = &.{69, 42, 0};
try testing.expect(hashBytes(bytes_sequence) == hashBytes(bytes_sequence));
@ -360,15 +370,42 @@ test "Hashing bytes" {
pub fn makeMany(comptime Element: type, allocator: Allocator, size: usize) MakeError![*]Element {
const alignment = @alignOf(Element);
if (allocator.call(.{
return @ptrCast([*]Element, @alignCast(alignment, allocator.call(.{
.existing = null,
.size = @sizeOf(Element) * size,
.alignment = alignment,
})) |buffer| {
return @ptrCast([*]Element, @alignCast(alignment, buffer));
}
}) orelse return error.OutOfMemory));
}
return error.OutOfMemory;
test "makeMany" {
var buffer = [_]u8{0} ** 4096;
var memory = stack.Fixed(u8){.buffer = &buffer};
const block_size = 8;
// Don't care about the actual allocation - just assertions about it.
_ = (try makeMany(u8, stack.fixedAllocator(&memory), block_size))[0 .. block_size];
}
///
/// Attempts to allocate a buffer of `1` `Element` using `allocator`, returning it or a [MakeError]
/// if it failed.
///
pub fn makeOne(comptime Element: type, allocator: Allocator) MakeError!*Element {
const alignment = @alignOf(Element);
return @ptrCast(*Element, @alignCast(alignment, allocator.call(.{
.existing = null,
.size = @sizeOf(Element),
.alignment = alignment,
}) orelse return error.OutOfMemory));
}
test "makeOne" {
var buffer = [_]u8{0} ** 4096;
var memory = stack.Fixed(u8){.buffer = &buffer};
// Don't care about the actual allocation - just assertions about it.
_ = try makeOne(u8, stack.fixedAllocator(&memory));
}
///
@ -380,7 +417,7 @@ pub fn swap(comptime Data: type, this: *Data, that: *Data) void {
that.* = temp;
}
test "Data swapping" {
test "swap" {
var a: u64 = 0;
var b: u64 = 1;
@ -403,7 +440,7 @@ pub const null_writer = Writer.from(struct {
}
}.write);
test "Null writing" {
test "null_writer" {
const sequence = "foo";
try testing.expect(null_writer.call(sequence) == sequence.len);

View File

@ -1,10 +1,12 @@
const std = @import("std");
const testing = @import("./testing.zig");
pub const IntFittingRange = @import("std").math.IntFittingRange;
pub const IntFittingRange = 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`.
/// Returns the highest integer value representable by `Integer`.
///
pub fn maxInt(comptime Integer: type) comptime_int {
pub fn maxIntValue(comptime Integer: type) comptime_int {
return switch (@typeInfo(Integer)) {
.Int => |info| if (info.bits == 0) 0 else
((1 << (info.bits - @boolToInt(info.signedness == .signed))) - 1),
@ -13,6 +15,32 @@ pub fn maxInt(comptime Integer: type) comptime_int {
};
}
test "maxIntValue" {
try testing.expect(maxIntValue(u8) == 255);
try testing.expect(maxIntValue(i8) == 127);
try testing.expect(maxIntValue(u16) == 65535);
try testing.expect(maxIntValue(i16) == 32767);
}
///
/// Returns the highest `Number` value between `this` and `that`.
///
pub fn max(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"),
};
}
test "max" {
try testing.expect(max(f32, 0.1, 1.0) == 1.0);
try testing.expect(max(f64, 1.0, 1.01) == 1.01);
try testing.expect(max(u32, 35615, 2873) == 35615);
}
///
/// Returns the lowest `Number` value between `this` and `that`.
///
@ -24,3 +52,9 @@ pub fn min(comptime Number: type, this: Number, that: Number) Number {
"` must be an int, float, comptime_int, or comptime_float"),
};
}
test "min" {
try testing.expect(min(f32, 0.1, 1.0) == 0.1);
try testing.expect(min(f64, 1.0, 1.01) == 1.0);
try testing.expect(min(u32, 35615, 2873) == 2873);
}

View File

@ -22,6 +22,13 @@ pub fn Fixed(comptime Element: type) type {
self.filled = 0;
}
///
/// Returns `true` if `self` has filled its buffer to maximum capacity, otherwise `false`.
///
pub fn isFull(self: Self) bool {
return (self.filled == self.buffer.len);
}
///
/// 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
@ -42,7 +49,7 @@ pub fn Fixed(comptime Element: type) type {
/// Attempts to push `element` into `self`, returning a [PushError] if it failed.
///
pub fn push(self: *Self, element: Element) PushError!void {
if (self.filled == self.buffer.len) return error.OutOfMemory;
if (self.isFull()) return error.OutOfMemory;
self.buffer[self.filled] = element;
self.filled += 1;
@ -77,7 +84,7 @@ pub fn Fixed(comptime Element: type) type {
};
}
test "Fixed stack of string literals" {
test "Fixed([]const u8)" {
const default_value = "";
var buffer = [_][]const u8{default_value} ** 4;
var shopping_list = Fixed([]const u8){.buffer = &buffer};
@ -140,12 +147,12 @@ pub fn fixedAllocator(fixed_stack: *Fixed(u8)) io.Allocator {
if (allocation.existing) |buffer| if (allocation.size == 0) {
// Deallocate the memory.
const buffer_address = @ptrToInt(buffer);
const stack_buffer_address = @ptrToInt(stack.buffer.ptr);
const stack_address = @ptrToInt(stack.buffer.ptr);
// Check the buffer is within the address space of the stack buffer. If not, it
// should just be returned to let the caller know it cannot be freed.
if ((buffer_address < stack_buffer_address) or
(buffer_address >= (stack_buffer_address + stack.filled))) return buffer;
if (buffer_address < stack_address or buffer_address >=
(stack_address + stack.filled)) return buffer;
// TODO: Investigate ways of freeing if it is the last allocation.
return null;
kayomn marked this conversation as resolved Outdated

Reallocation could benefit from the same kind of last-alloc check optimization as deallocation.

May be worth clarifying that in the comment and code structure.

Reallocation could benefit from the same kind of last-alloc check optimization as deallocation. May be worth clarifying that in the comment and code structure.
@ -167,6 +174,54 @@ pub fn fixedAllocator(fixed_stack: *Fixed(u8)) io.Allocator {
}.alloc);
}
test "fixedAllocator" {
var buffer = [_]u8{0} ** 32;
var stack = Fixed(u8){.buffer = &buffer};
const allocator = fixedAllocator(&stack);
// Allocation
var block_memory = allocator.call(.{
.existing = null,
.alignment = @alignOf(u64),
.size = @sizeOf(u64),
});
try testing.expect(block_memory != null);
const buffer_address_head = @ptrToInt(&buffer);
const buffer_address_tail = @ptrToInt(&buffer) + buffer.len;
{
const block_memory_address = @ptrToInt(block_memory);
try testing.expect(block_memory_address >= buffer_address_head and
block_memory_address < buffer_address_tail);
}
// Reallocation.
block_memory = allocator.call(.{
.existing = block_memory,
.alignment = @alignOf(u64),
.size = @sizeOf(u64),
});
try testing.expect(block_memory != null);
{
const block_memory_address = @ptrToInt(block_memory);
try testing.expect(block_memory_address >= buffer_address_head and
block_memory_address < buffer_address_tail);
}
// Deallocation.
try testing.expect(allocator.call(.{
.existing = block_memory,
.alignment = 0,
.size = 0,
}) == null);
}
///
/// Returns an [io.Writer] wrapping `fixed_stack`.
///
@ -185,7 +240,7 @@ pub fn fixedWriter(fixed_stack: *Fixed(u8)) io.Writer {
}.write);
}
test "Fixed writer" {
test "fixedWriter" {
var buffer = [_]u8{0} ** 4;
var sequence_stack = Fixed(u8){.buffer = &buffer};
const sequence_data = [_]u8{8, 16, 32, 64};

View File

@ -197,12 +197,12 @@ pub const string_literal_context = KeyContext([]const u8){
}.stringsEqual,
};
test "Hash table manipulation with string literal context" {
test "Hashed([]const u8, u32, string_literal_context)" {
var buffer = [_]u8{0} ** 4096;
var fixed_stack = stack.Fixed(u8){.buffer = &buffer};
var memory = stack.Fixed(u8){.buffer = &buffer};
var table = try Hashed([]const u8, u32, string_literal_context).
init(stack.fixedAllocator(&fixed_stack));
init(stack.fixedAllocator(&memory));
defer table.deinit();

View File

@ -13,6 +13,10 @@ pub fn expect(ok: bool) TestError!void {
if (!ok) return error.UnexpectedResult;
}
// TODO: Implement tests.
test "expect" {
try expect(true);
expect(false) catch {};
}
pub const expectError = @import("std").testing.expectError;

View File

@ -1,5 +1,7 @@
const io = @import("./io.zig");
const math = @import("./math.zig");
const stack = @import("./stack.zig");
const testing = @import("./testing.zig");
///
/// [PrintError.WriteFailure] occurs when the underlying [io.Writer] implementation failed to write
@ -10,7 +12,7 @@ pub const PrintError = error {
};
///
/// Number formatting modes supported by [printInt].
/// Named identifiers for number formats used in printing functions.
///
pub const Radix = enum {
binary,
@ -28,6 +30,29 @@ pub const Radix = enum {
tetradecimal,
pentadecimal,
hexadecimal,
///
/// Returns the base number of `radix`.
///
pub fn base(radix: Radix) u8 {
return switch (radix) {
.binary => 2,
.tinary => 3,
.quaternary => 4,
.quinary => 5,
.senary => 6,
.septenary => 7,
.octal => 8,
.nonary => 9,
.decimal => 10,
.undecimal => 11,
.duodecimal => 12,
.tridecimal => 13,
.tetradecimal => 14,
.pentadecimal => 15,
.hexadecimal => 16,
};
}
};
///
@ -40,19 +65,19 @@ 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));
.Int => |info| {
if (value == 0) {
const zero = "0";
if (writer.call(zero) != zero.len) return error.WriteFailure;
} else {
// Big enough to hold the hexadecimal representation of the integer type, which is
// the largest number format accomodated for in [Radix].
var buffer = [_]u8{0} ** (@sizeOf(Int) * (@bitSizeOf(u8) / 4));
var buffer_count: usize = 0;
var n1 = value;
if (is_signed and (value < 0)) {
if (info.signedness == .signed and value < 0) {
// Negative value.
n1 = -value;
buffer[0] = '-';
@ -60,6 +85,8 @@ pub fn printInt(writer: io.Writer, radix: Radix, value: anytype) PrintError!void
}
while (n1 != 0) {
const base = radix.base();
buffer[buffer_count] = @intCast(u8, (n1 % base) + '0');
n1 = (n1 / base);
buffer_count += 1;
@ -68,7 +95,9 @@ pub fn printInt(writer: io.Writer, radix: Radix, value: anytype) PrintError!void
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;
if (writer.call(buffer[0 .. buffer_count]) != buffer_count)
return error.WriteFailure;
}
},
// Cast comptime int into known-size integer and try again.
@ -79,6 +108,12 @@ pub fn printInt(writer: io.Writer, radix: Radix, value: anytype) PrintError!void
}
}
test "Print 64-bit signed integer" {
// TODO: implement.
test "printInt" {
// Max digits to represent a decimal u8 is 3 (i.e. 127 / 255).
var decimal_buffer = [_]u8{0} ** 3;
var decimal_stack = stack.Fixed(u8){.buffer = &decimal_buffer};
var decimal_writer = stack.fixedWriter(&decimal_stack);
try printInt(decimal_writer, .decimal, 365);
try testing.expect(decimal_stack.isFull());
}

View File

@ -114,7 +114,10 @@ pub const ReadableFile = opaque {
{
ext.SDL_ClearError();
var sought = core.math.min(u64, offset, core.math.maxInt(i64));
const math = core.math;
const min = math.min;
const maxIntValue = math.maxIntValue;
var sought = min(u64, offset, maxIntValue(i64));
if (ext.SDL_RWseek(rw_ops, @intCast(i64, sought), ext.RW_SEEK_SET) < 0)
return error.FileInaccessible;
@ -122,7 +125,7 @@ pub const ReadableFile = opaque {
var to_seek = offset - sought;
while (to_seek != 0) {
sought = core.math.min(u64, to_seek, core.math.maxInt(i64));
sought = min(u64, to_seek, maxIntValue(i64));
ext.SDL_ClearError();