From 3657b846f2431e7f2917064e5ff19e515e6bb89e Mon Sep 17 00:00:00 2001 From: kayomn Date: Tue, 27 Sep 2022 21:45:32 +0100 Subject: [PATCH] Experimental file system API changes --- src/stack.zig | 108 +++------------------ src/sys.zig | 254 +++++++++++++++++++------------------------------- 2 files changed, 106 insertions(+), 256 deletions(-) diff --git a/src/stack.zig b/src/stack.zig index 3fc2de1..f052c13 100755 --- a/src/stack.zig +++ b/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(); diff --git a/src/sys.zig b/src/sys.zig index 5bc25a2..5ced8ef 100644 --- a/src/sys.zig +++ b/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, + }; };