Experimental file system API changes
This commit is contained in:
parent
e3d51ae9c2
commit
3657b846f2
108
src/stack.zig
108
src/stack.zig
|
@ -8,97 +8,13 @@ const std = @import("std");
|
|||
/// [FinitePushError.Overflow] is returned if the stack does not have sufficient capacity to hold a
|
||||
/// given set of elements.
|
||||
///
|
||||
pub const FinitePushError = error {
|
||||
pub const FixedPushError = error {
|
||||
Overflow,
|
||||
};
|
||||
|
||||
///
|
||||
/// Returns a fixed-size stack collection capable of holding a maximum of `capacity` elements of
|
||||
/// type `Element`.
|
||||
///
|
||||
pub fn Fixed(comptime Element: type, comptime capacity: usize) type {
|
||||
pub fn Fixed(comptime Element: type) type {
|
||||
return struct {
|
||||
filled: usize,
|
||||
buffer: [capacity]Element,
|
||||
|
||||
const Self = @This();
|
||||
|
||||
///
|
||||
/// Wraps `self` and returns it in a [io.Writer] value.
|
||||
///
|
||||
/// Note that this will raise a compilation error if [Element] is not `u8`.
|
||||
///
|
||||
pub fn asWriter(self: *Self) io.Writer {
|
||||
if (Element != u8) @compileError("Cannot coerce fixed stack of type " ++
|
||||
@typeName(Element) ++ " into a Writer");
|
||||
|
||||
return io.Writer.wrap(Self, self, struct {
|
||||
fn write(stack: *Self, buffer: []const u8) usize {
|
||||
stack.pushAll(buffer) catch |err| switch (err) {
|
||||
error.Overflow => return 0,
|
||||
};
|
||||
|
||||
return buffer.len;
|
||||
}
|
||||
}.write);
|
||||
}
|
||||
|
||||
///
|
||||
/// Clears all elements from `self`.
|
||||
///
|
||||
pub fn clear(self: *Self) void {
|
||||
self.filled = 0;
|
||||
}
|
||||
|
||||
///
|
||||
/// Counts and returns the number of pushed elements in `self`.
|
||||
///
|
||||
pub fn count(self: Self) usize {
|
||||
return self.filled;
|
||||
}
|
||||
|
||||
///
|
||||
/// 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 {
|
||||
if (self.filled == 0) return null;
|
||||
|
||||
self.filled -= 1;
|
||||
|
||||
return self.buffer[self.filled];
|
||||
}
|
||||
|
||||
///
|
||||
/// Attempts to push `element` into `self`, returning [FinitePushError.Overflow] if the
|
||||
/// stack is full.
|
||||
///
|
||||
pub fn push(self: *Self, element: Element) FinitePushError!void {
|
||||
if (self.filled == capacity) return error.Overflow;
|
||||
|
||||
self.buffer[self.filled] = element;
|
||||
self.filled += 1;
|
||||
}
|
||||
|
||||
///
|
||||
/// Attempts to push all of `elements` into `self`, returning [FinitePushError.Overflow] if
|
||||
/// the stack does not have sufficient capacity to hold the new elements.
|
||||
///
|
||||
pub fn pushAll(self: *Self, elements: []const u8) FinitePushError!void {
|
||||
const filled = (self.filled + elements.len);
|
||||
|
||||
if (filled > capacity) return error.Overflow;
|
||||
|
||||
std.mem.copy(u8, self.buffer[self.filled ..], elements);
|
||||
|
||||
self.filled = filled;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub fn Unmanaged(comptime Element: type) type {
|
||||
return struct {
|
||||
filled: usize,
|
||||
filled: usize = 0,
|
||||
buffer: []Element,
|
||||
|
||||
const Self = @This();
|
||||
|
@ -150,10 +66,9 @@ pub fn Unmanaged(comptime Element: type) type {
|
|||
}
|
||||
|
||||
///
|
||||
/// Attempts to push `element` into `self`, returning [FinitePushError.Overflow] if the
|
||||
/// stack is full.
|
||||
/// Attempts to push `element` into `self`, returning a [FixedPushError] if it failed.
|
||||
///
|
||||
pub fn push(self: *Self, element: Element) FinitePushError!void {
|
||||
pub fn push(self: *Self, element: Element) FixedPushError!void {
|
||||
if (self.filled == self.buffer.len) return error.Overflow;
|
||||
|
||||
self.buffer[self.filled] = element;
|
||||
|
@ -161,10 +76,10 @@ pub fn Unmanaged(comptime Element: type) type {
|
|||
}
|
||||
|
||||
///
|
||||
/// Attempts to push all of `elements` into `self`, returning [FinitePushError.Overflow] if
|
||||
/// the stack does not have sufficient capacity to hold the new elements.
|
||||
/// Attempts to push all of `elements` into `self`, returning a [FixedPushError] if it
|
||||
/// failed.
|
||||
///
|
||||
pub fn pushAll(self: *Self, elements: []const u8) FinitePushError!void {
|
||||
pub fn pushAll(self: *Self, elements: []const u8) FixedPushError!void {
|
||||
const filled = (self.filled + elements.len);
|
||||
|
||||
if (filled > self.buffer.len) return error.Overflow;
|
||||
|
@ -180,7 +95,8 @@ test "fixed stack" {
|
|||
const testing = @import("std").testing;
|
||||
const expectError = testing.expectError;
|
||||
const expectEqual = testing.expectEqual;
|
||||
var stack = Fixed(u8, 4){};
|
||||
var buffer = std.mem.zeroes([4]u8);
|
||||
var stack = Fixed(u8){.buffer = &buffer};
|
||||
|
||||
try expectEqual(stack.count(), 0);
|
||||
try expectEqual(stack.pop(), null);
|
||||
|
@ -189,8 +105,8 @@ test "fixed stack" {
|
|||
try expectEqual(stack.pop(), 69);
|
||||
try stack.pushAll(&.{42, 10, 95, 0});
|
||||
try expectEqual(stack.count(), 4);
|
||||
try expectError(FinitePushError.Overflow, stack.push(1));
|
||||
try expectError(FinitePushError.Overflow, stack.pushAll(&.{1, 11, 11}));
|
||||
try expectError(FixedPushError.Overflow, stack.push(1));
|
||||
try expectError(FixedPushError.Overflow, stack.pushAll(&.{1, 11, 11}));
|
||||
|
||||
stack.clear();
|
||||
|
||||
|
|
254
src/sys.zig
254
src/sys.zig
|
@ -2,6 +2,7 @@ const ext = @cImport({
|
|||
@cInclude("SDL2/SDL.h");
|
||||
});
|
||||
|
||||
const io = @import("./io.zig");
|
||||
const mem = @import("./mem.zig");
|
||||
const stack = @import("./stack.zig");
|
||||
const std = @import("std");
|
||||
|
@ -12,15 +13,6 @@ const std = @import("std");
|
|||
pub const EventLoop = packed struct {
|
||||
current_request: ?*Request = null,
|
||||
|
||||
///
|
||||
/// With files typically being backed by a block device, they can produce a variety of errors -
|
||||
/// from physical to virtual errors - these are all encapsulated by the API as general
|
||||
/// [Error.Inaccessible] errors.
|
||||
///
|
||||
pub const FileError = error {
|
||||
Inaccessible,
|
||||
};
|
||||
|
||||
///
|
||||
/// [OpenError.NotFound] is used as a catch-all for any hardware or software-specific reason for
|
||||
/// failing to open a given file. This includes file-system restrictions surrounding a specific
|
||||
|
@ -46,7 +38,7 @@ pub const EventLoop = packed struct {
|
|||
/// such file exists, on the end of whatever it already contains.
|
||||
///
|
||||
pub const OpenMode = enum {
|
||||
read,
|
||||
readonly,
|
||||
overwrite,
|
||||
append,
|
||||
};
|
||||
|
@ -57,13 +49,14 @@ pub const EventLoop = packed struct {
|
|||
|
||||
message: union(enum) {
|
||||
close: struct {
|
||||
file: *ext.SDL_RWops,
|
||||
file: *FileAccess,
|
||||
},
|
||||
|
||||
open: struct {
|
||||
file_system: FileSystem,
|
||||
path: *const Path,
|
||||
file: OpenError!*ext.SDL_RWops,
|
||||
mode: OpenMode,
|
||||
file: OpenError!*FileAccess = error.OutOfFiles,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
@ -82,44 +75,36 @@ pub const EventLoop = packed struct {
|
|||
.message = .{.close = @ptrCast(*ext.SDL_RWops, file_access)},
|
||||
};
|
||||
|
||||
suspend {
|
||||
if (event_loop.current_request) |current_request| {
|
||||
current_request.next = &request;
|
||||
} else {
|
||||
event_loop.current_request = &request;
|
||||
}
|
||||
}
|
||||
suspend if (event_loop.current_request) |current_request| {
|
||||
current_request.next = &request;
|
||||
} else {
|
||||
event_loop.current_request = &request;
|
||||
};
|
||||
}
|
||||
|
||||
///
|
||||
/// Asynchronously attempts to open access to a file at `path` via `event_loop`, with `mode` as
|
||||
/// the preferences for how it should be opened.
|
||||
///
|
||||
/// A reference to a [FileAccess] representing the bound file is returned if the operation was
|
||||
/// successful, otherwise an [OpenError] if the file could not be opened.
|
||||
///
|
||||
/// *Note* that, regardless of platform, files will always be treated as containing binary data.
|
||||
///
|
||||
pub fn open(event_loop: *EventLoop, path: Path, mode: OpenMode) OpenError!*FileAccess {
|
||||
pub fn open(event_loop: *EventLoop, file_system: FileSystem,
|
||||
path: Path, mode: OpenMode) OpenError!*FileAccess {
|
||||
|
||||
var request = Request{
|
||||
.frame = @frame(),
|
||||
|
||||
.message = .{
|
||||
.open = .{
|
||||
.file_system = file_system,
|
||||
.path = &path,
|
||||
.file = error.OutOfFiles,
|
||||
.mode = mode,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
suspend {
|
||||
if (event_loop.current_request) |current_request| {
|
||||
current_request.next = &request;
|
||||
} else {
|
||||
event_loop.current_request = &request;
|
||||
}
|
||||
}
|
||||
suspend if (event_loop.current_request) |current_request| {
|
||||
current_request.next = &request;
|
||||
} else {
|
||||
event_loop.current_request = &request;
|
||||
};
|
||||
|
||||
return @ptrCast(*FileAccess, try request.message.open.file);
|
||||
}
|
||||
|
@ -132,15 +117,41 @@ pub const EventLoop = packed struct {
|
|||
switch (request.message) {
|
||||
.close => |*close| {
|
||||
// Swallow file close errors.
|
||||
_ = ext.SDL_RWclose(close.file);
|
||||
_ = ext.SDL_RWclose(@ptrCast(*ext.SDL_RWops, @alignCast(@alignOf(ext.SDL_RWops), close.file)));
|
||||
},
|
||||
|
||||
.open => |*open| {
|
||||
open.file = ext.SDL_RWFromFile(&open.path.buffer, switch (open.mode) {
|
||||
.read => "rb",
|
||||
.overwrite => "wb",
|
||||
.append => "ab",
|
||||
}) orelse error.NotFound;
|
||||
switch (open.file_system) {
|
||||
.data => {
|
||||
var zeroed_path_buffer = std.mem.zeroes([4096]u8);
|
||||
var zeroed_path = stack.Fixed(u8){.buffer = &zeroed_path_buffer};
|
||||
|
||||
zeroed_path.push('.') catch continue;
|
||||
|
||||
_ = open.path.write(zeroed_path.asWriter());
|
||||
|
||||
zeroed_path.push(0) catch continue;
|
||||
|
||||
// TODO: Access TAR file.
|
||||
},
|
||||
|
||||
.user => {
|
||||
var zeroed_path_buffer = std.mem.zeroes([4096]u8);
|
||||
var zeroed_path = stack.Fixed(u8){.buffer = &zeroed_path_buffer};
|
||||
|
||||
zeroed_path.pushAll("/home/kayomn/.local/share") catch continue;
|
||||
|
||||
_ = open.path.write(zeroed_path.asWriter());
|
||||
|
||||
zeroed_path.push(0) catch continue;
|
||||
|
||||
open.file = @ptrCast(?*FileAccess, ext.SDL_RWFromFile(&zeroed_path_buffer, switch (open.mode) {
|
||||
.readonly => "rb",
|
||||
.overwrite => "wb",
|
||||
.append => "ab",
|
||||
})) orelse error.NotFound;
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -151,8 +162,26 @@ pub const EventLoop = packed struct {
|
|||
}
|
||||
};
|
||||
|
||||
pub const FileAccess = opaque {
|
||||
///
|
||||
///
|
||||
///
|
||||
pub const FileAccess = opaque {};
|
||||
|
||||
///
|
||||
/// With files typically being backed by a block device, they can produce a variety of errors -
|
||||
/// from physical to virtual errors - these are all encapsulated by the API as general
|
||||
/// [Error.Inaccessible] errors.
|
||||
///
|
||||
pub const FileError = error {
|
||||
Inaccessible,
|
||||
};
|
||||
|
||||
///
|
||||
///
|
||||
///
|
||||
pub const FileSystem = enum {
|
||||
data,
|
||||
user,
|
||||
};
|
||||
|
||||
///
|
||||
|
@ -160,30 +189,13 @@ pub const FileAccess = opaque {
|
|||
/// Ona.
|
||||
///
|
||||
pub const Path = struct {
|
||||
locator: Locator,
|
||||
length: u16,
|
||||
buffer: [max]u8,
|
||||
|
||||
///
|
||||
/// Virtual file-system locators supported by Ona.
|
||||
///
|
||||
pub const Locator = enum(u8) {
|
||||
relative,
|
||||
data,
|
||||
user,
|
||||
};
|
||||
|
||||
///
|
||||
/// Errors that may occur during parsing of an incorrectly formatted [Path] URI.
|
||||
///
|
||||
pub const ParseError = (JoinError || error {
|
||||
BadLocator,
|
||||
});
|
||||
|
||||
///
|
||||
///
|
||||
///
|
||||
pub const JoinError = error {
|
||||
pub const Error = error {
|
||||
TooLong,
|
||||
};
|
||||
|
||||
|
@ -194,73 +206,11 @@ pub const Path = struct {
|
|||
return (path.length == 0);
|
||||
}
|
||||
|
||||
///
|
||||
/// Attempts to lazily join `components` into a new [Path] value derived from `path`, returning
|
||||
/// it when `components` has no more data or a [JoinError] if the operation failed.
|
||||
///
|
||||
/// Any duplicate path components, such as trailing ASCII forward-slashes (`/`) or periods
|
||||
/// (`.`), will be normalized to a more concise internal representation.
|
||||
///
|
||||
/// *Note* that `components` may be mutated during execution of the operation.
|
||||
///
|
||||
pub fn joinSpliterator(path: Path, components: *mem.Spliterator) JoinError!Path {
|
||||
var joined_path = path;
|
||||
|
||||
var path_buffer = stack.Unmanaged{
|
||||
.buffer = &joined_path.buffer,
|
||||
.filled = if (joined_path.length == 0) joined_path.length else (joined_path.length - 1),
|
||||
};
|
||||
|
||||
if (components.next()) |component| switch (component.len) {
|
||||
0 => if (joined_path.isEmpty()) path_buffer.push('/') catch return error.TooLong,
|
||||
|
||||
1 => if ((component[0] == '.') and joined_path.isEmpty())
|
||||
path_buffer.push("./") catch return error.TooLong,
|
||||
|
||||
else => {
|
||||
if (!joined_path.isEmpty()) path_buffer.push('/') catch return error.TooLong;
|
||||
|
||||
path_buffer.pushAll(component) catch return error.TooLong;
|
||||
|
||||
if (components.hasNext()) path_buffer.push('/') catch return error.TooLong;
|
||||
},
|
||||
};
|
||||
|
||||
while (components.next()) |component|
|
||||
if ((component.len != 0) or (!((component.len == 1) and (component[0] == '.')))) {
|
||||
|
||||
if (!joined_path.isEmpty()) path_buffer.push('/') catch return error.TooLong;
|
||||
|
||||
path_buffer.pushAll(component) catch return error.TooLong;
|
||||
|
||||
if (components.hasNext()) path_buffer.push('/') catch return error.TooLong;
|
||||
};
|
||||
|
||||
// No space left over for the null terminator.
|
||||
if (path_buffer.filled >= max) return error.TooLong;
|
||||
|
||||
joined_path.length = path_buffer.filled;
|
||||
|
||||
return joined_path;
|
||||
}
|
||||
|
||||
///
|
||||
/// Returns `true` if `this` is equal to `that`, otherwise `false`.
|
||||
///
|
||||
pub fn equals(this: Path, that: Path) bool {
|
||||
return (this.locator == that.locator) and
|
||||
std.mem.eql(this.buffer[0 .. this.length], that.buffer[0 .. that.length]);
|
||||
}
|
||||
|
||||
///
|
||||
/// Creates and returns an empty [Path] value rooted at the location of `locator`.
|
||||
///
|
||||
pub fn from(locator: Locator) Path {
|
||||
return .{
|
||||
.locator = locator,
|
||||
.length = 0,
|
||||
.buffer = std.mem.zeroes([max]u8),
|
||||
};
|
||||
return std.mem.eql(u8, this.buffer[0 .. this.length], that.buffer[0 .. that.length]);
|
||||
}
|
||||
|
||||
///
|
||||
|
@ -273,46 +223,30 @@ pub const Path = struct {
|
|||
pub const max = 1000;
|
||||
|
||||
///
|
||||
/// Attempts to parse the data in `sequence`, returning the [Path] value or an error from
|
||||
/// [ParseError] if it failed to parse.
|
||||
///
|
||||
/// The rules surrounding the encoding of `sequence` are loose, with the only fundamental
|
||||
/// requirements being:
|
||||
///
|
||||
/// * It starts with a locator key followed by an ASCII colon (`data:`, `user:`, etc.)
|
||||
/// followed by the rest of the path.
|
||||
///
|
||||
/// * Each component of the path is separated by an ASCII forward-slash (`/`).
|
||||
///
|
||||
/// * A path that begins with an ASCII forward-slash ('/') after the locator key is considered
|
||||
/// to be relative to the root of the specified locator key instead of relative to the path
|
||||
/// caller.
|
||||
///
|
||||
/// Additional encoding rules are enforced via underlying file-system being used. For example,
|
||||
/// Microsoft Windows is case-insensitive while Unix and Linux systems are not. Additionally,
|
||||
/// Windows has far more reserved characters and sequences which cannot be used when interfacing
|
||||
/// with files compared to Linux and Unix systems.
|
||||
///
|
||||
/// See [ParseError] for more information on the kinds of errors that may be returned.
|
||||
///
|
||||
pub fn parse(sequence: []const u8) ParseError!Path {
|
||||
if (sequence.len == 0) return Path.from(.relative);
|
||||
pub fn parseJoins(path: Path, joins: []const []const u8) Error!Path {
|
||||
_ = path;
|
||||
_ = joins;
|
||||
|
||||
if (mem.forwardFind(u8, sequence, ':')) |locator_path_delimiter_index| {
|
||||
var locator = std.meta.stringToEnum(Locator,
|
||||
sequence[0 .. locator_path_delimiter_index]) orelse return error.BadLocator;
|
||||
|
||||
const components_index = (locator_path_delimiter_index + 1);
|
||||
|
||||
return Path.from(locator).joinSpliterator(&.{
|
||||
.source = sequence[components_index .. (sequence.len - components_index)],
|
||||
.delimiter = "/",
|
||||
});
|
||||
}
|
||||
|
||||
return Path.from(.relative).joinSpliterator(&.{
|
||||
.source = sequence,
|
||||
.delimiter = "/",
|
||||
});
|
||||
return Path{
|
||||
.buffer = std.mem.zeroes([max]u8),
|
||||
.length = 0
|
||||
};
|
||||
}
|
||||
|
||||
///
|
||||
///
|
||||
///
|
||||
pub fn write(path: Path, writer: io.Writer) bool {
|
||||
return (writer.write(path.buffer[0 .. path.length]) == path.length);
|
||||
}
|
||||
|
||||
///
|
||||
///
|
||||
///
|
||||
pub const root = Path{
|
||||
.buffer = [_]u8{'/'} ** max,
|
||||
.length = 1,
|
||||
};
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue