Compare commits
4 Commits
595ad83cdd
...
d49608f7bb
Author | SHA1 | Date |
---|---|---|
kayomn | d49608f7bb | |
kayomn | cfd3e85021 | |
kayomn | 14e5e18228 | |
kayomn | dd81ae76ec |
169
src/core/io.zig
169
src/core/io.zig
|
@ -4,9 +4,66 @@ const stack = @import("./stack.zig");
|
||||||
const testing = @import("./testing.zig");
|
const testing = @import("./testing.zig");
|
||||||
|
|
||||||
///
|
///
|
||||||
|
/// Dynamic memory allocation interface.
|
||||||
///
|
///
|
||||||
///
|
pub const Allocator = union (enum) {
|
||||||
pub const Allocator = @import("std").mem.Allocator;
|
bound: struct {
|
||||||
|
alloc: fn (usize) ?[*]u8,
|
||||||
|
dealloc: fn ([*]u8) void,
|
||||||
|
},
|
||||||
|
|
||||||
|
arena: struct {
|
||||||
|
buffer: []u8,
|
||||||
|
cursor: usize = 0,
|
||||||
|
},
|
||||||
|
|
||||||
|
///
|
||||||
|
/// [MakeError.OutOfMemory] is used to indicate there is not enough memory available for a given
|
||||||
|
/// operation.
|
||||||
|
///
|
||||||
|
pub const MakeError = error {
|
||||||
|
OutOfMemory,
|
||||||
|
};
|
||||||
|
|
||||||
|
///
|
||||||
|
/// Frees `allocation` using `allocator`.
|
||||||
|
///
|
||||||
|
pub fn free(allocator: *Allocator, allocation: anytype) void {
|
||||||
|
switch (@typeInfo(@TypeOf(allocation))) {
|
||||||
|
.Pointer => |pointer| if (pointer.size == .Slice)
|
||||||
|
@compileError("`allocation` cannot be a slice"),
|
||||||
|
|
||||||
|
else => @compileError("`allocation` must be a pointer"),
|
||||||
|
}
|
||||||
|
|
||||||
|
if (@typeInfo(@TypeOf(allocation)) != .Pointer)
|
||||||
|
@compileError("`allocation` must be a pointer");
|
||||||
|
|
||||||
|
// TODO: Implement arena de-allocation.
|
||||||
|
switch (allocator.*) {
|
||||||
|
.bound => |bound| bound.dealloc(@ptrCast([*]u8, allocation)),
|
||||||
|
.arena => {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
/// Attempts to allocate `size` number of `Element`s using `allocator`.
|
||||||
|
///
|
||||||
|
/// Returns the allocation or a [MakeError] if it failed.
|
||||||
|
///
|
||||||
|
pub fn make(allocator: *Allocator, comptime Element: type, size: usize) MakeError![*]Element {
|
||||||
|
switch (allocator.*) {
|
||||||
|
.bound => |bound| return @ptrCast([*]Element, @alignCast(@alignOf(Element),
|
||||||
|
bound.alloc(@sizeOf(Element) * size) orelse return error.OutOfMemory)),
|
||||||
|
|
||||||
|
.arena => |*stack| {
|
||||||
|
defer stack.cursor += size;
|
||||||
|
|
||||||
|
return @ptrCast([*]Element, @alignCast(@alignOf(Element), stack.buffer.ptr));
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
///
|
///
|
||||||
/// 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,
|
||||||
|
@ -32,26 +89,6 @@ 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.
|
||||||
|
@ -59,22 +96,52 @@ 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 (self.delimiter.len == 0) {
|
||||||
|
defer self.source = self.source[self.source.len .. self.source.len];
|
||||||
|
|
||||||
|
return self.source[0 .. self.source.len];
|
||||||
|
}
|
||||||
|
|
||||||
if (findFirstOf(Element, self.source, self.delimiter, struct {
|
if (findFirstOf(Element, self.source, self.delimiter, struct {
|
||||||
fn testEquality(this: Element, that: Element) bool {
|
fn testEquality(this: Element, that: Element) bool {
|
||||||
return this == that;
|
return this == that;
|
||||||
}
|
}
|
||||||
}.testEquality)) |index| {
|
}.testEquality)) |head| {
|
||||||
defer self.source = self.source[(index + self.delimiter.len) .. self.source.len];
|
defer self.source = self.source[(head + self.delimiter.len) .. self.source.len];
|
||||||
|
|
||||||
return self.source[0 .. index];
|
return self.source[0 .. head];
|
||||||
}
|
}
|
||||||
|
|
||||||
defer self.source = self.source[self.source.len .. self.source.len];
|
defer self.source = self.source[self.source.len .. self.source.len];
|
||||||
|
|
||||||
return self.source;
|
return self.source;
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
test "Spliterator of string literals" {
|
||||||
|
// Empty source.
|
||||||
|
{
|
||||||
|
var spliterator = Spliterator(u8){
|
||||||
|
.source = "",
|
||||||
|
.delimiter = " ",
|
||||||
|
};
|
||||||
|
|
||||||
|
try testing.expect(!spliterator.hasNext());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Empty delimiter.
|
||||||
|
{
|
||||||
|
var spliterator = Spliterator(u8){
|
||||||
|
.source = "aaa",
|
||||||
|
.delimiter = "",
|
||||||
|
};
|
||||||
|
|
||||||
|
try testing.expect(spliterator.hasNext());
|
||||||
|
try testing.expect(equals(u8, spliterator.next().?, "aaa"));
|
||||||
|
try testing.expect(!spliterator.hasNext());
|
||||||
|
}
|
||||||
|
|
||||||
test "Iterate through data" {
|
|
||||||
// Single-character delimiter.
|
// Single-character delimiter.
|
||||||
{
|
{
|
||||||
var spliterator = Spliterator(u8){
|
var spliterator = Spliterator(u8){
|
||||||
|
@ -86,10 +153,14 @@ pub fn Spliterator(comptime Element: type) type {
|
||||||
"character", "separated", "hello", "world"};
|
"character", "separated", "hello", "world"};
|
||||||
|
|
||||||
var index = @as(usize, 0);
|
var index = @as(usize, 0);
|
||||||
|
const components_tail = components.len - 1;
|
||||||
|
|
||||||
while (spliterator.next()) |split| : (index += 1) {
|
while (spliterator.next()) |split| : (index += 1) {
|
||||||
|
try testing.expect(spliterator.hasNext() == (index < components_tail));
|
||||||
try testing.expect(equals(u8, split, components[index]));
|
try testing.expect(equals(u8, split, components[index]));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try testing.expect(!spliterator.hasNext());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Multi-character delimiter.
|
// Multi-character delimiter.
|
||||||
|
@ -101,13 +172,15 @@ pub fn Spliterator(comptime Element: type) type {
|
||||||
|
|
||||||
const components = [_][]const u8{"finding a ", " in a ", " stack"};
|
const components = [_][]const u8{"finding a ", " in a ", " stack"};
|
||||||
var index = @as(usize, 0);
|
var index = @as(usize, 0);
|
||||||
|
const components_tail = components.len - 1;
|
||||||
|
|
||||||
while (spliterator.next()) |split| : (index += 1) {
|
while (spliterator.next()) |split| : (index += 1) {
|
||||||
|
try testing.expect(spliterator.hasNext() == (index < components_tail));
|
||||||
try testing.expect(equals(u8, split, components[index]));
|
try testing.expect(equals(u8, split, components[index]));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try testing.expect(!spliterator.hasNext());
|
||||||
}
|
}
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
///
|
///
|
||||||
|
@ -116,22 +189,6 @@ pub fn Spliterator(comptime Element: type) type {
|
||||||
///
|
///
|
||||||
pub const Writer = meta.Function(@sizeOf(usize), []const u8, usize);
|
pub const Writer = meta.Function(@sizeOf(usize), []const u8, usize);
|
||||||
|
|
||||||
///
|
|
||||||
/// Returns `true` if `elements` starts with the characters in `with`, otherwise `false`.
|
|
||||||
///
|
|
||||||
pub fn begins(comptime Element: type, elements: []const Element, with: []const Element) bool {
|
|
||||||
if (elements.len < with.len) return false;
|
|
||||||
|
|
||||||
return equals(Element, elements[0 .. with.len], with);
|
|
||||||
}
|
|
||||||
|
|
||||||
test "Check memory begins with" {
|
|
||||||
const bytes_sequence = &.{69, 42};
|
|
||||||
|
|
||||||
try testing.expect(begins(u8, &.{69, 42, 0, 89}, bytes_sequence));
|
|
||||||
try testing.expect(!begins(u8, &.{69, 89, 42, 0}, bytes_sequence));
|
|
||||||
}
|
|
||||||
|
|
||||||
///
|
///
|
||||||
/// Returns a sliced reference of the raw bytes in `pointer`.
|
/// Returns a sliced reference of the raw bytes in `pointer`.
|
||||||
///
|
///
|
||||||
|
@ -254,24 +311,30 @@ test "Find first of element" {
|
||||||
pub fn findFirstOf(comptime Element: type, haystack: []const Element,
|
pub fn findFirstOf(comptime Element: type, haystack: []const Element,
|
||||||
needle: []const Element, comptime testEquality: fn (Element, Element) bool) ?usize {
|
needle: []const Element, comptime testEquality: fn (Element, Element) bool) ?usize {
|
||||||
|
|
||||||
var cursor: usize = 0;
|
var head: usize = 0;
|
||||||
const end = (haystack.len - needle.len);
|
const tail = (haystack.len - needle.len);
|
||||||
|
|
||||||
walk_haystack: while (cursor <= end) : (cursor += 1) {
|
walk_haystack: while (head <= tail) : (head += 1) {
|
||||||
const range = (cursor + needle.len);
|
for (needle) |element, index|
|
||||||
var index = cursor;
|
if (!testEquality(haystack[head + index], element)) continue: walk_haystack;
|
||||||
|
|
||||||
while (index < range) : (index += 1)
|
return head;
|
||||||
if (testEquality(haystack[index], needle[index])) continue: walk_haystack;
|
|
||||||
|
|
||||||
return cursor;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
test "Find first of sequence" {
|
test "Find first of sequence" {
|
||||||
|
const haystack = &.{"foo", "bar", "baz"};
|
||||||
|
|
||||||
|
const testEquality = struct {
|
||||||
|
fn testEquality(this: []const u8, that: []const u8) bool {
|
||||||
|
return equals(u8, this, that);
|
||||||
|
}
|
||||||
|
}.testEquality;
|
||||||
|
|
||||||
|
try testing.expect(findFirstOf([]const u8, haystack, &.{"bar", "baz"}, testEquality).? == 1);
|
||||||
|
try testing.expect(findFirstOf([]const u8, haystack, &.{"baz", "bar"}, testEquality) == null);
|
||||||
}
|
}
|
||||||
|
|
||||||
///
|
///
|
||||||
|
|
|
@ -113,7 +113,7 @@ test "Fixed stack of string literals" {
|
||||||
///
|
///
|
||||||
/// 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 = io.Allocator.Error;
|
pub const PushError = io.Allocator.MakeError;
|
||||||
|
|
||||||
///
|
///
|
||||||
/// Returns an [io.Writer] wrapping `fixed_stack`.
|
/// Returns an [io.Writer] wrapping `fixed_stack`.
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
const io = @import("./io.zig");
|
const io = @import("./io.zig");
|
||||||
|
const testing = @import("./testing.zig");
|
||||||
|
|
||||||
///
|
///
|
||||||
/// Returns a hash-backed table type of `Value`s indexed by `Key` and using `key_context` as the key
|
/// Returns a hash-backed table type of `Value`s indexed by `Key` and using `key_context` as the key
|
||||||
|
@ -10,7 +11,7 @@ pub fn Hashed(comptime Key: type, comptime Value: type,
|
||||||
const Allocator = io.Allocator;
|
const Allocator = io.Allocator;
|
||||||
|
|
||||||
return struct {
|
return struct {
|
||||||
allocator: Allocator,
|
allocator: *Allocator,
|
||||||
load_limit: f32,
|
load_limit: f32,
|
||||||
buckets: []Bucket,
|
buckets: []Bucket,
|
||||||
filled: usize,
|
filled: usize,
|
||||||
|
@ -36,7 +37,7 @@ pub fn Hashed(comptime Key: type, comptime Value: type,
|
||||||
/// Deinitializes `self`, preventing any further use.
|
/// Deinitializes `self`, preventing any further use.
|
||||||
///
|
///
|
||||||
pub fn deinit(self: *Self) void {
|
pub fn deinit(self: *Self) void {
|
||||||
self.allocator.free(self.buckets);
|
self.allocator.free(self.buckets.ptr);
|
||||||
|
|
||||||
self.buckets = &.{};
|
self.buckets = &.{};
|
||||||
}
|
}
|
||||||
|
@ -44,13 +45,13 @@ pub fn Hashed(comptime Key: type, comptime Value: type,
|
||||||
///
|
///
|
||||||
/// Initializes a [Self] using `allocator` as the memory allocation strategy.
|
/// Initializes a [Self] using `allocator` as the memory allocation strategy.
|
||||||
///
|
///
|
||||||
/// Returns a new [Self] value or an [io.Allocator.Error] if initializing failed.
|
/// Returns a new [Self] value or an [io.Allocator.MakeError] if initializing failed.
|
||||||
///
|
///
|
||||||
pub fn init(allocator: Allocator) Allocator.Error!Self {
|
pub fn init(allocator: *Allocator) Allocator.MakeError!Self {
|
||||||
const initial_capacity = 4;
|
const initial_capacity = 4;
|
||||||
|
|
||||||
return Self{
|
return Self{
|
||||||
.buckets = try allocator.alloc(Bucket, initial_capacity),
|
.buckets = (try allocator.make(Bucket, initial_capacity))[0 .. initial_capacity],
|
||||||
.filled = 0,
|
.filled = 0,
|
||||||
.allocator = allocator,
|
.allocator = allocator,
|
||||||
.load_limit = 0.75,
|
.load_limit = 0.75,
|
||||||
|
@ -98,9 +99,11 @@ pub fn Hashed(comptime Key: type, comptime Value: type,
|
||||||
if (self.loadFactor() >= self.load_limit) {
|
if (self.loadFactor() >= self.load_limit) {
|
||||||
const old_buckets = self.buckets;
|
const old_buckets = self.buckets;
|
||||||
|
|
||||||
defer self.allocator.free(old_buckets);
|
defer self.allocator.free(old_buckets.ptr);
|
||||||
|
|
||||||
self.buckets = try self.allocator.alloc(Bucket, old_buckets.len * 2);
|
const bucket_count = old_buckets.len * 2;
|
||||||
|
|
||||||
|
self.buckets = (try self.allocator.make(Bucket, bucket_count))[0 .. bucket_count];
|
||||||
|
|
||||||
for (old_buckets) |bucket, index| self.buckets[index] = bucket;
|
for (old_buckets) |bucket, index| self.buckets[index] = bucket;
|
||||||
}
|
}
|
||||||
|
@ -160,7 +163,7 @@ pub fn Hashed(comptime Key: type, comptime Value: type,
|
||||||
/// [InsertError.KeyExists] occurs when an insertion was attempted on a table with a matching key
|
/// [InsertError.KeyExists] occurs when an insertion was attempted on a table with a matching key
|
||||||
/// already present.
|
/// already present.
|
||||||
///
|
///
|
||||||
pub const InsertError = io.Allocator.Error || error {
|
pub const InsertError = io.Allocator.MakeError || error {
|
||||||
KeyExists,
|
KeyExists,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -193,16 +196,16 @@ pub const string_literal_context = KeyContext([]const u8){
|
||||||
};
|
};
|
||||||
|
|
||||||
test "Hash table manipulation with string literal context" {
|
test "Hash table manipulation with string literal context" {
|
||||||
const testing = @import("std").testing;
|
var buffer = [_]u8{0} ** 1024;
|
||||||
|
var allocator = io.Allocator{.arena = .{.buffer = &buffer}};
|
||||||
var table = try Hashed([]const u8, u32, string_literal_context).init(testing.allocator);
|
var table = try Hashed([]const u8, u32, string_literal_context).init(&allocator);
|
||||||
|
|
||||||
defer table.deinit();
|
defer table.deinit();
|
||||||
|
|
||||||
const foo = @as(u32, 69);
|
const foo = @as(u32, 69);
|
||||||
|
|
||||||
try testing.expectEqual(table.remove("foo"), null);
|
try testing.expect(table.remove("foo") == null);
|
||||||
try table.insert("foo", foo);
|
try table.insert("foo", foo);
|
||||||
try testing.expectEqual(table.remove("foo"), foo);
|
try testing.expect(table.remove("foo").? == foo);
|
||||||
try testing.expectEqual(table.remove("foo"), null);
|
try testing.expect(table.remove("foo") == null);
|
||||||
}
|
}
|
||||||
|
|
|
@ -378,12 +378,14 @@ pub const Path = extern struct {
|
||||||
while (components.next()) |component| if (component.len != 0) {
|
while (components.next()) |component| if (component.len != 0) {
|
||||||
for (component) |byte| {
|
for (component) |byte| {
|
||||||
if (path.length == max) return error.TooLong;
|
if (path.length == max) return error.TooLong;
|
||||||
|
|
||||||
path.buffer[path.length] = byte;
|
path.buffer[path.length] = byte;
|
||||||
path.length += 1;
|
path.length += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (components.hasNext()) {
|
if (components.hasNext()) {
|
||||||
if (path.length == max) return error.TooLong;
|
if (path.length == max) return error.TooLong;
|
||||||
|
|
||||||
path.buffer[path.length] = '/';
|
path.buffer[path.length] = '/';
|
||||||
path.length += 1;
|
path.length += 1;
|
||||||
}
|
}
|
||||||
|
@ -426,6 +428,19 @@ pub const RunError = error {
|
||||||
InitFailure,
|
InitFailure,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
///
|
||||||
|
/// Returns a [core.io.Allocator] bound to the underlying system allocator.
|
||||||
|
///
|
||||||
|
pub fn allocator() core.io.Allocator {
|
||||||
|
// TODO: Add leak detection.
|
||||||
|
return .{
|
||||||
|
.bound = .{
|
||||||
|
.alloc = ext.SDL_alloc,
|
||||||
|
.dealloc = ext.SDL_free,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
///
|
///
|
||||||
/// Runs a graphical application referenced by `run` with `error` as its error set.
|
/// Runs a graphical application referenced by `run` with `error` as its error set.
|
||||||
///
|
///
|
||||||
|
|
Loading…
Reference in New Issue