Compare commits
	
		
			6 Commits
		
	
	
		
			7599ce61f2
			...
			ac95993a4b
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| ac95993a4b | |||
| a07b56d2d5 | |||
| 35de8cf2ae | |||
| 14397b05e3 | |||
| 5a913ef0f8 | |||
| b4816a34f6 | 
							
								
								
									
										12
									
								
								.vscode/launch.json
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										12
									
								
								.vscode/launch.json
									
									
									
									
										vendored
									
									
								
							@ -10,15 +10,7 @@
 | 
				
			|||||||
			"valuesFormatting": "parseText",
 | 
								"valuesFormatting": "parseText",
 | 
				
			||||||
			"preLaunchTask": "Build Debug",
 | 
								"preLaunchTask": "Build Debug",
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
		{
 | 
					
 | 
				
			||||||
			"name": "Oar",
 | 
					 | 
				
			||||||
			"type": "gdb",
 | 
					 | 
				
			||||||
			"request": "launch",
 | 
					 | 
				
			||||||
			"target": "${workspaceFolder}/zig-out/bin/oar",
 | 
					 | 
				
			||||||
			"cwd": "${workspaceRoot}",
 | 
					 | 
				
			||||||
			"valuesFormatting": "parseText",
 | 
					 | 
				
			||||||
			"preLaunchTask": "Build Debug",
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
		{
 | 
							{
 | 
				
			||||||
			"name": "Test",
 | 
								"name": "Test",
 | 
				
			||||||
			"type": "gdb",
 | 
								"type": "gdb",
 | 
				
			||||||
@ -27,7 +19,7 @@
 | 
				
			|||||||
			"arguments": "main.zig",
 | 
								"arguments": "main.zig",
 | 
				
			||||||
			"cwd": "${workspaceRoot}",
 | 
								"cwd": "${workspaceRoot}",
 | 
				
			||||||
			"valuesFormatting": "parseText",
 | 
								"valuesFormatting": "parseText",
 | 
				
			||||||
			"preLaunchTask": "Build Tests",
 | 
								"preLaunchTask": "Build Debug",
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
	]
 | 
						]
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										9
									
								
								.vscode/tasks.json
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										9
									
								
								.vscode/tasks.json
									
									
									
									
										vendored
									
									
								
							@ -28,7 +28,7 @@
 | 
				
			|||||||
			"group": "build",
 | 
								"group": "build",
 | 
				
			||||||
			"presentation": {
 | 
								"presentation": {
 | 
				
			||||||
				"echo": true,
 | 
									"echo": true,
 | 
				
			||||||
				"reveal": "always",
 | 
									"reveal": "silent",
 | 
				
			||||||
				"focus": true,
 | 
									"focus": true,
 | 
				
			||||||
				"panel": "shared",
 | 
									"panel": "shared",
 | 
				
			||||||
				"showReuseMessage": true,
 | 
									"showReuseMessage": true,
 | 
				
			||||||
@ -36,12 +36,5 @@
 | 
				
			|||||||
				"revealProblems": "onProblem",
 | 
									"revealProblems": "onProblem",
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
					 | 
				
			||||||
		{
 | 
					 | 
				
			||||||
			"label": "Build Tests",
 | 
					 | 
				
			||||||
			"type": "shell",
 | 
					 | 
				
			||||||
			"command": "zig build test",
 | 
					 | 
				
			||||||
			"group": "build",
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
	],
 | 
						],
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										28
									
								
								build.zig
									
									
									
									
									
								
							
							
						
						
									
										28
									
								
								build.zig
									
									
									
									
									
								
							@ -10,26 +10,15 @@ pub fn build(builder: *std.build.Builder) void {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    // Ona executable.
 | 
					    // Ona executable.
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        const exe = builder.addExecutable("ona", "./src/ona/main.zig");
 | 
					        const ona = builder.addExecutable("ona", "./src/ona/main.zig");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        exe.addPackage(projectPkg("oar", &.{core_pkg}));
 | 
					        ona.addPackage(core_pkg);
 | 
				
			||||||
        exe.addPackage(core_pkg);
 | 
					        ona.setTarget(target);
 | 
				
			||||||
        exe.setTarget(target);
 | 
					        ona.setBuildMode(mode);
 | 
				
			||||||
        exe.setBuildMode(mode);
 | 
					        ona.install();
 | 
				
			||||||
        exe.install();
 | 
					        ona.addIncludeDir("./ext");
 | 
				
			||||||
        exe.addIncludeDir("./ext");
 | 
					        ona.linkSystemLibrary("SDL2");
 | 
				
			||||||
        exe.linkSystemLibrary("SDL2");
 | 
					        ona.linkLibC();
 | 
				
			||||||
        exe.linkLibC();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Oar executable.
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        const exe = builder.addExecutable("oar", "./src/oar/main.zig");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        exe.addPackage(core_pkg);
 | 
					 | 
				
			||||||
        exe.setTarget(target);
 | 
					 | 
				
			||||||
        exe.setBuildMode(mode);
 | 
					 | 
				
			||||||
        exe.install();
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Tests executable.
 | 
					    // Tests executable.
 | 
				
			||||||
@ -40,7 +29,6 @@ pub fn build(builder: *std.build.Builder) void {
 | 
				
			|||||||
        tests.setTarget(target);
 | 
					        tests.setTarget(target);
 | 
				
			||||||
        tests.setBuildMode(mode);
 | 
					        tests.setBuildMode(mode);
 | 
				
			||||||
        tests.install();
 | 
					        tests.install();
 | 
				
			||||||
        builder.step("test", "Run unit tests").dependOn(&tests.step);
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -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"),
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
							
								
								
									
										278
									
								
								src/core/io.zig
									
									
									
									
									
								
							
							
						
						
									
										278
									
								
								src/core/io.zig
									
									
									
									
									
								
							@ -1,107 +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;
 | 
				
			||||||
 | 
					 | 
				
			||||||
///
 | 
					 | 
				
			||||||
/// File-system agnostic abstraction for manipulating a file.
 | 
					 | 
				
			||||||
///
 | 
					 | 
				
			||||||
pub const FileAccess = struct {
 | 
					 | 
				
			||||||
    context: *anyopaque,
 | 
					 | 
				
			||||||
    implementation: *const Implementation,
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    ///
 | 
					 | 
				
			||||||
    /// Provides a set of implementation-specific behaviors to a [FileAccess] instance.
 | 
					 | 
				
			||||||
    ///
 | 
					 | 
				
			||||||
    pub const Implementation = struct {
 | 
					 | 
				
			||||||
        close: fn (*anyopaque) void,
 | 
					 | 
				
			||||||
        queryCursor: fn (*anyopaque) Error!u64,
 | 
					 | 
				
			||||||
        queryLength: fn (*anyopaque) Error!u64,
 | 
					 | 
				
			||||||
        read: fn (*anyopaque, []u8) Error!usize,
 | 
					 | 
				
			||||||
        seek: fn (*anyopaque, u64) Error!void,
 | 
					 | 
				
			||||||
        seekToEnd: fn (*anyopaque) Error!void,
 | 
					 | 
				
			||||||
        skip: fn (*anyopaque, i64) Error!void,
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    ///
 | 
					 | 
				
			||||||
    /// [Error.FileInaccessible] is a generic catch-all for a [FileAccess] reference no longer
 | 
					 | 
				
			||||||
    /// pointing to a file or the file becomming invalid for whatever reason.
 | 
					 | 
				
			||||||
    ///
 | 
					 | 
				
			||||||
    pub const Error = error {
 | 
					 | 
				
			||||||
        FileInaccessible,
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    ///
 | 
					 | 
				
			||||||
    /// Close the file referenced by `file_access` on the main thread, invalidating the reference to
 | 
					 | 
				
			||||||
    /// it and releasing any associated resources.
 | 
					 | 
				
			||||||
    ///
 | 
					 | 
				
			||||||
    /// Freeing an invalid `file_access` has no effect on the file and logs a warning over the
 | 
					 | 
				
			||||||
    /// wasted effort.
 | 
					 | 
				
			||||||
    ///
 | 
					 | 
				
			||||||
    pub fn close(file_access: FileAccess) void {
 | 
					 | 
				
			||||||
        return file_access.implementation.close(file_access.context);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    ///
 | 
					 | 
				
			||||||
    /// Attempts to query the current cursor position for the file referenced by `file_access`.
 | 
					 | 
				
			||||||
    ///
 | 
					 | 
				
			||||||
    /// Returns the number of bytes into the file that the cursor is relative to its beginning or a
 | 
					 | 
				
			||||||
    /// [Error] on failure.
 | 
					 | 
				
			||||||
    ///
 | 
					 | 
				
			||||||
    pub fn queryCursor(file_access: FileAccess) Error!u64 {
 | 
					 | 
				
			||||||
        return file_access.implementation.queryCursor(file_access.context);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    ///
 | 
					 | 
				
			||||||
    /// Attempts to query the current length for the file referenced by `file_access`.
 | 
					 | 
				
			||||||
    ///
 | 
					 | 
				
			||||||
    /// Returns the current length of the file at the time of the operation or a [Error] if the file
 | 
					 | 
				
			||||||
    /// failed to be queried.
 | 
					 | 
				
			||||||
    ///
 | 
					 | 
				
			||||||
    pub fn queryLength(file_access: FileAccess) Error!u64 {
 | 
					 | 
				
			||||||
        return file_access.implementation.queryLength(file_access.context);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    ///
 | 
					 | 
				
			||||||
    /// Attempts to read `file_access` from the its current position into `buffer`.
 | 
					 | 
				
			||||||
    ///
 | 
					 | 
				
			||||||
    /// Returns the number of bytes that were available to be read, otherwise an [Error] on failure.
 | 
					 | 
				
			||||||
    ///
 | 
					 | 
				
			||||||
    pub fn read(file_access: FileAccess, buffer: []u8) Error!usize {
 | 
					 | 
				
			||||||
        return file_access.implementation.read(file_access.context, buffer);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    ///
 | 
					 | 
				
			||||||
    /// Attempts to seek `file_access` from the beginning of the file to `cursor` bytes.
 | 
					 | 
				
			||||||
    ///
 | 
					 | 
				
			||||||
    /// Returns [Error] on failure.
 | 
					 | 
				
			||||||
    ///
 | 
					 | 
				
			||||||
    pub fn seek(file_access: FileAccess, cursor: u64) Error!void {
 | 
					 | 
				
			||||||
        return file_access.implementation.seek(file_access.context, cursor);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    ///
 | 
					 | 
				
			||||||
    /// Attempts to seek `file_access` to the end of the file.
 | 
					 | 
				
			||||||
    ///
 | 
					 | 
				
			||||||
    /// Returns [Error] on failure.
 | 
					 | 
				
			||||||
    ///
 | 
					 | 
				
			||||||
    pub fn seekToEnd(file_access: FileAccess) Error!void {
 | 
					 | 
				
			||||||
        return file_access.implementation.seekToEnd(file_access.context);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    ///
 | 
					 | 
				
			||||||
    /// Attempts to seek `file_access` by `offset` from the current file position.
 | 
					 | 
				
			||||||
    ///
 | 
					 | 
				
			||||||
    /// Returns [Error] on failure;
 | 
					 | 
				
			||||||
    ///
 | 
					 | 
				
			||||||
    pub fn skip(file_access: FileAccess, offset: i64) Error!void {
 | 
					 | 
				
			||||||
        return file_access.implementation.skip(file_access.context, offset);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
///
 | 
					///
 | 
				
			||||||
/// 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,
 | 
				
			||||||
@ -127,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.
 | 
				
			||||||
@ -134,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];
 | 
				
			||||||
@ -144,12 +73,8 @@ pub fn Spliterator(comptime Element: type) type {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
            return self.source;
 | 
					            return self.source;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
test "Spliterating text" {
 | 
					 | 
				
			||||||
    const testing = std.testing;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        test "Iterate through data" {
 | 
				
			||||||
            // Single-character delimiter.
 | 
					            // Single-character delimiter.
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                var spliterator = Spliterator(u8){
 | 
					                var spliterator = Spliterator(u8){
 | 
				
			||||||
@ -157,11 +82,13 @@ test "Spliterating text" {
 | 
				
			|||||||
                    .delimiter = ".",
 | 
					                    .delimiter = ".",
 | 
				
			||||||
                };
 | 
					                };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const components = [_][]const u8{"single", "character", "separated", "hello", "world"};
 | 
					                const components = [_][]const u8{"single",
 | 
				
			||||||
 | 
					                    "character", "separated", "hello", "world"};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                var index = @as(usize, 0);
 | 
					                var index = @as(usize, 0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                while (spliterator.next()) |split| : (index += 1) {
 | 
					                while (spliterator.next()) |split| : (index += 1) {
 | 
				
			||||||
            try testing.expect(std.mem.eql(u8, split, components[index]));
 | 
					                    try testing.expect(equals(u8, split, components[index]));
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -176,10 +103,12 @@ test "Spliterating text" {
 | 
				
			|||||||
                var index = @as(usize, 0);
 | 
					                var index = @as(usize, 0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                while (spliterator.next()) |split| : (index += 1) {
 | 
					                while (spliterator.next()) |split| : (index += 1) {
 | 
				
			||||||
            try testing.expect(std.mem.eql(u8, split, components[index]));
 | 
					                    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,
 | 
				
			||||||
@ -198,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));
 | 
				
			||||||
@ -209,7 +137,7 @@ test "Check memory begins with" {
 | 
				
			|||||||
///
 | 
					///
 | 
				
			||||||
/// **Note** that passing a slice will convert it to a byte slice.
 | 
					/// **Note** that passing a slice will convert it to a byte slice.
 | 
				
			||||||
///
 | 
					///
 | 
				
			||||||
pub fn bytes(pointer: anytype) switch (@typeInfo(@TypeOf(pointer))) {
 | 
					pub fn bytesOf(pointer: anytype) switch (@typeInfo(@TypeOf(pointer))) {
 | 
				
			||||||
    .Pointer => |info| if (info.is_const) []const u8 else []u8,
 | 
					    .Pointer => |info| if (info.is_const) []const u8 else []u8,
 | 
				
			||||||
    else => @compileError("`pointer` must be a pointer type"),
 | 
					    else => @compileError("`pointer` must be a pointer type"),
 | 
				
			||||||
} {
 | 
					} {
 | 
				
			||||||
@ -229,11 +157,48 @@ pub fn bytes(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(bytes(&foo), 0x0a);
 | 
					    try testing.expect(bytesOf(&foo)[0] == 0x0a);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					///
 | 
				
			||||||
 | 
					/// Compares `this` to `that`, returning the difference between the first byte deviation in the two
 | 
				
			||||||
 | 
					/// sequences, otherwise `0` if they are identical.
 | 
				
			||||||
 | 
					///
 | 
				
			||||||
 | 
					pub fn compareBytes(this: []const u8, that: []const u8) isize {
 | 
				
			||||||
 | 
					    const range = math.min(usize, this.len, that.len);
 | 
				
			||||||
 | 
					    var index: usize = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    while (index < range) : (index += 1) {
 | 
				
			||||||
 | 
					        const difference = (this[index] - that[index]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (difference != 0) return difference;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return (@intCast(isize, this.len) - @intCast(isize, that.len));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					test "Compare bytes" {
 | 
				
			||||||
 | 
					    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);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					///
 | 
				
			||||||
 | 
					/// Copies the contents of `source` into `target`
 | 
				
			||||||
 | 
					///
 | 
				
			||||||
 | 
					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);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
///
 | 
					///
 | 
				
			||||||
@ -243,23 +208,72 @@ test "Bytes of types" {
 | 
				
			|||||||
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 index: usize = 0;
 | 
				
			||||||
        var i = std.mem.zeroes(usize);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        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;
 | 
				
			||||||
 | 
					    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`.
 | 
				
			||||||
///
 | 
					///
 | 
				
			||||||
@ -275,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.
 | 
				
			||||||
///
 | 
					///
 | 
				
			||||||
@ -288,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;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@ -296,11 +328,7 @@ pub fn nullWriter() Writer {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
test "Null writing" {
 | 
					test "Null writing" {
 | 
				
			||||||
    const testing = std.testing;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
    const sequence = "foo";
 | 
					    const sequence = "foo";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        try testing.expectEqual(nullWriter().apply(sequence), sequence.len);
 | 
					    try testing.expect(nullWriter().call(sequence) == sequence.len);
 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -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
									
								
							
							
						
						
									
										26
									
								
								src/core/math.zig
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,26 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					pub const IntFittingRange = @import("std").math.IntFittingRange;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					///
 | 
				
			||||||
 | 
					/// 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"),
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -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,38 +24,38 @@ 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,
 | 
				
			||||||
            };
 | 
					            };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (Captures != void)
 | 
				
			||||||
                @ptrCast(*Captures, @alignCast(captures_align, &function.context)).* = captures;
 | 
					                @ptrCast(*Captures, @alignCast(captures_align, &function.context)).* = captures;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            return function;
 | 
					            return function;
 | 
				
			||||||
 | 
				
			|||||||
@ -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" {
 | 
				
			||||||
    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
									
								
							
							
						
						
									
										18
									
								
								src/core/testing.zig
									
									
									
									
									
										Normal 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
									
								
							
							
						
						
									
										84
									
								
								src/core/unicode.zig
									
									
									
									
									
										Normal 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.
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										253
									
								
								src/oar/main.zig
									
									
									
									
									
								
							
							
						
						
									
										253
									
								
								src/oar/main.zig
									
									
									
									
									
								
							@ -1,253 +0,0 @@
 | 
				
			|||||||
const core = @import("core");
 | 
					 | 
				
			||||||
const std = @import("std");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
///
 | 
					 | 
				
			||||||
///
 | 
					 | 
				
			||||||
///
 | 
					 | 
				
			||||||
pub const Archive = struct {
 | 
					 | 
				
			||||||
    pub fn deinit(archive: *Archive) {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    pub fn init(file_system: *const sys.FileSystem, file_path: sys.Path) {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
///
 | 
					 | 
				
			||||||
/// An entry block of an Oar archive file.
 | 
					 | 
				
			||||||
///
 | 
					 | 
				
			||||||
/// Typically, following the block in memory is the file data it holds the meta-information for.
 | 
					 | 
				
			||||||
///
 | 
					 | 
				
			||||||
pub const Entry = extern struct {
 | 
					 | 
				
			||||||
    signature: [signature_magic.len]u8 = signature_magic,
 | 
					 | 
				
			||||||
    revision: u8,
 | 
					 | 
				
			||||||
    path: Path,
 | 
					 | 
				
			||||||
    file_size: u64,
 | 
					 | 
				
			||||||
    absolute_offset: u64,
 | 
					 | 
				
			||||||
    padding: [232]u8 = std.mem.zeroes([232]u8),
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    comptime {
 | 
					 | 
				
			||||||
        const entry_size = @sizeOf(Entry);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (entry_size != 512)
 | 
					 | 
				
			||||||
            @compileError("Entry is " ++
 | 
					 | 
				
			||||||
                std.fmt.comptimePrint("{d}", .{entry_size}) ++ " bytes");
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    ///
 | 
					 | 
				
			||||||
    /// Attempts to read the next [Entry] from `file_access`.
 | 
					 | 
				
			||||||
    ///
 | 
					 | 
				
			||||||
    /// Returns the read [Entry], `null` if there is no more to read, or a
 | 
					 | 
				
			||||||
    /// [core.io.FileAccess.Error] if it failed.
 | 
					 | 
				
			||||||
    ///
 | 
					 | 
				
			||||||
    pub fn next(file_access: core.io.FileAccess) core.io.FileAccess.Error!?Entry {
 | 
					 | 
				
			||||||
        const mem = std.mem;
 | 
					 | 
				
			||||||
        var entry = mem.zeroes(Entry);
 | 
					 | 
				
			||||||
        const origin = try file_access.queryCursor();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (((try file_access.read(mem.asBytes(&entry))) != @sizeOf(Entry)) and
 | 
					 | 
				
			||||||
            core.io.equals(u8, &entry.signature, &signature_magic)) {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            try file_access.seek(origin);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            return null;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return entry;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    ///
 | 
					 | 
				
			||||||
    /// Magic identifier used to validate [Entry] data.
 | 
					 | 
				
			||||||
    ///
 | 
					 | 
				
			||||||
    pub const signature_magic = [3]u8{'o', 'a', 'r'};
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
///
 | 
					 | 
				
			||||||
/// Unique identifier pointing to an entry within an archive.
 | 
					 | 
				
			||||||
///
 | 
					 | 
				
			||||||
/// A path does not do any verification that the given entry pointed to actually exists.
 | 
					 | 
				
			||||||
///
 | 
					 | 
				
			||||||
pub const Path = extern struct {
 | 
					 | 
				
			||||||
    buffer: [255]u8,
 | 
					 | 
				
			||||||
    length: u8,
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    ///
 | 
					 | 
				
			||||||
    /// [Error.TooLong] occurs when creating a path that is greater than the maximum path size **in
 | 
					 | 
				
			||||||
    /// bytes**.
 | 
					 | 
				
			||||||
    ///
 | 
					 | 
				
			||||||
    pub const Error = error {
 | 
					 | 
				
			||||||
        TooLong,
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    ///
 | 
					 | 
				
			||||||
    /// An empty [Path] with a length of `0`.
 | 
					 | 
				
			||||||
    ///
 | 
					 | 
				
			||||||
    pub const empty = std.mem.zeroes(Path);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    ///
 | 
					 | 
				
			||||||
    /// Returns `true` if `this_path` is equal to `that_path, otherwise `false`.
 | 
					 | 
				
			||||||
    ///
 | 
					 | 
				
			||||||
    pub fn equals(this_path: Path, that_path: Path) bool {
 | 
					 | 
				
			||||||
        return core.io.equals(u8, this_path.buffer[0 ..this_path.
 | 
					 | 
				
			||||||
            length], that_path.buffer[0 .. that_path.length]);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    ///
 | 
					 | 
				
			||||||
    /// Returns the hash of the text in `path`.
 | 
					 | 
				
			||||||
    ///
 | 
					 | 
				
			||||||
    pub fn hash(path: Path) usize {
 | 
					 | 
				
			||||||
        return core.io.hashBytes(path.buffer[0 .. path.length]);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    ///
 | 
					 | 
				
			||||||
    /// Attempts to create a [Path] with the path components in `sequences` as a fully qualified
 | 
					 | 
				
			||||||
    /// path from root.
 | 
					 | 
				
			||||||
    ///
 | 
					 | 
				
			||||||
    /// A [Path] value is returned containing the fully qualified path from the file-system root or
 | 
					 | 
				
			||||||
    /// a [Error] if it could not be created.
 | 
					 | 
				
			||||||
    ///
 | 
					 | 
				
			||||||
    pub fn joined(sequences: []const []const u8) Error!Path {
 | 
					 | 
				
			||||||
        var path = empty;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (sequences.len != 0) {
 | 
					 | 
				
			||||||
            const last_sequence_index = sequences.len - 1;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            for (sequences) |sequence, index| if (sequence.len != 0) {
 | 
					 | 
				
			||||||
                var components = core.io.Spliterator(u8){
 | 
					 | 
				
			||||||
                    .source = sequence,
 | 
					 | 
				
			||||||
                    .delimiter = "/",
 | 
					 | 
				
			||||||
                };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                while (components.next()) |component| if (component.len != 0) {
 | 
					 | 
				
			||||||
                    for (component) |byte| {
 | 
					 | 
				
			||||||
                        if (path.length == max) return error.TooLong;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                        path.buffer[path.length] = byte;
 | 
					 | 
				
			||||||
                        path.length += 1;
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    if (components.hasNext()) {
 | 
					 | 
				
			||||||
                        if (path.length == max) return error.TooLong;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                        path.buffer[path.length] = '/';
 | 
					 | 
				
			||||||
                        path.length += 1;
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                if (index < last_sequence_index) {
 | 
					 | 
				
			||||||
                    if (path.length == max) return error.TooLong;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    path.buffer[path.length] = '/';
 | 
					 | 
				
			||||||
                    path.length += 1;
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            };
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return path;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    ///
 | 
					 | 
				
			||||||
    /// Maximum number of **bytes** in a [Path].
 | 
					 | 
				
			||||||
    ///
 | 
					 | 
				
			||||||
    pub const max = 255;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    ///
 | 
					 | 
				
			||||||
    /// Textual separator between components of a [Path].
 | 
					 | 
				
			||||||
    ///
 | 
					 | 
				
			||||||
    pub const seperator = '/';
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
test "Path" {
 | 
					 | 
				
			||||||
    const testing = std.testing;
 | 
					 | 
				
			||||||
    const empty_path = Path.empty;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    try testing.expectEqual(empty_path.length, 0);
 | 
					 | 
				
			||||||
    try testing.expect(empty_path.equals(Path.empty));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const joined_component_path = try Path.joined(&.{"path", "to/my", "/file"});
 | 
					 | 
				
			||||||
    const joined_normalized_path = try Path.joined(&.{"path/to/my/file"});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    try testing.expectEqual(joined_component_path.length, joined_normalized_path.length);
 | 
					 | 
				
			||||||
    try testing.expect(joined_component_path.equals(joined_normalized_path));
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
///
 | 
					 | 
				
			||||||
/// Starts the **O**na **Ar**chive packer utility.
 | 
					 | 
				
			||||||
///
 | 
					 | 
				
			||||||
pub fn main() u8 {
 | 
					 | 
				
			||||||
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    defer std.debug.assert(!gpa.deinit());
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const allocator = gpa.allocator();
 | 
					 | 
				
			||||||
    const out_writer = std.io.getStdOut().writer();
 | 
					 | 
				
			||||||
    const process = std.process;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const args = process.argsAlloc(allocator) catch {
 | 
					 | 
				
			||||||
        out_writer.print("Failed to allocate args memory\n", .{}) catch undefined;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return 1;
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    defer process.argsFree(allocator, args);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (args.len < 2) {
 | 
					 | 
				
			||||||
        out_writer.print("Usage: oar [OPTION]... [FILE]...\n", .{}) catch undefined;
 | 
					 | 
				
			||||||
        out_writer.print("Options and arguments\n", .{}) catch undefined;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return 0;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const arg = std.mem.sliceTo(args[1], 0);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (core.io.equals(u8, arg, "--create")) {
 | 
					 | 
				
			||||||
        if (args.len < 3) {
 | 
					 | 
				
			||||||
            out_writer.print("Expected output file specified after `--create`\n", .{}) catch undefined;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            return 1;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        var archive = Archive.init(allocator, Path.joined(&.{args[2]})) catch {
 | 
					 | 
				
			||||||
            out_writer.print("Failed to initialize archive for create\n", .{}) catch undefined;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            return 1;
 | 
					 | 
				
			||||||
        };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        defer archive.deinit();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        for (args[3 .. ]) |input_file_path| {
 | 
					 | 
				
			||||||
            const file = std.fs.cwd().openFile(input_file_path) catch {
 | 
					 | 
				
			||||||
                out_writer.print("Failed to open {s}\n", .{input_file_path}) catch undefined;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                return 1;
 | 
					 | 
				
			||||||
            };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            defer file.close();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            var entry = archive.open(Path.joined(&.{input_file_path})) catch {
 | 
					 | 
				
			||||||
                out_writer.print("Failed to open {s}\n", .{input_file_path}) catch undefined;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                return 1;
 | 
					 | 
				
			||||||
            };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            defer archive.close(entry);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            var copy_buffer = std.mem.zeroes([4096]u8);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            while (true) {
 | 
					 | 
				
			||||||
                const read = try file.read(©_buffer);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                if (read == 0) break;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                try entry.write(copy_buffer[read ..]);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return 0;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    out_writer.print("Unrecognized command-line option `{s}`\n", .{arg}) catch undefined;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return 0;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@ -1,5 +1,4 @@
 | 
				
			|||||||
test {
 | 
					test {
 | 
				
			||||||
    _ = @import("./core/main.zig");
 | 
					    _ = @import("./core/main.zig");
 | 
				
			||||||
    _ = @import("./oar/main.zig");
 | 
					 | 
				
			||||||
    _ = @import("./ona/main.zig");
 | 
					    _ = @import("./ona/main.zig");
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user