feat(lang): std.cli exit-code + --json contract helpers [F3.3]

Foundation milestone close — the minimal exit-code / --json contract
`dist` relies on, in pure sx (no compiler change).

- EX_OK (0) / EX_USAGE (64, sysexits.h) / EX_UNAVAILABLE (70) named
  constants in std.cli.
- exit_ok() / exit_usage() terminators routing through the canonical
  process.exit(code: u8) — removes the hand-rolled cli_bail_exit `_exit`
  binding; the unsupported-platform path now uses proc.exit(EX_UNAVAILABLE).
- --json read is parsed.json (already parsed by F3.2); documented as the
  detection point with a stdout-pure / stderr-human convention.
- examples/0718-modules-cli-exit-json.sx exercises the contract: json true
  with --json / false without, EX_USAGE == 64, and a usage path that exits
  64 via exit_usage() (expected .exit = 64).
- readme.md gains a std.cli command-line-interface subsection.
This commit is contained in:
agra
2026-06-05 01:01:25 +03:00
parent 9db26e9780
commit 0fc7a72cbc
6 changed files with 143 additions and 7 deletions

View File

@@ -25,11 +25,13 @@
//
// Platform: macOS only for now, via the C runtime's `_NSGetArgv()`
// (char***) and `_NSGetArgc()` (int*). On any other OS the accessors bail
// loudly (message + non-zero exit) rather than returning a silent empty.
// loudly (message + `EX_UNAVAILABLE` exit via `process.exit`) rather than
// returning a silent empty.
// =====================================================================
#import "modules/std.sx";
#import "modules/compiler.sx";
proc :: #import "modules/process.sx";
libc :: #library "c";
@@ -42,9 +44,39 @@ libc :: #library "c";
ns_get_argv :: () -> *s64 #foreign libc "_NSGetArgv";
ns_get_argc :: () -> *s32 #foreign libc "_NSGetArgc";
// Bound to POSIX `_exit(2)`. Used only on the unsupported-platform path to
// terminate loudly instead of handing back a misleading empty slice.
cli_bail_exit :: (code: s32) -> noreturn #foreign libc "_exit";
// =====================================================================
// EXIT-CODE & `--json` CONTRACT (F3.3) — the minimal surface `dist` (and
// any sx CLI front-end) relies on. Two pieces:
//
// 1. NAMED EXIT CODES (sysexits.h subset). A front-end ends with the
// code that matches the outcome: `EX_OK` on success, `EX_USAGE`
// after a `parse` failure (a `CliError` — bad command / flag /
// value), `EX_UNAVAILABLE` when the platform is unsupported.
// 2. TERMINATORS. `exit_ok()` / `exit_usage()` end the process with the
// matching code. They route through the canonical
// `process.exit(code: u8)` (modules/process.sx) — there is NO second
// hand-rolled `_exit` binding in this module; the unsupported-platform
// path below goes through `proc.exit(EX_UNAVAILABLE)` too.
//
// `--json` MODE needs no new code here: the parser already surfaces it as
// `parsed.json` (true iff `--json` appears in the argv — see `Parsed.json`).
// The convention a front-end follows: in json mode stdout carries ONLY the
// machine result, and human diagnostics go to stderr (e.g. via
// `modules/log.sx`'s `log.err`). Detect json mode by reading `parsed.json`.
// =====================================================================
EX_OK :u8: 0; // success
EX_USAGE :u8: 64; // command-line usage error (sysexits.h EX_USAGE)
EX_UNAVAILABLE :u8: 70; // service / platform unavailable (sysexits.h)
// End the process successfully (`EX_OK` = 0). Thin wrapper over the
// canonical `process.exit` terminator — immediate `_exit(2)`, no unwinding.
exit_ok :: () -> noreturn { proc.exit(EX_OK); }
// End the process with the usage-error code (`EX_USAGE` = 64). A CLI
// front-end calls this after `parse` raises a `CliError` and the human
// diagnostic has been written to stderr.
exit_usage :: () -> noreturn { proc.exit(EX_USAGE); }
// Number of process arguments (argc). >= 1 for any normally-launched
// process, since argv[0] is the executable path.
@@ -53,7 +85,7 @@ os_argc :: () -> s64 {
case .macos: { return cast(s64) ns_get_argc().*; }
else: {
out("std.cli: unsupported platform — only macOS is implemented (needs _NSGetArgv/_NSGetArgc).\n");
cli_bail_exit(70);
proc.exit(EX_UNAVAILABLE);
}
}
}
@@ -88,7 +120,7 @@ os_args :: (buf: []string) -> []string {
}
else: {
out("std.cli: unsupported platform — only macOS is implemented (needs _NSGetArgv/_NSGetArgc).\n");
cli_bail_exit(70);
proc.exit(EX_UNAVAILABLE);
}
}
}
@@ -194,7 +226,7 @@ Parsed :: struct {
group: string;
command: string;
cmd_index: s64;
json: bool;
json: bool; // `--json` mode: true iff `--json` is in argv
rest: []string;
spec: []FlagSpec; // view of the matched command's flag specs
values: [16]FlagValue; // fixed inline storage, indexed by spec position