A rewrite of the Plan 9 rc shell in Goose for UNIX-like systems and with structured CLI args
rc CONFORMANCE.md
11 kB · 133 lines
Markdown

Conformance matrix #

The Plan 9 rc manual defines this matrix. tests/cases.edn contains the named shell regressions. The matrix records implemented coverage, not a proof of compatibility for every input.

Manual section Coverage Representative regressions
Command lines Sequence, background PID, status, comments, continuation comments, line continuation, background wait, external status
Simple commands Function priority, builtins, executable search, explicit UNIX paths function deletion, path excludes current directory, explicit relative executable
Arguments and variables Flat lists, quotes, counts, indirection, ranges, quoted variables lists, subscripts, indirection, quoted variable, list command words
Patterns Unicode code points, classes, complement, unmatched patterns, hidden files Unicode wildcard, glob complement, Plan 9 hidden files, unmatched glob
Concatenation and free carets Pairwise and distributed concatenation, literal suffixes, empty-list rejection pairwise concatenation, distributed concatenation, list mismatch, empty concatenation
Substitution Field separators, captures, input and output process substitutions custom ifs, input process substitution, output process substitution, nonlinear pipeline large
I/O redirections Read, write, append, read/write, descriptor copies, closure, restoration append, read write descriptor opens once, descriptor restoration, bare exec retains redirection
Here-documents Quoted and expanded bodies, multiple bodies, function bodies expanded here document, quoted here document, multiple here documents, function here document
Pipelines Flat stages, descriptor qualifiers, ordered per-stage status three pipeline statuses, pipeline middle descriptor, pipeline descriptor collision
Boolean commands Left-associative logical operators, negation, conditional execution logical operators are left associative, pipeline status condition
Compound commands Groups, subshells, loops, conditions, adjacent if not, switches subshell isolation, for, implicit for list, if not adjacency, switch
Functions and assignments Definition, deletion, dynamic argument scope, temporary bindings, global updates function scope, function global assignment, temporary assignment global effect
Signal functions Traps during input, captures, waits, and evaluation signal function, inherited signal function, tests/signals.py, tests/terminal.py
., builtin, cd, eval, exec Source search and arguments, dispatch, directory search, evaluation, replacement source path, source arguments, cdpath order, eval, bare exec retains descriptor copy
flag, exit, shift, wait Invocation state, exit status, argument removal, child status retention flag state, exit status text, shift, completed background status, wait unknown child
rfork Approved POSIX subset and explicit namespace rejection rfork portable copies, rfork process group, rfork empty environment, rfork rejects namespaces
whatis, ~ Reusable definitions, quoted names, function and variable coexistence, unexpanded patterns function description, whatis quoted variable name, whatis variable and function, tilde patterns skip path expansion
Environment List packets, function export, restricted import, UNIX variable aliases inherited function, empty environment component, inherited function rejects extra commands, PATH empty components survive import
Special variables $*, $apid, $home, $ifs, $path, $pid, $prompt, $status Argument, background, source, login, capture, terminal, and status suites
Invocation Grouped flags, command/file/stdin input, login profile, initialization, diagnostics grouped flags, login profile, initialization file, status flag, verbose flag, debug invocation flag

UNIX extensions #

Feature Coverage
break, continue, return Nested loops, function return status, nonlocal loop flow, scope and descriptor restoration
umask, limit, newpgrp Inherited process state, invalid input, suffix overflow, process groups
whatis selectors, . -i tests/unix.mjs: type selection, lookup priority, reusable definitions, interactive source errors, terminal input, argument restoration
Interrupted source reads tests/source-read.mjs: partial input, child signals, user traps, and nested source reads
Interactive source signals tests/source-signals.mjs: interrupt recovery, nested input, explicit traps, inherited ignores, caller restoration, and canceled child waits
Interactive jobs tests/jobs.mjs: terminal ownership, stops, foreground/background resume, pipeline status, signal exits, terminal restoration, stty, invalid selectors
Persistent history tests/history.mjs: opt-in files, permissions, locks, concurrent append, multiline records, safe reload
Unicode editor tests/editor.mjs, layout assertions, and 853 Unicode 18 grapheme cases
Declarative arguments tests/declarations.clj: parsing, help, version, extraction without script execution, four completion formats
Automatic completion tests/automatic.mjs, tests/groups.mjs, and context assertions: direct declarations, nested contexts, scalar prefixes, grouped values

Reference boundaries #

The adjacent ../rc reference supplies umask, limit, newpgrp, nonlocal flow, and the $history convention. It does not supply a complete job-control implementation. jobs, fg, and bg are rc2 extensions. Its optional example arithmetic builtins in addon.c are outside this scope. Its whatis selection flags and . -i are approved UNIX extensions, not part of the Plan 9 target. Resource reports use raw numeric units instead of the reference's formatted unit suffixes.

