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()); }