阶段2-2:MachineConfig 重写为 V2(强校验 5 项)+ TypeInfo/machine_test V2 适配
- ConfigType:base/kind/func(V1 的 tag/range 删除);ConfigOp:prefix/op/fmt/ func_mode(V1 的 opcode/class 删除);ConfigFb:fb_id(V1 的 opcode 删除) - 强校验:meta(version=2) / type 19 条全集·base 命中·kind 合法·func 与 (width,signed,float) 互锁 / op prefix 合法·prefix↔fmt 互锁·(prefix,op) 分区 唯一·func 类别·params 互锁 / fb 15 条全集·fb_id 唯一·字段命中 type 表 / op-fb 互锁(CAL↔fb 表) - TypeMeta:tag/range → func/kind - machine_test:V2 断言(正例 68 op/19 type/15 fb + 14 个负例) - 状态:compiler 库暂挂于 V1 Codec/Codegen/Stb/main(下一步按 Codec → Stb → Codegen → main 顺序 V2 化)
This commit is contained in:
@@ -1,13 +1,16 @@
|
||||
/**
|
||||
* @file MachineConfig.h
|
||||
* @brief 机器定义(machine.toml)加载与强校验
|
||||
* @brief 机器定义(machine.toml V2)加载与强校验
|
||||
* @author
|
||||
* @date 2026-08-21
|
||||
*
|
||||
* @details 见 Doc/isa/指令配置.md:
|
||||
* @details 见 Doc/isa/指令配置.md(V2 schema):
|
||||
* - 配置只给编译器(toml++ 运行时加载);vm 不读配置(内建全指令集)
|
||||
* - 强校验 5 项:字段必填 / type.base 命中内建基元·range 合法·tag 契约 /
|
||||
* op opcode 唯一·类别-格式互锁 / fb.opcode 与 op 一致·字段类型命中 type 表
|
||||
* - 强校验 5 项:meta(version=2)/ type 19 条全集·base 命中基元·kind 合法·
|
||||
* func 与 base (width,signed) 互锁 / op prefix 合法·prefix↔fmt 互锁·
|
||||
* op 分区内唯一·func 类别合法·params 数量与 fmt 互锁 /
|
||||
* fb 15 条全集(fb_id 0..14)·字段类型命中 type 表·字段名唯一 /
|
||||
* op-fb 互锁(CAL 存在 ↔ fb 表非空)
|
||||
* - 基元内置(bit/int8..uint64/float32/float64,元数据在代码)
|
||||
*/
|
||||
|
||||
@@ -31,30 +34,31 @@ namespace compiler {
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 配置类型 = 基元别名(+ 值域约束 + .stb 契约 tag)。
|
||||
* @details 对应 machine.toml [[type]] 行:base 必为内建基元名;range 可选且
|
||||
* min ≤ max;tag 与 .stb 常量表契约一致(0=BOOL、1=INT、2=TIME,唯一)。
|
||||
* @brief 配置类型 = 基元别名 + kind 语义 + func 映射(V2)。
|
||||
* @details 对应 machine.toml [[type]] 行:base 必为内建基元名;kind 为语言语义
|
||||
* (bool/bit/int/uint/float/time/date);func 为 type 位段值(0..9),
|
||||
* 与 base 的 (width, signed, is_float) 互锁强校验。
|
||||
*/
|
||||
struct ConfigType {
|
||||
std::string name; ///< 配置类型名(大写,如 BOOL/INT/TIME)
|
||||
std::string base; ///< 基元名(命中内建基元表)
|
||||
bool has_range = false; ///< 是否有值域约束
|
||||
int64_t range_min = 0; ///< 值域下界(含)
|
||||
int64_t range_max = 0; ///< 值域上界(含)
|
||||
uint32_t tag = 0; ///< .stb 常量表契约 tag
|
||||
std::string kind; ///< 语言语义:bool/bit/int/uint/float/time/date
|
||||
uint32_t func = 0; ///< type 位段值(0..9)
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 配置指令。
|
||||
* @details 对应 machine.toml [[op]] 行:opcode 唯一且 0..255;class 与 format
|
||||
* 互锁(INSTANCE ↔ CAL);params 数量与 format 期望一致。
|
||||
* @brief 配置指令(V2:prefix + op + fmt + func 类别)。
|
||||
* @details 对应 machine.toml [[op]] 行:prefix 为形态前缀(11/10/011/010000..
|
||||
* 010101/0010/0011/0001),op 为分区内编号(0 起);prefix 与 fmt 互锁;
|
||||
* func_mode 为数据语义类别(none/width/uint/full/float/dst)。
|
||||
*/
|
||||
struct ConfigOp {
|
||||
std::string name; ///< 助记符(与 .stb / isa 一致)
|
||||
uint32_t opcode = 0; ///< 操作码(0..255,表内唯一)
|
||||
bool is_instance = false; ///< class:instance / plain
|
||||
std::string format; ///< 操作数形态:RR/RRR/IMM/SLOT/JMP/JC/CALL/CAL/NONE
|
||||
std::vector<std::string> params; ///< 参数名(数量与 format 一致)
|
||||
std::string prefix; ///< 形态前缀(长度+形态分区)
|
||||
uint32_t op = 0; ///< 分区内编号(0 起)
|
||||
std::string fmt; ///< 操作数形态:RR/NONE/RRR/IMM/SLOT/JMP/JC/CALL/CAL/SLOT+off/IMM64/预留
|
||||
std::string func_mode; ///< func 类别:none/width/uint/full/float/dst
|
||||
std::vector<std::string> params; ///< 参数名(数量与 fmt 一致)
|
||||
bool enabled = false; ///< 是否启用
|
||||
};
|
||||
|
||||
@@ -63,34 +67,37 @@ namespace compiler {
|
||||
*/
|
||||
struct ConfigFbField {
|
||||
std::string name; ///< 字段名(如 in/pt/q/et/cu/cv)
|
||||
std::string type; ///< 配置类型名(BOOL/INT/TIME)
|
||||
std::string type; ///< 配置类型名(BOOL/INT/TIME/...)
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 内建功能块(FB)布局登记。
|
||||
* @details 对应 machine.toml [[fb]] 行;opcode 必须命中 instance 类 op 行。
|
||||
* @brief 内建功能块(FB)布局登记(V2:fb_id)。
|
||||
* @details 对应 machine.toml [[fb]] 行;fb_id 唯一(0..14,15 条全集);
|
||||
* CAL 指令(010101)的 fb_id 由本表提供。
|
||||
*/
|
||||
struct ConfigFb {
|
||||
std::string name; ///< FB 名(小写,如 ton/ctu)
|
||||
uint32_t opcode = 0; ///< 对应 op 行 opcode(CAL_*)
|
||||
std::string name; ///< FB 名(大写,如 TON/CTU)
|
||||
uint32_t fb_id = 0; ///< FB 类型 id(0..14)
|
||||
std::vector<ConfigFbField> fields; ///< 字段列表(名称唯一,类型命中 type 表)
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 机器定义:加载 machine.toml 并做强校验。
|
||||
* @brief 机器定义:加载 machine.toml 并做强校验(V2)。
|
||||
* @details 编译器指令集 / 类型的唯一事实来源;校验失败时 ok() 为 false,
|
||||
* err 前缀 "machine error"。vm 不读配置(内建全指令集)。
|
||||
*/
|
||||
class MachineConfig {
|
||||
public:
|
||||
/**
|
||||
* @brief 加载 + 强校验。
|
||||
* @brief 加载 + 强校验(V2)。
|
||||
* @param path machine.toml 路径
|
||||
* @param err 错误输出;可为 nullptr(静默)
|
||||
* @return true 成功;false(err 已写,前缀 "machine error")
|
||||
* @details 强校验项:字段必填 / type.base 命中内建基元·range 合法·tag 契约 /
|
||||
* op opcode 唯一·类别-格式互锁 / fb.opcode 与 op 一致·字段类型命中
|
||||
* type 表(清单见文件头)。
|
||||
* @details 强校验项:meta(name/version=2)/ type 19 条全集·base 命中基元·
|
||||
* kind 合法·func 与 (width,signed,float) 互锁 / op prefix 合法·
|
||||
* prefix↔fmt 互锁·(prefix,op) 分区内唯一·func 类别合法·
|
||||
* params 数量与 fmt 互锁 / fb 15 条全集·fb_id 唯一·字段类型命中
|
||||
* type 表·字段名唯一 / op-fb 互锁(CAL ↔ fb 表非空)。
|
||||
*/
|
||||
bool load(const std::string& path, std::string* err);
|
||||
|
||||
@@ -98,7 +105,7 @@ namespace compiler {
|
||||
bool ok() const { return ok_; }
|
||||
/// @brief 型号名(meta.name,参与 .stb 型号标识)。
|
||||
const std::string& model_name() const { return model_name_; }
|
||||
/// @brief 指令集版本(meta.version,参与 .stb 型号标识)。
|
||||
/// @brief 格式版本(meta.version,= 2,参与 .stb 型号标识)。
|
||||
uint32_t version() const { return version_; }
|
||||
|
||||
/// @brief 配置类型表([[type]])。
|
||||
@@ -110,7 +117,7 @@ namespace compiler {
|
||||
|
||||
/**
|
||||
* @brief 按配置类型名查类型行。
|
||||
* @param name 配置类型名
|
||||
* @param name 配置类型名(如 "INT")
|
||||
* @return 命中指针;未找到返回 nullptr
|
||||
*/
|
||||
const ConfigType* find_type(const std::string& name) const;
|
||||
@@ -123,26 +130,35 @@ namespace compiler {
|
||||
const ConfigOp* find_op(const std::string& name) const;
|
||||
|
||||
/**
|
||||
* @brief 按 opcode 查指令行。
|
||||
* @param opcode 操作码(0..255)
|
||||
* @brief 按 (prefix, op) 键查指令行。
|
||||
* @param prefix 形态前缀(如 "11"/"010001")
|
||||
* @param op 分区内编号
|
||||
* @return 命中指针;未找到返回 nullptr
|
||||
*/
|
||||
const ConfigOp* find_op_by_code(uint32_t opcode) const;
|
||||
const ConfigOp* find_op_by_key(const std::string& prefix, uint32_t op) const;
|
||||
|
||||
/**
|
||||
* @brief 查询某 opcode 是否启用。
|
||||
* @param opcode 操作码
|
||||
* @return 指令存在且 enabled 为 true;未知 opcode 返回 false
|
||||
* @brief 查询某 (prefix, op) 是否启用。
|
||||
* @param prefix 形态前缀
|
||||
* @param op 分区内编号
|
||||
* @return 指令存在且 enabled 为 true;未知返回 false
|
||||
*/
|
||||
bool op_enabled(uint32_t opcode) const;
|
||||
bool op_enabled(const std::string& prefix, uint32_t op) const;
|
||||
|
||||
/**
|
||||
* @brief 按名称查内建 FB。
|
||||
* @param name FB 名(小写,如 "ton")
|
||||
* @param name FB 名(如 "TON")
|
||||
* @return 命中指针;未找到返回 nullptr
|
||||
*/
|
||||
const ConfigFb* find_fb(const std::string& name) const;
|
||||
|
||||
/**
|
||||
* @brief 按 fb_id 查内建 FB。
|
||||
* @param fb_id FB 类型 id(0..14)
|
||||
* @return 命中指针;未找到返回 nullptr
|
||||
*/
|
||||
const ConfigFb* find_fb_by_id(uint32_t fb_id) const;
|
||||
|
||||
/// @brief 内建基元表(bit / int8..uint64 / float32 / float64)。
|
||||
static const std::vector<PrimType>& prims();
|
||||
|
||||
@@ -156,7 +172,7 @@ namespace compiler {
|
||||
private:
|
||||
bool ok_ = false; ///< 加载成功标志
|
||||
std::string model_name_; ///< 型号名(meta.name)
|
||||
uint32_t version_ = 0; ///< 指令集版本(meta.version)
|
||||
uint32_t version_ = 0; ///< 格式版本(meta.version)
|
||||
std::vector<ConfigType> types_; ///< 配置类型表
|
||||
std::vector<ConfigOp> ops_; ///< 配置指令表
|
||||
std::vector<ConfigFb> fbs_; ///< 内建 FB 表
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
* @date 2026-08-21
|
||||
*
|
||||
* @details 类型定义数据化(配置),运算/类型检查语义代码化:
|
||||
* - 元数据(宽度/符号/浮点/base/range/tag)全部来自 machine.toml + 内建基元表
|
||||
* - 元数据(宽度/符号/浮点/kind/func)全部来自 machine.toml + 内建基元表
|
||||
* - 查询大小写不敏感(配置名大写,Linker type_name 小写)
|
||||
*/
|
||||
|
||||
@@ -19,12 +19,12 @@
|
||||
namespace compiler {
|
||||
|
||||
/**
|
||||
* @brief 语言类型元数据。
|
||||
* @brief 语言类型元数据(V2)。
|
||||
* @details 由配置类型行 + 内建基元合成;config 或 prim 未命中时
|
||||
* 对应访问器取安全默认值,operator bool 为 false。
|
||||
*/
|
||||
struct TypeMeta {
|
||||
const ConfigType* config = nullptr; ///< 配置类型行(别名 + range + tag)
|
||||
const ConfigType* config = nullptr; ///< 配置类型行(别名 + kind + func)
|
||||
const PrimType* prim = nullptr; ///< 内建基元(宽度/符号/浮点)
|
||||
|
||||
/// @brief 宽度(字节);无基元返回 0。
|
||||
@@ -33,18 +33,12 @@ namespace compiler {
|
||||
bool is_signed() const { return prim ? prim->is_signed : false; }
|
||||
/// @brief 是否浮点;无基元返回 false。
|
||||
bool is_float() const { return prim ? prim->is_float : false; }
|
||||
/// @brief .stb 契约 tag;无配置返回 0。
|
||||
uint32_t tag() const { return config ? config->tag : 0; }
|
||||
/// @brief 是否有值域约束;无配置返回 false。
|
||||
bool has_range() const { return config ? config->has_range : false; }
|
||||
/**
|
||||
* @brief 值是否落在配置值域内。
|
||||
* @param v 待查值
|
||||
* @return 无配置或无 range 时为 true;否则 min ≤ v ≤ max
|
||||
*/
|
||||
bool value_in_range(int64_t v) const {
|
||||
return !config || !config->has_range ||
|
||||
(v >= config->range_min && v <= config->range_max);
|
||||
/// @brief type 位段值 func(0..9);无配置返回 0。
|
||||
uint32_t func() const { return config ? config->func : 0; }
|
||||
/// @brief 语言语义 kind(bool/bit/int/uint/float/time/date);无配置返回空串。
|
||||
const std::string& kind() const {
|
||||
static const std::string kEmpty;
|
||||
return config ? config->kind : kEmpty;
|
||||
}
|
||||
|
||||
/// @brief 是否有效(config 与 prim 都已命中)。
|
||||
|
||||
+255
-121
@@ -1,16 +1,17 @@
|
||||
/**
|
||||
* @file MachineConfig.cpp
|
||||
* @brief 机器定义(machine.toml)加载与强校验
|
||||
* @brief 机器定义(machine.toml V2)加载与强校验
|
||||
* @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 类)一致;字段类型命中配置类型表;字段名唯一
|
||||
* @details 校验项(见 Doc/isa/指令配置.md 强校验清单 1~5,V2):
|
||||
* 1. meta:name 非空、version = 2
|
||||
* 2. type:19 条必填全集(BOOL..DT);base 命中内建基元;kind 合法;
|
||||
* func ∈ 0..9 且与 base 的 (width, signed, is_float) 互锁
|
||||
* 3. op:prefix 合法;prefix ↔ fmt 互锁;op 分区内唯一;func 类别合法;
|
||||
* params 数量与 fmt 互锁;enabled 布尔
|
||||
* 4. fb:15 条必填全集(fb_id 0..14);fb_id 唯一;字段名唯一;字段类型命中 type 表
|
||||
* 5. op-fb 互锁:CAL(010101)存在 ↔ fb 表非空
|
||||
*/
|
||||
|
||||
#include "compiler/MachineConfig.h"
|
||||
@@ -73,31 +74,113 @@ namespace {
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief format → 期望参数数量(解析时校验)。
|
||||
* @param format 操作数形态(RR/RRR/IMM/SLOT/JMP/JC/CALL/CAL/NONE)
|
||||
* @return 参数数量;未知 format 返回 -1
|
||||
* @brief fmt → 期望参数数量(解析时校验)。
|
||||
* @param fmt 操作数形态(RR/NONE/RRR/IMM/SLOT/JMP/JC/CALL/CAL/SLOT+off/IMM64/预留)
|
||||
* @return 参数数量;未知 fmt 返回 -1
|
||||
*/
|
||||
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;
|
||||
int params_of(const std::string& fmt) {
|
||||
if (fmt == "RR") return 2;
|
||||
if (fmt == "NONE") return 0;
|
||||
if (fmt == "RRR") return 3;
|
||||
if (fmt == "IMM") return 2;
|
||||
if (fmt == "SLOT") return 2;
|
||||
if (fmt == "JMP") return 1;
|
||||
if (fmt == "JC") return 2;
|
||||
if (fmt == "CALL") return 1;
|
||||
if (fmt == "CAL") return 2;
|
||||
if (fmt == "SLOT+off") return 3;
|
||||
if (fmt == "IMM64") return 2;
|
||||
if (fmt == "预留") return 0;
|
||||
return -1;
|
||||
}
|
||||
|
||||
/// @brief format 是否已知(params_of 返回非负)。
|
||||
/// @brief fmt 是否已知(params_of 返回非负)。
|
||||
bool is_known_format(const std::string& f) {
|
||||
return params_of(f) >= 0;
|
||||
}
|
||||
|
||||
/// @brief tag 是否在 .stb 常量表契约内(0=BOOL、1=INT、2=TIME)。
|
||||
bool is_known_tag(uint32_t tag) {
|
||||
return tag <= 2; // .stb 常量表契约:0=BOOL 1=INT 2=TIME
|
||||
/**
|
||||
* @brief prefix 是否合法(V2 形态前缀集合)。
|
||||
* @param prefix 前缀字符串
|
||||
* @return 是否在 11/10/011/010000..010101/0010/0011/0001 内
|
||||
*/
|
||||
bool is_known_prefix(const std::string& prefix) {
|
||||
static const char* kPrefixes[] = {
|
||||
"11", "10", "011", "010000", "010001", "010010", "010011",
|
||||
"010100", "010101", "0010", "0011", "0001",
|
||||
};
|
||||
for (const char* p : kPrefixes) {
|
||||
if (prefix == p) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief prefix ↔ fmt 互锁表查询。
|
||||
* @param prefix 形态前缀
|
||||
* @return 期望 fmt;未知 prefix 返回空串
|
||||
*/
|
||||
const char* fmt_of_prefix(const std::string& prefix) {
|
||||
if (prefix == "11") return "RR";
|
||||
if (prefix == "10") return "NONE";
|
||||
if (prefix == "011") return "RRR";
|
||||
if (prefix == "010000") return "IMM";
|
||||
if (prefix == "010001") return "SLOT";
|
||||
if (prefix == "010010") return "JMP";
|
||||
if (prefix == "010011") return "JC";
|
||||
if (prefix == "010100") return "CALL";
|
||||
if (prefix == "010101") return "CAL";
|
||||
if (prefix == "0010") return "SLOT+off";
|
||||
if (prefix == "0011") return "IMM64";
|
||||
if (prefix == "0001") return "预留";
|
||||
return "";
|
||||
}
|
||||
|
||||
/// @brief func 类别是否合法(V2:none/width/uint/full/float/dst)。
|
||||
bool is_known_func_mode(const std::string& m) {
|
||||
return m == "none" || m == "width" || m == "uint" || m == "full" ||
|
||||
m == "float" || m == "dst";
|
||||
}
|
||||
|
||||
/// @brief kind 是否合法(V2:bool/bit/int/uint/float/time/date)。
|
||||
bool is_known_kind(const std::string& k) {
|
||||
return k == "bool" || k == "bit" || k == "int" || k == "uint" ||
|
||||
k == "float" || k == "time" || k == "date";
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief func 与基元的 (width, signed, is_float) 互锁校验。
|
||||
* @param type_name 类型名(错误信息用)
|
||||
* @param prim 基元
|
||||
* @param func func 值
|
||||
* @param err 错误输出
|
||||
* @return true 匹配;false(err 已写)
|
||||
*/
|
||||
bool func_matches_prim(const std::string& type_name, const PrimType& prim,
|
||||
uint32_t func, std::string* err) {
|
||||
if (prim.is_float) {
|
||||
if (func == (prim.width == 4 ? 8 : 9)) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
uint32_t want = 0;
|
||||
if (prim.width == 1 && !prim.is_signed) want = 0;
|
||||
else if (prim.width == 1 && prim.is_signed) want = 1;
|
||||
else if (prim.width == 2 && !prim.is_signed) want = 2;
|
||||
else if (prim.width == 2 && prim.is_signed) want = 3;
|
||||
else if (prim.width == 4 && !prim.is_signed) want = 4;
|
||||
else if (prim.width == 4 && prim.is_signed) want = 5;
|
||||
else if (prim.width == 8 && !prim.is_signed) want = 6;
|
||||
else if (prim.width == 8 && prim.is_signed) want = 7;
|
||||
if (func == want) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
fail(err, "type '" + type_name + "': func " + std::to_string(func) +
|
||||
" mismatches base '" + prim.name + "' (width/sign)");
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -163,17 +246,17 @@ namespace {
|
||||
} // namespace
|
||||
|
||||
/**
|
||||
* @brief 加载 machine.toml 并做强校验。
|
||||
* @brief 加载 machine.toml(V2)并做强校验。
|
||||
* @param path machine.toml 路径
|
||||
* @param err 错误输出;可为 nullptr(静默)
|
||||
* @return true 成功;false(err 已写,前缀 "machine error")
|
||||
* @details 校验顺序与文件头清单 1~5 一致:
|
||||
* 1. TOML 语法与字段完整性(meta/type/op/fb 全属性必填)
|
||||
* 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 类)一致;字段类型命中配置类型表;字段名唯一
|
||||
* @details 校验顺序与文件头清单 1~5 一致(V2):
|
||||
* 1. meta:name 非空、version = 2
|
||||
* 2. type:19 条必填全集;base 命中基元;kind 合法;func 与 base 互锁
|
||||
* 3. op:prefix 合法;prefix↔fmt 互锁;(prefix,op) 分区内唯一;
|
||||
* func 类别合法;params 数量与 fmt 互锁
|
||||
* 4. fb:15 条必填全集(fb_id 0..14);fb_id 唯一;字段类型命中 type 表
|
||||
* 5. op-fb 互锁:CAL(010101)存在 ↔ fb 表非空
|
||||
*/
|
||||
bool MachineConfig::load(const std::string& path, std::string* err) {
|
||||
ok_ = false;
|
||||
@@ -191,29 +274,38 @@ bool MachineConfig::load(const std::string& path, std::string* err) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// ---- meta ----
|
||||
// ---- 1. 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;
|
||||
if (model_name_.empty()) {
|
||||
fail(err, "empty name in [meta]");
|
||||
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]");
|
||||
if (ver != 2) {
|
||||
fail(err, "version must be 2 (V2, STATOR2)");
|
||||
return false;
|
||||
}
|
||||
version_ = static_cast<uint32_t>(ver);
|
||||
|
||||
// ---- type ----
|
||||
// ---- 2. type(19 条全集)----
|
||||
{
|
||||
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 → 类型名
|
||||
const char* kAllTypes[19] = {
|
||||
"BOOL", "BYTE", "WORD", "DWORD", "LWORD", "SINT", "INT", "DINT",
|
||||
"LINT", "USINT", "UINT", "UDINT", "ULINT", "TIME", "REAL",
|
||||
"LREAL", "DATE", "TOD", "DT",
|
||||
};
|
||||
std::map<std::string, bool> seen;
|
||||
for (const auto& el : *arr) {
|
||||
const toml::table* t = el.as_table();
|
||||
if (!t) {
|
||||
@@ -223,59 +315,56 @@ bool MachineConfig::load(const std::string& path, std::string* err) {
|
||||
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) {
|
||||
if (!req_string(*t, "type", "kind", &ct.kind, err)) return false;
|
||||
int64_t func = 0;
|
||||
if (!req_int(*t, "type", "func", &func, err)) return false;
|
||||
if (func < 0 || func > 9) {
|
||||
fail(err, "type '" + ct.name + "': func out of range (0..9)");
|
||||
return false;
|
||||
}
|
||||
ct.func = static_cast<uint32_t>(func);
|
||||
if (seen.count(ct.name)) {
|
||||
fail(err, "duplicate type '" + ct.name + "'");
|
||||
return false;
|
||||
}
|
||||
seen[ct.name] = true;
|
||||
if (!is_known_kind(ct.kind)) {
|
||||
fail(err, "type '" + ct.name + "': bad kind '" + ct.kind + "'");
|
||||
return false;
|
||||
}
|
||||
const PrimType* prim = find_prim(ct.base);
|
||||
if (prim == 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)");
|
||||
// kind 与基元语义互锁:float ↔ is_float
|
||||
const bool kind_float = ct.kind == "float";
|
||||
if (kind_float != prim->is_float) {
|
||||
fail(err, "type '" + ct.name + "': kind '" + ct.kind +
|
||||
"' mismatches base '" + ct.base + "'");
|
||||
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;
|
||||
}
|
||||
}
|
||||
if (!func_matches_prim(ct.name, *prim, ct.func, err)) 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]);
|
||||
// 19 条全集必填
|
||||
for (const char* need : kAllTypes) {
|
||||
if (!seen.count(need)) {
|
||||
fail(err, std::string("type contract broken: missing '") + need + "'");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---- op ----
|
||||
// ---- 3. 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;
|
||||
std::map<std::string, bool> key_owner; // "prefix/op" → 已存在
|
||||
std::map<std::string, bool> name_owner; // 助记符 → 已存在
|
||||
for (const auto& el : *arr) {
|
||||
const toml::table* t = el.as_table();
|
||||
if (!t) {
|
||||
@@ -284,51 +373,56 @@ bool MachineConfig::load(const std::string& path, std::string* err) {
|
||||
}
|
||||
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");
|
||||
if (!req_string(*t, "op", "prefix", &op.prefix, err)) return false;
|
||||
if (!req_string(*t, "op", "fmt", &op.fmt, err)) return false;
|
||||
if (!req_string(*t, "op", "func", &op.func_mode, err)) return false;
|
||||
int64_t opno = 0;
|
||||
if (!req_int(*t, "op", "op", &opno, err)) return false;
|
||||
if (opno < 0 || opno > 63) {
|
||||
fail(err, "op '" + op.name + "': op out of range (0..63)");
|
||||
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;
|
||||
op.op = static_cast<uint32_t>(opno);
|
||||
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 + "'");
|
||||
name_owner[op.name] = true;
|
||||
if (!is_known_prefix(op.prefix)) {
|
||||
fail(err, "op '" + op.name + "': bad prefix '" + op.prefix + "'");
|
||||
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 + "'");
|
||||
// prefix ↔ fmt 互锁
|
||||
const char* want_fmt = fmt_of_prefix(op.prefix);
|
||||
if (want_fmt == nullptr || op.fmt != want_fmt) {
|
||||
fail(err, "op '" + op.name + "': prefix '" + op.prefix +
|
||||
"' must pair with fmt '" + std::string(want_fmt ? want_fmt : "?") + "'");
|
||||
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)");
|
||||
if (!is_known_format(op.fmt)) {
|
||||
fail(err, "op '" + op.name + "': bad fmt '" + op.fmt + "'");
|
||||
return false;
|
||||
}
|
||||
// (prefix, op) 分区内唯一
|
||||
const std::string key = op.prefix + "/" + std::to_string(op.op);
|
||||
if (key_owner.count(key)) {
|
||||
fail(err, "duplicate (prefix, op) '" + key + "'");
|
||||
return false;
|
||||
}
|
||||
key_owner[key] = true;
|
||||
// func 类别合法
|
||||
if (!is_known_func_mode(op.func_mode)) {
|
||||
fail(err, "op '" + op.name + "': bad func mode '" + op.func_mode + "'");
|
||||
return false;
|
||||
}
|
||||
if (!req_bool(*t, "op", "enabled", &op.enabled, err)) return false;
|
||||
// params 数量与 fmt 互锁
|
||||
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);
|
||||
const int want = params_of(op.fmt);
|
||||
for (const auto& p : *pv.as_array()) {
|
||||
if (!p.is_string()) {
|
||||
fail(err, "op '" + op.name + "': params must be strings");
|
||||
@@ -338,7 +432,7 @@ bool MachineConfig::load(const std::string& path, std::string* err) {
|
||||
}
|
||||
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(op.params.size()) + " != fmt expects " +
|
||||
std::to_string(want));
|
||||
return false;
|
||||
}
|
||||
@@ -346,13 +440,15 @@ bool MachineConfig::load(const std::string& path, std::string* err) {
|
||||
}
|
||||
}
|
||||
|
||||
// ---- fb ----
|
||||
// ---- 4. fb(15 条全集 fb_id 0..14)----
|
||||
{
|
||||
const toml::array* arr = root["fb"].as_array();
|
||||
if (!arr || arr->empty()) {
|
||||
fail(err, "missing 'fb' table");
|
||||
return false;
|
||||
}
|
||||
std::map<uint32_t, bool> id_owner;
|
||||
std::map<std::string, bool> name_owner;
|
||||
for (const auto& el : *arr) {
|
||||
const toml::table* t = el.as_table();
|
||||
if (!t) {
|
||||
@@ -361,16 +457,23 @@ bool MachineConfig::load(const std::string& path, std::string* err) {
|
||||
}
|
||||
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");
|
||||
int64_t fid = 0;
|
||||
if (!req_int(*t, "fb", "fb_id", &fid, err)) return false;
|
||||
if (fid < 0 || fid > 14) {
|
||||
fail(err, "fb '" + fb.name + "': fb_id out of range (0..14)");
|
||||
return false;
|
||||
}
|
||||
fb.fb_id = static_cast<uint32_t>(fid);
|
||||
if (id_owner.count(fb.fb_id)) {
|
||||
fail(err, "duplicate fb_id " + std::to_string(fb.fb_id));
|
||||
return false;
|
||||
}
|
||||
id_owner[fb.fb_id] = true;
|
||||
if (name_owner.count(fb.name)) {
|
||||
fail(err, "duplicate fb name '" + fb.name + "'");
|
||||
return false;
|
||||
}
|
||||
name_owner[fb.name] = true;
|
||||
const auto fv = (*t)["fields"];
|
||||
if (!fv || !fv.is_array()) {
|
||||
fail(err, "fb '" + fb.name + "': missing 'fields'");
|
||||
@@ -403,6 +506,20 @@ bool MachineConfig::load(const std::string& path, std::string* err) {
|
||||
}
|
||||
fbs_.push_back(fb);
|
||||
}
|
||||
// 15 条全集必填(fb_id 0..14)
|
||||
for (uint32_t i = 0; i < 15; ++i) {
|
||||
if (!id_owner.count(i)) {
|
||||
fail(err, "fb contract broken: missing fb_id " + std::to_string(i));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---- 5. op-fb 互锁:CAL(010101)存在 ↔ fb 表非空 ----
|
||||
const ConfigOp* cal = find_op_by_key("010101", 0);
|
||||
if (cal == nullptr || !cal->enabled) {
|
||||
fail(err, "op-fb interlock: CAL (010101) must exist and be enabled");
|
||||
return false;
|
||||
}
|
||||
|
||||
ok_ = true;
|
||||
@@ -438,13 +555,15 @@ const ConfigOp* MachineConfig::find_op(const std::string& name) const {
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 按 opcode 查指令行。
|
||||
* @param opcode 操作码(0..255)
|
||||
* @brief 按 (prefix, op) 键查指令行。
|
||||
* @param prefix 形态前缀(如 "11"/"010001")
|
||||
* @param op 分区内编号
|
||||
* @return 命中指针;未找到返回 nullptr
|
||||
*/
|
||||
const ConfigOp* MachineConfig::find_op_by_code(uint32_t opcode) const {
|
||||
const ConfigOp* MachineConfig::find_op_by_key(const std::string& prefix,
|
||||
uint32_t op) const {
|
||||
for (const ConfigOp& o : ops_) {
|
||||
if (o.opcode == opcode) {
|
||||
if (o.prefix == prefix && o.op == op) {
|
||||
return &o;
|
||||
}
|
||||
}
|
||||
@@ -452,18 +571,19 @@ const ConfigOp* MachineConfig::find_op_by_code(uint32_t opcode) const {
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 查询某 opcode 是否启用。
|
||||
* @param opcode 操作码
|
||||
* @return 指令存在且 enabled 为 true;未知 opcode 返回 false
|
||||
* @brief 查询某 (prefix, op) 是否启用。
|
||||
* @param prefix 形态前缀
|
||||
* @param op 分区内编号
|
||||
* @return 指令存在且 enabled 为 true;未知返回 false
|
||||
*/
|
||||
bool MachineConfig::op_enabled(uint32_t opcode) const {
|
||||
const ConfigOp* op = find_op_by_code(opcode);
|
||||
return op != nullptr && op->enabled;
|
||||
bool MachineConfig::op_enabled(const std::string& prefix, uint32_t op) const {
|
||||
const ConfigOp* o = find_op_by_key(prefix, op);
|
||||
return o != nullptr && o->enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 按名称查内建 FB。
|
||||
* @param name FB 名(小写,如 "ton")
|
||||
* @param name FB 名(如 "TON")
|
||||
* @return 命中指针;未找到返回 nullptr
|
||||
*/
|
||||
const ConfigFb* MachineConfig::find_fb(const std::string& name) const {
|
||||
@@ -475,4 +595,18 @@ const ConfigFb* MachineConfig::find_fb(const std::string& name) const {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 按 fb_id 查内建 FB。
|
||||
* @param fb_id FB 类型 id(0..14)
|
||||
* @return 命中指针;未找到返回 nullptr
|
||||
*/
|
||||
const ConfigFb* MachineConfig::find_fb_by_id(uint32_t fb_id) const {
|
||||
for (const ConfigFb& f : fbs_) {
|
||||
if (f.fb_id == fb_id) {
|
||||
return &f;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
} // namespace compiler
|
||||
|
||||
+212
-155
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* @file machine_test.cpp
|
||||
* @brief machine.toml 加载与强校验测试(阶段 A 步骤 3)
|
||||
* @brief machine.toml V2 加载与强校验测试(阶段 2)
|
||||
* @author
|
||||
* @date 2026-08-21
|
||||
*/
|
||||
@@ -64,16 +64,51 @@ static bool expect_load_err(const char* name, const char* content, const char* k
|
||||
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";
|
||||
// 合法 type 模板(19 条全集,可注入损坏;缺 DT 时用于"缺全集"负例)
|
||||
static const char* kTypesAll =
|
||||
"[[type]]\nname = \"BOOL\"\nbase = \"uint8\"\nkind = \"bool\"\nfunc = 0\n"
|
||||
"[[type]]\nname = \"BYTE\"\nbase = \"uint8\"\nkind = \"bit\"\nfunc = 0\n"
|
||||
"[[type]]\nname = \"WORD\"\nbase = \"uint16\"\nkind = \"bit\"\nfunc = 2\n"
|
||||
"[[type]]\nname = \"DWORD\"\nbase = \"uint32\"\nkind = \"bit\"\nfunc = 4\n"
|
||||
"[[type]]\nname = \"LWORD\"\nbase = \"uint64\"\nkind = \"bit\"\nfunc = 6\n"
|
||||
"[[type]]\nname = \"SINT\"\nbase = \"int8\"\nkind = \"int\"\nfunc = 1\n"
|
||||
"[[type]]\nname = \"INT\"\nbase = \"int16\"\nkind = \"int\"\nfunc = 3\n"
|
||||
"[[type]]\nname = \"DINT\"\nbase = \"int32\"\nkind = \"int\"\nfunc = 5\n"
|
||||
"[[type]]\nname = \"LINT\"\nbase = \"int64\"\nkind = \"int\"\nfunc = 7\n"
|
||||
"[[type]]\nname = \"USINT\"\nbase = \"uint8\"\nkind = \"uint\"\nfunc = 0\n"
|
||||
"[[type]]\nname = \"UINT\"\nbase = \"uint16\"\nkind = \"uint\"\nfunc = 2\n"
|
||||
"[[type]]\nname = \"UDINT\"\nbase = \"uint32\"\nkind = \"uint\"\nfunc = 4\n"
|
||||
"[[type]]\nname = \"ULINT\"\nbase = \"uint64\"\nkind = \"uint\"\nfunc = 6\n"
|
||||
"[[type]]\nname = \"TIME\"\nbase = \"int64\"\nkind = \"time\"\nfunc = 7\n"
|
||||
"[[type]]\nname = \"REAL\"\nbase = \"float32\"\nkind = \"float\"\nfunc = 8\n"
|
||||
"[[type]]\nname = \"LREAL\"\nbase = \"float64\"\nkind = \"float\"\nfunc = 9\n"
|
||||
"[[type]]\nname = \"DATE\"\nbase = \"uint32\"\nkind = \"date\"\nfunc = 4\n"
|
||||
"[[type]]\nname = \"TOD\"\nbase = \"uint32\"\nkind = \"date\"\nfunc = 4\n"
|
||||
"[[type]]\nname = \"DT\"\nbase = \"uint64\"\nkind = \"date\"\nfunc = 6\n";
|
||||
|
||||
// 合法 op 模板(含 CAL 与 RET,可注入损坏)
|
||||
static const char* kOpsBasic =
|
||||
"[[op]]\nname = \"MOVE\"\nprefix = \"11\"\nop = 0\nfmt = \"RR\"\nfunc = \"width\"\nparams = [\"rd\", \"rs\"]\nenabled = true\n"
|
||||
"[[op]]\nname = \"CAL\"\nprefix = \"010101\"\nop = 0\nfmt = \"CAL\"\nfunc = \"none\"\nparams = [\"fb_id\", \"slot\"]\nenabled = true\n"
|
||||
"[[op]]\nname = \"RET\"\nprefix = \"10\"\nop = 1\nfmt = \"NONE\"\nfunc = \"none\"\nparams = []\nenabled = true\n";
|
||||
|
||||
// 合法 fb 模板(15 条全集,可注入损坏)
|
||||
static const char* kFbsAll =
|
||||
"[[fb]]\nname = \"TON\"\nfb_id = 0\nfields = [[\"in\", \"BOOL\"], [\"pt\", \"TIME\"], [\"q\", \"BOOL\"], [\"et\", \"TIME\"]]\n"
|
||||
"[[fb]]\nname = \"TOF\"\nfb_id = 1\nfields = [[\"in\", \"BOOL\"], [\"pt\", \"TIME\"], [\"q\", \"BOOL\"], [\"et\", \"TIME\"]]\n"
|
||||
"[[fb]]\nname = \"TP\"\nfb_id = 2\nfields = [[\"in\", \"BOOL\"], [\"pt\", \"TIME\"], [\"q\", \"BOOL\"], [\"et\", \"TIME\"]]\n"
|
||||
"[[fb]]\nname = \"CTU\"\nfb_id = 3\nfields = [[\"cu\", \"BOOL\"], [\"r\", \"BOOL\"], [\"pv\", \"INT\"], [\"q\", \"BOOL\"], [\"cv\", \"INT\"]]\n"
|
||||
"[[fb]]\nname = \"DCTU\"\nfb_id = 4\nfields = [[\"cu\", \"BOOL\"], [\"r\", \"BOOL\"], [\"pv\", \"DINT\"], [\"q\", \"BOOL\"], [\"cv\", \"DINT\"]]\n"
|
||||
"[[fb]]\nname = \"CTD\"\nfb_id = 5\nfields = [[\"cd\", \"BOOL\"], [\"ld\", \"BOOL\"], [\"pv\", \"INT\"], [\"q\", \"BOOL\"], [\"cv\", \"INT\"]]\n"
|
||||
"[[fb]]\nname = \"DCTD\"\nfb_id = 6\nfields = [[\"cd\", \"BOOL\"], [\"ld\", \"BOOL\"], [\"pv\", \"DINT\"], [\"q\", \"BOOL\"], [\"cv\", \"DINT\"]]\n"
|
||||
"[[fb]]\nname = \"CTUD\"\nfb_id = 7\nfields = [[\"cu\", \"BOOL\"], [\"cd\", \"BOOL\"], [\"r\", \"BOOL\"], [\"lu\", \"BOOL\"], [\"pv\", \"INT\"], [\"qu\", \"BOOL\"], [\"qd\", \"BOOL\"], [\"cv\", \"INT\"]]\n"
|
||||
"[[fb]]\nname = \"DCTUD\"\nfb_id = 8\nfields = [[\"cu\", \"BOOL\"], [\"cd\", \"BOOL\"], [\"r\", \"BOOL\"], [\"lu\", \"BOOL\"], [\"pv\", \"DINT\"], [\"qu\", \"BOOL\"], [\"qd\", \"BOOL\"], [\"cv\", \"DINT\"]]\n"
|
||||
"[[fb]]\nname = \"R_TRIG\"\nfb_id = 9\nfields = [[\"clk\", \"BOOL\"], [\"q\", \"BOOL\"]]\n"
|
||||
"[[fb]]\nname = \"F_TRIG\"\nfb_id = 10\nfields = [[\"clk\", \"BOOL\"], [\"q\", \"BOOL\"]]\n"
|
||||
"[[fb]]\nname = \"SR\"\nfb_id = 11\nfields = [[\"S1\", \"BOOL\"], [\"R\", \"BOOL\"], [\"Q1\", \"BOOL\"]]\n"
|
||||
"[[fb]]\nname = \"RS\"\nfb_id = 12\nfields = [[\"SET\", \"BOOL\"], [\"RESET1\", \"BOOL\"], [\"Q1\", \"BOOL\"]]\n"
|
||||
"[[fb]]\nname = \"PWM\"\nfb_id = 13\nfields = [[\"in\", \"BOOL\"], [\"pt\", \"TIME\"], [\"duty\", \"REAL\"], [\"q\", \"BOOL\"], [\"et\", \"TIME\"]]\n"
|
||||
"[[fb]]\nname = \"RTC\"\nfb_id = 14\nfields = [[\"enable\", \"BOOL\"], [\"date\", \"DATE\"], [\"tod\", \"TOD\"]]\n";
|
||||
|
||||
// ---- 1. 正例:仓库 machine.toml ----
|
||||
|
||||
@@ -83,31 +118,45 @@ static bool test_positive() {
|
||||
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.version() == 2);
|
||||
|
||||
CHECK(cfg.types().size() == 3);
|
||||
CHECK(cfg.types().size() == 19);
|
||||
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(bt != nullptr && bt->base == "uint8" && bt->kind == "bool" && bt->func == 0);
|
||||
CHECK(cfg.find_type("INT") != nullptr && cfg.find_type("INT")->base == "int16" &&
|
||||
cfg.find_type("INT")->kind == "int" && cfg.find_type("INT")->func == 3);
|
||||
CHECK(cfg.find_type("REAL") != nullptr && cfg.find_type("REAL")->func == 8);
|
||||
CHECK(cfg.find_type("LREAL") != nullptr && cfg.find_type("LREAL")->func == 9);
|
||||
CHECK(cfg.find_type("DATE") != nullptr && cfg.find_type("DATE")->func == 4);
|
||||
CHECK(cfg.find_type("TOD") != nullptr && cfg.find_type("TOD")->func == 4);
|
||||
CHECK(cfg.find_type("DT") != nullptr && cfg.find_type("DT")->func == 6);
|
||||
CHECK(cfg.find_type("NOPE") == 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.ops().size() == 68);
|
||||
const compiler::ConfigOp* move = cfg.find_op("MOVE");
|
||||
CHECK(move != nullptr && move->prefix == "11" && move->op == 0 && move->fmt == "RR" &&
|
||||
move->func_mode == "width" && move->params.size() == 2);
|
||||
const compiler::ConfigOp* load = cfg.find_op("LOAD");
|
||||
CHECK(load != nullptr && load->prefix == "010001" && load->fmt == "SLOT" &&
|
||||
load->func_mode == "full");
|
||||
const compiler::ConfigOp* cal = cfg.find_op("CAL");
|
||||
CHECK(cal != nullptr && cal->prefix == "010101" && cal->fmt == "CAL" &&
|
||||
cal->func_mode == "none" && cal->params.size() == 2);
|
||||
CHECK(cfg.find_op_by_key("11", 0) != nullptr &&
|
||||
cfg.find_op_by_key("11", 0)->name == "MOVE");
|
||||
CHECK(cfg.find_op_by_key("010001", 1) != nullptr &&
|
||||
cfg.find_op_by_key("010001", 1)->name == "STORE");
|
||||
CHECK(cfg.op_enabled("11", 0) && cfg.op_enabled("010101", 0));
|
||||
CHECK(!cfg.op_enabled("11", 63));
|
||||
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(cfg.fbs().size() == 15);
|
||||
const compiler::ConfigFb* ton = cfg.find_fb("TON");
|
||||
CHECK(ton != nullptr && ton->fb_id == 0 && ton->fields.size() == 4);
|
||||
CHECK(cfg.find_fb("DCTU") != nullptr && cfg.find_fb("DCTU")->fb_id == 4 &&
|
||||
cfg.find_fb("DCTU")->fields[2].type == "DINT");
|
||||
CHECK(cfg.find_fb_by_id(14) != nullptr && cfg.find_fb_by_id(14)->name == "RTC");
|
||||
CHECK(cfg.find_fb_by_id(15) == nullptr);
|
||||
|
||||
// 基元表
|
||||
CHECK(compiler::MachineConfig::find_prim("int16") != nullptr);
|
||||
@@ -118,178 +167,187 @@ static bool test_positive() {
|
||||
return true;
|
||||
}
|
||||
|
||||
// ---- 2. 负例:9 类 ----
|
||||
// ---- 2. 负例:V2 校验项 ----
|
||||
|
||||
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'")) {
|
||||
const std::string m = "[meta]\nname = \"STATOR\"\nversion = 2\n";
|
||||
// version != 2
|
||||
if (!expect_load_err("mc_v2_bad1.toml",
|
||||
"[meta]\nname = \"STATOR\"\nversion = 1\n" + std::string(kTypesAll) +
|
||||
kOpsBasic + kFbsAll,
|
||||
"version must be 2")) {
|
||||
return false;
|
||||
}
|
||||
// type 缺全集(去掉 DT)
|
||||
{
|
||||
std::string types = kTypesAll;
|
||||
const std::string dt = "[[type]]\nname = \"DT\"\nbase = \"uint64\"\nkind = \"date\"\nfunc = 6\n";
|
||||
const size_t pos = types.find(dt);
|
||||
types.erase(pos, dt.size());
|
||||
if (!expect_load_err("mc_v2_bad2.toml", m + types + kOpsBasic + kFbsAll,
|
||||
"missing 'DT'")) {
|
||||
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",
|
||||
{
|
||||
std::string types = kTypesAll;
|
||||
const std::string old = "[[type]]\nname = \"BYTE\"\nbase = \"uint8\"\nkind = \"bit\"\nfunc = 0\n";
|
||||
const std::string bad = "[[type]]\nname = \"BYTE\"\nbase = \"bigint\"\nkind = \"bit\"\nfunc = 0\n";
|
||||
types.replace(types.find(old), old.size(), bad);
|
||||
if (!expect_load_err("mc_v2_bad3.toml", m + types + kOpsBasic + kFbsAll,
|
||||
"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")) {
|
||||
}
|
||||
// func 与 base 互锁破坏(INT func=0)
|
||||
{
|
||||
std::string types = kTypesAll;
|
||||
const std::string old = "[[type]]\nname = \"INT\"\nbase = \"int16\"\nkind = \"int\"\nfunc = 3\n";
|
||||
const std::string bad = "[[type]]\nname = \"INT\"\nbase = \"int16\"\nkind = \"int\"\nfunc = 0\n";
|
||||
types.replace(types.find(old), old.size(), bad);
|
||||
if (!expect_load_err("mc_v2_bad4.toml", m + types + kOpsBasic + kFbsAll,
|
||||
"func 0 mismatches base")) {
|
||||
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")) {
|
||||
}
|
||||
// kind 非法
|
||||
{
|
||||
std::string types = kTypesAll;
|
||||
const std::string old = "[[type]]\nname = \"BOOL\"\nbase = \"uint8\"\nkind = \"bool\"\nfunc = 0\n";
|
||||
const std::string bad = "[[type]]\nname = \"BOOL\"\nbase = \"uint8\"\nkind = \"weird\"\nfunc = 0\n";
|
||||
types.replace(types.find(old), old.size(), bad);
|
||||
if (!expect_load_err("mc_v2_bad5.toml", m + types + kOpsBasic + kFbsAll,
|
||||
"bad kind")) {
|
||||
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")) {
|
||||
}
|
||||
// prefix 非法
|
||||
{
|
||||
std::string ops = kOpsBasic;
|
||||
const std::string old = "[[op]]\nname = \"MOVE\"\nprefix = \"11\"\nop = 0\nfmt = \"RR\"\nfunc = \"width\"\nparams = [\"rd\", \"rs\"]\nenabled = true\n";
|
||||
const std::string bad = "[[op]]\nname = \"MOVE\"\nprefix = \"99\"\nop = 0\nfmt = \"RR\"\nfunc = \"width\"\nparams = [\"rd\", \"rs\"]\nenabled = true\n";
|
||||
ops.replace(ops.find(old), old.size(), bad);
|
||||
if (!expect_load_err("mc_v2_bad6.toml", m + kTypesAll + ops + kFbsAll,
|
||||
"bad prefix")) {
|
||||
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")) {
|
||||
}
|
||||
// prefix-fmt 互锁破坏(11 + RRR)
|
||||
{
|
||||
std::string ops = kOpsBasic;
|
||||
const std::string old = "[[op]]\nname = \"MOVE\"\nprefix = \"11\"\nop = 0\nfmt = \"RR\"\nfunc = \"width\"\nparams = [\"rd\", \"rs\"]\nenabled = true\n";
|
||||
const std::string bad = "[[op]]\nname = \"MOVE\"\nprefix = \"11\"\nop = 0\nfmt = \"RRR\"\nfunc = \"width\"\nparams = [\"rd\", \"ra\", \"rb\"]\nenabled = true\n";
|
||||
ops.replace(ops.find(old), old.size(), bad);
|
||||
if (!expect_load_err("mc_v2_bad7.toml", m + kTypesAll + ops + kFbsAll,
|
||||
"must pair with fmt")) {
|
||||
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")) {
|
||||
}
|
||||
// (prefix, op) 分区内重复
|
||||
{
|
||||
std::string ops = kOpsBasic;
|
||||
ops += "[[op]]\nname = \"DUP\"\nprefix = \"11\"\nop = 0\nfmt = \"RR\"\nfunc = \"width\"\nparams = [\"a\", \"b\"]\nenabled = true\n";
|
||||
if (!expect_load_err("mc_v2_bad8.toml", m + kTypesAll + ops + kFbsAll,
|
||||
"duplicate (prefix, op)")) {
|
||||
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",
|
||||
}
|
||||
// func 类别非法
|
||||
{
|
||||
std::string ops = kOpsBasic;
|
||||
const std::string old = "[[op]]\nname = \"MOVE\"\nprefix = \"11\"\nop = 0\nfmt = \"RR\"\nfunc = \"width\"\nparams = [\"rd\", \"rs\"]\nenabled = true\n";
|
||||
const std::string bad = "[[op]]\nname = \"MOVE\"\nprefix = \"11\"\nop = 0\nfmt = \"RR\"\nfunc = \"magic\"\nparams = [\"rd\", \"rs\"]\nenabled = true\n";
|
||||
ops.replace(ops.find(old), old.size(), bad);
|
||||
if (!expect_load_err("mc_v2_bad9.toml", m + kTypesAll + ops + kFbsAll,
|
||||
"bad func mode")) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// params 数量与 fmt 不符(CAL 应为 2)
|
||||
{
|
||||
std::string ops = kOpsBasic;
|
||||
const std::string old = "[[op]]\nname = \"CAL\"\nprefix = \"010101\"\nop = 0\nfmt = \"CAL\"\nfunc = \"none\"\nparams = [\"fb_id\", \"slot\"]\nenabled = true\n";
|
||||
const std::string bad = "[[op]]\nname = \"CAL\"\nprefix = \"010101\"\nop = 0\nfmt = \"CAL\"\nfunc = \"none\"\nparams = [\"fb_id\"]\nenabled = true\n";
|
||||
ops.replace(ops.find(old), old.size(), bad);
|
||||
if (!expect_load_err("mc_v2_bad10.toml", m + kTypesAll + ops + kFbsAll,
|
||||
"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")) {
|
||||
}
|
||||
// fb 缺全集(去掉 RTC)
|
||||
{
|
||||
std::string fbs = kFbsAll;
|
||||
const std::string rtc = "[[fb]]\nname = \"RTC\"\nfb_id = 14\nfields = [[\"enable\", \"BOOL\"], [\"date\", \"DATE\"], [\"tod\", \"TOD\"]]\n";
|
||||
fbs.erase(fbs.find(rtc), rtc.size());
|
||||
if (!expect_load_err("mc_v2_bad11.toml", m + kTypesAll + kOpsBasic + fbs,
|
||||
"missing fb_id 14")) {
|
||||
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",
|
||||
}
|
||||
// fb 字段类型未知
|
||||
{
|
||||
std::string fbs = kFbsAll;
|
||||
const std::string old = "[[\"in\", \"BOOL\"]], [\"pt\", \"TIME\"]";
|
||||
const std::string bad = "[[\"in\", \"REAL\"]], [\"pt\", \"TIME\"]";
|
||||
fbs.replace(fbs.find(old), old.size(), bad);
|
||||
if (!expect_load_err("mc_v2_bad12.toml", m + kTypesAll + kOpsBasic + fbs,
|
||||
"unknown field type")) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// 缺 CAL(op-fb 互锁)
|
||||
{
|
||||
std::string ops = kOpsBasic;
|
||||
const std::string cal = "[[op]]\nname = \"CAL\"\nprefix = \"010101\"\nop = 0\nfmt = \"CAL\"\nfunc = \"none\"\nparams = [\"fb_id\", \"slot\"]\nenabled = true\n";
|
||||
ops.erase(ops.find(cal), cal.size());
|
||||
if (!expect_load_err("mc_v2_bad13.toml", m + kTypesAll + ops + kFbsAll,
|
||||
"op-fb interlock")) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// 语法错误
|
||||
if (!expect_load_err("mc_bad11.toml", "[meta\nname = \"S\"\n", "parse error")) {
|
||||
if (!expect_load_err("mc_v2_bad14.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;
|
||||
}
|
||||
|
||||
// ---- 4. 类型元数据(TypeInfo)----
|
||||
// ---- 3. 类型元数据(TypeInfo,V2)----
|
||||
|
||||
static bool test_type_meta() {
|
||||
compiler::MachineConfig cfg;
|
||||
std::string err;
|
||||
CHECK(cfg.load(std::string(REPO_ROOT) + "/compiler/machine.toml", &err));
|
||||
|
||||
// BOOL:base=uint8 → 1 字节、无符号、非浮点、tag 0、range [0,1]
|
||||
// BOOL:1 字节、无符号、kind bool、func 0
|
||||
const compiler::TypeMeta b = compiler::type_meta(cfg, "BOOL");
|
||||
CHECK(b && b.width() == 1 && !b.is_signed() && !b.is_float() && b.tag() == 0);
|
||||
CHECK(b.has_range() && b.value_in_range(0) && b.value_in_range(1));
|
||||
CHECK(!b.value_in_range(2) && !b.value_in_range(-1));
|
||||
CHECK(b && b.width() == 1 && !b.is_signed() && !b.is_float() && b.func() == 0);
|
||||
CHECK(b.kind() == "bool");
|
||||
// 大小写不敏感(Linker type_name 是小写)
|
||||
const compiler::TypeMeta b2 = compiler::type_meta(cfg, "bool");
|
||||
CHECK(b2 && b2.tag() == 0 && b2.width() == 1);
|
||||
CHECK(b2 && b2.func() == 0 && b2.width() == 1);
|
||||
|
||||
// INT:int16 → 2 字节、有符号、tag 1、无 range
|
||||
// INT:2 字节、有符号、kind int、func 3
|
||||
const compiler::TypeMeta i = compiler::type_meta(cfg, "int");
|
||||
CHECK(i && i.width() == 2 && i.is_signed() && !i.is_float() && i.tag() == 1);
|
||||
CHECK(!i.has_range() || i.value_in_range(30000));
|
||||
CHECK(i && i.width() == 2 && i.is_signed() && !i.is_float() && i.func() == 3);
|
||||
CHECK(i.kind() == "int");
|
||||
|
||||
// TIME:int64 → 8 字节、有符号、tag 2
|
||||
const compiler::TypeMeta t = compiler::type_meta(cfg, "TIME");
|
||||
CHECK(t && t.width() == 8 && t.is_signed() && !t.is_float() && t.tag() == 2);
|
||||
// REAL:4 字节、浮点、func 8;TOD:4 字节、func 4
|
||||
const compiler::TypeMeta r = compiler::type_meta(cfg, "REAL");
|
||||
CHECK(r && r.width() == 4 && r.is_float() && r.func() == 8);
|
||||
const compiler::TypeMeta tod = compiler::type_meta(cfg, "TOD");
|
||||
CHECK(tod && tod.width() == 4 && tod.func() == 4);
|
||||
|
||||
// 未定义类型 → 空
|
||||
CHECK(!compiler::type_meta(cfg, "REAL"));
|
||||
CHECK(!compiler::type_meta(cfg, "NOPE"));
|
||||
CHECK(!compiler::type_meta(cfg, ""));
|
||||
return true;
|
||||
}
|
||||
|
||||
// ---- 5. SHA-256 已知向量 + 型号标识 ----
|
||||
// ---- 4. SHA-256 已知向量 + 型号标识 ----
|
||||
|
||||
static bool test_sha256() {
|
||||
// 标准测试向量
|
||||
{
|
||||
uint8_t out[compiler::kSha256Size];
|
||||
compiler::sha256(nullptr, 0, out);
|
||||
@@ -309,10 +367,10 @@ static bool test_sha256() {
|
||||
0xb4, 0x10, 0xff, 0x61, 0xf2, 0x00, 0x15, 0xad};
|
||||
CHECK(std::memcmp(out, want, 32) == 0);
|
||||
}
|
||||
// 型号标识:STATOR + 1 → "STATOR1" 补 '\0'
|
||||
// 型号标识:STATOR + 2 → "STATOR2" 补 '\0'
|
||||
char mid[compiler::kModelIdSize];
|
||||
compiler::fill_model_id("STATOR", 1, mid);
|
||||
CHECK(std::strncmp(mid, "STATOR1", 7) == 0);
|
||||
compiler::fill_model_id("STATOR", 2, mid);
|
||||
CHECK(std::strncmp(mid, "STATOR2", 7) == 0);
|
||||
CHECK(mid[7] == '\0');
|
||||
CHECK(mid[31] == '\0');
|
||||
return true;
|
||||
@@ -321,7 +379,6 @@ static bool test_sha256() {
|
||||
int main() {
|
||||
if (!test_positive()) return 1;
|
||||
if (!test_negative()) return 1;
|
||||
if (!test_positive_min()) return 1;
|
||||
if (!test_type_meta()) return 1;
|
||||
if (!test_sha256()) return 1;
|
||||
std::printf("machine_test: %d checks passed\n", g_checks);
|
||||
|
||||
Reference in New Issue
Block a user