阶段 A 步骤 3:MachineConfig 加载层(toml++ + 强校验 5 项)。

- MachineConfig.h/cpp:解析 machine.toml → meta/types/ops/fbs 表
- 强校验:全属性必填、base 命中内建基元、range min≤max、tag 契约(BOOL=0/INT=1/TIME=2 唯一)、
  opcode 唯一 0..255、类别-格式互锁(instance↔CAL)、params 数量与 format 一致、
  fb.opcode 匹配 instance op、fb 字段类型命中 type 表
- 基元内置表(bit/int8..uint64/float32/64,宽度/符号/浮点)
- machine_test:38 断言(正例仓库表 + 9 类负例 + 最小配置),ctest 12/12
This commit is contained in:
2026-08-21 22:09:39 +08:00
parent 4dab914e7c
commit 44b5ecd46d
5 changed files with 762 additions and 1 deletions
+261
View File
@@ -0,0 +1,261 @@
/**
* @file machine_test.cpp
* @brief machine.toml 加载与强校验测试(阶段 A 步骤 3)
* @author
* @date 2026-08-21
*/
#include <cstdio>
#include <filesystem>
#include <string>
#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;
}