diff options
| -rw-r--r-- | .agents/skills/pardes-9p/SKILL.md | 6 | ||||
| -rw-r--r-- | docs/fs.md | 17 | ||||
| -rw-r--r-- | src/ninep/addr.zig | 31 | ||||
| -rw-r--r-- | src/ninep/pane.zig | 10 |
4 files changed, 55 insertions, 9 deletions
diff --git a/.agents/skills/pardes-9p/SKILL.md b/.agents/skills/pardes-9p/SKILL.md index 6346e7b4..49db81b9 100644 --- a/.agents/skills/pardes-9p/SKILL.md +++ b/.agents/skills/pardes-9p/SKILL.md @@ -148,7 +148,11 @@ is why copying one onto another is all that acme's `addr=dot`, `dot=addr` and `/pattern/`, `2+1`), whose regexps are mvzr's searched as sam searches: `^`/`$` match at any line's start and end, `.` and `[^...]` never match a newline, the leftmost match wins (the first alternative there, not the -longest), `/re/` wraps unless `limit` is set, and a search that backtracks +longest). An expression is evaluated from the current address (the last one +written, or just past the last `data` write): `.` is that address, not the +selection, `/re/` searches on from its end and wraps unless `limit` is set, +`?re?` or `-/re/` searches back, `#100,#50` fails `addresses out of order`, +and a search that backtracks past a step budget (about 300 ms) fails with `regular expression search took too long`. A failed address says why (`no match for regexp`, `address out of range`) and leaves no address: @@ -298,10 +298,14 @@ lazy `*?` included), searched the way sam searches (editors/acme/regx.c): as lines, so `^` and `$` match at the start and end of any line, `.` and a negated class never match a newline, and `$` also matches at the end of a text with no final newline. A pattern that names a newline (`\n`) runs over -the whole text instead, its `.` kept to one line. `/re/` searches forward -from the end of the current range to `limit` if one is set, and otherwise -wraps to the start of the text; `?re?` finds the last match ending before -the range, wrapping to the text's last. The match is the leftmost, but of +the whole text instead, its `.` kept to one line. An expression is +evaluated from the current address, the range last written to `addr` (or +left by the last `data` write, just past it), as acme evaluates it from +`w->addr` (xfid.c:446): `.` is that address, not the selection (`dot` is +the selection's own file), and `#9/re/` searches from `#9`. `/re/` searches +forward from the end of the current range to `limit` if one is set, and +otherwise wraps to the start of the text; `?re?` and `-/re/` find the last +match ending before the range, wrapping to the text's last. The match is the leftmost, but of the alternatives at that place mvzr takes the first that matches where sam takes the longest (`/gam|gamma/` finds `gam`); in a search begun in the middle of a line, `^` inside an alternation can match there; and in a @@ -319,7 +323,10 @@ is the one place both call), so `^` there also means a line's start. An address that does not evaluate fails the write with why: `bad address syntax`, `no match for regexp`, `address out of range`, `bad regular -expression` or `regular expression search took too long`. A failed write to `addr` leaves no address at all, where acme +expression`, `regular expression search took too long`, or sam's +`addresses out of order` for a range that ends before it starts +(`#100,#50`), which acme lets through. A failed write to `addr` leaves no +address at all, where acme keeps the old one: until an address is written or `addr` is truncated, reading `addr`, and reading, writing or truncating `data` and `xdata`, fail with `no address: the last one written to addr failed`, so a script that diff --git a/src/ninep/addr.zig b/src/ninep/addr.zig index cc7ca632..6656c786 100644 --- a/src/ninep/addr.zig +++ b/src/ninep/addr.zig @@ -16,6 +16,7 @@ pub const e_range = "address out of range"; pub const e_regexp = "bad regular expression"; pub const e_slow = "regular expression search took too long"; pub const e_syntax = "bad address syntax"; +pub const e_order = "addresses out of order"; pub const Addr = struct { text: []const u8, @@ -93,7 +94,9 @@ pub const Addr = struct { size = .line; }, '/', '?' => { - const back = c == '?'; + // `-/re/` searches back as `?re?` does (acme's address() + // takes the direction a `-` set, addr.c). + const back = c == '?' or dir == '-'; r = a.regexp(r, a.pattern(c), back) orelse return null; dir = 0; size = .line; @@ -243,6 +246,32 @@ const Node = tree.Node; const E = tree.E; const Status = tree.Status; +test "an address is evaluated from the current one, as acme's are" { + const p = try th.withFile(testing.allocator, "a foo b foo c foo\n"); + defer p.deinit(); + const addr = Node.of(th.serialOf(p), .addr); + const Step = struct { expr: []const u8, q0: u32, q1: u32 }; + _ = th.wr(p, addr, "#0"); + for ([_]Step{ + // each search starts where the last address ended + .{ .expr = "/foo/", .q0 = 2, .q1 = 5 }, + .{ .expr = "/foo/", .q0 = 8, .q1 = 11 }, + // `.` is that address, and `-` before a pattern searches back + .{ .expr = ".", .q0 = 8, .q1 = 11 }, + .{ .expr = "-/foo/", .q0 = 2, .q1 = 5 }, + .{ .expr = "./foo/", .q0 = 8, .q1 = 11 }, + .{ .expr = "+/foo/", .q0 = 14, .q1 = 17 }, + // a number then a pattern: the search starts at the number + .{ .expr = "#9/foo/", .q0 = 14, .q1 = 17 }, + }) |c| { + try testing.expectEqual(Status.ok, th.wr(p, addr, c.expr).reply.status); + try testing.expectEqual(c.q0, p.panes[0].?.fs.addr.q0); + try testing.expectEqual(c.q1, p.panes[0].?.fs.addr.q1); + } + // A range whose end comes before its start is sam's error. + try testing.expectEqualStrings(e_order, th.wr(p, addr, "#10,#5").reply.ename); +} + test "regular expressions search lines as sam's do, and a search wraps" { const p = try th.withFile(testing.allocator, "alpha beta\nbeta gamma\ngamma\n"); defer p.deinit(); diff --git a/src/ninep/pane.zig b/src/ninep/pane.zig index 0a511118..6b6d5728 100644 --- a/src/ninep/pane.zig +++ b/src/ninep/pane.zig @@ -453,16 +453,22 @@ pub const e_addr_failed = "no address: the last one written to addr failed"; /// A range file takes an address expression, or that pair of offsets. One /// that does not evaluate says why: `bad address syntax`, `no match for -/// regexp`, `address out of range`, `bad regular expression`. +/// regexp`, `address out of range`, `bad regular expression`, `addresses +/// out of order`. fn writeRange(req: Req, pane: *Pane, file: PaneFile) Reply { const pf = &pane.fs; const text = bodyOf(pane); clampAddr(pf, text.len); const expr = std.mem.trimEnd(u8, req.data, "\n"); var a: addressing.Addr = .{ .text = text, .lim = pf.limit, .expr = expr }; - const r = if (pairOf(expr)) |pair| + var r = if (pairOf(expr)) |pair| State.Range{ .q0 = @min(pair.q0, clip(text.len)), .q1 = @min(pair.q1, clip(text.len)) } else if (a.address(pf.addr)) |found| (if (a.i < expr.len) null else found) else null; + // sam's check, which acme leaves out: `#100,#50` names no range. + if (r) |found| if (found.q0 > found.q1) { + a.err = addressing.e_order; + r = null; + }; const range = r orelse { if (file == .addr) pf.addr_failed = true; return tree.failText(req.tag, E.INVAL, a.err); |
