// getopt — POSIX-style short-flag argument parser. // // Mirrors Hare's getopts:: at ref/hare/getopt/getopts.ha; drops // underscores per plan 9 style (parameter_help → paramhelp, // unknown_option → unknownopt). // // Surface divergence from Hare, all forced by current cgen + lib/fmt // limits: // // * [[command]] is a flat struct: instead of `opts: []option` / // `args: []str` / `help: []help`, it carries three (ptr, len, // cap) triples — `optsptr/optslen/optscap`, `argsptr/argslen/ // argscap`, `helpptr/helplen/helpcap`. The cstage cgen drops // chained writes through a slice subfield (`out.opts.len = v` // stores nothing) and similarly mis-reads chained `cmd.opts.len`, // so a struct that *contains* slice fields can't round-trip. // Build a `[]option` locally from the triple when iteration is // wanted (or index `cmd.optsptr[i]` directly). // * [[command]] is filled through an out-pointer; Hare's // `let c = parse(args, help...)` is blocked by the 32B+ return- // by-value gap (task #5). Same shape as lib/memio. // * [[help]] is a flat 40B struct with a `kind: helpkind` // discriminator rather than Hare's // `(cmdhelp | flaghelp | paramhelp)` tagged union. The Hare // paramhelp variant alone is 40B, which doesn't fit ww's 32B // tagged-union return slot. Constructors [[cmdhelp]] / // [[flaghelp]] / [[paramhelp]] write through an out-pointer for // the same reason (task #5), and because task #6 (arr[i].field // = v) blocks the obvious `h[i].kind = ...` callsite spelling. // * [[option]] is a `struct { flag: rune, value: str }` rather // than Hare's `(rune, str)` 2-tuple: ww's cgen mis-pads a // tuple of (i32, str) (no 4B pad before the str field). The // struct shape lays out as rune (4) + pad (4) + str (24) = 32B // as expected. // * [[error]] is a flat 24B struct (kind + flag + name). Hare's // `!(str, []help, (requiresarg | unknownopt | unknownsubcmd))` // 3-tuple-with-nested-union doesn't map onto ww's single-payload // `!` tag. UNKNOWNSUBCMD is omitted because we don't parse // subcommands; the remaining 24B fits the tagged-union return // ABI (tag + 3 words). // * Hare's subcmd_help variant + recursive subcommand parsing is // not shipped — no SUBCMD helpkind, no subcmdhelp constructor. // The recursion needs heap-allocated self-referential types // (`(str, *command)` payload + `alloc(command)?`) and a real // consumer to drive the test surface. Add when the first // consumer is queued. // * [[printusage]] and [[printhelp]] are now shipped. They use // fmt.fprintf + fmt.fprint with [[io.empty]] for the two-pass // width measurement. [[io.empty]] diverges from Hare's // `const empty: *stream` — it is a function call (#118 blocks // static vtable const-init). [[printsubcmds]] is not shipped: // it exists solely to enumerate subcmd_help entries, and ww has // no SUBCMD helpkind (see subcommand note above). // * [[parse]] is still deferred: it calls os.exit with // `os.status.FAILURE` which doesn't exist (os.exit takes bare // i32; task #126). // * [[strerror]] formats a one-line summary into a module-level // buffer (same shape as strconv.*tos). // // GRADUATE-IN-ONE-GO WARNING (lib/CLAUDE.md policy). Callers MUST // NOT bake the current cgen-forced field names into themselves — // they will collapse to the Hare shape in a single sweep when the // underlying cgen gaps close, and the old spellings will disappear // in the same commit (no transition period). // // * `cmd.optsptr / cmd.optslen / cmd.optscap` (and `args*` / // `help*`) graduate to `cmd.opts: []option`, `cmd.args: []str`, // `cmd.help: []help` once tasks #9 + #10 land. Iterate via the // slice header you build locally; don't read the triple by name // from many call sites. // * `option.flag` / `option.value` graduate to tuple positions // `cmd.opts[i].0` (rune) / `cmd.opts[i].1` (str) once task #11 // lands. Treat them as opaque positional fields where possible. // * `strerror(err: *error)` graduates to `strerror(err: error)` // (by value, no &) once task #14 lands. // // Caller layout for `ls -Fa files.txt`: // // let helps: [4]getopt.help; // getopt.cmdhelp(&helps[0], "list files"); // getopt.flaghelp(&helps[1], 'F': rune, "..."); // getopt.flaghelp(&helps[2], 'a': rune, "..."); // getopt.cmdhelp(&helps[3], "files..."); // // let cmd: getopt.command; // match (getopt.tryparse(&cmd, args, helps[0:4])) { // case void => { // let j: i32 = 0; // for (j < cmd.optslen) { // let o: *getopt.option = &cmd.optsptr[j]; // /* o.flag, o.value */ // j += 1; // }; // }; // case let e: getopt.error => { /* render via strerror */ }; // }; // defer getopt.finish(&cmd); package getopt; import encoding.utf8; import fmt; import io; import os; import strings; // helpkind — which slot of [[help]] is meaningful. Hare's getopt // also has a subcmd_help variant; not shipped here (see file header). export type helpkind = enum i32 { CMD = 0, // label / arg-name / one-line summary FLAG = 1, // -X (no argument) PARAM = 2, // -X }; // help — one entry in the program's option/help list. // // CMD: `text` is the label. // FLAG: `flag` is the rune; `text` is the help text. // PARAM: `flag` is the rune; `name` is the value's display name; // `text` is the help text. // // Use [[cmdhelp]] / [[flaghelp]] / [[paramhelp]] to fill help // entries — direct struct-field assignment via `h[i].field = v` // is blocked by task #6. export type help = struct { kind: helpkind, flag: rune, name: str, text: str, }; // cmdhelp — fill `*out` as a CMD-kind [[help]] entry (label / arg- // name). Mirrors Hare's cmd_help. export fn cmdhelp(out: *help, text: str) void = { out.kind = helpkind.CMD; out.flag = 0: rune; out.name = ""; out.text = text; }; // flaghelp — fill `*out` as a FLAG-kind [[help]] entry. Mirrors // Hare's flag_help = (rune, str). export fn flaghelp(out: *help, r: rune, text: str) void = { out.kind = helpkind.FLAG; out.flag = r; out.name = ""; out.text = text; }; // paramhelp — fill `*out` as a PARAM-kind [[help]] entry. Mirrors // Hare's parameter_help = (rune, str, str). export fn paramhelp(out: *help, r: rune, name: str, text: str) void = { out.kind = helpkind.PARAM; out.flag = r; out.name = name; out.text = text; }; // errorkind — which payload of [[error]] carries the offender. export type errorkind = enum i32 { REQUIRESARG = 0, // -X needs an argument we didn't get UNKNOWNOPT = 1, // -X isn't in the help list }; // error — parse failure. 32B (i32 kind + rune flag + str name); // the 40B `(void | error)` slot exceeds the 24B register cap, so // it returns via sret/MEMORY, not the register ABI (see #38). export type error = struct { kind: errorkind, flag: rune, // the offending letter name: str, // argv[0] (program name) }; // option — one element of the parsed option list. Hare's getopts // uses a `(rune, str)` 2-tuple; ww's cgen mis-pads that (no 4B pad // before the str field), so we use a named struct — the struct // layout pads correctly to rune (4) + pad (4) + str (24) = 32B. export type option = struct { flag: rune, value: str, }; // command — parse result. Caller owns the slot; [[finish]] releases // the storage tryparse allocated. // // Flat (ptr, len, cap) triples replace the natural slice fields // because the cstage cgen miscompiles both writes and reads of a // slice subfield through a struct (see file header). Reconstruct a // `[]option` view locally if iteration is more convenient than // pointer-indexing `optsptr[i]`. // // opts*: every option encountered, in argv order. // args*: positional arguments after option processing — a borrowed // view into the caller's argv. // help*: a borrowed view of the help slice tryparse was called with. export type command = struct { optsptr: *option, optslen: i32, optscap: i32, argsptr: *str, argslen: i32, argscap: i32, helpptr: *help, helplen: i32, helpcap: i32, }; // findflag — lookup the kind of `r` in `hs`. Returns void if absent. // // Reads go through `&hs[i]` rather than `hs[i].field` directly: the // cstage cgen emits a bare MOVQ for a slice-indexed struct-field // read, so `hs[i].kind == K` and `hs[i].flag == r` would compare 8B // loads (kind packed with flag) against the 4B constants. The // chained-N_DOT-through-*struct path (cmd/w6c/cgen.c, 3801) uses // MOVL for i32 fields and avoids the read-too-much. Same pattern // callers should use when iterating `cmd.optsptr[i]`. fn findflag(hs: []help, r: rune) (helpkind | void) = { let i: i32 = 0; for (i < hs.len) { let p: *help = &hs[i]; if (p.kind == helpkind.FLAG && p.flag == r) { return helpkind.FLAG; }; if (p.kind == helpkind.PARAM && p.flag == r) { return helpkind.PARAM; }; i += 1; }; return; }; // tryparse — parse `argv` against `help`, filling `*out` on success. // `argv` must include the program name in argv[0]; matches Hare and // matches the slice [[os.args]] hands back. // // Recognised forms: // -X FLAG-kind option // -X value PARAM-kind option, value from next argv slot // -Xvalue PARAM-kind option, value glued to flag // -XYZ FLAG-cluster (each rune looked up independently) // -- stop processing; everything after is positional // - positional argument (commonly stdin) // // Returns void on success; an [[error]] on the first failure. // // `opts` is accumulated in a *local* slice and the (ptr, len, cap) // triple is copied into `*out.opts*` at the end: `append` on a // slice that lives inside `*out` would need `&out.foo`, which hits // the `&x.field` gap (task #4). export fn tryparse(out: *command, argv: []str, help: []help) (void | error) = { out.optsptr = nil: *option; out.optslen = 0; out.optscap = 0; out.argsptr = nil: *str; out.argslen = 0; out.argscap = 0; out.helpptr = help.ptr; out.helplen = help.len; out.helpcap = help.cap; let opts: []option; opts.ptr = nil: *option; opts.len = 0; opts.cap = 0; let i: i32 = 1; for (i < argv.len) { let arg: str = argv[i]; if (arg.len == 0) { break; }; if (arg.len == 1) { break; }; // bare "-" → positional if (arg[0] != 45: u8) { break; }; // not '-' → positional if (arg.len == 2 && arg[1] == 45: u8) { // "--" separator: skip and stop. i += 1; break; }; let bi: i32 = 1; let advanced: bool = false; for (bi < arg.len) { if (advanced) { break; }; let r: rune = arg[bi]: rune; let look: (helpkind | void) = findflag(help, r); match (look) { case void => { if (opts.cap > 0) { os.free(opts.ptr: *void, (opts.cap: u64) * size(option): u64); }; let e: error; e.kind = errorkind.UNKNOWNOPT; e.flag = r; e.name = argv[0]; return e; }; case let k: helpkind => { if (k == helpkind.FLAG) { append(opts, option { flag = r, value = "" }); bi += 1; } else { // PARAM: glued value, or next argv slot. if (bi + 1 < arg.len) { // strings.bytesub now validates rune // boundaries (#7). `bi` is the byte // offset of the just-matched ASCII flag // char, so `bi + 1` cannot land on a // continuation byte in well-formed argv; // abort spells the well-formedness // precondition as Hare's `!` does. let v: str; match (strings.bytesub(arg, bi + 1, arg.len)) { case let s: str => v = s; case utf8.invalid => abort("getopt: malformed argv UTF-8"); }; append(opts, option { flag = r, value = v }); advanced = true; } else { if (i + 1 >= argv.len) { if (opts.cap > 0) { os.free(opts.ptr: *void, (opts.cap: u64) * size(option): u64); }; let e: error; e.kind = errorkind.REQUIRESARG; e.flag = r; e.name = argv[0]; return e; }; i += 1; append(opts, option { flag = r, value = argv[i] }); advanced = true; }; }; }; }; }; i += 1; }; out.optsptr = opts.ptr; out.optslen = opts.len; out.optscap = opts.cap; if (i < argv.len) { out.argsptr = &argv[i]; out.argslen = argv.len - i; out.argscap = argv.len - i; }; return; }; // finish — release storage owned by `cmd`. Only opts was grown by // tryparse; args is a borrowed view into argv. export fn finish(cmd: *command) void = { if (cmd.optscap > 0) { // option layout: rune (4) + pad (4) + str (24) = 32B. os.free(cmd.optsptr: *void, (cmd.optscap: u64) * size(option): u64); }; cmd.optsptr = nil: *option; cmd.optslen = 0; cmd.optscap = 0; }; // strerror — render `err` as a one-line message into a module-level // buffer. The buffer is reused; callers needing the bytes to outlive // the next strerror call duplicate via strings.dup. Mirrors Hare's // strerror(error) str shape (and matches strconv's static-buffer // idiom). let strerrorbuf: [1024]u8; fn writebuf(s: str, off: i32) i32 = { let n: i32 = s.len; if (off + n > 1024) { n = 1024 - off; }; let i: i32 = 0; for (i < n) { strerrorbuf[off + i] = s[i]; i += 1; }; return off + n; }; // strerror takes `*error` rather than Hare's value `error`: the cstage // cgen only passes the first 8 bytes of a >8B struct argument, which // would truncate the `name` field. Returning a `case let e: error` // scrutinee through `&e` keeps the call shape uniform. export fn strerror(err: *error) str = { let off: i32 = 0; off = writebuf(err.name, off); off = writebuf(": ", off); if (err.kind == errorkind.REQUIRESARG) { off = writebuf("option -", off); if (off < 1024) { strerrorbuf[off] = err.flag: u8; off += 1; }; off = writebuf(" requires an argument", off); }; if (err.kind == errorkind.UNKNOWNOPT) { off = writebuf("unrecognized option: -", off); if (off < 1024) { strerrorbuf[off] = err.flag: u8; off += 1; }; }; let r: str; r.ptr = &strerrorbuf[0]; r.len = off; return r; }; // ref/hare/getopt/getopts.ha:202-335. // // Divergences from Hare (all forced): // * [[printusage]] / [[printhelp]] take `io.handle` directly; // Hare's signatures match. // * [[io.empty]] is a function call, not a `const *stream` (#118). // * [[containsh]] iterates the flat `help` struct (not a tagged // union) and checks `kind == FLAG|PARAM && flag == 'h'`. // * [[printsubcmds]] is not shipped — ww has no SUBCMD helpkind // (see file header). The `printhelp` call site that would invoke // it is dropped. // * Error returns from fmt.fprint/fprintf are propagated via explicit // match rather than Hare's `?` shorthand (ww has no `?` yet). // // containsh — true if `hs` declares an explicit `-h` flag or param. // Mirrors ref/hare/getopt/getopts.ha:267-279 adapted to the flat // [[help]] struct. fn containsh(hs: []help) bool = { let i: i32 = 0; for (i < hs.len) { let p: *help = &hs[i]; if ((p.kind == helpkind.FLAG || p.kind == helpkind.PARAM) && p.flag == 'h': rune) { return true; }; i += 1; }; return false; }; // _printusage — inner two-pass renderer for [[printusage]]. // Mirrors ref/hare/getopt/getopts.ha:213-265. // Returns total bytes written for the width-measurement pass. fn _printusage( out: io.handle, name: str, indent: bool, hascmdh: bool, help: []help, ) (size | io.error) = { let z: size = 0; match (fmt.fprint(out, "Usage:", name)) { case let n: size => z += n; case let e: io.error => return e; }; let startedflags: bool = false; if (!hascmdh) { match (fmt.fprint(out, " [-h")) { case let n: size => { z += n; startedflags = true; }; case let e: io.error => return e; }; }; let i: i32 = 0; for (i < help.len) { let p: *help = &help[i]; if (p.kind == helpkind.FLAG) { if (!startedflags) { match (fmt.fprint(out, " [-")) { case let n: size => { z += n; startedflags = true; }; case let e: io.error => return e; }; }; match (fmt.fprint(out, p.flag)) { case let n: size => z += n; case let e: io.error => return e; }; }; i += 1; }; if (startedflags) { match (fmt.fprint(out, "]")) { case let n: size => z += n; case let e: io.error => return e; }; }; i = 0; for (i < help.len) { let p: *help = &help[i]; if (p.kind == helpkind.PARAM) { if (indent) { match (fmt.fprint(out, "\n\t")) { case let n: size => z += n; case let e: io.error => return e; }; }; match (fmt.fprintf(out, " [-{} <{}>]", p.flag, p.name)) { case let n: size => z += n; case let e: io.error => return e; }; }; i += 1; }; // Positional CMD labels (indices > 0 only; index 0 is the command // summary). Mirrors ref/hare/getopt/getopts.ha:253-264. let firstarg: bool = true; i = 1; for (i < help.len) { let p: *help = &help[i]; if (p.kind == helpkind.CMD) { if (firstarg) { if (indent) { match (fmt.fprint(out, "\n\t")) { case let n: size => z += n; case let e: io.error => return e; }; }; firstarg = false; }; match (fmt.fprintf(out, " {}", p.text)) { case let n: size => z += n; case let e: io.error => return e; }; }; i += 1; }; match (fmt.fprint(out, "\n")) { case let n: size => z += n; case let e: io.error => return e; }; return z; }; // printusage — print a one-line usage summary to `out`. // Mirrors ref/hare/getopt/getopts.ha:202-211. export fn printusage( out: io.handle, name: str, help: []help, ) (void | io.error) = { let hascmdh: bool = containsh(help); let z: size = 0; // Measure with io.empty to decide whether to indent long lines. // Diverges from Hare's `_printusage(io::empty, ...)`: ww's // io.empty() is a function (not a const *stream) due to #118. let esink: io.handle = io.empty(): io.handle; match (_printusage(esink, name, false, hascmdh, help)) { case let n: size => z = n; case let e: io.error => return e; }; match (_printusage(out, name, z > 72, hascmdh, help)) { case let n: size => {}; case let e: io.error => return e; }; return void; }; // printhelp — print full help text (summary + usage + option list) to // `out`. Mirrors ref/hare/getopt/getopts.ha:281-314. // Omits the printsubcmds call at the tail (ww has no SUBCMD helpkind). export fn printhelp( out: io.handle, name: str, help: []help, ) (void | io.error) = { if (help.len == 0) { return void; }; let p0: *help = &help[0]; if (p0.kind == helpkind.CMD) { match (fmt.fprintfln(out, "{}: {}\n", name, p0.text)) { case let n: size => {}; case let e: io.error => return e; }; }; // Usage line (two-pass for 72-char indent). let hascmdh: bool = containsh(help); let z: size = 0; let esink: io.handle = io.empty(): io.handle; match (_printusage(esink, name, false, hascmdh, help)) { case let n: size => z = n; case let e: io.error => return e; }; match (_printusage(out, name, z > 72, hascmdh, help)) { case let n: size => {}; case let e: io.error => return e; }; match (fmt.fprint(out, "\n")) { case let n: size => {}; case let e: io.error => return e; }; if (!hascmdh) { match (fmt.fprintln(out, "-h: print this help text")) { case let n: size => {}; case let e: io.error => return e; }; }; let i: i32 = 0; for (i < help.len) { let p: *help = &help[i]; if (p.kind == helpkind.FLAG) { match (fmt.fprintfln(out, "-{}: {}", p.flag, p.text)) { case let n: size => {}; case let e: io.error => return e; }; }; if (p.kind == helpkind.PARAM) { match (fmt.fprintfln(out, "-{} <{}>: {}", p.flag, p.name, p.text)) { case let n: size => {}; case let e: io.error => return e; }; }; i += 1; }; return void; };