// 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-sizes a // tuple of (i32, str) as 20B (no 4B pad before the str field). // The struct shape lays out as 24B 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. // * [[parse]], [[printusage]], [[printhelp]], [[printsubcmds]] // aren't shipped. The print family needs fmt.fprintf-with-{}- // interpolation that lib/fmt doesn't expose (lib/fmt is print- // string-only); [[parse]] is a thin wrapper that bundles // printusage + os.exit on top of [[tryparse]], so it goes too. // Callers handle errors themselves; [[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 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. 24B layout (i32 kind + rune flag + str // name) so `(void | error)` rides the tagged-union return ABI. 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-sizes that as 20B // (no 4B pad before the str field), so we use a named struct — the // struct layout pads correctly. 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]] would hand 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) * 24u64); }; 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) * 24u64); }; 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 (16) = 24B. os.free(cmd.optsptr: *void, (cmd.optscap: u64) * 24u64); }; 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; };