112 lines
4.5 KiB
Markdown
112 lines
4.5 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).
|
|
|
|
## The regex engine
|
|
|
|
katesam uses a **faithful port of sam's own regular-expression engine**
|
|
(`src/sam/samregex.{h,cpp}`, from plan9port `src/cmd/sam/regexp.c`), not PCRE.
|
|
This is a Thompson/Pike NFA, which gives the two properties sam relies on:
|
|
|
|
- **Leftmost-longest** (POSIX) matching, so results match real sam exactly,
|
|
including overlapping alternation.
|
|
- **Linear time**, with no backtracking — immune to catastrophic blowup. A
|
|
pattern like `(a*)*b` against thousands of `a` returns instantly.
|
|
|
|
There is **no PCRE deviation anymore**: the dialect, the match semantics, and
|
|
the anchors are sam's.
|
|
|
|
### Dialect
|
|
|
|
The sam dialect is deliberately small (sam `regexp(7)`):
|
|
|
|
```
|
|
. any character except newline
|
|
* + ? zero-or-more / one-or-more / zero-or-one
|
|
| alternation
|
|
( ) grouping and capture (\1..\9 in replacements)
|
|
[ ] [^ ] character class / negated class (ranges a-z)
|
|
^ $ beginning / end of a LINE
|
|
\c literal c (escapes a metacharacter); \n is newline
|
|
```
|
|
|
|
There is intentionally **no** `\d \w \s \b`, no lookaround, no non-greedy, and
|
|
no inline flags. sam never had them. Use character classes (`[0-9]`,
|
|
`[a-zA-Z_]`) and structural composition (`x y g v`) instead — that is the sam
|
|
way, and patterns stay portable to real sam.
|
|
|
|
### Semantics (all verified against the port)
|
|
|
|
| behaviour | katesam | note |
|
|
|---|---|---|
|
|
| overlapping alternation `a\|ab` on `ab` | matches `ab` | leftmost-**longest**; order of branches does not matter |
|
|
| `.` crosses newline | **no** | matches sam |
|
|
| `^` / `$` | per-line | `^` = start of a line, `$` = end of a line |
|
|
| `[^z]+` crosses newline | no | negated classes also exclude `\n`, as in sam |
|
|
| empty-match global `s/x*/-/g` on `axbx` | `-a-b-` | one advance per null match |
|
|
| `&` whole match, `\1..\9` groups in replacement | yes | up to 9 capture groups |
|
|
| pathological `(a*)*b` | linear time | NFA, no backtracking |
|
|
|
|
### Anchors are per-line
|
|
|
|
`^` matches the beginning of any line and `$` the end of any line — built into
|
|
the engine (sam `regexp.c`: `BOL` fires at offset 0 or after `\n`; `EOL` before
|
|
`\n`). So:
|
|
|
|
| program | input | result |
|
|
|---|---|---|
|
|
| `,s/^/> /g` | `a⏎b⏎c` | `> a⏎> b⏎> c` |
|
|
| `,s/$/;/g` | `a⏎b⏎c` | `a;⏎b;⏎c;` |
|
|
|
|
The structural idiom is equivalent and composes with guards/loops:
|
|
|
|
```
|
|
,x/.+/ s/^/> / prefix every (non-empty) line → > a⏎> b⏎> c
|
|
,x/.+/ a/;/ append ";" to every line → a;⏎b;⏎c;
|
|
```
|
|
|
|
## Composing edits — the sam way
|
|
|
|
sam's power is structural composition, not clever single regexes. Descend into
|
|
matches with `x`/`y`, guard with `g`/`v`, group with `{}`:
|
|
|
|
```
|
|
,x/[a-zA-Z_]+/ g/^[A-Z]/ c/CONST/ every identifier starting uppercase → CONST
|
|
,x/"[^"]*"/ s/foo/bar/g foo→bar only inside double-quoted strings
|
|
0/start/,/end/ x/[a-z]+/ s/.*/[&]/ bracket every lowercase word in a region
|
|
```
|
|
|
|
Multi-file `X`/`Y` applies an inner program across the **open buffers**:
|
|
|
|
```
|
|
X/\.cpp$/ ,s/old_api/new_api/g rewrite every open .cpp buffer
|
|
Y/_test\./ ,x/TODO/ d drop TODO lines in non-test buffers
|
|
```
|
|
|
|
(`X`/`Y` iterate the set of documents Kate currently has open — acme's
|
|
open-window set — not the project on disk. The regex matches each buffer's file
|
|
path, or its display name for an unsaved scratch buffer. Nothing is opened or
|
|
read from disk; edits land in the live buffers, each its own undo step. Review
|
|
and save deliberately.)
|
|
|
|
## Practical guidance
|
|
|
|
- Use character classes, not PCRE shorthands: `[0-9]` not `\d`, `[a-zA-Z_]` not
|
|
`\w`.
|
|
- Prefer structural composition (`x g v {}`) over one dense pattern.
|
|
- `^`/`$` are per-line; anchor directly (`,s/^…`) or loop (`,x/.+/ …`).
|
|
- Empty-match globals (`s/x*/…/g`) advance one position per null match — safe,
|
|
but as always with `*`, check the result.
|
|
|
|
## Scope
|
|
|
|
Supported: the full address language (`#n n 0 $ . '` `/re/ ?re?` compound
|
|
`+ - , ;`) and commands `a c i d s p = m t k x y g v {}` plus shell filters
|
|
`< > | !`. Out of scope (Kate manages files / its own undo): the multi-file menu
|
|
(`b B n D`), external file I/O (`e r w f`), `"re"` file-addressing, and sam's own
|
|
`u` (use Kate's Ctrl+Z).
|