summaryrefslogtreecommitdiffstats
path: root/CONTEXT.md
diff options
context:
space:
mode:
authorsalmonik <salmonik@tuta.io>2026-08-01 13:14:07 +0200
committersalmonik <salmonik@tuta.io>2026-08-01 13:14:07 +0200
commit6eb14b64209b9d24554c587f610d7e300ef809ea (patch)
treec308507a2140680eb82fdb8b360aa346e7c5c9fe /CONTEXT.md
parentBump workspace to edition 2024 (diff)
downloadrs-dos-6eb14b64209b9d24554c587f610d7e300ef809ea.tar.gz
rs-dos-6eb14b64209b9d24554c587f610d7e300ef809ea.zip
shell: keyword redirection + post-filter flags (ADR 0002)
Implement the shell experience redesign: one canonical grammar for every command — 'cmd args [USE src] [TIL marker] [filters...] [output kw dst]'. - Keyword redirection (uppercase-only, aliases valid, fully reserved): WRITING/WRITE overwrite, TO append, USE/USING input source, TIL/UNTIL heredoc/terminator (exact full-line marker). > >> < << are removed; '>' is plain text. - Post-filter stages applied left to right over a line stream, each repeatable: -h N (head kills the upstream), -t N (tail holds a rolling window, flushes at EOF/^C), -g PAT (smart-case literal substring), -p (screenful pager, terminal only). A file sink replaces the screen; -p is a no-op with one. - C-style quoting: "..." groups and literalizes, \x escapes one char; quoted/escaped text is never an operator, never split. - Input consumers: cat/hexdump, path-less only (stdin until ^D, USE f, TIL m heredoc, USE f TIL m truncation). Other commands reject input keywords; paths + input keyword is a syntax error. - ^D is EOF everywhere and never powers off; poweroff/exit are the only shutdown verbs. - help rewritten; test-disk.sh and sendkeys.py use the keyword syntax. Verified in QEMU: 26 grammar checks on fresh and reused disks, disk round trip, pager pacing, -h stopping /dev/mem at N lines, ^C flushing a -t window. Two bugs found during verification and fixed: 1. WRITING over an existing file wrote after stale NUL padding — fs::size() is stale after set_len(0); the sink writes at offset 0 after truncation. 2. USE f TIL m leaked the post-marker remainder — the final partial-line emit ran after the marker break; guarded with a stopped flag.
Diffstat (limited to 'CONTEXT.md')
-rw-r--r--CONTEXT.md208
1 files changed, 204 insertions, 4 deletions
diff --git a/CONTEXT.md b/CONTEXT.md
index 1c73656..7514a6f 100644
--- a/CONTEXT.md
+++ b/CONTEXT.md
@@ -168,8 +168,8 @@ Workspace of four crates, all built for the `x86_64-unknown-uefi` target:
168 (`ls`/`cat`/`hexdump`/`mkdir`/`touch`/`rm`, `echo >`/`>>`; devices 168 (`ls`/`cat`/`hexdump`/`mkdir`/`touch`/`rm`, `echo >`/`>>`; devices
169 regenerate on read, `/dev/mem` is raw physical RAM) 169 regenerate on read, `/dev/mem` is raw physical RAM)
170✅ **^C / ^D** — Ctrl+C cancels the line or aborts a running stream 170✅ **^C / ^D** — Ctrl+C cancels the line or aborts a running stream
171 (`cat`/`hexdump` check between chunks), Ctrl+D on an empty line 171 (`cat`/`hexdump` check between chunks); Ctrl+D is EOF (no poweroff —
172 powers off (EOF) 172 removed this session; see session notes below)
173✅ **tty + vt (terminal) — the 1:1 UNIX console split**: `tty` owns the 173✅ **tty + vt (terminal) — the 1:1 UNIX console split**: `tty` owns the
174 line discipline (canonical echo/buffer/^C/^D for the shell; raw mode 174 line discipline (canonical echo/buffer/^C/^D for the shell; raw mode
175 translates keys to the classic terminal byte protocol), `vt` owns the 175 translates keys to the classic terminal byte protocol), `vt` owns the
@@ -205,6 +205,32 @@ Workspace of four crates, all built for the `x86_64-unknown-uefi` target:
205 **Files survive reboot** — verified by poweroff → `mdir`/`mtype` on 205 **Files survive reboot** — verified by poweroff → `mdir`/`mtype` on
206 the image and by a fresh boot seeing the prior session's files. 206 the image and by a fresh boot seeing the prior session's files.
207 207
208✅ **Shell grammar (ADR 0002)** — one canonical grammar for every
209 command: `cmd args [USE src] [TIL marker] [filters…] [output kw dst]`.
210 Keyword redirection (`WRITING`/`WRITE` overwrite, `TO` append,
211 `USE`/`USING` input source, `TIL`/`UNTIL` heredoc/terminator;
212 uppercase-only, aliases valid, fully reserved), C-style quoting
213 (`"…"` groups, `\x` escapes — quoted text is never an operator),
214 post-filter stages applied left to right (`-h N` head kills the
215 upstream, `-t N` tail holds back and flushes at EOF/^C, `-g PAT`
216 smart-case literal substring, `-p` screenful pager; all repeatable),
217 `>`/`<`/`<<`/`>>` removed (`>` is plain text), file sinks replace the
218 screen, `cat`/`hexdump` consume input when path-less (stdin heredoc,
219 `cat USE a TO b` copy, `USE f TIL m` truncation). ^D is EOF
220 everywhere and never powers off. `help` rewritten; `test-disk.sh`
221 and `sendkeys.py` updated to the keyword syntax.
222
223 Verified in QEMU (26 grammar checks on a fresh *and* a reused disk,
224 disk round trip, pager pacing): head kills at N lines, ^C flushes a
225 `-t` window, smart-case grep, chained/repeated filters, `WRITING`+
226 `TO`, `USE..TO` copy, `USE..TIL`, heredoc → file, quoting, `-p`.
227 Two bugs found and fixed during verification: (1) `WRITING` over an
228 existing file wrote after stale NUL padding — `fs::size()` returns
229 the node-cached size, which is stale after `set_len(0)`; the sink
230 now writes at literal offset 0 after truncation; (2) `USE f TIL m`
231 leaked everything after the marker — the final partial-line emit
232 ran even after the marker break; fixed with a `stopped` flag.
233
208 QEMU setup: `-drive id=disk,if=none,format=raw,file=disk.img` + 234 QEMU setup: `-drive id=disk,if=none,format=raw,file=disk.img` +
209 `-device ide-hd,drive=disk,bus=ide.0` puts the data disk on AHCI port 235 `-device ide-hd,drive=disk,bus=ide.0` puts the data disk on AHCI port
210 0; the ESP gets `bus=ide.1`. (The implicit `-drive file=fat:rw:esp` 236 0; the ESP gets `bus=ide.1`. (The implicit `-drive file=fat:rw:esp`
@@ -223,7 +249,7 @@ Workspace of four crates, all built for the `x86_64-unknown-uefi` target:
223- `make test-disk` / `./scripts/test-disk.sh` — full automated round 249- `make test-disk` / `./scripts/test-disk.sh` — full automated round
224 trip: build → deploy → ensure image → boot QEMU headless → drive 250 trip: build → deploy → ensure image → boot QEMU headless → drive
225 the shell via `sendkey` through the monitor (`ls /disk`, `mkdir`, 251 the shell via `sendkey` through the monitor (`ls /disk`, `mkdir`,
226 `echo >`, `cat`, `poweroff`) → fsck-clean check → `mtype` verifies 252 `echo WRITING`, `cat`, `poweroff`) → fsck-clean check → `mtype` verifies
227 the written file survived poweroff 253 the written file survived poweroff
228- `./scripts/sendkeys.py` — types lines into the guest via the QEMU 254- `./scripts/sendkeys.py` — types lines into the guest via the QEMU
229 monitor (`--sock`, US layout, named keys for punctuation; bare 255 monitor (`--sock`, US layout, named keys for punctuation; bare
@@ -269,7 +295,7 @@ Workspace of four crates, all built for the `x86_64-unknown-uefi` target:
269 128 chars ≈ 16 KB, sequence numbers, wrap/drop accounting. 295 128 chars ≈ 16 KB, sequence numbers, wrap/drop accounting.
270- **`klog` command** = thin alias for `cat /dev/klog` plus filters 296- **`klog` command** = thin alias for `cat /dev/klog` plus filters
271 `-h` (head) and `-t` (tail); no log levels, no timestamps, nothing 297 `-h` (head) and `-t` (tail); no log levels, no timestamps, nothing
272 fancier. `/dev/klog` is a char device so `klog > /disk/…/boot.log` 298 fancier. `/dev/klog` is a char device so `klog WRITING /disk/…/boot.log`
273 persists a boot log via the existing redirection. 299 persists a boot log via the existing redirection.
274- **Disk recognition scope — keep simple for now**: no pre-EBS UEFI 300- **Disk recognition scope — keep simple for now**: no pre-EBS UEFI
275 block-device census, no GPT/MBR partition parsing, no NVMe driver. 301 block-device census, no GPT/MBR partition parsing, no NVMe driver.
@@ -280,3 +306,177 @@ Workspace of four crates, all built for the `x86_64-unknown-uefi` target:
280## Target audience 306## Target audience
281 307
282Learning exercise and toy. Not a Linux replacement. Not POSIX-compatible. 308Learning exercise and toy. Not a Linux replacement. Not POSIX-compatible.
309
310---
311
312## Session notes — shell experience redesign (implemented)
313
314Working from `shell_experience`. Plan as stated:
315
316- Commands accept **post-filters** as trailing flags: `-h N` (head),
317 `-t N` (tail), `-g PAT` (grep). Flag placement (at the end of the
318 command) is respected.
319- Redirection uses **keywords**, not `> << < >>`:
320 `WRITING`/`WRITE` → overwrite, `UNTIL`/`TIL` → heredoc,
321 `USE`/`USING` → input, `TO` → append.
322- **No pipes.** `head`/`tail`/`grep` needs are covered by the flags;
323 a pager flag is a maybe.
324
325Resolved this session:
326
327- **Post-filter order = written order, left to right.** Each flag is a
328 filter stage applied to the command's output as a line stream:
329 `cat file -h 50 -t 10 -g pat` = "lines containing `pat` among the last
330 10 lines of the first 50 lines". (The `shell_experience` prose for
331 that example was loose; the left-to-right rule is canonical.)
332- **`-t N` holds back.** The tail stage buffers a rolling window of the
333 last N lines and emits nothing until its upstream ends — EOF, or the
334 user's ^C, which flushes the window to the screen. `cat /dev/mem -g
335 100 -t 10` therefore shows nothing while it runs and prints the last 10
336 matches when ^C ends it.
337- **`-h N` truncates.** Once N lines have passed through, the head stage
338 kills the upstream command — `cat /dev/mem -h 5` peeks 5 lines and
339 stops instead of flooding.
340- **`-g PAT` = smart-case literal substring.** Match is case-insensitive
341 unless PAT contains an uppercase letter, in which case it is
342 case-sensitive (editor-style smart case). No regex, no wildcards.
343- **Post-filters repeat.** `-h`, `-t` and `-g` may each appear more than
344 once; every instance is a separate stage in the left-to-right chain
345 (so `-h 10 -h 5` = first 5, `-t 10 -t 5` = last 5, `-g foo -g bar` =
346 lines containing both). After `-g`, the next token is the pattern
347 verbatim (`-g -h` = pattern "-h").
348- **Redirection keywords pair strictly.** At most one input keyword and
349 one output keyword per command line (both may appear together); the
350 input keyword must precede the output keyword. `USING f TO g` is
351 accepted, `TO g USING f` is a syntax error, and so is any second
352 keyword of the same kind (`TO a WRITE b`).
353- **Trailing-region grammar (canonical order):**
354 `cmd args [USE src] [TIL marker] [filters…] [output kw dst]` —
355 input keywords first, then filter flags, then the output keyword.
356 Rationale: "we feed the command input, parse it with filters, put
357 that into output". `USE` must precede `TIL`; filters come after the
358 input keywords and before the output keyword.
359- **`TIL` is a terminator, not a source.** Unlike `USE` (which names an
360 input source), `TIL <marker>` ends the input at a marker, so it may
361 combine with `USE` — two "input" keywords are allowed, in the fixed
362 order `USE` then `TIL`.
363- **`TIL` truncates the source at the marker.** The marker line is not
364 part of the input and everything from the marker on is discarded —
365 `USE f TIL m` reads only the text of `f` before `m`. Marker match is
366 an exact full-line match.
367- **^D is EOF everywhere; ^D/^C no longer power off.** Inside a `TIL`
368 input block, ^D ends the input early exactly like a file's EOF.
369 Shutting down via ^D or ^C is removed: at the prompt, ^D on an empty
370 line is a no-op (fresh prompt); ^C keeps its line-cancel /
371 stream-abort role; `poweroff`/`exit` remain the only shutdown verbs.
372- **Input consumers: `cat` and `hexdump` only, and only when path-less.**
373 With no path (or with an input keyword), they read the input stream
374 (`cat USE a TO b` copies a→b; `cat TIL END` is the classic heredoc).
375 All other commands reject input keywords with a syntax error — a
376 command declares whether it takes input. `USE`/`TIL` are general
377 building blocks; more consumers will come later.
378- **Keywords: uppercase-only, aliases valid.** `WRITE`==`WRITING`,
379 `TIL`==`UNTIL`, `USE`==`USING`; lowercase forms are ordinary text, so
380 `echo use the force` can never redirect. Keywords are reserved
381 operators — a keyword is always an operator, never an argument
382 (collision escape: quoting, a future building block).
383- **`-p` = pause flag (pager), more-style.** A stage that pauses at
384 every screenful of output and waits for a keypress; engages only when
385 output exceeds one screenful, no args, no scrollback/search. As a
386 stage it chains with the others — `-h N -p -g pat -p` lets the user
387 see intermediate steps of the filtering.
388- **`> >> < <<` are gone.** Keywords are the *only* redirection syntax;
389 `>` is now ordinary text (`echo a > b` prints `a > b`). Collateral:
390 `scripts/test-disk.sh` and the `help` text must be rewritten to the
391 keyword syntax; the milestone bullets above describing `echo >`/`>>`
392 record the old behavior (superseded by these notes).
393- **Quoting (C): `"..."` groups + literalizes; `\x` escapes one char.**
394 Quoted/escaped text is always literal — never an operator, never
395 split. So `echo foo "WRITING" bar` prints `foo WRITING bar`; to
396 print literal quote characters: `echo foo \"WRITING\" bar`. Also
397 enables spaces in filenames and multi-word `-g` patterns
398 (`-g "a b"`). Quote chars are delimiters, not content.
399
400Assumed edge semantics (flag if wrong):
401
402- Filtered output goes to both terminal (GOP) and serial, identically.
403 A file sink (WRITING/TO) replaces terminal output entirely — screen
404 is silent, as `echo >` behaved before.
405- `-p` pauses terminal output only; with a file sink it is a no-op.
406- `-h N` / `-t N` require N ≥ 1; otherwise syntax error.
407- `cat p USE s` (path args *and* an input keyword) is a syntax error —
408 the input source is ambiguous. `cat USE s` and path-less `cat TIL m`
409 are the valid input forms.
410- ADR 0002 (`docs/adr/0002-keyword-redirection-and-post-filters.md`)
411 captures the keyword-redirection + post-filter decision; the ADR is
412 the authoritative statement, these notes are the working detail.
413
414Open questions (to resolve this session):
415
4161. ~~Order of application of `-h`/`-t`/`-g`~~ — **resolved**: left to
417 right.
4182. ~~`-h`/`-t` streaming semantics~~ — **resolved**: `-t` hold-back +
419 ^C flush; `-h` truncates the upstream.
4203. ~~`-h`/`-t` semantics on infinite streams (e.g. `cat /dev/mem`)~~ —
421 **resolved**: folded into 2.
4224. ~~`-g` pattern semantics (literal substring vs regex; case
423 sensitivity)~~ — **resolved**: smart-case literal substring;
424 repeatable stages.
4255. ~~Keyword details~~ — **resolved**: uppercase-only, both aliases
426 valid, keywords fully reserved; quoting (also resolved this
427 session) is the escape mechanism.
4286. ~~Pager flag: in or out~~ — **resolved**: `-p` pause flag, in.
4297. ~~Which commands consume input~~ — **resolved**: `cat` + `hexdump`,
430 path-less only; other commands reject input keywords.
4318. ~~Fate of the existing `>` / `>>` redirection~~ — **resolved**: full
432 removal; `>` is plain text; scripts/help to be rewritten.
433
434## Implementation (this session — done, verified in QEMU)
435
436Everything above is now implemented in `shell/src/lib.rs`:
437
438- **Tokenizer** — `"…"` groups text (delimiter, not content), `\x`
439 escapes one char, a token containing any quoted/escaped char is
440 flagged literal and can never be an operator or flag.
441- **Parser** — canonical grammar for every command; keywords
442 uppercase-only with aliases, fully reserved (quoted forms are
443 literal data); strict ordering enforced (USE before TIL, input
444 before filters, output keyword last) with syntax errors otherwise;
445 `-h`/`-t` need N ≥ 1, `-g` takes the next token verbatim; unknown
446 flags before the trailing region are ordinary arguments.
447- **Filter pipeline** — stages over a line stream, left to right,
448 each repeatable: `Head` kills the upstream (producers check the
449 `line()` return), `Tail` holds a rolling window and flushes through
450 the remaining stages at end-of-stream (EOF, ^C, or head kill),
451 `Grep` is smart-case literal substring, `Pause` is a terminal-only
452 screenful pager (^C aborts; dropped entirely with a file sink).
453- **Sinks** — terminal (GOP + serial, identical) or file; a file sink
454 replaces the screen, opens lazily on first output, `WRITING`
455 truncates and writes from offset 0, `TO` appends at current size.
456 Diagnostics (errors, usage) always go to the terminal, never into
457 the pipeline or a file.
458- **Input consumers** — `cat`/`hexdump`, path-less only: no keyword =
459 keyboard until ^D, `USE f` = file, `TIL m` = keyboard heredoc,
460 `USE f TIL m` = file truncated at the exact marker line (marker and
461 everything after discarded). Other commands reject input keywords.
462- **^D** — EOF everywhere, at the prompt it is a no-op (fresh
463 prompt); ^C keeps line-cancel / stream-abort; `poweroff`/`exit`
464 are the only shutdown verbs.
465- **Collateral** — `help` text rewritten; `scripts/test-disk.sh` and
466 the `sendkeys.py` docstring updated to keyword syntax.
467
468Bugs found by QEMU verification and fixed:
469
4701. `WRITING` over an existing file left stale NUL padding —
471 `fs::size()` returns the node-cached size, stale after
472 `set_len(0)`; the overwrite path now writes at literal offset 0
473 (the pre-redesign echo wrote at 0 too).
4742. `cat USE f TIL m` leaked everything after the marker — the
475 final-partial-line emit ran even after the marker break; fixed
476 with a `stopped` flag in `cat_file_until`/`hexdump_file_until`.
477
478Verified: 26 grammar checks pass on a fresh disk *and* on a reused
479one (rewriting existing files), `make test-disk` round trip is
480fsck-clean, the `-p` pager pauses at screenfuls and resumes on
481keypress, `-h` stops `/dev/mem` at exactly N lines, and ^C flushes a
482`-t` window on an infinite stream.