diff --git a/compiler/CMakeLists.txt b/compiler/CMakeLists.txt index eecfc8e..3432b2f 100644 --- a/compiler/CMakeLists.txt +++ b/compiler/CMakeLists.txt @@ -13,7 +13,8 @@ add_library(compiler STATIC ./src/Parser.cpp ./src/Linker.cpp ./src/Typecheck.cpp - ./src/Codegen.cpp) + ./src/Codegen.cpp + ./src/MachineConfig.cpp) target_include_directories(compiler PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include) target_include_directories(compiler PRIVATE ${CMAKE_SOURCE_DIR}/third_party/tomlplusplus/include) diff --git a/compiler/include/compiler/MachineConfig.h b/compiler/include/compiler/MachineConfig.h new file mode 100644 index 0000000..52f7600 --- /dev/null +++ b/compiler/include/compiler/MachineConfig.h @@ -0,0 +1,92 @@ +/** + * @file MachineConfig.h + * @brief 机器定义(machine.toml)加载与强校验 + * @author + * @date 2026-08-21 + * + * @details 见 Doc/isa/指令配置.md: + * - 配置只给编译器(toml++ 运行时加载);vm 不读配置(内建全指令集) + * - 强校验 5 项:字段必填 / type.base 命中内建基元·range 合法·tag 契约 / + * op opcode 唯一·类别-格式互锁 / fb.opcode 与 op 一致·字段类型命中 type 表 + * - 基元内置(bit/int8..uint64/float32/float64,元数据在代码) + */ + +#pragma once + +#include +#include +#include + +namespace compiler { + + // 内建基元(编译器内建,C 语义) + struct PrimType { + const char* name; + uint32_t width; // 字节 + bool is_signed; + bool is_float; + }; + + // 配置类型 = 基元别名(+ 值域约束 + .stb 契约 tag) + struct ConfigType { + std::string name; + std::string base; + bool has_range = false; + int64_t range_min = 0; + int64_t range_max = 0; + uint32_t tag = 0; + }; + + // 配置指令 + struct ConfigOp { + std::string name; + uint32_t opcode = 0; + bool is_instance = false; // class: instance / plain + std::string format; // RR/RRR/IMM/SLOT/JMP/JC/CALL/CAL/NONE + std::vector params; + bool enabled = false; + }; + + struct ConfigFbField { + std::string name; + std::string type; // 配置类型名(BOOL/INT/TIME) + }; + + struct ConfigFb { + std::string name; + uint32_t opcode = 0; + std::vector fields; + }; + + class MachineConfig { + public: + // 加载 + 强校验;失败返回 false 并写 err(前缀 "machine error") + bool load(const std::string& path, std::string* err); + + bool ok() const { return ok_; } + const std::string& model_name() const { return model_name_; } + uint32_t version() const { return version_; } + + const std::vector& types() const { return types_; } + const std::vector& ops() const { return ops_; } + const std::vector& fbs() const { return fbs_; } + + const ConfigType* find_type(const std::string& name) const; + const ConfigOp* find_op(const std::string& name) const; + const ConfigOp* find_op_by_code(uint32_t opcode) const; + bool op_enabled(uint32_t opcode) const; + const ConfigFb* find_fb(const std::string& name) const; + + // 内建基元表 + static const std::vector& prims(); + static const PrimType* find_prim(const std::string& name); + + private: + bool ok_ = false; + std::string model_name_; + uint32_t version_ = 0; + std::vector types_; + std::vector ops_; + std::vector fbs_; + }; +} diff --git a/compiler/src/MachineConfig.cpp b/compiler/src/MachineConfig.cpp new file mode 100644 index 0000000..f3c000a --- /dev/null +++ b/compiler/src/MachineConfig.cpp @@ -0,0 +1,396 @@ +/** + * @file MachineConfig.cpp + * @brief 机器定义(machine.toml)加载与强校验 + * @author + * @date 2026-08-21 + * + * @details 校验项(见 Doc/isa/指令配置.md 强校验清单 1~5): + * 1. TOML 语法与字段完整性(全属性必填) + * 2. type.base 命中内建基元;range 合法(min ≤ max) + * 3. tag 与 .stb 常量表契约一致(BOOL=0、INT=1、TIME=2,唯一) + * 4. op.opcode 唯一、0..255;class/format 合法;类别-格式互锁(INSTANCE ↔ CAL); + * params 数量与 format 一致 + * 5. fb.opcode 与 op 行(instance 类)一致;字段类型命中配置类型表;字段名唯一 + */ + +#include "compiler/MachineConfig.h" + +#include + +#include +#include + +namespace compiler { + +const std::vector& MachineConfig::prims() { + static const std::vector kPrims = { + {"bit", 1, false, false}, + {"int8", 1, true, false}, + {"int16", 2, true, false}, + {"int32", 4, true, false}, + {"int64", 8, true, false}, + {"uint8", 1, false, false}, + {"uint16", 2, false, false}, + {"uint32", 4, false, false}, + {"uint64", 8, false, false}, + {"float32", 4, true, true}, + {"float64", 8, true, true}, + }; + return kPrims; +} + +const PrimType* MachineConfig::find_prim(const std::string& name) { + for (const PrimType& p : prims()) { + if (p.name == name) { + return &p; + } + } + return nullptr; +} + +namespace { + + // 稳定前缀 + std::string fail(std::string* err, const std::string& msg) { + if (err) { + *err = "machine error: " + msg; + } + return msg; + } + + // format → 参数数量(解析时校验) + int params_of(const std::string& format) { + if (format == "RR") return 2; + if (format == "RRR") return 3; + if (format == "IMM") return 2; + if (format == "SLOT") return 2; + if (format == "JMP") return 1; + if (format == "JC") return 2; + if (format == "CALL") return 1; + if (format == "CAL") return 1; + if (format == "NONE") return 0; + return -1; + } + + bool is_known_format(const std::string& f) { + return params_of(f) >= 0; + } + + bool is_known_tag(uint32_t tag) { + return tag <= 2; // .stb 常量表契约:0=BOOL 1=INT 2=TIME + } + + // 必填字符串 + bool req_string(const toml::table& t, const char* what, const std::string& key, + std::string* out, std::string* err) { + const auto nv = t[key]; + if (!nv || !nv.is_string()) { + fail(err, std::string("missing field '") + key + "' in " + what); + return false; + } + *out = nv.value_or(std::string()); + return true; + } + + // 必填整数 + bool req_int(const toml::table& t, const char* what, const std::string& key, + int64_t* out, std::string* err) { + const auto nv = t[key]; + if (!nv || !nv.is_integer()) { + fail(err, std::string("missing field '") + key + "' in " + what); + return false; + } + *out = nv.value_or(0); + return true; + } + + // 必填布尔 + bool req_bool(const toml::table& t, const char* what, const std::string& key, + bool* out, std::string* err) { + const auto nv = t[key]; + if (!nv || !nv.is_boolean()) { + fail(err, std::string("missing field '") + key + "' in " + what); + return false; + } + *out = nv.value_or(false); + return true; + } + +} // namespace + +bool MachineConfig::load(const std::string& path, std::string* err) { + ok_ = false; + model_name_.clear(); + version_ = 0; + types_.clear(); + ops_.clear(); + fbs_.clear(); + + toml::table root; + try { + root = toml::parse_file(path); + } catch (const toml::parse_error& e) { + fail(err, std::string("parse error: ") + std::string(e.description())); + return false; + } + + // ---- meta ---- + const toml::table* meta = root["meta"].as_table(); + if (!meta) { + fail(err, "missing table 'meta'"); + return false; + } + if (!req_string(*meta, "[meta]", "name", &model_name_, err)) return false; + int64_t ver = 0; + if (!req_int(*meta, "[meta]", "version", &ver, err)) return false; + if (ver < 0 || ver > 0xFFFFFFFFLL) { + fail(err, "invalid version in [meta]"); + return false; + } + version_ = static_cast(ver); + + // ---- type ---- + { + const toml::array* arr = root["type"].as_array(); + if (!arr || arr->empty()) { + fail(err, "missing 'type' table"); + return false; + } + std::map tag_owner; // tag → 类型名 + for (const auto& el : *arr) { + const toml::table* t = el.as_table(); + if (!t) { + fail(err, "bad entry in type"); + return false; + } + ConfigType ct; + if (!req_string(*t, "type", "name", &ct.name, err)) return false; + if (!req_string(*t, "type", "base", &ct.base, err)) return false; + if (find_prim(ct.base) == nullptr) { + fail(err, "type '" + ct.name + "': unknown base '" + ct.base + "'"); + return false; + } + int64_t tag = 0; + if (!req_int(*t, "type", "tag", &tag, err)) return false; + if (tag < 0 || !is_known_tag(static_cast(tag))) { + fail(err, "type '" + ct.name + "': tag out of contract (0..2)"); + return false; + } + ct.tag = static_cast(tag); + if (tag_owner.count(ct.tag)) { + fail(err, "duplicate tag " + std::to_string(ct.tag)); + return false; + } + tag_owner[ct.tag] = ct.name; + if (t->contains("range")) { + const toml::array* r = (*t)["range"].as_array(); + if (!r || r->size() != 2) { + fail(err, "type '" + ct.name + "': range must be [min, max]"); + return false; + } + ct.has_range = true; + ct.range_min = (*r)[0].value_or(0); + ct.range_max = (*r)[1].value_or(0); + if (ct.range_min > ct.range_max) { + fail(err, "type '" + ct.name + "': range min > max"); + return false; + } + } + types_.push_back(ct); + } + // .stb 契约:BOOL/INT/TIME 必须存在且 tag 为 0/1/2 + const std::string need[3] = {"BOOL", "INT", "TIME"}; + for (uint32_t i = 0; i < 3; ++i) { + const auto it = tag_owner.find(i); + if (it == tag_owner.end() || it->second != need[i]) { + fail(err, std::string("type contract broken: tag ") + std::to_string(i) + + " must be " + need[i]); + return false; + } + } + } + + // ---- op ---- + { + const toml::array* arr = root["op"].as_array(); + if (!arr || arr->empty()) { + fail(err, "missing 'op' table"); + return false; + } + std::map code_owner; + std::map name_owner; + for (const auto& el : *arr) { + const toml::table* t = el.as_table(); + if (!t) { + fail(err, "bad entry in op"); + return false; + } + ConfigOp op; + if (!req_string(*t, "op", "name", &op.name, err)) return false; + int64_t code = 0; + if (!req_int(*t, "op", "opcode", &code, err)) return false; + if (code < 0 || code > 255) { + fail(err, "op '" + op.name + "': opcode out of range"); + return false; + } + op.opcode = static_cast(code); + if (code_owner.count(op.opcode)) { + fail(err, "duplicate opcode " + std::to_string(op.opcode)); + return false; + } + code_owner[op.opcode] = op.name; + if (name_owner.count(op.name)) { + fail(err, "duplicate op name '" + op.name + "'"); + return false; + } + name_owner[op.name] = op.opcode; + std::string cls; + if (!req_string(*t, "op", "class", &cls, err)) return false; + if (cls == "instance") { + op.is_instance = true; + } else if (cls == "plain") { + op.is_instance = false; + } else { + fail(err, "op '" + op.name + "': bad class '" + cls + "'"); + return false; + } + if (!req_string(*t, "op", "format", &op.format, err)) return false; + if (!is_known_format(op.format)) { + fail(err, "op '" + op.name + "': bad format '" + op.format + "'"); + return false; + } + // 类别-格式互锁:INSTANCE ↔ CAL + const bool is_cal = op.format == "CAL"; + if (op.is_instance != is_cal) { + fail(err, "op '" + op.name + "': class-format mismatch (instance <-> CAL)"); + return false; + } + if (!req_bool(*t, "op", "enabled", &op.enabled, err)) return false; + const auto pv = (*t)["params"]; + if (!pv || !pv.is_array()) { + fail(err, "op '" + op.name + "': missing 'params'"); + return false; + } + const int want = params_of(op.format); + for (const auto& p : *pv.as_array()) { + if (!p.is_string()) { + fail(err, "op '" + op.name + "': params must be strings"); + return false; + } + op.params.push_back(p.value_or(std::string())); + } + if (static_cast(op.params.size()) != want) { + fail(err, "op '" + op.name + "': params count " + + std::to_string(op.params.size()) + " != format expects " + + std::to_string(want)); + return false; + } + ops_.push_back(op); + } + } + + // ---- fb ---- + { + const toml::array* arr = root["fb"].as_array(); + if (!arr || arr->empty()) { + fail(err, "missing 'fb' table"); + return false; + } + for (const auto& el : *arr) { + const toml::table* t = el.as_table(); + if (!t) { + fail(err, "bad entry in fb"); + return false; + } + ConfigFb fb; + if (!req_string(*t, "fb", "name", &fb.name, err)) return false; + int64_t code = 0; + if (!req_int(*t, "fb", "opcode", &code, err)) return false; + fb.opcode = static_cast(code); + // opcode 必须命中 instance 类 op 行 + const ConfigOp* op = find_op_by_code(fb.opcode); + if (op == nullptr || !op->is_instance) { + fail(err, "fb '" + fb.name + "': opcode " + std::to_string(code) + + " must match an instance op"); + return false; + } + const auto fv = (*t)["fields"]; + if (!fv || !fv.is_array()) { + fail(err, "fb '" + fb.name + "': missing 'fields'"); + return false; + } + std::map fnames; + for (const auto& f : *fv.as_array()) { + const toml::array* pair = f.as_array(); + if (!pair || pair->size() != 2) { + fail(err, "fb '" + fb.name + "': field must be [name, type]"); + return false; + } + ConfigFbField field; + field.name = (*pair)[0].value_or(std::string()); + field.type = (*pair)[1].value_or(std::string()); + if (field.name.empty() || field.type.empty()) { + fail(err, "fb '" + fb.name + "': empty field name/type"); + return false; + } + if (fnames.count(field.name)) { + fail(err, "fb '" + fb.name + "': duplicate field '" + field.name + "'"); + return false; + } + fnames[field.name] = true; + if (find_type(field.type) == nullptr) { + fail(err, "fb '" + fb.name + "': unknown field type '" + field.type + "'"); + return false; + } + fb.fields.push_back(field); + } + fbs_.push_back(fb); + } + } + + ok_ = true; + return true; +} + +const ConfigType* MachineConfig::find_type(const std::string& name) const { + for (const ConfigType& t : types_) { + if (t.name == name) { + return &t; + } + } + return nullptr; +} + +const ConfigOp* MachineConfig::find_op(const std::string& name) const { + for (const ConfigOp& o : ops_) { + if (o.name == name) { + return &o; + } + } + return nullptr; +} + +const ConfigOp* MachineConfig::find_op_by_code(uint32_t opcode) const { + for (const ConfigOp& o : ops_) { + if (o.opcode == opcode) { + return &o; + } + } + return nullptr; +} + +bool MachineConfig::op_enabled(uint32_t opcode) const { + const ConfigOp* op = find_op_by_code(opcode); + return op != nullptr && op->enabled; +} + +const ConfigFb* MachineConfig::find_fb(const std::string& name) const { + for (const ConfigFb& f : fbs_) { + if (f.name == name) { + return &f; + } + } + return nullptr; +} + +} // namespace compiler diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 7f96223..c8032fa 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -100,3 +100,14 @@ target_compile_definitions(cases_test PRIVATE add_test(NAME cases_all COMMAND cases_test) + +# machine.toml 加载与强校验(阶段 A 步骤 3;REPO_ROOT 注入源目录绝对路径) +add_executable(machine_test + ./src/machine_test.cpp) + +target_link_libraries(machine_test PRIVATE compiler) +target_compile_definitions(machine_test PRIVATE + REPO_ROOT="${CMAKE_SOURCE_DIR}") + +add_test(NAME machine_config + COMMAND machine_test) diff --git a/tests/src/machine_test.cpp b/tests/src/machine_test.cpp new file mode 100644 index 0000000..1cdcb3b --- /dev/null +++ b/tests/src/machine_test.cpp @@ -0,0 +1,261 @@ +/** + * @file machine_test.cpp + * @brief machine.toml 加载与强校验测试(阶段 A 步骤 3) + * @author + * @date 2026-08-21 + */ + +#include +#include +#include + +#include "compiler/MachineConfig.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 bool expect_load_err(const char* name, const char* content, const char* keyword) { + const std::string path = write_tmp(name, content); + compiler::MachineConfig cfg; + std::string err; + if (cfg.load(path, &err)) { + std::printf("FAIL %s: loaded ok\n", name); + std::remove(path.c_str()); + return false; + } + if (err.find("machine error") != 0) { + std::printf("FAIL %s: want 'machine error', got '%s'\n", name, err.c_str()); + std::remove(path.c_str()); + return false; + } + if (err.find(keyword) == std::string::npos) { + std::printf("FAIL %s: want '%s', got '%s'\n", name, keyword, err.c_str()); + std::remove(path.c_str()); + return false; + } + ++g_checks; + std::remove(path.c_str()); + return true; +} + +// 合法内容模板(可注入损坏) +static const char* kGood = + "[meta]\nname = \"STATOR\"\nversion = 1\n" + "[[type]]\nname = \"BOOL\"\nbase = \"uint8\"\nrange = [0, 1]\ntag = 0\n" + "[[type]]\nname = \"INT\"\nbase = \"int16\"\ntag = 1\n" + "[[type]]\nname = \"TIME\"\nbase = \"int64\"\ntag = 2\n" + "[[op]]\nname = \"MOVE\"\nopcode = 0\nclass = \"plain\"\nformat = \"RR\"\nparams = [\"rd\", \"rs\"]\nenabled = true\n" + "[[op]]\nname = \"CAL_TON\"\nopcode = 24\nclass = \"instance\"\nformat = \"CAL\"\nparams = [\"instance\"]\nenabled = true\n" + "[[op]]\nname = \"RET\"\nopcode = 33\nclass = \"plain\"\nformat = \"NONE\"\nparams = []\nenabled = true\n" + "[[fb]]\nname = \"ton\"\nopcode = 24\nfields = [[\"in\", \"BOOL\"], [\"pt\", \"TIME\"], [\"q\", \"BOOL\"], [\"et\", \"TIME\"]]\n"; + +// ---- 1. 正例:仓库 machine.toml ---- + +static bool test_positive() { + compiler::MachineConfig cfg; + std::string err; + CHECK(cfg.load(std::string(REPO_ROOT) + "/compiler/machine.toml", &err)); + CHECK(cfg.ok()); + CHECK(cfg.model_name() == "STATOR"); + CHECK(cfg.version() == 1); + + CHECK(cfg.types().size() == 3); + const compiler::ConfigType* bt = cfg.find_type("BOOL"); + CHECK(bt != nullptr && bt->base == "uint8" && bt->has_range && bt->range_min == 0 && + bt->range_max == 1 && bt->tag == 0); + CHECK(cfg.find_type("INT") != nullptr && cfg.find_type("INT")->tag == 1); + CHECK(cfg.find_type("TIME") != nullptr && cfg.find_type("TIME")->tag == 2); + CHECK(cfg.find_type("REAL") == nullptr); + + CHECK(cfg.ops().size() == 34); + const compiler::ConfigOp* add = cfg.find_op("ADD"); + CHECK(add != nullptr && add->opcode == 5 && add->format == "RRR" && !add->is_instance && + add->params.size() == 3); + const compiler::ConfigOp* ton = cfg.find_op("CAL_TON"); + CHECK(ton != nullptr && ton->opcode == 24 && ton->is_instance && ton->format == "CAL"); + CHECK(cfg.find_op("CAL_F_TRIG") != nullptr && cfg.find_op("CAL_F_TRIG")->opcode == 31); + CHECK(cfg.find_op_by_code(33) != nullptr && cfg.find_op_by_code(33)->name == "RET"); + CHECK(cfg.op_enabled(0) && cfg.op_enabled(24)); + CHECK(cfg.find_op("NOPE") == nullptr); + + CHECK(cfg.fbs().size() == 8); + const compiler::ConfigFb* ctud = cfg.find_fb("ctud"); + CHECK(ctud != nullptr && ctud->opcode == 29 && ctud->fields.size() == 8); + CHECK(cfg.find_fb("r_trig") != nullptr && cfg.find_fb("r_trig")->fields.size() == 2); + + // 基元表 + CHECK(compiler::MachineConfig::find_prim("int16") != nullptr); + const compiler::PrimType* p = compiler::MachineConfig::find_prim("uint8"); + CHECK(p != nullptr && p->width == 1 && !p->is_signed && !p->is_float); + CHECK(compiler::MachineConfig::find_prim("float64") != nullptr); + CHECK(compiler::MachineConfig::find_prim("bfloat16") == nullptr); + return true; +} + +// ---- 2. 负例:9 类 ---- + +static bool test_negative() { + // 缺属性(enabled 缺失) + if (!expect_load_err( + "mc_bad1.toml", + "[meta]\nname = \"S\"\nversion = 1\n" + "[[type]]\nname = \"BOOL\"\nbase = \"uint8\"\ntag = 0\n" + "[[type]]\nname = \"INT\"\nbase = \"int16\"\ntag = 1\n" + "[[type]]\nname = \"TIME\"\nbase = \"int64\"\ntag = 2\n" + "[[op]]\nname = \"MOVE\"\nopcode = 0\nclass = \"plain\"\nformat = \"RR\"\nparams = [\"rd\", \"rs\"]\n", + "missing field 'enabled'")) { + return false; + } + // base 未命中基元 + if (!expect_load_err( + "mc_bad2.toml", + "[meta]\nname = \"S\"\nversion = 1\n" + "[[type]]\nname = \"BOOL\"\nbase = \"bigint\"\ntag = 0\n" + "[[type]]\nname = \"INT\"\nbase = \"int16\"\ntag = 1\n" + "[[type]]\nname = \"TIME\"\nbase = \"int64\"\ntag = 2\n" + "[[op]]\nname = \"RET\"\nopcode = 33\nclass = \"plain\"\nformat = \"NONE\"\nparams = []\nenabled = true\n", + "unknown base")) { + return false; + } + // range min > max + if (!expect_load_err( + "mc_bad3.toml", + "[meta]\nname = \"S\"\nversion = 1\n" + "[[type]]\nname = \"BOOL\"\nbase = \"uint8\"\nrange = [1, 0]\ntag = 0\n" + "[[type]]\nname = \"INT\"\nbase = \"int16\"\ntag = 1\n" + "[[type]]\nname = \"TIME\"\nbase = \"int64\"\ntag = 2\n" + "[[op]]\nname = \"RET\"\nopcode = 33\nclass = \"plain\"\nformat = \"NONE\"\nparams = []\nenabled = true\n", + "range min > max")) { + return false; + } + // tag 契约破坏(BOOL tag=5) + if (!expect_load_err( + "mc_bad4.toml", + "[meta]\nname = \"S\"\nversion = 1\n" + "[[type]]\nname = \"BOOL\"\nbase = \"uint8\"\ntag = 5\n" + "[[type]]\nname = \"INT\"\nbase = \"int16\"\ntag = 1\n" + "[[type]]\nname = \"TIME\"\nbase = \"int64\"\ntag = 2\n" + "[[op]]\nname = \"RET\"\nopcode = 33\nclass = \"plain\"\nformat = \"NONE\"\nparams = []\nenabled = true\n", + "tag out of contract")) { + return false; + } + // tag 契约破坏(INT 占 tag 0) + if (!expect_load_err( + "mc_bad5.toml", + "[meta]\nname = \"S\"\nversion = 1\n" + "[[type]]\nname = \"BOOL\"\nbase = \"uint8\"\ntag = 1\n" + "[[type]]\nname = \"INT\"\nbase = \"int16\"\ntag = 0\n" + "[[type]]\nname = \"TIME\"\nbase = \"int64\"\ntag = 2\n" + "[[op]]\nname = \"RET\"\nopcode = 33\nclass = \"plain\"\nformat = \"NONE\"\nparams = []\nenabled = true\n", + "type contract broken")) { + return false; + } + // opcode 重号 + if (!expect_load_err( + "mc_bad6.toml", + "[meta]\nname = \"S\"\nversion = 1\n" + "[[type]]\nname = \"BOOL\"\nbase = \"uint8\"\ntag = 0\n" + "[[type]]\nname = \"INT\"\nbase = \"int16\"\ntag = 1\n" + "[[type]]\nname = \"TIME\"\nbase = \"int64\"\ntag = 2\n" + "[[op]]\nname = \"MOVE\"\nopcode = 0\nclass = \"plain\"\nformat = \"RR\"\nparams = [\"rd\", \"rs\"]\nenabled = true\n" + "[[op]]\nname = \"NOPE\"\nopcode = 0\nclass = \"plain\"\nformat = \"RR\"\nparams = [\"a\", \"b\"]\nenabled = true\n", + "duplicate opcode")) { + return false; + } + // 类别-格式互锁破坏(instance + RRR) + if (!expect_load_err( + "mc_bad7.toml", + "[meta]\nname = \"S\"\nversion = 1\n" + "[[type]]\nname = \"BOOL\"\nbase = \"uint8\"\ntag = 0\n" + "[[type]]\nname = \"INT\"\nbase = \"int16\"\ntag = 1\n" + "[[type]]\nname = \"TIME\"\nbase = \"int64\"\ntag = 2\n" + "[[op]]\nname = \"BAD\"\nopcode = 24\nclass = \"instance\"\nformat = \"RRR\"\nparams = [\"a\", \"b\", \"c\"]\nenabled = true\n", + "class-format mismatch")) { + return false; + } + // params 数量与 format 不符 + if (!expect_load_err( + "mc_bad8.toml", + "[meta]\nname = \"S\"\nversion = 1\n" + "[[type]]\nname = \"BOOL\"\nbase = \"uint8\"\ntag = 0\n" + "[[type]]\nname = \"INT\"\nbase = \"int16\"\ntag = 1\n" + "[[type]]\nname = \"TIME\"\nbase = \"int64\"\ntag = 2\n" + "[[op]]\nname = \"MOVE\"\nopcode = 0\nclass = \"plain\"\nformat = \"RR\"\nparams = [\"rd\"]\nenabled = true\n", + "params count")) { + return false; + } + // fb.opcode 不匹配 instance op + if (!expect_load_err( + "mc_bad9.toml", + "[meta]\nname = \"S\"\nversion = 1\n" + "[[type]]\nname = \"BOOL\"\nbase = \"uint8\"\ntag = 0\n" + "[[type]]\nname = \"INT\"\nbase = \"int16\"\ntag = 1\n" + "[[type]]\nname = \"TIME\"\nbase = \"int64\"\ntag = 2\n" + "[[op]]\nname = \"RET\"\nopcode = 33\nclass = \"plain\"\nformat = \"NONE\"\nparams = []\nenabled = true\n" + "[[fb]]\nname = \"ton\"\nopcode = 33\nfields = [[\"in\", \"BOOL\"]]\n", + "must match an instance op")) { + return false; + } + // fb 字段类型未命中 type 表 + if (!expect_load_err( + "mc_bad10.toml", + "[meta]\nname = \"S\"\nversion = 1\n" + "[[type]]\nname = \"BOOL\"\nbase = \"uint8\"\ntag = 0\n" + "[[type]]\nname = \"INT\"\nbase = \"int16\"\ntag = 1\n" + "[[type]]\nname = \"TIME\"\nbase = \"int64\"\ntag = 2\n" + "[[op]]\nname = \"CAL_TON\"\nopcode = 24\nclass = \"instance\"\nformat = \"CAL\"\nparams = [\"instance\"]\nenabled = true\n" + "[[fb]]\nname = \"ton\"\nopcode = 24\nfields = [[\"in\", \"REAL\"]]\n", + "unknown field type")) { + return false; + } + // 语法错误 + if (!expect_load_err("mc_bad11.toml", "[meta\nname = \"S\"\n", "parse error")) { + return false; + } + return true; +} + +// ---- 3. 合法最小配置正例 ---- + +static bool test_positive_min() { + const std::string path = write_tmp("mc_good.toml", kGood); + compiler::MachineConfig cfg; + std::string err; + CHECK(cfg.load(path, &err)); + CHECK(cfg.ops().size() == 3); + CHECK(cfg.fbs().size() == 1); + CHECK(cfg.find_fb("ton")->fields.size() == 4); + std::remove(path.c_str()); + return true; +} + +int main() { + if (!test_positive()) return 1; + if (!test_negative()) return 1; + if (!test_positive_min()) return 1; + std::printf("machine_test: %d checks passed\n", g_checks); + return 0; +}