Files
Interpreter/compiler/src/Typecheck.cpp
T
Admin 9cd4997a3a 实现 12.7 类型检查:表达式类型、语句规则、FUNCTION 禁写全局。
- Typecheck.h/cpp:三型(BOOL/INT/TIME)求值,AND/OR/NOT 只吃 BOOL、算术只吃 INT(TIME 无算术)、比较同型、IF/WHILE 条件必须 BOOL
- FUNCTION 禁写全局(含经 VAR_EXTERNAL)——用例 14 点亮;函数名赋值=返回类型
- FB 命名实参匹配字段类型;FB 实例不能当值
- typecheck_test:23 断言(用例 14 + 8 类负例 + 13 个正例),ctest 8/8
2026-08-21 11:26:55 +08:00

495 lines
20 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* @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 → TTypeFB 类型返回 false
* - to_type(string) 符号 type_name 字符串 → TType
* - Checker::Checker (构造)存工程/源文件/链接结果/错误输出
* - Checker::run 逐 POU 逐语句检查
* - Checker::fail 组装 "type error: <msg> (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 <cstdio>
#include <string>
#include <vector>
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<SourceUnit>& 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: <msg> (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 &sc;
}
}
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 合法;falseerr 已写)
*/
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 条件表达式必须为 BOOLIF / WHILE 共用)
* @param file 出错文件
* @param sc 当前作用域
* @param e 条件表达式
* @return true 合法;falseerr 已写)
*/
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 合法;falseerr 已写)
*/
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 合法;falseerr 已写)
*/
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<SourceUnit>& 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<SourceUnit>& units,
const LinkResult& link, std::string* err) {
Checker c(proj, units, link, err);
return c.run();
}
} // namespace compiler