test: port the qualstructlit AST proof to ww; retire 848_xmod_qualstructlit_run
Only the ast_proof leg was unowned: wwdump -a / wwdump_ww -a dump byte-id plus the ordered dot-over-structlit-over-tname node shape for a trailing op on a qualified struct literal (inexpressible as a runtime scenario while the #77 literal-temporary cgen gap holds; the multilevel row pins the source-order a.b.C tname flatten). The three build/run scenarios are owned by the r84_qualstruct_* fixtures plus the test-data-byteid blanket — their vestigial want_exit fields were already dead in the carrier.
This commit is contained in:
2
Makefile
2
Makefile
@@ -409,7 +409,7 @@ OBJECT_WW_TARGETS = $(OBJECT_WW_TESTS:%=wwtest/%)
|
|||||||
# under test-compiler.
|
# under test-compiler.
|
||||||
MISC_WW_TESTS = test/misc/arrglob_test.ww test/misc/packedwwi_test.ww \
|
MISC_WW_TESTS = test/misc/arrglob_test.ww test/misc/packedwwi_test.ww \
|
||||||
test/misc/reject_test.ww test/misc/heldasym_test.ww \
|
test/misc/reject_test.ww test/misc/heldasym_test.ww \
|
||||||
test/misc/stampdiag_test.ww
|
test/misc/stampdiag_test.ww test/misc/qualast_test.ww
|
||||||
MISC_WW_TARGETS = $(MISC_WW_TESTS:%=wwtest/%)
|
MISC_WW_TARGETS = $(MISC_WW_TESTS:%=wwtest/%)
|
||||||
BOOTSTRAP_WRAPPER_SOURCES = test/wcc/950_selfcheck.c \
|
BOOTSTRAP_WRAPPER_SOURCES = test/wcc/950_selfcheck.c \
|
||||||
test/wcc/991_w6a_ww.c \
|
test/wcc/991_w6a_ww.c \
|
||||||
|
|||||||
95
test/misc/qualast_test.ww
Normal file
95
test/misc/qualast_test.ww
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
package qualast_test;
|
||||||
|
|
||||||
|
// The ast_proof remainder of the retired native carrier
|
||||||
|
// test/wcc/848_xmod_qualstructlit_run.c (#76 qualified struct
|
||||||
|
// literals). The three build/run scenarios are owned by the
|
||||||
|
// r84_qualstruct_{fields,ret_assign,nested} fixtures plus the
|
||||||
|
// test-data-byteid blanket; what no fixture can host is the PARSE
|
||||||
|
// node-shape proof for a TRAILING op on a qualified struct literal —
|
||||||
|
// `pkg.point{...}.x` trips the pre-existing cgen literal-temporary
|
||||||
|
// gap (#77), so it can never ride a runtime scenario.
|
||||||
|
//
|
||||||
|
// Per row: `wwdump -a` (parse-only) on both stages, rc 0, dumps
|
||||||
|
// byte-identical (rule 10), then ordered substrings prove
|
||||||
|
// dot-over-structlit-over-tname — the parser's postfix loop stayed
|
||||||
|
// live past the `}` so `.x` wrapped the literal, and the dotted path
|
||||||
|
// flattened into ONE source-order N_TNAME (parsetype's dotted
|
||||||
|
// collapse, byte-identical across parse positions). astprint resolves
|
||||||
|
// no names, so `pkg` / `a.b.C` need not name a real import.
|
||||||
|
|
||||||
|
import os;
|
||||||
|
import os.exec;
|
||||||
|
import strings;
|
||||||
|
import testenv;
|
||||||
|
import time;
|
||||||
|
|
||||||
|
fn fail(label: str, why: str) void = {
|
||||||
|
let m: str = strings.concat("qualast FAIL: ", label, " -- ", why,
|
||||||
|
"\n");
|
||||||
|
os.write(2, m.ptr, m.len: u64);
|
||||||
|
assert(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
fn tmo() time.duration = {
|
||||||
|
return (180i64 * (time.second: i64)): time.duration;
|
||||||
|
};
|
||||||
|
|
||||||
|
fn dumpstage(td: str, label: str, stage: str, tool: str) str = {
|
||||||
|
let av: []str = [testenv.driver(tool), "-a",
|
||||||
|
strings.concat(td, "/a.ww")];
|
||||||
|
let co: testenv.commandout;
|
||||||
|
testenv.runcommand(td, td, stage, av, tmo(), &co);
|
||||||
|
if (co.termination != exec.termination.EXIT || co.code != 0) {
|
||||||
|
fail(label, strings.concat(stage, " ", tool, " -a failed"));
|
||||||
|
};
|
||||||
|
return co.stdout;
|
||||||
|
};
|
||||||
|
|
||||||
|
// The needles must occur in order (tree nesting without coupling to
|
||||||
|
// astprint's exact indentation).
|
||||||
|
fn ordered(hay: str, needles: []str) bool = {
|
||||||
|
let from: i32 = 0;
|
||||||
|
let i: i32 = 0;
|
||||||
|
for (i < needles.len) {
|
||||||
|
let rest: str = strings.sub(hay, from, hay.len);
|
||||||
|
let p: i32 = testenv.pos(rest, needles[i]);
|
||||||
|
if (p < 0) { return false; };
|
||||||
|
from = from + p + 1;
|
||||||
|
i += 1;
|
||||||
|
};
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
fn astrow(label: str, src: str, needles: []str) void = {
|
||||||
|
let td: str = testenv.fresh();
|
||||||
|
testenv.writefile(strings.concat(td, "/a.ww"), src);
|
||||||
|
let cs: str = dumpstage(td, label, "cstage", "wwdump");
|
||||||
|
let ws: str = dumpstage(td, label, "wwstage", "wwdump_ww");
|
||||||
|
if (!testenv.same(cs, ws)) {
|
||||||
|
fail(label, "cs/ww AST dumps DIFFER (rule-10)");
|
||||||
|
};
|
||||||
|
if (!ordered(cs, needles)) {
|
||||||
|
fail(label, strings.concat("AST node shape wrong (trailing ",
|
||||||
|
"op did not wrap the qualified struct literal)"));
|
||||||
|
};
|
||||||
|
testenv.clean(td);
|
||||||
|
};
|
||||||
|
|
||||||
|
@test fn astproof() void = {
|
||||||
|
let trailing: []str = ["(dot \"x\"", "(structlit",
|
||||||
|
"(tname \"pkg.point\""];
|
||||||
|
astrow("trailing", strings.concat(
|
||||||
|
"package main;\n",
|
||||||
|
"export fn f() i64 = {\n",
|
||||||
|
"\tlet v: i64 = pkg.point { x = 1i64, y = 2i64 }.x;\n",
|
||||||
|
"\treturn v;\n",
|
||||||
|
"};\n"), trailing);
|
||||||
|
let multilevel: []str = ["(dot \"x\"", "(structlit",
|
||||||
|
"(tname \"a.b.C\""];
|
||||||
|
astrow("multilevel", strings.concat(
|
||||||
|
"package main;\n",
|
||||||
|
"export fn g() i64 = {\n",
|
||||||
|
"\tlet v: i64 = a.b.C { x = 1i64 }.x;\n",
|
||||||
|
"\treturn v;\n",
|
||||||
|
"};\n"), multilevel);
|
||||||
|
};
|
||||||
@@ -1,440 +0,0 @@
|
|||||||
/*
|
|
||||||
* 848_xmod_qualstructlit_run — task #76. Pins that a qualified struct
|
|
||||||
* literal `pkg.Type{...}` (the type named through an imported module)
|
|
||||||
* constructs end-to-end, on BOTH stages, byte-identically (rule-10).
|
|
||||||
*
|
|
||||||
* THE BUG (both stages, pre-fix a PARSE reject): `pkg.Type` parses to
|
|
||||||
* two different node shapes by position. In DECL position parsetype
|
|
||||||
* collapses the dotted path into ONE N_TNAME ("pkg.Type") and the
|
|
||||||
* checker resolves it via the strrchr-leaf path. In LITERAL position
|
|
||||||
* the dotted path folds into an N_DOT chain; the struct-literal handoff
|
|
||||||
* handed that N_DOT to the checker, whose resolve_type has N_IDENT and
|
|
||||||
* N_TNAME arms but no N_DOT arm — "expected type expression". So a
|
|
||||||
* qualified struct literal was unparseable, blocking wcc's own use of
|
|
||||||
* `syntax.tok{...}`-style construction across the syntax->wcc boundary
|
|
||||||
* (#75).
|
|
||||||
*
|
|
||||||
* THE FIX (#76, PARSER root, zero new checker arms): at the struct-lit
|
|
||||||
* handoff, if the typeref is an N_DOT chain rooted at a bare ident with
|
|
||||||
* all-identifier components, FLATTEN it into one N_TNAME whose str joins
|
|
||||||
* the chain in SOURCE order ("pkg.Type") — byte-identical to parsetype's
|
|
||||||
* dotted collapse — so the existing N_TNAME resolver arm handles it. The
|
|
||||||
* two parse positions then converge on one canonical qualified-type
|
|
||||||
* representation. cstage flattens in parseprimary (peek-driven dotted
|
|
||||||
* fold); the ww parser has no token-peek and folds dotted chains in
|
|
||||||
* parsepostfix, so its flatten lives there (faithful adaptation, same
|
|
||||||
* canonical N_TNAME output). Bare `Foo{}` keeps the N_IDENT fast-path.
|
|
||||||
*
|
|
||||||
* scenario | shape | exit | byte-id
|
|
||||||
* ----------+---------------------------------------------+------+--------
|
|
||||||
* fields | several `pkg.point{x=..,y=..}` with | 37 | cs==ww
|
|
||||||
* | differing field values; each field | |
|
|
||||||
* | verified, single derived exit | |
|
|
||||||
* ret_assign| qualified lit in a `return` and an `=` | 27 | cs==ww
|
|
||||||
* | reassignment position (`return pkg.point | |
|
|
||||||
* | {...}` / `a = pkg.point{...}`) | |
|
|
||||||
* nested | nested qualified lit | 18 | cs==ww
|
|
||||||
* | `pkg.rect{lo=pkg.point{...},hi=...}` | |
|
|
||||||
*
|
|
||||||
* A trailing op on a struct-literal temporary — `pkg.point{...}.x` — is
|
|
||||||
* rejected by cgen the same way the BARE `point{...}.x` form is (a
|
|
||||||
* pre-existing literal-temporary lowering gap, #77, orthogonal to #76),
|
|
||||||
* so it can't ride a runtime scenario. But that trailing op is exactly
|
|
||||||
* what the in-loop-`continue` requirement (the parser must stay in the
|
|
||||||
* postfix loop so `.x` wraps the literal, not terminate at the `}`) bites
|
|
||||||
* on. The ast_proof scenarios cover it WITHOUT cgen: dump the parse AST
|
|
||||||
* (`wwdump -a`, parse-only) on BOTH stages and assert (1) the dumps are
|
|
||||||
* byte-identical (rule-10), (2) the node shape is dot-over-structlit-over-
|
|
||||||
* tname (proving `continue` kept the loop live), and (3) a multi-level
|
|
||||||
* path `a.b.C` joins SOURCE order in the flattened tname (req-c).
|
|
||||||
*
|
|
||||||
* GATE POLARITY: must stay GREEN. Pre-fix the driver build fails (parse
|
|
||||||
* reject). Red means a qualified struct literal stopped parsing again,
|
|
||||||
* or the two stages' .s diverged (rule-10 break).
|
|
||||||
*
|
|
||||||
* Builds its own 2-module fixtures in a private mktemp dir, so all
|
|
||||||
* intermediates (the linked binary, the .s probes) land OFF the
|
|
||||||
* source tree (CLAUDE.md rule 14).
|
|
||||||
*/
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <errno.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <sys/stat.h>
|
|
||||||
#include <sys/wait.h>
|
|
||||||
|
|
||||||
static int
|
|
||||||
runwait(const char *cmd)
|
|
||||||
{
|
|
||||||
int rc = system(cmd);
|
|
||||||
if (rc == -1) return -1;
|
|
||||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
slurp_eq(const char *a, const char *b)
|
|
||||||
{
|
|
||||||
FILE *fa = fopen(a, "rb");
|
|
||||||
FILE *fb = fopen(b, "rb");
|
|
||||||
if (!fa || !fb) { if (fa) fclose(fa); if (fb) fclose(fb); return -1; }
|
|
||||||
int rc = 0;
|
|
||||||
for (;;) {
|
|
||||||
int ca = fgetc(fa);
|
|
||||||
int cb = fgetc(fb);
|
|
||||||
if (ca != cb) { rc = -1; break; }
|
|
||||||
if (ca == EOF) break;
|
|
||||||
}
|
|
||||||
fclose(fa); fclose(fb);
|
|
||||||
return rc;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* slurp a (small) text file into buf; returns bytes read, -1 on error. */
|
|
||||||
static long
|
|
||||||
slurp(const char *path, char *buf, long cap)
|
|
||||||
{
|
|
||||||
FILE *f = fopen(path, "rb");
|
|
||||||
if (!f) return -1;
|
|
||||||
long n = (long)fread(buf, 1, (size_t)cap - 1, f);
|
|
||||||
int truncated = !feof(f);
|
|
||||||
fclose(f);
|
|
||||||
if (n < 0 || truncated) return -1;
|
|
||||||
buf[n] = '\0';
|
|
||||||
return n;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* substrings must occur in the given order (proves tree nesting without
|
|
||||||
* coupling to astprint's exact indentation). */
|
|
||||||
static int
|
|
||||||
ordered(const char *hay, const char *const *needles)
|
|
||||||
{
|
|
||||||
const char *p = hay;
|
|
||||||
for (int i = 0; needles[i]; i++) {
|
|
||||||
const char *q = strstr(p, needles[i]);
|
|
||||||
if (!q) return -1;
|
|
||||||
p = q + 1;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ast_proof: req-(a)/(c). Dump the parse AST of a TRAILING-op qualified
|
|
||||||
* struct literal on both stages (wwdump -a, parse-only — sidesteps the #77
|
|
||||||
* cgen gap), assert byte-id (rule-10) and the dot-over-structlit-over-tname
|
|
||||||
* ordering. Single-file fixtures: astprint resolves no names, so the
|
|
||||||
* `pkg`/`a.b.C` paths need not name a real import. */
|
|
||||||
struct ast_case {
|
|
||||||
const char *label;
|
|
||||||
const char *src;
|
|
||||||
const char *const *order; /* NULL-terminated ordered substrings */
|
|
||||||
};
|
|
||||||
|
|
||||||
static int
|
|
||||||
run_ast_proof(const char *wwdump, const char *wwdump_ww,
|
|
||||||
const struct ast_case *ac)
|
|
||||||
{
|
|
||||||
char dir[] = "/tmp/ww848a_XXXXXX";
|
|
||||||
if (mkdtemp(dir) == NULL) {
|
|
||||||
fprintf(stderr, "848-ast[%s]: mkdtemp failed\n", ac->label);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
char src[1024], cs[1024], ww[1024], cmd[4096];
|
|
||||||
int rc = 0;
|
|
||||||
snprintf(src, sizeof src, "%s/a.ww", dir);
|
|
||||||
FILE *f = fopen(src, "wb");
|
|
||||||
if (!f) { fprintf(stderr, "848-ast[%s]: write\n", ac->label);
|
|
||||||
rc = -1; goto done; }
|
|
||||||
int writebad = fputs(ac->src, f) == EOF || ferror(f);
|
|
||||||
if (fclose(f) != 0) writebad = 1;
|
|
||||||
if (writebad) {
|
|
||||||
fprintf(stderr, "848-ast[%s]: write\n", ac->label);
|
|
||||||
rc = -1; goto done;
|
|
||||||
}
|
|
||||||
|
|
||||||
snprintf(cs, sizeof cs, "%s/cs.ast", dir);
|
|
||||||
snprintf(ww, sizeof ww, "%s/ww.ast", dir);
|
|
||||||
snprintf(cmd, sizeof cmd, "%s -a %s > %s 2>/dev/null", wwdump, src, cs);
|
|
||||||
if (runwait(cmd) != 0) {
|
|
||||||
fprintf(stderr, "848-ast[%s]: cstage wwdump -a failed\n", ac->label);
|
|
||||||
rc = -1; goto done;
|
|
||||||
}
|
|
||||||
snprintf(cmd, sizeof cmd, "%s -a %s > %s 2>/dev/null", wwdump_ww, src, ww);
|
|
||||||
if (runwait(cmd) != 0) {
|
|
||||||
fprintf(stderr, "848-ast[%s]: wwstage wwdump_ww -a failed\n",
|
|
||||||
ac->label);
|
|
||||||
rc = -1; goto done;
|
|
||||||
}
|
|
||||||
if (slurp_eq(cs, ww) != 0) {
|
|
||||||
fprintf(stderr, "848-ast[%s]: cs/ww AST dumps DIFFER (rule-10)\n",
|
|
||||||
ac->label);
|
|
||||||
rc = -1;
|
|
||||||
}
|
|
||||||
char buf[65536];
|
|
||||||
if (slurp(cs, buf, (long)sizeof buf) < 0) {
|
|
||||||
fprintf(stderr, "848-ast[%s]: slurp dump\n", ac->label);
|
|
||||||
rc = -1; goto done;
|
|
||||||
}
|
|
||||||
if (ordered(buf, ac->order) != 0) {
|
|
||||||
fprintf(stderr, "848-ast[%s]: AST node shape wrong (trailing op "
|
|
||||||
"did not wrap the qualified struct literal — req-a/c)\n",
|
|
||||||
ac->label);
|
|
||||||
rc = -1;
|
|
||||||
}
|
|
||||||
done:
|
|
||||||
{
|
|
||||||
int cleanfail = 0;
|
|
||||||
snprintf(src, sizeof src, "%s/a.ww", dir);
|
|
||||||
if (unlink(src) != 0 && errno != ENOENT) cleanfail = 1;
|
|
||||||
snprintf(cs, sizeof cs, "%s/cs.ast", dir);
|
|
||||||
if (unlink(cs) != 0 && errno != ENOENT) cleanfail = 1;
|
|
||||||
snprintf(ww, sizeof ww, "%s/ww.ast", dir);
|
|
||||||
if (unlink(ww) != 0 && errno != ENOENT) cleanfail = 1;
|
|
||||||
if (rmdir(dir) != 0) cleanfail = 1;
|
|
||||||
if (cleanfail) {
|
|
||||||
fprintf(stderr, "848-ast[%s]: workspace cleanup failed\n",
|
|
||||||
ac->label);
|
|
||||||
if (rc == 0) rc = -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return rc;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* trailing `.x` must wrap the qualified literal: dot -> structlit -> tname. */
|
|
||||||
static const char *const trailing_order[] = {
|
|
||||||
"(dot \"x\"", "(structlit", "(tname \"pkg.point\"", NULL
|
|
||||||
};
|
|
||||||
/* a multi-level `a.b.C` path flattens to a source-order tname. */
|
|
||||||
static const char *const multilevel_order[] = {
|
|
||||||
"(dot \"x\"", "(structlit", "(tname \"a.b.C\"", NULL
|
|
||||||
};
|
|
||||||
|
|
||||||
static const struct ast_case ast_cases[] = {
|
|
||||||
{ "trailing",
|
|
||||||
"package main;\n"
|
|
||||||
"export fn f() i64 = {\n"
|
|
||||||
" let v: i64 = pkg.point { x = 1i64, y = 2i64 }.x;\n"
|
|
||||||
" return v;\n"
|
|
||||||
"};\n",
|
|
||||||
trailing_order },
|
|
||||||
{ "multilevel",
|
|
||||||
"package main;\n"
|
|
||||||
"export fn g() i64 = {\n"
|
|
||||||
" let v: i64 = a.b.C { x = 1i64 }.x;\n"
|
|
||||||
" return v;\n"
|
|
||||||
"};\n",
|
|
||||||
multilevel_order },
|
|
||||||
};
|
|
||||||
|
|
||||||
struct file { const char *name; const char *src; };
|
|
||||||
|
|
||||||
struct scenario {
|
|
||||||
const char *label;
|
|
||||||
const struct file *files; /* name==NULL terminates */
|
|
||||||
int want_exit;
|
|
||||||
};
|
|
||||||
|
|
||||||
/* shared package: two struct types, the second nesting the first. */
|
|
||||||
#define PKG_WW \
|
|
||||||
{ "pkg.ww", \
|
|
||||||
"package pkg;\n" \
|
|
||||||
"export type point = struct { x: i64, y: i64 };\n" \
|
|
||||||
"export type rect = struct { lo: point, hi: point };\n" }
|
|
||||||
|
|
||||||
/* ---- fields: several qualified lits, differing values, verify each -- */
|
|
||||||
static const struct file fields_files[] = {
|
|
||||||
PKG_WW,
|
|
||||||
{ "main.ww",
|
|
||||||
"package main;\n"
|
|
||||||
"import pkg;\n"
|
|
||||||
"export fn main() i32 = {\n"
|
|
||||||
" let a: pkg.point = pkg.point { x = 3i64, y = 4i64 };\n"
|
|
||||||
" let b: pkg.point = pkg.point { x = 10i64, y = 20i64 };\n"
|
|
||||||
" if (a.x != 3i64) { return 1; };\n"
|
|
||||||
" if (a.y != 4i64) { return 2; };\n"
|
|
||||||
" if (b.x != 10i64) { return 3; };\n"
|
|
||||||
" if (b.y != 20i64) { return 4; };\n"
|
|
||||||
" return (a.x + a.y + b.x + b.y): i32;\n" /* 3+4+10+20 = 37 */
|
|
||||||
"};\n" },
|
|
||||||
{ NULL, NULL }
|
|
||||||
};
|
|
||||||
|
|
||||||
/* ---- ret_assign: a qualified struct literal in `return` position (the
|
|
||||||
* callee builds `pkg.point{...}` and returns it) and in `=` reassignment
|
|
||||||
* position (`a = pkg.point{...}`). Both are cgen-supported aggregate
|
|
||||||
* stores, so this pins the new parser arm firing outside let-init too. */
|
|
||||||
static const struct file ret_assign_files[] = {
|
|
||||||
PKG_WW,
|
|
||||||
{ "main.ww",
|
|
||||||
"package main;\n"
|
|
||||||
"import pkg;\n"
|
|
||||||
"fn mk(vx: i64, vy: i64) pkg.point = {\n"
|
|
||||||
" return pkg.point { x = vx, y = vy };\n"
|
|
||||||
"};\n"
|
|
||||||
"export fn main() i32 = {\n"
|
|
||||||
" let a: pkg.point = mk(8i64, 2i64);\n"
|
|
||||||
" a = pkg.point { x = 15i64, y = 5i64 };\n"
|
|
||||||
" if (a.x != 15i64) { return 1; };\n"
|
|
||||||
" if (a.y != 5i64) { return 2; };\n"
|
|
||||||
" let b: pkg.point = mk(3i64, 4i64);\n"
|
|
||||||
" if (b.x != 3i64) { return 3; };\n"
|
|
||||||
" return (a.x + a.y + b.x + b.y): i32;\n" /* 15+5+3+4 = 27 */
|
|
||||||
"};\n" },
|
|
||||||
{ NULL, NULL }
|
|
||||||
};
|
|
||||||
|
|
||||||
/* ---- nested: a qualified lit whose fields are themselves qualified
|
|
||||||
* lits (`pkg.rect{ lo = pkg.point{...}, hi = pkg.point{...} }`). */
|
|
||||||
static const struct file nested_files[] = {
|
|
||||||
PKG_WW,
|
|
||||||
{ "main.ww",
|
|
||||||
"package main;\n"
|
|
||||||
"import pkg;\n"
|
|
||||||
"export fn main() i32 = {\n"
|
|
||||||
" let r: pkg.rect = pkg.rect {\n"
|
|
||||||
" lo = pkg.point { x = 1i64, y = 2i64 },\n"
|
|
||||||
" hi = pkg.point { x = 7i64, y = 5i64 }\n"
|
|
||||||
" };\n"
|
|
||||||
" if (r.lo.x != 1i64) { return 1; };\n"
|
|
||||||
" if (r.hi.y != 5i64) { return 2; };\n"
|
|
||||||
" return ((r.hi.x - r.lo.x) * (r.hi.y - r.lo.y)): i32;\n" /* 6*3=18 */
|
|
||||||
"};\n" },
|
|
||||||
{ NULL, NULL }
|
|
||||||
};
|
|
||||||
|
|
||||||
static const struct scenario scenarios[] = {
|
|
||||||
{ "fields", fields_files, 37 },
|
|
||||||
{ "ret_assign", ret_assign_files, 27 },
|
|
||||||
{ "nested", nested_files, 18 },
|
|
||||||
};
|
|
||||||
|
|
||||||
static int
|
|
||||||
run_scenario(const char *bin, const char *cdrv, const char *wdrv,
|
|
||||||
const struct scenario *sc)
|
|
||||||
{
|
|
||||||
(void)bin;
|
|
||||||
char dir[] = "/tmp/ww848_XXXXXX";
|
|
||||||
if (mkdtemp(dir) == NULL) {
|
|
||||||
fprintf(stderr, "848[%s]: mkdtemp failed\n", sc->label);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
char path[1024], cmd[4096];
|
|
||||||
int rc = 0;
|
|
||||||
|
|
||||||
for (int i = 0; sc->files[i].name; i++) {
|
|
||||||
snprintf(path, sizeof path, "%s/%s", dir, sc->files[i].name);
|
|
||||||
FILE *f = fopen(path, "wb");
|
|
||||||
if (!f) { fprintf(stderr, "848[%s]: write %s\n", sc->label,
|
|
||||||
sc->files[i].name); rc = -1; goto done; }
|
|
||||||
int writebad = fputs(sc->files[i].src, f) == EOF || ferror(f);
|
|
||||||
if (fclose(f) != 0) writebad = 1;
|
|
||||||
if (writebad) {
|
|
||||||
fprintf(stderr, "848[%s]: write %s\n", sc->label,
|
|
||||||
sc->files[i].name);
|
|
||||||
rc = -1; goto done;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Runtime values moved to wwfixtures. These builds intentionally keep
|
|
||||||
* their caller-owned compiler artifacts for the byte assertion below. */
|
|
||||||
snprintf(cmd, sizeof cmd,
|
|
||||||
"cd %s && %s build -I %s -o %s/main %s/main.ww",
|
|
||||||
dir, cdrv, dir, dir, dir);
|
|
||||||
if (runwait(cmd) != 0) {
|
|
||||||
fprintf(stderr, "848[%s]: cstage build failed "
|
|
||||||
"(qualified struct literal parse reject?)\n", sc->label);
|
|
||||||
rc = -1; goto done;
|
|
||||||
}
|
|
||||||
/* rule-10 discriminator: this colocated two-file fixture composes into
|
|
||||||
* the exact retained __root.s artifact, which must be byte-identical
|
|
||||||
* across driver stages. */
|
|
||||||
char cs_s[1024], ws_s[1024];
|
|
||||||
snprintf(cmd, sizeof cmd,
|
|
||||||
"cd %s && %s build -I %s -o %s/mainww %s/main.ww",
|
|
||||||
dir, wdrv, dir, dir, dir);
|
|
||||||
if (runwait(cmd) != 0) {
|
|
||||||
fprintf(stderr, "848[%s]: ww_ww build failed "
|
|
||||||
"(qualified struct literal parse reject?)\n", sc->label);
|
|
||||||
rc = -1; goto done;
|
|
||||||
}
|
|
||||||
snprintf(cs_s, sizeof cs_s, "%s/main.sepwork/__root.s", dir);
|
|
||||||
snprintf(ws_s, sizeof ws_s, "%s/mainww.sepwork/__root.s", dir);
|
|
||||||
if (slurp_eq(cs_s, ws_s) != 0) {
|
|
||||||
fprintf(stderr, "848[%s]: cs.s/ww.s DIFFER (rule-10 byte-id "
|
|
||||||
"violation)\n", sc->label);
|
|
||||||
rc = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
done:
|
|
||||||
{
|
|
||||||
int cleanfail = 0;
|
|
||||||
for (int i = 0; sc->files[i].name; i++) {
|
|
||||||
snprintf(path, sizeof path, "%s/%s", dir,
|
|
||||||
sc->files[i].name);
|
|
||||||
if (unlink(path) != 0 && errno != ENOENT) cleanfail = 1;
|
|
||||||
}
|
|
||||||
const char *files[] = { "main", "mainww", NULL };
|
|
||||||
for (int i = 0; files[i]; i++) {
|
|
||||||
snprintf(path, sizeof path, "%s/%s", dir, files[i]);
|
|
||||||
if (unlink(path) != 0 && errno != ENOENT) cleanfail = 1;
|
|
||||||
}
|
|
||||||
snprintf(path, sizeof path, "%s/main.sepwork", dir);
|
|
||||||
snprintf(cmd, sizeof cmd, "rm -rf %s", path);
|
|
||||||
if (runwait(cmd) != 0) cleanfail = 1;
|
|
||||||
snprintf(path, sizeof path, "%s/mainww.sepwork", dir);
|
|
||||||
snprintf(cmd, sizeof cmd, "rm -rf %s", path);
|
|
||||||
if (runwait(cmd) != 0) cleanfail = 1;
|
|
||||||
if (rmdir(dir) != 0) cleanfail = 1;
|
|
||||||
if (cleanfail) {
|
|
||||||
fprintf(stderr, "848[%s]: workspace cleanup failed\n",
|
|
||||||
sc->label);
|
|
||||||
if (rc == 0) rc = -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return rc;
|
|
||||||
}
|
|
||||||
|
|
||||||
int
|
|
||||||
main(void)
|
|
||||||
{
|
|
||||||
const char *bin = getenv("BIN");
|
|
||||||
if (!bin) bin = "out/bin";
|
|
||||||
char absbin[2048];
|
|
||||||
if (bin[0] != '/') {
|
|
||||||
char cwd[1024];
|
|
||||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
|
||||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
|
||||||
bin = absbin;
|
|
||||||
}
|
|
||||||
|
|
||||||
char cdrv[2100], wdrv[2100], wwdump[2100], wwdump_ww[2100];
|
|
||||||
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
|
|
||||||
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
|
|
||||||
snprintf(wwdump, sizeof wwdump, "%s/wwdump", bin);
|
|
||||||
snprintf(wwdump_ww, sizeof wwdump_ww, "%s/wwdump_ww", bin);
|
|
||||||
if (access(wdrv, X_OK) != 0 || access(wwdump_ww, X_OK) != 0) {
|
|
||||||
fprintf(stderr, "848: ww_ww/wwdump_ww missing — cannot run the "
|
|
||||||
"cs==ww byte-id gate (the whole point of this test)\n");
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
int n = (int)(sizeof scenarios / sizeof scenarios[0]);
|
|
||||||
int na = (int)(sizeof ast_cases / sizeof ast_cases[0]);
|
|
||||||
int fail = 0;
|
|
||||||
for (int i = 0; i < n; i++) {
|
|
||||||
if (run_scenario(bin, cdrv, wdrv, &scenarios[i]) != 0)
|
|
||||||
fail++;
|
|
||||||
}
|
|
||||||
for (int i = 0; i < na; i++) {
|
|
||||||
if (run_ast_proof(wwdump, wwdump_ww, &ast_cases[i]) != 0)
|
|
||||||
fail++;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fail) {
|
|
||||||
fprintf(stderr, "848_xmod_qualstructlit: %d/%d checks failed\n",
|
|
||||||
fail, n + na);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
printf("848_xmod_qualstructlit: %d/%d ok\n", n + na, n + na);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user