#include "gc.h" #include #include #include static int slurp(const char *path, char **outbuf, u64 *outlen) { FILE *f = fopen(path, "rb"); if (f == NULL) return -1; fseek(f, 0, SEEK_END); long n = ftell(f); fseek(f, 0, SEEK_SET); if (n < 0) { fclose(f); return -1; } char *b = malloc((size_t)n + 1); if (b == NULL) { fclose(f); return -1; } if (fread(b, 1, (size_t)n, f) != (size_t)n) { free(b); fclose(f); return -1; } b[n] = '\0'; fclose(f); *outbuf = b; *outlen = (u64)n; return 0; } static int export_owner_matches(const char *buf, u64 len, const char *path) { static const char prefix[] = "//ww:module "; size_t pn = strlen(path); size_t need = sizeof prefix - 1 + pn + 1; return len >= need && memcmp(buf, prefix, sizeof prefix - 1) == 0 && memcmp(buf + sizeof prefix - 1, path, pn) == 0 && buf[need - 1] == '\n'; } struct importin { const char *path; const char *file; char *buf; u64 len; Node *ast; }; struct importmap { const char *source; const char *path; int seen; }; static Node * parseinput(Arena *a, const char *file, char *buf, u64 len, const char *mod, const char *testsupport, int commandpackage, int *bad) { Lex l; Parser p; lexinit(&l, a, file, buf, len); parserinit(&p, a, &l); p.testmodule = testsupport; p.commandpackage = commandpackage; if (mod != NULL) { p.pathmod = mod; p.curmod = mod; } Node *f = parsefile(&p); *bad = l.errs || p.errs; return f; } /* Export data carries canonical owner and declared package name separately. * Search every direct interface's package-clause markers because its * self-contained fact closure can also name a transitive owner. */ static const char * import_pkgname(struct importin *imports, int nimports, const char *path, Node *primary, int *conflict) { const char *name = NULL; for (int i = 0; i < nimports; i++) { Node *file = imports[i].ast; for (Node *p = file ? file->body : NULL; p; p = p->next) { if (p->module == NULL || p->pkgname == NULL || strcmp(p->module, path) != 0) continue; if (name != NULL && strcmp(name, p->pkgname) != 0) { *conflict = 1; return NULL; } name = p->pkgname; } } for (Node *p = primary ? primary->body : NULL; p; p = p->next) { if (p->module == NULL || p->pkgname == NULL || strcmp(p->module, path) != 0) continue; if (name != NULL && strcmp(name, p->pkgname) != 0) { *conflict = 1; return NULL; } name = p->pkgname; } return name; } static int bind_import_names(Node *list, struct importin *imports, int nimports, Node *primary, const char *testsupport) { for (Node *u = list; u; u = u->next) { if (u->kind != N_USE || u->usepath == NULL) continue; /* The reserved test-support spelling is a compiler-owned alias, not * source default-import syntax. */ if (testsupport != NULL && strcmp(testsupport, "__wwtest") == 0 && strcmp(u->usepath, testsupport) == 0) continue; int conflict = 0; const char *name = import_pkgname(imports, nimports, u->usepath, primary, &conflict); if (conflict) { fprintf(stderr, "w6c: package %s has conflicting declared names in export data\n", u->usepath); return -1; } if (name != NULL) { u->str = name; u->strlen = strlen(name); } else if (!u->imported) { fprintf(stderr, "w6c: import %s has no declared package name in direct export data\n", u->usepath); return -1; } else { /* A closure-only import need not contribute declarations to this * interface. Keep it canonical-path keyed without reinstalling * the historical path-leaf qualifier. */ u->str = u->usepath; u->strlen = strlen(u->usepath); } } return 0; } static void appendnodes(Node **head, Node **tail, Node *list) { if (list == NULL) return; if (*head == NULL) *head = list; else (*tail)->next = list; while (list->next != NULL) list = list->next; *tail = list; } int main(int argc, char **argv) { const char *src = NULL; const char *out = NULL; const char *wwiout = NULL; /* -I : M2 export-data producer */ const char *testsupport = NULL; const char *testtarget = NULL; int testmode = 0; int testpackage = 0; int commandpackage = 0; int entrymode = 0; int sepmode = 0; /* -c: #22 M3 separate-compile / primary- * only codegen (emit imported==0 decls * only; treat `.wwi` deps as external) */ struct importin *imports = calloc((size_t)argc, sizeof *imports); struct importmap *maps = calloc((size_t)argc, sizeof *maps); if (imports == NULL || maps == NULL) { fputs("w6c: out of memory\n", stderr); free(maps); free(imports); return 1; } int nimports = 0; int nmaps = 0; for (int i = 1; i < argc; i++) { const char *a = argv[i]; if (strcmp(a, "-o") == 0 && i + 1 < argc) { out = argv[++i]; } else if (strcmp(a, "-I") == 0 && i + 1 < argc) { wwiout = argv[++i]; } else if (strcmp(a, "-T") == 0) { testmode = 1; } else if (strcmp(a, "--test-package") == 0) { testpackage = 1; } else if (strcmp(a, "--command-package") == 0) { commandpackage = 1; } else if (strcmp(a, "--entry") == 0) { entrymode = 1; } else if (strcmp(a, "--test-support-module") == 0) { if (i + 1 >= argc) { fputs("w6c: --test-support-module requires arg\n", stderr); return 2; } testsupport = argv[++i]; } else if (strcmp(a, "--test-target-package") == 0) { if (i + 1 >= argc) { fputs("w6c: --test-target-package requires arg\n", stderr); return 2; } testtarget = argv[++i]; } else if (strcmp(a, "-c") == 0) { sepmode = 1; } else if (strcmp(a, "--import") == 0) { if (i + 2 >= argc) { fputs("w6c: --import requires path and file\n", stderr); return 2; } imports[nimports].path = argv[++i]; imports[nimports].file = argv[++i]; nimports++; } else if (strcmp(a, "--import-map") == 0) { if (i + 2 >= argc) { fputs("w6c: --import-map requires source and path\n", stderr); return 2; } maps[nmaps].source = argv[++i]; maps[nmaps].path = argv[++i]; nmaps++; } else if (a[0] == '-') { fprintf(stderr, "w6c: unknown flag %s\n", a); return 2; } else if (src == NULL) { src = a; } else { fprintf(stderr, "w6c: only one input supported\n"); return 2; } } if (src == NULL) { fputs("usage: w6c [-T|--test-package] [--command-package] [--entry] [--test-target-package path] [-c] [-I out.wwi] " "[--import path dep.wwi]... [--import-map source path]... [-o out.s] file.ww\n", stderr); return 2; } if (nimports > 0 && !sepmode) { fputs("w6c: --import requires -c\n", stderr); return 2; } if (nmaps > 0 && !sepmode) { fputs("w6c: --import-map requires -c\n", stderr); return 2; } if ((entrymode || testpackage || commandpackage) && !sepmode) { fputs("w6c: --entry, --test-package, and --command-package require -c\n", stderr); return 2; } if (testmode && testpackage) { fputs("w6c: -T and --test-package are mutually exclusive\n", stderr); return 2; } for (int i = 0; i < nimports; i++) { if (imports[i].path[0] == '\0') { fputs("w6c: --import path is empty\n", stderr); return 2; } if (i > 0 && strcmp(imports[i-1].path, imports[i].path) >= 0) { fputs("w6c: --import paths must be sorted and unique\n", stderr); return 2; } } for (int i = 0; i < nmaps; i++) { if (maps[i].source[0] == '\0' || maps[i].path[0] == '\0') { fputs("w6c: --import-map path is empty\n", stderr); return 2; } if (i > 0 && strcmp(maps[i-1].source, maps[i].source) >= 0) { fputs("w6c: --import-map sources must be sorted and unique\n", stderr); return 2; } int direct = 0; for (int j = 0; j < nimports; j++) if (strcmp(maps[i].path, imports[j].path) == 0) { direct = 1; break; } if (!direct) { fputs("w6c: --import-map target is not a direct import\n", stderr); return 2; } } if (testsupport != NULL && (!sepmode || (strcmp(testsupport, "test") != 0 && strcmp(testsupport, "__wwtest") != 0))) { fputs("w6c: invalid --test-support-module\n", stderr); return 2; } if (testtarget != NULL && (!sepmode || !testmode || testtarget[0] == '\0')) { fputs("w6c: invalid --test-target-package\n", stderr); return 2; } if (testtarget != NULL) { int direct = 0; for (int i = 0; i < nimports; i++) if (strcmp(imports[i].path, testtarget) == 0) direct = 1; if (!direct) { fputs("w6c: --test-target-package is not a direct import\n", stderr); return 2; } } Arena *a = newarena(); Checker c; Cg cg; Node *head = NULL, *tail = NULL; for (int i = 0; i < nimports; i++) { if (slurp(imports[i].file, &imports[i].buf, &imports[i].len) < 0) { fprintf(stderr, "w6c: import %s: cannot read %s\n", imports[i].path, imports[i].file); return 1; } if (!export_owner_matches(imports[i].buf, imports[i].len, imports[i].path)) { fprintf(stderr, "w6c: import %s: export owner mismatch in %s\n", imports[i].path, imports[i].file); return 1; } int bad = 0; Node *f = parseinput(a, imports[i].file, imports[i].buf, imports[i].len, imports[i].path, testsupport, 0, &bad); if (bad) return 1; imports[i].ast = f; if (bind_import_names(f->list, imports, i + 1, NULL, testsupport) < 0) return 1; appendnodes(&head, &tail, f->list); } char *buf; u64 len; if (slurp(src, &buf, &len) < 0) { fprintf(stderr, "w6c: %s: cannot read\n", src); return 1; } int bad = 0; /* --entry classifies an ordinary selected command. The separate * --command-package marker carries the same parser-only fact for command * test variants whose code generation must not expose bare main. */ Node *file = parseinput(a, src, buf, len, NULL, testsupport, commandpackage || entrymode, &bad); if (bad) return 1; /* Source keeps its effective spelling and position, while package * resolution supplies the expanded canonical owner. Rewrite only the * primary import key before imported interface nodes are prepended. */ for (Node *u = file->list; u; u = u->next) { if (u->kind != N_USE || u->usepath == NULL) continue; for (int i = 0; i < nmaps; i++) if (strcmp(u->usepath, maps[i].source) == 0) { u->usepath = maps[i].path; maps[i].seen = 1; break; } } for (int i = 0; i < nmaps; i++) { if (maps[i].seen) continue; fputs("w6c: --import-map source is not in primary input\n", stderr); return 2; } /* Later direct interfaces may supply names for origin sections referenced * by an earlier interface, so perform one complete metadata pass now. */ for (int i = 0; i < nimports; i++) if (bind_import_names(imports[i].ast->list, imports, nimports, NULL, testsupport) < 0) return 1; if (bind_import_names(file->list, imports, nimports, file, testsupport) < 0) return 1; if (testtarget != NULL) { int seen = 0; for (Node *u = file->list; u; u = u->next) { if (u->kind != N_USE || u->imported || u->usepath == NULL || strcmp(u->usepath, testtarget) != 0) continue; u->str = u->usepath; u->strlen = strlen(u->usepath); seen++; } if (seen != 1) { fputs("w6c: generated test target import is not unique\n", stderr); return 2; } } if (head != NULL) { tail->next = file->list; file->list = head; } check_init(&c, a); c.is_test = testmode; c.is_test_package = testpackage; if (testsupport != NULL) c.test_module = testsupport; c.test_target = testtarget; c.sep_mode = sepmode; check_file(&c, file); if (c.errs) return 1; /* M2 export-data: write the `.wwi` after a clean check, before cgen. * Dead on the live path (no existing invocation passes -I); the * producer's check_exported_type may reject a dangling export. */ if (wwiout) { FILE *wf = fopen(wwiout, "wb"); if (wf == NULL) { fprintf(stderr, "w6c: cannot open %s\n", wwiout); return 1; } if (wwi_emit(&c, wf, file) != 0) { fclose(wf); return 1; } fclose(wf); } FILE *of = stdout; if (out) { of = fopen(out, "wb"); if (of == NULL) { fprintf(stderr, "w6c: cannot open %s\n", out); return 1; } } cg_init(&cg, a); cg.sep_mode = sepmode; /* Export production and entry identity are independent package-action * properties. Legacy raw invocations without -I remain entry-like; every * driver package now supplies -I, and only link roots add --entry. */ cg.sep_isdep = (wwiout != NULL && !entrymode); cg_file(&cg, of, file); if (of != stdout) fclose(of); freearena(a); for (int i = 0; i < nimports; i++) free(imports[i].buf); free(imports); free(maps); free(buf); return 0; }