/** * @file Typecheck.cpp * @brief 类型检查 * @author * @date 2026-08-21 * * @details 设计说明(详见 Doc/compiler/类型检查.md): * - 在链接成功后运行:符号/字段/函数存在性已由 12.6 保证,本阶段只查类型 * - 表达式求值类型三型:BOOL / INT / TIME;无隐式宽化,INT 与 TIME 不混用 * - 语句规则:赋值类型相等、FUNCTION 禁写全局(含 EXTERNAL,用例 14)、 * FB 命名实参匹配字段类型、IF/WHILE 条件必须 BOOL * - 错误:稳定前缀 "type error" + 文件(Expr/Stmt 无行列信息,只带文件) * * 函数清单: * - 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 检查 */ #include "compiler/Typecheck.h" #include #include #include namespace compiler { namespace { /** * @brief TType → 文本名(错误报文用) * @param t 求值类型 * @return "BOOL" / "INT" / "TIME" */ const char* tname(TType t) { switch (t) { case TType::Bool: return "BOOL"; case TType::Int: return "INT"; case TType::Time: return "TIME"; } return "?"; } /** * @brief TypeKind → TType * @param k 类型种类 * @param out 输出求值类型 * @return true 标量(BOOL/INT/TIME);FB 类型返回 false */ 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; } } /** * @brief 符号 type_name 字符串 → TType * @param name "bool" / "int" / "time"(小写) * @param out 输出求值类型 * @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; } return false; } /** * @brief 类型检查器 * * @details 链接结果只读;不修改 AST 与符号表。 */ class Checker { public: /** * @brief 构造检查器 * @param proj 工程定义(未用,保留接口对称;io 类型映射留待 12.8) * @param units 全部源文件的 AST * @param link 链接结果(符号/布局已解析) * @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) {} /** * @brief 逐 POU 逐语句做类型检查 * @return true 全部通过;false 首个类型错误(err 已写) */ bool run() { for (const SourceUnit& u : units_) { for (const POU& p : u.ast.pous) { const LinkResult::PouScope* sc = find_scope(p.name); if (sc == nullptr) { continue; // 不应发生(12.6 已登记) } for (const Stmt& st : p.body) { if (!check_stmt(u.path, *sc, p, st)) { return false; } } } } return true; } private: /** * @brief 组装 "type error: (file)" 写入 err * @details Expr/Stmt 不携带行列,只报文件;err_ 为 nullptr 时静默 * @param file 出错文件 * @param msg 错误描述(不含前缀) * @return 恒 false(便于 return fail(...) 一行退出) */ bool fail(const std::string& file, const std::string& msg) { if (err_) { *err_ = "type error: " + msg + " (" + file + ")"; } 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) { return ≻ } } 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) { if (s.name == name) { return &s; } } 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()) { return nullptr; } 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) { if (p.name == name) { return &p; } } } return nullptr; } /** * @brief 语句规则分发 * @param file 出错文件 * @param sc 当前作用域 * @param pou 当前 POU(函数名赋值 / 禁写全局判定用) * @param st 语句 AST * @return true 合法;false(err 已写) */ bool check_stmt(const std::string& file, const LinkResult::PouScope& sc, const POU& pou, const Stmt& st) { switch (st.kind) { case StmtKind::Assign: { TType lhs; if (!assign_target_type(file, sc, pou, st.target, &lhs)) { return false; } TType rhs; 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) + ")"); } return true; } case StmtKind::FbCall: { const auto it = sc.fb_instances.find(st.instance); if (it == sc.fb_instances.end()) { return fail(file, "no layout for FB instance '" + st.instance + "'"); } for (const FbArg& a : st.args) { TType want = TType::Bool; bool found = false; for (const FbField& f : it->second.fields) { if (f.name == a.name) { found = to_type(f.type, &want); break; } } if (!found) { return fail(file, "unknown input '" + a.name + "' for FB '" + st.instance + "'"); } TType got; if (!check_expr(file, sc, *a.value, &got)) { return false; } if (got != want) { return fail(file, "FB input '" + a.name + "' expects " + tname(want)); } } return true; } case StmtKind::If: if (!check_cond(file, sc, *st.cond)) { return false; } for (const Stmt& s : st.body) { if (!check_stmt(file, sc, pou, s)) return false; } for (const IfBranch& b : st.elsifs) { if (!check_cond(file, sc, *b.cond)) return false; for (const Stmt& s : b.body) { if (!check_stmt(file, sc, pou, s)) return false; } } for (const Stmt& s : st.else_body) { if (!check_stmt(file, sc, pou, s)) return false; } return true; case StmtKind::While: if (!check_cond(file, sc, *st.cond)) { return false; } for (const Stmt& s : st.body) { if (!check_stmt(file, sc, pou, s)) return false; } return true; } return true; } /** * @brief 条件表达式必须为 BOOL(IF / WHILE 共用) * @param file 出错文件 * @param sc 当前作用域 * @param e 条件表达式 * @return true 合法;false(err 已写) */ bool check_cond(const std::string& file, const LinkResult::PouScope& sc, const Expr& e) { TType t; 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))); } return true; } /** * @brief 解析赋值左值类型 * @details 普通左值:局部/输入/输出/外部/全局 → 符号类型; * FUNCTION 内函数名 → 返回类型;**FUNCTION 内左值是全局/外部 → 拒绝** * (用例 14:FUNCTION 禁止写全局,含经 VAR_EXTERNAL) * @param file 出错文件 * @param sc 当前作用域 * @param pou 当前 POU * @param target 左值标识符 * @param out 输出左值类型 * @return true 合法;false(err 已写) */ 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) || fail(file, "function result must be scalar"); } const Symbol* s = find_sym(sc, target); if (s == nullptr) { s = find_global(target); } if (s == nullptr) { return fail(file, "undeclared identifier '" + target + "'"); } // FUNCTION 禁写全局(含经 VAR_EXTERNAL) if (pou.kind == PouKind::Function && (s->kind == SymbolKind::Global || s->kind == SymbolKind::External)) { return fail(file, "function cannot write global '" + target + "'"); } if (!to_type(s->type_name, out)) { return fail(file, "'" + target + "' has no scalar type"); } return true; } /** * @brief 表达式定类型(递归) * @param file 出错文件 * @param sc 当前作用域 * @param e 表达式 AST * @param out 输出求值类型 * @return true 合法;false(err 已写) */ bool check_expr(const std::string& file, const LinkResult::PouScope& sc, const Expr& e, TType* out) { switch (e.kind) { case ExprKind::LitBool: *out = TType::Bool; return true; case ExprKind::LitInt: *out = TType::Int; return true; case ExprKind::LitTime: *out = TType::Time; return true; case ExprKind::VarRef: { const Symbol* s = find_sym(sc, e.name); if (s == nullptr) { s = find_global(e.name); } if (s == nullptr) { return fail(file, "undeclared identifier '" + e.name + "'"); } if (s->kind == SymbolKind::FbInstance) { return fail(file, "FB instance '" + e.name + "' cannot be used as a value"); } if (!to_type(s->type_name, out)) { return fail(file, "'" + e.name + "' has no scalar type"); } return true; } case ExprKind::Field: { const auto it = sc.fb_instances.find(e.name); if (it == sc.fb_instances.end()) { return fail(file, "no layout for FB instance '" + e.name + "'"); } for (const FbField& f : it->second.fields) { if (f.name == e.field) { return to_type(f.type, out) || fail(file, "field '" + e.field + "' has no scalar type"); } } return fail(file, "unknown field '" + e.field + "' for FB '" + e.name + "'"); } case ExprKind::Not: { TType t; if (!check_expr(file, sc, *e.operand, &t)) { return false; } if (t != TType::Bool) { return fail(file, "NOT operand must be BOOL"); } *out = TType::Bool; return true; } case ExprKind::And: case ExprKind::Or: { TType l, r; if (!check_expr(file, sc, *e.lhs, &l) || !check_expr(file, sc, *e.rhs, &r)) { return false; } if (l != TType::Bool || r != TType::Bool) { const char* op = (e.kind == ExprKind::And) ? "AND" : "OR"; return fail(file, std::string(op) + " operands must be BOOL"); } *out = TType::Bool; return true; } case ExprKind::Cmp: { TType l, r; if (!check_expr(file, sc, *e.lhs, &l) || !check_expr(file, sc, *e.rhs, &r)) { return false; } if (l != r) { return fail(file, "comparison of mismatched types (" + std::string(tname(l)) + " vs " + std::string(tname(r)) + ")"); } *out = TType::Bool; return true; } case ExprKind::Add: case ExprKind::Sub: case ExprKind::Mul: case ExprKind::Div: { 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)"); } *out = TType::Int; return true; } case ExprKind::Neg: { TType t; if (!check_expr(file, sc, *e.operand, &t)) { return false; } if (t != TType::Int) { return fail(file, "unary minus operand must be INT"); } *out = TType::Int; return true; } case ExprKind::Call: { const POU* f = find_pou(e.name); 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; } } if (!to_type(f->result_type.kind, out)) { return fail(file, "function '" + e.name + "' result must be scalar"); } return true; } } return fail(file, "unsupported expression"); } // ---- 成员 ---- const Project& proj_; // 工程定义(本阶段未用) const std::vector& units_; // 全部源文件 AST const LinkResult& link_; // 链接结果(只读) std::string* err_; // 错误输出(可空) }; } // namespace /** * @brief 对工程做类型检查(对外入口,链接成功后调用) * @details 逐 POU 逐语句检查;规则见 Doc/compiler/类型检查.md * @param proj 工程定义(本阶段未用,保留接口对称) * @param units 全部源文件的 AST * @param link 链接结果(符号/布局已解析) * @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); return c.run(); } } // namespace compiler