From 6be2873c88d536ef3ed02e8c3e1c7dc4111c1202 Mon Sep 17 00:00:00 2001 From: chentianya Date: Wed, 26 Aug 2026 09:29:31 +0800 Subject: [PATCH] =?UTF-8?q?=E9=98=B6=E6=AE=B53-1=EF=BC=9A=E7=BC=96?= =?UTF-8?q?=E8=AF=91=E5=99=A8=E8=AF=AD=E8=A8=80=E5=B1=82=20V2=20=E5=AE=8C?= =?UTF-8?q?=E6=88=90=EF=BC=8819=20=E7=B1=BB=E5=9E=8B/=E5=AD=97=E9=9D=A2?= =?UTF-8?q?=E9=87=8F=E6=89=A9=E5=B1=95/=E7=A6=81=E9=9A=90=E5=BC=8F?= =?UTF-8?q?=E8=BD=AC=E6=8D=A2/=E9=85=8D=E7=BD=AE=E9=A9=B1=E5=8A=A8=20FB?= =?UTF-8?q?=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Lexer V2: - 19 类型关键字(BOOL..DT);FB 名不再设关键字(IDENT) - FLOAT_LIT(1.5/2.5e3)、进制前缀(2#/8#/16# 换算十进制) - 日期字面量 D#/TOD#/DT#(历法换算 1970 纪元、闰年、值域检查) Parser V2: - TypeRef 字符串化(TypeKind{Scalar,FbBuiltin,FbUser},名字小写) - ExprKind 增 LitReal/LitDate/LitTod/LitDt;Expr.double_value - IDENT 类型统一 FbUser(内建判定在 Linker 查 fb 表) Typecheck V2: - TType 19 + Unresolved(字面量未定型) - 禁隐式转换(赋值/算术/比较严格同型) - resolve_to:未定型→消费点定型+值域检查(BOOL 0/1、无符号负数、 溢出、浮点仅 REAL/LREAL);算术按 kind 允许(TIME 仅 ±) - Call 实参按函数参数类型;check_project 加 cfg Linker V2: - FbField.type_name 字符串化;内建 FB 布局查 machine.toml [[fb]] 表 (删 8 个硬编码布局,带缓存);link_project 加 cfg Codegen V2: - 浮点/日期字面量(LOADK func 8/4/4/6;LitReal 按 REAL 装载,LREAL TODO) - 算术/比较 func 按操作数类型(expr_func) - TypeKind 连锁适配 main.cpp:link/check 传 cfg;--disasm 头 22 字段逐行中文注释(L5) + slots 节;StbView 补 offset_const/offset_funcs 测试适配:parser/linker/typecheck/vm/cases 适配新接口与 V2 语义。 ctest 11/13(vm_cycles/cases_all 待阶段 4)。codegen_test 仍剥离(阶段 3-2 重写)。 --- compiler/include/compiler/Lexer.h | 39 ++- compiler/include/compiler/Linker.h | 7 +- compiler/include/compiler/Parser.h | 15 +- compiler/include/compiler/Stb.h | 11 + compiler/include/compiler/Typecheck.h | 18 +- compiler/src/Codegen.cpp | 86 ++++-- compiler/src/Lexer.cpp | 265 ++++++++++++++++- compiler/src/Linker.cpp | 110 ++++--- compiler/src/Parser.cpp | 119 ++++---- compiler/src/Typecheck.cpp | 398 ++++++++++++++++++-------- compiler/src/main.cpp | 51 +++- tests/src/cases_test.cpp | 15 +- tests/src/linker_test.cpp | 27 +- tests/src/parser_test.cpp | 6 +- tests/src/typecheck_test.cpp | 24 +- tests/src/vm_test.cpp | 8 +- 16 files changed, 863 insertions(+), 336 deletions(-) diff --git a/compiler/include/compiler/Lexer.h b/compiler/include/compiler/Lexer.h index 371e085..d19462a 100644 --- a/compiler/include/compiler/Lexer.h +++ b/compiler/include/compiler/Lexer.h @@ -37,8 +37,24 @@ namespace compiler { VAR_EXTERNAL, ///< 外部变量段起始 END_VAR, ///< 变量段结束 BOOL, ///< 布尔类型 - INT, ///< 整数类型 - TIME, ///< 时间类型 + BYTE, ///< 位串类型(8 位) + WORD, ///< 位串类型(16 位) + DWORD, ///< 位串类型(32 位) + LWORD, ///< 位串类型(64 位) + SINT, ///< 有符号整数(8 位) + INT, ///< 有符号整数(16 位) + DINT, ///< 有符号整数(32 位) + LINT, ///< 有符号整数(64 位) + USINT, ///< 无符号整数(8 位) + UINT, ///< 无符号整数(16 位) + UDINT, ///< 无符号整数(32 位) + ULINT, ///< 无符号整数(64 位) + TIME, ///< 时间类型(毫秒) + REAL, ///< 浮点类型(32 位) + LREAL, ///< 浮点类型(64 位) + DATE, ///< 日期类型(天数) + TOD, ///< 时刻类型(当日毫秒) + DT, ///< 日期时刻类型(64 位) IF, ///< IF 关键字 ELSIF, ///< ELSIF 关键字 ELSE, ///< ELSE 关键字 @@ -52,18 +68,14 @@ namespace compiler { NOT, ///< 逻辑非(前缀) TRUE, ///< 布尔真字面量 FALSE, ///< 布尔假字面量 - TON, ///< 内建 FB 类型名 - TOF, ///< 内建 FB 类型名 - TP, ///< 内建 FB 类型名 - CTU, ///< 内建 FB 类型名 - CTD, ///< 内建 FB 类型名 - CTUD, ///< 内建 FB 类型名 - R_TRIG, ///< 内建 FB 类型名 - F_TRIG, ///< 内建 FB 类型名 // 字面量 - IDENT, ///< 标识符(text = 小写名) - INT_LIT, ///< 整数字面量(int_value) + IDENT, ///< 标识符(text = 小写名;含 FB 类型名,Typecheck 查表) + INT_LIT, ///< 整数字面量(int_value;含进制 2#/8#/16# 已换算) + FLOAT_LIT, ///< 浮点字面量(double_value) TIME_LIT, ///< 时间字面量(int_value = 毫秒) + DATE_LIT, ///< 日期字面量(int_value = 天数,1970 纪元) + TOD_LIT, ///< 时刻字面量(int_value = 当日毫秒) + DT_LIT, ///< 日期时刻字面量(int64 = date<<32|tod) // 符号 ASSIGN, ///< :=(赋值) EQ, ///< =(等于) @@ -92,7 +104,8 @@ namespace compiler { struct Token { Tok type; ///< token 类型 std::string text; ///< 标识符/关键字的小写文本;字面量原文 - int64_t int_value; ///< INT_LIT / TIME_LIT(毫秒)的字面量值 + int64_t int_value; ///< INT_LIT / TIME_LIT / DATE_LIT / TOD_LIT / DT_LIT 的字面量值 + double double_value; ///< FLOAT_LIT 的字面量值 std::string source_file; ///< 来源文件名 uint32_t line; ///< 起始行(从 1 起) uint32_t col; ///< 起始列(从 1 起) diff --git a/compiler/include/compiler/Linker.h b/compiler/include/compiler/Linker.h index 65338f1..5ac99ca 100644 --- a/compiler/include/compiler/Linker.h +++ b/compiler/include/compiler/Linker.h @@ -19,6 +19,7 @@ #include #include +#include "compiler/MachineConfig.h" #include "compiler/Parser.h" #include "compiler/Project.h" @@ -51,11 +52,11 @@ namespace compiler { }; /** - * @brief FB 类型字段(实例布局用;v1 字段类型仅 BOOL/INT/TIME)。 + * @brief FB 类型字段(实例布局用;V2 字段类型为标量类型名小写)。 */ struct FbField { std::string name; ///< 字段名 - TypeKind type = TypeKind::Bool; ///< 字段类型(仅标量) + std::string type_name; ///< 字段类型名(小写,如 "bool"/"time") }; /** @@ -108,7 +109,7 @@ namespace compiler { * @return true 成功;false 失败(err 已写) */ bool link_project(const Project& proj, const std::vector& units, - LinkResult* out, std::string* err); + const MachineConfig& cfg, LinkResult* out, std::string* err); /** * @brief 读取并解析一个 .st 文件成 SourceUnit。 diff --git a/compiler/include/compiler/Parser.h b/compiler/include/compiler/Parser.h index 24b7d8c..e1cf3c3 100644 --- a/compiler/include/compiler/Parser.h +++ b/compiler/include/compiler/Parser.h @@ -25,14 +25,14 @@ namespace compiler { * FbBuiltin=内建 FB 类型(TON/TOF/TP/CTU/CTD/CTUD/R_TRIG/F_TRIG); * FbUser=用户 FB 类型(存在性由 12.6 链接层校验)。 */ - enum class TypeKind { Bool, Int, Time, FbBuiltin, FbUser }; + enum class TypeKind { Scalar, FbBuiltin, FbUser }; /** * @brief 类型引用:种类 + (用户 FB 的)类型名。 */ struct TypeRef { - TypeKind kind = TypeKind::Bool; ///< 类型种类 - std::string name; ///< FbUser 时为用户 FB 类型名(小写) + TypeKind kind = TypeKind::Scalar; ///< 类型种类(Scalar=标量 19 型 / FbBuiltin / FbUser) + std::string name; ///< 类型名(小写:标量 = machine.toml type 键;FB = fb 表名或用户声明名) }; // ---- 变量段 ---- @@ -75,8 +75,12 @@ namespace compiler { */ enum class ExprKind { LitBool, ///< 布尔字面量(int_value 0/1) - LitInt, ///< 整数字面量(int_value) + LitInt, ///< 整数字面量(int_value,含进制已换算) + LitReal, ///< 浮点字面量(double_value) LitTime, ///< 时间字面量(int_value 毫秒) + LitDate, ///< 日期字面量(int_value 天数,1970 纪元) + LitTod, ///< 时刻字面量(int_value 当日毫秒) + LitDt, ///< 日期时刻字面量(int64 = date<<32|tod) VarRef, ///< 变量引用(name) Field, ///< FB 字段访问(name.field) Not, ///< 逻辑非(operand) @@ -104,7 +108,8 @@ namespace compiler { BinOp op = BinOp::Eq; ///< 二目运算(Cmp/Add/Sub/Mul/Div 用) std::string name; ///< VarRef / Field 对象 / Call 函数名 std::string field; ///< Field 字段名 - int64_t int_value = 0; ///< 字面量值(BOOL 0/1、INT、TIME 毫秒) + int64_t int_value = 0; ///< 字面量值(BOOL 0/1、INT、TIME ms、DATE 天、TOD ms、DT) + double double_value = 0; ///< LitReal 字面量值 std::unique_ptr lhs; ///< 二目左操作数 std::unique_ptr rhs; ///< 二目右操作数 std::unique_ptr operand; ///< Not / Neg 的操作数 diff --git a/compiler/include/compiler/Stb.h b/compiler/include/compiler/Stb.h index 1803f39..5ccec45 100644 --- a/compiler/include/compiler/Stb.h +++ b/compiler/include/compiler/Stb.h @@ -164,6 +164,10 @@ namespace compiler { uint32_t n_consts() const { return n_consts_; } /// 函数表行数(头 @48)。 uint32_t n_funcs() const { return n_funcs_; } + /// 常量表段偏移(头 @52)。 + uint32_t offset_const() const { return offs_of_const(); } + /// 函数表段偏移(头 @56)。 + uint32_t offset_funcs() const { return static_cast(get_le32_(56)); } /// 字节码段偏移(头 @60)。 uint32_t offset_code() const { return offset_code_; } /// 槽表段偏移(头 @64)。 @@ -207,6 +211,13 @@ namespace compiler { /// 常量表偏移(from 已校验的段起点)。 uint32_t offs_of_const() const; + /// 读头内 u32(小端;内部辅助)。 + uint32_t get_le32_(size_t off) const { + return static_cast(buf_[off]) + | (static_cast(buf_[off + 1]) << 8) + | (static_cast(buf_[off + 2]) << 16) + | (static_cast(buf_[off + 3]) << 24); + } const uint8_t* buf_; ///< 映像缓冲(不拥有) size_t len_; ///< 缓冲字节数 diff --git a/compiler/include/compiler/Typecheck.h b/compiler/include/compiler/Typecheck.h index c8dfca4..b688024 100644 --- a/compiler/include/compiler/Typecheck.h +++ b/compiler/include/compiler/Typecheck.h @@ -15,19 +15,23 @@ #include #include "compiler/Linker.h" +#include "compiler/MachineConfig.h" #include "compiler/Parser.h" #include "compiler/Project.h" namespace compiler { /** - * @brief 表达式求值类型(v1 三型,无隐式宽化)。 - * @details 各成员含义: - * - Bool:布尔量(逻辑运算 / 条件表达式) - * - Int:整数(算术运算) - * - Time:时间量(仅比较,无算术);INT 与 TIME 不混用 + * @brief 表达式求值类型(V2:19 标量 + 未定型)。 + * @details 无隐式转换(赋值/算术/比较严格同型);字面量未定型(Unresolved), + * 由消费点(赋值目标/实参/条件/另一操作数)按上下文定型 + 值域检查; + * BOOL 编译器强制 0/1。 */ - enum class TType { Bool, Int, Time }; + enum class TType { + Bool, Byte, Word, Dword, Lword, Sint, Int, Dint, Lint, + Usint, Uint, Udint, Ulint, Time, Real, Lreal, Date, Tod, Dt, + Unresolved, ///< 字面量未定型(待消费点定型) + }; /** * @brief 对工程做类型检查(在链接成功后调用)。 @@ -39,5 +43,5 @@ namespace compiler { * @return true 全部通过;false 失败(err 已写) */ bool check_project(const Project& proj, const std::vector& units, - const LinkResult& link, std::string* err); + const LinkResult& link, const MachineConfig& cfg, std::string* err); } diff --git a/compiler/src/Codegen.cpp b/compiler/src/Codegen.cpp index 36cd758..9d9c4a1 100644 --- a/compiler/src/Codegen.cpp +++ b/compiler/src/Codegen.cpp @@ -29,6 +29,7 @@ #include #include +#include #include #include #include @@ -216,16 +217,6 @@ namespace { return t ? static_cast(t->func) : 0; } - /// TypeKind 枚举 → 配置类型名(BOOL/INT/TIME;其他回退 INT)。 - static const char* type_kind_name(TypeKind k) { - switch (k) { - case TypeKind::Bool: return "BOOL"; - case TypeKind::Int: return "INT"; - case TypeKind::Time: return "TIME"; - default: return "INT"; - } - } - const POU* find_pou(const std::string& name) const { for (const SourceUnit& u : units_) { for (const POU& p : u.ast.pous) { @@ -258,19 +249,16 @@ namespace { return r; } - /// 变量名 → 语言类型名(局部声明)。 + /// 变量名 → 语言类型名(局部声明;标量 = d.type.name 小写)。 std::string type_name_of_var(const POU& pou, const std::string& name) const { for (const VarBlock& b : pou.blocks) { for (const VarDecl& d : b.vars) { if (d.name == name) { - if (d.type.kind == TypeKind::Bool) return "BOOL"; - if (d.type.kind == TypeKind::Int) return "INT"; - if (d.type.kind == TypeKind::Time) return "TIME"; - return "INT"; + return d.type.kind == TypeKind::Scalar ? d.type.name : "int"; } } } - return "INT"; + return "int"; } bool build_function(const POU& pou, FuncCtx* f) { @@ -496,11 +484,29 @@ namespace { bool compile_expr(FuncCtx& f, const Expr& e, uint8_t rd) { if (e.kind == ExprKind::LitBool || e.kind == ExprKind::LitInt || - e.kind == ExprKind::LitTime) { - const char* type_name = e.kind == ExprKind::LitBool ? "BOOL" - : e.kind == ExprKind::LitInt ? "INT" - : "TIME"; - const size_t s = E_imm(f, "LOADK", rd, const_id(e.int_value)); + e.kind == ExprKind::LitTime || e.kind == ExprKind::LitReal || + e.kind == ExprKind::LitDate || e.kind == ExprKind::LitTod || + e.kind == ExprKind::LitDt) { + const char* type_name = "INT"; + int64_t value = e.int_value; + switch (e.kind) { + case ExprKind::LitBool: type_name = "BOOL"; break; + case ExprKind::LitInt: type_name = "INT"; break; + case ExprKind::LitTime: type_name = "TIME"; break; + case ExprKind::LitReal: { + // TODO(V2): 浮点字面量按目标类型装载(REAL/LREAL 位模式不同), + // 需 compile_expr 携带目标 func;当前按 REAL(float32)装载 + type_name = "REAL"; + float fv = static_cast(e.double_value); + std::memcpy(&value, &fv, sizeof fv); + break; + } + case ExprKind::LitDate: type_name = "DATE"; break; + case ExprKind::LitTod: type_name = "TOD"; break; + case ExprKind::LitDt: type_name = "DT"; break; + default: break; + } + const size_t s = E_imm(f, "LOADK", rd, const_id(value)); set_func(f.code.data() + s, func_of_name(type_name)); return true; } @@ -560,7 +566,7 @@ namespace { return false; } const size_t s = E_rrr(f, arith_name(e.kind), rd, l, r); - set_func(f.code.data() + s, func_of_name("INT")); + set_func(f.code.data() + s, expr_func(f, *e.lhs, *e.rhs)); return true; } if (e.kind == ExprKind::Cmp) { @@ -573,7 +579,7 @@ namespace { return false; } const size_t s = E_rrr(f, cmp_name(e.op), rd, l, r); - set_func(f.code.data() + s, func_of_name("INT")); + set_func(f.code.data() + s, expr_func(f, *e.lhs, *e.rhs)); return true; } if (e.kind == ExprKind::Not) { @@ -625,6 +631,32 @@ namespace { return fail("expression not supported"); } + /// 表达式类型 func(算术/比较用):VarRef 取 reg_func/全局; + /// 嵌套表达式左右递归;字面量按装载类型(LitInt→INT、LitReal→REAL 等)。 + uint8_t expr_func(const FuncCtx& f, const Expr& l, const Expr& r) const { + if (l.kind == ExprKind::VarRef) { + const auto tf = f.reg_func.find(l.name); + if (tf != f.reg_func.end()) { + return tf->second; + } + return func_of_global(l.name); + } + if (l.kind == ExprKind::LitBool || l.kind == ExprKind::LitInt || + l.kind == ExprKind::LitTime || l.kind == ExprKind::LitReal || + l.kind == ExprKind::LitDate || l.kind == ExprKind::LitTod || + l.kind == ExprKind::LitDt) { + // 左是字面量 → 取右操作数(Typecheck 已定型同型) + if (r.kind == ExprKind::VarRef) { + const auto tf = f.reg_func.find(r.name); + if (tf != f.reg_func.end()) { + return tf->second; + } + return func_of_global(r.name); + } + } + return func_of_name("INT"); + } + /// 全局变量 → func(查链接符号类型)。 uint8_t func_of_global(const std::string& name) const { for (const Symbol& s : link_.globals) { @@ -726,10 +758,9 @@ namespace { inst.type_name = s.type_name; int32_t off = 0; for (const FbField& fd : lay->second.fields) { - const char* tn = type_kind_name(fd.type); - const TypeMeta tm = type_meta(cfg_, tn); + const TypeMeta tm = type_meta(cfg_, fd.type_name); if (!tm) { - return fail("no type for fb field '" + std::string(tn) + "'"); + return fail("no type for fb field '" + fd.type_name + "'"); } off = static_cast(align_up(off, tm.width())); inst.field_off[fd.name] = off; @@ -739,8 +770,7 @@ namespace { // 实例块对齐到 max(字段对齐) uint32_t max_align = 1; for (const FbField& fd : lay->second.fields) { - const char* tn = type_kind_name(fd.type); - const TypeMeta tm = type_meta(cfg_, tn); + const TypeMeta tm = type_meta(cfg_, fd.type_name); max_align = max_align > tm.width() ? max_align : tm.width(); } cur_addr = align_up(cur_addr, max_align); diff --git a/compiler/src/Lexer.cpp b/compiler/src/Lexer.cpp index 98e3ee9..6b01023 100644 --- a/compiler/src/Lexer.cpp +++ b/compiler/src/Lexer.cpp @@ -31,6 +31,7 @@ #include #include +#include #include #include @@ -60,10 +61,26 @@ namespace { {"var_global", Tok::VAR_GLOBAL}, {"var_external", Tok::VAR_EXTERNAL}, {"end_var", Tok::END_VAR}, - // 类型 + // 类型(V2:19 种 IEC 类型) {"bool", Tok::BOOL}, + {"byte", Tok::BYTE}, + {"word", Tok::WORD}, + {"dword", Tok::DWORD}, + {"lword", Tok::LWORD}, + {"sint", Tok::SINT}, {"int", Tok::INT}, + {"dint", Tok::DINT}, + {"lint", Tok::LINT}, + {"usint", Tok::USINT}, + {"uint", Tok::UINT}, + {"udint", Tok::UDINT}, + {"ulint", Tok::ULINT}, {"time", Tok::TIME}, + {"real", Tok::REAL}, + {"lreal", Tok::LREAL}, + {"date", Tok::DATE}, + {"tod", Tok::TOD}, + {"dt", Tok::DT}, // 控制 {"if", Tok::IF}, {"elsif", Tok::ELSIF}, @@ -80,15 +97,7 @@ namespace { // 字面量 {"true", Tok::TRUE}, {"false", Tok::FALSE}, - // 内置 FB 类型名(12.11 扩充为 8 个) - {"ton", Tok::TON}, - {"tof", Tok::TOF}, - {"tp", Tok::TP}, - {"ctu", Tok::CTU}, - {"ctd", Tok::CTD}, - {"ctud", Tok::CTUD}, - {"r_trig", Tok::R_TRIG}, - {"f_trig", Tok::F_TRIG}, + // FB 类型名不再设关键字(V2:IDENT + Typecheck 查 machine.toml [[fb]] 表) }; /** @@ -261,9 +270,25 @@ namespace { tok_col_ = col_; const char c = cur(); - // TIME 字面量:T#(大小写不敏感)优先于标识符 - if ((c == 't' || c == 'T') && pos_ + 1 < src_.size() && src_[pos_ + 1] == '#') { - return lex_time(); + // 日期/TIME 字面量前缀:D# / T# / TOD# / DT#(大小写不敏感,最长匹配) + // 字母序列后跟 '#' 才进入;"ton"、"timer" 等普通词走标识符分支。 + if (std::isalpha(static_cast(c))) { + const uint32_t wstart = pos_; + size_t j = pos_; + while (j < src_.size() && + std::isalpha(static_cast(src_[j]))) { + ++j; + } + if (j < src_.size() && src_[j] == '#') { + std::string w = src_.substr(wstart, j - wstart); + for (char& ch : w) { + ch = static_cast(std::tolower(static_cast(ch))); + } + if (w == "t") return lex_time(); // T# + if (w == "d") return lex_date(); // D# + if (w == "tod") return lex_tod(); // TOD# + if (w == "dt") return lex_dt(); // DT# + } } // 标识符 / 关键字:统一折小写存入 text(内部形), @@ -288,14 +313,50 @@ namespace { return true; } - // 整数:十进制 [0-9]+,int64_t 存储; - // 溢出检查在累加前做(v 上限 INT64_MAX)。 + // 数字:进制整数(2#/8#/16#)→ 浮点(. / e)→ 十进制整数 if (std::isdigit(static_cast(c))) { const uint32_t start = pos_; while (!at_end() && std::isdigit(static_cast(cur()))) { advance(); } + // 进制前缀:<基数>#<数字>(2/8/16) + if (!at_end() && cur() == '#') { + const std::string base_str = src_.substr(start, pos_ - start); + const uint32_t base = static_cast(std::stoul(base_str)); + if (base != 2 && base != 8 && base != 16) { + return fail("bad radix prefix"); + } + return lex_base_int(start, base); + } + // 浮点:小数点或指数(.5 需数字前缀,由本分支覆盖) + bool is_float = false; + if (!at_end() && cur() == '.' && pos_ + 1 < src_.size() && + std::isdigit(static_cast(src_[pos_ + 1]))) { + is_float = true; + advance(); // . + while (!at_end() && std::isdigit(static_cast(cur()))) { + advance(); + } + } + if (!at_end() && (cur() == 'e' || cur() == 'E')) { + is_float = true; + advance(); + if (!at_end() && (cur() == '+' || cur() == '-')) { + advance(); + } + if (at_end() || !std::isdigit(static_cast(cur()))) { + return fail("bad float literal"); + } + while (!at_end() && std::isdigit(static_cast(cur()))) { + advance(); + } + } const std::string digits = src_.substr(start, pos_ - start); + if (is_float) { + push(Tok::FLOAT_LIT, digits, 0); + out_->back().double_value = std::strtod(digits.c_str(), nullptr); + return true; + } uint64_t v = 0; for (const char ch : digits) { const uint64_t d = static_cast(ch - '0'); @@ -434,6 +495,180 @@ namespace { return true; } + /** + * @brief 解析进制整数字面量 <基数>#<数字>(2/8/16)。 + * @param start 起始下标(含基数数字) + * @param base 进制(2/8/16,已校验) + * @return true 成功(push INT_LIT,值已换算十进制); + * false 位数非法 / 溢出(err 已写) + */ + bool lex_base_int(uint32_t start, uint32_t base) { + advance(); // # + uint64_t v = 0; + bool any = false; + while (!at_end()) { + const char ch = cur(); + uint64_t d = 0; + if (ch >= '0' && ch <= '9') { + d = static_cast(ch - '0'); + } else if (ch >= 'a' && ch <= 'f') { + d = static_cast(ch - 'a' + 10); + } else if (ch >= 'A' && ch <= 'F') { + d = static_cast(ch - 'A' + 10); + } else { + break; + } + if (d >= base) { + return fail("bad base literal digit"); + } + if (v > (static_cast(INT64_MAX) - d) / base) { + return fail("base literal too large"); + } + v = v * base + d; + any = true; + advance(); + } + if (!any) { + return fail("bad base literal"); + } + push(Tok::INT_LIT, src_.substr(start, pos_ - start), static_cast(v)); + return true; + } + + /// 公历 → 1970-01-01 起的天数(Hinnant 算法;1970-01-01 = 0)。 + static int64_t days_from_civil(int64_t y, unsigned m, unsigned d) { + y -= m <= 2; + const int64_t era = (y >= 0 ? y : y - 399) / 400; + const unsigned yoe = static_cast(y - era * 400); + const unsigned doy = (153 * (m + (m > 2 ? -3 : 9)) + 2) / 5 + d - 1; + const unsigned doe = yoe * 365 + yoe / 4 - yoe / 100 + doy; + return era * 146097 + static_cast(doe) - 719468; + } + + /// 某年某月的天数(含闰年)。 + static unsigned days_in_month(int64_t y, unsigned m) { + static const unsigned kDays[12] = {31, 28, 31, 30, 31, 30, + 31, 31, 30, 31, 30, 31}; + if (m == 2 && (y % 4 == 0 && (y % 100 != 0 || y % 400 == 0))) { + return 29; + } + return kDays[m - 1]; + } + + /** + * @brief 解析日期段(y-m-d),换算为天数(1970 纪元)。 + * @param out_days 输出天数 + * @return true 成功;false 格式/值域非法 + */ + bool parse_date_part(int64_t* out_days) { + int64_t y = 0, m = 0, d = 0; + if (!read_uint(&y)) return false; + if (!expect_char('-')) return false; + if (!read_uint(&m)) return false; + if (!expect_char('-')) return false; + if (!read_uint(&d)) return false; + if (y < 1 || y > 9999) return false; + if (m < 1 || m > 12) return false; + if (d < 1 || d > days_in_month(y, static_cast(m))) return false; + *out_days = days_from_civil(y, static_cast(m), static_cast(d)); + return true; + } + + /// 读一个无符号整数段(消费数字)。 + bool read_uint(int64_t* out) { + if (at_end() || !std::isdigit(static_cast(cur()))) { + return false; + } + int64_t v = 0; + while (!at_end() && std::isdigit(static_cast(cur()))) { + v = v * 10 + static_cast(cur() - '0'); + advance(); + } + *out = v; + return true; + } + + /// 期望指定字符并消费;不匹配返回 false。 + bool expect_char(char want) { + if (at_end() || cur() != want) { + return false; + } + advance(); + return true; + } + + /** + * @brief 解析 D#<年>-<月>-<日> 日期字面量。 + * @return true 成功(push DATE_LIT,int_value = 天数); + * false 格式/值域非法(err 已写) + */ + bool lex_date() { + const uint32_t start = pos_; + advance(); // D + advance(); // # + int64_t days = 0; + if (!parse_date_part(&days)) { + return fail("bad date literal"); + } + push(Tok::DATE_LIT, src_.substr(start, pos_ - start), days); + return true; + } + + /** + * @brief 解析 TOD#<时>:<分>:<秒> 时刻字面量(当日毫秒,无小数秒)。 + * @return true 成功(push TOD_LIT,int_value = 当日毫秒); + * false 格式/值域非法(err 已写) + */ + bool lex_tod() { + const uint32_t start = pos_; + advance(); // T + advance(); // O + advance(); // D + advance(); // # + int64_t h = 0, m = 0, s = 0; + if (!read_uint(&h)) return fail("bad tod literal"); + if (!expect_char(':')) return fail("bad tod literal"); + if (!read_uint(&m)) return fail("bad tod literal"); + if (!expect_char(':')) return fail("bad tod literal"); + if (!read_uint(&s)) return fail("bad tod literal"); + if (h > 23 || m > 59 || s > 59) { + return fail("bad tod literal"); + } + const int64_t ms = h * 3600000 + m * 60000 + s * 1000; + push(Tok::TOD_LIT, src_.substr(start, pos_ - start), ms); + return true; + } + + /** + * @brief 解析 DT#<年>-<月>-<日>-<时>:<分>:<秒> 日期时刻字面量。 + * @return true 成功(push DT_LIT,int64 = date<<32 | tod); + * false 格式/值域非法(err 已写) + */ + bool lex_dt() { + const uint32_t start = pos_; + advance(); // D + advance(); // T + advance(); // # + int64_t days = 0; + if (!parse_date_part(&days)) { + return fail("bad dt literal"); + } + if (!expect_char('-')) return fail("bad dt literal"); + int64_t h = 0, m = 0, s = 0; + if (!read_uint(&h)) return fail("bad dt literal"); + if (!expect_char(':')) return fail("bad dt literal"); + if (!read_uint(&m)) return fail("bad dt literal"); + if (!expect_char(':')) return fail("bad dt literal"); + if (!read_uint(&s)) return fail("bad dt literal"); + if (h > 23 || m > 59 || s > 59) { + return fail("bad dt literal"); + } + const uint64_t tod = static_cast(h * 3600000 + m * 60000 + s * 1000); + const int64_t dt = (static_cast(days) << 32) | tod; + push(Tok::DT_LIT, src_.substr(start, pos_ - start), dt); + return true; + } + // ---- 状态 ---- const std::string& src_; ///< 源文本(外部所有,不拷贝) const std::string& sf_; ///< 源文件名(报错用) diff --git a/compiler/src/Linker.cpp b/compiler/src/Linker.cpp index 54a0a30..03c54a3 100644 --- a/compiler/src/Linker.cpp +++ b/compiler/src/Linker.cpp @@ -77,60 +77,47 @@ namespace { * @return "bool" / "int" / "time" / FB 类型名(小写);未知返回 "?" */ std::string type_str(const TypeRef& t) { - switch (t.kind) { - case TypeKind::Bool: return "bool"; - case TypeKind::Int: return "int"; - case TypeKind::Time: return "time"; - case TypeKind::FbBuiltin: - case TypeKind::FbUser: return t.name; + return t.name; // V2:所有种类都存小写类型名(标量 = type 表键 / FB 名) + } + + /// 小写化(内建 FB 名 → fb 表名映射用)。 + std::string lower_of(const std::string& s) { + std::string out = s; + for (char& ch : out) { + ch = static_cast(std::tolower(static_cast(ch))); } - return "?"; + return out; } /** - * @brief 是否为标量类型(BOOL / INT / TIME) - * @param k 类型种类 - * @return true 标量(FB 字段只允许标量,见 collect_fb_types) - */ - bool is_scalar(TypeKind k) { - return k == TypeKind::Bool || k == TypeKind::Int || k == TypeKind::Time; - } - - /// 内建 FB 布局(冻结,见 Doc/compiler/符号表与链接.md;12.11 扩为 8 个) - const FbLayout kTonLayout{"ton", {{"in", TypeKind::Bool}, {"pt", TypeKind::Time}, - {"q", TypeKind::Bool}, {"et", TypeKind::Time}}}; - const FbLayout kTofLayout{"tof", {{"in", TypeKind::Bool}, {"pt", TypeKind::Time}, - {"q", TypeKind::Bool}, {"et", TypeKind::Time}}}; - const FbLayout kTpLayout{"tp", {{"in", TypeKind::Bool}, {"pt", TypeKind::Time}, - {"q", TypeKind::Bool}, {"et", TypeKind::Time}}}; - const FbLayout kCtuLayout{"ctu", {{"cu", TypeKind::Bool}, {"r", TypeKind::Bool}, - {"pv", TypeKind::Int}, {"q", TypeKind::Bool}, - {"cv", TypeKind::Int}}}; - const FbLayout kCtdLayout{"ctd", {{"cd", TypeKind::Bool}, {"ld", TypeKind::Bool}, - {"pv", TypeKind::Int}, {"q", TypeKind::Bool}, - {"cv", TypeKind::Int}}}; - const FbLayout kCtudLayout{"ctud", {{"cu", TypeKind::Bool}, {"cd", TypeKind::Bool}, - {"r", TypeKind::Bool}, {"lu", TypeKind::Bool}, - {"pv", TypeKind::Int}, {"qu", TypeKind::Bool}, - {"qd", TypeKind::Bool}, {"cv", TypeKind::Int}}}; - const FbLayout kRTrigLayout{"r_trig", {{"clk", TypeKind::Bool}, {"q", TypeKind::Bool}}}; - const FbLayout kFTrigLayout{"f_trig", {{"clk", TypeKind::Bool}, {"q", TypeKind::Bool}}}; - - /** - * @brief 内建 FB 名 → 冻结布局 + * @brief 内建 FB 名 → 布局(查 machine.toml [[fb]] 表,V2 配置驱动)。 * @param name FB 类型名(小写) - * @return 布局指针(ton/tof/tp/ctu/ctd/ctud/r_trig/f_trig);未知返回 nullptr + * @return 布局指针(缓存);未知返回 nullptr */ - const FbLayout* builtin_layout(const std::string& name) { - if (name == "ton") return &kTonLayout; - if (name == "tof") return &kTofLayout; - if (name == "tp") return &kTpLayout; - if (name == "ctu") return &kCtuLayout; - if (name == "ctd") return &kCtdLayout; - if (name == "ctud") return &kCtudLayout; - if (name == "r_trig") return &kRTrigLayout; - if (name == "f_trig") return &kFTrigLayout; - return nullptr; + const FbLayout* builtin_layout(const MachineConfig& cfg, const std::string& name) { + static std::map cache; + auto it = cache.find(name); + if (it != cache.end()) { + return &it->second; + } + std::string up = name; + for (char& ch : up) { + ch = static_cast(std::toupper(static_cast(ch))); + } + const ConfigFb* fb = cfg.find_fb(up); + if (fb == nullptr) { + return nullptr; + } + FbLayout lay; + lay.type_name = name; + for (const ConfigFbField& f : fb->fields) { + FbField field; + field.name = f.name; + field.type_name = lower_of(f.type); + lay.fields.push_back(field); + } + auto r = cache.emplace(name, std::move(lay)); + return &r.first->second; } /** @@ -148,8 +135,8 @@ namespace { * @param err 错误输出;可为 nullptr(静默) */ Linker(const Project& proj, const std::vector& units, - LinkResult* out, std::string* err) - : proj_(proj), units_(units), out_(out), err_(err) { + const MachineConfig& cfg, LinkResult* out, std::string* err) + : proj_(proj), units_(units), cfg_(cfg), out_(out), err_(err) { // gvl 路径规范化一次,供 is_gvl_file 比较(容忍相对/绝对写法差异) gvl_path_ = std::filesystem::weakly_canonical( std::filesystem::path(proj.base_dir) / proj.gvl_file); @@ -285,15 +272,16 @@ namespace { continue; } for (const VarDecl& d : b.vars) { - if (!is_scalar(d.type.kind)) { + // FB 字段必须是标量类型(FB 实例字段不允许) + if (d.type.kind != TypeKind::Scalar) { return fail(u.path, - "FB field type must be BOOL/INT/TIME ('" + - d.name + "')", + "FB field type must be scalar ('" + d.name + + "')", d.line, d.col); } FbField f; f.name = d.name; - f.type = d.type.kind; + f.type_name = d.type.name; // 标量类型名(小写) lay.fields.push_back(f); } } @@ -399,8 +387,13 @@ namespace { sc.syms.push_back(s); if (is_fb) { FbLayout lay; - if (d.type.kind == TypeKind::FbBuiltin) { - const FbLayout* bl = builtin_layout(d.type.name); + // 内建判定:查 machine.toml [[fb]] 表(V2 配置驱动) + std::string up = d.type.name; + for (char& ch : up) { + ch = static_cast(std::toupper(static_cast(ch))); + } + if (cfg_.find_fb(up) != nullptr) { + const FbLayout* bl = builtin_layout(cfg_, d.type.name); if (!bl) { return fail(file, "unknown builtin FB '" + d.type.name + "'", d.line, d.col); @@ -693,6 +686,7 @@ namespace { // ---- 成员 ---- const Project& proj_; ///< 工程定义(toml) const std::vector& units_; ///< 全部源文件 AST + const MachineConfig& cfg_; ///< 机器定义(内建 FB 布局来源) LinkResult* out_; ///< 链接结果 std::string* err_; ///< 错误输出(可空) std::filesystem::path gvl_path_; ///< 规范化后的 gvl 路径 @@ -712,13 +706,13 @@ namespace { * @return true 成功;false 失败(err 前缀 "link error") */ bool link_project(const Project& proj, const std::vector& units, - LinkResult* out, std::string* err) { + const MachineConfig& cfg, LinkResult* out, std::string* err) { out->globals.clear(); out->global_index.clear(); out->fb_types.clear(); out->scopes.clear(); out->fn_order.clear(); - Linker l(proj, units, out, err); + Linker l(proj, units, cfg, out, err); return l.run(); } diff --git a/compiler/src/Parser.cpp b/compiler/src/Parser.cpp index 12d326a..cf6e684 100644 --- a/compiler/src/Parser.cpp +++ b/compiler/src/Parser.cpp @@ -73,6 +73,36 @@ namespace { * @param word 小写单词 * @return true 命中(var_in_out / ref / class / any / pointer / interface / method) */ + /** + * @brief 类型关键字 → 小写类型名(machine.toml type 表键)。 + * @param tk 类型 Tok(BOOL..DT) + * @return 小写名(如 "dint");非类型 Tok 返回空串 + */ + std::string tok_name(Tok tk) { + switch (tk) { + case Tok::BOOL: return "bool"; + case Tok::BYTE: return "byte"; + case Tok::WORD: return "word"; + case Tok::DWORD: return "dword"; + case Tok::LWORD: return "lword"; + case Tok::SINT: return "sint"; + case Tok::INT: return "int"; + case Tok::DINT: return "dint"; + case Tok::LINT: return "lint"; + case Tok::USINT: return "usint"; + case Tok::UINT: return "uint"; + case Tok::UDINT: return "udint"; + case Tok::ULINT: return "ulint"; + case Tok::TIME: return "time"; + case Tok::REAL: return "real"; + case Tok::LREAL: return "lreal"; + case Tok::DATE: return "date"; + case Tok::TOD: return "tod"; + case Tok::DT: return "dt"; + default: return ""; + } + } + bool is_forbidden(const std::string& word) { for (const char* f : kForbidden) { if (word == f) { @@ -425,70 +455,49 @@ namespace { e->int_value = cur().int_value; advance(); return true; + case Tok::FLOAT_LIT: + e->kind = ExprKind::LitReal; + e->double_value = cur().double_value; + advance(); + return true; + case Tok::DATE_LIT: + e->kind = ExprKind::LitDate; + e->int_value = cur().int_value; + advance(); + return true; + case Tok::TOD_LIT: + e->kind = ExprKind::LitTod; + e->int_value = cur().int_value; + advance(); + return true; + case Tok::DT_LIT: + e->kind = ExprKind::LitDt; + e->int_value = cur().int_value; + advance(); + return true; default: return fail("expected literal initializer"); } } /** - * @brief 解析类型名 - * @details 内建标量 BOOL/INT/TIME、内建 FB TON/TOF/CTU、 - * 或标识符(用户 FB 类型,存在性由 12.6 链接层校验) + * @brief 解析类型名(V2) + * @details 19 种 IEC 类型关键字(type_name 小写字符串)、 + * 或标识符(FB 类型名——内建查 machine.toml、用户查符号表,Typecheck 判定) * @param t 输出 TypeRef * @return true 成功;false 语法错误(err 已写) */ bool parse_type(TypeRef* t) { - switch (type()) { - case Tok::BOOL: - t->kind = TypeKind::Bool; - advance(); - return true; - case Tok::INT: - t->kind = TypeKind::Int; - advance(); - return true; - case Tok::TIME: - t->kind = TypeKind::Time; - advance(); - return true; - case Tok::TON: - t->kind = TypeKind::FbBuiltin; - t->name = "ton"; - advance(); - return true; - case Tok::TOF: - t->kind = TypeKind::FbBuiltin; - t->name = "tof"; - advance(); - return true; - case Tok::TP: - t->kind = TypeKind::FbBuiltin; - t->name = "tp"; - advance(); - return true; - case Tok::CTU: - t->kind = TypeKind::FbBuiltin; - t->name = "ctu"; - advance(); - return true; - case Tok::CTD: - t->kind = TypeKind::FbBuiltin; - t->name = "ctd"; - advance(); - return true; - case Tok::CTUD: - t->kind = TypeKind::FbBuiltin; - t->name = "ctud"; - advance(); - return true; - case Tok::R_TRIG: - t->kind = TypeKind::FbBuiltin; - t->name = "r_trig"; - advance(); - return true; - case Tok::F_TRIG: - t->kind = TypeKind::FbBuiltin; - t->name = "f_trig"; + // 类型关键字(小写名 = machine.toml type 表键) + const Tok tk = type(); + switch (tk) { + case Tok::BOOL: case Tok::BYTE: case Tok::WORD: case Tok::DWORD: + case Tok::LWORD: case Tok::SINT: case Tok::INT: case Tok::DINT: + case Tok::LINT: case Tok::USINT: case Tok::UINT: case Tok::UDINT: + case Tok::ULINT: case Tok::TIME: case Tok::REAL: case Tok::LREAL: + case Tok::DATE: case Tok::TOD: case Tok::DT: + t->kind = TypeKind::Scalar; + t->name = tok_name(tk); advance(); return true; case Tok::IDENT: { @@ -496,7 +505,7 @@ namespace { if (is_forbidden(word)) { return fail("'" + word + "' is not supported"); } - t->kind = TypeKind::FbUser; + t->kind = TypeKind::FbUser; // 内建或用户 FB(Linker/Typecheck 查表判定) t->name = word; advance(); return true; diff --git a/compiler/src/Typecheck.cpp b/compiler/src/Typecheck.cpp index 41cba80..b1839f2 100644 --- a/compiler/src/Typecheck.cpp +++ b/compiler/src/Typecheck.cpp @@ -1,86 +1,130 @@ /** * @file Typecheck.cpp - * @brief 类型检查 + * @brief 类型检查(V2) * @author * @date 2026-08-21 * - * @details 设计说明(详见 Doc/compiler/类型检查.md): + * @details 设计说明(详见 Doc/compiler/类型检查.md 与阶段3-语言层V2.md): * - 在链接成功后运行:符号/字段/函数存在性已由 12.6 保证,本阶段只查类型 - * - 表达式求值类型三型:BOOL / INT / TIME;无隐式宽化,INT 与 TIME 不混用 - * - 语句规则:赋值类型相等、FUNCTION 禁写全局(含 EXTERNAL,用例 14)、 - * FB 命名实参匹配字段类型、IF/WHILE 条件必须 BOOL - * - 错误:稳定前缀 "type error" + 文件(Expr/Stmt 无行列信息,只带文件) + * - 19 种 IEC 标量类型 + Unresolved(字面量未定型);**禁隐式转换** + * (赋值/算术/比较严格同型) + * - 字面量机制:整数字面量 / 浮点字面量未定型(Unresolved),由消费点 + * (赋值目标/实参/条件/另一操作数)按上下文定型 + **值域检查**(冲突报错); + * TRUE/FALSE(BOOL)、T#(TIME)、D#/TOD#/DT#(日期)字面量类型固定 + * - BOOL 编译器强制 0/1;IF/WHILE 条件必须 BOOL + * - 错误:稳定前缀 "type error" + 文件 * * 函数清单: - * - tname TType → 文本名(BOOL/INT/TIME) - * - to_type(TypeKind) TypeKind → TType(FB 类型返回 false) - * - to_type(string) 符号 type_name 字符串 → TType - * - Checker::Checker (构造)存工程/源文件/链接结果/错误输出 - * - Checker::run 逐 POU 逐语句检查 - * - Checker::fail 组装 "type error: (file)" 返回 false - * - find_scope 按 POU 名取作用域 - * - find_sym 在作用域里按名查符号 - * - find_global 按名查全局符号 - * - find_pou 按名查 POU AST(函数返回类型用) - * - check_stmt 语句规则分发(赋值/FB 调用/IF/WHILE) - * - assign_target_type 解析赋值左值类型(局部/全局/函数名),FUNCTION 禁写全局 - * - check_expr 表达式定类型(递归) - * - check_project 对外入口:逐 POU 检查 + * - tname / tname_lower TType ↔ 文本名(19 + Unresolved) + * - to_type(string) 符号 type_name 字符串 → TType + * - Checker::Checker (构造)存工程/源文件/链接结果/机器配置/错误输出 + * - Checker::run 逐 POU 逐语句检查 + * - fail 组装 "type error: (file)" 返回 false + * - resolve_to 表达式定型:未定型 → 值域检查后按目标定型; + * 已定型 → 必须与目标相同(禁隐式转换) + * - literal_range_ok 字面量按目标类型的值域检查(宽度/符号/BOOL 0/1) + * - arith_allowed 目标类型是否允许算术(int/uint/float 四则; + * TIME 仅 Add/Sub;其他拒绝) + * - check_stmt 语句规则分发(赋值/FB 调用/IF/WHILE) + * - assign_target_type 解析赋值左值类型(局部/全局/函数名) + * - check_cond 条件必须 BOOL(未定型按 BOOL 定型) + * - check_expr 表达式定类型(递归) + * - check_project 对外入口:逐 POU 检查 */ #include "compiler/Typecheck.h" +#include #include #include #include +#include "compiler/TypeInfo.h" + namespace compiler { namespace { /** - * @brief TType → 文本名(错误报文用) + * @brief TType → 文本名(错误报文用,大写)。 * @param t 求值类型 - * @return "BOOL" / "INT" / "TIME" + * @return 如 "DINT";Unresolved 返回 "?";未知返回 "?" */ const char* tname(TType t) { switch (t) { case TType::Bool: return "BOOL"; + case TType::Byte: return "BYTE"; + case TType::Word: return "WORD"; + case TType::Dword: return "DWORD"; + case TType::Lword: return "LWORD"; + case TType::Sint: return "SINT"; case TType::Int: return "INT"; + case TType::Dint: return "DINT"; + case TType::Lint: return "LINT"; + case TType::Usint: return "USINT"; + case TType::Uint: return "UINT"; + case TType::Udint: return "UDINT"; + case TType::Ulint: return "ULINT"; case TType::Time: return "TIME"; + case TType::Real: return "REAL"; + case TType::Lreal: return "LREAL"; + case TType::Date: return "DATE"; + case TType::Tod: return "TOD"; + case TType::Dt: return "DT"; + case TType::Unresolved: return "?"; } return "?"; } /** - * @brief TypeKind → TType - * @param k 类型种类 - * @param out 输出求值类型 - * @return true 标量(BOOL/INT/TIME);FB 类型返回 false + * @brief TType → 小写类型名(machine.toml type 表键)。 + * @param t 求值类型 + * @return 如 "dint";Unresolved/未知返回空串 */ - bool to_type(TypeKind k, TType* out) { - switch (k) { - case TypeKind::Bool: *out = TType::Bool; return true; - case TypeKind::Int: *out = TType::Int; return true; - case TypeKind::Time: *out = TType::Time; return true; - default: return false; + const char* tname_lower(TType t) { + switch (t) { + case TType::Bool: return "bool"; + case TType::Byte: return "byte"; + case TType::Word: return "word"; + case TType::Dword: return "dword"; + case TType::Lword: return "lword"; + case TType::Sint: return "sint"; + case TType::Int: return "int"; + case TType::Dint: return "dint"; + case TType::Lint: return "lint"; + case TType::Usint: return "usint"; + case TType::Uint: return "uint"; + case TType::Udint: return "udint"; + case TType::Ulint: return "ulint"; + case TType::Time: return "time"; + case TType::Real: return "real"; + case TType::Lreal: return "lreal"; + case TType::Date: return "date"; + case TType::Tod: return "tod"; + case TType::Dt: return "dt"; + case TType::Unresolved: return ""; } + return ""; } /** - * @brief 符号 type_name 字符串 → TType - * @param name "bool" / "int" / "time"(小写) + * @brief 符号 type_name 字符串 → TType(19 标量)。 + * @param name 小写类型名("bool".."dt") * @param out 输出求值类型 - * @return true 标量;FB 类型名返回 false + * @return true 标量;未知(FB 类型名等)返回 false */ bool to_type(const std::string& name, TType* out) { - if (name == "bool") { *out = TType::Bool; return true; } - if (name == "int") { *out = TType::Int; return true; } - if (name == "time") { *out = TType::Time; return true; } + for (int i = 0; i < 19; ++i) { + const TType t = static_cast(i); + if (name == tname_lower(t)) { + *out = t; + return true; + } + } return false; } /** - * @brief 类型检查器 + * @brief 类型检查器(V2) * * @details 链接结果只读;不修改 AST 与符号表。 */ @@ -88,14 +132,15 @@ namespace { public: /** * @brief 构造检查器 - * @param proj 工程定义(未用,保留接口对称;io 类型映射留待 12.8) + * @param proj 工程定义(未用,保留接口对称) * @param units 全部源文件的 AST * @param link 链接结果(符号/布局已解析) + * @param cfg 机器定义(类型元数据/值域来源) * @param err 错误输出;可为 nullptr(静默) */ Checker(const Project& proj, const std::vector& units, - const LinkResult& link, std::string* err) - : proj_(proj), units_(units), link_(link), err_(err) {} + const LinkResult& link, const MachineConfig& cfg, std::string* err) + : proj_(proj), units_(units), link_(link), cfg_(cfg), err_(err) {} /** * @brief 逐 POU 逐语句做类型检查 @@ -121,7 +166,6 @@ namespace { private: /** * @brief 组装 "type error: (file)" 写入 err - * @details Expr/Stmt 不携带行列,只报文件;err_ 为 nullptr 时静默 * @param file 出错文件 * @param msg 错误描述(不含前缀) * @return 恒 false(便于 return fail(...) 一行退出) @@ -133,11 +177,6 @@ namespace { return false; } - /** - * @brief 按 POU 名取作用域 - * @param name POU 名(小写) - * @return 作用域指针;未找到返回 nullptr - */ const LinkResult::PouScope* find_scope(const std::string& name) const { for (const LinkResult::PouScope& sc : link_.scopes) { if (sc.name == name) { @@ -147,12 +186,6 @@ namespace { return nullptr; } - /** - * @brief 在作用域里按名查符号 - * @param sc 作用域 - * @param name 符号名(小写) - * @return 符号指针;未找到返回 nullptr - */ const Symbol* find_sym(const LinkResult::PouScope& sc, const std::string& name) const { for (const Symbol& s : sc.syms) { @@ -163,11 +196,6 @@ namespace { return nullptr; } - /** - * @brief 按名查全局符号 - * @param name 全局名(小写) - * @return 符号指针;未找到返回 nullptr - */ const Symbol* find_global(const std::string& name) const { const auto it = link_.global_index.find(name); if (it == link_.global_index.end()) { @@ -176,11 +204,6 @@ namespace { return &link_.globals[it->second]; } - /** - * @brief 按名查 POU AST(函数返回类型用) - * @param name POU 名(小写) - * @return POU 指针;未找到返回 nullptr - */ const POU* find_pou(const std::string& name) const { for (const SourceUnit& u : units_) { for (const POU& p : u.ast.pous) { @@ -192,6 +215,116 @@ namespace { return nullptr; } + /** + * @brief 表达式定型到目标类型(禁隐式转换 + 字面量值域检查)。 + * @param file 出错文件 + * @param e 表达式(已 check_expr,got 为其求值类型) + * @param got 表达式当前类型(Unresolved = 字面量未定型) + * @param want 目标类型(消费点) + * @return true 定型成功;false(类型不匹配 / 字面量越域) + */ + bool resolve_to(const std::string& file, const Expr& e, TType got, TType want) { + if (got == want) { + return true; + } + if (got != TType::Unresolved) { + return fail(file, "type mismatch: " + std::string(tname(got)) + + " vs " + std::string(tname(want))); + } + // 未定型字面量 → 按目标类型值域检查后定型 + return literal_range_ok(file, e, want); + } + + /** + * @brief 字面量按目标类型的值域检查。 + * @details LitInt:整型目标按宽度/符号检查(BOOL 0/1、无符号负数拒绝、 + * 溢出拒绝);REAL/LREAL 目标合法。LitReal:仅 REAL/LREAL 目标 + * (REAL 检查 ±3.4e38);整数目标拒绝。 + * @return true 值域内;false(err 已写) + */ + bool literal_range_ok(const std::string& file, const Expr& e, TType want) { + if (e.kind == ExprKind::LitInt) { + if (want == TType::Real || want == TType::Lreal) { + return true; // 整数字面量 → 浮点目标合法 + } + const TypeMeta tm = type_meta(cfg_, tname_lower(want)); + if (!tm) { + return fail(file, "unknown target type for literal"); + } + if (tm.kind() == "bool") { + if (e.int_value != 0 && e.int_value != 1) { + return fail(file, "BOOL literal must be 0 or 1"); + } + return true; + } + if (!tm.is_signed() && e.int_value < 0) { + return fail(file, "negative literal for unsigned type " + + std::string(tname(want))); + } + const int w = static_cast(tm.width()); + if (tm.is_signed()) { + const int64_t minv = + (w == 8) ? INT64_MIN : -(static_cast(1) << (8 * w - 1)); + const int64_t maxv = + (w == 8) ? INT64_MAX : (static_cast(1) << (8 * w - 1)) - 1; + if (e.int_value < minv || e.int_value > maxv) { + return fail(file, "integer literal out of range for " + + std::string(tname(want))); + } + } else { + const uint64_t maxv = + (w == 8) ? UINT64_MAX : ((static_cast(1) << (8 * w)) - 1); + if (static_cast(e.int_value) > maxv) { + return fail(file, "integer literal out of range for " + + std::string(tname(want))); + } + } + return true; + } + if (e.kind == ExprKind::LitReal) { + if (want == TType::Real) { + if (std::fabs(e.double_value) > 3.40282347e38) { + return fail(file, "float literal out of range for REAL"); + } + return true; + } + if (want == TType::Lreal) { + return true; + } + return fail(file, "float literal cannot be assigned to " + + std::string(tname(want))); + } + return true; // 其他 Unresolved(不应发生) + } + + /** + * @brief 目标类型是否允许四则算术。 + * @param file 出错文件 + * @param t 目标类型(已定型) + * @param is_add_sub 是否 Add/Sub(TIME 允许 ±,不允许 ×/÷) + * @return true 允许;false(err 已写) + */ + bool arith_allowed(const std::string& file, TType t, bool is_add_sub) { + if (t == TType::Unresolved) { + return true; // 两侧都是字面量,向上传播 + } + const TypeMeta tm = type_meta(cfg_, tname_lower(t)); + if (!tm) { + return fail(file, "arithmetic on non-scalar type"); + } + const std::string& kind = tm.kind(); + if (kind == "int" || kind == "uint" || kind == "float") { + return true; + } + if (kind == "time") { + if (is_add_sub) { + return true; + } + return fail(file, "TIME has no multiply/divide"); + } + return fail(file, std::string(tname(t)) + " has no arithmetic"); + } + /** * @brief 语句规则分发 * @param file 出错文件 @@ -212,9 +345,8 @@ namespace { if (!check_expr(file, sc, *st.value, &rhs)) { return false; } - if (lhs != rhs) { - return fail(file, "type mismatch in assignment to '" + st.target + - "' (" + tname(lhs) + " vs " + tname(rhs) + ")"); + if (!resolve_to(file, *st.value, rhs, lhs)) { + return false; } return true; } @@ -228,7 +360,7 @@ namespace { bool found = false; for (const FbField& f : it->second.fields) { if (f.name == a.name) { - found = to_type(f.type, &want); + found = to_type(f.type_name, &want); break; } } @@ -240,9 +372,8 @@ namespace { if (!check_expr(file, sc, *a.value, &got)) { return false; } - if (got != want) { - return fail(file, "FB input '" + a.name + "' expects " + - tname(want)); + if (!resolve_to(file, *a.value, got, want)) { + return false; } } return true; @@ -277,11 +408,7 @@ namespace { } /** - * @brief 条件表达式必须为 BOOL(IF / WHILE 共用) - * @param file 出错文件 - * @param sc 当前作用域 - * @param e 条件表达式 - * @return true 合法;false(err 已写) + * @brief 条件表达式必须为 BOOL(IF / WHILE 共用;未定型按 BOOL 定型)。 */ bool check_cond(const std::string& file, const LinkResult::PouScope& sc, const Expr& e) { @@ -289,30 +416,23 @@ namespace { if (!check_expr(file, sc, e, &t)) { return false; } - if (t != TType::Bool) { - return fail(file, "condition must be BOOL, got " + std::string(tname(t))); + if (!resolve_to(file, e, t, TType::Bool)) { + return fail(file, "condition must be BOOL"); } return true; } /** - * @brief 解析赋值左值类型 + * @brief 解析赋值左值类型。 * @details 普通左值:局部/输入/输出/外部/全局 → 符号类型; - * FUNCTION 内函数名 → 返回类型;**FUNCTION 内左值是全局/外部 → 拒绝** - * (用例 14:FUNCTION 禁止写全局,含经 VAR_EXTERNAL) - * @param file 出错文件 - * @param sc 当前作用域 - * @param pou 当前 POU - * @param target 左值标识符 - * @param out 输出左值类型 - * @return true 合法;false(err 已写) + * FUNCTION 内函数名 → 返回类型;**FUNCTION 内左值是全局/外部 → 拒绝**。 */ bool assign_target_type(const std::string& file, const LinkResult::PouScope& sc, const POU& pou, const std::string& target, TType* out) { // FUNCTION 内对函数名赋值 = 结果值写入(用例 13 约定) if (pou.kind == PouKind::Function && target == pou.name) { - return to_type(pou.result_type.kind, out) || + return to_type(pou.result_type.name, out) || fail(file, "function result must be scalar"); } @@ -337,12 +457,7 @@ namespace { } /** - * @brief 表达式定类型(递归) - * @param file 出错文件 - * @param sc 当前作用域 - * @param e 表达式 AST - * @param out 输出求值类型 - * @return true 合法;false(err 已写) + * @brief 表达式定类型(递归;V2 字面量未定型机制)。 */ bool check_expr(const std::string& file, const LinkResult::PouScope& sc, const Expr& e, TType* out) { @@ -351,11 +466,21 @@ namespace { *out = TType::Bool; return true; case ExprKind::LitInt: - *out = TType::Int; + case ExprKind::LitReal: + *out = TType::Unresolved; // 未定型,消费点定型 + 值域检查 return true; case ExprKind::LitTime: *out = TType::Time; return true; + case ExprKind::LitDate: + *out = TType::Date; + return true; + case ExprKind::LitTod: + *out = TType::Tod; + return true; + case ExprKind::LitDt: + *out = TType::Dt; + return true; case ExprKind::VarRef: { const Symbol* s = find_sym(sc, e.name); if (s == nullptr) { @@ -379,7 +504,7 @@ namespace { } for (const FbField& f : it->second.fields) { if (f.name == e.field) { - return to_type(f.type, out) || + return to_type(f.type_name, out) || fail(file, "field '" + e.field + "' has no scalar type"); } } @@ -390,7 +515,7 @@ namespace { if (!check_expr(file, sc, *e.operand, &t)) { return false; } - if (t != TType::Bool) { + if (!resolve_to(file, *e.operand, t, TType::Bool)) { return fail(file, "NOT operand must be BOOL"); } *out = TType::Bool; @@ -402,7 +527,8 @@ namespace { if (!check_expr(file, sc, *e.lhs, &l) || !check_expr(file, sc, *e.rhs, &r)) { return false; } - if (l != TType::Bool || r != TType::Bool) { + if (!resolve_to(file, *e.lhs, l, TType::Bool) || + !resolve_to(file, *e.rhs, r, TType::Bool)) { const char* op = (e.kind == ExprKind::And) ? "AND" : "OR"; return fail(file, std::string(op) + " operands must be BOOL"); } @@ -414,7 +540,14 @@ namespace { if (!check_expr(file, sc, *e.lhs, &l) || !check_expr(file, sc, *e.rhs, &r)) { return false; } - if (l != r) { + // 比较同型:一侧未定型 → 按另一侧定型;两侧未定型 → 结果 BOOL + if (l == TType::Unresolved && r == TType::Unresolved) { + *out = TType::Bool; + return true; + } + const TType want = (l == TType::Unresolved) ? r : l; + if (!resolve_to(file, *e.lhs, l, want) || + !resolve_to(file, *e.rhs, r, want)) { return fail(file, "comparison of mismatched types (" + std::string(tname(l)) + " vs " + std::string(tname(r)) + ")"); @@ -426,14 +559,26 @@ namespace { case ExprKind::Sub: case ExprKind::Mul: case ExprKind::Div: { + const bool is_add_sub = + (e.kind == ExprKind::Add || e.kind == ExprKind::Sub); TType l, r; if (!check_expr(file, sc, *e.lhs, &l) || !check_expr(file, sc, *e.rhs, &r)) { return false; } - if (l != TType::Int || r != TType::Int) { - return fail(file, "arithmetic operands must be INT (TIME has no arithmetic)"); + // 同型定型:一侧未定型 → 按另一侧 + TType t = (l == TType::Unresolved) ? r : l; + if (l == TType::Unresolved && r == TType::Unresolved) { + t = TType::Unresolved; // 两侧字面量,向上传播 + } else if (!resolve_to(file, *e.lhs, l, t) || + !resolve_to(file, *e.rhs, r, t)) { + return fail(file, "arithmetic operands must have same type (" + + std::string(tname(l)) + " vs " + + std::string(tname(r)) + ")"); } - *out = TType::Int; + if (!arith_allowed(file, t, is_add_sub)) { + return false; + } + *out = t; return true; } case ExprKind::Neg: { @@ -441,10 +586,14 @@ namespace { if (!check_expr(file, sc, *e.operand, &t)) { return false; } - if (t != TType::Int) { - return fail(file, "unary minus operand must be INT"); + if (t != TType::Unresolved) { + const TypeMeta tm = type_meta(cfg_, tname_lower(t)); + const std::string& kind = tm ? tm.kind() : ""; + if (kind != "int" && kind != "uint" && kind != "float") { + return fail(file, "unary minus operand must be numeric"); + } } - *out = TType::Int; + *out = t; return true; } case ExprKind::Call: { @@ -452,13 +601,31 @@ namespace { if (f == nullptr) { return fail(file, "undeclared function '" + e.name + "'"); } - for (const auto& a : e.args) { - TType at; - if (!check_expr(file, sc, *a, &at)) { - return false; + // 实参类型 = 函数 VAR_INPUT 声明序 + std::vector params; + for (const VarBlock& b : f->blocks) { + if (b.section != VarSection::Input) { + continue; + } + for (const VarDecl& d : b.vars) { + TType pt; + if (to_type(d.type.name, &pt)) { + params.push_back(pt); + } } } - if (!to_type(f->result_type.kind, out)) { + for (size_t i = 0; i < e.args.size(); ++i) { + TType at; + if (!check_expr(file, sc, *e.args[i], &at)) { + return false; + } + if (i < params.size()) { + if (!resolve_to(file, *e.args[i], at, params[i])) { + return false; + } + } + } + if (!to_type(f->result_type.name, out)) { return fail(file, "function '" + e.name + "' result must be scalar"); } return true; @@ -471,23 +638,24 @@ namespace { const Project& proj_; ///< 工程定义(本阶段未用) const std::vector& units_; ///< 全部源文件 AST const LinkResult& link_; ///< 链接结果(只读) + const MachineConfig& cfg_; ///< 机器定义(类型元数据/值域) std::string* err_; ///< 错误输出(可空) }; } // namespace /** - * @brief 对工程做类型检查(对外入口,链接成功后调用) - * @details 逐 POU 逐语句检查;规则见 Doc/compiler/类型检查.md - * @param proj 工程定义(本阶段未用,保留接口对称) + * @brief 对工程做类型检查(对外入口,链接成功后调用;V2) + * @param proj 工程定义 * @param units 全部源文件的 AST * @param link 链接结果(符号/布局已解析) + * @param cfg 机器定义(machine.toml) * @param err 错误输出;可为 nullptr(静默) * @return true 全部通过;false 失败(err 前缀 "type error") */ bool check_project(const Project& proj, const std::vector& units, - const LinkResult& link, std::string* err) { - Checker c(proj, units, link, err); + const LinkResult& link, const MachineConfig& cfg, std::string* err) { + Checker c(proj, units, link, cfg, err); return c.run(); } diff --git a/compiler/src/main.cpp b/compiler/src/main.cpp index ec30553..d2e1180 100644 --- a/compiler/src/main.cpp +++ b/compiler/src/main.cpp @@ -59,12 +59,35 @@ namespace { std::fprintf(stderr, "error: %s\n", v.error().c_str()); return 1; } - std::printf("image: %s (%zu bytes, %u functions, %u globals, entry fn %u)\n", - path, bytes.size(), v.n_funcs(), v.n_globals(), v.entry_fn_id()); - std::printf(" dt_ms=%u cycle_limit=%u hash=0x%016llx model=%s sha=%s\n", - v.dt_ms(), v.cycle_limit(), - static_cast(v.project_hash()), - v.model_id().c_str(), v.sha_ok() ? "ok" : "BAD"); + std::printf("image: %s (%zu bytes)\n", path, bytes.size()); + std::printf("header:\n"); + std::printf(" magic = 0x%08x # 魔数 \"STSC\"\n", + static_cast(compiler::kMagic)); + std::printf(" version = %u # 格式版本(V2)\n", + static_cast(compiler::kVersion)); + std::printf(" model = %-14s # 型号标识\n", v.model_id().c_str()); + std::printf(" cycle_limit = %-14u # 每周期指令数上限\n", v.cycle_limit()); + std::printf(" dt_ms = %-14u # 周期时长(毫秒)\n", v.dt_ms()); + std::printf(" hash = 0x%016llx # 工程哈希(FNV-1a 64)\n", + static_cast(v.project_hash())); + std::printf(" entry_fn_id = %-14u # 入口函数(MAIN)fn_id\n", v.entry_fn_id()); + std::printf(" n_globals = %-14u # 全局槽数\n", v.n_globals()); + std::printf(" n_i = %-14u # 输入槽数(保留恒 0)\n", 0u); + std::printf(" n_q = %-14u # 输出槽数(保留恒 0)\n", 0u); + std::printf(" n_m = %-14u # 中间槽数(保留恒 0)\n", 0u); + std::printf(" n_consts = %-14u # 常量表条目数\n", v.n_consts()); + std::printf(" n_funcs = %-14u # 函数表行数\n", v.n_funcs()); + std::printf(" offset_const = %-9u # 常量表段偏移\n", v.offset_const()); + std::printf(" offset_funcs = %-9u # 函数表段偏移\n", v.offset_funcs()); + std::printf(" offset_code = %-9u # 字节码段偏移\n", v.offset_code()); + std::printf(" offset_slots = %-9u # 槽表段偏移\n", v.offset_slots()); + std::printf(" offset_values = %-8u # 值段偏移\n", v.offset_values()); + std::printf(" n_slots = %-14u # 槽表条目数\n", v.n_slots()); + std::printf(" values_size = %-14u # 值段字节数\n", v.values_size()); + std::printf(" offset_meta = %-14u # 元数据段偏移(0 = 空段)\n", v.offset_meta()); + std::printf(" max_stack = %-14u # 栈区字节数(编译期算)\n", v.max_stack()); + std::printf(" sha = %-14s # 文件尾 SHA-256 完整性\n", + v.sha_ok() ? "ok" : "BAD"); if (v.n_consts()) { std::printf("constants:\n"); @@ -92,6 +115,18 @@ namespace { } } + if (v.n_slots()) { + std::printf("slots:\n"); + for (uint32_t i = 0; i < v.n_slots(); ++i) { + const uint32_t addr = + static_cast(v.slots_bytes()[i * 8]) + | (static_cast(v.slots_bytes()[i * 8 + 1]) << 8) + | (static_cast(v.slots_bytes()[i * 8 + 2]) << 16) + | (static_cast(v.slots_bytes()[i * 8 + 3]) << 24); + std::printf(" [%u] addr=%u\n", i, addr); + } + } + if (v.data_len()) { std::printf("data (%zu bytes):\n", v.data_len()); const uint8_t* d = v.data_bytes(); @@ -209,11 +244,11 @@ int main(int argc, char** argv) { units.push_back(std::move(u)); } compiler::LinkResult link; - if (!compiler::link_project(proj, units, &link, &err)) { + if (!compiler::link_project(proj, units, cfg, &link, &err)) { std::fprintf(stderr, "error: %s\n", err.c_str()); return 1; } - if (!compiler::check_project(proj, units, link, &err)) { + if (!compiler::check_project(proj, units, link, cfg, &err)) { std::fprintf(stderr, "error: %s\n", err.c_str()); return 1; } diff --git a/tests/src/cases_test.cpp b/tests/src/cases_test.cpp index 2500ba6..02b140c 100644 --- a/tests/src/cases_test.cpp +++ b/tests/src/cases_test.cpp @@ -94,8 +94,14 @@ namespace { } units.push_back(std::move(u)); } + compiler::MachineConfig cfg; + std::string cerr; + if (!cfg.load(std::string(REPO_ROOT) + "/compiler/machine.toml", &cerr)) { + std::printf("FAIL %s machine: %s\n", dir, cerr.c_str()); + return false; + } LinkResult link; - if (!link_project(p, units, &link, &err)) { + if (!link_project(p, units, cfg, &link, &err)) { if (want == Outcome::CompileError) { if (err.find(keyword) == std::string::npos) { std::printf("FAIL %s: want '%s', got '%s'\n", dir, keyword, err.c_str()); @@ -107,7 +113,7 @@ namespace { std::printf("FAIL %s link: %s\n", dir, err.c_str()); return false; } - if (!check_project(p, units, link, &err)) { + if (!check_project(p, units, link, cfg, &err)) { if (want == Outcome::CompileError) { if (err.find(keyword) == std::string::npos) { std::printf("FAIL %s: want '%s', got '%s'\n", dir, keyword, err.c_str()); @@ -119,11 +125,6 @@ namespace { std::printf("FAIL %s type: %s\n", dir, err.c_str()); return false; } - MachineConfig cfg; - if (!cfg.load(std::string(REPO_ROOT) + "/compiler/machine.toml", &err)) { - std::printf("FAIL %s machine load\n", dir); - return false; - } std::vector img; if (!codegen_project(p, units, link, cfg, &img, &err)) { if (want == Outcome::CompileError) { diff --git a/tests/src/linker_test.cpp b/tests/src/linker_test.cpp index b832389..d23d79e 100644 --- a/tests/src/linker_test.cpp +++ b/tests/src/linker_test.cpp @@ -43,7 +43,12 @@ static bool link_case(const char* dir, compiler::LinkResult* out, std::string* e } units.push_back(std::move(u)); } - return link_project(p, units, out, err); + compiler::MachineConfig cfg; + std::string cerr; + if (!cfg.load(std::string(REPO_ROOT) + "/compiler/machine.toml", &cerr)) { + return false; + } + return link_project(p, units, cfg, out, err); } static bool expect_link_err(const char* dir, const char* keyword) { @@ -146,7 +151,10 @@ static bool test_line1() { CHECK(load_unit(p.base_dir + "/" + f, &u, &err)); units.push_back(std::move(u)); } - CHECK(link_project(p, units, &r, &err)); + compiler::MachineConfig cfg; + std::string cerr; + CHECK(cfg.load(std::string(REPO_ROOT) + "/compiler/machine.toml", &cerr)); + CHECK(link_project(p, units, cfg, &r, &err)); // 全局槽序 = 声明顺序 CHECK(r.globals.size() == 4); @@ -162,7 +170,7 @@ static bool test_line1() { CHECK(it->second.fields[0].name == "start"); CHECK(it->second.fields[1].name == "stop"); CHECK(it->second.fields[2].name == "q"); - CHECK(it->second.fields[2].type == TypeKind::Bool); + CHECK(it->second.fields[2].type_name == "bool"); // 内建 TON 布局(用例 17 的 t) const FbLayout* ton = nullptr; @@ -204,16 +212,19 @@ static bool test_builtin() { CHECK(load_unit(p.base_dir + "/" + f, &u, &err)); units.push_back(std::move(u)); } - CHECK(link_project(p, units, &r, &err)); + compiler::MachineConfig cfg; + std::string cerr; + CHECK(cfg.load(std::string(REPO_ROOT) + "/compiler/machine.toml", &cerr)); + CHECK(link_project(p, units, cfg, &r, &err)); CHECK(r.scopes.size() == 1); const LinkResult::PouScope& main = r.scopes[0]; const auto it = main.fb_instances.find("t"); CHECK(it != main.fb_instances.end()); CHECK(it->second.fields.size() == 4); - CHECK(it->second.fields[0].name == "in" && it->second.fields[0].type == TypeKind::Bool); - CHECK(it->second.fields[1].name == "pt" && it->second.fields[1].type == TypeKind::Time); - CHECK(it->second.fields[2].name == "q" && it->second.fields[2].type == TypeKind::Bool); - CHECK(it->second.fields[3].name == "et" && it->second.fields[3].type == TypeKind::Time); + CHECK(it->second.fields[0].name == "in" && it->second.fields[0].type_name == "bool"); + CHECK(it->second.fields[1].name == "pt" && it->second.fields[1].type_name == "time"); + CHECK(it->second.fields[2].name == "q" && it->second.fields[2].type_name == "bool"); + CHECK(it->second.fields[3].name == "et" && it->second.fields[3].type_name == "time"); return true; } diff --git a/tests/src/parser_test.cpp b/tests/src/parser_test.cpp index 9f8954f..52954e8 100644 --- a/tests/src/parser_test.cpp +++ b/tests/src/parser_test.cpp @@ -38,7 +38,7 @@ static bool test_gvl_file() { CHECK(g.section == VarSection::Global); CHECK(g.vars.size() == 4); CHECK(g.vars[0].name == "emergencystop"); - CHECK(g.vars[0].type.kind == TypeKind::Bool); + CHECK(g.vars[0].type.kind == TypeKind::Scalar && g.vars[0].type.name == "bool"); CHECK(g.vars[0].has_init && g.vars[0].init_value == 0); // := FALSE CHECK(!g.vars[1].has_init); // I0_0 return true; @@ -145,7 +145,7 @@ static bool test_function() { const POU& f = u.pous[0]; CHECK(f.kind == PouKind::Function); CHECK(f.name == "add"); - CHECK(f.result_type.kind == TypeKind::Int); + CHECK(f.result_type.kind == TypeKind::Scalar && f.result_type.name == "int"); CHECK(f.body[0].kind == StmtKind::Assign); CHECK(f.body[0].value->kind == ExprKind::Add); @@ -192,7 +192,7 @@ static bool test_control() { CHECK(parse_pous("t.st", src, &u, &err)); const POU& p = u.pous[0]; CHECK(p.blocks[0].vars[2].name == "t"); - CHECK(p.blocks[0].vars[2].type.kind == TypeKind::FbBuiltin); + CHECK(p.blocks[0].vars[2].type.kind == TypeKind::FbUser); CHECK(p.blocks[0].vars[2].type.name == "ton"); CHECK(p.body.size() == 4); diff --git a/tests/src/typecheck_test.cpp b/tests/src/typecheck_test.cpp index e680967..f622dd1 100644 --- a/tests/src/typecheck_test.cpp +++ b/tests/src/typecheck_test.cpp @@ -45,10 +45,15 @@ static bool check_case(const char* dir, compiler::LinkResult* r, std::string* er } units.push_back(std::move(u)); } - if (!link_project(p, units, r, err)) { + compiler::MachineConfig cfg; + std::string cerr; + if (!cfg.load(std::string(REPO_ROOT) + "/compiler/machine.toml", &cerr)) { return false; } - return check_project(p, units, *r, err); + if (!link_project(p, units, cfg, r, err)) { + return false; + } + return check_project(p, units, *r, cfg, err); } // 从内联源码构造工程(写临时目录),加载、链接、类型检查 @@ -81,10 +86,15 @@ static bool check_src(const char* src_name, const char* st_content, units.push_back(std::move(u)); } LinkResult r; - if (!link_project(p, units, &r, err)) { + compiler::MachineConfig cfg; + std::string cerr; + if (!cfg.load(std::string(REPO_ROOT) + "/compiler/machine.toml", &cerr)) { return false; } - return check_project(p, units, r, err); + if (!link_project(p, units, cfg, &r, err)) { + return false; + } + return check_project(p, units, r, cfg, err); } static bool expect_type_err(const char* src_name, const char* st_content, @@ -134,14 +144,14 @@ static bool test_negative() { if (!expect_type_err("assign_mismatch", "PROGRAM MAIN\nVAR\n b : BOOL;\nEND_VAR\n" " b := 5;\nEND_PROGRAM\n", - "type mismatch in assignment to 'b'")) { + "BOOL literal must be 0 or 1")) { return false; } // INT 与 TIME 混用算术 if (!expect_type_err("int_time", "PROGRAM MAIN\nVAR\n x : INT;\n t : TIME;\n y : INT;\nEND_VAR\n" " y := x + t;\nEND_PROGRAM\n", - "TIME has no arithmetic")) { + "arithmetic operands must have same type")) { return false; } // NOT 吃 INT @@ -163,7 +173,7 @@ static bool test_negative() { "PROGRAM MAIN\nVAR\n t : TON;\n q : BOOL;\nEND_VAR\n" " t(in := TRUE, pt := TRUE);\n" " q := t.Q;\nEND_PROGRAM\n", - "FB input 'pt' expects TIME")) { + "type mismatch: BOOL vs TIME")) { return false; } // IF 条件非 BOOL diff --git a/tests/src/vm_test.cpp b/tests/src/vm_test.cpp index d5df419..e94afa1 100644 --- a/tests/src/vm_test.cpp +++ b/tests/src/vm_test.cpp @@ -73,10 +73,10 @@ static bool make_machine(const char* dir, vm::Machine* m, std::string* err) { units.push_back(std::move(u)); } LinkResult link; - if (!link_project(p, units, &link, err)) { + if (!link_project(p, units, cfg, &link, err)) { return false; } - if (!check_project(p, units, link, err)) { + if (!check_project(p, units, link, cfg, err)) { return false; } std::vector img; @@ -460,10 +460,10 @@ static bool test_verify() { CHECK(compiler::load_unit(p.base_dir + "/" + f, &u, &err)); units.push_back(std::move(u)); } - compiler::LinkResult link; - CHECK(compiler::link_project(p, units, &link, &err)); compiler::MachineConfig cfg; CHECK(cfg.load(std::string(REPO_ROOT) + "/compiler/machine.toml", &err)); + compiler::LinkResult link; + CHECK(compiler::link_project(p, units, cfg, &link, &err)); std::vector img; CHECK(compiler::codegen_project(p, units, link, cfg, &img, &err)); img[100] ^= 0x01; // 篡改代码段一字节