chore(bot.zig): refactor text parsing logic
This commit is contained in:
parent
1e4c90822a
commit
7690c8b46d
1 changed files with 77 additions and 30 deletions
107
src/bot.zig
107
src/bot.zig
|
|
@ -1,6 +1,55 @@
|
|||
const std = @import("std");
|
||||
const zircon = @import("zircon");
|
||||
|
||||
pub const Parser = struct {
|
||||
original: []const u8,
|
||||
rest: []const u8,
|
||||
end_idx: usize,
|
||||
|
||||
fn seek(self: *const Parser, skip: usize) Parser {
|
||||
return .{ .original = self.original, .rest = self.rest[skip..], .end_idx = self.end_idx + skip };
|
||||
}
|
||||
|
||||
fn init(s: []const u8) Parser {
|
||||
return .{
|
||||
.original = s,
|
||||
.end_idx = 0,
|
||||
.rest = s,
|
||||
};
|
||||
}
|
||||
|
||||
fn consume_char(self: *const Parser, c: u8) ?Parser {
|
||||
if (self.rest[0] != c) {
|
||||
return null;
|
||||
}
|
||||
return self.seek(1);
|
||||
}
|
||||
|
||||
fn consume_str(self: *const Parser, s: []const u8) ?Parser {
|
||||
const len = s.len;
|
||||
if (self.rest.len < len) {
|
||||
return null;
|
||||
}
|
||||
if (!std.mem.eql(u8, self.rest[0..len], s)) {
|
||||
return null;
|
||||
}
|
||||
return self.seek(len);
|
||||
}
|
||||
|
||||
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] };
|
||||
}
|
||||
|
||||
fn take_char(self: *const Parser) struct { Parser, u8 } {
|
||||
return .{ self.seek(1), self.rest[0] };
|
||||
}
|
||||
|
||||
fn parsed(self: *const Parser) []const u8 {
|
||||
return self.original[0..self.end_idx];
|
||||
}
|
||||
};
|
||||
|
||||
/// AdminCommand are commands useful for debugging zigeru, since they
|
||||
/// are more spammy than others they are separated and only sent to
|
||||
/// #eru-admin.
|
||||
|
|
@ -9,18 +58,19 @@ pub const AdminCommand = union(enum) {
|
|||
status: void,
|
||||
|
||||
pub fn parse(text: []const u8) ?AdminCommand {
|
||||
if (text.len < 2) return null;
|
||||
|
||||
if (std.mem.eql(u8, text, "!status")) {
|
||||
return .status;
|
||||
}
|
||||
|
||||
if (text.len > 8 and std.mem.eql(u8, text[0..8], "!backlog")) {
|
||||
const history = std.fmt.parseInt(u16, text[9..], 10) catch |err| {
|
||||
std.debug.print("failed to parse int ('{s}') with error: {}\n", .{ text[8..], err });
|
||||
return null;
|
||||
};
|
||||
return .{ .backlog = .{ .history = history } };
|
||||
const original = Parser.init(text);
|
||||
if (original.consume_char('!')) |command| {
|
||||
if (command.consume_str("status")) |_| {
|
||||
return .status;
|
||||
}
|
||||
if (command.consume_str("backlog")) |backlog| {
|
||||
const history = std.fmt.parseInt(u16, backlog.rest, 10) catch |err| {
|
||||
std.debug.print("failed to parse int ('{s}') with error: {}\n", .{ backlog.rest, err });
|
||||
return null;
|
||||
};
|
||||
return .{ .backlog = .{ .history = history } };
|
||||
}
|
||||
std.log.debug("unknown command: \"{s}\"", .{command.rest});
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
|
@ -34,32 +84,29 @@ pub const Command = union(enum) {
|
|||
help: void,
|
||||
|
||||
pub fn parse(nick: []const u8, text: []const u8) ?Command {
|
||||
if (std.mem.eql(u8, text, "!help")) {
|
||||
const original = Parser.init(text);
|
||||
if (original.consume_str("!help")) |_| {
|
||||
return .help;
|
||||
}
|
||||
|
||||
if (text[0] == 's') {
|
||||
if (text.len == 1) return null;
|
||||
const delim = switch (text[1]) {
|
||||
'/', '|', '#' => text[1],
|
||||
else => {
|
||||
return null;
|
||||
},
|
||||
};
|
||||
if (std.mem.count(u8, text, &.{delim}) != 3) {
|
||||
// invalid format, we expect three delimiters
|
||||
if (original.consume_char('s')) |substitute| {
|
||||
const delim_parser, const delim = substitute.take_char();
|
||||
if (std.ascii.isAlphanumeric(delim)) {
|
||||
std.log.debug("parsing substitute command: delimiter cannot be a whitespace: \"{s}\"", .{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});
|
||||
return null;
|
||||
}
|
||||
var parts = std.mem.splitScalar(u8, text, delim);
|
||||
_ = parts.next() orelse return null;
|
||||
const needle = parts.next().?;
|
||||
const replacement = parts.next().?;
|
||||
return .{
|
||||
.substitute = .{
|
||||
.author = nick,
|
||||
.needle = needle,
|
||||
.replacement = replacement,
|
||||
.all = if (parts.next()) |flags| std.mem.eql(u8, flags, "g") else false,
|
||||
.needle = typo,
|
||||
.replacement = correction,
|
||||
.all = false,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue