#define COMMON_CFLAGS "-Wall", "-Wextra", "-D_CRT_SECURE_NO_WARNINGS" #define SPL_CFLAGS COMMON_CFLAGS, "-O0", "-g" #define SPL_OPTIMIZATION_FLAG COMMON_CFLAGS, "-O2" #define nob_cc_flags(cmd) nob_cmd_append(cmd, SPL_CFLAGS) #undef UNICODE #define NOB_IMPLEMENTATION #include "nob.h" #undef NOB_IMPLEMENTATION #include "nob_popen.h" #ifdef _WIN32 #include #endif #define BUILD_FOLDER "build/" // Content of the .cflags stamp file; must match SPL_CFLAGS above. #define CFLAGS_STR "-Wall -Wextra -O0 -g" // Stamp file whose mtime is refreshed whenever the flags change so that all // object files get rebuilt. static const char *CFLAGS_STAMP = BUILD_FOLDER ".cflags"; /* clang-format off */ #define VM_SRCS "stage0/spl_mcode.c", "stage0/spl_syscall.c", "stage0/spl_vm.c", static const char *spl_cli_srcs[] = {"stage0/spl_cli.c", VM_SRCS}; static const char *vm_test_srcs[] = {"stage0/test_spl_vm.c", VM_SRCS}; static const char *splc0_srcs[] = { "stage1/splc0.c", "stage1/spl_diag.c", "stage1/spl_tok.c", "stage1/spl_lexer.c", "stage1/spl_ast.c", "stage1/spl_parser.c", "stage1/spl_dumptree.c", "stage1/spl_type.c", "stage1/spl_fold.c", "stage1/spl_lang_type.c", "stage1/spl_attr.c", "stage1/spl_scope.c", "stage1/spl_sema.c", "stage1/spl_intrin.c", "stage1/spl_ir.c", "stage1/spl_ast2ir.c", "stage1/spl_layout.c", "stage1/spl_ir2vm.c", "stage1/ir_opt.c", "stage1/vm_opt.c", "stage0/spl_mcode.c", }; static const char* splc1_deps[] = { "stage2/sys/exit.spl", "stage2/sys/file.spl", "stage2/sys/mem.spl", "stage2/sys/spl_rt.h", "stage2/sys/spl_vm_embed.h", "stage2/sys/time.spl", "stage2/sys/vm.spl", "stage2/utils/algorithm.spl", "stage2/utils/arena.spl", "stage2/utils/ascii.spl", "stage2/utils/byte_buffer.spl", "stage2/utils/byte_view.spl", "stage2/utils/error.spl", "stage2/utils/fmt.spl", "stage2/utils/hash.spl", "stage2/utils/io.spl", "stage2/utils/json.spl", "stage2/utils/log.spl", "stage2/utils/lsp_frame.spl", "stage2/utils/map.spl", "stage2/utils/memory.spl", "stage2/utils/mp.spl", "stage2/utils/path.spl", "stage2/utils/profile.spl", "stage2/utils/set.spl", "stage2/utils/source.spl", "stage2/utils/str.spl", "stage2/utils/utf8.spl", "stage2/utils/value.spl", "stage2/utils/vec.spl", "stage2/ast.spl", "stage2/ast2ir.spl", "stage2/attr.spl", "stage2/def.spl", "stage2/diag.spl", "stage2/eval.spl", "stage2/intrin.spl", "stage2/ir.spl", "stage2/ir2c.spl", "stage2/ir2vm.spl", "stage2/ir_builder.spl", "stage2/lang_type.spl", "stage2/layout.spl", "stage2/lexer.spl", "stage2/mcode.spl", "stage2/parser.spl", "stage2/scope.spl", "stage2/sema.spl", "stage2/splc1.spl", "stage2/token.spl", "stage2/type_table.spl", }; static const char* splc2_deps[] = { }; /* clang-format on */ static const char *obj_path(const char *src) { // "stage0/spl_vm.c" -> "build/stage0_spl_vm.o" size_t prefix_len = strlen(BUILD_FOLDER); char *out = (char *)nob_temp_alloc(prefix_len + strlen(src) + 2 + 1); char *w = out; memcpy(w, BUILD_FOLDER, prefix_len); w += prefix_len; for (const char *p = src; *p && *p != '.'; ++p) { *w++ = (*p == '/' || *p == '\\') ? '_' : *p; } memcpy(w, ".o", 3); return out; } // Windows 上 clang/gcc 会输出 build/foo.exe;这里统一带上后缀, // 否则 nob_needs_rebuild 找不到输出会每次重新链接。 static const char *exe_path(const char *name) { #ifdef _WIN32 return nob_temp_sprintf(BUILD_FOLDER "%s.exe", name); #else return nob_temp_sprintf(BUILD_FOLDER "%s", name); #endif } static const char *sir_path(const char *name) { return nob_temp_sprintf(BUILD_FOLDER "%s.sir", name); } static const char *c_path(const char *name) { return nob_temp_sprintf(BUILD_FOLDER "%s.c", name); } static bool sync_cflags_stamp(void) { Nob_String_View cflags = nob_sv_from_cstr(CFLAGS_STR); Nob_String_Builder old = {0}; bool changed = true; if (nob_file_exists(CFLAGS_STAMP) && nob_read_entire_file(CFLAGS_STAMP, &old)) { changed = !nob_sv_eq(nob_sb_to_sv(old), cflags); } nob_sb_free(old); if (changed) { if (!nob_write_entire_file(CFLAGS_STAMP, cflags.data, cflags.count)) { return false; } } return true; } static bool prepare_build(void) { if (!nob_mkdir_if_not_exists(BUILD_FOLDER)) return false; if (!sync_cflags_stamp()) return false; return true; } typedef struct build { const char *name; enum { BUILD_EXE, BUILD_EXE_WITH_OPTIMIZATION, BUILD_SIR, BUILD_NATIVE, } kind; union { struct { const char **srcs; size_t srcs_count; const char **headers; size_t headers_count; } exe; struct { const char *src; const char *compiler; const char **deps; size_t deps_count; } sir; struct { const char *src; const char *compiler; const char **deps; size_t deps_count; } native; }; } build_t; typedef struct { const char **items; size_t count; size_t capacity; } str_da_t; typedef struct run { const char *name; const char *interpreter; } run_t; bool run_target(const char *name, Nob_Cmd *args); const char *build_target(const char *name); static const char *build_exe(build_t *target) { Nob_Cmd cmd = {0}; str_da_t objs = {0}; str_da_t deps = {0}; for (size_t i = 0; i < target->exe.srcs_count; ++i) { const char *src = target->exe.srcs[i]; const char *obj = obj_path(src); if (!nob_file_exists(src)) { nob_log(NOB_ERROR, "source file `%s` does not exist", src); goto defer; } da_append(&objs, obj); da_append(&deps, src); da_append(&deps, CFLAGS_STAMP); for (size_t j = 0; j < target->exe.headers_count; ++j) { da_append(&deps, target->exe.headers[j]); } int needs = nob_needs_rebuild(obj, deps.items, deps.count); deps.count = 0; // 清空 deps,下一轮循环重新收集 if (needs < 0) goto defer; if (needs == 0) continue; nob_cc(&cmd); nob_cc_flags(&cmd); nob_cmd_append(&cmd, "-c"); nob_cc_inputs(&cmd, src); nob_cc_output(&cmd, obj); if (!nob_cmd_run(&cmd)) { nob_log(NOB_ERROR, "failed to compile `%s`", src); goto defer; } cmd.count = 0; // 清空 cmd,下一轮循环重新构建 } const char *exe = exe_path(target->name); int needs = nob_needs_rebuild(exe, objs.items, objs.count); if (needs < 0) goto defer; if (needs == 0) return exe; nob_cc(&cmd); nob_cmd_append(&cmd, SPL_OPTIMIZATION_FLAG); for (size_t i = 0; i < objs.count; ++i) { nob_cc_inputs(&cmd, objs.items[i]); } nob_cc_output(&cmd, exe); if (!nob_cmd_run(&cmd)) { goto defer; } return exe; defer: nob_cmd_free(cmd); da_free(objs); da_free(deps); return NULL; } static const char *build_native(const char *name) { const char *exe = exe_path(name); const char *cli = build_target("spl_cli"); const char *sir = build_target("splc1"); if (!cli || !sir) { nob_log(NOB_ERROR, "failed to build prerequisites for `%s`", name); return NULL; } const char *cpath = BUILD_FOLDER "splc1.c"; const char *inputs[] = {sir, "stage2/sys/spl_rt.h", "stage2/sys/spl_vm_embed.h", "stage2/sys/spl_vm_embed.c", "stage0/spl_vm.c", "stage0/spl_mcode.c", "stage0/spl_syscall.c", "stage0/spl_vm.h", "stage0/spl_mcode.h"}; int needs = nob_needs_rebuild(exe, inputs, NOB_ARRAY_LEN(inputs)); if (needs < 0) return NULL; if (needs == 0) return exe; Nob_Cmd cmd = {0}; if (nob_file_exists(exe)) { // 已有原生编译器:用原生自举重新生成 C(0.1s 级) nob_cmd_append(&cmd, exe, "--dump", "c", "stage2/splc1.spl", cpath); } else { // 冷启动:用 VM 版 splc1 生成 C nob_cmd_append(&cmd, cli, sir, "--dump", "c", "stage2/splc1.spl", cpath); } if (!nob_cmd_run(&cmd)) { nob_log(NOB_ERROR, "ir2c C generation failed"); nob_cmd_free(cmd); return NULL; } cmd.count = 0; nob_cc(&cmd); nob_cc_inputs(&cmd, cpath); nob_cc_inputs(&cmd, "stage2/sys/spl_vm_embed.c"); nob_cc_inputs(&cmd, "stage0/spl_vm.c"); nob_cc_inputs(&cmd, "stage0/spl_mcode.c"); nob_cc_inputs(&cmd, "stage0/spl_syscall.c"); nob_cc_output(&cmd, exe); if (!nob_cmd_run(&cmd)) { nob_log(NOB_ERROR, "native link failed"); nob_cmd_free(cmd); return NULL; } nob_cmd_free(cmd); return exe; } static build_t targets[] = { { "spl_cli", BUILD_EXE_WITH_OPTIMIZATION, .exe = {.srcs = spl_cli_srcs, .srcs_count = NOB_ARRAY_LEN(spl_cli_srcs), .headers = NULL, .headers_count = 0}, }, { "spl_cli_g", BUILD_EXE, .exe = {.srcs = spl_cli_srcs, .srcs_count = NOB_ARRAY_LEN(spl_cli_srcs), .headers = NULL, .headers_count = 0}, }, { "vm_test", BUILD_EXE, .exe = {.srcs = vm_test_srcs, .srcs_count = NOB_ARRAY_LEN(vm_test_srcs), .headers = NULL, .headers_count = 0}, }, { "splc0", BUILD_EXE, .exe = {.srcs = splc0_srcs, .srcs_count = NOB_ARRAY_LEN(splc0_srcs), .headers = NULL, .headers_count = 0}, }, { "splc1", BUILD_SIR, .sir = {.src = "stage2/splc1.spl", .compiler = "splc0", .deps = splc1_deps, .deps_count = NOB_ARRAY_LEN(splc1_deps)}, }, { "splc1s", BUILD_SIR, .sir = {.src = "stage2/splc1.spl", .compiler = "splc1", .deps = splc1_deps, .deps_count = NOB_ARRAY_LEN(splc1_deps)}, }, { "splc1r", BUILD_SIR, .sir = {.src = "stage2/splc1.spl", .compiler = "splc1s", .deps = splc1_deps, .deps_count = NOB_ARRAY_LEN(splc1_deps)}, }, { "splc1n", BUILD_NATIVE, .native = {.src = "stage2/splc1.spl", .compiler = "splc1", .deps = splc1_deps, .deps_count = NOB_ARRAY_LEN(splc1_deps)}, }, { "splc2", BUILD_SIR, .sir = {.src = "stage3/splc2.spl", .compiler = "splc1n", .deps = splc2_deps, .deps_count = NOB_ARRAY_LEN(splc2_deps)}, }, { "splc2n", BUILD_NATIVE, .native = {.src = "stage3/splc2.spl", .compiler = "splc1n", .deps = splc2_deps, .deps_count = NOB_ARRAY_LEN(splc2_deps)}, }, }; const char *build_target(const char *name) { if (!prepare_build()) return NULL; for (size_t i = 0; i < NOB_ARRAY_LEN(targets); ++i) { if (name != NULL && strcmp(targets[i].name, name) != 0) { continue; } build_t *target = &targets[i]; switch (target->kind) { case BUILD_EXE_WITH_OPTIMIZATION: case BUILD_EXE: { return build_exe(target); } case BUILD_SIR: { Nob_Cmd cmd = {0}; const char *target_sir = sir_path(name); nob_cmd_append(&cmd, target->sir.src, target_sir); if (!run_target(target->sir.compiler, &cmd)) { nob_log(NOB_ERROR, "failed to build `%s`", name); nob_cmd_free(cmd); return NULL; } nob_cmd_free(cmd); return target_sir; } case BUILD_NATIVE: { Nob_Cmd cmd = {0}; const char *target_exe = exe_path(name); const char *target_c = c_path(name); // TODO need_rebuild int needs = nob_needs_rebuild(target_exe, target->native.deps, target->native.deps_count); if (needs < 0) return NULL; if (needs == 0) return target_exe; nob_cmd_append(&cmd, "--dump", "c", target->native.src, target_c); if (!run_target(target->native.compiler, &cmd)) { nob_log(NOB_ERROR, "failed to build `%s`", name); nob_cmd_free(cmd); return NULL; } cmd.count = 0; nob_cc(&cmd); nob_cmd_append(&cmd, "-I./stage2/sys", "-O2", "-D_CRT_SECURE_NO_WARNINGS"); nob_cc_inputs(&cmd, target_c); nob_cc_output(&cmd, target_exe); if (!nob_cmd_run(&cmd)) { nob_log(NOB_ERROR, "failed to build `%s`", name); nob_cmd_free(cmd); return NULL; } nob_cmd_free(cmd); return target_exe; } default: NOB_UNREACHABLE("build_t.kind"); } } nob_log(NOB_ERROR, "unknown build target `%s`", name); return NULL; } static run_t runs[] = { {"spl_cli", NULL}, {"vm_test", NULL}, {"splc0", NULL}, {"splc1", "spl_cli"}, {"splc1r", "spl_cli"}, {"splc1s", "spl_cli"}, {"splc1n", NULL}, {"splc2", "spl_cli"}, {"splc2n", NULL}, }; bool run_target_dry(const char *name, Nob_Cmd *args, Nob_Cmd *result) { for (size_t i = 0; i < NOB_ARRAY_LEN(runs); ++i) { if (strcmp(runs[i].name, name) != 0) { continue; } const char *exe = build_target(runs[i].name); const char *interpreter = runs[i].interpreter; if (interpreter) { const char *inter = build_target(interpreter); if (!exe) { nob_log(NOB_ERROR, "failed to build interpreter `%s` for `%s`", interpreter, name); return false; } nob_cmd_append(result, inter); } nob_cmd_append(result, exe); nob_cmd_extend(result, args); return true; } nob_log(NOB_ERROR, "unknown run target `%s`", name); return false; } bool run_target(const char *name, Nob_Cmd *args) { Nob_Cmd result = {0}; if (!run_target_dry(name, args, &result)) { return false; } nob_log(NOB_INFO, "running `%s`", name); bool ok = nob_cmd_run(&result); nob_cmd_free(result); return ok; } static void parse_expect(const char *name, Nob_Cmd_Result *expect) { Nob_String_Builder sb = {0}; nob_read_entire_file(name, &sb); Nob_String_View sv = nob_sb_to_sv(sb); while (sv.count > 0) { Nob_String_View line = nob_sv_chop_by_delim(&sv, '\n'); line = nob_sv_trim_left(line); // 去掉前导空格 if (!nob_sv_starts_with(line, nob_sv_from_cstr("//"))) { break; } Nob_String_View rest = nob_sv_from_parts(line.data + 2, line.count - 2); rest = nob_sv_trim_left(rest); if (rest.count == 0) continue; if (!nob_sv_starts_with(rest, nob_sv_from_cstr("Expect"))) { break; } rest = nob_sv_from_parts(rest.data + 6, rest.count - 6); rest = nob_sv_trim_left(rest); // 按 '=' 分割 Nob_String_View key = nob_sv_chop_by_delim(&rest, '='); key = nob_sv_trim(key); Nob_String_View value = nob_sv_trim(rest); if (nob_sv_eq(key, nob_sv_from_cstr("retcode"))) { int val = 0; for (size_t i = 0; i < value.count && isdigit(value.data[i]); ++i) { val = val * 10 + (value.data[i] - '0'); } // printf("expected code %d\n", val); expect->exit_code = val; } else if (nob_sv_eq(key, nob_sv_from_cstr("stdout"))) { } } } static bool test_once(const char *fname, void *user_data) { (void)user_data; const char *compiler_path = "splc0"; const char *cli_path = "spl_cli"; const char *target_sir = BUILD_FOLDER "test.sir"; Nob_Cmd cmd = {0}; if (!build_target(compiler_path)) { return 1; } cmd.count = 0; nob_cmd_append(&cmd, exe_path(compiler_path)); nob_cmd_append(&cmd, fname, target_sir); if (!nob_cmd_run(&cmd)) { return false; } if (!build_target(cli_path)) { return 1; } cmd.count = 0; nob_cmd_append(&cmd, exe_path(cli_path)); nob_cmd_append(&cmd, target_sir); // nob_cmd_run(&cmd); Nob_Cmd_Result result = {0}; Nob_String_Builder cmd_str = {0}; nob_cmd_render(cmd, &cmd_str); Nob_String_View sv = nob_sb_to_sv(cmd_str); nob_cmd_capture_popen(nob_temp_sv_to_cstr(sv), &result); // Expect retcode = number Nob_Cmd_Result expect = {0}; parse_expect(fname, &expect); if (expect.exit_code != result.exit_code) { nob_log(NOB_ERROR, "Expect code `%d`, Result code `%d`", expect.exit_code, result.exit_code); return false; } nob_cmd_result_free(result); return true; } static bool test_once_with_dump(const char *fname, void *user_data) { const char *target_name = "splc0"; if (!prepare_build()) return 1; if (!build_target(target_name)) return 1; Nob_Cmd cmd = {0}; nob_cmd_append(&cmd, exe_path(target_name)); nob_cmd_append(&cmd, "--dump", (char *)user_data, fname, BUILD_FOLDER "t.sir"); return nob_cmd_run(&cmd); } // 由源路径派生构建产物路径:build/.sir static const char *artifact_sir(const char *src) { const char *base = src; for (const char *p = src; *p; ++p) if (*p == '/' || *p == '\\') base = p + 1; const char *dot = strrchr(base, '.'); size_t len = dot ? (size_t)(dot - base) : strlen(base); return nob_temp_sprintf(BUILD_FOLDER "%.*s.sir", (int)len, base); } static bool run_capture(Nob_Cmd *cmd, Nob_Cmd_Result *result) { Nob_String_Builder rendered = {0}; nob_cmd_render(*cmd, &rendered); Nob_String_View sv = nob_sb_to_sv(rendered); bool ok = nob_cmd_capture_popen(nob_temp_sv_to_cstr(sv), result); nob_sb_free(rendered); return ok; } // diff 测试:对同一文件跑 `splc0 --dump ` 与 `spl_cli splc1.sir --dump `, // 逐字节比较 stdout。 static bool test_once_diff(const char *fname, void *user_data) { const char *mode = (const char *)user_data; if (!mode) { nob_log(NOB_ERROR, "diff test requires a mode: lex|ast|sema|ir|vm"); return false; } if (!build_target("splc0")) return false; if (!build_target("spl_cli")) return false; if (!build_target("splc1")) return false; Nob_Cmd c_cmd = {0}; nob_cmd_append(&c_cmd, exe_path("splc0"), "--dump", (char *)mode, (char *)fname, BUILD_FOLDER "diff_c.sir"); Nob_Cmd_Result c_res = {0}; if (!run_capture(&c_cmd, &c_res)) { nob_cmd_result_free(c_res); return false; } Nob_Cmd s_cmd = {0}; nob_cmd_append(&s_cmd, exe_path("spl_cli"), BUILD_FOLDER "splc1.sir", "--dump", (char *)mode, (char *)fname); Nob_Cmd_Result s_res = {0}; if (!run_capture(&s_cmd, &s_res)) { nob_cmd_result_free(c_res); nob_cmd_result_free(s_res); return false; } Nob_String_View c_out = nob_sb_to_sv(c_res.out); Nob_String_View s_out = nob_sb_to_sv(s_res.out); bool same = nob_sv_eq(c_out, s_out); if (!same) { nob_log(NOB_ERROR, "diff mismatch: %s (c=%zu bytes, spl=%zu bytes)", fname, c_out.count, s_out.count); // 打印首个不相同行(按行比较)。 Nob_String_View ca = c_out; Nob_String_View sa = s_out; size_t lineno = 1; while (ca.count > 0 || sa.count > 0) { Nob_String_View cl = nob_sv_chop_by_delim(&ca, '\n'); Nob_String_View sl = nob_sv_chop_by_delim(&sa, '\n'); if (!nob_sv_eq(cl, sl)) { nob_log(NOB_ERROR, " line %zu:", lineno); nob_log(NOB_ERROR, " c : %.*s", (int)cl.count, cl.data); nob_log(NOB_ERROR, " spl: %.*s", (int)sl.count, sl.data); break; } lineno++; } nob_log(NOB_ERROR, "--- c stderr/stdout merged above; exit=%d vs spl exit=%d ---", c_res.exit_code, s_res.exit_code); } nob_cmd_result_free(c_res); nob_cmd_result_free(s_res); return same; } static bool is_stage_name(const char *s) { return strcmp(s, "stage1") == 0 || strcmp(s, "stage2") == 0 || strcmp(s, "diff") == 0; } static bool is_dump_mode(const char *s) { return strcmp(s, "lex") == 0 || strcmp(s, "ast") == 0 || strcmp(s, "sema") == 0 || strcmp(s, "ir") == 0 || strcmp(s, "vm") == 0; } static const char *stage_dir_of(const char *stage) { if (strcmp(stage, "stage2") == 0) return "./stage2/"; return "./tests/stage1/"; } typedef bool (*test_fn_t)(const char *fname, void *user_data); typedef struct { Nob_Files *files; test_fn_t test_once; void *user_data; } test_walk_ctx_t; static bool test_walk_entry(Nob_Walk_Entry entry) { if (entry.type != NOB_FILE_REGULAR) return true; const char *dot = strrchr(entry.path, '.'); if (!dot || strcmp(dot, ".spl") != 0) return true; test_walk_ctx_t *ctx = (test_walk_ctx_t *)entry.data; bool run_all = (ctx->files->count == 0); bool matched = run_all; for (int i = 0; !matched && i < ctx->files->count; i += 1) { if (strstr(entry.path, ctx->files->items[i])) matched = true; } if (!matched) return true; printf("--- test %s ---\n", entry.path); if (ctx->test_once(entry.path, ctx->user_data) == false) return false; return true; } static bool run_test(const char *stage, Nob_Files *files, test_fn_t test_once, void *user_data) { const char *test_folder = stage_dir_of(stage); if (!prepare_build()) return false; if (!nob_file_exists(test_folder)) { nob_log(NOB_INFO, "%s does not exist, skip", test_folder); return true; } test_walk_ctx_t ctx = { .files = files, .test_once = test_once, .user_data = user_data, }; return nob_walk_dir(test_folder, test_walk_entry, .data = &ctx); } static bool delete_walk_entry(Nob_Walk_Entry entry) { return nob_delete_file(entry.path); } static bool delete_directory_recursively(const char *dir_path) { return nob_walk_dir(dir_path, delete_walk_entry, .post_order = true); } static void print_help(const char *program_name) { nob_log(NOB_INFO, "SPL C-side bootstrap build system (nob)"); nob_log(NOB_INFO, "%s", " "); nob_log(NOB_INFO, "Usage: %s [args...]", program_name); nob_log(NOB_INFO, "%s", " "); nob_log(NOB_INFO, "Commands:"); nob_log(NOB_INFO, " build [targets...] build all targets or the given ones"); nob_log(NOB_INFO, " run [args] build and run a target"); nob_log(NOB_INFO, " test build and run tests"); nob_log(NOB_INFO, " clean remove build artifacts"); nob_log(NOB_INFO, " list list all targets"); nob_log(NOB_INFO, " help/-h/--help print this help message"); } int main(int argc, char **argv) { #ifdef _WIN32 SetConsoleOutputCP(CP_UTF8); SetConsoleCP(CP_UTF8); #endif NOB_GO_REBUILD_URSELF_PLUS(argc, argv, "nob.h", "nob_popen.h"); set_log_handler(nob_cancer_log_handler); const char *program_name = nob_shift(argv, argc); const char *command_name = "build"; if (argc > 0) { command_name = nob_shift(argv, argc); } if (strcmp(command_name, "build") == 0) { if (!prepare_build()) return 1; if (argc > 0) { while (argc > 0) { const char *target_name = nob_shift(argv, argc); if (!build_target(target_name)) return 1; } } else { if (!build_target(NULL)) return 1; } return 0; } if (strcmp(command_name, "run") == 0) { if (argc <= 0) { nob_log(NOB_ERROR, "usage: %s run [args...]", program_name); return 1; } const char *target_name = nob_shift(argv, argc); Nob_Cmd runcmd = {0}; while (argc > 0) { nob_cmd_append(&runcmd, nob_shift(argv, argc)); } return !run_target(target_name, &runcmd); } if (strcmp(command_name, "test") == 0) { // 1. stage(可选,默认 stage1) const char *stage = "stage1"; if (argc > 0 && is_stage_name(argv[0])) { stage = nob_shift(argv, argc); } // 2. mode(可选) test_fn_t tfn = test_once; const char *mode_name = NULL; if (argc > 0) { if (is_dump_mode(argv[0])) { tfn = test_once_with_dump; mode_name = nob_shift(argv, argc); } else if (strcmp(argv[0], "run") == 0) { nob_shift(argv, argc); // HACK:只对 stage1 有意义,跑上次生成的 build/test.sir Nob_Cmd cmd = {0}; nob_cmd_append(&cmd, BUILD_FOLDER "spl_cli", "-d"); nob_cmd_append(&cmd, BUILD_FOLDER "test.sir"); return nob_cmd_run(&cmd) ? 0 : 1; } } // diff 需要显式 mode,且使用专门的双编译器比较函数。 if (strcmp(stage, "diff") == 0) { if (!mode_name) { nob_log(NOB_ERROR, "diff requires a mode: lex|ast|sema|ir|vm"); return 1; } tfn = test_once_diff; } // 3. 剩下的都当 pattern Nob_Files files = {0}; while (argc > 0) { da_append(&files, nob_shift(argv, argc)); } return run_test(stage, &files, tfn, (void *)mode_name) ? 0 : 1; } if (strcmp(command_name, "clean") == 0) { if (nob_file_exists(BUILD_FOLDER)) { if (!delete_directory_recursively(BUILD_FOLDER)) return 1; } nob_log(NOB_INFO, "cleaned %s", BUILD_FOLDER); return 0; } if (strcmp(command_name, "list") == 0) { for (size_t i = 0; i < NOB_ARRAY_LEN(targets); ++i) { nob_log(NOB_INFO, "%s:", targets[i].name); } return 0; } if (strcmp(command_name, "help") == 0 || strcmp(command_name, "-h") == 0 || strcmp(command_name, "--help") == 0) { print_help(program_name); return 0; } nob_log(NOB_ERROR, "unknown command `%s`", command_name); print_help(program_name); return 1; }