cli design
Design goal
Conformance runs are driven by Python tooling that starts many native
processes in parallel. One executable with a --backend switch keeps the
build simple (one MoonBit package, one link step) while each backend keeps its
own option syntax. The dispatcher’s job is only to choose a runner and turn its
return value into the process exit status.
Mathematical background
None beyond the runners’. The only contract is functional: the exit status is the runner’s return value, and the runner’s output depends only on its arguments and the files it reads (see internal/runner_cli design).
Design decisions
Three layers
Parsing and execution of corpora live in pure frontend/* packages; argument
handling, file access and output live in cli/*_expr_cli runners, each a
library with run(arguments) -> Int; the process boundary (@env.args(),
@sys.exit) lives only in cli. Because runners are libraries, their usage
paths are unit-testable without spawning processes, and the frontends stay
usable on every target.
Forward the program name
The dispatcher removes --backend and its value and forwards
[program, rest…]. Every runner skips element 0, so a runner behaves the
same when called from the dispatcher, from a test, or (in principle) as its
own executable.
One binary, copied per backend
tools/conformance_cli.py builds src/cli into a backend-specific target
directory and copies it to <backend>-conformance.exe. Parallel builds for
different backends therefore never share a _build directory or lock, and
tools always invoke an executable whose name says what it runs.
Dispatcher help wins
--help is handled while scanning arguments, before the backend is known, so
it always prints the dispatcher usage and exits with 0. This keeps
--help safe to call in any combination; runner options are documented on
their pages instead.
Correctness / invariants
- Exactly one runner is called per invocation, or none when the arguments are
invalid (exit
2) or--helpis given (exit0). - The exit status equals the runner’s return value:
0,1or2. - Arguments other than
--backend, its value and--help/-hreach the runner unchanged and in order.
Alternatives rejected
- Four executables. Four packages with identical
mainfunctions and four link steps for no behavioural gain. - Subcommands (
floating-conformance gda …). A positional backend would collide with runner paths; an explicit option is unambiguous. - Exit codes from runners. Calling
exitinside a runner would make it untestable as a library.
Boundaries
- Native target only (file access through
moonbitlang/x/fs, exit throughmoonbitlang/x/sys). - No corpus download, planning, parallelism or aggregation: those are in
tools/conformance.pyand thetools/run_*_interpreter.pyscripts. - No public MoonBit API.