There was a bug in the parsing logic that caused a substitution command like `s/typo/correction` to crash the bot. Correct command is of course `s/typo/correction/`, but now it at least shouldn't crash.
89 lines
2.6 KiB
Zig
89 lines
2.6 KiB
Zig
const std = @import("std");
|
|
|
|
pub fn init(s: []const u8) Parser {
|
|
return .init(s);
|
|
}
|
|
|
|
pub const Parser = struct {
|
|
original: []const u8,
|
|
rest: []const u8,
|
|
end_idx: usize,
|
|
|
|
// Initializes a Parser for s.
|
|
pub fn init(s: []const u8) Parser {
|
|
return .{
|
|
.original = s,
|
|
.end_idx = 0,
|
|
.rest = s,
|
|
};
|
|
}
|
|
|
|
// Seek the parser window of the text forward skip bytes and return a new Parser.
|
|
pub fn seek(self: *const Parser, skip: usize) Parser {
|
|
return .{ .original = self.original, .rest = self.rest[skip..], .end_idx = self.end_idx + skip };
|
|
}
|
|
|
|
// Attempts to consume at least one whitespace character from the input text.
|
|
pub fn consume_space(self: *const Parser) ?Parser {
|
|
if (!std.ascii.isWhitespace(self.rest[0])) {
|
|
return null;
|
|
}
|
|
for (self.rest[1..], 1..) |c, idx| {
|
|
if (!std.ascii.isWhitespace(c)) {
|
|
return self.seek(idx);
|
|
}
|
|
}
|
|
return self.seek(self.rest.len);
|
|
}
|
|
|
|
// Attempts to consume a character c.
|
|
pub fn consume_char(self: *const Parser, c: u8) ?Parser {
|
|
if (self.rest[0] != c) {
|
|
return null;
|
|
}
|
|
return self.seek(1);
|
|
}
|
|
|
|
// Attempts to consume a string s.
|
|
pub 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);
|
|
}
|
|
|
|
// Finds the next occurrence of c (idx) in the current parser
|
|
// window and extracts it.
|
|
//
|
|
// 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 } {
|
|
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.
|
|
pub fn take_char(self: *const Parser) struct { Parser, u8 } {
|
|
return .{ self.seek(1), self.rest[0] };
|
|
}
|
|
|
|
// Return the currently accepted text.
|
|
pub fn parsed(self: *const Parser) []const u8 {
|
|
return self.original[0..self.end_idx];
|
|
}
|
|
};
|
|
|
|
test "parser can skip whitespace" {
|
|
var parser = init("Hello, World");
|
|
parser = parser.consume_str("Hello,").?;
|
|
parser = parser.consume_space().?;
|
|
parser = parser.consume_str("World").?;
|
|
|
|
try std.testing.expectEqual("Hello, World", parser.parsed());
|
|
}
|