From c076c253683d4b6d90ed341e2e886fb060a5a615 Mon Sep 17 00:00:00 2001 From: chentianya Date: Fri, 21 Aug 2026 00:32:53 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=9E=E7=8E=B0=2012.3=20toml=20=E8=AF=BB?= =?UTF-8?q?=E5=8F=96=EF=BC=9Aschema=20=E6=A0=A1=E9=AA=8C=E3=80=81=E6=96=87?= =?UTF-8?q?=E4=BB=B6=E9=9B=86=E5=90=88=E3=80=81=E5=B7=A5=E7=A8=8B=E5=93=88?= =?UTF-8?q?=E5=B8=8C=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Project.h/cpp:toml++ 解析 + 字段白名单(未知键带行号报错)、name/entry/cycle_limit/dt_ms/files.st 必填、io 条目完整性、entry 必须 program MAIN - compile_files:files.st ∪ gvl.file 去重;compute_project_hash:路径排序后 FNV-1a 64 增量,缺文件报 file missing - STCompiler :打印文件列表与哈希,失败退出码 1,--help - compiler 私有链接 toml++ v3.4.0;测试固化 compiler_toml(35 断言,line1 哈希冻结 0xc3fe4ae74ad45900) --- compiler/CMakeLists.txt | 4 +- compiler/include/compiler/Project.h | 42 ++++ compiler/src/Project.cpp | 366 ++++++++++++++++++++++++++++ compiler/src/main.cpp | 47 +++- tests/CMakeLists.txt | 11 + tests/src/compiler_test.cpp | 217 +++++++++++++++++ 6 files changed, 682 insertions(+), 5 deletions(-) create mode 100644 compiler/include/compiler/Project.h create mode 100644 compiler/src/Project.cpp create mode 100644 tests/src/compiler_test.cpp diff --git a/compiler/CMakeLists.txt b/compiler/CMakeLists.txt index c09970c..eba0270 100644 --- a/compiler/CMakeLists.txt +++ b/compiler/CMakeLists.txt @@ -8,9 +8,11 @@ project(Compiler # 词法、语法、符号表、类型检查、codegen、链接都留在 compiler 一个库里 add_library(compiler STATIC - ./src/Lexer.cpp) + ./src/Lexer.cpp + ./src/Project.cpp) target_include_directories(compiler PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include) +target_include_directories(compiler PRIVATE ${CMAKE_SOURCE_DIR}/third_party/tomlplusplus/include) target_link_libraries(compiler PUBLIC isa) # 可执行入口:STCompiler -o .stb diff --git a/compiler/include/compiler/Project.h b/compiler/include/compiler/Project.h new file mode 100644 index 0000000..eb9e24f --- /dev/null +++ b/compiler/include/compiler/Project.h @@ -0,0 +1,42 @@ +/** + * @file Project.h + * @brief 工程定义与 project.toml 解析(schema 校验) + * @author + * @date 2026-08-21 + */ + +#pragma once + +#include +#include +#include + +#include "isa/Image.h" + +namespace compiler { + + // 与 Doc/初步计划.md 12.1 的 toml 字段表一一对应。 + // 字段白名单 / 必填 / io 完整性在 parse_project 内校验。 + struct Project { + std::string name; // [project] 必填 + std::string entry; // 必填,第一版必须 "program MAIN" + uint32_t cycle_limit = 0; // 必填 > 0 + uint32_t dt_ms = 0; // 必填 > 0 + std::vector files_st; // [files] 必填非空 + std::string gvl_file; // [gvl] 可选;无则空串 + std::vector io; // [[io.*]] 可选;slot 12.6 才解析,此处填 0 + std::string base_dir; // toml 所在目录(解析相对路径用) + }; + + // 解析并校验 project.toml。成功返回 true;失败返回 false 并写 err, + // err 带稳定类别前缀(unknown key / missing field / invalid value / bad entry)+ 行号。 + bool parse_project(const std::string& toml_path, Project* out, std::string* err); + + // 编译文件集合:files.st ∪ gvl.file,去重(gvl 已在 files.st 则跳过)。 + // 保持 files.st 顺序,gvl 追加在后;相对路径以 base_dir 为基准解析。 + std::vector compile_files(const Project& p); + + // 校验全部文件存在并计算工程哈希:集合按路径排序,对内容做 FNV-1a 64 增量; + // 空集合 = basis(isa::kFnvBasis)。缺文件报错前缀 "file missing"。 + bool compute_project_hash(const Project& p, uint64_t* hash, std::string* err); +} diff --git a/compiler/src/Project.cpp b/compiler/src/Project.cpp new file mode 100644 index 0000000..c809ee5 --- /dev/null +++ b/compiler/src/Project.cpp @@ -0,0 +1,366 @@ +/** + * @file Project.cpp + * @brief 工程定义与 project.toml 解析(schema 校验) + * @author + * @date 2026-08-21 + */ + +#include "compiler/Project.h" + +#include + +#include +#include +#include +#include +#include +#include + +#include "isa/Image.h" + +namespace compiler { +namespace { + + // ---- 错误收集:稳定类别前缀 + 行号 ---- + + void fail(std::string* err, const std::string& msg) { + if (err) { + *err = msg; + } + } + + std::string at(const toml::node& n) { + char buf[32]; + std::snprintf(buf, sizeof buf, " (line %u)", n.source().begin.line); + return buf; + } + + // parse_error 的位置可能是空的(如文件打不开):只在有位置时带行号 + std::string at_pos(const toml::source_position& pos) { + char buf[32]; + std::snprintf(buf, sizeof buf, " (line %u)", pos.line); + return static_cast(pos) ? buf : std::string(); + } + + // 取字符串,必填校验 + bool req_string(const toml::table& tbl, const char* sec, const char* key, + std::string* out, std::string* err) { + if (const auto nv = tbl[key]) { + if (!nv.is_string()) { + fail(err, std::string("invalid value for '") + key + "' in [" + sec + + "] (expect string)" + at(*nv.node())); + return false; + } + *out = nv.value_or(std::string()); + return true; + } + fail(err, std::string("missing field '") + key + "' in [" + sec + "]"); + return false; + } + + // 取正整数,必填校验 + bool req_pos_int(const toml::table& tbl, const char* sec, const char* key, + uint32_t* out, std::string* err) { + if (const auto nv = tbl[key]) { + if (!nv.is_integer()) { + fail(err, std::string("invalid value for '") + key + "' in [" + sec + + "] (expect integer)" + at(*nv.node())); + return false; + } + const int64_t v = nv.value_or(0); + if (v <= 0) { + fail(err, std::string("invalid value for '") + key + "' in [" + sec + + "] (expect > 0)" + at(*nv.node())); + return false; + } + *out = static_cast(v); + return true; + } + fail(err, std::string("missing field '") + key + "' in [" + sec + "]"); + return false; + } + + // 取非负整数,必填校验(channel / bit 允许 0) + bool req_uint(const toml::table& tbl, const char* sec, const char* key, + uint32_t* out, std::string* err) { + if (const auto nv = tbl[key]) { + if (!nv.is_integer()) { + fail(err, std::string("invalid value for '") + key + "' in [" + sec + + "] (expect integer)" + at(*nv.node())); + return false; + } + const int64_t v = nv.value_or(0); + if (v < 0) { + fail(err, std::string("invalid value for '") + key + "' in [" + sec + + "] (expect >= 0)" + at(*nv.node())); + return false; + } + *out = static_cast(v); + return true; + } + fail(err, std::string("missing field '") + key + "' in [" + sec + "]"); + return false; + } + + // 表内键白名单;返回 false 并报首个未知键 + bool check_keys(const toml::table& tbl, const char* sec, + const char* const* allowed, size_t n_allowed, + std::string* err) { + for (const auto& [key, node] : tbl) { + bool ok = false; + for (size_t i = 0; i < n_allowed; ++i) { + if (key == allowed[i]) { + ok = true; + break; + } + } + if (!ok) { + fail(err, std::string("unknown key '") + std::string(key) + "' in [" + + sec + "]" + at(node)); + return false; + } + } + return true; + } + + // ---- [project] ---- + + bool parse_project_section(const toml::table& root, Project* out, std::string* err) { + static const char* const allowed[] = {"name", "entry", "cycle_limit", "dt_ms"}; + + if (const auto nv = root["project"]) { + if (!nv.is_table()) { + fail(err, "invalid value for 'project' (expect table)" + at(*nv.node())); + return false; + } + const toml::table& sec = *nv.as_table(); + if (!check_keys(sec, "project", allowed, 4, err)) { + return false; + } + if (!req_string(sec, "project", "name", &out->name, err)) { + return false; + } + if (!req_string(sec, "project", "entry", &out->entry, err)) { + return false; + } + if (out->entry != "program MAIN") { + fail(err, "invalid value for 'entry' in [project] (expect \"program MAIN\")" + + at(*sec["entry"].node())); + return false; + } + if (!req_pos_int(sec, "project", "cycle_limit", &out->cycle_limit, err)) { + return false; + } + if (!req_pos_int(sec, "project", "dt_ms", &out->dt_ms, err)) { + return false; + } + return true; + } + fail(err, "missing field 'project' in "); + return false; + } + + // ---- [files] ---- + + bool parse_files_section(const toml::table& root, Project* out, std::string* err) { + static const char* const allowed[] = {"st"}; + + if (const auto nv = root["files"]) { + if (!nv.is_table()) { + fail(err, "invalid value for 'files' (expect table)" + at(*nv.node())); + return false; + } + const toml::table& sec = *nv.as_table(); + if (!check_keys(sec, "files", allowed, 1, err)) { + return false; + } + if (const auto st = sec["st"]) { + if (!st.is_array()) { + fail(err, "invalid value for 'st' in [files] (expect array)" + at(*st.node())); + return false; + } + const toml::array& arr = *st.as_array(); + if (arr.empty()) { + fail(err, "invalid value for 'st' in [files] (expect non-empty array)"); + return false; + } + for (const auto& el : arr) { + if (!el.is_string()) { + fail(err, "invalid value in 'st' in [files] (expect array of strings)" + + at(el)); + return false; + } + out->files_st.push_back(el.value_or(std::string())); + } + return true; + } + fail(err, "missing field 'st' in [files]"); + return false; + } + fail(err, "missing field 'files' in "); + return false; + } + + // ---- [gvl](可选)---- + + bool parse_gvl_section(const toml::table& root, Project* out, std::string* err) { + static const char* const allowed[] = {"file"}; + + if (const auto nv = root["gvl"]) { + if (!nv.is_table()) { + fail(err, "invalid value for 'gvl' (expect table)" + at(*nv.node())); + return false; + } + const toml::table& sec = *nv.as_table(); + if (!check_keys(sec, "gvl", allowed, 1, err)) { + return false; + } + return req_string(sec, "gvl", "file", &out->gvl_file, err); + } + return true; // gvl 可选 + } + + // ---- [[io.input]] / [[io.output]](可选)---- + + bool parse_io_entry(const toml::node& el, bool is_input, Project* out, std::string* err) { + static const char* const allowed[] = {"var", "channel", "bit"}; + + if (!el.is_table()) { + fail(err, std::string("bad entry in [[io.") + (is_input ? "input" : "output") + + "]] (expect table)" + at(el)); + return false; + } + const toml::table& sec = *el.as_table(); + if (!check_keys(sec, is_input ? "io.input" : "io.output", allowed, 3, err)) { + return false; + } + isa::IoBinding b; + b.is_input = is_input; + b.slot = 0; // 12.6 链接阶段再解析 + if (!req_string(sec, is_input ? "io.input" : "io.output", "var", &b.var, err)) { + return false; + } + if (!req_uint(sec, is_input ? "io.input" : "io.output", "channel", &b.channel, err)) { + return false; + } + if (!req_uint(sec, is_input ? "io.input" : "io.output", "bit", &b.bit, err)) { + return false; + } + out->io.push_back(b); + return true; + } + + bool parse_io_section(const toml::table& root, Project* out, std::string* err) { + static const char* const allowed[] = {"input", "output"}; + + if (const auto nv = root["io"]) { + if (!nv.is_table()) { + fail(err, "invalid value for 'io' (expect table)" + at(*nv.node())); + return false; + } + const toml::table& sec = *nv.as_table(); + if (!check_keys(sec, "io", allowed, 2, err)) { + return false; + } + for (const char* which : {"input", "output"}) { + if (const auto arr = sec[which]) { + if (!arr.is_array()) { + fail(err, std::string("invalid value for '") + which + + "' in [io] (expect array of tables)" + at(*arr.node())); + return false; + } + const bool is_input = (which[0] == 'i'); + for (const auto& el : *arr.as_array()) { + if (!parse_io_entry(el, is_input, out, err)) { + return false; + } + } + } + } + return true; + } + return true; // io 可选 + } + + // ---- 顶层 ---- + + bool parse_root(const toml::table& root, Project* out, std::string* err) { + static const char* const allowed[] = {"project", "files", "gvl", "io"}; + if (!check_keys(root, "root", allowed, 4, err)) { + return false; + } + if (!parse_project_section(root, out, err)) { + return false; + } + if (!parse_files_section(root, out, err)) { + return false; + } + if (!parse_gvl_section(root, out, err)) { + return false; + } + return parse_io_section(root, out, err); + } + + std::string dir_of(const std::string& path) { + const size_t slash = path.find_last_of("/\\"); + return (slash == std::string::npos) ? "." : path.substr(0, slash); + } + +} // namespace + +bool parse_project(const std::string& toml_path, Project* out, std::string* err) { + out->base_dir = dir_of(toml_path); + + // toml++ 默认 TOML_EXCEPTIONS=1:parse_file 失败直接抛 parse_error + toml::table tbl; + try { + tbl = toml::parse_file(toml_path); + } catch (const toml::parse_error& e) { + fail(err, std::string("parse error: ") + std::string(e.description()) + + at_pos(e.source().begin)); + return false; + } + return parse_root(tbl, out, err); +} + +// ---- 文件集合与工程哈希 ---- + +std::vector compile_files(const Project& p) { + std::vector out = p.files_st; + if (!p.gvl_file.empty() && + std::find(out.begin(), out.end(), p.gvl_file) == out.end()) { + out.push_back(p.gvl_file); + } + return out; +} + +bool compute_project_hash(const Project& p, uint64_t* hash, std::string* err) { + std::vector files = compile_files(p); + + // 路径排序(规格:路径只当排序键) + std::vector sorted = files; + std::sort(sorted.begin(), sorted.end()); + + std::filesystem::path base(p.base_dir); + uint64_t h = isa::kFnvBasis; + for (const std::string& f : sorted) { + std::ifstream in(base / f, std::ios::binary); + if (!in) { + fail(err, "file missing: " + (base / f).string()); + return false; + } + char buf[4096]; + while (in) { + in.read(buf, sizeof buf); + const std::streamsize n = in.gcount(); + if (n > 0) { + h = isa::fnv1a64_update(h, reinterpret_cast(buf), + static_cast(n)); + } + } + } + *hash = h; + return true; +} + +} // namespace compiler diff --git a/compiler/src/main.cpp b/compiler/src/main.cpp index 222bc03..a45da05 100644 --- a/compiler/src/main.cpp +++ b/compiler/src/main.cpp @@ -2,14 +2,53 @@ * @file main.cpp * @brief STCompiler 可执行入口 * @author - * @date 2026-08-19 + * @date 2026-08-21 */ #include +#include +#include +#include + +#include "compiler/Project.h" + +namespace { + void usage() { + std::printf("usage: STCompiler \n" + " 解析工程并打印文件集合与工程哈希(12.3 阶段)\n" + " --help 打印本帮助\n"); + } +} int main(int argc, char** argv) { - (void)argc; - (void)argv; - std::printf("STCompiler 0.1\n"); + if (argc < 2) { + std::printf("STCompiler 0.1\n"); + usage(); + return 0; + } + if (std::strcmp(argv[1], "--help") == 0) { + usage(); + return 0; + } + + compiler::Project proj; + std::string err; + if (!compiler::parse_project(argv[1], &proj, &err)) { + std::fprintf(stderr, "error: %s\n", err.c_str()); + return 1; + } + const std::vector files = compiler::compile_files(proj); + uint64_t hash = 0; + if (!compiler::compute_project_hash(proj, &hash, &err)) { + std::fprintf(stderr, "error: %s\n", err.c_str()); + return 1; + } + + std::printf("project: %s\n", proj.name.c_str()); + std::printf("files:\n"); + for (const std::string& f : files) { + std::printf(" %s\n", f.c_str()); + } + std::printf("hash: 0x%016llx\n", static_cast(hash)); return 0; } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 44ab0de..7765d03 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -12,3 +12,14 @@ target_link_libraries(isa_test PRIVATE isa) add_test(NAME isa_roundtrip COMMAND isa_test) + +# compiler 库测试:toml schema、文件集合、工程哈希(REPO_ROOT 注入源目录绝对路径) +add_executable(compiler_test + ./src/compiler_test.cpp) + +target_link_libraries(compiler_test PRIVATE compiler) +target_compile_definitions(compiler_test PRIVATE + REPO_ROOT="${CMAKE_SOURCE_DIR}") + +add_test(NAME compiler_toml + COMMAND compiler_test) diff --git a/tests/src/compiler_test.cpp b/tests/src/compiler_test.cpp new file mode 100644 index 0000000..d77fcb0 --- /dev/null +++ b/tests/src/compiler_test.cpp @@ -0,0 +1,217 @@ +/** + * @file compiler_test.cpp + * @brief compiler 库测试:toml schema、文件集合、工程哈希 + * @author + * @date 2026-08-21 + */ + +#include +#include +#include +#include + +#include "compiler/Project.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) + +// ---- 写临时 toml(负例用),返回路径 ---- + +static std::string write_tmp(const char* name, const char* content) { + const std::filesystem::path dir = std::filesystem::temp_directory_path(); + const std::filesystem::path path = dir / name; + std::FILE* f = std::fopen(path.string().c_str(), "w"); + std::fputs(content, f); + std::fclose(f); + return path.string(); +} + +static void rm_tmp(const std::string& path) { + std::remove(path.c_str()); +} + +// ---- 1. line1 正例 ---- + +static bool test_line1() { + using namespace compiler; + Project p; + std::string err; + CHECK(parse_project(std::string(REPO_ROOT) + "/examples/line1/project.toml", &p, &err)); + CHECK(p.name == "line1"); + CHECK(p.entry == "program MAIN"); + CHECK(p.cycle_limit == 100000); + CHECK(p.dt_ms == 10); + CHECK(p.files_st.size() == 3); + CHECK(p.files_st[0] == "globals.st"); + CHECK(p.gvl_file == "globals.st"); + CHECK(p.io.size() == 3); + CHECK(p.io[0].var == "EmergencyStop" && p.io[0].is_input && p.io[0].bit == 2); + CHECK(p.io[2].var == "Q0_0" && !p.io[2].is_input); + CHECK(p.base_dir == std::string(REPO_ROOT) + "/examples/line1"); + + const std::vector files = compile_files(p); + CHECK(files.size() == 3); // gvl 已在 files.st,不重复 + + uint64_t h = 0; + CHECK(compute_project_hash(p, &h, &err)); + CHECK(h == 0xc3fe4ae74ad45900ull); // 冻结值:确定性回放 + return true; +} + +// ---- 2. 无 io / 无 gvl 的用例 ---- + +static bool test_minimal() { + using namespace compiler; + Project p; + std::string err; + CHECK(parse_project(std::string(REPO_ROOT) + "/tests/cases/01_empty_main/project.toml", &p, &err)); + CHECK(p.gvl_file.empty()); + CHECK(p.io.empty()); + CHECK(compile_files(p).size() == 1); + CHECK(parse_project(std::string(REPO_ROOT) + "/tests/cases/15_fb_instance/project.toml", &p, &err)); + CHECK(p.gvl_file.empty()); + return true; +} + +// ---- 3. gvl.file 不在 files.st 时追加 ---- + +static bool test_gvl_append() { + using namespace compiler; + const std::string path = write_tmp( + "stator_gvl_append.toml", + "[project]\nname = \"x\"\nentry = \"program MAIN\"\ncycle_limit = 1\ndt_ms = 1\n" + "[files]\nst = [\"main.st\"]\n[gvl]\nfile = \"globals.st\"\n"); + Project p; + std::string err; + CHECK(parse_project(path, &p, &err)); + const std::vector files = compile_files(p); + CHECK(files.size() == 2); + CHECK(files[0] == "main.st"); + CHECK(files[1] == "globals.st"); + rm_tmp(path); + return true; +} + +// ---- 4. 负例:schema 校验类别 ---- + +static bool expect_err(const std::string& path, const char* prefix) { + compiler::Project p; + std::string err; + if (compiler::parse_project(path, &p, &err)) { + std::printf("FAIL %s: parsed ok\n", path.c_str()); + return false; + } + if (err.find(prefix) != 0) { + std::printf("FAIL %s: want '%s', got '%s'\n", path.c_str(), prefix, err.c_str()); + return false; + } + ++g_checks; + return true; +} + +static bool test_negative() { + using namespace compiler; + + const std::string b1 = write_tmp( + "stator_bad1.toml", + "[project]\nname = \"x\"\nentry = \"program MAIN\"\ncycle_limit = 1\ndt_ms = 1\n" + "[files]\nst = [\"a.st\"]\n[bogus]\nx = 1\n"); + if (!expect_err(b1, "unknown key")) return false; + + const std::string b2 = write_tmp( + "stator_bad2.toml", + "[project]\nentry = \"program MAIN\"\ncycle_limit = 1\ndt_ms = 1\n" + "[files]\nst = [\"a.st\"]\n"); + if (!expect_err(b2, "missing field")) return false; + + const std::string b3 = write_tmp( + "stator_bad3.toml", + "[project]\nname = \"x\"\nentry = \"program foo\"\ncycle_limit = 1\ndt_ms = 1\n" + "[files]\nst = [\"a.st\"]\n"); + if (!expect_err(b3, "invalid value")) return false; + + const std::string b4 = write_tmp( + "stator_bad4.toml", + "[project]\nname = \"x\"\nentry = \"program MAIN\"\ncycle_limit = 1\ndt_ms = 1\n" + "[files]\nst = [\"a.st\"]\n[[io.input]]\nvar = \"I0_0\"\nchannel = 0\n"); + if (!expect_err(b4, "missing field")) return false; + + const std::string b5 = write_tmp("stator_bad5.toml", "[project\nname = \"x\"\n"); + if (!expect_err(b5, "parse error")) return false; + + // 缺文件(compute_project_hash 层) + const std::string b6 = write_tmp( + "stator_bad6.toml", + "[project]\nname = \"x\"\nentry = \"program MAIN\"\ncycle_limit = 1\ndt_ms = 1\n" + "[files]\nst = [\"nope.st\"]\n"); + Project p; + std::string err; + CHECK(parse_project(b6, &p, &err)); + uint64_t h = 0; + CHECK(!compute_project_hash(p, &h, &err)); + CHECK(err.find("file missing") == 0); + + // toml 文件本身不存在 + if (!expect_err(std::filesystem::temp_directory_path().string() + "/stator_nope.toml", + "parse error")) { + return false; + } + + rm_tmp(b1); + rm_tmp(b2); + rm_tmp(b3); + rm_tmp(b4); + rm_tmp(b5); + rm_tmp(b6); + return true; +} + +// ---- 5. 20 个用例全部可解析 + 可哈希 ---- + +static bool test_all_cases() { + using namespace compiler; + int n = 0; + for (const auto& dir : std::filesystem::directory_iterator( + std::string(REPO_ROOT) + "/tests/cases")) { + if (!dir.is_directory()) { + continue; + } + const std::string toml = dir.path().string() + "/project.toml"; + Project p; + std::string err; + if (!parse_project(toml, &p, &err)) { + std::printf("FAIL parse %s: %s\n", toml.c_str(), err.c_str()); + return false; + } + uint64_t h = 0; + if (!compute_project_hash(p, &h, &err)) { + std::printf("FAIL hash %s: %s\n", toml.c_str(), err.c_str()); + return false; + } + ++n; + } + CHECK(n == 20); + return true; +} + +int main() { + if (!test_line1()) return 1; + if (!test_minimal()) return 1; + if (!test_gvl_append()) return 1; + if (!test_negative()) return 1; + if (!test_all_cases()) return 1; + std::printf("compiler_test: %d checks passed\n", g_checks); + return 0; +}