summaryrefslogtreecommitdiff
path: root/src/regexp.zig
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-09-30 11:50:52 -0300
committerGabriel Schneider <[email protected]>2026-10-01 00:12:17 -0300
commit7c329baddb0d45ce7937cf29941dde861f9ea27f (patch)
tree76919df40dbaccf6cd148dcbf2f46efc508df76c /src/regexp.zig
parentfc8e1abf361ab2151109e68e5c88a0ed45650b38 (diff)
downloadpardes-7c329baddb0d45ce7937cf29941dde861f9ea27f.tar.gz
pardes-7c329baddb0d45ce7937cf29941dde861f9ea27f.zip
A pattern anchoring some alternatives with ^ and not others is refused with why, not a bare bad regular expression
`^def|x` and `foo$|^zèbre` were refused with a bare "bad regular expression", the same words as a syntax error, so a script could not tell what to change. They now say "an alternation anchors every branch with ^ or none (^a|^b, not ^a|b)", through addr and Edit alike. fs.md states the rule and that a `$` does not count toward it (`foo$|bar` is fine). Co-Authored-By: Claude Opus 5.5 <[email protected]>
Diffstat (limited to 'src/regexp.zig')
-rw-r--r--src/regexp.zig10
1 files changed, 6 insertions, 4 deletions
diff --git a/src/regexp.zig b/src/regexp.zig
index ddb5eb28..575e6861 100644
--- a/src/regexp.zig
+++ b/src/regexp.zig
@@ -56,6 +56,7 @@ pub const Regex = struct {
pub const e_anchor = "bad regular expression: in a pattern with \\n, ^ can only come first and $ only just before a \\n";
pub const e_wide = std.fmt.comptimePrint("bad regular expression: a range of runes wider than {d} in [...] is not supported", .{max_range});
pub const e_negated = "bad regular expression: a [^...] with non-ASCII runes is not supported (mvzr's classes hold bytes)";
+ pub const e_mixed = "bad regular expression: an alternation anchors every branch with ^ or none (^a|^b, not ^a|b)";
/// The most runes a non-ASCII range in a class is spelled out as.
pub const max_range = 256;
@@ -79,7 +80,7 @@ pub const Regex = struct {
return @intCast(n);
}
- pub const Error = error{ Bad, Anchor, TooLong, WideRange, NegatedRunes };
+ pub const Error = error{ Bad, Anchor, TooLong, WideRange, NegatedRunes, Mixed };
pub fn compile(pat: []const u8) Error!Regex {
if (pat.len == 0) return error.Bad;
@@ -273,7 +274,7 @@ pub const Regex = struct {
/// `^a|^b` as `^(a|b)` in `out`, when the pattern is an alternation at
/// its top level and every branch starts with `^`; null when it is not
/// one, or no branch does.
- fn anchoredAlternation(pat: []const u8, out: *[5 * max_ops + 2]u8) error{Bad}!?[]const u8 {
+ fn anchoredAlternation(pat: []const u8, out: *[5 * max_ops + 2]u8) error{ Bad, Mixed }!?[]const u8 {
var bars: [16]usize = undefined;
var n: usize = 0;
var depth: usize = 0;
@@ -306,7 +307,8 @@ pub const Regex = struct {
from = to + 1;
}
if (anchored == 0) return null;
- if (anchored != n + 1) return error.Bad;
+ // Only `^` counts: `$` is mvzr's own at a branch's end (`a$|b`).
+ if (anchored != n + 1) return error.Mixed;
var w = std.Io.Writer.fixed(out);
w.writeAll("^(") catch return error.Bad;
from = 0;
@@ -481,7 +483,7 @@ test "a ^ after | anchors that branch: ^def|^ finds a line that starts either w
var both = try Regex.compile("^def|^ ");
try std.testing.expectEqual(@as(usize, 6), (try both.find(text, 0, text.len, text.len)).?.start);
try std.testing.expectEqual(@as(usize, 9), (try both.find(text, 7, text.len, text.len)).?.start);
- try std.testing.expectError(error.Bad, Regex.compile("^def|x"));
+ try std.testing.expectError(error.Mixed, Regex.compile("^def|x"));
var plain = try Regex.compile("a|b");
try std.testing.expectEqual(@as(usize, 7), (try plain.find(text, 0, text.len, text.len)).?.start);
}