diff --git a/src/commands.zig b/src/commands.zig index 6a57a73..cff4b98 100644 --- a/src/commands.zig +++ b/src/commands.zig @@ -1,6 +1,6 @@ const std = @import("std"); -const parser = @import("root.zig").parser; +const Parser = @import("root.zig").parser.Parser; /// UserCommand represents the commands that ordinary IRC users can use. pub const UserCommand = union(enum) { @@ -10,23 +10,34 @@ pub const UserCommand = union(enum) { help: void, pub fn parse(nick: []const u8, text: []const u8) ?UserCommand { - const original = parser.init(text); + const original = Parser.init(text); if (original.consume_str("!help")) |_| { return .help; } if (original.consume_char('s')) |substitute| { - const delim_parser, const delim = substitute.take_char(); + const log_prefix = "parsing substitute command"; + var parser = substitute; + parser, const delim = parser.take_char(); if (std.ascii.isAlphanumeric(delim)) { - std.log.debug("parsing substitute command: delimiter cannot be a whitespace: \"{s}\"", .{text}); + std.log.debug("{s}: delimiter cannot be a whitespace: \"{s}\"", .{ log_prefix, text }); return null; } - const typo_parser, const typo = delim_parser.take_until_char(delim); - const correction_parser, const correction = typo_parser.consume_char(delim).?.take_until_char(delim); - if (correction_parser.consume_char(delim) == null) { - std.log.debug("parsing substitute command: missing an ending '/' in \"{s}\"", .{text}); + var result = parser.take_until_char(delim); + if (result == null) { + std.log.debug( + "{s}: cannot find typo, expecting a message on the form 's{}TYPO{}CORRECTION{}', but got {s}", + .{ log_prefix, delim, delim, delim, text }, + ); return null; } + parser, const typo = result.?; + result = parser.consume_char(delim).?.take_until_char(delim); + if (result == null) { + std.log.debug("{s}: missing an ending '/' in \"{s}\"", .{ log_prefix, text }); + return null; + } + parser, const correction = result.?; return .{ .substitute = .{ .author = nick, @@ -50,7 +61,7 @@ pub const AdminCommand = union(enum) { err: struct { message: []const u8 }, pub fn parse(text: []const u8) ?AdminCommand { - const original = parser.init(text); + const original = Parser.init(text); if (original.consume_char('!')) |command| { if (command.consume_str("status")) |_| { return .status; @@ -97,6 +108,14 @@ test "can parse s/hello/world/ successful" { ); } +test "can parse s/hello/world and report failure" { + try std.testing.expectEqualDeep(null, UserCommand.parse("jassob", "s/hello/world")); +} + +test "can parse s/hello|world| and report failure" { + try std.testing.expectEqualDeep(null, UserCommand.parse("jassob", "s/hello/world")); +} + test "correctly ignores non-messages when trying to parse" { try std.testing.expectEqualDeep(null, UserCommand.parse("jassob", "Hello, world")); } diff --git a/src/parser.zig b/src/parser.zig index 13acaea..080c708 100644 --- a/src/parser.zig +++ b/src/parser.zig @@ -61,9 +61,11 @@ pub const Parser = struct { // // Returns a new parser window that starts after idx and the // extracted byte slice. - pub fn take_until_char(self: *const Parser, c: u8) struct { Parser, []const u8 } { - const idx = std.mem.indexOfScalar(u8, self.rest, c) orelse unreachable; - return .{ self.seek(idx), self.rest[0..idx] }; + pub fn take_until_char(self: *const Parser, c: u8) ?struct { Parser, []const u8 } { + if (std.mem.indexOfScalar(u8, self.rest, c)) |idx| { + return .{ self.seek(idx), self.rest[0..idx] }; + } + return null; } // Take the current character and advance the parser one step.