/* * ww — the user-facing driver. Plan 9 cc(1) / Hare hare(1) analogue. * * Pipeline: * ww build foo.ww → w6c foo.ww > foo.s ; w6a foo.s > foo.o ; * w6l -o foo foo.o * ww run foo.ww → build then exec ./foo * * Tool paths default to siblings of $0 (so a fresh build runs out of * out/bin/), and can be overridden with WW_W6C / WW_W6A / WW_W6L. */ #include "ww.h" #include #include #include #include #include #include #include static const char *usage = "usage: ww [-V] [args...]\n" " -V print version and exit\n" " build [path] compile module to a static binary (path defaults to cwd)\n" " run [path] ... build then exec, passing extra args to the program\n" " test [path] build and run *_test.ww in the module (path defaults to cwd)\n" " fmt reformat ww source\n" " version print version and exit\n" "\n" " path forms:\n" " foo.ww literal file\n" " foo search cwd, -I dirs, then $WW_LIB for foo.ww or foo/foo.ww\n" " lib/foo directory: build lib/foo/foo.ww\n" " . build the cwd's .ww\n"; static char *self_dir; /* directory containing this binary */ static const char * toolpath(const char *envvar, const char *name) { const char *p = getenv(envvar); if (p && p[0]) return p; static char buf[1024]; snprintf(buf, sizeof buf, "%s/%s", self_dir, name); return strdup(buf); } static int run(const char *cmd) { int rc = system(cmd); if (rc == -1) return -1; if (WIFEXITED(rc)) return WEXITSTATUS(rc); 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. */ struct ImportSet { char **paths; int n, cap; }; static int import_seen(struct ImportSet *s, const char *path) { for (int i = 0; i < s->n; i++) if (strcmp(s->paths[i], path) == 0) return 1; return 0; } static void import_add(struct ImportSet *s, const char *path) { if (s->n + 1 > s->cap) { s->cap = s->cap ? s->cap * 2 : 8; s->paths = realloc(s->paths, s->cap * sizeof *s->paths); } s->paths[s->n++] = strdup(path); } /* Translate dots in an `import` name to slashes for path lookup. * `encoding.utf8` → `encoding/utf8`. Mirrors Hare hare(1)'s * use-path → fs-path mapping (ref/hare/hare/module/srcs.ha:78 * builds the same shape via path::push per ident part). */ static void import_path_form(const char *name, char *out, size_t outsz) { size_t i; for (i = 0; i + 1 < outsz && name[i] != '\0'; i++) out[i] = (name[i] == '.') ? '/' : name[i]; out[i] = '\0'; } /* try // as a directory, then /.ww as a file. * Sets *is_dir on hit. Symmetric with wwstage locatein for byte-id * driver output (rule 10). The legacy //.ww form * was dropped in task #22 — directory-as-module enumeration replaces * it, mirroring ref/hare/hare/module/srcs.ha (Hare has no fallback * matching `foo/foo.ha`; a module IS the directory). */ static int locate_import_in(const char *dir, const char *path_form, char *out, size_t outsz, int *is_dir) { struct stat st; snprintf(out, outsz, "%s/%s", dir, path_form); if (stat(out, &st) == 0 && S_ISDIR(st.st_mode)) { *is_dir = 1; return 1; } snprintf(out, outsz, "%s/%s.ww", dir, path_form); if (access(out, 0) == 0) { *is_dir = 0; return 1; } return 0; } /* Walk a colon-separated dirlist trying to resolve `path_form`. Returns * 1 on the first hit and writes the concrete path + dir/file marker. */ static int locate_import(const char *dirs, const char *path_form, char *out, size_t outsz, int *is_dir) { const char *p = dirs; while (*p) { const char *e = strchr(p, ':'); size_t n = e ? (size_t)(e - p) : strlen(p); if (n > 0 && n < outsz) { char dir[1024]; if (n >= sizeof dir) n = sizeof dir - 1; memcpy(dir, p, n); dir[n] = '\0'; if (locate_import_in(dir, path_form, out, outsz, is_dir)) return 1; } if (!e) break; p = e + 1; } return 0; } /* memcmp-based string compare for qsort. Byte-wise total order is * locale-independent; rule-10 byte-id requires the two stages sort * the same way. (strcmp would work today but Hare-fidelity points * at memcmp via ref/hare/sort/cmp/cmp.ha:9.) */ static int strs_cmp(const void *a, const void *b) { const char *sa = *(const char *const *)a; const char *sb = *(const char *const *)b; return strcmp(sa, sb); } /* enumerate_dir_ww — collect *.ww names in `dirpath` excluding * *test.ww, sort byte-wise. Returns count; caller frees entries. */ static int enumerate_dir_ww(const char *dirpath, char ***out_files) { DIR *d = opendir(dirpath); if (d == NULL) { *out_files = NULL; return 0; } char **arr = NULL; int n = 0, cap = 0; struct dirent *ent; while ((ent = readdir(d)) != NULL) { const char *nm = ent->d_name; size_t nl = strlen(nm); if (nl <= 3) continue; if (strcmp(nm + nl - 3, ".ww") != 0) continue; /* skip "*test.ww" (no underscore — bytestest.ww * ostest.ww utf8test.ww — Hare convention is _test.ha * but ww corpus settled on the un-underscored shape). */ if (nl >= 7 && strcmp(nm + nl - 7, "test.ww") == 0) continue; /* skip "*.combined.ww" — driver-generated concat * artifacts (the previous build leaves them next to * the source). They look like .ww but parse-erroring * when re-included. */ if (nl >= 12 && strcmp(nm + nl - 12, ".combined.ww") == 0) continue; if (n + 1 > cap) { cap = cap ? cap * 2 : 8; arr = realloc(arr, cap * sizeof *arr); } arr[n++] = strdup(nm); } closedir(d); if (n > 1) qsort(arr, n, sizeof *arr, strs_cmp); *out_files = arr; return n; } static void expand(FILE *out, const char *path, struct ImportSet *visited, const char *libdir); /* Scan `path` for its first non-comment-non-blank line; if it starts * with `package ;` write the name into `out` (NUL-terminated) * and return 1, else 0. Strict-same-package enforcement (task #23 * subset) is bundled here because the failure mode is dir-enum's * own — a non-dir-enum compilation unit cannot trigger it. Hare's * hare/module/srcs.ha:131 has the same constraint via the README * gate; we encode it as same-package across all enumerated files. */ static int peek_package(const char *path, char *out, size_t outsz) { FILE *in = fopen(path, "rb"); if (in == NULL) return 0; char line[2048]; int found = 0; while (fgets(line, sizeof line, in)) { const char *p = line; while (*p == ' ' || *p == '\t') p++; if (*p == '\n' || *p == '\0') continue; if (p[0] == '/' && p[1] == '/') continue; if (strncmp(p, "package ", 8) != 0 && strncmp(p, "package\t", 8) != 0) break; p += 8; while (*p == ' ' || *p == '\t') p++; size_t i = 0; while (i + 1 < outsz && ((p[i] >= 'a' && p[i] <= 'z') || (p[i] >= 'A' && p[i] <= 'Z') || p[i] == '_' || (p[i] >= '0' && p[i] <= '9'))) out[i] = p[i], i++; out[i] = '\0'; found = (i > 0); break; } fclose(in); return found; } /* expand_dir — enumerate /*.ww (skip *test.ww), byte-sort, * recurse into each. Mirrors ref/hare/hare/module/srcs.ha:183 * `_findsrcs` minus tag handling. The visited set still keys on * concrete file paths so multi-file modules are pulled once. * Strict-same-package: all enumerated files must declare the same * `package ;` (task #23 subset; failure mode native to * dir-enum). */ static void expand_dir(FILE *out, const char *dirpath, struct ImportSet *visited, const char *libdir) { char **files = NULL; int n = enumerate_dir_ww(dirpath, &files); char dirpkg[128] = {0}; for (int i = 0; i < n; i++) { char fp[1024]; snprintf(fp, sizeof fp, "%s/%s", dirpath, files[i]); char pkg[128]; if (peek_package(fp, pkg, sizeof pkg)) { if (dirpkg[0] == '\0') { snprintf(dirpkg, sizeof dirpkg, "%s", pkg); } else if (strcmp(dirpkg, pkg) != 0) { fprintf(stderr, "ww: %s: package %s differs from %s in same module dir %s\n", fp, pkg, dirpkg, dirpath); exit(1); } } expand(out, fp, visited, libdir); free(files[i]); } free(files); } /* Recursively expand `path`: for each top-level `import IDENT;` we * find, resolve the import and expand it first, then append our own * bytes. Already-visited paths are skipped. Each source carries its * own `package ;` declaration (the parser stamps decls from * it). */ static void expand(FILE *out, const char *path, struct ImportSet *visited, const char *libdir) { if (import_seen(visited, path)) return; import_add(visited, path); FILE *in = fopen(path, "rb"); if (in == NULL) { fprintf(stderr, "ww: cannot read %s\n", path); return; } char line[2048]; while (fgets(line, sizeof line, in)) { const char *p = line; while (*p == ' ' || *p == '\t') p++; if (strncmp(p, "import ", 7) != 0 && strncmp(p, "import\t", 7) != 0) continue; p += 7; while (*p == ' ' || *p == '\t') p++; char name[256] = {0}; int j = 0; while ((*p >= 'a' && *p <= 'z') || (*p >= 'A' && *p <= 'Z') || *p == '_' || *p == '.' || (*p >= '0' && *p <= '9')) if (j + 1 < (int)sizeof name) name[j++] = *p++; if (j == 0) continue; char path_form[256]; import_path_form(name, path_form, sizeof path_form); char ipath[1024]; int is_dir = 0; if (!locate_import(libdir, path_form, ipath, sizeof ipath, &is_dir)) continue; /* silently skip if not found */ if (is_dir) expand_dir(out, ipath, visited, libdir); else expand(out, ipath, visited, libdir); } rewind(in); int ch; while ((ch = fgetc(in)) != EOF) fputc(ch, out); fputc('\n', out); fclose(in); } static int build_one(const char *src, int entry_is_dir, const char *out, const char *extra_includes, const char *extra_libs, const char *extra_libdirs) { const char *c6 = toolpath("WW_W6C", "w6c"); const char *a6 = toolpath("WW_W6A", "w6a"); const char *l6 = toolpath("WW_W6L", "w6l"); const char *libdir = getenv("WW_LIB"); if (libdir == NULL || libdir[0] == 0) { static char libbuf[1024]; snprintf(libbuf, sizeof libbuf, "%s/../lib", self_dir); libdir = libbuf; } const char *srcdir = getenv("WW_SRCLIB"); static char srcbuf[1024]; if (srcdir == NULL || srcdir[0] == 0) { /* in-tree default: ../../lib relative to bin/ */ snprintf(srcbuf, sizeof srcbuf, "%s/../../lib", self_dir); if (access(srcbuf, 0) == 0) srcdir = srcbuf; else if (access("lib", 0) == 0) srcdir = "lib"; else srcdir = libdir; } /* Compose the search path: source-file's directory first, then any * -I dirs, then srcdir. The source-dir lead matches Hare's CWD-first * convention (its `hare test` is run from the module dir, making CWD * == module-dir); our wrappers don't cd, so dirname(src) is the * closest analog. Also matches cc -I. — source-dir wins ties over * the system path. locate_import walks left-to-right. * * For a dir entry the source-dir IS src; for a file entry it's * the dirname. */ char srcd[1024]; if (entry_is_dir) { snprintf(srcd, sizeof srcd, "%s", src); size_t n = strlen(srcd); while (n > 1 && srcd[n-1] == '/') srcd[--n] = '\0'; } else { const char *slash = strrchr(src, '/'); if (slash) { size_t n = (size_t)(slash - src); if (n >= sizeof srcd) n = sizeof srcd - 1; memcpy(srcd, src, n); srcd[n] = '\0'; } else { srcd[0] = '.'; srcd[1] = '\0'; } } static char searchpath[4096]; if (extra_includes && extra_includes[0]) snprintf(searchpath, sizeof searchpath, "%s:%s:%s", srcd, extra_includes, srcdir); else snprintf(searchpath, sizeof searchpath, "%s:%s", srcd, srcdir); srcdir = searchpath; /* Derive a stem for .s/.o/.combined.ww side files. For a file * entry strip the .ww. For a dir entry use / * so artifacts land inside the module directory. */ char stem[1024]; if (entry_is_dir) { const char *b = strrchr(srcd, '/'); const char *base = b ? b + 1 : srcd; snprintf(stem, sizeof stem, "%s/%s", srcd, base); } else { snprintf(stem, sizeof stem, "%s", src); char *dot = strrchr(stem, '.'); if (dot && strcmp(dot, ".ww") == 0) *dot = '\0'; } char asmf[1024], obj[1024], combined[1024]; snprintf(asmf, sizeof asmf, "%s.s", stem); snprintf(obj, sizeof obj, "%s.o", stem); snprintf(combined, sizeof combined, "%s.combined.ww", stem); /* Resolve imports by concatenating sources into a temp file. The * compiler then sees one flat source. Dir entry → enumerate the * module dir's *.ww (less *test.ww); file entry → start at the * file. */ { FILE *cf = fopen(combined, "wb"); if (cf == NULL) { fprintf(stderr, "ww: cannot open %s\n", combined); return 1; } struct ImportSet visited = {0}; if (entry_is_dir) expand_dir(cf, srcd, &visited, srcdir); else expand(cf, src, &visited, srcdir); fclose(cf); for (int i = 0; i < visited.n; i++) free(visited.paths[i]); free(visited.paths); } char cmd[4096]; snprintf(cmd, sizeof cmd, "%s -o %s %s", c6, asmf, combined); if (run(cmd) != 0) { fprintf(stderr, "ww: w6c failed\n"); return 1; } snprintf(cmd, sizeof cmd, "%s -o %s %s", a6, obj, asmf); if (run(cmd) != 0) { fprintf(stderr, "ww: w6a failed\n"); return 1; } /* Link runtime: prefer libwwrt.a (selective archive pull) but * fall back to start.o + syscall.o in the in-tree obj/ dir if * we're running uninstalled. */ char rtargs[2048] = {0}; char path[1024]; snprintf(path, sizeof path, "%s/libwwrt.a", libdir); if (access(path, 0) == 0) { snprintf(rtargs, sizeof rtargs, "%s", path); } else { char a1[1024], a2[1024]; snprintf(a1, sizeof a1, "%s/../obj/rt/start.o", self_dir); snprintf(a2, sizeof a2, "%s/../obj/rt/syscall.o", self_dir); snprintf(rtargs, sizeof rtargs, "%s %s", a1, a2); } /* -L goes before -l so w6l can resolve the latter. */ const char *libargs = (extra_libs && extra_libs[0]) ? extra_libs : ""; const char *libdirset = (extra_libdirs && extra_libdirs[0]) ? extra_libdirs : ""; snprintf(cmd, sizeof cmd, "%s -o %s %s %s%s%s%s%s", l6, out, obj, rtargs, libdirset[0] ? " " : "", libdirset, libargs[0] ? " " : "", libargs); if (run(cmd) != 0) { fprintf(stderr, "ww: w6l failed\n"); return 1; } return 0; } static int do_version(void) { printf("ww %s\n", WW_VERSION); return 0; } /* Compose the standard module search path: cwd : : $WW_LIB * source dir. The `extra` string is colon-separated -I dirs from argv. */ static const char * search_path(const char *extra, char *buf, size_t bufsz) { const char *libdir = getenv("WW_SRCLIB"); static char libbuf[1024]; if (libdir == NULL || libdir[0] == 0) { libdir = getenv("WW_LIB"); } if (libdir == NULL || libdir[0] == 0) { snprintf(libbuf, sizeof libbuf, "%s/../../lib", self_dir); if (access(libbuf, 0) == 0) libdir = libbuf; else if (access("lib", 0) == 0) libdir = "lib"; else { snprintf(libbuf, sizeof libbuf, "%s/../lib", self_dir); libdir = libbuf; } } if (extra && extra[0]) snprintf(buf, bufsz, ".:%s:%s", extra, libdir); else snprintf(buf, bufsz, ".:%s", libdir); return buf; } /* basename_no_ext: last path segment with any trailing ".ww" stripped. */ static void basename_no_ext(const char *path, char *out, size_t outsz) { const char *base = strrchr(path, '/'); base = base ? base + 1 : path; snprintf(out, outsz, "%s", base); char *dot = strrchr(out, '.'); if (dot && strcmp(dot, ".ww") == 0) *dot = '\0'; } /* resolve_module: turn a name into a concrete entry path. * foo.ww → use as-is if it exists * → returns the dir path (caller dir-enumerates) * . → cwd as a directory * foo (bare) → walk cwd:incs:WW_LIB; first hit is dir or file. * Sets *is_dir on hit. Dir resolution drives directory-as-module * enumeration in build_one. */ static int resolve_module(const char *name, const char *incs, char *out, size_t outsz, int *is_dir) { struct stat st; if (stat(name, &st) == 0) { if (S_ISREG(st.st_mode)) { snprintf(out, outsz, "%s", name); *is_dir = 0; return 1; } if (S_ISDIR(st.st_mode)) { snprintf(out, outsz, "%s", name); *is_dir = 1; return 1; } } char sp[4096]; search_path(incs, sp, sizeof sp); char path_form[256]; import_path_form(name, path_form, sizeof path_form); return locate_import(sp, path_form, out, outsz, is_dir); } /* Append `path` to a heap string-array. Caller frees each entry + the array. */ static void strarr_push(char ***arr, int *n, int *cap, const char *path) { if (*n + 1 > *cap) { *cap = *cap ? *cap * 2 : 8; *arr = realloc(*arr, *cap * sizeof **arr); } (*arr)[(*n)++] = strdup(path); } /* collect_tests: enumerate _test.ww files in (no recursion). * Returns 0 on success and writes the file list + count, -1 on failure. */ static int collect_tests(const char *dir, char ***files, int *n) { DIR *d = opendir(dir); if (!d) return -1; *files = NULL; *n = 0; int cap = 0; struct dirent *ent; while ((ent = readdir(d)) != NULL) { const char *nm = ent->d_name; size_t nl = strlen(nm); const char *suf = "_test.ww"; size_t sl = strlen(suf); if (nl <= sl) continue; if (strcmp(nm + nl - sl, suf) != 0) continue; char path[1024]; snprintf(path, sizeof path, "%s/%s", dir, nm); strarr_push(files, n, &cap, path); } closedir(d); return 0; } /* Parse the standard -I/-L/-l flags into incs/libdirs/libs. The first * non-flag positional becomes *src_out. Returns the index past the last * arg consumed for positionals (so callers can pick up trailing args). */ static int parse_build_flags(int argc, char **argv, char *incs, size_t incsz, char *libdirs, size_t libdirsz, char *libs, size_t libsz, const char **src_out) { *src_out = NULL; int i = 0; for (; i < argc; i++) { if (strncmp(argv[i], "-l", 2) == 0 && argv[i][2]) { size_t n = strlen(libs); snprintf(libs + n, libsz - n, "%s%s", n ? " " : "", argv[i]); } else if (strcmp(argv[i], "-l") == 0 && i + 1 < argc) { size_t n = strlen(libs); snprintf(libs + n, libsz - n, "%s-l%s", n ? " " : "", argv[++i]); } else if (strcmp(argv[i], "-L") == 0 && i + 1 < argc) { size_t n = strlen(libdirs); snprintf(libdirs + n, libdirsz - n, "%s-L%s", n ? " " : "", argv[++i]); } else if (strncmp(argv[i], "-L", 2) == 0 && argv[i][2]) { size_t n = strlen(libdirs); snprintf(libdirs + n, libdirsz - n, "%s%s", n ? " " : "", argv[i]); } else if (strcmp(argv[i], "-I") == 0 && i + 1 < argc) { size_t n = strlen(incs); snprintf(incs + n, incsz - n, "%s%s", n ? ":" : "", argv[++i]); } else if (strncmp(argv[i], "-I", 2) == 0 && argv[i][2]) { size_t n = strlen(incs); snprintf(incs + n, incsz - n, "%s%s", n ? ":" : "", argv[i] + 2); } else if (*src_out == NULL) { *src_out = argv[i]; } else { break; /* leave remaining argv to caller (run-args) */ } } return i; } static int do_build(int argc, char **argv) { const char *src = NULL; char libs[2048] = {0}; char libdirs[2048] = {0}; char incs[2048] = {0}; parse_build_flags(argc, argv, incs, sizeof incs, libdirs, sizeof libdirs, libs, sizeof libs, &src); if (src == NULL) src = "."; /* default: build cwd */ char resolved[1024]; int is_dir = 0; if (!resolve_module(src, incs, resolved, sizeof resolved, &is_dir)) { fprintf(stderr, "ww build: cannot find module %s\n", src); return 1; } char out[1024]; if (is_dir) { char tmp[1024]; snprintf(tmp, sizeof tmp, "%s", resolved); size_t n = strlen(tmp); while (n > 1 && tmp[n-1] == '/') tmp[--n] = '\0'; const char *b = strrchr(tmp, '/'); snprintf(out, sizeof out, "%s", b ? b + 1 : tmp); } else { basename_no_ext(resolved, out, sizeof out); } return build_one(resolved, is_dir, out, incs, libs, libdirs); } static int do_run(int argc, char **argv) { const char *src = NULL; char libs[2048] = {0}; char libdirs[2048] = {0}; char incs[2048] = {0}; int next = parse_build_flags(argc, argv, incs, sizeof incs, libdirs, sizeof libdirs, libs, sizeof libs, &src); if (src == NULL) src = "."; char resolved[1024]; int is_dir = 0; if (!resolve_module(src, incs, resolved, sizeof resolved, &is_dir)) { fprintf(stderr, "ww run: cannot find module %s\n", src); return 1; } char tmp[1024]; snprintf(tmp, sizeof tmp, "/tmp/ww_run_%d", getpid()); if (build_one(resolved, is_dir, tmp, incs, libs, libdirs) != 0) return 1; /* exec the built binary with any trailing argv as its argv. */ pid_t pid = fork(); if (pid < 0) { perror("ww: fork"); unlink(tmp); return 1; } if (pid == 0) { int n_extra = argc - next; char **xargv = calloc((size_t)n_extra + 2, sizeof *xargv); xargv[0] = tmp; for (int i = 0; i < n_extra; i++) xargv[i+1] = argv[next + i]; xargv[n_extra+1] = NULL; execv(tmp, xargv); perror("ww: exec"); _exit(127); } int status = 0; waitpid(pid, &status, 0); unlink(tmp); if (WIFEXITED(status)) return WEXITSTATUS(status); return 1; } static int do_test(int argc, char **argv) { const char *target = (argc > 0) ? argv[0] : "."; struct stat st; if (stat(target, &st) != 0) { /* not a literal path — try module resolution and run as * a single test program. */ char resolved[1024]; int is_dir = 0; if (!resolve_module(target, "", resolved, sizeof resolved, &is_dir)) { fprintf(stderr, "ww test: cannot find %s\n", target); return 1; } char tmp[1024]; snprintf(tmp, sizeof tmp, "/tmp/ww_test_%d", getpid()); if (build_one(resolved, is_dir, tmp, "", "", "") != 0) return 1; int rc = run(tmp); unlink(tmp); return rc; } if (S_ISREG(st.st_mode)) { /* single .ww file — build+run it. */ char tmp[1024]; snprintf(tmp, sizeof tmp, "/tmp/ww_test_%d", getpid()); if (build_one(target, 0, tmp, "", "", "") != 0) return 1; int rc = run(tmp); unlink(tmp); return rc; } if (!S_ISDIR(st.st_mode)) { fprintf(stderr, "ww test: %s is neither file nor directory\n", target); return 1; } /* directory — run every *_test.ww inside. */ char **files = NULL; int n = 0; if (collect_tests(target, &files, &n) < 0) { fprintf(stderr, "ww test: cannot read directory %s\n", target); return 1; } if (n == 0) { fprintf(stderr, "ww test: no *_test.ww files in %s\n", target); return 1; } int pass = 0, fail = 0; for (int i = 0; i < n; i++) { char tmp[1024]; snprintf(tmp, sizeof tmp, "/tmp/ww_test_%d_%d", getpid(), i); const char *label = strrchr(files[i], '/'); label = label ? label + 1 : files[i]; int rc = build_one(files[i], 0, tmp, target, "", ""); if (rc != 0) { fprintf(stderr, "FAIL %s (build)\n", label); fail++; } else { int xrc = run(tmp); if (xrc == 0) { printf("ok %s\n", label); pass++; } else { printf("FAIL %s (rc=%d)\n", label, xrc); fail++; } } unlink(tmp); free(files[i]); } free(files); printf("ww test: %d pass, %d fail\n", pass, fail); return fail == 0 ? 0 : 1; } static int do_fmt(int argc, char **argv) { (void)argc; (void)argv; fputs("ww: fmt: not implemented in this phase\n", stderr); return 1; } int main(int argc, char **argv) { if (argc >= 1) { char buf[1024]; snprintf(buf, sizeof buf, "%s", argv[0]); self_dir = strdup(dirname(buf)); } if (argc < 2) { fputs(usage, stderr); return 2; } const char *cmd = argv[1]; if (strcmp(cmd, "-V") == 0 || strcmp(cmd, "version") == 0) return do_version(); if (strcmp(cmd, "-h") == 0 || strcmp(cmd, "--help") == 0) { fputs(usage, stdout); return 0; } if (strcmp(cmd, "build") == 0) return do_build(argc - 2, argv + 2); if (strcmp(cmd, "run") == 0) return do_run(argc - 2, argv + 2); if (strcmp(cmd, "test") == 0) return do_test(argc - 2, argv + 2); if (strcmp(cmd, "fmt") == 0) return do_fmt(argc - 2, argv + 2); fprintf(stderr, "ww: unknown subcommand: %s\n", cmd); fputs(usage, stderr); return 2; }