ww test: @test name-filter via fnmatch (both stages)
`ww test <file> <pattern>` runs only the @test fns whose names match the fnmatch glob; no pattern runs all (byte-for-byte the pre-filter path); zero matches prints "No tests run" and exits 0 (Hare ground truth ref/hare/test/+test.ha:114-117). A pattern in directory mode is rejected "ww test: pattern needs a single test file" (rc 2), identical wording in both twins (cmd/ww/main.c do_test + selfhost/cmd/ww/main.ww dotest). Mechanism (a): rt/start.s stashes argc/argv into rt_argc/rt_argv getters (rt_envp twin shape, -T synth untouched so 990-997 byte-id holds); lib/os.args() rebuilds the []str view, build-once-cached; lib/test/run.ww imports fnmatch and filters av[1..] (argv[0] is the binary path). The driver forwards the 2nd positional as argv[1] via fork/execv (cstage) / procrun (wwstage) so glob metachars aren't shell-expanded. os.args() is the first `alloc`-caller in the base os module, so os.ww now imports rt — the `alloc` builtin's malloc lowers to rt_malloc only when the rt binding is bundled (mirror lib/strings/strings.ww:30); without it a plain `ww build` of any os-importing program links bare libc `malloc` (undefined). os is bundled by ~every program, so this is load-bearing. The lib/test floor rises os-only -> os+fnmatch+ascii+strings in every -T build; the bundled `ascii` module vs a `@test fn ascii` collision that exposed is closed by the preceding #30 promote commit. 989_test_filter pins the full matrix on both twins byte-identically; 949 gains the dir-mode reject row. (#17)
This commit is contained in:
10
Makefile
10
Makefile
@@ -479,6 +479,7 @@ TESTS = $(BIN)/test_smoke $(BIN)/test_lex $(BIN)/test_parse $(BIN)/test_check \
|
||||
$(BIN)/test_path_run \
|
||||
$(BIN)/test_letshadow_run \
|
||||
$(BIN)/test_strglobeq_run \
|
||||
$(BIN)/test_test_filter \
|
||||
$(BIN)/test_strconv_int_run \
|
||||
$(BIN)/test_stof_run $(BIN)/test_ftos_run \
|
||||
$(BIN)/test_memio_run $(BIN)/test_temp_run $(BIN)/test_getopt_run \
|
||||
@@ -610,6 +611,15 @@ $(BIN)/test_driver_flagargs: test/wcc/949_driver_flagargs.c $(BIN)/ww \
|
||||
$(BIN)/ww_ww | $(BIN)
|
||||
$(CC) $(CFLAGS) -o $@ $<
|
||||
|
||||
# 989_test_filter drives `ww test <file> [pattern]` end-to-end on BOTH
|
||||
# driver twins, so it needs the full cstage + wwstage tool sets (ww_ww
|
||||
# shells to w6c_ww/w6a_ww/w6l_ww) plus libwwrt for the link. #17.
|
||||
$(BIN)/test_test_filter: test/wcc/989_test_filter.c $(BIN)/ww $(BIN)/ww_ww \
|
||||
$(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \
|
||||
$(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \
|
||||
$(LIB)/libwwrt.a | $(BIN)
|
||||
$(CC) $(CFLAGS) -o $@ $<
|
||||
|
||||
$(BIN)/test_let_global: test/wcc/630_let_global.c $(BIN)/ww $(BIN)/w6c \
|
||||
$(BIN)/w6a $(BIN)/w6l $(LIB)/libwwrt.a | $(BIN)
|
||||
$(CC) $(CFLAGS) -o $@ $<
|
||||
|
||||
@@ -54,6 +54,32 @@ run(const char *cmd)
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* run_test_bin — exec the built test binary with an optional name-filter
|
||||
* pattern as argv[1] (lib/test run() reads it via os.args). fork+execv
|
||||
* (not system()) so glob metacharacters in the pattern reach the binary
|
||||
* verbatim instead of being expanded by the shell. Mirrors the wwstage
|
||||
* twin (selfhost/cmd/ww/main.ww runsingletest, which always builds an
|
||||
* execargv for procrun). #17 fnmatch filter. */
|
||||
static int
|
||||
run_test_bin(const char *bin, const char *pattern)
|
||||
{
|
||||
pid_t pid = fork();
|
||||
if (pid < 0) { perror("ww: fork"); return -1; }
|
||||
if (pid == 0) {
|
||||
char *xargv[3];
|
||||
xargv[0] = (char *)bin;
|
||||
if (pattern) { xargv[1] = (char *)pattern; xargv[2] = NULL; }
|
||||
else { xargv[1] = NULL; }
|
||||
execv(bin, xargv);
|
||||
perror("ww: exec");
|
||||
_exit(127);
|
||||
}
|
||||
int status = 0;
|
||||
waitpid(pid, &status, 0);
|
||||
if (WIFEXITED(status)) return WEXITSTATUS(status);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Set of imported module paths, kept on the heap. Used to break
|
||||
* cycles in `use` resolution. Linear because typical imports are
|
||||
* a handful per build. */
|
||||
@@ -815,6 +841,10 @@ do_test(int argc, char **argv)
|
||||
* wwstage twin (selfhost/cmd/ww/main.ww dotest). */
|
||||
int compileonly = 0;
|
||||
char outstem[1024] = {0};
|
||||
/* #17: an optional second positional after the target is a fnmatch
|
||||
* name-filter pattern, forwarded to the test binary as argv[1]. Only
|
||||
* meaningful for a single test file/module — rejected in dir mode. */
|
||||
const char *pattern = NULL;
|
||||
for (int i = 0; i < argc; i++) {
|
||||
if (argv[i][0] == '-') {
|
||||
if (argv[i][1] == 'I') {
|
||||
@@ -849,6 +879,8 @@ do_test(int argc, char **argv)
|
||||
}
|
||||
} else if (src == NULL) {
|
||||
src = argv[i];
|
||||
} else if (pattern == NULL) {
|
||||
pattern = argv[i];
|
||||
}
|
||||
}
|
||||
const char *target = src ? src : ".";
|
||||
@@ -870,7 +902,7 @@ do_test(int argc, char **argv)
|
||||
if (build_one(resolved, is_dir, outp, outstem[0] ? outstem : NULL,
|
||||
incs, "", "", 1) != 0) return 1;
|
||||
if (compileonly) return 0;
|
||||
int rc = run(outp);
|
||||
int rc = run_test_bin(outp, pattern);
|
||||
if (!outstem[0]) unlink(outp);
|
||||
return rc;
|
||||
}
|
||||
@@ -883,7 +915,7 @@ do_test(int argc, char **argv)
|
||||
if (build_one(target, 0, outp, outstem[0] ? outstem : NULL,
|
||||
incs, "", "", 1) != 0) return 1;
|
||||
if (compileonly) return 0;
|
||||
int rc = run(outp);
|
||||
int rc = run_test_bin(outp, pattern);
|
||||
if (!outstem[0]) unlink(outp);
|
||||
return rc;
|
||||
}
|
||||
@@ -895,6 +927,12 @@ do_test(int argc, char **argv)
|
||||
fprintf(stderr, "ww test: -c/-o need a single test file\n");
|
||||
return 2;
|
||||
}
|
||||
/* #17: a name-filter pattern is per-binary; directory mode builds one
|
||||
* binary per *_test.ww, so a single pattern can't sensibly route. */
|
||||
if (pattern) {
|
||||
fprintf(stderr, "ww test: pattern needs a single test file\n");
|
||||
return 2;
|
||||
}
|
||||
/* directory — run every *_test.ww inside. */
|
||||
char **files = NULL;
|
||||
int n = 0;
|
||||
|
||||
@@ -229,7 +229,7 @@ fn findflag(hs: []help, r: rune) (helpkind | void) = {
|
||||
|
||||
// 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.
|
||||
// matches the slice [[os.args]] hands back.
|
||||
//
|
||||
// Recognised forms:
|
||||
// -X FLAG-kind option
|
||||
|
||||
52
lib/os/os.ww
52
lib/os/os.ww
@@ -5,6 +5,12 @@
|
||||
package os;
|
||||
|
||||
import time;
|
||||
// [[args]] allocates the []str view via the `alloc` builtin, whose malloc
|
||||
// lowers to rt_malloc only when the rt binding is in the bundle (mirror
|
||||
// lib/strings/strings.ww:30 — every alloc-using module imports rt). os is
|
||||
// bundled by ~every program, so without this a plain `ww build` of any
|
||||
// os-importing program links bare libc `malloc` (undefined). Task #17.
|
||||
import rt;
|
||||
|
||||
@symbol("rt_syscall") fn syscall0(num: nr) i64;
|
||||
@symbol("rt_syscall") fn syscall1(num: nr, a: i64) i64;
|
||||
@@ -403,6 +409,12 @@ export fn getdents64(fd: i32, buf: *u8, n: u64) i64 = {
|
||||
// We don't expose `rtenvp` directly; [[getenv]] is the only consumer.
|
||||
@symbol("rt_envp") fn rtenvp() **u8;
|
||||
|
||||
// rt_argc / rt_argv — runtime-side getters for the argc/argv captured by
|
||||
// rt/start.s at process entry (same DATAW-slot + TEXT-getter shape as
|
||||
// rt_envp). [[args]] is the only consumer.
|
||||
@symbol("rt_argc") fn rtargc() i64;
|
||||
@symbol("rt_argv") fn rtargv() **u8;
|
||||
|
||||
// getenv — POSIX getenv. Returns a borrowed `str` view over the value
|
||||
// bytes of the named environment variable, or void if the name is not
|
||||
// present. The view is valid for the process lifetime — the bytes
|
||||
@@ -451,6 +463,46 @@ export fn getenv(name: str) (str | void) = {
|
||||
return;
|
||||
};
|
||||
|
||||
// argsbuilt / argscache — build-once cache for [[args]]. drew ruling
|
||||
// (task #17): an explicit `built` sentinel, NOT len==0 overloading (a
|
||||
// real argv always has argv[0], but the sentinel keeps the contract
|
||||
// honest and decoupled from content). args() is loop-callable; under
|
||||
// ww's no-free model a per-call rebuild would leak the slice each call,
|
||||
// so the slice is materialised once and reused.
|
||||
let argsbuilt: bool = false;
|
||||
let argscache: []str;
|
||||
|
||||
// args — the process arguments as a borrowed []str. args[0] is the
|
||||
// program name; args[1..] are the invocation arguments. Each str views
|
||||
// the NUL-terminated argv bytes in place (valid for the process
|
||||
// lifetime), so the slice must not be mutated or freed by the caller.
|
||||
//
|
||||
// DIVERGENCE (Hare-fidelity, task #26): Hare's `os::args` is a `[]str`
|
||||
// GLOBAL populated by an @init that walks rt's argv
|
||||
// (ref/hare/os/+linux/start.ha). ww has no @init mechanism, so the
|
||||
// faithful global is not expressible; this is the fn-shaped equivalent
|
||||
// (Hare NAME kept, shape diverged). Revisit if @init lands (#26).
|
||||
export fn args() []str = {
|
||||
if (argsbuilt) { return argscache; };
|
||||
let argc: i32 = rtargc(): i32;
|
||||
let argv: **u8 = rtargv();
|
||||
let r: []str = alloc([], argc: u64)!;
|
||||
let i: i32 = 0;
|
||||
for (i < argc) {
|
||||
let c: *u8 = argv[i];
|
||||
let n: i32 = 0;
|
||||
for (c[n] != 0u8) { n += 1; };
|
||||
let s: str;
|
||||
s.ptr = c;
|
||||
s.len = n;
|
||||
append(r, s);
|
||||
i += 1;
|
||||
};
|
||||
argscache = r;
|
||||
argsbuilt = true;
|
||||
return r;
|
||||
};
|
||||
|
||||
// ---- stat / lstat / fstat / exists -----------------------------------
|
||||
//
|
||||
// Ports of Hare's stat family (ref/hare/fs/fs.ha:172,196 +
|
||||
|
||||
@@ -16,9 +16,29 @@ package test;
|
||||
//
|
||||
// D1/D4/D5 reductions also retained from the -T synth era: no
|
||||
// __test_array linker section (the synth hands us a value table), no
|
||||
// sort, no file:line reflection. fnmatch name-filtering is task #17
|
||||
// commit-3 (drew spec §d), not here.
|
||||
// sort, no file:line reflection.
|
||||
//
|
||||
// fnmatch NAME-FILTER (task #17 commit-3, drew spec §d): when the test
|
||||
// binary is invoked with trailing argv (the driver passes `ww test
|
||||
// <file> [pattern]` through as argv[1..]), each pattern is a shell glob
|
||||
// matched against the test name via [[fnmatch.fnmatch]]; a test runs iff
|
||||
// some pattern matches. No pattern (argv.len == 1) runs ALL tests — the
|
||||
// no-filter path is byte-for-byte the pre-filter behavior. Zero matches
|
||||
// prints "No tests run" and exits 0, mirroring Hare's __test_main
|
||||
// (ref/hare/test/+test.ha:104-117). The synth (`run(__wwtests)`) and the
|
||||
// value table are UNCHANGED — the pattern reaches us via os.args, so
|
||||
// 990-997 byte-id and the no-pattern path hold.
|
||||
//
|
||||
// BUNDLE-FLOOR CONSEQUENCE (sanctioned, task #17): importing fnmatch
|
||||
// raises lib/test's auto-bundle floor from os-only to
|
||||
// os+fnmatch+ascii+strings (+their transitive bytes/encoding.utf8/types/
|
||||
// rt) in EVERY -T build. fnmatch's module-private symbols co-bundle into
|
||||
// the same flat "" scope as the test's modules (pre-#8, no package
|
||||
// isolation); a same-named module-private fn in a tested module would
|
||||
// collide. Accepted as the cost of name-filtering; #8 (real packages)
|
||||
// retires it.
|
||||
|
||||
import fnmatch;
|
||||
import os;
|
||||
|
||||
// run — execute each test in `tests` in a forked subprocess, report a
|
||||
@@ -41,30 +61,62 @@ import os;
|
||||
// rejects a duplicate fn, wwstage does not), so the collision is loud on
|
||||
// the reference stage but silent on wwstage until #23 lands.
|
||||
export fn run(tests: [](str, *fn() void)) i32 = {
|
||||
let av: []str = os.args();
|
||||
let nfail: i32 = 0;
|
||||
let nrun: i32 = 0;
|
||||
let i: i32 = 0;
|
||||
for (i < tests.len) {
|
||||
let row: (str, *fn() void) = tests[i];
|
||||
let name: str = row.0;
|
||||
let f: *fn() void = row.1;
|
||||
tstputs(name);
|
||||
tstputs(" ... ");
|
||||
let st: i32 = tstrunone(f);
|
||||
if (st == 0) {
|
||||
tstputs("ok\n");
|
||||
} else {
|
||||
tstputs("FAIL\n");
|
||||
nfail += 1;
|
||||
if (tstenabled(name, av)) {
|
||||
tstputs(name);
|
||||
tstputs(" ... ");
|
||||
let st: i32 = tstrunone(f);
|
||||
if (st == 0) {
|
||||
tstputs("ok\n");
|
||||
} else {
|
||||
tstputs("FAIL\n");
|
||||
nfail += 1;
|
||||
};
|
||||
nrun += 1;
|
||||
};
|
||||
i += 1;
|
||||
};
|
||||
tstputuint(tests.len - nfail);
|
||||
// Zero tests selected (an over-narrow pattern, or none matched): Hare
|
||||
// prints "No tests run" and returns 0 regardless of whether a pattern
|
||||
// was given (ref/hare/test/+test.ha:114-117).
|
||||
if (nrun == 0) {
|
||||
tstputs("No tests run\n");
|
||||
return 0;
|
||||
};
|
||||
tstputuint(nrun - nfail);
|
||||
tstputs(" passed, ");
|
||||
tstputuint(nfail);
|
||||
tstputs(" failed\n");
|
||||
return nfail;
|
||||
};
|
||||
|
||||
// tstenabled — does `name` pass the name-filter? `av` is os.args():
|
||||
// av[0] is the program name, av[1..] the glob patterns the driver
|
||||
// forwarded. No patterns (av.len <= 1) enables every test; otherwise a
|
||||
// test is enabled iff SOME pattern fnmatch-matches (Hare's OR-over-args,
|
||||
// ref/hare/test/+test.ha:106-113). DIVERGENCE: Hare iterates all of
|
||||
// os::args including [0]; ww skips [0] — the test binary path is never a
|
||||
// test-name pattern (the driver passes the pattern as the first
|
||||
// post-program arg, never argv[0]).
|
||||
fn tstenabled(name: str, av: []str) bool = {
|
||||
if (av.len <= 1) { return true; };
|
||||
let j: i32 = 1;
|
||||
for (j < av.len) {
|
||||
if (fnmatch.fnmatch(av[j], name, fnmatch.flag.NONE)) {
|
||||
return true;
|
||||
};
|
||||
j += 1;
|
||||
};
|
||||
return false;
|
||||
};
|
||||
|
||||
// runone — fork, run `f` in the child, decode the child's wait-status.
|
||||
// Byte-for-byte the procrun (main.ww:154-177) status decode: low 7 bits
|
||||
// are the killing signal (abort=SIGABRT, div0/SIGSEGV), 0 if the child
|
||||
|
||||
23
rt/start.s
23
rt/start.s
@@ -25,6 +25,11 @@ TEXT _start,$0
|
||||
ADDQ SP, AX // AX = SP + argc*8
|
||||
ADDQ $16, AX // AX = SP + argc*8 + 16 = &argv[argc+1] = envp
|
||||
MOVQ AX, rt_envp_slot(SB)
|
||||
// argc/argv survive the envp calc (only AX was clobbered): stash both
|
||||
// so lib/os.args can rebuild the []str view after entry. Same DATAW-
|
||||
// slot + TEXT-getter shape as rt_envp below (task #17 fnmatch filter).
|
||||
MOVQ DI, rt_argc_slot(SB)
|
||||
MOVQ SI, rt_argv_slot(SB)
|
||||
CALL main(SB)
|
||||
MOVQ AX, DI
|
||||
MOVQ $60, AX
|
||||
@@ -46,3 +51,21 @@ TEXT rt_envp,$0
|
||||
// slot (w6a has no GLOBL; same shape as lib/log's silent/default/
|
||||
// global cells in lib/log/log.s).
|
||||
DATAW rt_envp_slot(SB),"\x00\x00\x00\x00\x00\x00\x00\x00"
|
||||
|
||||
// rt_argc — getter returning the process argc captured at entry. Bound
|
||||
// from lib/os as `@symbol("rt_argc") fn rtargc() i64;` (rt_envp twin).
|
||||
TEXT rt_argc,$0
|
||||
MOVQ rt_argc_slot(SB), AX
|
||||
RET
|
||||
|
||||
// rt_argv — getter returning &argv[0], a NUL-terminated table of *u8
|
||||
// argument strings. Bound from lib/os as
|
||||
// `@symbol("rt_argv") fn rtargv() **u8;`.
|
||||
TEXT rt_argv,$0
|
||||
MOVQ rt_argv_slot(SB), AX
|
||||
RET
|
||||
|
||||
// rt_argc_slot / rt_argv_slot — 8-byte writable cells, zero in .data,
|
||||
// overwritten by _start before CALL main. Same shape as rt_envp_slot.
|
||||
DATAW rt_argc_slot(SB),"\x00\x00\x00\x00\x00\x00\x00\x00"
|
||||
DATAW rt_argv_slot(SB),"\x00\x00\x00\x00\x00\x00\x00\x00"
|
||||
|
||||
@@ -95,6 +95,29 @@ export fn compare(a: instant, b: instant) i8 = {
|
||||
return 0i8;
|
||||
};
|
||||
|
||||
// rt — runtime primitives exposed to ww programs.
|
||||
// Mirrors Hare's rt:: module placement (ref/hare/rt/).
|
||||
|
||||
package rt;
|
||||
|
||||
// malloc — mmap-backed page allocator. Untyped: `malloc(n)` returns a
|
||||
// `*void`; callers cast to the target type. Diverges from Hare: Hare
|
||||
// exposes `alloc` / `free` as typed language builtins that the
|
||||
// compiler lowers to rt::malloc/rt::free; ww has no such builtins,
|
||||
// so the rt-symbol surface is exposed directly. Stdlib callers that
|
||||
// need a typed allocation pattern wrap this with a cast plus a stored
|
||||
// capacity (see [[strings.dup]], [[memio.dynamic]]).
|
||||
//
|
||||
// OOM: rt_malloc is a bare mmap(MAP_ANON|MAP_PRIVATE) wrapper with no
|
||||
// error path. The raw Linux mmap syscall returns a negative errno cast
|
||||
// to `*void` on failure (e.g. `(void*)-12` for ENOMEM); the
|
||||
// `MAP_FAILED` (`(void*)-1`) value is a libc-wrapper convention that
|
||||
// rt_malloc doesn't apply. Neither `== nil` nor `== (void*)-1` catches
|
||||
// it; any deref of such a return faults. Today the stdlib does not
|
||||
// check; OOM faults on first dereference. A typed fallible variant is
|
||||
// a future task (task #39). ref/hare/rt/malloc.ha:27.
|
||||
@symbol("rt_malloc") export fn malloc(n: u64) *void;
|
||||
|
||||
// os — process and filesystem facade. The body of each call lands
|
||||
// either in libwwrt.a (rt_syscall trampoline) or libc bindings,
|
||||
// depending on how the program was linked.
|
||||
@@ -102,6 +125,12 @@ export fn compare(a: instant, b: instant) i8 = {
|
||||
package os;
|
||||
|
||||
import time;
|
||||
// [[args]] allocates the []str view via the `alloc` builtin, whose malloc
|
||||
// lowers to rt_malloc only when the rt binding is in the bundle (mirror
|
||||
// lib/strings/strings.ww:30 — every alloc-using module imports rt). os is
|
||||
// bundled by ~every program, so without this a plain `ww build` of any
|
||||
// os-importing program links bare libc `malloc` (undefined). Task #17.
|
||||
import rt;
|
||||
|
||||
@symbol("rt_syscall") fn syscall0(num: nr) i64;
|
||||
@symbol("rt_syscall") fn syscall1(num: nr, a: i64) i64;
|
||||
@@ -500,6 +529,12 @@ export fn getdents64(fd: i32, buf: *u8, n: u64) i64 = {
|
||||
// We don't expose `rtenvp` directly; [[getenv]] is the only consumer.
|
||||
@symbol("rt_envp") fn rtenvp() **u8;
|
||||
|
||||
// rt_argc / rt_argv — runtime-side getters for the argc/argv captured by
|
||||
// rt/start.s at process entry (same DATAW-slot + TEXT-getter shape as
|
||||
// rt_envp). [[args]] is the only consumer.
|
||||
@symbol("rt_argc") fn rtargc() i64;
|
||||
@symbol("rt_argv") fn rtargv() **u8;
|
||||
|
||||
// getenv — POSIX getenv. Returns a borrowed `str` view over the value
|
||||
// bytes of the named environment variable, or void if the name is not
|
||||
// present. The view is valid for the process lifetime — the bytes
|
||||
@@ -548,6 +583,46 @@ export fn getenv(name: str) (str | void) = {
|
||||
return;
|
||||
};
|
||||
|
||||
// argsbuilt / argscache — build-once cache for [[args]]. drew ruling
|
||||
// (task #17): an explicit `built` sentinel, NOT len==0 overloading (a
|
||||
// real argv always has argv[0], but the sentinel keeps the contract
|
||||
// honest and decoupled from content). args() is loop-callable; under
|
||||
// ww's no-free model a per-call rebuild would leak the slice each call,
|
||||
// so the slice is materialised once and reused.
|
||||
let argsbuilt: bool = false;
|
||||
let argscache: []str;
|
||||
|
||||
// args — the process arguments as a borrowed []str. args[0] is the
|
||||
// program name; args[1..] are the invocation arguments. Each str views
|
||||
// the NUL-terminated argv bytes in place (valid for the process
|
||||
// lifetime), so the slice must not be mutated or freed by the caller.
|
||||
//
|
||||
// DIVERGENCE (Hare-fidelity, task #26): Hare's `os::args` is a `[]str`
|
||||
// GLOBAL populated by an @init that walks rt's argv
|
||||
// (ref/hare/os/+linux/start.ha). ww has no @init mechanism, so the
|
||||
// faithful global is not expressible; this is the fn-shaped equivalent
|
||||
// (Hare NAME kept, shape diverged). Revisit if @init lands (#26).
|
||||
export fn args() []str = {
|
||||
if (argsbuilt) { return argscache; };
|
||||
let argc: i32 = rtargc(): i32;
|
||||
let argv: **u8 = rtargv();
|
||||
let r: []str = alloc([], argc: u64)!;
|
||||
let i: i32 = 0;
|
||||
for (i < argc) {
|
||||
let c: *u8 = argv[i];
|
||||
let n: i32 = 0;
|
||||
for (c[n] != 0u8) { n += 1; };
|
||||
let s: str;
|
||||
s.ptr = c;
|
||||
s.len = n;
|
||||
append(r, s);
|
||||
i += 1;
|
||||
};
|
||||
argscache = r;
|
||||
argsbuilt = true;
|
||||
return r;
|
||||
};
|
||||
|
||||
// ---- stat / lstat / fstat / exists -----------------------------------
|
||||
//
|
||||
// Ports of Hare's stat family (ref/hare/fs/fs.ha:172,196 +
|
||||
@@ -781,29 +856,6 @@ export fn exists(path: str) bool = {
|
||||
return r >= 0i64;
|
||||
};
|
||||
|
||||
// rt — runtime primitives exposed to ww programs.
|
||||
// Mirrors Hare's rt:: module placement (ref/hare/rt/).
|
||||
|
||||
package rt;
|
||||
|
||||
// malloc — mmap-backed page allocator. Untyped: `malloc(n)` returns a
|
||||
// `*void`; callers cast to the target type. Diverges from Hare: Hare
|
||||
// exposes `alloc` / `free` as typed language builtins that the
|
||||
// compiler lowers to rt::malloc/rt::free; ww has no such builtins,
|
||||
// so the rt-symbol surface is exposed directly. Stdlib callers that
|
||||
// need a typed allocation pattern wrap this with a cast plus a stored
|
||||
// capacity (see [[strings.dup]], [[memio.dynamic]]).
|
||||
//
|
||||
// OOM: rt_malloc is a bare mmap(MAP_ANON|MAP_PRIVATE) wrapper with no
|
||||
// error path. The raw Linux mmap syscall returns a negative errno cast
|
||||
// to `*void` on failure (e.g. `(void*)-12` for ENOMEM); the
|
||||
// `MAP_FAILED` (`(void*)-1`) value is a libc-wrapper convention that
|
||||
// rt_malloc doesn't apply. Neither `== nil` nor `== (void*)-1` catches
|
||||
// it; any deref of such a return faults. Today the stdlib does not
|
||||
// check; OOM faults on first dereference. A typed fallible variant is
|
||||
// a future task (task #39). ref/hare/rt/malloc.ha:27.
|
||||
@symbol("rt_malloc") export fn malloc(n: u64) *void;
|
||||
|
||||
// types — integer limits. Mirrors Hare's types::limits (I8_MAX, …)
|
||||
// platform-fixed for amd64. Numeric helpers live in lib/math, matching
|
||||
// Hare's split between types::limits and math::.
|
||||
|
||||
@@ -95,6 +95,29 @@ export fn compare(a: instant, b: instant) i8 = {
|
||||
return 0i8;
|
||||
};
|
||||
|
||||
// rt — runtime primitives exposed to ww programs.
|
||||
// Mirrors Hare's rt:: module placement (ref/hare/rt/).
|
||||
|
||||
package rt;
|
||||
|
||||
// malloc — mmap-backed page allocator. Untyped: `malloc(n)` returns a
|
||||
// `*void`; callers cast to the target type. Diverges from Hare: Hare
|
||||
// exposes `alloc` / `free` as typed language builtins that the
|
||||
// compiler lowers to rt::malloc/rt::free; ww has no such builtins,
|
||||
// so the rt-symbol surface is exposed directly. Stdlib callers that
|
||||
// need a typed allocation pattern wrap this with a cast plus a stored
|
||||
// capacity (see [[strings.dup]], [[memio.dynamic]]).
|
||||
//
|
||||
// OOM: rt_malloc is a bare mmap(MAP_ANON|MAP_PRIVATE) wrapper with no
|
||||
// error path. The raw Linux mmap syscall returns a negative errno cast
|
||||
// to `*void` on failure (e.g. `(void*)-12` for ENOMEM); the
|
||||
// `MAP_FAILED` (`(void*)-1`) value is a libc-wrapper convention that
|
||||
// rt_malloc doesn't apply. Neither `== nil` nor `== (void*)-1` catches
|
||||
// it; any deref of such a return faults. Today the stdlib does not
|
||||
// check; OOM faults on first dereference. A typed fallible variant is
|
||||
// a future task (task #39). ref/hare/rt/malloc.ha:27.
|
||||
@symbol("rt_malloc") export fn malloc(n: u64) *void;
|
||||
|
||||
// os — process and filesystem facade. The body of each call lands
|
||||
// either in libwwrt.a (rt_syscall trampoline) or libc bindings,
|
||||
// depending on how the program was linked.
|
||||
@@ -102,6 +125,12 @@ export fn compare(a: instant, b: instant) i8 = {
|
||||
package os;
|
||||
|
||||
import time;
|
||||
// [[args]] allocates the []str view via the `alloc` builtin, whose malloc
|
||||
// lowers to rt_malloc only when the rt binding is in the bundle (mirror
|
||||
// lib/strings/strings.ww:30 — every alloc-using module imports rt). os is
|
||||
// bundled by ~every program, so without this a plain `ww build` of any
|
||||
// os-importing program links bare libc `malloc` (undefined). Task #17.
|
||||
import rt;
|
||||
|
||||
@symbol("rt_syscall") fn syscall0(num: nr) i64;
|
||||
@symbol("rt_syscall") fn syscall1(num: nr, a: i64) i64;
|
||||
@@ -500,6 +529,12 @@ export fn getdents64(fd: i32, buf: *u8, n: u64) i64 = {
|
||||
// We don't expose `rtenvp` directly; [[getenv]] is the only consumer.
|
||||
@symbol("rt_envp") fn rtenvp() **u8;
|
||||
|
||||
// rt_argc / rt_argv — runtime-side getters for the argc/argv captured by
|
||||
// rt/start.s at process entry (same DATAW-slot + TEXT-getter shape as
|
||||
// rt_envp). [[args]] is the only consumer.
|
||||
@symbol("rt_argc") fn rtargc() i64;
|
||||
@symbol("rt_argv") fn rtargv() **u8;
|
||||
|
||||
// getenv — POSIX getenv. Returns a borrowed `str` view over the value
|
||||
// bytes of the named environment variable, or void if the name is not
|
||||
// present. The view is valid for the process lifetime — the bytes
|
||||
@@ -548,6 +583,46 @@ export fn getenv(name: str) (str | void) = {
|
||||
return;
|
||||
};
|
||||
|
||||
// argsbuilt / argscache — build-once cache for [[args]]. drew ruling
|
||||
// (task #17): an explicit `built` sentinel, NOT len==0 overloading (a
|
||||
// real argv always has argv[0], but the sentinel keeps the contract
|
||||
// honest and decoupled from content). args() is loop-callable; under
|
||||
// ww's no-free model a per-call rebuild would leak the slice each call,
|
||||
// so the slice is materialised once and reused.
|
||||
let argsbuilt: bool = false;
|
||||
let argscache: []str;
|
||||
|
||||
// args — the process arguments as a borrowed []str. args[0] is the
|
||||
// program name; args[1..] are the invocation arguments. Each str views
|
||||
// the NUL-terminated argv bytes in place (valid for the process
|
||||
// lifetime), so the slice must not be mutated or freed by the caller.
|
||||
//
|
||||
// DIVERGENCE (Hare-fidelity, task #26): Hare's `os::args` is a `[]str`
|
||||
// GLOBAL populated by an @init that walks rt's argv
|
||||
// (ref/hare/os/+linux/start.ha). ww has no @init mechanism, so the
|
||||
// faithful global is not expressible; this is the fn-shaped equivalent
|
||||
// (Hare NAME kept, shape diverged). Revisit if @init lands (#26).
|
||||
export fn args() []str = {
|
||||
if (argsbuilt) { return argscache; };
|
||||
let argc: i32 = rtargc(): i32;
|
||||
let argv: **u8 = rtargv();
|
||||
let r: []str = alloc([], argc: u64)!;
|
||||
let i: i32 = 0;
|
||||
for (i < argc) {
|
||||
let c: *u8 = argv[i];
|
||||
let n: i32 = 0;
|
||||
for (c[n] != 0u8) { n += 1; };
|
||||
let s: str;
|
||||
s.ptr = c;
|
||||
s.len = n;
|
||||
append(r, s);
|
||||
i += 1;
|
||||
};
|
||||
argscache = r;
|
||||
argsbuilt = true;
|
||||
return r;
|
||||
};
|
||||
|
||||
// ---- stat / lstat / fstat / exists -----------------------------------
|
||||
//
|
||||
// Ports of Hare's stat family (ref/hare/fs/fs.ha:172,196 +
|
||||
@@ -781,29 +856,6 @@ export fn exists(path: str) bool = {
|
||||
return r >= 0i64;
|
||||
};
|
||||
|
||||
// rt — runtime primitives exposed to ww programs.
|
||||
// Mirrors Hare's rt:: module placement (ref/hare/rt/).
|
||||
|
||||
package rt;
|
||||
|
||||
// malloc — mmap-backed page allocator. Untyped: `malloc(n)` returns a
|
||||
// `*void`; callers cast to the target type. Diverges from Hare: Hare
|
||||
// exposes `alloc` / `free` as typed language builtins that the
|
||||
// compiler lowers to rt::malloc/rt::free; ww has no such builtins,
|
||||
// so the rt-symbol surface is exposed directly. Stdlib callers that
|
||||
// need a typed allocation pattern wrap this with a cast plus a stored
|
||||
// capacity (see [[strings.dup]], [[memio.dynamic]]).
|
||||
//
|
||||
// OOM: rt_malloc is a bare mmap(MAP_ANON|MAP_PRIVATE) wrapper with no
|
||||
// error path. The raw Linux mmap syscall returns a negative errno cast
|
||||
// to `*void` on failure (e.g. `(void*)-12` for ENOMEM); the
|
||||
// `MAP_FAILED` (`(void*)-1`) value is a libc-wrapper convention that
|
||||
// rt_malloc doesn't apply. Neither `== nil` nor `== (void*)-1` catches
|
||||
// it; any deref of such a return faults. Today the stdlib does not
|
||||
// check; OOM faults on first dereference. A typed fallible variant is
|
||||
// a future task (task #39). ref/hare/rt/malloc.ha:27.
|
||||
@symbol("rt_malloc") export fn malloc(n: u64) *void;
|
||||
|
||||
// types — integer limits. Mirrors Hare's types::limits (I8_MAX, …)
|
||||
// platform-fixed for amd64. Numeric helpers live in lib/math, matching
|
||||
// Hare's split between types::limits and math::.
|
||||
|
||||
@@ -95,6 +95,29 @@ export fn compare(a: instant, b: instant) i8 = {
|
||||
return 0i8;
|
||||
};
|
||||
|
||||
// rt — runtime primitives exposed to ww programs.
|
||||
// Mirrors Hare's rt:: module placement (ref/hare/rt/).
|
||||
|
||||
package rt;
|
||||
|
||||
// malloc — mmap-backed page allocator. Untyped: `malloc(n)` returns a
|
||||
// `*void`; callers cast to the target type. Diverges from Hare: Hare
|
||||
// exposes `alloc` / `free` as typed language builtins that the
|
||||
// compiler lowers to rt::malloc/rt::free; ww has no such builtins,
|
||||
// so the rt-symbol surface is exposed directly. Stdlib callers that
|
||||
// need a typed allocation pattern wrap this with a cast plus a stored
|
||||
// capacity (see [[strings.dup]], [[memio.dynamic]]).
|
||||
//
|
||||
// OOM: rt_malloc is a bare mmap(MAP_ANON|MAP_PRIVATE) wrapper with no
|
||||
// error path. The raw Linux mmap syscall returns a negative errno cast
|
||||
// to `*void` on failure (e.g. `(void*)-12` for ENOMEM); the
|
||||
// `MAP_FAILED` (`(void*)-1`) value is a libc-wrapper convention that
|
||||
// rt_malloc doesn't apply. Neither `== nil` nor `== (void*)-1` catches
|
||||
// it; any deref of such a return faults. Today the stdlib does not
|
||||
// check; OOM faults on first dereference. A typed fallible variant is
|
||||
// a future task (task #39). ref/hare/rt/malloc.ha:27.
|
||||
@symbol("rt_malloc") export fn malloc(n: u64) *void;
|
||||
|
||||
// os — process and filesystem facade. The body of each call lands
|
||||
// either in libwwrt.a (rt_syscall trampoline) or libc bindings,
|
||||
// depending on how the program was linked.
|
||||
@@ -102,6 +125,12 @@ export fn compare(a: instant, b: instant) i8 = {
|
||||
package os;
|
||||
|
||||
import time;
|
||||
// [[args]] allocates the []str view via the `alloc` builtin, whose malloc
|
||||
// lowers to rt_malloc only when the rt binding is in the bundle (mirror
|
||||
// lib/strings/strings.ww:30 — every alloc-using module imports rt). os is
|
||||
// bundled by ~every program, so without this a plain `ww build` of any
|
||||
// os-importing program links bare libc `malloc` (undefined). Task #17.
|
||||
import rt;
|
||||
|
||||
@symbol("rt_syscall") fn syscall0(num: nr) i64;
|
||||
@symbol("rt_syscall") fn syscall1(num: nr, a: i64) i64;
|
||||
@@ -500,6 +529,12 @@ export fn getdents64(fd: i32, buf: *u8, n: u64) i64 = {
|
||||
// We don't expose `rtenvp` directly; [[getenv]] is the only consumer.
|
||||
@symbol("rt_envp") fn rtenvp() **u8;
|
||||
|
||||
// rt_argc / rt_argv — runtime-side getters for the argc/argv captured by
|
||||
// rt/start.s at process entry (same DATAW-slot + TEXT-getter shape as
|
||||
// rt_envp). [[args]] is the only consumer.
|
||||
@symbol("rt_argc") fn rtargc() i64;
|
||||
@symbol("rt_argv") fn rtargv() **u8;
|
||||
|
||||
// getenv — POSIX getenv. Returns a borrowed `str` view over the value
|
||||
// bytes of the named environment variable, or void if the name is not
|
||||
// present. The view is valid for the process lifetime — the bytes
|
||||
@@ -548,6 +583,46 @@ export fn getenv(name: str) (str | void) = {
|
||||
return;
|
||||
};
|
||||
|
||||
// argsbuilt / argscache — build-once cache for [[args]]. drew ruling
|
||||
// (task #17): an explicit `built` sentinel, NOT len==0 overloading (a
|
||||
// real argv always has argv[0], but the sentinel keeps the contract
|
||||
// honest and decoupled from content). args() is loop-callable; under
|
||||
// ww's no-free model a per-call rebuild would leak the slice each call,
|
||||
// so the slice is materialised once and reused.
|
||||
let argsbuilt: bool = false;
|
||||
let argscache: []str;
|
||||
|
||||
// args — the process arguments as a borrowed []str. args[0] is the
|
||||
// program name; args[1..] are the invocation arguments. Each str views
|
||||
// the NUL-terminated argv bytes in place (valid for the process
|
||||
// lifetime), so the slice must not be mutated or freed by the caller.
|
||||
//
|
||||
// DIVERGENCE (Hare-fidelity, task #26): Hare's `os::args` is a `[]str`
|
||||
// GLOBAL populated by an @init that walks rt's argv
|
||||
// (ref/hare/os/+linux/start.ha). ww has no @init mechanism, so the
|
||||
// faithful global is not expressible; this is the fn-shaped equivalent
|
||||
// (Hare NAME kept, shape diverged). Revisit if @init lands (#26).
|
||||
export fn args() []str = {
|
||||
if (argsbuilt) { return argscache; };
|
||||
let argc: i32 = rtargc(): i32;
|
||||
let argv: **u8 = rtargv();
|
||||
let r: []str = alloc([], argc: u64)!;
|
||||
let i: i32 = 0;
|
||||
for (i < argc) {
|
||||
let c: *u8 = argv[i];
|
||||
let n: i32 = 0;
|
||||
for (c[n] != 0u8) { n += 1; };
|
||||
let s: str;
|
||||
s.ptr = c;
|
||||
s.len = n;
|
||||
append(r, s);
|
||||
i += 1;
|
||||
};
|
||||
argscache = r;
|
||||
argsbuilt = true;
|
||||
return r;
|
||||
};
|
||||
|
||||
// ---- stat / lstat / fstat / exists -----------------------------------
|
||||
//
|
||||
// Ports of Hare's stat family (ref/hare/fs/fs.ha:172,196 +
|
||||
@@ -781,29 +856,6 @@ export fn exists(path: str) bool = {
|
||||
return r >= 0i64;
|
||||
};
|
||||
|
||||
// rt — runtime primitives exposed to ww programs.
|
||||
// Mirrors Hare's rt:: module placement (ref/hare/rt/).
|
||||
|
||||
package rt;
|
||||
|
||||
// malloc — mmap-backed page allocator. Untyped: `malloc(n)` returns a
|
||||
// `*void`; callers cast to the target type. Diverges from Hare: Hare
|
||||
// exposes `alloc` / `free` as typed language builtins that the
|
||||
// compiler lowers to rt::malloc/rt::free; ww has no such builtins,
|
||||
// so the rt-symbol surface is exposed directly. Stdlib callers that
|
||||
// need a typed allocation pattern wrap this with a cast plus a stored
|
||||
// capacity (see [[strings.dup]], [[memio.dynamic]]).
|
||||
//
|
||||
// OOM: rt_malloc is a bare mmap(MAP_ANON|MAP_PRIVATE) wrapper with no
|
||||
// error path. The raw Linux mmap syscall returns a negative errno cast
|
||||
// to `*void` on failure (e.g. `(void*)-12` for ENOMEM); the
|
||||
// `MAP_FAILED` (`(void*)-1`) value is a libc-wrapper convention that
|
||||
// rt_malloc doesn't apply. Neither `== nil` nor `== (void*)-1` catches
|
||||
// it; any deref of such a return faults. Today the stdlib does not
|
||||
// check; OOM faults on first dereference. A typed fallible variant is
|
||||
// a future task (task #39). ref/hare/rt/malloc.ha:27.
|
||||
@symbol("rt_malloc") export fn malloc(n: u64) *void;
|
||||
|
||||
// types — integer limits. Mirrors Hare's types::limits (I8_MAX, …)
|
||||
// platform-fixed for amd64. Numeric helpers live in lib/math, matching
|
||||
// Hare's split between types::limits and math::.
|
||||
|
||||
@@ -95,6 +95,29 @@ export fn compare(a: instant, b: instant) i8 = {
|
||||
return 0i8;
|
||||
};
|
||||
|
||||
// rt — runtime primitives exposed to ww programs.
|
||||
// Mirrors Hare's rt:: module placement (ref/hare/rt/).
|
||||
|
||||
package rt;
|
||||
|
||||
// malloc — mmap-backed page allocator. Untyped: `malloc(n)` returns a
|
||||
// `*void`; callers cast to the target type. Diverges from Hare: Hare
|
||||
// exposes `alloc` / `free` as typed language builtins that the
|
||||
// compiler lowers to rt::malloc/rt::free; ww has no such builtins,
|
||||
// so the rt-symbol surface is exposed directly. Stdlib callers that
|
||||
// need a typed allocation pattern wrap this with a cast plus a stored
|
||||
// capacity (see [[strings.dup]], [[memio.dynamic]]).
|
||||
//
|
||||
// OOM: rt_malloc is a bare mmap(MAP_ANON|MAP_PRIVATE) wrapper with no
|
||||
// error path. The raw Linux mmap syscall returns a negative errno cast
|
||||
// to `*void` on failure (e.g. `(void*)-12` for ENOMEM); the
|
||||
// `MAP_FAILED` (`(void*)-1`) value is a libc-wrapper convention that
|
||||
// rt_malloc doesn't apply. Neither `== nil` nor `== (void*)-1` catches
|
||||
// it; any deref of such a return faults. Today the stdlib does not
|
||||
// check; OOM faults on first dereference. A typed fallible variant is
|
||||
// a future task (task #39). ref/hare/rt/malloc.ha:27.
|
||||
@symbol("rt_malloc") export fn malloc(n: u64) *void;
|
||||
|
||||
// os — process and filesystem facade. The body of each call lands
|
||||
// either in libwwrt.a (rt_syscall trampoline) or libc bindings,
|
||||
// depending on how the program was linked.
|
||||
@@ -102,6 +125,12 @@ export fn compare(a: instant, b: instant) i8 = {
|
||||
package os;
|
||||
|
||||
import time;
|
||||
// [[args]] allocates the []str view via the `alloc` builtin, whose malloc
|
||||
// lowers to rt_malloc only when the rt binding is in the bundle (mirror
|
||||
// lib/strings/strings.ww:30 — every alloc-using module imports rt). os is
|
||||
// bundled by ~every program, so without this a plain `ww build` of any
|
||||
// os-importing program links bare libc `malloc` (undefined). Task #17.
|
||||
import rt;
|
||||
|
||||
@symbol("rt_syscall") fn syscall0(num: nr) i64;
|
||||
@symbol("rt_syscall") fn syscall1(num: nr, a: i64) i64;
|
||||
@@ -500,6 +529,12 @@ export fn getdents64(fd: i32, buf: *u8, n: u64) i64 = {
|
||||
// We don't expose `rtenvp` directly; [[getenv]] is the only consumer.
|
||||
@symbol("rt_envp") fn rtenvp() **u8;
|
||||
|
||||
// rt_argc / rt_argv — runtime-side getters for the argc/argv captured by
|
||||
// rt/start.s at process entry (same DATAW-slot + TEXT-getter shape as
|
||||
// rt_envp). [[args]] is the only consumer.
|
||||
@symbol("rt_argc") fn rtargc() i64;
|
||||
@symbol("rt_argv") fn rtargv() **u8;
|
||||
|
||||
// getenv — POSIX getenv. Returns a borrowed `str` view over the value
|
||||
// bytes of the named environment variable, or void if the name is not
|
||||
// present. The view is valid for the process lifetime — the bytes
|
||||
@@ -548,6 +583,46 @@ export fn getenv(name: str) (str | void) = {
|
||||
return;
|
||||
};
|
||||
|
||||
// argsbuilt / argscache — build-once cache for [[args]]. drew ruling
|
||||
// (task #17): an explicit `built` sentinel, NOT len==0 overloading (a
|
||||
// real argv always has argv[0], but the sentinel keeps the contract
|
||||
// honest and decoupled from content). args() is loop-callable; under
|
||||
// ww's no-free model a per-call rebuild would leak the slice each call,
|
||||
// so the slice is materialised once and reused.
|
||||
let argsbuilt: bool = false;
|
||||
let argscache: []str;
|
||||
|
||||
// args — the process arguments as a borrowed []str. args[0] is the
|
||||
// program name; args[1..] are the invocation arguments. Each str views
|
||||
// the NUL-terminated argv bytes in place (valid for the process
|
||||
// lifetime), so the slice must not be mutated or freed by the caller.
|
||||
//
|
||||
// DIVERGENCE (Hare-fidelity, task #26): Hare's `os::args` is a `[]str`
|
||||
// GLOBAL populated by an @init that walks rt's argv
|
||||
// (ref/hare/os/+linux/start.ha). ww has no @init mechanism, so the
|
||||
// faithful global is not expressible; this is the fn-shaped equivalent
|
||||
// (Hare NAME kept, shape diverged). Revisit if @init lands (#26).
|
||||
export fn args() []str = {
|
||||
if (argsbuilt) { return argscache; };
|
||||
let argc: i32 = rtargc(): i32;
|
||||
let argv: **u8 = rtargv();
|
||||
let r: []str = alloc([], argc: u64)!;
|
||||
let i: i32 = 0;
|
||||
for (i < argc) {
|
||||
let c: *u8 = argv[i];
|
||||
let n: i32 = 0;
|
||||
for (c[n] != 0u8) { n += 1; };
|
||||
let s: str;
|
||||
s.ptr = c;
|
||||
s.len = n;
|
||||
append(r, s);
|
||||
i += 1;
|
||||
};
|
||||
argscache = r;
|
||||
argsbuilt = true;
|
||||
return r;
|
||||
};
|
||||
|
||||
// ---- stat / lstat / fstat / exists -----------------------------------
|
||||
//
|
||||
// Ports of Hare's stat family (ref/hare/fs/fs.ha:172,196 +
|
||||
@@ -781,29 +856,6 @@ export fn exists(path: str) bool = {
|
||||
return r >= 0i64;
|
||||
};
|
||||
|
||||
// rt — runtime primitives exposed to ww programs.
|
||||
// Mirrors Hare's rt:: module placement (ref/hare/rt/).
|
||||
|
||||
package rt;
|
||||
|
||||
// malloc — mmap-backed page allocator. Untyped: `malloc(n)` returns a
|
||||
// `*void`; callers cast to the target type. Diverges from Hare: Hare
|
||||
// exposes `alloc` / `free` as typed language builtins that the
|
||||
// compiler lowers to rt::malloc/rt::free; ww has no such builtins,
|
||||
// so the rt-symbol surface is exposed directly. Stdlib callers that
|
||||
// need a typed allocation pattern wrap this with a cast plus a stored
|
||||
// capacity (see [[strings.dup]], [[memio.dynamic]]).
|
||||
//
|
||||
// OOM: rt_malloc is a bare mmap(MAP_ANON|MAP_PRIVATE) wrapper with no
|
||||
// error path. The raw Linux mmap syscall returns a negative errno cast
|
||||
// to `*void` on failure (e.g. `(void*)-12` for ENOMEM); the
|
||||
// `MAP_FAILED` (`(void*)-1`) value is a libc-wrapper convention that
|
||||
// rt_malloc doesn't apply. Neither `== nil` nor `== (void*)-1` catches
|
||||
// it; any deref of such a return faults. Today the stdlib does not
|
||||
// check; OOM faults on first dereference. A typed fallible variant is
|
||||
// a future task (task #39). ref/hare/rt/malloc.ha:27.
|
||||
@symbol("rt_malloc") export fn malloc(n: u64) *void;
|
||||
|
||||
// types — integer limits. Mirrors Hare's types::limits (I8_MAX, …)
|
||||
// platform-fixed for amd64. Numeric helpers live in lib/math, matching
|
||||
// Hare's split between types::limits and math::.
|
||||
@@ -4280,7 +4332,7 @@ fn dorun(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
|
||||
// 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, incs: *u8, compileonly: i32, outstem: *u8) i32 = {
|
||||
fn runsingletest(selfdir: *u8, src: *u8, incs: *u8, compileonly: i32, outstem: *u8, pattern: *u8) i32 = {
|
||||
let tmp: []u8 = alloc([], (os.PATH_MAX: u64))!;
|
||||
tmp.len = os.PATH_MAX;
|
||||
// -o redirects the binary + its combined (objstem, T3) to <stem>; the
|
||||
@@ -4299,10 +4351,19 @@ fn runsingletest(selfdir: *u8, src: *u8, incs: *u8, compileonly: i32, outstem: *
|
||||
return 1;
|
||||
};
|
||||
if (compileonly != 0) { return 0; };
|
||||
let execargv: []*u8 = alloc([], 2u64)!;
|
||||
execargv.len = 2;
|
||||
// #17 fnmatch filter: forward `pattern` as argv[1] so lib/test run()
|
||||
// reads it via os.args. procrun execve's a NUL-terminated argv, so the
|
||||
// terminator (not .len) bounds the vector. cstage twin: run_test_bin.
|
||||
let execargv: []*u8 = alloc([], 3u64)!;
|
||||
execargv[0] = outp;
|
||||
execargv[1] = nil;
|
||||
if (pattern != nil) {
|
||||
execargv.len = 3;
|
||||
execargv[1] = pattern;
|
||||
execargv[2] = nil;
|
||||
} else {
|
||||
execargv.len = 2;
|
||||
execargv[1] = nil;
|
||||
};
|
||||
let rc: i32 = procrun(outp, execargv.ptr);
|
||||
if (outstem == nil) { os.remove(pathstr(outp)); };
|
||||
return rc;
|
||||
@@ -4390,6 +4451,10 @@ fn dotest(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
|
||||
// transitive imports (e.g. 905_nkname asttest → tok); coupled to the
|
||||
// -T flip (task #5/#10).
|
||||
let target: *u8 = nil;
|
||||
// #17: optional 2nd positional = fnmatch name-filter pattern, forwarded
|
||||
// to the test binary as argv[1] (single-file/module only; dir-mode
|
||||
// rejects). cstage twin: do_test `pattern`.
|
||||
let patarg: *u8 = nil;
|
||||
let incs: []u8 = alloc([], (os.PATH_MAX: u64) * 2u64)!;
|
||||
incs.len = ((os.PATH_MAX: u64) * 2u64): i32;
|
||||
let incoff: u64 = 0u64;
|
||||
@@ -4440,7 +4505,8 @@ fn dotest(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
|
||||
return 2;
|
||||
}; }; };
|
||||
} else {
|
||||
if (target == nil) { target = p; };
|
||||
if (target == nil) { target = p; }
|
||||
else { if (patarg == nil) { patarg = p; }; };
|
||||
};
|
||||
i += 1;
|
||||
};
|
||||
@@ -4452,7 +4518,7 @@ fn dotest(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
|
||||
// single-file mode: literal *.ww that exists
|
||||
if (cstrendswithlit(target, ".ww")) {
|
||||
if (os.access(pathstr(target), 0i32) == 0) {
|
||||
return runsingletest(selfdir, target, incs.ptr, compileonly, outstem);
|
||||
return runsingletest(selfdir, target, incs.ptr, compileonly, outstem, patarg);
|
||||
};
|
||||
};
|
||||
|
||||
@@ -4460,6 +4526,12 @@ fn dotest(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
|
||||
cerr("ww test: -c/-o need a single test file\n");
|
||||
return 2;
|
||||
};
|
||||
// #17: a name-filter pattern is per-binary; dir mode builds one binary
|
||||
// per *_test.ww, so a single pattern can't route. cstage twin parity.
|
||||
if (patarg != nil) {
|
||||
cerr("ww test: pattern needs a single test file\n");
|
||||
return 2;
|
||||
};
|
||||
// otherwise treat target as a directory; enumerate *_test.ww
|
||||
return rundirtests(selfdir, target);
|
||||
};
|
||||
|
||||
@@ -1494,7 +1494,7 @@ fn dorun(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
|
||||
// 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, incs: *u8, compileonly: i32, outstem: *u8) i32 = {
|
||||
fn runsingletest(selfdir: *u8, src: *u8, incs: *u8, compileonly: i32, outstem: *u8, pattern: *u8) i32 = {
|
||||
let tmp: []u8 = alloc([], (os.PATH_MAX: u64))!;
|
||||
tmp.len = os.PATH_MAX;
|
||||
// -o redirects the binary + its combined (objstem, T3) to <stem>; the
|
||||
@@ -1513,10 +1513,19 @@ fn runsingletest(selfdir: *u8, src: *u8, incs: *u8, compileonly: i32, outstem: *
|
||||
return 1;
|
||||
};
|
||||
if (compileonly != 0) { return 0; };
|
||||
let execargv: []*u8 = alloc([], 2u64)!;
|
||||
execargv.len = 2;
|
||||
// #17 fnmatch filter: forward `pattern` as argv[1] so lib/test run()
|
||||
// reads it via os.args. procrun execve's a NUL-terminated argv, so the
|
||||
// terminator (not .len) bounds the vector. cstage twin: run_test_bin.
|
||||
let execargv: []*u8 = alloc([], 3u64)!;
|
||||
execargv[0] = outp;
|
||||
execargv[1] = nil;
|
||||
if (pattern != nil) {
|
||||
execargv.len = 3;
|
||||
execargv[1] = pattern;
|
||||
execargv[2] = nil;
|
||||
} else {
|
||||
execargv.len = 2;
|
||||
execargv[1] = nil;
|
||||
};
|
||||
let rc: i32 = procrun(outp, execargv.ptr);
|
||||
if (outstem == nil) { os.remove(pathstr(outp)); };
|
||||
return rc;
|
||||
@@ -1604,6 +1613,10 @@ fn dotest(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
|
||||
// transitive imports (e.g. 905_nkname asttest → tok); coupled to the
|
||||
// -T flip (task #5/#10).
|
||||
let target: *u8 = nil;
|
||||
// #17: optional 2nd positional = fnmatch name-filter pattern, forwarded
|
||||
// to the test binary as argv[1] (single-file/module only; dir-mode
|
||||
// rejects). cstage twin: do_test `pattern`.
|
||||
let patarg: *u8 = nil;
|
||||
let incs: []u8 = alloc([], (os.PATH_MAX: u64) * 2u64)!;
|
||||
incs.len = ((os.PATH_MAX: u64) * 2u64): i32;
|
||||
let incoff: u64 = 0u64;
|
||||
@@ -1654,7 +1667,8 @@ fn dotest(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
|
||||
return 2;
|
||||
}; }; };
|
||||
} else {
|
||||
if (target == nil) { target = p; };
|
||||
if (target == nil) { target = p; }
|
||||
else { if (patarg == nil) { patarg = p; }; };
|
||||
};
|
||||
i += 1;
|
||||
};
|
||||
@@ -1666,7 +1680,7 @@ fn dotest(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
|
||||
// single-file mode: literal *.ww that exists
|
||||
if (cstrendswithlit(target, ".ww")) {
|
||||
if (os.access(pathstr(target), 0i32) == 0) {
|
||||
return runsingletest(selfdir, target, incs.ptr, compileonly, outstem);
|
||||
return runsingletest(selfdir, target, incs.ptr, compileonly, outstem, patarg);
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1674,6 +1688,12 @@ fn dotest(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
|
||||
cerr("ww test: -c/-o need a single test file\n");
|
||||
return 2;
|
||||
};
|
||||
// #17: a name-filter pattern is per-binary; dir mode builds one binary
|
||||
// per *_test.ww, so a single pattern can't route. cstage twin parity.
|
||||
if (patarg != nil) {
|
||||
cerr("ww test: pattern needs a single test file\n");
|
||||
return 2;
|
||||
};
|
||||
// otherwise treat target as a directory; enumerate *_test.ww
|
||||
return rundirtests(selfdir, target);
|
||||
};
|
||||
|
||||
@@ -95,6 +95,29 @@ export fn compare(a: instant, b: instant) i8 = {
|
||||
return 0i8;
|
||||
};
|
||||
|
||||
// rt — runtime primitives exposed to ww programs.
|
||||
// Mirrors Hare's rt:: module placement (ref/hare/rt/).
|
||||
|
||||
package rt;
|
||||
|
||||
// malloc — mmap-backed page allocator. Untyped: `malloc(n)` returns a
|
||||
// `*void`; callers cast to the target type. Diverges from Hare: Hare
|
||||
// exposes `alloc` / `free` as typed language builtins that the
|
||||
// compiler lowers to rt::malloc/rt::free; ww has no such builtins,
|
||||
// so the rt-symbol surface is exposed directly. Stdlib callers that
|
||||
// need a typed allocation pattern wrap this with a cast plus a stored
|
||||
// capacity (see [[strings.dup]], [[memio.dynamic]]).
|
||||
//
|
||||
// OOM: rt_malloc is a bare mmap(MAP_ANON|MAP_PRIVATE) wrapper with no
|
||||
// error path. The raw Linux mmap syscall returns a negative errno cast
|
||||
// to `*void` on failure (e.g. `(void*)-12` for ENOMEM); the
|
||||
// `MAP_FAILED` (`(void*)-1`) value is a libc-wrapper convention that
|
||||
// rt_malloc doesn't apply. Neither `== nil` nor `== (void*)-1` catches
|
||||
// it; any deref of such a return faults. Today the stdlib does not
|
||||
// check; OOM faults on first dereference. A typed fallible variant is
|
||||
// a future task (task #39). ref/hare/rt/malloc.ha:27.
|
||||
@symbol("rt_malloc") export fn malloc(n: u64) *void;
|
||||
|
||||
// os — process and filesystem facade. The body of each call lands
|
||||
// either in libwwrt.a (rt_syscall trampoline) or libc bindings,
|
||||
// depending on how the program was linked.
|
||||
@@ -102,6 +125,12 @@ export fn compare(a: instant, b: instant) i8 = {
|
||||
package os;
|
||||
|
||||
import time;
|
||||
// [[args]] allocates the []str view via the `alloc` builtin, whose malloc
|
||||
// lowers to rt_malloc only when the rt binding is in the bundle (mirror
|
||||
// lib/strings/strings.ww:30 — every alloc-using module imports rt). os is
|
||||
// bundled by ~every program, so without this a plain `ww build` of any
|
||||
// os-importing program links bare libc `malloc` (undefined). Task #17.
|
||||
import rt;
|
||||
|
||||
@symbol("rt_syscall") fn syscall0(num: nr) i64;
|
||||
@symbol("rt_syscall") fn syscall1(num: nr, a: i64) i64;
|
||||
@@ -500,6 +529,12 @@ export fn getdents64(fd: i32, buf: *u8, n: u64) i64 = {
|
||||
// We don't expose `rtenvp` directly; [[getenv]] is the only consumer.
|
||||
@symbol("rt_envp") fn rtenvp() **u8;
|
||||
|
||||
// rt_argc / rt_argv — runtime-side getters for the argc/argv captured by
|
||||
// rt/start.s at process entry (same DATAW-slot + TEXT-getter shape as
|
||||
// rt_envp). [[args]] is the only consumer.
|
||||
@symbol("rt_argc") fn rtargc() i64;
|
||||
@symbol("rt_argv") fn rtargv() **u8;
|
||||
|
||||
// getenv — POSIX getenv. Returns a borrowed `str` view over the value
|
||||
// bytes of the named environment variable, or void if the name is not
|
||||
// present. The view is valid for the process lifetime — the bytes
|
||||
@@ -548,6 +583,46 @@ export fn getenv(name: str) (str | void) = {
|
||||
return;
|
||||
};
|
||||
|
||||
// argsbuilt / argscache — build-once cache for [[args]]. drew ruling
|
||||
// (task #17): an explicit `built` sentinel, NOT len==0 overloading (a
|
||||
// real argv always has argv[0], but the sentinel keeps the contract
|
||||
// honest and decoupled from content). args() is loop-callable; under
|
||||
// ww's no-free model a per-call rebuild would leak the slice each call,
|
||||
// so the slice is materialised once and reused.
|
||||
let argsbuilt: bool = false;
|
||||
let argscache: []str;
|
||||
|
||||
// args — the process arguments as a borrowed []str. args[0] is the
|
||||
// program name; args[1..] are the invocation arguments. Each str views
|
||||
// the NUL-terminated argv bytes in place (valid for the process
|
||||
// lifetime), so the slice must not be mutated or freed by the caller.
|
||||
//
|
||||
// DIVERGENCE (Hare-fidelity, task #26): Hare's `os::args` is a `[]str`
|
||||
// GLOBAL populated by an @init that walks rt's argv
|
||||
// (ref/hare/os/+linux/start.ha). ww has no @init mechanism, so the
|
||||
// faithful global is not expressible; this is the fn-shaped equivalent
|
||||
// (Hare NAME kept, shape diverged). Revisit if @init lands (#26).
|
||||
export fn args() []str = {
|
||||
if (argsbuilt) { return argscache; };
|
||||
let argc: i32 = rtargc(): i32;
|
||||
let argv: **u8 = rtargv();
|
||||
let r: []str = alloc([], argc: u64)!;
|
||||
let i: i32 = 0;
|
||||
for (i < argc) {
|
||||
let c: *u8 = argv[i];
|
||||
let n: i32 = 0;
|
||||
for (c[n] != 0u8) { n += 1; };
|
||||
let s: str;
|
||||
s.ptr = c;
|
||||
s.len = n;
|
||||
append(r, s);
|
||||
i += 1;
|
||||
};
|
||||
argscache = r;
|
||||
argsbuilt = true;
|
||||
return r;
|
||||
};
|
||||
|
||||
// ---- stat / lstat / fstat / exists -----------------------------------
|
||||
//
|
||||
// Ports of Hare's stat family (ref/hare/fs/fs.ha:172,196 +
|
||||
@@ -3329,29 +3404,6 @@ export fn position(d: *decoder) i32 = {
|
||||
};
|
||||
|
||||
|
||||
// rt — runtime primitives exposed to ww programs.
|
||||
// Mirrors Hare's rt:: module placement (ref/hare/rt/).
|
||||
|
||||
package rt;
|
||||
|
||||
// malloc — mmap-backed page allocator. Untyped: `malloc(n)` returns a
|
||||
// `*void`; callers cast to the target type. Diverges from Hare: Hare
|
||||
// exposes `alloc` / `free` as typed language builtins that the
|
||||
// compiler lowers to rt::malloc/rt::free; ww has no such builtins,
|
||||
// so the rt-symbol surface is exposed directly. Stdlib callers that
|
||||
// need a typed allocation pattern wrap this with a cast plus a stored
|
||||
// capacity (see [[strings.dup]], [[memio.dynamic]]).
|
||||
//
|
||||
// OOM: rt_malloc is a bare mmap(MAP_ANON|MAP_PRIVATE) wrapper with no
|
||||
// error path. The raw Linux mmap syscall returns a negative errno cast
|
||||
// to `*void` on failure (e.g. `(void*)-12` for ENOMEM); the
|
||||
// `MAP_FAILED` (`(void*)-1`) value is a libc-wrapper convention that
|
||||
// rt_malloc doesn't apply. Neither `== nil` nor `== (void*)-1` catches
|
||||
// it; any deref of such a return faults. Today the stdlib does not
|
||||
// check; OOM faults on first dereference. A typed fallible variant is
|
||||
// a future task (task #39). ref/hare/rt/malloc.ha:27.
|
||||
@symbol("rt_malloc") export fn malloc(n: u64) *void;
|
||||
|
||||
// strings — operations over str ({ptr,len}). Hare port; see
|
||||
// ref/hare/strings/.
|
||||
//
|
||||
|
||||
@@ -95,6 +95,29 @@ export fn compare(a: instant, b: instant) i8 = {
|
||||
return 0i8;
|
||||
};
|
||||
|
||||
// rt — runtime primitives exposed to ww programs.
|
||||
// Mirrors Hare's rt:: module placement (ref/hare/rt/).
|
||||
|
||||
package rt;
|
||||
|
||||
// malloc — mmap-backed page allocator. Untyped: `malloc(n)` returns a
|
||||
// `*void`; callers cast to the target type. Diverges from Hare: Hare
|
||||
// exposes `alloc` / `free` as typed language builtins that the
|
||||
// compiler lowers to rt::malloc/rt::free; ww has no such builtins,
|
||||
// so the rt-symbol surface is exposed directly. Stdlib callers that
|
||||
// need a typed allocation pattern wrap this with a cast plus a stored
|
||||
// capacity (see [[strings.dup]], [[memio.dynamic]]).
|
||||
//
|
||||
// OOM: rt_malloc is a bare mmap(MAP_ANON|MAP_PRIVATE) wrapper with no
|
||||
// error path. The raw Linux mmap syscall returns a negative errno cast
|
||||
// to `*void` on failure (e.g. `(void*)-12` for ENOMEM); the
|
||||
// `MAP_FAILED` (`(void*)-1`) value is a libc-wrapper convention that
|
||||
// rt_malloc doesn't apply. Neither `== nil` nor `== (void*)-1` catches
|
||||
// it; any deref of such a return faults. Today the stdlib does not
|
||||
// check; OOM faults on first dereference. A typed fallible variant is
|
||||
// a future task (task #39). ref/hare/rt/malloc.ha:27.
|
||||
@symbol("rt_malloc") export fn malloc(n: u64) *void;
|
||||
|
||||
// os — process and filesystem facade. The body of each call lands
|
||||
// either in libwwrt.a (rt_syscall trampoline) or libc bindings,
|
||||
// depending on how the program was linked.
|
||||
@@ -102,6 +125,12 @@ export fn compare(a: instant, b: instant) i8 = {
|
||||
package os;
|
||||
|
||||
import time;
|
||||
// [[args]] allocates the []str view via the `alloc` builtin, whose malloc
|
||||
// lowers to rt_malloc only when the rt binding is in the bundle (mirror
|
||||
// lib/strings/strings.ww:30 — every alloc-using module imports rt). os is
|
||||
// bundled by ~every program, so without this a plain `ww build` of any
|
||||
// os-importing program links bare libc `malloc` (undefined). Task #17.
|
||||
import rt;
|
||||
|
||||
@symbol("rt_syscall") fn syscall0(num: nr) i64;
|
||||
@symbol("rt_syscall") fn syscall1(num: nr, a: i64) i64;
|
||||
@@ -500,6 +529,12 @@ export fn getdents64(fd: i32, buf: *u8, n: u64) i64 = {
|
||||
// We don't expose `rtenvp` directly; [[getenv]] is the only consumer.
|
||||
@symbol("rt_envp") fn rtenvp() **u8;
|
||||
|
||||
// rt_argc / rt_argv — runtime-side getters for the argc/argv captured by
|
||||
// rt/start.s at process entry (same DATAW-slot + TEXT-getter shape as
|
||||
// rt_envp). [[args]] is the only consumer.
|
||||
@symbol("rt_argc") fn rtargc() i64;
|
||||
@symbol("rt_argv") fn rtargv() **u8;
|
||||
|
||||
// getenv — POSIX getenv. Returns a borrowed `str` view over the value
|
||||
// bytes of the named environment variable, or void if the name is not
|
||||
// present. The view is valid for the process lifetime — the bytes
|
||||
@@ -548,6 +583,46 @@ export fn getenv(name: str) (str | void) = {
|
||||
return;
|
||||
};
|
||||
|
||||
// argsbuilt / argscache — build-once cache for [[args]]. drew ruling
|
||||
// (task #17): an explicit `built` sentinel, NOT len==0 overloading (a
|
||||
// real argv always has argv[0], but the sentinel keeps the contract
|
||||
// honest and decoupled from content). args() is loop-callable; under
|
||||
// ww's no-free model a per-call rebuild would leak the slice each call,
|
||||
// so the slice is materialised once and reused.
|
||||
let argsbuilt: bool = false;
|
||||
let argscache: []str;
|
||||
|
||||
// args — the process arguments as a borrowed []str. args[0] is the
|
||||
// program name; args[1..] are the invocation arguments. Each str views
|
||||
// the NUL-terminated argv bytes in place (valid for the process
|
||||
// lifetime), so the slice must not be mutated or freed by the caller.
|
||||
//
|
||||
// DIVERGENCE (Hare-fidelity, task #26): Hare's `os::args` is a `[]str`
|
||||
// GLOBAL populated by an @init that walks rt's argv
|
||||
// (ref/hare/os/+linux/start.ha). ww has no @init mechanism, so the
|
||||
// faithful global is not expressible; this is the fn-shaped equivalent
|
||||
// (Hare NAME kept, shape diverged). Revisit if @init lands (#26).
|
||||
export fn args() []str = {
|
||||
if (argsbuilt) { return argscache; };
|
||||
let argc: i32 = rtargc(): i32;
|
||||
let argv: **u8 = rtargv();
|
||||
let r: []str = alloc([], argc: u64)!;
|
||||
let i: i32 = 0;
|
||||
for (i < argc) {
|
||||
let c: *u8 = argv[i];
|
||||
let n: i32 = 0;
|
||||
for (c[n] != 0u8) { n += 1; };
|
||||
let s: str;
|
||||
s.ptr = c;
|
||||
s.len = n;
|
||||
append(r, s);
|
||||
i += 1;
|
||||
};
|
||||
argscache = r;
|
||||
argsbuilt = true;
|
||||
return r;
|
||||
};
|
||||
|
||||
// ---- stat / lstat / fstat / exists -----------------------------------
|
||||
//
|
||||
// Ports of Hare's stat family (ref/hare/fs/fs.ha:172,196 +
|
||||
@@ -3329,29 +3404,6 @@ export fn position(d: *decoder) i32 = {
|
||||
};
|
||||
|
||||
|
||||
// rt — runtime primitives exposed to ww programs.
|
||||
// Mirrors Hare's rt:: module placement (ref/hare/rt/).
|
||||
|
||||
package rt;
|
||||
|
||||
// malloc — mmap-backed page allocator. Untyped: `malloc(n)` returns a
|
||||
// `*void`; callers cast to the target type. Diverges from Hare: Hare
|
||||
// exposes `alloc` / `free` as typed language builtins that the
|
||||
// compiler lowers to rt::malloc/rt::free; ww has no such builtins,
|
||||
// so the rt-symbol surface is exposed directly. Stdlib callers that
|
||||
// need a typed allocation pattern wrap this with a cast plus a stored
|
||||
// capacity (see [[strings.dup]], [[memio.dynamic]]).
|
||||
//
|
||||
// OOM: rt_malloc is a bare mmap(MAP_ANON|MAP_PRIVATE) wrapper with no
|
||||
// error path. The raw Linux mmap syscall returns a negative errno cast
|
||||
// to `*void` on failure (e.g. `(void*)-12` for ENOMEM); the
|
||||
// `MAP_FAILED` (`(void*)-1`) value is a libc-wrapper convention that
|
||||
// rt_malloc doesn't apply. Neither `== nil` nor `== (void*)-1` catches
|
||||
// it; any deref of such a return faults. Today the stdlib does not
|
||||
// check; OOM faults on first dereference. A typed fallible variant is
|
||||
// a future task (task #39). ref/hare/rt/malloc.ha:27.
|
||||
@symbol("rt_malloc") export fn malloc(n: u64) *void;
|
||||
|
||||
// strings — operations over str ({ptr,len}). Hare port; see
|
||||
// ref/hare/strings/.
|
||||
//
|
||||
|
||||
@@ -9,7 +9,9 @@
|
||||
* rejected with "ww test: unknown flag" (rc 2); a lone -I/-o
|
||||
* is "ww test: -X needs an argument" (rc 2); -c/-o without a
|
||||
* single test file is "ww test: -c/-o need a single test file"
|
||||
* (rc 2, #17).
|
||||
* (rc 2, #17); a 2nd positional (fnmatch name-filter pattern)
|
||||
* in directory mode is "ww test: pattern needs a single test
|
||||
* file" (rc 2, #17).
|
||||
* Each row asserts the expected rc + stderr substring AND that the two
|
||||
* drivers are byte-identical (rule 10): the cstage parse_build_flags /
|
||||
* do_test must match the wwstage main.ww dobuild/dorun/dotest verbatim.
|
||||
@@ -46,6 +48,10 @@ static const struct row rows[] = {
|
||||
{ "test -o", 2, "ww test: -o needs an argument" },
|
||||
{ "test -o x", 2, "ww test: -c/-o need a single test file" },
|
||||
{ "test -c", 2, "ww test: -c/-o need a single test file" },
|
||||
/* #17: a 2nd positional is a fnmatch name-filter pattern, valid only
|
||||
* for a single test file/module; in directory mode (target ".") it
|
||||
* has no single binary to route to and is rejected (rc 2). */
|
||||
{ "test . zzz", 2, "ww test: pattern needs a single test file" },
|
||||
};
|
||||
|
||||
/* Run "<bin>/<drv> <args>" capturing rc + stderr text into err (NUL-
|
||||
|
||||
142
test/wcc/989_test_filter.c
Normal file
142
test/wcc/989_test_filter.c
Normal file
@@ -0,0 +1,142 @@
|
||||
/*
|
||||
* 989_test_filter — the `ww test <file> [pattern]` fnmatch NAME-FILTER
|
||||
* (task #17 commit-3, drew spec §d). Drives the full driver→binary→
|
||||
* lib/test path on BOTH driver twins (cstage `ww`, wwstage `ww_ww`):
|
||||
*
|
||||
* - no pattern → every @test runs (byte-for-byte the pre-filter
|
||||
* behavior: "N passed, M failed").
|
||||
* - "beta" → only the matching @test runs; the others are
|
||||
* absent from the per-test output.
|
||||
* - "ga*" → glob selects gamma only.
|
||||
* - "zzz" → zero matches → "No tests run", rc 0 (Hare ground
|
||||
* truth, ref/hare/test/+test.ha:114-117).
|
||||
*
|
||||
* Each row asserts rc + required/forbidden stdout substrings AND that the
|
||||
* two driver twins emit byte-identical stdout (rule 10). The fixture is
|
||||
* test/wcc/data/filter_fixture.ww (three trivially-passing @test fns
|
||||
* alpha/beta/gamma). The filter lives in lib/test run() reading os.args;
|
||||
* the -T synth + value table are unchanged, so 990-997 byte-id holds.
|
||||
*
|
||||
* Driver-arg ERROR forms (lone/dir-mode pattern) live in
|
||||
* 949_driver_flagargs. Light driver test, intermediates go to the
|
||||
* driver's own /tmp temp (rule 14), any NNN.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
struct row {
|
||||
const char *pat; /* pattern arg, or NULL for none */
|
||||
int rc; /* expected exit code */
|
||||
const char *must[4]; /* substrings required in stdout */
|
||||
const char *mustnot[4]; /* substrings forbidden in stdout */
|
||||
};
|
||||
|
||||
static const struct row rows[] = {
|
||||
/* no pattern → all three, full count */
|
||||
{ NULL, 0, { "alpha ... ok", "beta ... ok", "gamma ... ok",
|
||||
"3 passed, 0 failed" }, { NULL } },
|
||||
/* exact name → only beta */
|
||||
{ "beta", 0, { "beta ... ok", "1 passed, 0 failed", NULL },
|
||||
{ "alpha", "gamma", NULL } },
|
||||
/* glob → only gamma */
|
||||
{ "ga*", 0, { "gamma ... ok", "1 passed, 0 failed", NULL },
|
||||
{ "alpha", "beta", NULL } },
|
||||
/* no match → "No tests run", success */
|
||||
{ "zzz", 0, { "No tests run", NULL },
|
||||
{ "alpha", "beta", "gamma", "passed", NULL } },
|
||||
/* star matches everything */
|
||||
{ "*", 0, { "alpha ... ok", "beta ... ok", "gamma ... ok",
|
||||
"3 passed, 0 failed" }, { NULL } },
|
||||
};
|
||||
|
||||
/* Run "<bin>/<drv> test <fixture> [pat]" capturing rc + stdout into out
|
||||
* (NUL-terminated). Returns the child's exit code (or -1 on spawn fail). */
|
||||
static int
|
||||
run_drv(const char *bin, const char *drv, const char *pat,
|
||||
char *out, size_t outsz)
|
||||
{
|
||||
int pid = getpid();
|
||||
char outf[64], cmd[2048];
|
||||
snprintf(outf, sizeof outf, "/tmp/tf989_%d.out", pid);
|
||||
if (pat) {
|
||||
snprintf(cmd, sizeof cmd,
|
||||
"%s/%s test test/wcc/data/filter_fixture.ww '%s' >%s 2>/dev/null",
|
||||
bin, drv, pat, outf);
|
||||
} else {
|
||||
snprintf(cmd, sizeof cmd,
|
||||
"%s/%s test test/wcc/data/filter_fixture.ww >%s 2>/dev/null",
|
||||
bin, drv, outf);
|
||||
}
|
||||
int rc = system(cmd);
|
||||
if (rc == -1) { unlink(outf); return -1; }
|
||||
rc = WIFEXITED(rc) ? WEXITSTATUS(rc) : 1;
|
||||
out[0] = '\0';
|
||||
FILE *f = fopen(outf, "r");
|
||||
if (f) {
|
||||
size_t got = fread(out, 1, outsz - 1, f);
|
||||
out[got] = '\0';
|
||||
fclose(f);
|
||||
}
|
||||
unlink(outf);
|
||||
return rc;
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
char absbin[1024];
|
||||
if (bin[0] != '/') {
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||
bin = absbin;
|
||||
}
|
||||
|
||||
int fail = 0;
|
||||
size_t n = sizeof rows / sizeof rows[0];
|
||||
for (size_t i = 0; i < n; i++) {
|
||||
const char *pat = rows[i].pat ? rows[i].pat : "(none)";
|
||||
char cout[8192], wout[8192];
|
||||
int crc = run_drv(bin, "ww", rows[i].pat, cout, sizeof cout);
|
||||
int wrc = run_drv(bin, "ww_ww", rows[i].pat, wout, sizeof wout);
|
||||
|
||||
if (crc != rows[i].rc) {
|
||||
fprintf(stderr, "989 FAIL: ww pat=%s rc=%d want=%d\n",
|
||||
pat, crc, rows[i].rc);
|
||||
fail = 1;
|
||||
}
|
||||
for (int k = 0; k < 4 && rows[i].must[k]; k++)
|
||||
if (!strstr(cout, rows[i].must[k])) {
|
||||
fprintf(stderr, "989 FAIL: ww pat=%s stdout missing "
|
||||
"'%s' (got '%s')\n", pat, rows[i].must[k], cout);
|
||||
fail = 1;
|
||||
}
|
||||
for (int k = 0; k < 4 && rows[i].mustnot[k]; k++)
|
||||
if (strstr(cout, rows[i].mustnot[k])) {
|
||||
fprintf(stderr, "989 FAIL: ww pat=%s stdout has "
|
||||
"forbidden '%s' (got '%s')\n",
|
||||
pat, rows[i].mustnot[k], cout);
|
||||
fail = 1;
|
||||
}
|
||||
/* twin parity: ww_ww must match ww exactly (rc + stdout). */
|
||||
if (wrc != crc) {
|
||||
fprintf(stderr, "989 FAIL: pat=%s ww_ww rc=%d != ww rc=%d\n",
|
||||
pat, wrc, crc);
|
||||
fail = 1;
|
||||
}
|
||||
if (strcmp(wout, cout) != 0) {
|
||||
fprintf(stderr, "989 FAIL: pat=%s ww_ww stdout '%s' != ww '%s'\n",
|
||||
pat, wout, cout);
|
||||
fail = 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (fail) return 1;
|
||||
printf("test name-filter: %zu rows, ww==ww_ww pinned\n", n);
|
||||
return 0;
|
||||
}
|
||||
13
test/wcc/data/filter_fixture.ww
Normal file
13
test/wcc/data/filter_fixture.ww
Normal file
@@ -0,0 +1,13 @@
|
||||
// @test fixture for the fnmatch name-filter (task #17 commit-3). Three
|
||||
// trivially-passing @test fns with distinct names so a glob selects a
|
||||
// known subset; 989_test_filter drives `ww test <this> [pattern]` and
|
||||
// asserts which "<name> ... ok" lines appear. Bodies are empty (clean
|
||||
// return = pass); the test observes SELECTION, not assertion outcomes.
|
||||
|
||||
package filterfix;
|
||||
|
||||
@test fn alpha() void = { };
|
||||
|
||||
@test fn beta() void = { };
|
||||
|
||||
@test fn gamma() void = { };
|
||||
Reference in New Issue
Block a user