// selfhost/cmd/ww/main.ww — port of cmd/ww/main.c. // // The user-facing driver. Plan 9 cc(1) / Hare hare(1) analogue: // // ww build foo.ww → w6c foo.ww > foo.s ; w6a foo.s > foo.o ; // w6l -o foo foo.o libwwrt.a // ww run foo.ww → build then exec // ww version → print version // // Tool paths default to siblings of $0 so a fresh build runs out of // out/bin/. Env-var overrides (WW_W6C / WW_W6A / WW_W6L / WW_LIB) are // not yet supported in this port; the bootstrap doesn't need them. package main; import os; import rt; import strings; // All path/string scratch buffers go on the runtime page allocator. // One page is plenty for any path we build. def PATH_MAX: u64 = 4096u64; def CMD_MAX: u64 = 8192u64; // ---- C-string helpers -------------------------------------------------- fn cstrlen(p: *u8) u64 = { let n: u64 = 0u64; for (p[n] != 0u8) { n += 1u64; }; return n; }; // pathstr — view a NUL-terminated *u8 as a str. Bridges the // driver's argv-style *u8 paths to lib/os entrypoints (str // post-task-#23). fn pathstr(p: *u8) str = { let r: str; r.ptr = p; r.len = cstrlen(p): i32; return r; }; fn cstreq(a: *u8, b: *u8) bool = { let i: u64 = 0u64; for (a[i] == b[i]) { if (a[i] == 0u8) { return true; }; i += 1u64; }; return false; }; // cstreqlit — compare a NUL-terminated *u8 to a ww string literal. fn cstreqlit(a: *u8, lit: str) bool = { let n: i32 = lit.len; let i: i32 = 0; for (i < n) { if (a[i] != lit[i]) { return false; }; i += 1; }; return a[n] == 0u8; }; // startswith — does a have b as a prefix? fn cstrstartswith(a: *u8, b: *u8) bool = { let i: u64 = 0u64; for (b[i] != 0u8) { if (a[i] != b[i]) { return false; }; i += 1u64; }; return true; }; // memcpy fn bytecpy(dst: *u8, src: *u8, n: u64) void = { let i: u64 = 0u64; for (i < n) { dst[i] = src[i]; i += 1u64; }; }; // Copy a NUL-terminated *u8 into dst starting at off; return the new // offset (without writing a NUL). fn cstrinto(dst: *u8, off: u64, src: *u8) u64 = { let i: u64 = 0u64; for (src[i] != 0u8) { dst[off + i] = src[i]; i += 1u64; }; return off + i; }; // Same, but for a ww `str` (no NUL on the source side; we copy len bytes). fn strinto(dst: *u8, off: u64, src: str) u64 = { let n: i32 = src.len; let i: i32 = 0; for (i < n) { let iu: u64 = i: u64; dst[off + iu] = src[i]; i += 1; }; let nu: u64 = n: u64; return off + nu; }; // Write a single byte, return new offset. fn byteinto(dst: *u8, off: u64, c: u8) u64 = { dst[off] = c; return off + 1u64; }; // NUL-terminate at off and return the same off (handy when passing the // buffer to a syscall that expects a C-string). fn cstrseal(dst: *u8, off: u64) void = { dst[off] = 0u8; }; // ---- Tool-path resolution --------------------------------------------- // dirname-equivalent: copy argv[0] up to (but not including) the last // '/' into dst, NUL-terminated. If no slash, write ".". fn selfdirinto(dst: *u8, dstsz: u64, argv0: *u8) void = { let n: u64 = cstrlen(argv0); let cut: u64 = 0u64; let i: u64 = 0u64; for (i < n) { if (argv0[i] == 47u8) { cut = i; }; // '/' i += 1u64; }; if (cut == 0u64) { dst[0u64] = 46u8; // '.' dst[1u64] = 0u8; return; }; if (cut + 1u64 >= dstsz) { cut = dstsz - 2u64; }; bytecpy(dst, argv0, cut); dst[cut] = 0u8; }; // Build "$dir/$name" (NUL-terminated) into a fresh page-sized buffer. fn joinpath(dir: *u8, name: *u8) *u8 = { let buf: []u8 = alloc([], PATH_MAX)!; buf.len = PATH_MAX: i32; let off: u64 = cstrinto(buf.ptr, 0u64, dir); off = byteinto(buf.ptr, off, 47u8); off = cstrinto(buf.ptr, off, name); cstrseal(buf.ptr, off); return buf.ptr; }; // Same, but the second component is a ww `str` literal. fn joinpathlit(dir: *u8, name: str) *u8 = { let buf: []u8 = alloc([], PATH_MAX)!; buf.len = PATH_MAX: i32; let off: u64 = cstrinto(buf.ptr, 0u64, dir); off = byteinto(buf.ptr, off, 47u8); off = strinto(buf.ptr, off, name); cstrseal(buf.ptr, off); return buf.ptr; }; // ---- Subprocess plumbing ---------------------------------------------- // procrun — fork, execve `path` with `argv` (NULL-terminated), wait. // Returns 0 on clean exit-0, 1 on any non-zero exit or signal kill, // -1 on fork/wait failure. fn procrun(path: *u8, argv: **u8) i32 = { let pid: i32 = os.fork(); if (pid < 0) { os.write(2, "ww: fork failed\n".ptr, 16u64); return -1; }; if (pid == 0) { os.execve(pathstr(path), argv, nil: **u8); os.write(2, "ww: execve failed\n".ptr, 18u64); os.exit(127); }; let status: i32 = 0; let r: i32 = os.wait4(pid, &status, 0i32, nil: *void); if (r < 0) { os.write(2, "ww: wait4 failed\n".ptr, 17u64); return -1; }; // Linux wait status: low byte = signal (0 if exited cleanly), // next byte = exit code. if ((status & 127i32) != 0) { return 1; }; let code: i32 = (status >> 8i32) & 255i32; if (code != 0) { return 1; }; return 0; }; // ---- `use` resolution + source concatenation -------------------------- // // Recursive expansion: for each `use IDENT;` we find at the top of // `path`, resolve via the colon-separated `dirs`, expand the imported // file first, then append our own bytes. Already-visited paths are // skipped (linear scan; typical builds visit a handful of modules). type strnode = struct { s: str, snext: *strnode, }; type expctx = struct { out: i32, // fd we're writing the combined source to dirs: *u8, // ":"-separated search path (NUL-terminated) visit: *strnode, }; fn visitseen(c: *expctx, path: str) bool = { let n: *strnode = c.visit; for (n != nil) { if (n.s.len == path.len) { let i: i32 = 0; let eq: bool = true; for (i < path.len) { if (n.s[i] != path[i]) { eq = false; i = path.len; } else { i += 1; }; }; if (eq) { return true; }; }; n = n.snext; }; return false; }; fn visitadd(c: *expctx, path: str) void = { let n: *strnode = alloc(strnode{s=path, snext=c.visit})!; c.visit = n; }; // Translate dots in an `import` name to slashes for path lookup. // `encoding.utf8` → `encoding/utf8`. Mirrors Hare's hare(1) // use-path → fs-path mapping // (ref/hare/hare/module/srcs.ha:78 builds the same shape via // path::push per ident part). fn importpathform(name: *u8, namelen: u64) *u8 = { let buf: []u8 = alloc([], namelen + 1u64)!; let i: u64 = 0u64; for (i < namelen) { if (name[i] == 46u8) { buf[i] = 47u8; } // '.' -> '/' else { buf[i] = name[i]; }; i += 1u64; }; buf[namelen] = 0u8; return buf.ptr; }; // Try // as a directory, then /.ww as a file. // Sets *isdir on hit. Symmetric with cstage locate_import_in for // byte-id driver output (rule 10). The legacy //.ww // form was dropped in task #22 — directory-as-module enumeration // replaces it, mirroring ref/hare/hare/module/srcs.ha (Hare has no // `foo/foo.ha` fallback; a module IS the directory). fn locatein(dir: *u8, dirlen: u64, pathform: *u8, pflen: u64, isdir: *i32) *u8 = { let buf: []u8 = alloc([], PATH_MAX)!; let off: u64 = 0u64; let i: u64 = 0u64; for (i < dirlen) { buf[off + i] = dir[i]; i += 1u64; }; off += dirlen; buf[off] = 47u8; off += 1u64; // '/' i = 0u64; for (i < pflen) { buf[off + i] = pathform[i]; i += 1u64; }; off += pflen; buf[off] = 0u8; let fi: os.filestat; let r: (void | os.oserror) = os.stat(&fi, pathstr(buf.ptr)); let isdirhit: bool = false; match (r) { case void => { let t: u32 = (fi.mode: u32) & 61440u32; // S_IFMT if (t == os.mode.DIR: u32) { isdirhit = true; }; }; case let e: os.oserror => void; }; if (isdirhit) { *isdir = 1; return buf.ptr; }; let buf2: []u8 = alloc([], PATH_MAX)!; off = 0u64; i = 0u64; for (i < dirlen) { buf2[off + i] = dir[i]; i += 1u64; }; off += dirlen; buf2[off] = 47u8; off += 1u64; i = 0u64; for (i < pflen) { buf2[off + i] = pathform[i]; i += 1u64; }; off += pflen; buf2[off] = 46u8; off += 1u64; // '.' buf2[off] = 119u8; off += 1u64; // 'w' buf2[off] = 119u8; off += 1u64; // 'w' buf2[off] = 0u8; if (os.access(pathstr(buf2.ptr), 0i32) == 0) { *isdir = 0; return buf2.ptr; }; return nil; }; // Walk a colon-separated dirlist, return first hit or nil. Sets // *isdir on hit. fn locateimport(dirs: *u8, name: *u8, namelen: u64, isdir: *i32) *u8 = { let pathform: *u8 = importpathform(name, namelen); let pflen: u64 = cstrlen(pathform); let total: u64 = cstrlen(dirs); let p: u64 = 0u64; for (p < total) { let q: u64 = p; for (q < total) { if (dirs[q] == 58u8) { break; }; // ':' q += 1u64; }; let seglen: u64 = q - p; if (seglen > 0u64) { let hit: *u8 = locatein(dirs + p, seglen, pathform, pflen, isdir); if (hit != nil) { return hit; }; }; p = q + 1u64; }; return nil; }; // Filter for dir enumeration: keep `*.ww` minus `*test.ww` and the // `*.combined.ww` driver-generated concat artifacts (the previous // build leaves them in the source tree; they parse-error when // re-included). Returns true to keep. fn dirfilekeep(name: *u8, nlen: u64) bool = { if (nlen <= 3u64) { return false; }; if (name[nlen - 3u64] != 46u8) { return false; }; // '.' if (name[nlen - 2u64] != 119u8) { return false; }; // 'w' if (name[nlen - 1u64] != 119u8) { return false; }; // 'w' if (nlen >= 7u64) { if (name[nlen - 7u64] == 116u8) { // 't' if (name[nlen - 6u64] == 101u8) { // 'e' if (name[nlen - 5u64] == 115u8) { // 's' if (name[nlen - 4u64] == 116u8) { // 't' return false; }; }; }; }; }; if (nlen >= 12u64) { // ".combined.ww" — full 12-char match mirrors cstage // cmd/ww/main.c enumerate_dir_ww strcmp (rule 10); the // trailing .ww is pre-guaranteed by the early-out above. if (name[nlen - 12u64] == 46u8) { // '.' if (name[nlen - 11u64] == 99u8) { // 'c' if (name[nlen - 10u64] == 111u8) { // 'o' if (name[nlen - 9u64] == 109u8) { // 'm' if (name[nlen - 8u64] == 98u8) { // 'b' if (name[nlen - 7u64] == 105u8) { // 'i' if (name[nlen - 6u64] == 110u8) { // 'n' if (name[nlen - 5u64] == 101u8) { // 'e' if (name[nlen - 4u64] == 100u8) { // 'd' return false; }; }; }; }; }; }; }; }; }; }; return true; }; // Byte-wise memcmp returning < 0, 0, > 0. Rule-10 byte-id requires // cstage and wwstage sort the same way; memcmp is the // locale-independent total order (mirrors ref/hare/sort/cmp/cmp.ha // strs). fn bytecmp(a: *u8, alen: u64, b: *u8, blen: u64) i32 = { let n: u64 = alen; if (blen < n) { n = blen; }; let i: u64 = 0u64; for (i < n) { let av: i32 = (a[i]): i32; let bv: i32 = (b[i]): i32; if (av < bv) { return -1; }; if (av > bv) { return 1; }; i += 1u64; }; if (alen < blen) { return -1; }; if (alen > blen) { return 1; }; return 0; }; // enumeratedir — list *.ww entries of `dirpath` (less *test.ww and // *.combined.ww), byte-sort. Returns (names[], nnames) with each // name a NUL-terminated heap copy. fn enumeratedir(dirpath: *u8) (**u8, i32) = { let fd: i32 = os.open(pathstr(dirpath), os.flag.RDONLY, 0i32); if (fd < 0) { return nil: **u8, 0; }; let maxnames: i32 = 256; let names: []*u8 = alloc([], maxnames: u64)!; let nlens: []u64 = alloc([], maxnames: u64)!; let n: i32 = 0; let buf: []u8 = alloc([], 8192u64)!; buf.len = 8192; let r: i64 = os.getdents64(fd, buf.ptr, 8192u64); for (r > 0i64) { let off: u64 = 0u64; let ru: u64 = r: u64; for (off < ru) { let blo: u64 = (buf[off + 16u64]): u64; let bhi: u64 = (buf[off + 17u64]): u64; let reclen: u64 = blo + (bhi * 256u64); let nm: *u8 = buf.ptr + off + 19u64; let nl: u64 = cstrlen(nm); if (dirfilekeep(nm, nl)) { if (n < maxnames) { let cp: []u8 = alloc([], nl + 1u64)!; let i: u64 = 0u64; for (i < nl) { cp[i] = nm[i]; i += 1u64; }; cp[nl] = 0u8; names[n] = cp.ptr; nlens[n] = nl; n += 1; }; }; off += reclen; }; r = os.getdents64(fd, buf.ptr, 8192u64); }; os.close(fd); // Insertion sort, byte-wise. n is small (≤16 in practice). let i: i32 = 1; for (i < n) { let j: i32 = i; for (j > 0) { if (bytecmp(names[j - 1], nlens[j - 1], names[j], nlens[j]) <= 0) { j = 0; } else { let t: *u8 = names[j]; names[j] = names[j - 1]; names[j - 1] = t; let tl: u64 = nlens[j]; nlens[j] = nlens[j - 1]; nlens[j - 1] = tl; j -= 1; }; }; i += 1; }; return names.ptr, n; }; // ---- file slurp ------------------------------------------------------- fn slurp(pathcs: *u8) (*u8, u64) = { let fd: i32 = os.open(pathstr(pathcs), os.flag.RDONLY, 0i32); if (fd < 0) { return nil, 0u64; }; let szr: (i64 | os.oserror) = os.filesize(fd); let n: i64 = 0i64; match (szr) { case let v: i64 => n = v; case let e: os.oserror => { os.close(fd); return nil, 0u64; }; }; let nu: u64 = n: u64; let buf: []u8 = alloc([], nu + 1u64)!; buf.len = (nu + 1u64): i32; let rr: (i64 | os.oserror) = os.readall(fd, buf.ptr, nu); os.close(fd); let got: i64 = 0i64; match (rr) { case let v: i64 => got = v; case let e: os.oserror => return nil, 0u64; }; if (got != n) { return nil, 0u64; }; buf[nu] = 0u8; return buf.ptr, nu; }; fn isidentbyte(c: u8) bool = { if (c >= 97u8) { if (c <= 122u8) { return true; }; }; // a..z if (c >= 65u8) { if (c <= 90u8) { return true; }; }; // A..Z if (c >= 48u8) { if (c <= 57u8) { return true; }; }; // 0..9 if (c == 95u8) { return true; }; // _ if (c == 46u8) { return true; }; // . return false; }; // Scan one `import IDENT;` line out of [start, end). Returns the start // of the ident and its length, or (nil, 0) if no `import` here. The // caller passes a slice of the source: src points at the line start. fn scanuse(src: *u8, len: u64) (*u8, u64) = { let i: u64 = 0u64; // skip leading whitespace for (i < len) { if (src[i] != 32u8) { if (src[i] != 9u8) { break; }; }; i += 1u64; }; if (i + 7u64 > len) { return nil, 0u64; }; if (src[i] != 105u8) { return nil, 0u64; }; // 'i' if (src[i + 1u64] != 109u8) { return nil, 0u64; }; // 'm' if (src[i + 2u64] != 112u8) { return nil, 0u64; }; // 'p' if (src[i + 3u64] != 111u8) { return nil, 0u64; }; // 'o' if (src[i + 4u64] != 114u8) { return nil, 0u64; }; // 'r' if (src[i + 5u64] != 116u8) { return nil, 0u64; }; // 't' let sep: u8 = src[i + 6u64]; if (sep != 32u8) { if (sep != 9u8) { return nil, 0u64; }; }; i += 7u64; for (i < len) { if (src[i] != 32u8) { if (src[i] != 9u8) { break; }; }; i += 1u64; }; let idstart: u64 = i; for (i < len) { if (!isidentbyte(src[i])) { break; }; i += 1u64; }; let idlen: u64 = i - idstart; if (idlen == 0u64) { return nil, 0u64; }; return src + idstart, idlen; }; // expand — emit one file's bytes verbatim into the combined stream, // after recursive-expanding its top-of-file `import X;` imports. // Each source declares its own `package ;` (parser stamps // decls). fn expand(c: *expctx, pathcs: *u8) void = { let plen: u64 = cstrlen(pathcs); let view: str; view.ptr = pathcs; view.len = plen: i32; let pathstr: str = strings.dup(view); if (visitseen(c, pathstr)) { return; }; visitadd(c, pathstr); let bufp: *u8; let blen: u64; bufp, blen = slurp(pathcs); if (bufp == nil) { os.write(2, "ww: cannot read source\n".ptr, 23u64); return; }; // Pass 1: scan top-of-file `import X;` lines, recursively expand. let i: u64 = 0u64; for (i < blen) { let j: u64 = i; for (j < blen) { if (bufp[j] == 10u8) { break; }; // '\n' j += 1u64; }; let idp: *u8; let idn: u64; idp, idn = scanuse(bufp + i, j - i); if (idp != nil) { let isdir: i32 = 0; let ipath: *u8 = locateimport(c.dirs, idp, idn, &isdir); if (ipath != nil) { if (isdir != 0) { expanddir(c, ipath); } else { expand(c, ipath); }; }; }; i = j + 1u64; }; os.writeall(c.out, bufp, blen); os.writeall(c.out, "\n".ptr, 1u64); }; // Scan `pathcs` for its first non-comment-non-blank line; if it // starts with `package ;` return the package name as a fresh // heap-allocated NUL-terminated *u8, else nil. Same shape as cstage // peek_package. fn peekpackage(pathcs: *u8) *u8 = { let fd: i32 = os.open(pathstr(pathcs), os.flag.RDONLY, 0i32); if (fd < 0) { return nil; }; let buf: []u8 = alloc([], 2048u64)!; buf.len = 2048; let n: i64 = os.read(fd, buf.ptr, 2048u64); os.close(fd); if (n <= 0i64) { return nil; }; let nu: u64 = n: u64; let p: u64 = 0u64; for (p < nu) { let q: u64 = p; for (q < nu) { if (buf[q] == 10u8) { break; }; // '\n' q += 1u64; }; let s: u64 = p; for (s < q) { if (buf[s] != 32u8) { if (buf[s] != 9u8) { break; }; }; s += 1u64; }; if (s < q) { if (s + 1u64 < q) { if (buf[s] == 47u8) { if (buf[s + 1u64] == 47u8) { p = q + 1u64; continue; }; }; }; if (s + 8u64 <= q) { if (buf[s] == 112u8) { // 'p' if (buf[s + 1u64] == 97u8) { // 'a' if (buf[s + 2u64] == 99u8) { // 'c' if (buf[s + 3u64] == 107u8) { // 'k' if (buf[s + 4u64] == 97u8) { // 'a' if (buf[s + 5u64] == 103u8) { // 'g' if (buf[s + 6u64] == 101u8) { // 'e' let sep: u8 = buf[s + 7u64]; if (sep == 32u8) { } else { if (sep != 9u8) { return nil; }; }; let t: u64 = s + 8u64; for (t < q) { if (buf[t] != 32u8) { if (buf[t] != 9u8) { break; }; }; t += 1u64; }; let start: u64 = t; for (t < q) { let ch: u8 = buf[t]; let isalpha: bool = false; if (ch >= 97u8) { if (ch <= 122u8) { isalpha = true; }; }; if (ch >= 65u8) { if (ch <= 90u8) { isalpha = true; }; }; if (ch >= 48u8) { if (ch <= 57u8) { isalpha = true; }; }; if (ch == 95u8) { isalpha = true; }; if (!isalpha) { break; }; t += 1u64; }; let plen: u64 = t - start; if (plen == 0u64) { return nil; }; let r: []u8 = alloc([], plen + 1u64)!; let k: u64 = 0u64; for (k < plen) { r[k] = buf[start + k]; k += 1u64; }; r[plen] = 0u8; return r.ptr; }; }; }; }; }; }; }; }; return nil; }; p = q + 1u64; }; return nil; }; // Strict-same-package error helper. Bundled here per task #22 // brief — failure mode is dir-enum's own. fn strictpkgmismatch(file: *u8, pkg: *u8, dirpkg: *u8, dirpath: *u8) void = { os.write(2, "ww: ".ptr, 4u64); os.write(2, file, cstrlen(file)); os.write(2, ": package ".ptr, 10u64); os.write(2, pkg, cstrlen(pkg)); os.write(2, " differs from ".ptr, 14u64); os.write(2, dirpkg, cstrlen(dirpkg)); os.write(2, " in same module dir ".ptr, 20u64); os.write(2, dirpath, cstrlen(dirpath)); os.write(2, "\n".ptr, 1u64); os.exit(1); }; // expanddir — enumerate /*.ww (skip *test.ww and // *.combined.ww), byte-sort, recurse into each. Mirrors // ref/hare/hare/module/srcs.ha:183 `_findsrcs` minus tag handling. // The visited set keys on concrete file paths so multi-file modules // are pulled once. Strict-same-package: all enumerated files must // declare the same `package ;` (task #23 subset; failure // mode native to dir-enum). fn expanddir(c: *expctx, dirpath: *u8) void = { let names: **u8; let n: i32; names, n = enumeratedir(dirpath); let dlen: u64 = cstrlen(dirpath); let dirpkg: *u8 = nil; let i: i32 = 0; for (i < n) { let nlen: u64 = cstrlen(names[i]); let fp: []u8 = alloc([], dlen + 1u64 + nlen + 1u64)!; let k: u64 = 0u64; for (k < dlen) { fp[k] = dirpath[k]; k += 1u64; }; fp[dlen] = 47u8; // '/' k = 0u64; for (k < nlen) { fp[dlen + 1u64 + k] = names[i][k]; k += 1u64; }; fp[dlen + 1u64 + nlen] = 0u8; let pkg: *u8 = peekpackage(fp.ptr); if (pkg != nil) { if (dirpkg == nil) { dirpkg = pkg; } else { if (!cstreq(dirpkg, pkg)) { strictpkgmismatch(fp.ptr, pkg, dirpkg, dirpath); }; }; }; expand(c, fp.ptr); i += 1; }; }; // ---- Build pipeline --------------------------------------------------- // Strip the trailing ".ww" off `src` (a NUL-terminated path) into // `stem`, NUL-terminated. If there's no .ww, the stem is the whole // path. fn makestem(stem: *u8, src: *u8) void = { let n: u64 = cstrlen(src); let stop: u64 = n; if (n >= 3u64) { if (src[n - 3u64] == 46u8) { // '.' if (src[n - 2u64] == 119u8) { // 'w' if (src[n - 1u64] == 119u8) { // 'w' stop = n - 3u64; }; }; }; }; let i: u64 = 0u64; for (i < stop) { stem[i] = src[i]; i += 1u64; }; stem[stop] = 0u8; }; // Append a literal suffix to `stem` (which already lives in a buffer). fn appendlit(stem: *u8, suffix: str) *u8 = { let buf: []u8 = alloc([], PATH_MAX)!; buf.len = PATH_MAX: i32; let off: u64 = cstrinto(buf.ptr, 0u64, stem); off = strinto(buf.ptr, off, suffix); cstrseal(buf.ptr, off); return buf.ptr; }; // linker flags bundled as a struct so buildone stays at the wwstage // w6c's 6-argument calling-convention limit. type lflags = struct { libdirs: **u8, nlibdirs: i32, libs: **u8, nlibs: i32, }; // buildone — compile `src` (file or directory) into the executable // named `out`. // selfdir: NUL-terminated dir containing this driver and the // wwstage tools (w6c_ww/w6a_ww/w6l_ww) // src: NUL-terminated entry path (file or directory). // entryisdir: non-zero when src is a module directory. // out: NUL-terminated desired output path // incs: NUL-terminated colon-list of -I dirs (may be empty) // lf: extra linker flags (-L, -l); may be nil // // The ww-side driver shells to the ww-side tools so a `ww_ww build` // touches no C-built code at runtime. The C `ww` driver in cmd/ww/ // still drives the C-built w6c/w6a/w6l. Test 993 pins the two // pipelines to byte-identical output on a corpus. fn buildone(selfdir: *u8, src: *u8, entryisdir: i32, out: *u8, incs: *u8, lf: *lflags) i32 = { let c6: *u8 = joinpathlit(selfdir, "w6c_ww"); let a6: *u8 = joinpathlit(selfdir, "w6a_ww"); let l6: *u8 = joinpathlit(selfdir, "w6l_ww"); // Default lib search path: /../../lib let dotdotlib: []u8 = alloc([], PATH_MAX)!; dotdotlib.len = PATH_MAX: i32; { let off: u64 = cstrinto(dotdotlib.ptr, 0u64, selfdir); off = strinto(dotdotlib.ptr, off, "/../../lib"); cstrseal(dotdotlib.ptr, off); }; // Compute the source directory. For a file entry: bytes of `src` // up to the last '/' (or "." when src has no '/'). For a dir // entry: the dir itself (less trailing slashes). Hare's CWD-first // convention assumes you're running from the module dir; our // wrappers don't cd, so dirname(src) stands in as the closest // analog. Source-dir wins ties over the system path (cc -I.). let srcd: []u8 = alloc([], PATH_MAX)!; srcd.len = PATH_MAX: i32; if (entryisdir != 0) { let slen: u64 = cstrlen(src); let k: u64 = 0u64; for (k < slen) { srcd[k] = src[k]; k += 1u64; }; for (slen > 1u64) { if (srcd[slen - 1u64] != 47u8) { break; }; slen -= 1u64; }; srcd[slen] = 0u8; } else { let slen: u64 = cstrlen(src); let last: u64 = slen; let found: bool = false; let i: u64 = slen; for (i > 0u64) { i -= 1u64; if (src[i] == 47u8) { // '/' last = i; found = true; i = 0u64; }; }; if (found) { let k: u64 = 0u64; for (k < last) { srcd[k] = src[k]; k += 1u64; }; srcd[last] = 0u8; } else { srcd[0] = 46u8; // '.' srcd[1] = 0u8; }; }; // Compose searchpath: srcd + ':' + incs + ':' + dotdotlib. let searchpath: []u8 = alloc([], PATH_MAX * 3u64)!; searchpath.len = (PATH_MAX * 3u64): i32; { let off: u64 = cstrinto(searchpath.ptr, 0u64, srcd.ptr); off = byteinto(searchpath.ptr, off, 58u8); // ':' if (incs[0u64] != 0u8) { off = cstrinto(searchpath.ptr, off, incs); off = byteinto(searchpath.ptr, off, 58u8); // ':' }; off = cstrinto(searchpath.ptr, off, dotdotlib.ptr); cstrseal(searchpath.ptr, off); }; // Stem for .s/.o/.combined.ww side files. Dir entry: /; // file entry: src stripped of .ww. let stem: []u8 = alloc([], PATH_MAX)!; stem.len = PATH_MAX: i32; if (entryisdir != 0) { let dlen: u64 = cstrlen(srcd.ptr); let bo: u64 = basenameoff(srcd.ptr, dlen); let off: u64 = cstrinto(stem.ptr, 0u64, srcd.ptr); stem[off] = 47u8; off += 1u64; // '/' let i: u64 = bo; for (i < dlen) { stem[off] = srcd[i]; off += 1u64; i += 1u64; }; cstrseal(stem.ptr, off); } else { makestem(stem.ptr, src); }; let asmf: *u8 = appendlit(stem.ptr, ".s"); let objf: *u8 = appendlit(stem.ptr, ".o"); let combined: *u8 = appendlit(stem.ptr, ".combined.ww"); // libwwrt.a path: /../lib/libwwrt.a let libwwrt: []u8 = alloc([], PATH_MAX)!; libwwrt.len = PATH_MAX: i32; { let off: u64 = cstrinto(libwwrt.ptr, 0u64, selfdir); off = strinto(libwwrt.ptr, off, "/../lib/libwwrt.a"); cstrseal(libwwrt.ptr, off); }; // Step 1: expand imports into the combined file. Dir entry → // enumerate the module dir; file entry → start at the file. let cf: i32 = os.open(pathstr(combined), os.flag.WRONLY | os.flag.CREATE | os.flag.TRUNC, 420i32); // 0o644 if (cf < 0) { os.write(2, "ww: cannot open combined\n".ptr, 25u64); return 1; }; { let c: expctx; c.out = cf; c.dirs = searchpath.ptr; c.visit = nil; if (entryisdir != 0) { expanddir(&c, srcd.ptr); } else { expand(&c, src); }; }; os.close(cf); // Step 2: w6c -o .s .combined.ww { let argv: []*u8 = alloc([], 5u64)!; argv.len = 5; argv[0] = "w6c\0".ptr; argv[1] = "-o\0".ptr; argv[2] = asmf; argv[3] = combined; argv[4] = nil; if (procrun(c6, argv.ptr) != 0) { os.write(2, "ww: w6c failed\n".ptr, 15u64); return 1; }; }; // Step 3: w6a -o .o .s { let argv: []*u8 = alloc([], 5u64)!; argv.len = 5; argv[0] = "w6a\0".ptr; argv[1] = "-o\0".ptr; argv[2] = objf; argv[3] = asmf; argv[4] = nil; if (procrun(a6, argv.ptr) != 0) { os.write(2, "ww: w6a failed\n".ptr, 15u64); return 1; }; }; // Step 4: w6l -o .o libwwrt.a [-L...] [-l...] { let nldirs: i32 = 0; let nllibs: i32 = 0; let ldirs: **u8 = nil; let llibs: **u8 = nil; if (lf != nil) { nldirs = lf.nlibdirs; nllibs = lf.nlibs; ldirs = lf.libdirs; llibs = lf.libs; }; // argv slots: 5 fixed (w6l, -o, out, objf, libwwrt) // + 2 * nlibdirs (-L, dir) // + 2 * nlibs (-l, name) // + 1 nil terminator. let total: i32 = 5 + 2 * nldirs + 2 * nllibs + 1; let argv: []*u8 = alloc([], total: u64)!; argv.len = total; argv[0] = "w6l\0".ptr; argv[1] = "-o\0".ptr; argv[2] = out; argv[3] = objf; argv[4] = libwwrt.ptr; let pos: i32 = 5; let k: i32 = 0; for (k < nldirs) { argv[pos] = "-L\0".ptr; argv[pos + 1] = ldirs[k]; pos += 2; k += 1; }; k = 0; for (k < nllibs) { argv[pos] = "-l\0".ptr; argv[pos + 1] = llibs[k]; pos += 2; k += 1; }; argv[pos] = nil; if (procrun(l6, argv.ptr) != 0) { os.write(2, "ww: w6l failed\n".ptr, 15u64); return 1; }; }; return 0; }; // ---- Module-by-name resolution ---------------------------------------- // // Mirrors cmd/ww/main.c:resolvemodule. Maps a name like "foo", "lib/foo", // "foo.ww", or "." to a concrete .ww file path: // 1. literal .ww that exists → use as-is // 2. "." → /.ww → that, if it exists // 3. /.ww → that, if it exists // 4. walk search path (cwd:incs:/../../lib): // /.ww or //.ww fn cstrendswithlit(p: *u8, lit: str) bool = { let plen: u64 = cstrlen(p); let slen: u64 = lit.len: u64; if (plen < slen) { return false; }; let off: u64 = plen - slen; let i: i32 = 0; for (i < lit.len) { let iu: u64 = i: u64; if (p[off + iu] != lit[i]) { return false; }; i += 1; }; return true; }; // basenameoff — return the offset of the last path segment within `p` // (i.e. one past the final '/'). Returns 0 if there's no slash. fn basenameoff(p: *u8, plen: u64) u64 = { let start: u64 = 0u64; let i: u64 = 0u64; for (i < plen) { if (p[i] == 47u8) { start = i + 1u64; }; // '/' i += 1u64; }; return start; }; // arenadupcstr — copy `plen` bytes from `src` into a fresh NUL-sealed // heap buffer. fn arenadupcstr(src: *u8, plen: u64) *u8 = { let buf: []u8 = alloc([], plen + 1u64)!; let i: u64 = 0u64; for (i < plen) { buf[i] = src[i]; i += 1u64; }; buf[plen] = 0u8; return buf.ptr; }; // buildsearchpath — compose the colon-separated lookup path used by // resolvemodule's case (4). Order: "." : : /../../lib fn buildsearchpath(selfdir: *u8, incs: *u8) *u8 = { let buf: []u8 = alloc([], PATH_MAX * 2u64)!; let off: u64 = 0u64; buf[off] = 46u8; off += 1u64; // '.' if (incs != nil) { if (incs[0u64] != 0u8) { buf[off] = 58u8; off += 1u64; // ':' off = cstrinto(buf.ptr, off, incs); }; }; buf[off] = 58u8; off += 1u64; off = cstrinto(buf.ptr, off, selfdir); off = strinto(buf.ptr, off, "/../../lib"); cstrseal(buf.ptr, off); return buf.ptr; }; // resolvemodule — map a name like "foo", "lib/foo", "foo.ww", or // "." to a concrete entry path. Sets *isdir when the entry is a // module directory (caller will dir-enumerate). fn resolvemodule(selfdir: *u8, name: *u8, incs: *u8, isdir: *i32) *u8 = { let nlen: u64 = cstrlen(name); // (1) Literal file that exists → use as-is. if (cstrendswithlit(name, ".ww")) { if (os.access(pathstr(name), 0i32) == 0) { *isdir = 0; return arenadupcstr(name, nlen); }; }; // (2) Existing path → use as-is, dir vs file via stat. let fi: os.filestat; let sr: (void | os.oserror) = os.stat(&fi, pathstr(name)); let found: bool = false; let foundisdir: i32 = 0; match (sr) { case void => { let t: u32 = (fi.mode: u32) & 61440u32; // S_IFMT if (t == os.mode.DIR: u32) { foundisdir = 1; }; found = true; }; case let e: os.oserror => void; }; if (found) { *isdir = foundisdir; return arenadupcstr(name, nlen); }; // (3) Search-path lookup with dot-to-slash path translation. let search: *u8 = buildsearchpath(selfdir, incs); return locateimport(search, name, nlen, isdir); }; // ---- Subcommand handlers ---------------------------------------------- fn writeusage(fd: i32) void = { let s: str = "usage: ww [-V] [args...]\n -V print version and exit\n build [path] compile module to a static binary (path defaults to cwd)\n run [path] ... build then exec, passing extra args to the program\n test [path] build and run *_test.ww in the module (path defaults to cwd)\n version print version and exit\n\n path forms:\n foo.ww literal file\n foo search cwd, -I dirs, then $WW_LIB-equiv for foo.ww or foo/foo.ww\n lib/foo directory: build lib/foo/foo.ww\n . build the cwd's .ww\n"; os.write(fd, s.ptr, s.len: u64); }; fn doversion() i32 = { os.write(1, "ww 0.0\n".ptr, 7u64); return 0; }; // Compute the basename of src (without trailing ".ww") into a fresh // buffer. Used as the default output path for `ww build`. fn defaultoutpath(src: *u8) *u8 = { let n: u64 = cstrlen(src); let start: u64 = 0u64; let i: u64 = 0u64; for (i < n) { if (src[i] == 47u8) { start = i + 1u64; }; // '/' i += 1u64; }; let out: []u8 = alloc([], PATH_MAX)!; out.len = PATH_MAX: i32; let off: u64 = 0u64; let j: u64 = start; for (j < n) { out[off] = src[j]; off += 1u64; j += 1u64; }; // Strip ".ww" if present. if (off >= 3u64) { if (out[off - 3u64] == 46u8) { if (out[off - 2u64] == 119u8) { if (out[off - 1u64] == 119u8) { off -= 3u64; }; }; }; }; cstrseal(out.ptr, off); return out.ptr; }; fn dobuild(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = { let src: *u8 = nil; let incs: []u8 = alloc([], PATH_MAX * 2u64)!; incs.len = (PATH_MAX * 2u64): i32; let incoff: u64 = 0u64; cstrseal(incs.ptr, 0u64); let maxlflags: i32 = 32; let libdirs: []*u8 = alloc([], maxlflags: u64)!; libdirs.len = maxlflags; let nlibdirs: i32 = 0; let libs: []*u8 = alloc([], maxlflags: u64)!; libs.len = maxlflags; let nlibs: i32 = 0; let i: i32 = start; for (i < argc) { let p: *u8 = argv[i]; if (p[0u64] == 45u8) { // '-' if (p[1u64] == 73u8) { // '-I' let dir: *u8 = nil; if (p[2u64] != 0u8) { dir = p + 2u64; } else { if (i + 1 >= argc) { os.write(2, "ww build: -I needs an argument\n".ptr, 31u64); return 2; }; i += 1; dir = argv[i]; }; if (incoff > 0u64) { incs[incoff] = 58u8; // ':' incoff += 1u64; }; incoff = cstrinto(incs.ptr, incoff, dir); cstrseal(incs.ptr, incoff); } else { if (p[1u64] == 76u8) { // '-L' let dir: *u8 = nil; if (p[2u64] != 0u8) { dir = p + 2u64; } else { if (i + 1 >= argc) { os.write(2, "ww build: -L needs an argument\n".ptr, 31u64); return 2; }; i += 1; dir = argv[i]; }; if (nlibdirs >= maxlflags) { os.write(2, "ww build: too many -L\n".ptr, 22u64); return 2; }; libdirs[nlibdirs] = dir; nlibdirs += 1; } else { if (p[1u64] == 108u8) { // '-l' let nm: *u8 = nil; if (p[2u64] != 0u8) { nm = p + 2u64; } else { if (i + 1 >= argc) { os.write(2, "ww build: -l needs an argument\n".ptr, 31u64); return 2; }; i += 1; nm = argv[i]; }; if (nlibs >= maxlflags) { os.write(2, "ww build: too many -l\n".ptr, 22u64); return 2; }; libs[nlibs] = nm; nlibs += 1; } else { os.write(2, "ww build: unknown flag\n".ptr, 23u64); return 2; }; }; }; } else { if (src == nil) { src = p; }; }; i += 1; }; if (src == nil) { // default to cwd module let dot: [2]u8 = ['.': u8, 0u8]; src = &dot[0]; }; let isdir: i32 = 0; let resolved: *u8 = resolvemodule(selfdir, src, incs.ptr, &isdir); if (resolved == nil) { os.write(2, "ww build: cannot find module\n".ptr, 29u64); return 1; }; let out: *u8 = nil; if (isdir != 0) { let rlen: u64 = cstrlen(resolved); for (rlen > 1u64) { if (resolved[rlen - 1u64] != 47u8) { break; }; rlen -= 1u64; }; let bo: u64 = basenameoff(resolved, rlen); let outbuf: []u8 = alloc([], PATH_MAX)!; outbuf.len = PATH_MAX: i32; out = outbuf.ptr; let i: u64 = bo; let off: u64 = 0u64; for (i < rlen) { out[off] = resolved[i]; off += 1u64; i += 1u64; }; cstrseal(out, off); } else { out = defaultoutpath(resolved); }; let lf: lflags; lf.libdirs = libdirs.ptr; lf.nlibdirs = nlibdirs; lf.libs = libs.ptr; lf.nlibs = nlibs; return buildone(selfdir, resolved, isdir, out, incs.ptr, &lf); }; // Format the scratch path /tmp/ww_run_ into buf. Returns NUL- // terminated buf. Pid is folded in decimal manually since we don't // import strconv. fn makeruntmp(buf: *u8) void = { let off: u64 = 0u64; off = strinto(buf, off, "/tmp/ww_run_"); let pid: i32 = os.getpid(); // itoa for non-negative pid let dig: [16]u8; let n: i32 = 0; if (pid <= 0) { dig[n] = 48u8; // '0' n += 1; } else { let v: i32 = pid; for (v > 0) { dig[n] = ((v % 10) + 48): u8; n += 1; v = v / 10; }; }; let k: i32 = n - 1; for (k >= 0) { buf[off] = dig[k]; off += 1u64; k -= 1; }; cstrseal(buf, off); }; fn dorun(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = { let src: *u8 = nil; let passstart: i32 = -1; // first argv idx to pass through to program let incs: []u8 = alloc([], PATH_MAX * 2u64)!; incs.len = (PATH_MAX * 2u64): i32; let incoff: u64 = 0u64; cstrseal(incs.ptr, 0u64); let maxlflags: i32 = 32; let libdirs: []*u8 = alloc([], maxlflags: u64)!; libdirs.len = maxlflags; let nlibdirs: i32 = 0; let libs: []*u8 = alloc([], maxlflags: u64)!; libs.len = maxlflags; let nlibs: i32 = 0; let i: i32 = start; for (i < argc) { if (passstart >= 0) { i = argc; } // stop, leave rest for exec else { let p: *u8 = argv[i]; if (p[0u64] == 45u8) { if (p[1u64] == 73u8) { let dir: *u8 = nil; if (p[2u64] != 0u8) { dir = p + 2u64; } else { if (i + 1 >= argc) { os.write(2, "ww run: -I needs an argument\n".ptr, 29u64); return 2; }; i += 1; dir = argv[i]; }; if (incoff > 0u64) { incs[incoff] = 58u8; incoff += 1u64; }; incoff = cstrinto(incs.ptr, incoff, dir); cstrseal(incs.ptr, incoff); } else { if (p[1u64] == 76u8) { let dir: *u8 = nil; if (p[2u64] != 0u8) { dir = p + 2u64; } else { if (i + 1 >= argc) { os.write(2, "ww run: -L needs an argument\n".ptr, 29u64); return 2; }; i += 1; dir = argv[i]; }; if (nlibdirs >= maxlflags) { os.write(2, "ww run: too many -L\n".ptr, 20u64); return 2; }; libdirs[nlibdirs] = dir; nlibdirs += 1; } else { if (p[1u64] == 108u8) { let nm: *u8 = nil; if (p[2u64] != 0u8) { nm = p + 2u64; } else { if (i + 1 >= argc) { os.write(2, "ww run: -l needs an argument\n".ptr, 29u64); return 2; }; i += 1; nm = argv[i]; }; if (nlibs >= maxlflags) { os.write(2, "ww run: too many -l\n".ptr, 20u64); return 2; }; libs[nlibs] = nm; nlibs += 1; } else { os.write(2, "ww run: unknown flag\n".ptr, 21u64); return 2; }; }; }; i += 1; } else { if (src == nil) { src = p; i += 1; } else { passstart = i; // remaining args go to the program }; }; }; }; if (src == nil) { let dot: [2]u8 = ['.': u8, 0u8]; src = &dot[0]; }; let isdir: i32 = 0; let resolved: *u8 = resolvemodule(selfdir, src, incs.ptr, &isdir); if (resolved == nil) { os.write(2, "ww run: cannot find module\n".ptr, 27u64); return 1; }; let tmp: []u8 = alloc([], PATH_MAX)!; tmp.len = PATH_MAX: i32; makeruntmp(tmp.ptr); let lf: lflags; lf.libdirs = libdirs.ptr; lf.nlibdirs = nlibdirs; lf.libs = libs.ptr; lf.nlibs = nlibs; if (buildone(selfdir, resolved, isdir, tmp.ptr, incs.ptr, &lf) != 0) { os.remove(pathstr(tmp.ptr)); return 1; }; // exec with [tmp, argv[passstart..argc), nil] let nextra: i32 = 0; if (passstart >= 0) { nextra = argc - passstart; }; let total: i32 = nextra + 2; let execargv: []*u8 = alloc([], total: u64)!; execargv.len = total; execargv[0] = tmp.ptr; let k: i32 = 0; for (k < nextra) { execargv[k + 1] = argv[passstart + k]; k += 1; }; execargv[nextra + 1] = nil; let rc: i32 = procrun(tmp.ptr, execargv.ptr); os.remove(pathstr(tmp.ptr)); return rc; }; // ---- ww test ---------------------------------------------------------- // // Mirrors cmd/ww/main.c:dotest. Two modes: // single-file: build+run a literal *.ww file, return its exit code // directory: open the dir, getdents64, build+run each *_test.ww, // report ok/FAIL per file, return 0 iff all pass. fn runsingletest(selfdir: *u8, src: *u8) i32 = { let tmp: []u8 = alloc([], PATH_MAX)!; tmp.len = PATH_MAX: i32; makeruntmp(tmp.ptr); if (buildone(selfdir, src, 0, tmp.ptr, "\0".ptr, nil) != 0) { os.remove(pathstr(tmp.ptr)); return 1; }; let execargv: []*u8 = alloc([], 2u64)!; execargv.len = 2; execargv[0] = tmp.ptr; execargv[1] = nil; let rc: i32 = procrun(tmp.ptr, execargv.ptr); os.remove(pathstr(tmp.ptr)); return rc; }; fn rundirtests(selfdir: *u8, dir: *u8) i32 = { let fd: i32 = os.open(pathstr(dir), os.flag.RDONLY, 0i32); if (fd < 0) { os.write(2, "ww test: cannot open directory\n".ptr, 31u64); return 1; }; let pass: i32 = 0; let fail: i32 = 0; let buf: []u8 = alloc([], 8192u64)!; buf.len = 8192; let dirlen: u64 = cstrlen(dir); let n: i64 = os.getdents64(fd, buf.ptr, 8192u64); for (n > 0i64) { let off: u64 = 0u64; let nu: u64 = n: u64; for (off < nu) { // d_reclen at offset+16 (u16 LE), d_name at offset+19 (cstr) let blo: u64 = (buf[off + 16u64]): u64; let bhi: u64 = (buf[off + 17u64]): u64; let reclen: u64 = blo + (bhi * 256u64); let name: *u8 = buf.ptr + off + 19u64; if (cstrendswithlit(name, "_test.ww")) { let nlen: u64 = cstrlen(name); // path = / let path: []u8 = alloc([], PATH_MAX)!; path.len = PATH_MAX: i32; let poff: u64 = cstrinto(path.ptr, 0u64, dir); path[poff] = 47u8; poff += 1u64; let i: u64 = 0u64; for (i < nlen) { path[poff + i] = name[i]; i += 1u64; }; poff += nlen; cstrseal(path.ptr, poff); // incs = so test files can `use ` siblings let tincs: []u8 = alloc([], PATH_MAX)!; tincs.len = PATH_MAX: i32; let ic: u64 = cstrinto(tincs.ptr, 0u64, dir); cstrseal(tincs.ptr, ic); let tmp: []u8 = alloc([], PATH_MAX)!; tmp.len = PATH_MAX: i32; makeruntmp(tmp.ptr); let bres: i32 = buildone(selfdir, path.ptr, 0, tmp.ptr, tincs.ptr, nil); if (bres != 0) { fail += 1; os.write(2, "FAIL ".ptr, 5u64); os.write(2, name, nlen); os.write(2, " (build)\n".ptr, 9u64); } else { let execargv: []*u8 = alloc([], 2u64)!; execargv.len = 2; execargv[0] = tmp.ptr; execargv[1] = nil; let rc: i32 = procrun(tmp.ptr, execargv.ptr); if (rc == 0) { pass += 1; os.write(1, "ok ".ptr, 5u64); os.write(1, name, nlen); os.write(1, "\n".ptr, 1u64); } else { fail += 1; os.write(2, "FAIL ".ptr, 5u64); os.write(2, name, nlen); os.write(2, "\n".ptr, 1u64); }; }; os.remove(pathstr(tmp.ptr)); }; off += reclen; }; n = os.getdents64(fd, buf.ptr, 8192u64); }; os.close(fd); if (fail == 0) { return 0; }; return 1; }; fn dotest(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = { let target: *u8; if (start >= argc) { let dot: [2]u8 = ['.': u8, 0u8]; target = &dot[0]; } else { target = argv[start]; }; // single-file mode: literal *.ww that exists if (cstrendswithlit(target, ".ww")) { if (os.access(pathstr(target), 0i32) == 0) { return runsingletest(selfdir, target); }; }; // otherwise treat target as a directory; enumerate *_test.ww return rundirtests(selfdir, target); }; // ---- Entry ------------------------------------------------------------- export fn main(argc: i32, argv: **u8) i32 = { if (argc < 1) { writeusage(2); return 2; }; // selfdir = dirname(argv[0]) let selfdir: []u8 = alloc([], PATH_MAX)!; selfdir.len = PATH_MAX: i32; selfdirinto(selfdir.ptr, PATH_MAX, argv[0]); if (argc < 2) { writeusage(2); return 2; }; let cmd: *u8 = argv[1]; if (cstreqlit(cmd, "-V")) { return doversion(); }; if (cstreqlit(cmd, "version")) { return doversion(); }; if (cstreqlit(cmd, "-h")) { writeusage(1); return 0; }; if (cstreqlit(cmd, "--help")) { writeusage(1); return 0; }; if (cstreqlit(cmd, "build")) { return dobuild(selfdir.ptr, argv, argc, 2); }; if (cstreqlit(cmd, "run")) { return dorun(selfdir.ptr, argv, argc, 2); }; if (cstreqlit(cmd, "test")) { return dotest(selfdir.ptr, argv, argc, 2); }; os.write(2, "ww: unknown subcommand\n".ptr, 23u64); writeusage(2); return 2; };