The approved scope excludes Plan 9 namespace operations. Declarative subcommands are a later, user-approved UNIX extension. Completion never executes a substitution from the command line. tests/subcommands.mjs covers recursive declarations, scoped help, the implicit command-path variable, argument boundaries, and four-shell completion. COMPATIBILITY.md records platform limits and deliberate adaptations.

Validation #

Release and debug suites pass on macOS arm64, Linux arm64, and emulated Linux amd64. Each run includes 171 shell cases, 202 parser cases, 853 grapheme cases, and all supporting suites. tests/conformance.mjs adds 33 focused regressions for subscript selection, bounded counts, here-document snapshots, and the manual's optional eval arguments. The Linux runs use disposable Ubuntu 26.04 containers, a read-only source mount, and no network during tests. The amd64 memory suite uses a 300-second allowance under emulation. Its workload and 96 MiB limit remain unchanged. Observed memory-suite peak RSS was 61–62 MiB on macOS, 64–65 MiB on Linux arm64, and 78–79 MiB on emulated amd64. Goose format checks, Clang warnings with -Werror, and Clang static analysis pass for the native adapter. bench/RESULTS.md records the final macOS measurements without a universal speed claim.

Differential tests #

tests/compatibility.mjs compares rc2 with explicit plan9port and Rakitzis binaries. It also accepts either reference alone. The suite adds no package dependencies and never downloads a reference.

  1. Select the plan9port binary at $PLAN9/bin/rc. The 9 launcher can report $PLAN9 through 9 rc -c 'echo $PLAN9'.

  2. Select a Rakitzis binary, or build an isolated source snapshot:

    mkdir -p .build/rakitzis-rc
    git -C ../rc archive HEAD | tar -x -C .build/rakitzis-rc
    make -C .build/rakitzis-rc CC=clang EDIT=null
    
  3. Run both references:

    make test-compatibility PLAN9PORT_RC=/path/to/plan9/bin/rc RAKITZIS_RC=.build/rakitzis-rc/rc
    
  4. For a debug candidate or a separate report, run the runner directly:

    node tests/compatibility.mjs .build/rc2-debug \
      --reference plan9port=/path/to/plan9/bin/rc \
      --reference rakitzis=.build/rakitzis-rc/rc \
      --report .build/compatibility-debug.json
    

The default Rakitzis configuration disables optional addon.c builtins. Both builds use the host libc.

The corpus covers lists, patterns, Unicode, scopes, control flow, substitution, redirections, here-documents, environment export, invocation, and errors. Each run uses a fresh directory and a restricted environment. A timeout or output limit kills the case's process group. The suite compares exit status, signals, standard output, and selected file bytes. Error cases also compare diagnostic presence. The report retains diagnostic text but does not require identical diagnostic wording. Definition round trips use eval instead of raw definition text.

The JSON report contains scripts, invocation arguments, standard input, fixtures, results, binary paths, SHA-256 hashes, and host details. Known differences require exact reviewed outputs and exit codes from both implementations. A changed result returns to the mismatch category. The runner reports unsupported reference features separately. These cases still run on rc2 but have no comparison oracle for that reference. Timeouts and execution errors always fail, including cases with a known difference or an unsupported reference feature. The runner exits with failure for mismatches or execution errors. Ordinary regression suites do not require reference binaries. tests/compatibility-test.mjs covers classification, isolation, input, timeout cleanup, output limits, and invalid arguments.

The macOS release and debug comparisons use plan9port 0-unstable-2025-11-10 and Rakitzis commit 418fa950edc3584d7be01c8cb982c39c933bee93. The plan9port binary comes from the installed Nix package. The Rakitzis binary uses an isolated build of the adjacent repository.

Both candidate builds report the same results across 241 cases per reference:

Reference Matches Known differences Unsupported Unresolved mismatches Execution errors
plan9port 188 5 48 0 0
Rakitzis 205 36 0 0 0

Tasks tsk-23 through tsk-26 resolve the eleven former plan9port mismatches:

  • Four subscript cases now produce empty lists instead of errors.
  • Four here-document cases now use variable values from the input boundary.
  • Empty eval retains the manual's optional arguments instead of plan9port's usage error.
  • Two shift cases now use plan9port's permissive count conversion.

The deliberate plan9port differences cover empty eval, function here-documents, pipeline stage positions, and two conventional UNIX exit codes. Ten additional Rakitzis differences now follow the Plan 9 subscript, here-document, and shift behavior. The reports remain at .build/compatibility-release.json and .build/compatibility-debug.json. These results do not establish full compatibility.