// std.process — access to the program's command-line arguments. // // `args()` returns an `Args` cursor; `next(cursor)` yields each argument // as a `[]u8` (argv[0] is the program path, by convention) until done: // // var it: Args = args(); // match (next(it)) { // Option([]u8).Some(arg) { /* arg is a []u8 */ } // Option([]u8).None { /* exhausted */ } // } // // The cursor API is function-style (`next(it)`, `count(it)`) rather than // method-style: Jam can't currently dispatch a method on a local var // whose type comes from another module. // // The capture mechanism is chosen at comptime via `@isDarwin()`, so the // inactive branch is dead-code-eliminated — no `_NSGetArgv` reference // survives on a non-Darwin target, where that symbol doesn't exist: // * Darwin: libSystem's _NSGetArgc / _NSGetArgv (live pointers). // * Linux: /proc/self/cmdline — argv joined by NULs. The buffer and // argv table are malloc'd once and intentionally leaked, // since they must outlive every `args()`/`next()` call. const { Option } = import("option"); // `pub extern` so the declarations are visible when these functions are // codegen'd in an importer's compilation context (mirrors std.collections). pub extern fn strlen(s: *const[] u8) u64; pub extern fn _NSGetArgc() u64; // C: int* (Darwin / libSystem) pub extern fn _NSGetArgv() u64; // C: char*** pub extern fn fopen(path: *const[] u8, mode: *const[] u8) *mut[] u8; pub extern fn fread(buf: *mut[] u8, size: u64, n: u64, f: *mut[] u8) u64; pub extern fn fclose(f: *mut[] u8) i32; pub extern fn malloc(size: u64) *mut[] u8; pub extern fn free(ptr: *mut[] u8); // Terminate the process with status `code`, never returning. A noreturn // binding to libc exit(3) — the same model as Rust's // `std::process::exit(code: i32) -> !` and Zig's `std.os.exit`, both of // which bottom out in the C `exit` when linked against libc. The status // is an i32 to match C's `exit(int)`; POSIX masks it to the low 8 bits. // `noreturn` lets callers treat the call site as diverging (no return / // dead code after it required). pub extern fn exit(code: i32) noreturn; // Abnormal termination: raise SIGABRT (core dump / debugger trap) and // never return. A noreturn binding to libc abort(3) — mirrors Rust's // `std::process::abort()` and Zig's `std.os.abort` (both noreturn // libc-abort bindings; Zig raises SIGABRT by hand when not linked // against libc). Use for unrecoverable invariant violations where no // meaningful exit status exists; prefer `exit(code)` for normal failure. pub extern fn abort() noreturn; // Cursor over argv. `table` points at the argv array (each slot an // 8-byte `char*`); `index` is the next slot to yield; `n` is argc. pub const Args = struct { table: *mut[] u64, index: u32, n: u32, }; fn emptyArgs() Args { var zero: u64 = 0; var none: *mut[] u64 = zero as *mut[] u64; return Args { table: none, index: 0, n: 0 }; } // Parse /proc/self/cmdline into a (table, argc) pair. The kernel writes // the args joined by NULs (and usually ends with a final NUL too). We // read into a growable malloc'd buffer, count NULs to determine argc, // then allocate a u64 table of pointers — table[0] points at the start // of the buffer; each subsequent slot at the byte after the previous // NUL. Both buffer and table are leaked (process-lifetime). fn linuxArgs() Args { var path: []u8 = "/proc/self/cmdline"; var mode: []u8 = "rb"; var f: *mut[] u8 = fopen(path.ptr, mode.ptr); if ((f as u64) == 0) { return emptyArgs(); } var cap: u64 = 4096; var buf: *mut[] u8 = malloc(cap); var total: u64 = 0; loop { var room: u64 = cap - total; var dst: *mut[] u8 = (buf as u64 + total) as *mut[] u8; var got: u64 = fread(dst, 1, room, f); total = total + got; if (got < room) { break; } var newCap: u64 = cap * 2; var newBuf: *mut[] u8 = malloc(newCap); var j: u64 = 0; while (j < total) { newBuf[j] = buf[j]; j = j + 1; } free(buf); buf = newBuf; cap = newCap; } fclose(f); if (total == 0) { return emptyArgs(); } // Count NUL-terminated segments. If the kernel didn't emit a trailing // NUL (rare), the final unterminated chunk still counts as one arg. var argc: u32 = 0; var k: u64 = 0; while (k < total) { if (buf[k] == 0) { argc = argc + 1; } k = k + 1; } if (buf[total - 1] != 0) { argc = argc + 1; } if (argc == 0) { return emptyArgs(); } var table: *mut[] u64 = malloc((argc as u64) * 8) as *mut[] u64; table[0] = buf as u64; var slot: u32 = 1; var p: u64 = 0; while (p < total && slot < argc) { if (buf[p] == 0) { table[slot] = (buf as u64) + p + 1; slot = slot + 1; } p = p + 1; } return Args { table: table, index: 0, n: argc }; } pub fn args() Args { if (@isDarwin()) { // _NSGetArgc() -> int* : deref for argc. // _NSGetArgv() -> char***: deref once for the char** argv table. var argcPtr: *mut[] i32 = _NSGetArgc() as *mut[] i32; var argc: u32 = argcPtr[0] as u32; var pp: *mut[] u64 = _NSGetArgv() as *mut[] u64; // &argv var table: *mut[] u64 = pp[0] as *mut[] u64; // argv table return Args { table: table, index: 0, n: argc }; } if (@isLinux()) { return linuxArgs(); } return emptyArgs(); } // Next argument as a []u8 (borrows argv's memory — valid for the process // lifetime), advancing the cursor; None once every argument is consumed. pub fn next(it: mut Args) Option([]u8) { if (it.index >= it.n) { return Option([]u8).None(); } var cstr: *const[] u8 = it.table[it.index] as *const[] u8; it.index = it.index + 1; var s: []u8 = cstr[0..strlen(cstr)]; return Option([]u8).Some(s); } // Total number of arguments (argc), including argv[0]. pub fn count(it: Args) u32 { return it.n; }