阶段 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:
@@ -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)
|
||||
|
||||
@@ -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 <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
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<std::string> 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<ConfigFbField> 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<ConfigType>& types() const { return types_; }
|
||||
const std::vector<ConfigOp>& ops() const { return ops_; }
|
||||
const std::vector<ConfigFb>& 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<PrimType>& prims();
|
||||
static const PrimType* find_prim(const std::string& name);
|
||||
|
||||
private:
|
||||
bool ok_ = false;
|
||||
std::string model_name_;
|
||||
uint32_t version_ = 0;
|
||||
std::vector<ConfigType> types_;
|
||||
std::vector<ConfigOp> ops_;
|
||||
std::vector<ConfigFb> fbs_;
|
||||
};
|
||||
}
|
||||
@@ -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 <toml++/toml.hpp>
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
namespace compiler {
|
||||
|
||||
const std::vector<PrimType>& MachineConfig::prims() {
|
||||
static const std::vector<PrimType> 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<int64_t>(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<bool>(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<uint32_t>(ver);
|
||||
|
||||
// ---- type ----
|
||||
{
|
||||
const toml::array* arr = root["type"].as_array();
|
||||
if (!arr || arr->empty()) {
|
||||
fail(err, "missing 'type' table");
|
||||
return false;
|
||||
}
|
||||
std::map<uint32_t, std::string> 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<uint32_t>(tag))) {
|
||||
fail(err, "type '" + ct.name + "': tag out of contract (0..2)");
|
||||
return false;
|
||||
}
|
||||
ct.tag = static_cast<uint32_t>(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<int64_t>(0);
|
||||
ct.range_max = (*r)[1].value_or<int64_t>(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<uint32_t, std::string> code_owner;
|
||||
std::map<std::string, uint32_t> 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<uint32_t>(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<int>(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<uint32_t>(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<std::string, bool> 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
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user