/** * @file cases_test.cpp * @brief 12.2 二十用例点亮(12.11):每例按 EXPECTED.md 期望跑完整管线 * @author * @date 2026-08-21 * * @details 对 tests/cases/01..20 逐一执行:解析 → 链接 → 类型检查 → 寄存器码 → VM 周期, * 断言期望结果(编译运行通过 / 报错类别 / cycle_limit 故障),与各用例 EXPECTED.md 一致。 */ #include #include #include #include "compiler/Codegen.h" #include "compiler/Linker.h" #include "compiler/Project.h" #include "compiler/Typecheck.h" #include "vm/Machine.h" #ifndef REPO_ROOT #define REPO_ROOT "." #endif static int g_checks = 0; #define CHECK(cond) \ do { \ if (!(cond)) { \ std::printf("FAIL %s:%d: %s\n", __FILE__, __LINE__, #cond); \ return false; \ } \ ++g_checks; \ } while (0) namespace { enum class Outcome { Ok, CompileError, CycleLimit }; // 期望表(与 tests/cases/*/EXPECTED.md 一致;负例断言错误类别关键词) struct CaseSpec { const char* dir; Outcome out; const char* err_keyword; // CompileError 时须出现在错误消息里 }; const CaseSpec kCases[] = { {"01_empty_main", Outcome::Ok, ""}, {"02_bool_assign", Outcome::Ok, ""}, {"03_short_circuit", Outcome::Ok, ""}, {"04_if_elsif_else", Outcome::Ok, ""}, {"05_while_normal", Outcome::Ok, ""}, {"06_while_cycle_limit", Outcome::CycleLimit, ""}, {"07_int_arith", Outcome::Ok, ""}, {"08_time_literal", Outcome::Ok, ""}, {"09_gvl_external", Outcome::Ok, ""}, {"10_gvl_wrong_file", Outcome::CompileError, "VAR_GLOBAL only allowed in gvl file"}, {"11_duplicate_global", Outcome::CompileError, "duplicate global"}, {"12_io_unknown_var", Outcome::CompileError, "io.var"}, {"13_function_call", Outcome::Ok, ""}, {"14_function_write_global", Outcome::CompileError, "function cannot write global"}, {"15_fb_instance", Outcome::Ok, ""}, {"16_fb_undeclared", Outcome::CompileError, "undeclared FB instance"}, {"17_ton", Outcome::Ok, ""}, {"18_tof_ctu", Outcome::Ok, ""}, {"19_recursive_call", Outcome::CompileError, "recursive call"}, {"20_line1", Outcome::Ok, ""}, }; bool run_case(const char* dir, Outcome want, const char* keyword) { using namespace compiler; const std::string toml = std::string(REPO_ROOT) + "/tests/cases/" + dir + "/project.toml"; std::string err; Project p; if (!parse_project(toml, &p, &err)) { if (want == Outcome::CompileError) { ++g_checks; return true; } std::printf("FAIL %s parse: %s\n", dir, err.c_str()); return false; } std::vector units; for (const std::string& f : compile_files(p)) { SourceUnit u; if (!load_unit(p.base_dir + "/" + f, &u, &err)) { if (want == Outcome::CompileError) { ++g_checks; return true; } std::printf("FAIL %s load: %s\n", dir, err.c_str()); return false; } units.push_back(std::move(u)); } LinkResult link; if (!link_project(p, units, &link, &err)) { if (want == Outcome::CompileError) { if (err.find(keyword) == std::string::npos) { std::printf("FAIL %s: want '%s', got '%s'\n", dir, keyword, err.c_str()); return false; } ++g_checks; return true; } std::printf("FAIL %s link: %s\n", dir, err.c_str()); return false; } if (!check_project(p, units, link, &err)) { if (want == Outcome::CompileError) { if (err.find(keyword) == std::string::npos) { std::printf("FAIL %s: want '%s', got '%s'\n", dir, keyword, err.c_str()); return false; } ++g_checks; return true; } std::printf("FAIL %s type: %s\n", dir, err.c_str()); return false; } MachineConfig cfg; if (!cfg.load(std::string(REPO_ROOT) + "/compiler/machine.toml", &err)) { std::printf("FAIL %s machine load\n", dir); return false; } std::vector img; if (!codegen_project(p, units, link, cfg, &img, &err)) { if (want == Outcome::CompileError) { ++g_checks; return true; } std::printf("FAIL %s codegen: %s\n", dir, err.c_str()); return false; } if (want == Outcome::CompileError) { std::printf("FAIL %s: expected compile error\n", dir); return false; } vm::Machine m; if (!vm::Machine::create(img, &m, &err)) { std::printf("FAIL %s vm create: %s\n", dir, err.c_str()); return false; } const vm::Fault f = m.run_cycle(); if (want == Outcome::CycleLimit) { if (f == vm::Fault::CycleLimit) { ++g_checks; return true; } std::printf("FAIL %s: want CycleLimit, got %d\n", dir, static_cast(f)); return false; } if (f != vm::Fault::None) { std::printf("FAIL %s: fault %d\n", dir, static_cast(f)); return false; } // 可重复性:再跑一个周期 if (m.run_cycle() != vm::Fault::None) { std::printf("FAIL %s: 2nd cycle fault\n", dir); return false; } ++g_checks; return true; } } // namespace int main() { for (const CaseSpec& c : kCases) { if (!run_case(c.dir, c.out, c.err_keyword)) { return 1; } } std::printf("cases_test: %d cases lit up\n", g_checks); return 0; }