diff --git a/examples/0716-modules-cli-argv.sx b/examples/0716-modules-cli-argv.sx new file mode 100644 index 0000000..5e58fa7 --- /dev/null +++ b/examples/0716-modules-cli-argv.sx @@ -0,0 +1,31 @@ +// Real OS-argv accessor from `modules/std/cli.sx` (#foreign _NSGetArgv). +// +// Only DETERMINISTIC structural invariants are asserted — the actual arg +// contents depend on how the test is invoked (under `sx run` the process +// argv is the interpreter's: ["sx", "run", ""]), so we never +// pin exact strings: +// - argc >= 1 (every process has argv[0]) +// - argv[0] is non-empty (the executable path) +// - os_argc() agrees with the filled slice length (no truncation) +// +// `buf` is a stack `[64]string`; `os_args` fills it with zero-copy views +// over the C runtime's argv block — no heap, no per-arg allocation. + +#import "modules/std.sx"; +#import "modules/std/cli.sx"; + +main :: () { + buf : [64]string = ---; + args := os_args(buf[0..64]); + + if args.len >= 1 { print("argc>=1: ok\n"); } + else { print("argc>=1: FAIL ({})\n", args.len); } + + if args.len >= 1 { + if args[0].len > 0 { print("arg0-nonempty: ok\n"); } + else { print("arg0-nonempty: FAIL\n"); } + } + + if os_argc() == args.len { print("argc-consistent: ok\n"); } + else { print("argc-consistent: FAIL (os_argc={} len={})\n", os_argc(), args.len); } +} diff --git a/examples/expected/0716-modules-cli-argv.exit b/examples/expected/0716-modules-cli-argv.exit new file mode 100644 index 0000000..573541a --- /dev/null +++ b/examples/expected/0716-modules-cli-argv.exit @@ -0,0 +1 @@ +0 diff --git a/examples/expected/0716-modules-cli-argv.stderr b/examples/expected/0716-modules-cli-argv.stderr new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/examples/expected/0716-modules-cli-argv.stderr @@ -0,0 +1 @@ + diff --git a/examples/expected/0716-modules-cli-argv.stdout b/examples/expected/0716-modules-cli-argv.stdout new file mode 100644 index 0000000..272f8da --- /dev/null +++ b/examples/expected/0716-modules-cli-argv.stdout @@ -0,0 +1,3 @@ +argc>=1: ok +arg0-nonempty: ok +argc-consistent: ok diff --git a/library/modules/std/cli.sx b/library/modules/std/cli.sx new file mode 100644 index 0000000..304da5f --- /dev/null +++ b/library/modules/std/cli.sx @@ -0,0 +1,94 @@ +// ===================================================================== +// cli.sx — process command-line argument accessor (macOS), pure sx. +// +// `os_args(buf)` returns the real OS-level process argv as a `[]string`, +// each element a zero-copy VIEW over the C runtime's argv memory. The +// caller provides the `buf: []string` backing (typically a stack array); +// every element points straight into the process's own argv block, which +// lives for the whole process lifetime, so the views never dangle. +// +// Zero heap, zero per-arg allocation: nothing here touches +// `context.allocator`. The returned slice header is a by-value stack +// return whose `.ptr` is the caller's `buf` and whose elements are views +// into C argv — ladder rungs "by-value", "view", and "caller buffer". +// +// `sx run ...` reality — READ THIS BEFORE CONSUMING: +// Under `sx run`, the process argv is the sx INTERPRETER's argv, e.g. +// ["sx", "run", "prog.sx", ...] — NOT a program's "own" logical args. +// (The interpreter also consumes trailing tokens as additional source +// files, so they don't reach the program as plain args anyway.) This +// accessor reports the real process argv truthfully and does NOT strip +// the interpreter prefix. Mapping process argv -> a program's logical +// args (dropping the `sx run prog.sx` prefix, or via an sx-run +// convention) is a CONSUMER concern handled later (distribution P3.1), +// NOT here. +// +// 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. +// ===================================================================== + +#import "modules/std.sx"; +#import "modules/compiler.sx"; + +libc :: #library "c"; + +// macOS C-runtime argv/argc accessors (crt_externs.h): +// extern char ***_NSGetArgv(void); extern int *_NSGetArgc(void); +// Each returns a pointer to the runtime's slot; dereference once for the +// `char**` / `int` the process was launched with. Declared as `*s64` / +// `*s32` since on 64-bit a `char***` is just a pointer to a pointer-sized +// slot. +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"; + +// Number of process arguments (argc). >= 1 for any normally-launched +// process, since argv[0] is the executable path. +os_argc :: () -> s64 { + inline if OS == { + 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); + } + } +} + +// Fill `buf` with VIEWS over the process argv and return the filled prefix +// `buf[0 .. min(argc, buf.len)]`. Zero heap, zero copy: each element's +// bytes live in the C runtime's argv block, valid for the whole process. +// +// The caller owns `buf` (typically a stack `[N]string`); the returned +// slice points into it and is valid for as long as `buf` is in scope. If +// the process has more than `buf.len` arguments only the first `buf.len` +// are returned — call `os_argc()` first and size `buf` accordingly when an +// exact count matters. +os_args :: (buf: []string) -> []string { + inline if OS == { + case .macos: { + argc := cast(s64) ns_get_argc().*; + argv : [*]s64 = xx ns_get_argv().*; + n := if argc > buf.len then buf.len else argc; + i := 0; + while i < n { + cstr : [*]u8 = xx argv[i]; + len := 0; + while cstr[len] != 0 { len += 1; } + buf[i] = string.{ ptr = cstr, len = len }; + i += 1; + } + result : []string = ---; + result.ptr = buf.ptr; + result.len = n; + return result; + } + else: { + out("std.cli: unsupported platform — only macOS is implemented (needs _NSGetArgv/_NSGetArgc).\n"); + cli_bail_exit(70); + } + } +}