132 lines
5.7 KiB
Markdown
132 lines
5.7 KiB
Markdown
# katesam — sam structural-regexp editing in Kate
|
|
|
|
The **Sam** tool view (left sidebar) runs the plan9
|
|
[sam](https://doc.cat-v.org/plan_9/4th_edition/papers/sam/) command language
|
|
against the active document, or project-wide via `X`/`Y`. Type a program, press
|
|
**Ctrl+Return** or click **Run**. Each Run is one undo step (Ctrl+Z reverts the
|
|
whole Run; multi-file `X`/`Y` undoes per file).
|
|
|
|
This document covers the **regex deviation** — katesam uses PCRE
|
|
(`QRegularExpression`), not plan9 `regexp(7)` — and what that means in practice.
|
|
Every example below is verified against the engine.
|
|
|
|
## TL;DR
|
|
|
|
- Everyday patterns behave **identically** to sam.
|
|
- One real semantic difference: **PCRE is leftmost-greedy, sam is
|
|
leftmost-longest.** It only bites on *overlapping alternation* (`a|ab`).
|
|
- `^` and `$` are **per-line** (match at every line boundary), the same as sam's
|
|
`regexp(7)`. `.` does not cross newlines, also the same as sam.
|
|
- PCRE *also* offers things sam lacks (`\d \w \b`, lookahead, non-greedy, inline
|
|
flags). They work, but prefer sam-style structural composition (`x g v`) — see
|
|
"Extras" below.
|
|
|
|
---
|
|
|
|
## The one semantic divergence: greedy vs longest
|
|
|
|
sam's `regexp(7)` matches the **leftmost-longest** substring. PCRE matches
|
|
**leftmost**, then resolves alternation **left-to-right, first wins** (greedy
|
|
within a branch, but alternation order decides between branches).
|
|
|
|
| program | input | sam (leftmost-longest) | **katesam (PCRE)** |
|
|
|---|---|---|---|
|
|
| `,s/a\|ab/X/` | `ab` | `X` (matches `ab`) | **`Xb`** (matches `a`) |
|
|
| `,s/ab\|a/X/` | `ab` | `X` | `X` |
|
|
|
|
**Implication:** when alternatives overlap, **order them longest-first**
|
|
(`ab|a`, not `a|ab`). This is the only case where a working sam command can
|
|
silently produce a different edit in katesam. Non-overlapping alternation
|
|
(`cat|dog`) and all non-alternation patterns are unaffected.
|
|
|
|
Quantifier greediness is the same in both (`a.*c` on `axxcxxc` → whole string in
|
|
both). katesam additionally offers non-greedy `.*?` (a PCRE extra).
|
|
|
|
---
|
|
|
|
## Anchors `^` / `$` are per-line — same as sam
|
|
|
|
This is **not** a deviation. plan9 `regexp(7)` defines `^` as "the beginning of
|
|
a line" and `$` as "the end of a line" (sam's `regexp.c`: `BOL` fires at offset
|
|
0 or after a `\n`; `EOL` fires before a `\n`). katesam compiles every pattern
|
|
with `QRegularExpression::MultilineOption`, so `^`/`$` match at every line
|
|
boundary, exactly as sam does:
|
|
|
|
| program | input | result |
|
|
|---|---|---|
|
|
| `,s/^/> /g` | `a⏎b⏎c` | `> a⏎> b⏎> c` (every line) |
|
|
| `,s/$/;/g` | `a⏎b⏎c` | `a;⏎b;⏎c;` (every line) |
|
|
|
|
The sam-idiomatic structural form works too, and is preferable because it
|
|
composes with guards and other loops:
|
|
|
|
```
|
|
,x/.+/ s/^/> / prefix every (non-empty) line → > a⏎> b⏎> c
|
|
,x/.+/ a/;/ append ";" to every line → a;⏎b;⏎c;
|
|
```
|
|
|
|
`.` does **not** cross newlines (sam: a "character" is "any character but
|
|
newline"), which is also PCRE's default. Line-structured descent like
|
|
`,x/.*\n/ …` therefore behaves as expected.
|
|
|
|
---
|
|
|
|
## What matches identically (no surprises)
|
|
|
|
| behaviour | katesam (PCRE) | sam | same? |
|
|
|---|---|---|---|
|
|
| `.` crosses newline | no | no | ✅ |
|
|
| `[^z]+` crosses newline | yes | yes | ✅ |
|
|
| empty-match advance `s/x*/-/g` on `axbx` → `-a-b-` | ✅ | ✅ | ✅ |
|
|
| greedy quantifiers `* + ?` | ✅ | ✅ | ✅ |
|
|
| character classes `[a-z]`, groups `( )`, `|` | ✅ | ✅ | ✅ |
|
|
| `&` whole-match, `\1..\9` groups in replacement | ✅ | ✅ | ✅ |
|
|
|
|
> `.` not crossing newlines matches sam, so line-structured descent
|
|
> (`,x/.*\n/ ...`) behaves as expected. Use `(?s)` if you *want* `.` to span
|
|
> newlines.
|
|
|
|
---
|
|
|
|
## Extras — available, but not the sam way
|
|
|
|
PCRE accepts syntax plan9 `regexp(7)` never had. It all works, but treat it as
|
|
an **escape hatch, not a headline**: sam's power comes from *composing* simple
|
|
patterns with `x`/`y`/`g`/`v`/`{}`, not from clever single regexes. Prefer
|
|
structural composition; reach for these only when it is genuinely simpler.
|
|
|
|
| extra | example | note |
|
|
|---|---|---|
|
|
| shorthand classes `\d \w \s` | `,s/\d+/N/g` | less typing than `[0-9]`; genuinely handy |
|
|
| word boundary `\b` | `,s/\bfoo\b/X/g` | handy; sam would use `x/foo/` with context |
|
|
| ignore-case `(?i)` | `,s/(?i)todo/DONE/g` | fills a real gap — sam has no case-insensitive match |
|
|
| non-greedy `*?` | `,s/a.*?c/X/` | the structural `x/…/` loop is the sam alternative |
|
|
| lookahead/behind | `,s/foo(?=bar)/X/` | un-sam; express context with `x`/`g`/`v` instead |
|
|
| backreference in pattern | `,s/(\w)\1/D/g` | rarely the right tool |
|
|
|
|
A pattern using these is **not portable back to sam**. If portability or
|
|
staying in the structural idiom matters, avoid them.
|
|
|
|
---
|
|
|
|
## Practical guidance
|
|
|
|
- **Order overlapping alternatives longest-first** (`\.tar\.gz|\.gz`, not the
|
|
reverse). The only silent divergence from sam.
|
|
- **`^`/`$` are per-line** — just like sam. For whole-line edits either anchor
|
|
directly (`,s/^/> /g`) or, more sam-idiomatically, loop (`,x/.+/ …`).
|
|
- **Prefer structural composition** (`x g v {}`) over PCRE extras. The extras
|
|
work but pull you out of the sam idiom and are not sam-portable.
|
|
- **Empty-match globals** (`s/x*/…/g`) advance one position per null match, as in
|
|
sam — safe, but as always with `*`, double-check the result.
|
|
|
|
## Why PCRE and not regexp(7)
|
|
|
|
`QRegularExpression` ships with Qt (already a dependency) and gives a richer,
|
|
familiar syntax for free. plan9 `regexp(7)` is a Rune-based NFA woven into sam's
|
|
own buffer types; using it would mean porting that engine. The tradeoff accepted
|
|
here: everyday fidelity plus extra power, at the cost of exact leftmost-longest
|
|
semantics on overlapping alternation. If that matters for your workflow, the
|
|
engine's regex calls are isolated in `SamEngine` and could be swapped for a
|
|
ported `regexp.c` later.
|