summaryrefslogtreecommitdiff
path: root/src/estd
diff options
context:
space:
mode:
Diffstat (limited to 'src/estd')
-rw-r--r--src/estd/cursor.zig69
-rw-r--r--src/estd/parser/common.zig28
-rw-r--r--src/estd/parser/context.zig9
-rw-r--r--src/estd/parser/root.zig284
-rw-r--r--src/estd/root.zig8
5 files changed, 398 insertions, 0 deletions
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());
+}