From 85bcada8cf78bdf2bfb3be583289686026e0f25e Mon Sep 17 00:00:00 2001 From: Nathan Reiner Date: Sat, 1 Feb 2025 12:47:35 +0100 Subject: screen: start drm implementation --- .gitignore | 1 + build.zig | 73 ++++++++--- build.zig.zon | 15 --- build/build-kernel | 24 ++++ build/run-qemu | 16 +++ eostre/build.zig | 20 --- eostre/build.zig.zon | 10 -- eostre/lib/cursor.zig | 69 ---------- eostre/lib/parser/common.zig | 28 ----- eostre/lib/parser/context.zig | 9 -- eostre/lib/parser/root.zig | 284 ------------------------------------------ eostre/lib/root.zig | 8 -- flake.nix | 15 ++- src/estd/cursor.zig | 69 ++++++++++ src/estd/parser/common.zig | 28 +++++ src/estd/parser/context.zig | 9 ++ src/estd/parser/root.zig | 284 ++++++++++++++++++++++++++++++++++++++++++ src/estd/root.zig | 8 ++ src/init/main.zig | 21 ++++ src/main.zig | 157 ----------------------- src/screen/cerror.zig | 272 ++++++++++++++++++++++++++++++++++++++++ src/screen/drm/card.zig | 45 +++++++ src/screen/drm/connector.zig | 156 +++++++++++++++++++++++ src/screen/drm/request.zig | 19 +++ src/screen/drm/resources.zig | 76 +++++++++++ src/screen/main.zig | 20 +++ src/shell/main.zig | 157 +++++++++++++++++++++++ 27 files changed, 1272 insertions(+), 621 deletions(-) delete mode 100644 build.zig.zon create mode 100755 build/build-kernel create mode 100755 build/run-qemu delete mode 100644 eostre/build.zig delete mode 100644 eostre/build.zig.zon delete mode 100644 eostre/lib/cursor.zig delete mode 100644 eostre/lib/parser/common.zig delete mode 100644 eostre/lib/parser/context.zig delete mode 100644 eostre/lib/parser/root.zig delete mode 100644 eostre/lib/root.zig create mode 100644 src/estd/cursor.zig create mode 100644 src/estd/parser/common.zig create mode 100644 src/estd/parser/context.zig create mode 100644 src/estd/parser/root.zig create mode 100644 src/estd/root.zig create mode 100644 src/init/main.zig delete mode 100644 src/main.zig create mode 100644 src/screen/cerror.zig create mode 100644 src/screen/drm/card.zig create mode 100644 src/screen/drm/connector.zig create mode 100644 src/screen/drm/request.zig create mode 100644 src/screen/drm/resources.zig create mode 100644 src/screen/main.zig create mode 100644 src/shell/main.zig diff --git a/.gitignore b/.gitignore index 6781a58..5da3d0f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ .direnv .zig-cache zig-out +.kernel diff --git a/build.zig b/build.zig index 40cae2d..14f17d6 100644 --- a/build.zig +++ b/build.zig @@ -1,39 +1,72 @@ const std = @import("std"); +const Program = struct { + name: []const u8, + qemu: bool, +}; + +const programs = [_]Program { + .{ .name = "shell", .qemu = false }, + .{ .name = "screen", .qemu = true }, +}; + pub fn build(b: *std.Build) void { const target = b.standardTargetOptions(.{}); const optimize = b.standardOptimizeOption(.{}); - const pkg = b.dependency("eostre", .{ - .target = target, - .optimize = optimize, + const estd = b.createModule(.{ + .root_source_file = b.path("src/estd/root.zig"), }); - const exe = b.addExecutable(.{ - .name = "shell", - .root_source_file = b.path("src/main.zig"), + const init_exe = b.addExecutable(.{ + .name = "init", + .root_source_file = b.path("src/init/main.zig"), .target = target, - .optimize = optimize + .optimize = optimize, }); + init_exe.root_module.addImport("estd", estd); + + const test_step = b.step("test", "Run tests"); - const run_exe = b.addRunArtifact(exe); - const run_step = b.step("run", "Run the application"); - run_step.dependOn(&run_exe.step); + inline for (programs) |program| { + const path = b.path("src/" ++ program.name ++ "/main.zig"); - exe.root_module.addImport("eostre", pkg.module("eostre")); + const exe = b.addExecutable(.{ + .name = program.name, + .root_source_file = path, + .target = target, + .optimize = optimize + }); + exe.root_module.addImport("estd", estd); + b.installArtifact(exe); - const test_step = b.step("test", "Run unit tests"); + const run = b.step("run-" ++ program.name, "Run " ++ program.name); + + if (program.qemu) { + const run_qemu = b.addSystemCommand(&.{ "./build/run-qemu" }); + run_qemu.addFileArg(init_exe.getEmittedBin()); + run_qemu.addFileArg(exe.getEmittedBin()); + run_qemu.step.dependOn(&exe.step); + run_qemu.step.dependOn(&init_exe.step); + run.dependOn(&run_qemu.step); + } else { + const run_artifact = b.addRunArtifact(exe); + run.dependOn(&run_artifact.step); + } + + const tests = b.addTest(.{ + .name = program.name, + .root_source_file = path, + }); + tests.root_module.addImport("estd", estd); + const run_tests = b.addRunArtifact(tests); + test_step.dependOn(&run_tests.step); + } const tests = b.addTest(.{ - .name = "shell", - .root_source_file = b.path("src/main.zig"), - .target = target, + .name = "estd", + .root_source_file = b.path("src/estd/root.zig"), }); - tests.root_module.addImport("eostre", pkg.module("eostre")); - const run_tests = b.addRunArtifact(tests); test_step.dependOn(&run_tests.step); - - const lib_tests = b.addRunArtifact(pkg.artifact("eostre")); - test_step.dependOn(&lib_tests.step); } diff --git a/build.zig.zon b/build.zig.zon deleted file mode 100644 index 311d2b8..0000000 --- a/build.zig.zon +++ /dev/null @@ -1,15 +0,0 @@ -.{ - .name = "shell", - .version = "0.0.0", - .dependencies = .{ - .eostre = .{ - .path = "./eostre", - }, - }, - - .paths = .{ - "src", - "build.zig", - "build.zig.zon" - }, -} diff --git a/build/build-kernel b/build/build-kernel new file mode 100755 index 0000000..4265e94 --- /dev/null +++ b/build/build-kernel @@ -0,0 +1,24 @@ +#!/bin/sh + +VERSION="6.12.11" +MAJOR_VERSION="$(echo "$VERSION" | sed -E 's/^([0-9]*)\..*$/\1/g')" + +SRC_DIR=".kernel" +URL="https://cdn.kernel.org/pub/linux/kernel/v$MAJOR_VERSION.x/linux-$VERSION.tar.xz" + +TARGET_KERNEL="linux-$VERSION/arch/x86_64/boot/bzImage" + +mkdir -p "$SRC_DIR" +cd "$SRC_DIR" + +if [ ! -e "linux-$VERSION" ]; then + curl "$URL" | tar -J -xvf - +fi + +if [ ! -e "$TARGET_KERNEL" ]; then + cd "linux-$VERSION" + make defconfig + make -j$(nproc) +fi + +echo "$SRC_DIR/$TARGET_KERNEL" diff --git a/build/run-qemu b/build/run-qemu new file mode 100755 index 0000000..dbe7895 --- /dev/null +++ b/build/run-qemu @@ -0,0 +1,16 @@ +#!/bin/sh + +KERNEL_PATH=$(./build/build-kernel) + +mkdir -p .kernel/initramfs +cp "$1" .kernel/initramfs/init +cp "$2" .kernel/initramfs/process +printf "./init\n./process\n" | cpio -D .kernel/initramfs/ --quiet -H newc -o | gzip -9 -n > .kernel/initramfs.gz + +qemu-system-x86_64 \ + --cpu host \ + --enable-kvm \ + -initrd .kernel/initramfs.gz \ + -kernel "$KERNEL_PATH" \ + -append "quiet rdinit=init" \ + -device virtio-gpu-pci diff --git a/eostre/build.zig b/eostre/build.zig deleted file mode 100644 index da302f9..0000000 --- a/eostre/build.zig +++ /dev/null @@ -1,20 +0,0 @@ -const std = @import("std"); - -pub fn build(b: *std.Build) void { - const target = b.standardTargetOptions(.{}); - const optimize = b.standardOptimizeOption(.{}); - - _ = b.addModule("eostre", .{ - .root_source_file = b.path("lib/root.zig"), - .target = target, - .optimize = optimize, - }); - - const tests = b.addTest(.{ - .name = "eostre", - .root_source_file = b.path("lib/root.zig"), - .target = target, - }); - - b.installArtifact(tests); -} diff --git a/eostre/build.zig.zon b/eostre/build.zig.zon deleted file mode 100644 index 39514b6..0000000 --- a/eostre/build.zig.zon +++ /dev/null @@ -1,10 +0,0 @@ -.{ - .name = "eostre", - .version = "0.0.0", - - .paths = .{ - "lib", - "build.zig", - "build.zig.zon" - } -} diff --git a/eostre/lib/cursor.zig b/eostre/lib/cursor.zig deleted file mode 100644 index c0a4a32..0000000 --- a/eostre/lib/cursor.zig +++ /dev/null @@ -1,69 +0,0 @@ -const std = @import("std"); - -pub const ReadError = error { - EndOfBuffer -}; - -pub fn Cursor(comptime T: type) type { - return struct { - const Self = @This(); - - buffer: []const T, - index: usize, - touched_index: usize, - - pub fn init(buffer: []const T) Self { - return .{ - .buffer = buffer, - .index = 0, - .touched_index = 0, - }; - } - - pub fn peek(self: Self, amount: usize) ReadError![]const T { - if (self.index + amount > self.buffer.len) { - return ReadError.EndOfBuffer; - } - - return self.buffer[self.index..self.index + amount]; - } - - pub fn consume(self: *Self, amount: usize) ReadError![]const T { - const slice = try self.peek(amount); - self.index += amount; - self.touched_index = self.index; - return slice; - } - - pub fn snapshot(self: Self) usize { - return self.index; - } - - pub fn rollback(self: *Self, index: usize) void { - self.index = index; - } - - pub fn touched_slice(self: Self) []const T { - return self.buffer[0..self.touched_index]; - } - }; -} - -test "peek" { - const buffer = "1234"; - const cursor = Cursor(u8).init(buffer); - try std.testing.expectEqualSlices(u8, "1", try cursor.peek(1)); - try std.testing.expectEqualSlices(u8, "12", try cursor.peek(2)); - try std.testing.expectEqualSlices(u8, "123", try cursor.peek(3)); - try std.testing.expectEqualSlices(u8, "1234", try cursor.peek(4)); - try std.testing.expectError(ReadError.EndOfBuffer, cursor.peek(5)); -} - -test "consume" { - const buffer = "12345678"; - var cursor = Cursor(u8).init(buffer); - try std.testing.expectEqualSlices(u8, "1", try cursor.consume(1)); - try std.testing.expectEqualSlices(u8, "23", try cursor.consume(2)); - try std.testing.expectEqualSlices(u8, "456", try cursor.consume(3)); - try std.testing.expectError(ReadError.EndOfBuffer, cursor.consume(4)); -} diff --git a/eostre/lib/parser/common.zig b/eostre/lib/parser/common.zig deleted file mode 100644 index 986af58..0000000 --- a/eostre/lib/parser/common.zig +++ /dev/null @@ -1,28 +0,0 @@ -const std = @import("std"); -const root = @import("root.zig"); -const Context = @import("context.zig").Context; -const ParseFn = root.ParseFn; - -pub fn LiteralFn(comptime I: type, comptime O: type) type { - return *const fn (comptime literal: []const I, value: O) ParseFn(I, O); -} - -pub fn Literal(comptime I: type, comptime O: type) LiteralFn(I, O) { - return struct { - pub fn literal(comptime lit: []const I, value: O) ParseFn(I, O) { - return struct { - pub fn parse(ctx: *Context(I)) !O { - const snapshot = ctx.cursor.snapshot(); - errdefer ctx.cursor.rollback(snapshot); - - const slice = try ctx.cursor.consume(lit.len); - if (std.mem.eql(I, lit, slice)) { - return value; - } - - return error.UnexpectedLiteral; - } - }.parse; - } - }.literal; -} diff --git a/eostre/lib/parser/context.zig b/eostre/lib/parser/context.zig deleted file mode 100644 index 56fb025..0000000 --- a/eostre/lib/parser/context.zig +++ /dev/null @@ -1,9 +0,0 @@ -const std = @import("std"); -const Cursor = @import("../cursor.zig").Cursor; - -pub fn Context(comptime I: type) type { - return struct { - cursor: Cursor(I), - allocator: std.mem.Allocator, - }; -} diff --git a/eostre/lib/parser/root.zig b/eostre/lib/parser/root.zig deleted file mode 100644 index b168a88..0000000 --- a/eostre/lib/parser/root.zig +++ /dev/null @@ -1,284 +0,0 @@ -const std = @import("std"); -const Cursor = @import("../cursor.zig").Cursor; -pub const common = @import("common.zig"); -pub const Context = @import("context.zig").Context; - - -pub fn ParseFn(comptime I: type, comptime O: type) type { - return *const fn (ctx: *Context(I)) anyerror!O; -} - -pub fn Parser(comptime I: type, comptime O: type, comptime parse_fn: ParseFn(I, O)) type { - return struct { - const Self = @This(); - - pub fn parse(ctx: *Context(I)) !O { - return try parse_fn(ctx); - } - - pub fn or_else(comptime other: ParseFn(I, O)) type { - return Parser(I, O, struct { - pub fn parse(ctx: *Context(I)) !O { - return parse_fn(ctx) catch other(ctx); - } - }.parse); - } - - pub fn and_skip(comptime skip: anytype) type { - return Parser(I, O, struct { - pub fn parse(ctx: *Context(I)) !O { - const snapshot = ctx.cursor.snapshot(); - errdefer ctx.cursor.rollback(snapshot); - - _ = try skip(ctx); - return try parse_fn(ctx); - } - }.parse); - } - - pub fn then_skip(comptime skip: anytype) type { - return Parser(I, O, struct { - pub fn parse(ctx: *Context(I)) !O { - const snapshot = ctx.cursor.snapshot(); - errdefer ctx.cursor.rollback(snapshot); - const value = try parse_fn(ctx); - _ = try skip(ctx); - return value; - } - }.parse); - } - - pub fn then(comptime O2: type, comptime other: ParseFn(I, O2)) type { - const CombinedOutput = struct { O, O2 }; - - return Parser(I, CombinedOutput, struct { - pub fn parse(ctx: *Context(I)) !CombinedOutput { - const snapshot = ctx.cursor.snapshot(); - errdefer ctx.cursor.rollback(snapshot); - - return .{ try parse_fn(ctx), try other(ctx) }; - } - }.parse); - } - - pub fn zero_or_more() type { - return Parser(I, std.ArrayList(O), struct { - pub fn parse(ctx: *Context(I)) !std.ArrayList(O) { - var values = std.ArrayList(O).init(ctx.allocator); - errdefer values.deinit(); - - while (parse_fn(ctx)) |value| { - try values.append(value); - } else |_| {} - - return values; - } - }.parse); - } - - pub fn repeat(n: usize) type { - return Parser(I, [n]O, struct { - pub fn parse(ctx: *Context(I)) ![n]O { - const snapshot = ctx.cursor.snapshot(); - errdefer ctx.cursor.rollback(snapshot); - - var values: [n]O = undefined; - - for (&values) |*value| { - value.* = try parse_fn(ctx); - } - - return values; - } - }.parse); - } - - pub fn map(M: type, map_fn: *const fn (value: O) M) type { - return Parser(I, M, struct { - pub fn parse(ctx: *Context(I)) !M { - return map_fn(try parse_fn(ctx)); - } - }.parse); - } - - pub fn end() type { - return Parser(I, O, struct { - pub fn parse(ctx: *Context(I)) !O { - const value = try parse_fn(ctx); - _ = ctx.cursor.peek(1) catch return value; - return error.NotEnd; - } - }.parse); - } - }; -} - -fn parse_zero(ctx: *Context(u8)) !u8 { - const snapshot = ctx.cursor.snapshot(); - errdefer ctx.cursor.rollback(snapshot); - - const value = try ctx.cursor.consume(1); - - if (value[0] == '0') { - return 0; - } - - return error.NotZero; -} - -fn parse_one(ctx: *Context(u8)) !u8 { - const snapshot = ctx.cursor.snapshot(); - errdefer ctx.cursor.rollback(snapshot); - - const value = try ctx.cursor.consume(1); - - if (value[0] == '1') { - return 1; - } - - return error.NotOne; -} - - -test "direct" { - const ZeroParser = Parser(u8, u8, parse_zero); - - var ctx = Context(u8) { - .cursor = Cursor(u8).init("0"), - .allocator = std.testing.allocator, - }; - try std.testing.expectEqual(0, try ZeroParser.parse(&ctx)); - - ctx.cursor = Cursor(u8).init("1"); - try std.testing.expectError(error.NotZero, ZeroParser.parse(&ctx)); -} - -test "or_else" { - const ZeroParser = Parser(u8, u8, parse_zero); - const ZeroAndOneParser = ZeroParser.or_else(parse_one); - - var ctx = Context(u8) { - .cursor = Cursor(u8).init("0"), - .allocator = std.testing.allocator, - }; - try std.testing.expectEqual(0, try ZeroAndOneParser.parse(&ctx)); - - ctx.cursor = Cursor(u8).init("1"); - try std.testing.expectEqual(1, try ZeroAndOneParser.parse(&ctx)); - - ctx.cursor = Cursor(u8).init("2"); - try std.testing.expectError(error.NotZero, ZeroParser.parse(&ctx)); -} - -test "then" { - const ZeroParser = Parser(u8, u8, parse_zero); - const ZeroThenOneParser = ZeroParser.then(u8, parse_one); - - var ctx = Context(u8) { - .cursor = Cursor(u8).init("01"), - .allocator = std.testing.allocator, - }; - try std.testing.expectEqual(.{ 0, 1 }, try ZeroThenOneParser.parse(&ctx)); - - ctx.cursor = Cursor(u8).init("11"); - try std.testing.expectError(error.NotZero, ZeroThenOneParser.parse(&ctx)); - - ctx.cursor = Cursor(u8).init("00"); - try std.testing.expectError(error.NotOne, ZeroThenOneParser.parse(&ctx)); -} - -test "zero_or_more" { - const ZeroParser = Parser(u8, u8, parse_zero); - const ZeroWildcardParser = ZeroParser.zero_or_more(); - const ZeroOrOneWildcardParser = ZeroParser.or_else(parse_one).zero_or_more(); - - var ctx = Context(u8) { - .cursor = Cursor(u8).init("0000"), - .allocator = std.testing.allocator, - }; - var result = try ZeroWildcardParser.parse(&ctx); - try std.testing.expectEqualSlices(u8, &[_]u8{0, 0, 0, 0}, result.items); - result.deinit(); - - ctx.cursor = Cursor(u8).init("00001"); - result = try ZeroWildcardParser.parse(&ctx); - try std.testing.expectEqualSlices(u8, &[_]u8{0, 0, 0, 0}, result.items); - result.deinit(); - - ctx.cursor = Cursor(u8).init("0101110"); - result = try ZeroOrOneWildcardParser.parse(&ctx); - try std.testing.expectEqualSlices(u8, &[_]u8{0, 1, 0, 1, 1, 1, 0}, result.items); - result.deinit(); - - ctx.cursor = Cursor(u8).init("0101110abc"); - result = try ZeroOrOneWildcardParser.parse(&ctx); - try std.testing.expectEqualSlices(u8, &[_]u8{0, 1, 0, 1, 1, 1, 0}, result.items); - result.deinit(); -} - -test "repeat" { - const ZeroParser = Parser(u8, u8, parse_zero); - const FourZerosParser = ZeroParser.repeat(4); - - var ctx = Context(u8) { - .cursor = Cursor(u8).init("00001"), - .allocator = std.testing.allocator, - }; - try std.testing.expectEqualSlices( - u8, - &[_]u8{0, 0, 0, 0}, - &(try FourZerosParser.parse(&ctx)) - ); - - ctx.cursor = Cursor(u8).init("0001"); - try std.testing.expectError( - error.NotZero, - FourZerosParser.parse(&ctx) - ); -} - -test "map" { - const ZeroOrOneParser = Parser(u8, u8, parse_zero).or_else(parse_one); - const ZeroOrOneMappedParser = ZeroOrOneParser.map(u8, struct { - pub fn map(i: u8) u8 { - return i * 2; - } - }.map).zero_or_more(); - - var ctx = Context(u8) { - .cursor = Cursor(u8).init("00110101"), - .allocator = std.testing.allocator, - }; - const result = try ZeroOrOneMappedParser.parse(&ctx); - try std.testing.expectEqualSlices(u8, &[_]u8{ 0, 0, 2, 2, 0, 2, 0, 2 }, result.items); - defer result.deinit(); -} - -test "cursor rollback" { - const DoubleZeroOrOneParser = Parser(u8, u8, parse_zero).repeat(3).or_else( - Parser(u8, u8, parse_zero).repeat(2).then(u8, parse_one).map([3]u8, struct { - pub fn map(r: struct { [2]u8, u8 }) [3]u8 { - const array, const last = r; - return array ++ .{ last }; - } - }.map).parse - ); - var ctx = Context(u8) { - .cursor = Cursor(u8).init("0001"), - .allocator = std.testing.allocator, - }; - try std.testing.expectEqualSlices(u8, &[_]u8{ 0, 0, 0 }, &(try DoubleZeroOrOneParser.parse(&ctx))); - - ctx.cursor = Cursor(u8).init("0010"); - try std.testing.expectEqualSlices(u8, &[_]u8{ 0, 0, 1 }, &(try DoubleZeroOrOneParser.parse(&ctx))); -} - -test "cursor error touched slice" { - const OneThenDoubleZeroParser = Parser(u8, u8, parse_one).then([2]u8, Parser(u8, u8, parse_zero).repeat(2).parse); - var ctx = Context(u8) { - .cursor = Cursor(u8).init("10110"), - .allocator = std.testing.allocator, - }; - try std.testing.expectError(error.NotZero, OneThenDoubleZeroParser.parse(&ctx)); - try std.testing.expectEqualSlices(u8, "101", ctx.cursor.touched_slice()); -} diff --git a/eostre/lib/root.zig b/eostre/lib/root.zig deleted file mode 100644 index 86af090..0000000 --- a/eostre/lib/root.zig +++ /dev/null @@ -1,8 +0,0 @@ -const std = @import("std"); - -pub const cursor = @import("cursor.zig"); -pub const parser = @import("parser/root.zig"); - -test { - std.testing.refAllDecls(@This()); -} diff --git a/flake.nix b/flake.nix index 8ffc51e..4158197 100644 --- a/flake.nix +++ b/flake.nix @@ -12,7 +12,20 @@ { devShells.x86_64-linux.default = pkgs.zigStdenv.mkDerivation { name = "zig-dev-shell"; - buildInputs = [ pkgs.zig ]; + packages = [ pkgs.libdrm ]; + buildInputs = [ + pkgs.zig + pkgs.qemu_full + pkgs.gnumake + pkgs.gcc + pkgs.flex + pkgs.bison + pkgs.elfutils + pkgs.openssl + pkgs.moreutils + pkgs.libdrm + pkgs.pkg-config + ]; }; }; } diff --git a/src/estd/cursor.zig b/src/estd/cursor.zig new file mode 100644 index 0000000..c0a4a32 --- /dev/null +++ b/src/estd/cursor.zig @@ -0,0 +1,69 @@ +const std = @import("std"); + +pub const ReadError = error { + EndOfBuffer +}; + +pub fn Cursor(comptime T: type) type { + return struct { + const Self = @This(); + + buffer: []const T, + index: usize, + touched_index: usize, + + pub fn init(buffer: []const T) Self { + return .{ + .buffer = buffer, + .index = 0, + .touched_index = 0, + }; + } + + pub fn peek(self: Self, amount: usize) ReadError![]const T { + if (self.index + amount > self.buffer.len) { + return ReadError.EndOfBuffer; + } + + return self.buffer[self.index..self.index + amount]; + } + + pub fn consume(self: *Self, amount: usize) ReadError![]const T { + const slice = try self.peek(amount); + self.index += amount; + self.touched_index = self.index; + return slice; + } + + pub fn snapshot(self: Self) usize { + return self.index; + } + + pub fn rollback(self: *Self, index: usize) void { + self.index = index; + } + + pub fn touched_slice(self: Self) []const T { + return self.buffer[0..self.touched_index]; + } + }; +} + +test "peek" { + const buffer = "1234"; + const cursor = Cursor(u8).init(buffer); + try std.testing.expectEqualSlices(u8, "1", try cursor.peek(1)); + try std.testing.expectEqualSlices(u8, "12", try cursor.peek(2)); + try std.testing.expectEqualSlices(u8, "123", try cursor.peek(3)); + try std.testing.expectEqualSlices(u8, "1234", try cursor.peek(4)); + try std.testing.expectError(ReadError.EndOfBuffer, cursor.peek(5)); +} + +test "consume" { + const buffer = "12345678"; + var cursor = Cursor(u8).init(buffer); + try std.testing.expectEqualSlices(u8, "1", try cursor.consume(1)); + try std.testing.expectEqualSlices(u8, "23", try cursor.consume(2)); + try std.testing.expectEqualSlices(u8, "456", try cursor.consume(3)); + try std.testing.expectError(ReadError.EndOfBuffer, cursor.consume(4)); +} diff --git a/src/estd/parser/common.zig b/src/estd/parser/common.zig new file mode 100644 index 0000000..986af58 --- /dev/null +++ b/src/estd/parser/common.zig @@ -0,0 +1,28 @@ +const std = @import("std"); +const root = @import("root.zig"); +const Context = @import("context.zig").Context; +const ParseFn = root.ParseFn; + +pub fn LiteralFn(comptime I: type, comptime O: type) type { + return *const fn (comptime literal: []const I, value: O) ParseFn(I, O); +} + +pub fn Literal(comptime I: type, comptime O: type) LiteralFn(I, O) { + return struct { + pub fn literal(comptime lit: []const I, value: O) ParseFn(I, O) { + return struct { + pub fn parse(ctx: *Context(I)) !O { + const snapshot = ctx.cursor.snapshot(); + errdefer ctx.cursor.rollback(snapshot); + + const slice = try ctx.cursor.consume(lit.len); + if (std.mem.eql(I, lit, slice)) { + return value; + } + + return error.UnexpectedLiteral; + } + }.parse; + } + }.literal; +} diff --git a/src/estd/parser/context.zig b/src/estd/parser/context.zig new file mode 100644 index 0000000..56fb025 --- /dev/null +++ b/src/estd/parser/context.zig @@ -0,0 +1,9 @@ +const std = @import("std"); +const Cursor = @import("../cursor.zig").Cursor; + +pub fn Context(comptime I: type) type { + return struct { + cursor: Cursor(I), + allocator: std.mem.Allocator, + }; +} diff --git a/src/estd/parser/root.zig b/src/estd/parser/root.zig new file mode 100644 index 0000000..b168a88 --- /dev/null +++ b/src/estd/parser/root.zig @@ -0,0 +1,284 @@ +const std = @import("std"); +const Cursor = @import("../cursor.zig").Cursor; +pub const common = @import("common.zig"); +pub const Context = @import("context.zig").Context; + + +pub fn ParseFn(comptime I: type, comptime O: type) type { + return *const fn (ctx: *Context(I)) anyerror!O; +} + +pub fn Parser(comptime I: type, comptime O: type, comptime parse_fn: ParseFn(I, O)) type { + return struct { + const Self = @This(); + + pub fn parse(ctx: *Context(I)) !O { + return try parse_fn(ctx); + } + + pub fn or_else(comptime other: ParseFn(I, O)) type { + return Parser(I, O, struct { + pub fn parse(ctx: *Context(I)) !O { + return parse_fn(ctx) catch other(ctx); + } + }.parse); + } + + pub fn and_skip(comptime skip: anytype) type { + return Parser(I, O, struct { + pub fn parse(ctx: *Context(I)) !O { + const snapshot = ctx.cursor.snapshot(); + errdefer ctx.cursor.rollback(snapshot); + + _ = try skip(ctx); + return try parse_fn(ctx); + } + }.parse); + } + + pub fn then_skip(comptime skip: anytype) type { + return Parser(I, O, struct { + pub fn parse(ctx: *Context(I)) !O { + const snapshot = ctx.cursor.snapshot(); + errdefer ctx.cursor.rollback(snapshot); + const value = try parse_fn(ctx); + _ = try skip(ctx); + return value; + } + }.parse); + } + + pub fn then(comptime O2: type, comptime other: ParseFn(I, O2)) type { + const CombinedOutput = struct { O, O2 }; + + return Parser(I, CombinedOutput, struct { + pub fn parse(ctx: *Context(I)) !CombinedOutput { + const snapshot = ctx.cursor.snapshot(); + errdefer ctx.cursor.rollback(snapshot); + + return .{ try parse_fn(ctx), try other(ctx) }; + } + }.parse); + } + + pub fn zero_or_more() type { + return Parser(I, std.ArrayList(O), struct { + pub fn parse(ctx: *Context(I)) !std.ArrayList(O) { + var values = std.ArrayList(O).init(ctx.allocator); + errdefer values.deinit(); + + while (parse_fn(ctx)) |value| { + try values.append(value); + } else |_| {} + + return values; + } + }.parse); + } + + pub fn repeat(n: usize) type { + return Parser(I, [n]O, struct { + pub fn parse(ctx: *Context(I)) ![n]O { + const snapshot = ctx.cursor.snapshot(); + errdefer ctx.cursor.rollback(snapshot); + + var values: [n]O = undefined; + + for (&values) |*value| { + value.* = try parse_fn(ctx); + } + + return values; + } + }.parse); + } + + pub fn map(M: type, map_fn: *const fn (value: O) M) type { + return Parser(I, M, struct { + pub fn parse(ctx: *Context(I)) !M { + return map_fn(try parse_fn(ctx)); + } + }.parse); + } + + pub fn end() type { + return Parser(I, O, struct { + pub fn parse(ctx: *Context(I)) !O { + const value = try parse_fn(ctx); + _ = ctx.cursor.peek(1) catch return value; + return error.NotEnd; + } + }.parse); + } + }; +} + +fn parse_zero(ctx: *Context(u8)) !u8 { + const snapshot = ctx.cursor.snapshot(); + errdefer ctx.cursor.rollback(snapshot); + + const value = try ctx.cursor.consume(1); + + if (value[0] == '0') { + return 0; + } + + return error.NotZero; +} + +fn parse_one(ctx: *Context(u8)) !u8 { + const snapshot = ctx.cursor.snapshot(); + errdefer ctx.cursor.rollback(snapshot); + + const value = try ctx.cursor.consume(1); + + if (value[0] == '1') { + return 1; + } + + return error.NotOne; +} + + +test "direct" { + const ZeroParser = Parser(u8, u8, parse_zero); + + var ctx = Context(u8) { + .cursor = Cursor(u8).init("0"), + .allocator = std.testing.allocator, + }; + try std.testing.expectEqual(0, try ZeroParser.parse(&ctx)); + + ctx.cursor = Cursor(u8).init("1"); + try std.testing.expectError(error.NotZero, ZeroParser.parse(&ctx)); +} + +test "or_else" { + const ZeroParser = Parser(u8, u8, parse_zero); + const ZeroAndOneParser = ZeroParser.or_else(parse_one); + + var ctx = Context(u8) { + .cursor = Cursor(u8).init("0"), + .allocator = std.testing.allocator, + }; + try std.testing.expectEqual(0, try ZeroAndOneParser.parse(&ctx)); + + ctx.cursor = Cursor(u8).init("1"); + try std.testing.expectEqual(1, try ZeroAndOneParser.parse(&ctx)); + + ctx.cursor = Cursor(u8).init("2"); + try std.testing.expectError(error.NotZero, ZeroParser.parse(&ctx)); +} + +test "then" { + const ZeroParser = Parser(u8, u8, parse_zero); + const ZeroThenOneParser = ZeroParser.then(u8, parse_one); + + var ctx = Context(u8) { + .cursor = Cursor(u8).init("01"), + .allocator = std.testing.allocator, + }; + try std.testing.expectEqual(.{ 0, 1 }, try ZeroThenOneParser.parse(&ctx)); + + ctx.cursor = Cursor(u8).init("11"); + try std.testing.expectError(error.NotZero, ZeroThenOneParser.parse(&ctx)); + + ctx.cursor = Cursor(u8).init("00"); + try std.testing.expectError(error.NotOne, ZeroThenOneParser.parse(&ctx)); +} + +test "zero_or_more" { + const ZeroParser = Parser(u8, u8, parse_zero); + const ZeroWildcardParser = ZeroParser.zero_or_more(); + const ZeroOrOneWildcardParser = ZeroParser.or_else(parse_one).zero_or_more(); + + var ctx = Context(u8) { + .cursor = Cursor(u8).init("0000"), + .allocator = std.testing.allocator, + }; + var result = try ZeroWildcardParser.parse(&ctx); + try std.testing.expectEqualSlices(u8, &[_]u8{0, 0, 0, 0}, result.items); + result.deinit(); + + ctx.cursor = Cursor(u8).init("00001"); + result = try ZeroWildcardParser.parse(&ctx); + try std.testing.expectEqualSlices(u8, &[_]u8{0, 0, 0, 0}, result.items); + result.deinit(); + + ctx.cursor = Cursor(u8).init("0101110"); + result = try ZeroOrOneWildcardParser.parse(&ctx); + try std.testing.expectEqualSlices(u8, &[_]u8{0, 1, 0, 1, 1, 1, 0}, result.items); + result.deinit(); + + ctx.cursor = Cursor(u8).init("0101110abc"); + result = try ZeroOrOneWildcardParser.parse(&ctx); + try std.testing.expectEqualSlices(u8, &[_]u8{0, 1, 0, 1, 1, 1, 0}, result.items); + result.deinit(); +} + +test "repeat" { + const ZeroParser = Parser(u8, u8, parse_zero); + const FourZerosParser = ZeroParser.repeat(4); + + var ctx = Context(u8) { + .cursor = Cursor(u8).init("00001"), + .allocator = std.testing.allocator, + }; + try std.testing.expectEqualSlices( + u8, + &[_]u8{0, 0, 0, 0}, + &(try FourZerosParser.parse(&ctx)) + ); + + ctx.cursor = Cursor(u8).init("0001"); + try std.testing.expectError( + error.NotZero, + FourZerosParser.parse(&ctx) + ); +} + +test "map" { + const ZeroOrOneParser = Parser(u8, u8, parse_zero).or_else(parse_one); + const ZeroOrOneMappedParser = ZeroOrOneParser.map(u8, struct { + pub fn map(i: u8) u8 { + return i * 2; + } + }.map).zero_or_more(); + + var ctx = Context(u8) { + .cursor = Cursor(u8).init("00110101"), + .allocator = std.testing.allocator, + }; + const result = try ZeroOrOneMappedParser.parse(&ctx); + try std.testing.expectEqualSlices(u8, &[_]u8{ 0, 0, 2, 2, 0, 2, 0, 2 }, result.items); + defer result.deinit(); +} + +test "cursor rollback" { + const DoubleZeroOrOneParser = Parser(u8, u8, parse_zero).repeat(3).or_else( + Parser(u8, u8, parse_zero).repeat(2).then(u8, parse_one).map([3]u8, struct { + pub fn map(r: struct { [2]u8, u8 }) [3]u8 { + const array, const last = r; + return array ++ .{ last }; + } + }.map).parse + ); + var ctx = Context(u8) { + .cursor = Cursor(u8).init("0001"), + .allocator = std.testing.allocator, + }; + try std.testing.expectEqualSlices(u8, &[_]u8{ 0, 0, 0 }, &(try DoubleZeroOrOneParser.parse(&ctx))); + + ctx.cursor = Cursor(u8).init("0010"); + try std.testing.expectEqualSlices(u8, &[_]u8{ 0, 0, 1 }, &(try DoubleZeroOrOneParser.parse(&ctx))); +} + +test "cursor error touched slice" { + const OneThenDoubleZeroParser = Parser(u8, u8, parse_one).then([2]u8, Parser(u8, u8, parse_zero).repeat(2).parse); + var ctx = Context(u8) { + .cursor = Cursor(u8).init("10110"), + .allocator = std.testing.allocator, + }; + try std.testing.expectError(error.NotZero, OneThenDoubleZeroParser.parse(&ctx)); + try std.testing.expectEqualSlices(u8, "101", ctx.cursor.touched_slice()); +} diff --git a/src/estd/root.zig b/src/estd/root.zig new file mode 100644 index 0000000..86af090 --- /dev/null +++ b/src/estd/root.zig @@ -0,0 +1,8 @@ +const std = @import("std"); + +pub const cursor = @import("cursor.zig"); +pub const parser = @import("parser/root.zig"); + +test { + std.testing.refAllDecls(@This()); +} diff --git a/src/init/main.zig b/src/init/main.zig new file mode 100644 index 0000000..0af43bc --- /dev/null +++ b/src/init/main.zig @@ -0,0 +1,21 @@ +const std = @import("std"); +const os = std.os.linux; + +pub fn main() !void { + std.debug.print("\u{1b}[1;1H\u{1b}[J", .{}); + _ = std.os.linux.mount("none", "/dev/", "devtmpfs", 0, 0); + + + const pid = @as(i32, @intCast(os.fork())); + + if (pid == 0) { + _ = os.execve("process", &[_:null]?[*:0]const u8 { "process" }, &[0:null]?[*:0]const u8{}); + + return error.ExecFailed; + } + + var status: u32 = undefined; + _ = os.waitpid(pid, &status, 0); + + while (true) { std.time.sleep(100); } +} diff --git a/src/main.zig b/src/main.zig deleted file mode 100644 index 2144798..0000000 --- a/src/main.zig +++ /dev/null @@ -1,157 +0,0 @@ -const std = @import("std"); -const eostre = @import("eostre"); -const Cursor = eostre.cursor.Cursor; -const Parser = eostre.parser.Parser; -const Context = eostre.parser.Context; -const Literal = eostre.parser.common.Literal; -const ParseFn = eostre.parser.ParseFn; - -const Keyword = enum { - if_statement, - other -}; - -const String = struct { -}; - -const Token = union(enum) { - openparenthesis: void, - closeparenthesis: void, - openscope: void, - closescope: void, - backslash: void, - pipe: void, - semicolon: void, - logical_and: void, - logical_or: void, - assign: void, - keyword: Keyword, - string: String, -}; - -pub fn parse_string(ctx: *Context(u8)) !Token { - const snapshot = ctx.cursor.snapshot(); - errdefer ctx.cursor.rollback(snapshot); - - const first_char = (try ctx.cursor.consume(1))[0]; - - if (first_char == ' ') { - return error.NotAString; - } - - if (first_char == '"') { - while (ctx.cursor.consume(1) catch null) |c| { - if (c[0] == '\"') { - return Token.string; - } else if (c[0] == '{') { - while (ctx.cursor.consume(1) catch null) |inner| { - if (inner[0] == '}') { - break; - } - } else { - return error.UnterminatedString; - } - } - } else { - return error.UnterminatedString; - } - } else if (first_char == '\'') { - while (ctx.cursor.consume(1) catch null) |c| { - if (c[0] == '\'') { - return Token.string; - } - } else { - return error.UnterminatedString; - } - } - - while (ctx.cursor.consume(1) catch null) |c| { - if (c[0] == ' ') { - return Token.string; - } else if (c[0] == '{') { - while (ctx.cursor.consume(1) catch null) |inner| { - if (inner[0] == '}') { - break; - } - } else { - return error.UnterminatedString; - } - } - } else { - return error.UnterminatedString; - } -} - -const unterminated_keyword = Parser(u8, Keyword, keyword_literal("if", Keyword.if_statement)); - -fn keyword(ctx: *Context(u8)) !Token { - const snapshot = ctx.cursor.snapshot(); - errdefer ctx.cursor.rollback(snapshot); - - const token = try unterminated_keyword.parse(ctx); - const next_char = (try ctx.cursor.peek(1))[0]; - - switch (next_char) { - '(', ')', '{', '}', '\\', '&', '|', ';', '=', ' ' => return .{ .keyword = token }, - else => return error.UnterminatedKeyword, - } -} - -const literal = Literal(u8, Token); -const keyword_literal = Literal(u8, Keyword); -const skip_literal = Literal(u8, void); - -const Tokenizer = Parser(u8, Token, literal("(", Token.openparenthesis)) - .or_else(literal(")", Token.closeparenthesis)) - .or_else(literal("{", Token.openscope)) - .or_else(literal("}", Token.closescope)) - .or_else(literal("\\", Token.backslash)) - .or_else(literal("&&", Token.logical_and)) - .or_else(literal("||", Token.logical_or)) - .or_else(literal("|", Token.pipe)) - .or_else(literal(";", Token.semicolon)) - .or_else(literal("=", Token.assign)) - .or_else(keyword) - .or_else(parse_string) - .and_skip(Parser(u8, void, skip_literal(" ", {})).zero_or_more().parse) - .zero_or_more() - .then_skip(Parser(u8, void, skip_literal(" ", {})).zero_or_more().parse) - .end(); - -pub fn main() void { - std.debug.print("Hello, World!\n", .{}); -} - -test "tokenizer" { - const example = " | 'asdf' ( || \"hello\" ) or_something &&{ =}\\ ; if "; - - var ctx = Context(u8) { - .cursor = Cursor(u8).init(example), - .allocator = std.testing.allocator, - }; - - const tokens = try Tokenizer.parse(&ctx); - defer tokens.deinit(); - - - try std.testing.expectEqualSlices( - Token, - &[_]Token { - .pipe, - .string, - .openparenthesis, - .logical_or, - .string, - .closeparenthesis, - .string, - .logical_and, - .openscope, - .assign, - .closescope, - .backslash, - .semicolon, - .{ .keyword = Keyword.if_statement } - }, - tokens.items - ); -} diff --git a/src/screen/cerror.zig b/src/screen/cerror.zig new file mode 100644 index 0000000..cd7f6e6 --- /dev/null +++ b/src/screen/cerror.zig @@ -0,0 +1,272 @@ + +pub const CError = error { + OperationNotPermitted, + NoSuchFileOrDirectory, + NoSuchProcess, + InterruptedSystemCall, + InputOutputError, + NoSuchDeviceOrAddress, + ArgumentListTooLong, + ExecFormatError, + BadFileDescriptor, + NoChildProcesses, + ResourceTemporarilyUnavailable, + CannotAllocateMemory, + PermissionDenied, + BadAddress, + BlockDeviceRequired, + DeviceOrResourceBusy, + FileExists, + InvalidCrossDeviceLink, + NoSuchDevice, + NotADirectory, + IsADirectory, + InvalidArgument, + TooManyOpenFilesInSystem, + TooManyOpenFiles, + InappropriateIoctlForDevice, + TextFileBusy, + FileTooLarge, + NoSpaceLeftOnDevice, + IllegalSeek, + ReadOnlyFileSystem, + TooManyLinks, + BrokenPipe, + NumericalArgumentOutOfDomain, + NumericalResultOutOfRange, + ResourceDeadlockAvoided, + FileNameTooLong, + NoLocksAvailable, + FunctionNotImplemented, + DirectoryNotEmpty, + TooManyLevelsOfSymbolicLinks, + NoMessageOfDesiredType, + IdentifierRemoved, + ChannelNumberOutOfRange, + Level2NotSynchronized, + Level3Halted, + Level3Reset, + LinkNumberOutOfRange, + ProtocolDriverNotAttached, + NoCSIStructureAvailable, + Level2Halted, + InvalidExchange, + InvalidRequestDescriptor, + ExchangeFull, + NoAnode, + InvalidRequestCode, + InvalidSlot, + BadFontFileFormat, + DeviceNotAStream, + NoDataAvailable, + TimerExpired, + OutOfStreamsResources, + MachineIsNotOnTheNetwork, + PackageNotInstalled, + ObjectIsRemote, + LinkHasBeenSevered, + AdvertiseError, + SrmountError, + CommunicationErrorOnSend, + ProtocolError, + MultihopAttempted, + RFSSpecificError, + BadMessage, + ValueTooLargeForDefinedDataType, + NameNotUniqueOnNetwork, + FileDescriptorInBadState, + RemoteAddressChanged, + CanNotAccessANeededSharedLibrary, + AccessingACorruptedSharedLibrary, + LibSectionInAOutCorrupted, + AttemptingToLinkInTooManySharedLibraries, + CannotExecASharedLibraryDirectly, + InvalidOrIncompleteMultibyteOrWideCharacter, + InterruptedSystemCallShouldBeRestarted, + StreamsPipeError, + TooManyUsers, + SocketOperationOnNonSocket, + DestinationAddressRequired, + MessageTooLong, + ProtocolWrongTypeForSocket, + ProtocolNotAvailable, + ProtocolNotSupported, + SocketTypeNotSupported, + OperationNotSupported, + ProtocolFamilyNotSupported, + AddressFamilyNotSupportedByProtocol, + AddressAlreadyInUse, + CannotAssignRequestedAddress, + NetworkIsDown, + NetworkIsUnreachable, + NetworkDroppedConnectionOnReset, + SoftwareCausedConnectionAbort, + ConnectionResetByPeer, + NoBufferSpaceAvailable, + TransportEndpointIsAlreadyConnected, + TransportEndpointIsNotConnected, + CannotSendAfterTransportEndpointShutdown, + TooManyReferences, + ConnectionTimedOut, + ConnectionRefused, + HostIsDown, + NoRouteToHost, + OperationAlreadyInProgress, + OperationNowInProgress, + StaleFileHandle, + StructureNeedsCleaning, + NotAXENIXNamedTypeFile, + NoXENIXSemaphoresAvailable, + IsANamedTypeFile, + RemoteIOError, + DiskQuotaExceeded, + NoMediumFound, + WrongMediumType, + OperationCanceled, + RequiredKeyNotAvailable, + KeyHasExpired, + KeyHasBeenRevoked, + KeyWasRejectedByService, + OwnerDied, + StateNotRecoverable, + OperationNotPossibleDueToRFKill, + MemoryPageHasHardwareError, +}; + +pub fn from_usize(errno: usize) CError!void { + const n = -@as(isize, @bitCast(errno)); + return switch (n) { + 1 => CError.OperationNotPermitted, + 2 => CError.NoSuchFileOrDirectory, + 3 => CError.NoSuchProcess, + 4 => CError.InterruptedSystemCall, + 5 => CError.InputOutputError, + 6 => CError.NoSuchDeviceOrAddress, + 7 => CError.ArgumentListTooLong, + 8 => CError.ExecFormatError, + 9 => CError.BadFileDescriptor, + 10 => CError.NoChildProcesses, + 11 => CError.ResourceTemporarilyUnavailable, + 12 => CError.CannotAllocateMemory, + 13 => CError.PermissionDenied, + 14 => CError.BadAddress, + 15 => CError.BlockDeviceRequired, + 16 => CError.DeviceOrResourceBusy, + 17 => CError.FileExists, + 18 => CError.InvalidCrossDeviceLink, + 19 => CError.NoSuchDevice, + 20 => CError.NotADirectory, + 21 => CError.IsADirectory, + 22 => CError.InvalidArgument, + 23 => CError.TooManyOpenFilesInSystem, + 24 => CError.TooManyOpenFiles, + 25 => CError.InappropriateIoctlForDevice, + 26 => CError.TextFileBusy, + 27 => CError.FileTooLarge, + 28 => CError.NoSpaceLeftOnDevice, + 29 => CError.IllegalSeek, + 30 => CError.ReadOnlyFileSystem, + 31 => CError.TooManyLinks, + 32 => CError.BrokenPipe, + 33 => CError.NumericalArgumentOutOfDomain, + 34 => CError.NumericalResultOutOfRange, + 35 => CError.ResourceDeadlockAvoided, + 36 => CError.FileNameTooLong, + 37 => CError.NoLocksAvailable, + 38 => CError.FunctionNotImplemented, + 39 => CError.DirectoryNotEmpty, + 40 => CError.TooManyLevelsOfSymbolicLinks, + 42 => CError.NoMessageOfDesiredType, + 43 => CError.IdentifierRemoved, + 44 => CError.ChannelNumberOutOfRange, + 45 => CError.Level2NotSynchronized, + 46 => CError.Level3Halted, + 47 => CError.Level3Reset, + 48 => CError.LinkNumberOutOfRange, + 49 => CError.ProtocolDriverNotAttached, + 50 => CError.NoCSIStructureAvailable, + 51 => CError.Level2Halted, + 52 => CError.InvalidExchange, + 53 => CError.InvalidRequestDescriptor, + 54 => CError.ExchangeFull, + 55 => CError.NoAnode, + 56 => CError.InvalidRequestCode, + 57 => CError.InvalidSlot, + 59 => CError.BadFontFileFormat, + 60 => CError.DeviceNotAStream, + 61 => CError.NoDataAvailable, + 62 => CError.TimerExpired, + 63 => CError.OutOfStreamsResources, + 64 => CError.MachineIsNotOnTheNetwork, + 65 => CError.PackageNotInstalled, + 66 => CError.ObjectIsRemote, + 67 => CError.LinkHasBeenSevered, + 68 => CError.AdvertiseError, + 69 => CError.SrmountError, + 70 => CError.CommunicationErrorOnSend, + 71 => CError.ProtocolError, + 72 => CError.MultihopAttempted, + 73 => CError.RFSSpecificError, + 74 => CError.BadMessage, + 75 => CError.ValueTooLargeForDefinedDataType, + 76 => CError.NameNotUniqueOnNetwork, + 77 => CError.FileDescriptorInBadState, + 78 => CError.RemoteAddressChanged, + 79 => CError.CanNotAccessANeededSharedLibrary, + 80 => CError.AccessingACorruptedSharedLibrary, + 81 => CError.LibSectionInAOutCorrupted, + 82 => CError.AttemptingToLinkInTooManySharedLibraries, + 83 => CError.CannotExecASharedLibraryDirectly, + 84 => CError.InvalidOrIncompleteMultibyteOrWideCharacter, + 85 => CError.InterruptedSystemCallShouldBeRestarted, + 86 => CError.StreamsPipeError, + 87 => CError.TooManyUsers, + 88 => CError.SocketOperationOnNonSocket, + 89 => CError.DestinationAddressRequired, + 90 => CError.MessageTooLong, + 91 => CError.ProtocolWrongTypeForSocket, + 92 => CError.ProtocolNotAvailable, + 93 => CError.ProtocolNotSupported, + 94 => CError.SocketTypeNotSupported, + 95 => CError.OperationNotSupported, + 96 => CError.ProtocolFamilyNotSupported, + 97 => CError.AddressFamilyNotSupportedByProtocol, + 98 => CError.AddressAlreadyInUse, + 99 => CError.CannotAssignRequestedAddress, + 100 => CError.NetworkIsDown, + 101 => CError.NetworkIsUnreachable, + 102 => CError.NetworkDroppedConnectionOnReset, + 103 => CError.SoftwareCausedConnectionAbort, + 104 => CError.ConnectionResetByPeer, + 105 => CError.NoBufferSpaceAvailable, + 106 => CError.TransportEndpointIsAlreadyConnected, + 107 => CError.TransportEndpointIsNotConnected, + 108 => CError.CannotSendAfterTransportEndpointShutdown, + 109 => CError.TooManyReferences, + 110 => CError.ConnectionTimedOut, + 111 => CError.ConnectionRefused, + 112 => CError.HostIsDown, + 113 => CError.NoRouteToHost, + 114 => CError.OperationAlreadyInProgress, + 115 => CError.OperationNowInProgress, + 116 => CError.StaleFileHandle, + 117 => CError.StructureNeedsCleaning, + 118 => CError.NotAXENIXNamedTypeFile, + 119 => CError.NoXENIXSemaphoresAvailable, + 120 => CError.IsANamedTypeFile, + 121 => CError.RemoteIOError, + 122 => CError.DiskQuotaExceeded, + 123 => CError.NoMediumFound, + 124 => CError.WrongMediumType, + 125 => CError.OperationCanceled, + 126 => CError.RequiredKeyNotAvailable, + 127 => CError.KeyHasExpired, + 128 => CError.KeyHasBeenRevoked, + 129 => CError.KeyWasRejectedByService, + 130 => CError.OwnerDied, + 131 => CError.StateNotRecoverable, + 132 => CError.OperationNotPossibleDueToRFKill, + 133 => CError.MemoryPageHasHardwareError, + else => void{} + }; +} diff --git a/src/screen/drm/card.zig b/src/screen/drm/card.zig new file mode 100644 index 0000000..67f81f3 --- /dev/null +++ b/src/screen/drm/card.zig @@ -0,0 +1,45 @@ +const std = @import("std"); +const os = std.os.linux; + +const cerror = @import("../cerror.zig"); + +const Resources = @import("resources.zig").Resources; +const Connector = @import("connector.zig").Connector; + + +pub const Card = struct { + const Self = @This(); + + file: std.fs.File, + allocator: std.mem.Allocator, + + pub fn open(name: []const u8, allocator: std.mem.Allocator) !Self { + var dri_dir = try std.fs.openDirAbsolute("/dev/dri", .{}); + defer dri_dir.close(); + + return .{ + .file = try dri_dir.openFile(name, .{ + .mode = .read_write, + .lock_nonblocking = true, + }), + .allocator = allocator, + }; + } + + pub fn close(self: *Card) void { + self.file.close(); + } + + pub fn is_kms(self: *Card) !bool { + const raw_info = Resources.raw_without_ids(self); + return raw_info.count_crtcs > 0 and raw_info.count_connectors > 0 and raw_info.count_encoders > 0; + } + + pub fn resources(self: *Card) !Resources { + return Resources.init(self); + } + + pub fn connector(self: *Card, id: u32) !Connector { + return Connector.init(self, id); + } +}; diff --git a/src/screen/drm/connector.zig b/src/screen/drm/connector.zig new file mode 100644 index 0000000..9d7f9ad --- /dev/null +++ b/src/screen/drm/connector.zig @@ -0,0 +1,156 @@ +const std = @import("std"); +const os = std.os.linux; +const Drm = @import("request.zig").Drm; +const Card = @import("card.zig").Card; + +const RawConnector = extern struct { + encoder_ids: ?*u32, + modes: ?*ModeInfo, + prop_ids: ?*u32, + prop_value_ids: ?*u64, + count_modes: u32, + count_props: u32, + count_encoders: u32, + encoder_id: u32, + connector_id: u32, + connector_type: u32, + connector_type_id: u32, + connection: Connection, + mm_width: u32, + mm_height: u32, + subpixel: u32, + pad: u32, +}; + +const Connection = enum(u32) { + undefined_state = 0, + connected = 1, + disconnected = 2, + unknown = 3, +}; + +const ModeInfo = extern struct { + const Self = @This(); + const Flags = packed struct(u32) { + phsync: bool, + nhsync: bool, + pvsync: bool, + nvsync: bool, + interlace: bool, + dblscan: bool, + csync: bool, + pcsync: bool, + ncsync: bool, + hskew: bool, + bcast: bool, + pixmux: bool, + dblclk: bool, + clkdiv2: bool, + _padding: u18, + }; + + clock: u32, + hdisplay: u16, + hsync_start: u16, + hsync_end: u16, + htotal: u16, + hskew: u16, + vdisplay: u16, + vsync_start: u16, + vsync_end: u16, + vtotal: u16, + vscan: u16, + + vrefresh: u32, + + flags: Flags, + type: u32, + name: [32]u8, + + pub fn frame_rate(self: *const Self) f32 { + var rate = (@as(u64, @intCast(self.clock)) * 1000000 / self.htotal + self.vtotal / 2) / self.vtotal; + + if (self.flags.interlace) { + rate *= 2; + } + + if (self.flags.dblscan) { + rate /= 2; + } + + if (self.vscan > 1) { + rate /= self.vscan; + } + + return @as(f32, @floatFromInt(rate)) / 1000.0; + } +}; + +pub const Connector = struct { + const Self = @This(); + + card: *Card, + + encoder_ids: []u32, + modes: []ModeInfo, + prop_ids: []u32, + prop_value_ids: []u64, + encoder_id: u32, + connector_id: u32, + connector_type: u32, + connector_type_id: u32, + connection: Connection, + mm_width: u32, + mm_height: u32, + subpixel: u32, + pad: u32, + + pub fn raw_without_ids(card: *Card, id: u32) !RawConnector { + var result = std.mem.zeroInit(RawConnector, .{ .connector_id = id }); + try Drm.get_connector.request(card.file.handle, RawConnector, &result); + return result; + } + + // NOTE: This function does not take in account + // that there might be some hot-plugging going + // on. This might have to change in the future. + pub fn init(card: *Card, id: u32) !Self { + var raw = try Self.raw_without_ids(card, id); + const resources = .{ + .encoder_ids = try card.allocator.alloc(u32, raw.count_encoders), + .modes = try card.allocator.alloc(ModeInfo, raw.count_modes), + .prop_ids = try card.allocator.alloc(u32, raw.count_props), + .prop_value_ids = try card.allocator.alloc(u64, raw.count_props), + .encoder_id = raw.encoder_id, + .connector_id = raw.connector_id, + .connector_type = raw.connector_type, + .connector_type_id = raw.connector_type_id, + .connection = raw.connection, + .mm_width = raw.mm_width, + .mm_height = raw.mm_height, + .subpixel = raw.subpixel, + .pad = raw.pad, + .card = card, + }; + + @memset(resources.encoder_ids, 0); + @memset(resources.modes, std.mem.zeroes(ModeInfo)); + @memset(resources.prop_ids, 0); + @memset(resources.prop_value_ids, 0); + + raw.encoder_ids = @ptrCast(resources.encoder_ids); + raw.modes = @ptrCast(resources.modes); + raw.prop_ids = @ptrCast(resources.prop_ids); + raw.prop_value_ids = @ptrCast(resources.prop_value_ids); + + try Drm.get_connector.request(card.file.handle, RawConnector, &raw); + + return resources; + } + + pub fn deinit(self: *Self) void { + self.card.allocator.free(self.encoder_ids); + self.card.allocator.free(self.modes); + self.card.allocator.free(self.prop_ids); + } +}; diff --git a/src/screen/drm/request.zig b/src/screen/drm/request.zig new file mode 100644 index 0000000..bf9701c --- /dev/null +++ b/src/screen/drm/request.zig @@ -0,0 +1,19 @@ +const std = @import("std"); +const os = std.os.linux; +const cerror = @import("../cerror.zig"); + +fn ioctl(fd: os.fd_t, request: u32, arg: usize) !void { + try cerror.from_usize(os.ioctl(fd, request, arg)); +} + +pub const Drm = enum(u8) { + const Self = @This(); + + get_resources = 0xA0, + get_connector = 0xA7, + + pub fn request(self: Self, fd: os.fd_t, T: type, arg: *T) !void { + const id = os.IOCTL.IOWR('d', @intFromEnum(self), T); + try ioctl(fd, id, @intFromPtr(arg)); + } +}; diff --git a/src/screen/drm/resources.zig b/src/screen/drm/resources.zig new file mode 100644 index 0000000..6128f37 --- /dev/null +++ b/src/screen/drm/resources.zig @@ -0,0 +1,76 @@ +const std = @import("std"); +const os = std.os.linux; +const cerror = @import("../cerror.zig"); +const Card = @import("card.zig").Card; +const Drm = @import("request.zig").Drm; + +const RawResources = extern struct { + fb_ids: ?*u32, + crtc_ids: ?*u32, + connector_ids: ?*u32, + encoder_ids: ?*u32, + count_fbs: u32, + count_crtcs: u32, + count_connectors: u32, + count_encoders: u32, + min_width: u32, + max_width: u32, + min_height: u32, + max_height: u32, +}; + +pub const Resources = struct { + const Self = @This(); + const Range = struct { min: u32, max: u32 }; + + card: *Card, + + fb_ids: []u32, + crtc_ids: []u32, + connector_ids: []u32, + encoder_ids: []u32, + width: Range, + height: Range, + + pub fn raw_without_ids(card: *Card) !RawResources { + var result = std.mem.zeroes(RawResources); + try Drm.get_resources.request(card.file.handle, RawResources, &result); + return result; + } + + // NOTE: This function does not take in account + // that there might be some hot-plugging going + // on. This might have to change in the future. + pub fn init(card: *Card) !Self { + var raw = try Self.raw_without_ids(card); + const resources = .{ + .fb_ids = try card.allocator.alloc(u32, raw.count_fbs), + .crtc_ids = try card.allocator.alloc(u32, raw.count_crtcs), + .connector_ids = try card.allocator.alloc(u32, raw.count_connectors), + .encoder_ids = try card.allocator.alloc(u32, raw.count_encoders), + .width = .{ .min = raw.min_width, .max = raw.max_width }, + .height = .{ .min = raw.min_height, .max = raw.max_height }, + .card = card, + }; + + @memset(resources.fb_ids, 0); + @memset(resources.crtc_ids, 0); + @memset(resources.connector_ids, 0); + @memset(resources.encoder_ids, 0); + + raw.fb_ids = @ptrCast(resources.fb_ids); + raw.crtc_ids = @ptrCast(resources.crtc_ids); + raw.connector_ids = @ptrCast(resources.connector_ids); + raw.encoder_ids = @ptrCast(resources.encoder_ids); + try Drm.get_resources.request(card.file.handle, RawResources, &raw); + + return resources; + } + + pub fn deinit(self: *Self) void { + self.card.allocator.free(self.fb_ids); + self.card.allocator.free(self.crtc_ids); + self.card.allocator.free(self.connector_ids); + self.card.allocator.free(self.encoder_ids); + } +}; diff --git a/src/screen/main.zig b/src/screen/main.zig new file mode 100644 index 0000000..c23e7c5 --- /dev/null +++ b/src/screen/main.zig @@ -0,0 +1,20 @@ +const std = @import("std"); +const drm = @import("drm/card.zig"); + +pub fn main() !void { + var gpa = std.heap.GeneralPurposeAllocator(.{}){}; + const allocator = gpa.allocator(); + + var card = try drm.Card.open("card0", allocator); + defer card.close(); + + var resources = try card.resources(); + defer resources.deinit(); + + var connector = try card.connector(resources.connector_ids[0]); + defer connector.deinit(); + + for (connector.modes) |mode| { + std.debug.print("{d}x{d}@{d}\n", .{mode.hdisplay, mode.vdisplay, mode.frame_rate()}); + } +} diff --git a/src/shell/main.zig b/src/shell/main.zig new file mode 100644 index 0000000..337185b --- /dev/null +++ b/src/shell/main.zig @@ -0,0 +1,157 @@ +const std = @import("std"); +const estd = @import("estd"); +const Cursor = estd.cursor.Cursor; +const Parser = estd.parser.Parser; +const Context = estd.parser.Context; +const Literal = estd.parser.common.Literal; +const ParseFn = estd.parser.ParseFn; + +const Keyword = enum { + if_statement, + other +}; + +const String = struct { +}; + +const Token = union(enum) { + openparenthesis: void, + closeparenthesis: void, + openscope: void, + closescope: void, + backslash: void, + pipe: void, + semicolon: void, + logical_and: void, + logical_or: void, + assign: void, + keyword: Keyword, + string: String, +}; + +pub fn parse_string(ctx: *Context(u8)) !Token { + const snapshot = ctx.cursor.snapshot(); + errdefer ctx.cursor.rollback(snapshot); + + const first_char = (try ctx.cursor.consume(1))[0]; + + if (first_char == ' ') { + return error.NotAString; + } + + if (first_char == '"') { + while (ctx.cursor.consume(1) catch null) |c| { + if (c[0] == '\"') { + return Token.string; + } else if (c[0] == '{') { + while (ctx.cursor.consume(1) catch null) |inner| { + if (inner[0] == '}') { + break; + } + } else { + return error.UnterminatedString; + } + } + } else { + return error.UnterminatedString; + } + } else if (first_char == '\'') { + while (ctx.cursor.consume(1) catch null) |c| { + if (c[0] == '\'') { + return Token.string; + } + } else { + return error.UnterminatedString; + } + } + + while (ctx.cursor.consume(1) catch null) |c| { + if (c[0] == ' ') { + return Token.string; + } else if (c[0] == '{') { + while (ctx.cursor.consume(1) catch null) |inner| { + if (inner[0] == '}') { + break; + } + } else { + return error.UnterminatedString; + } + } + } else { + return error.UnterminatedString; + } +} + +const unterminated_keyword = Parser(u8, Keyword, keyword_literal("if", Keyword.if_statement)); + +fn keyword(ctx: *Context(u8)) !Token { + const snapshot = ctx.cursor.snapshot(); + errdefer ctx.cursor.rollback(snapshot); + + const token = try unterminated_keyword.parse(ctx); + const next_char = (try ctx.cursor.peek(1))[0]; + + switch (next_char) { + '(', ')', '{', '}', '\\', '&', '|', ';', '=', ' ' => return .{ .keyword = token }, + else => return error.UnterminatedKeyword, + } +} + +const literal = Literal(u8, Token); +const keyword_literal = Literal(u8, Keyword); +const skip_literal = Literal(u8, void); + +const Tokenizer = Parser(u8, Token, literal("(", Token.openparenthesis)) + .or_else(literal(")", Token.closeparenthesis)) + .or_else(literal("{", Token.openscope)) + .or_else(literal("}", Token.closescope)) + .or_else(literal("\\", Token.backslash)) + .or_else(literal("&&", Token.logical_and)) + .or_else(literal("||", Token.logical_or)) + .or_else(literal("|", Token.pipe)) + .or_else(literal(";", Token.semicolon)) + .or_else(literal("=", Token.assign)) + .or_else(keyword) + .or_else(parse_string) + .and_skip(Parser(u8, void, skip_literal(" ", {})).zero_or_more().parse) + .zero_or_more() + .then_skip(Parser(u8, void, skip_literal(" ", {})).zero_or_more().parse) + .end(); + +pub fn main() void { + std.debug.print("Hello, World!\n", .{}); +} + +test "tokenizer" { + const example = " | 'asdf' ( || \"hello\" ) or_something &&{ =}\\ ; if "; + + var ctx = Context(u8) { + .cursor = Cursor(u8).init(example), + .allocator = std.testing.allocator, + }; + + const tokens = try Tokenizer.parse(&ctx); + defer tokens.deinit(); + + + try std.testing.expectEqualSlices( + Token, + &[_]Token { + .pipe, + .string, + .openparenthesis, + .logical_or, + .string, + .closeparenthesis, + .string, + .logical_and, + .openscope, + .assign, + .closescope, + .backslash, + .semicolon, + .{ .keyword = Keyword.if_statement } + }, + tokens.items + ); +} -- cgit v1.2.3-70-g09d2