diff options
| -rw-r--r-- | docs/fs.md | 5 | ||||
| -rw-r--r-- | src/ninep/addr.zig | 13 | ||||
| -rw-r--r-- | src/regexp.zig | 10 | ||||
| -rw-r--r-- | src/sam_edit.zig | 1 |
4 files changed, 23 insertions, 6 deletions
@@ -459,8 +459,9 @@ The same code (`src/regexp.zig`) serves addresses, Edit, and normal mode's submatches, so Edit's `s` has no `\1`-`\9`. - A pattern holding `\n` runs over the whole text: there `^` may only come first and `$` only just before a `\n`, else it is refused. -- An alternation must anchor every branch or none (`^def|^ ` works, - `^def|x` is refused). +- An alternation must anchor every branch with `^` or none (`^def|^ ` + works, `^def|x` is refused: `an alternation anchors every branch with ^ + or none`). A `$` does not count: `foo$|bar` is fine. - A class may hold non-ASCII runes (`[éa-z]`, a range up to 256 runes); a wider range or a negated class with one (`[^é]`) is refused. - At most 512 operations (about 512 characters, counted after that diff --git a/src/ninep/addr.zig b/src/ninep/addr.zig index 33a3f646..9674a323 100644 --- a/src/ninep/addr.zig +++ b/src/ninep/addr.zig @@ -304,6 +304,7 @@ pub const Addr = struct { error.TooLong => regexp_.Regex.e_long, error.WideRange => regexp_.Regex.e_wide, error.NegatedRunes => regexp_.Regex.e_negated, + error.Mixed => regexp_.Regex.e_mixed, error.Bad => e_regexp, }; return null; @@ -383,6 +384,18 @@ test "an address is evaluated from the current one, as acme's are" { try testing.expectEqualStrings(e_order, th.wr(q, qaddr, "/a/,/b/").reply.ename); } +test "an alternation anchoring some branches and not others is refused with why, a $ not counting" { + const p = try th.withFile(testing.allocator, "def\nfoo\n"); + defer p.deinit(); + const node = Node.of(th.serialOf(p), .addr); + for ([_][]const u8{ "/^def|x/", "/foo$|^zèbre/" }) |pat| { + const r = th.wr(p, node, pat); + try testing.expectEqual(E.INVAL, r.errno()); + try testing.expectEqualStrings(regexp_.Regex.e_mixed, r.reply.ename); + } + try testing.expectEqual(Status.ok, th.wr(p, node, "/foo$|def/").reply.status); +} + test "a pattern with a newline and an anchor mvzr would misread is refused EINVAL, by addr and by Edit" { const p = try th.withFile(testing.allocator, "a\nfoo\n"); defer p.deinit(); 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); } diff --git a/src/sam_edit.zig b/src/sam_edit.zig index 0f317a83..94cf6fe0 100644 --- a/src/sam_edit.zig +++ b/src/sam_edit.zig @@ -463,6 +463,7 @@ const Exec = struct { error.TooLong => fail(ex.why, "{s}", .{regexp.Regex.e_long}), error.WideRange => fail(ex.why, "{s}", .{regexp.Regex.e_wide}), error.NegatedRunes => fail(ex.why, "{s}", .{regexp.Regex.e_negated}), + error.Mixed => fail(ex.why, "{s}", .{regexp.Regex.e_mixed}), error.Bad => fail(ex.why, "bad regexp in {c} command", .{c}), }; } |
