diff options
Diffstat (limited to 'src/estd/parser')
| -rw-r--r-- | src/estd/parser/common.zig | 28 | ||||
| -rw-r--r-- | src/estd/parser/context.zig | 9 | ||||
| -rw-r--r-- | src/estd/parser/root.zig | 284 |
3 files changed, 321 insertions, 0 deletions
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()); +} |