三个 compiler 源文件补 Doxygen 注释:函数功能与参数说明。

- Lexer.cpp:13 个函数 @brief/@param/@return(含 T# 字面量、行列语义)
- Parser.cpp:24 个函数(含表达式优先级爬升、token 前瞻区分)
- Linker.cpp:24 个函数(含六步流程、三色 DFS 环检测、io 校验)
- 文件头统一:设计说明 + 函数清单;代码零改动,ctest 7/7 通过
This commit is contained in:
2026-08-21 11:23:37 +08:00
parent 36f2f5637c
commit 93a4662d6a
3 changed files with 627 additions and 39 deletions
+280 -14
View File
@@ -3,6 +3,47 @@
* @brief ST 子集递归下降语法分析
* @author
* @date 2026-08-21
*
* @details 设计说明(文法详见 Doc/compiler/语法.md):
* - 输入:lexer 产出的 token 流(含末尾 END);输出:Unit(顶层全局段 + POU 列表)
* - 递归下降 + 每层一个函数,token 流只前移不回溯(FB 调用/赋值用一 token 前瞻区分)
* - 表达式按优先级爬升:or → and → not → cmp → add → mul → unary → primary
* AND/OR 的短路语义保留在 AST(12.8 编成跳转)
* - 错误:稳定前缀 "syntax error" + 当前 token 的文件与行列
*
* 函数清单:
* - is_forbidden 命中保留名(var_in_out/ref/class/any…)→ true
* - Parser::Parser (构造)先整体 lex 成 token 流;词法失败透传 lex error
* - Parser::run 顶层循环:顶层 VAR_GLOBAL 段(GVL 形态)或 POU 直到 END
* - Parser::fail 组装 "syntax error: <msg> (file, line, col)" 返回 false
* - Parser::cur/type/check token 视图:当前 token / 类型 / 是否指定类型
* - Parser::advance 前进一个 tokenEND 处不动)
* - Parser::expect 断言当前是 t,否则报 "expected 'what'"
* - parse_pou POU 外壳:PROGRAM/FUNCTION[:类型]/FUNCTION_BLOCK + 段 + 体 + END_*
* - is_var_section 当前 token 是否五种变量段关键字之一
* - end_tok_for POU 种类 → 配对结束关键字
* - check_end_for 是否已到本 POU 的配对结束关键字(语句体循环出口)
* - parse_var_block VAR_* … END_VAR:逐行收集声明,定段种类
* - parse_vars_line 一行声明展开成多个 VarDecl(共享类型与初值)
* - parse_literal_expr 初值:只吃字面量(TRUE/FALSE/INT/TIME
* - parse_type 类型名:BOOL/INT/TIME/TON/TOF/CTU/标识符
* - parse_stmt 语句分发:IF/WHILE 直入;IDENT 前瞻区分 FB 调用 vs 赋值
* - parse_assign ident := expr ;fb.field 左值拒绝)
* - parse_fb_call ident(名 := expr, …); 命名实参 FB 调用
* - parse_if IF … THEN … (ELSIF …)* (ELSE …)? END_IF
* - parse_while WHILE … DO … END_WHILE
* - parse_body 连续语句,遇 ELSIF/ELSE/END_IF/END_WHILE/END 停
* - parse_expr 表达式入口 → parse_or
* - parse_or/and OR/AND 层(短路保留在 AST,左结合)
* - parse_not NOT 前缀一元(可连写)
* - parse_cmp 比较层(a<b<c 链式拒绝)
* - is_cmp_op 是否比较运算符 token
* - parse_add/mul 加减/乘除层(左结合)
* - parse_unary 一元负号(-5 = Neg(LitInt 5)
* - parse_primary 原子:字面量/括号/ident(变量 | fb.field | 函数调用)
* - parse_ident 标识符(非保留名,返回小写)
* - parse_pous 对外入口:清空输出 → 构造 Parser → run
* - parse_pous_file 读文件后调 parse_pous;打不开算 syntax error
*/
#include "compiler/Parser.h"
@@ -16,12 +57,19 @@
namespace compiler {
namespace {
// 明确拒绝的保留名(小写命中即报错,见 Doc/compiler/语法.md
// 明确拒绝的保留名(小写命中即报错,见 Doc/compiler/语法.md
// 这些词在词法层不是关键字(lex 成 IDENT),必须在语法层显式拦截,
// 避免 VAR_IN_OUT / REF / CLASS 等被静默当作用户标识符。
const char* const kForbidden[] = {
"var_in_out", "var_temp", "ref", "class", "any",
"pointer", "interface", "method",
};
/**
* @brief 判断单词是否命中保留名清单
* @param word 小写单词
* @return true 命中(var_in_out / ref / class / any / pointer / interface / method
*/
bool is_forbidden(const std::string& word) {
for (const char* f : kForbidden) {
if (word == f) {
@@ -31,8 +79,21 @@ namespace {
return false;
}
/**
* @brief ST 递归下降解析器
*
* @details 两阶段:构造时一次性 lex 成 token 流,再逐层解析;
* 不做流式交互。token 流只前移不回溯。
*/
class Parser {
public:
/**
* @brief 构造解析器(先整体 lex 成 token 流)
* @param source_file 源文件名(报错用)
* @param content 源文本
* @param out 解析结果 Unitglobals / pous
* @param err 错误输出;可为 nullptr(静默)
*/
Parser(const std::string& source_file, const std::string& content,
Unit* out, std::string* err)
: err_(err), out_(out) {
@@ -45,6 +106,13 @@ namespace {
pos_ = 0;
}
/**
* @brief 顶层循环:解析直至 END
*
* @details 每轮要么是 GVL 形态的顶层 VAR_GLOBAL 段(无 POU 的文件),
* 要么是一个 POU。
* @return true 成功(out 填满);false 语法错误(err 已写)
*/
bool run() {
if (!ok_) {
return false;
@@ -74,6 +142,12 @@ namespace {
private:
// ---- 错误 ----
/**
* @brief 组装 "syntax error: <msg> (file, line, col)" 写入 err
* @details 位置取当前 token(指向出错处,不回溯到行首)
* @param msg 错误描述(不含前缀与位置)
* @return 恒 false(便于 return fail(...) 一行退出)
*/
bool fail(const std::string& msg) {
if (err_) {
const Token& t = cur();
@@ -87,10 +161,30 @@ namespace {
// ---- token 视图 ----
/**
* @brief 当前 token(不消费)
* @return tokens_[pos_] 的引用
*/
const Token& cur() const { return tokens_[pos_]; }
/**
* @brief 当前 token 的类型
* @return cur().type
*/
Tok type() const { return cur().type; }
/**
* @brief 当前 token 是否为指定类型
* @param t 期望的 token 类型
* @return true 匹配
*/
bool check(Tok t) const { return type() == t; }
/**
* @brief 前进一个 token
* @details END 处原地不动(pos_ 永不越界)
* @return 前进前的 token 引用
*/
const Token& advance() {
const Token& t = tokens_[pos_];
if (pos_ + 1 < tokens_.size()) {
@@ -99,6 +193,12 @@ namespace {
return t;
}
/**
* @brief 断言当前 token 是 t,否则报错
* @param t 期望的 token 类型
* @param what 错误信息中的名字(如 "END_IF"
* @return true 匹配并前进;false 不匹配(err 已写)
*/
bool expect(Tok t, const char* what) {
if (!check(t)) {
return fail(std::string("expected '") + what + "'");
@@ -109,6 +209,14 @@ namespace {
// ---- POU ----
/**
* @brief 解析 POU 外壳
*
* @details PROGRAM / FUNCTION [: 返回类型] / FUNCTION_BLOCK
* 后接 0+ 变量段、0+ 语句,最后必须是配对 END_*。
* @param p 输出 POU
* @return true 成功;false 语法错误(err 已写)
*/
bool parse_pou(POU* p) {
if (check(Tok::PROGRAM)) {
advance();
@@ -127,6 +235,7 @@ namespace {
return false;
}
// 仅 FUNCTION 有返回类型:FUNCTION Add : INT
if (p->kind == PouKind::Function) {
if (!expect(Tok::COLON, ":")) {
return false;
@@ -136,6 +245,7 @@ namespace {
}
}
// 变量段可多个、可缺省,按声明顺序保留
while (is_var_section()) {
VarBlock b;
if (!parse_var_block(&b)) {
@@ -144,6 +254,7 @@ namespace {
p->blocks.push_back(std::move(b));
}
// 语句体直到配对 END_*
while (!check_end_for(p->kind)) {
Stmt s;
if (!parse_stmt(&s)) {
@@ -161,6 +272,10 @@ namespace {
return expect(end_tok_for(p->kind), end_kw);
}
/**
* @brief 当前 token 是否为五种变量段关键字之一
* @return true 为 VAR / VAR_INPUT / VAR_OUTPUT / VAR_GLOBAL / VAR_EXTERNAL
*/
bool is_var_section() const {
switch (type()) {
case Tok::VAR:
@@ -174,6 +289,12 @@ namespace {
}
}
/**
* @brief POU 种类 → 配对结束关键字
* @param k POU 种类
* @return PROGRAM → END_PROGRAM、FUNCTION → END_FUNCTION、
* FUNCTION_BLOCK → END_FUNCTION_BLOCK
*/
Tok end_tok_for(PouKind k) const {
switch (k) {
case PouKind::Program: return Tok::END_PROGRAM;
@@ -183,10 +304,20 @@ namespace {
return Tok::END;
}
/**
* @brief 当前 token 是否已到本 POU 的配对结束关键字
* @param k POU 种类
* @return true 语句体循环应停止
*/
bool check_end_for(PouKind k) const { return check(end_tok_for(k)); }
// ---- 变量段 ----
/**
* @brief 解析 VAR_* … END_VAR 变量段
* @param b 输出 VarBlocksection + vars
* @return true 成功;false 语法错误(err 已写)
*/
bool parse_var_block(VarBlock* b) {
switch (type()) {
case Tok::VAR: b->section = VarSection::Local; break;
@@ -212,7 +343,12 @@ namespace {
return true;
}
// ident (',' ident)* ':' type (':=' literal)? ';' —— 展开成多个 VarDecl
/**
* @brief 解析一行声明:ident (',' ident)* ':' type (':=' literal)? ';'
* @details 同一行多个名字共享类型与初值,展开成多个 VarDecl
* @param out 输出 VarDecl 列表
* @return true 成功;false 语法错误(err 已写)
*/
bool parse_vars_line(std::vector<VarDecl>* out) {
std::vector<VarDecl> names;
while (true) {
@@ -258,7 +394,12 @@ namespace {
return true;
}
// 初值只允许字面量(BOOL / INT / TIME / TRUE / FALSE
/**
* @brief 解析初值字面量
* @details 只允许字面量(TRUE/FALSE/INT/TIME);表达式初值 v1 不做
* @param e 输出 ExprLitBool / LitInt / LitTime
* @return true 成功;false 非字面量(err 已写)
*/
bool parse_literal_expr(Expr* e) {
switch (type()) {
case Tok::TRUE:
@@ -286,7 +427,13 @@ namespace {
}
}
// 类型:BOOL | INT | TIME | TON | TOF | CTU | ident(用户 FB 类型)
/**
* @brief 解析类型名
* @details 内建标量 BOOL/INT/TIME、内建 FB TON/TOF/CTU、
* 或标识符(用户 FB 类型,存在性由 12.6 链接层校验)
* @param t 输出 TypeRef
* @return true 成功;false 语法错误(err 已写)
*/
bool parse_type(TypeRef* t) {
switch (type()) {
case Tok::BOOL:
@@ -333,6 +480,14 @@ namespace {
// ---- 语句 ----
/**
* @brief 语句分发
* @details IF / WHILE 关键字直入;IDENT 用一 token 前瞻区分:
* ident '(' → FB 调用(无返回值,语句形态);
* ident ('.' ident)? ':=' → 赋值
* @param s 输出 Stmt
* @return true 成功;false 语法错误(err 已写)
*/
bool parse_stmt(Stmt* s) {
if (check(Tok::IF)) {
return parse_if(s);
@@ -352,6 +507,12 @@ namespace {
return is_call ? parse_fb_call(s) : parse_assign(s);
}
/**
* @brief 解析赋值语句:ident ':=' expr ';'
* @details 左值仅标识符;ident.ident 左值(fb.field 赋值)v1 明确拒绝
* @param s 输出 Stmtkind=Assign
* @return true 成功;false 语法错误(err 已写)
*/
bool parse_assign(Stmt* s) {
s->kind = StmtKind::Assign;
if (!parse_ident(&s->target)) {
@@ -370,6 +531,13 @@ namespace {
return expect(Tok::SEMI, ";");
}
/**
* @brief 解析 FB 调用语句:ident '(' arg (',' arg)* ')' ';'
* @details 全部命名实参(位置实参 v1 不做);
* 字段名与类型由 12.6 / 12.7 校验
* @param s 输出 Stmtkind=FbCall
* @return true 成功;false 语法错误(err 已写)
*/
bool parse_fb_call(Stmt* s) {
s->kind = StmtKind::FbCall;
if (!parse_ident(&s->instance)) {
@@ -404,6 +572,11 @@ namespace {
return expect(Tok::SEMI, ";");
}
/**
* @brief 解析 IF 语句:IF cond THEN body (ELSIF cond THEN body)* (ELSE body)? END_IF
* @param s 输出 Stmtkind=If
* @return true 成功;false 语法错误(err 已写)
*/
bool parse_if(Stmt* s) {
s->kind = StmtKind::If;
advance(); // IF
@@ -441,6 +614,12 @@ namespace {
return expect(Tok::END_IF, "END_IF");
}
/**
* @brief 解析 WHILE 语句:WHILE cond DO body END_WHILE
* @details 终止性由 cycle_limit 保证,不做证明
* @param s 输出 Stmtkind=While
* @return true 成功;false 语法错误(err 已写)
*/
bool parse_while(Stmt* s) {
s->kind = StmtKind::While;
advance(); // WHILE
@@ -457,6 +636,13 @@ namespace {
return expect(Tok::END_WHILE, "END_WHILE");
}
/**
* @brief 解析语句体(连续语句)
* @details 遇 ELSIF / ELSE / END_IF / END_WHILE / END 停止;
* 停止 token 由调用方消费,这里不越界
* @param out 输出 Stmt 列表
* @return true 成功;false 语法错误(err 已写)
*/
bool parse_body(std::vector<Stmt>* out) {
while (!check(Tok::ELSIF) && !check(Tok::ELSE) && !check(Tok::END_IF) &&
!check(Tok::END_WHILE) && !check(Tok::END)) {
@@ -471,8 +657,20 @@ namespace {
// ---- 表达式(优先级爬升)----
/**
* @brief 表达式入口
* @details 优先级从低到高:or < and < not < cmp < add < mul < unary < primary。
* 每个二目层用「局部链 + 末尾 move」累积左结合树(避免自引用指针)。
* @param e 输出 Expr 根
* @return true 成功;false 语法错误(err 已写)
*/
bool parse_expr(Expr* e) { return parse_or(e); }
/**
* @brief OR 层:and_expr (OR and_expr)*(左结合)
* @param e 输出 Expr 根(kind=Or 链或透传下层)
* @return true 成功;false 语法错误(err 已写)
*/
bool parse_or(Expr* e) {
std::unique_ptr<Expr> lhs = std::make_unique<Expr>();
if (!parse_and(lhs.get())) {
@@ -481,7 +679,7 @@ namespace {
while (check(Tok::OR)) {
advance();
auto node = std::make_unique<Expr>();
node->kind = ExprKind::Or;
node->kind = ExprKind::Or; // 短路语义保留到 12.8 编跳转
node->lhs = std::move(lhs);
node->rhs = std::make_unique<Expr>();
if (!parse_and(node->rhs.get())) {
@@ -493,6 +691,11 @@ namespace {
return true;
}
/**
* @brief AND 层:not_expr (AND not_expr)*(左结合)
* @param e 输出 Expr 根(kind=And 链或透传下层)
* @return true 成功;false 语法错误(err 已写)
*/
bool parse_and(Expr* e) {
std::unique_ptr<Expr> lhs = std::make_unique<Expr>();
if (!parse_not(lhs.get())) {
@@ -501,7 +704,7 @@ namespace {
while (check(Tok::AND)) {
advance();
auto node = std::make_unique<Expr>();
node->kind = ExprKind::And;
node->kind = ExprKind::And; // 短路语义保留到 12.8 编跳转
node->lhs = std::move(lhs);
node->rhs = std::make_unique<Expr>();
if (!parse_not(node->rhs.get())) {
@@ -513,6 +716,12 @@ namespace {
return true;
}
/**
* @brief NOT 层:NOT not_expr | cmp_expr
* @details NOT 是前缀一元,可连写(NOT NOT x);比 AND/OR 紧
* @param e 输出 Expr 根(kind=Not 或透传 cmp
* @return true 成功;false 语法错误(err 已写)
*/
bool parse_not(Expr* e) {
if (check(Tok::NOT)) {
advance();
@@ -523,6 +732,12 @@ namespace {
return parse_cmp(e);
}
/**
* @brief 比较层:add_expr (比较符 add_expr)?
* @details 比较不连锁(a < b < c 拒绝);结果类型 BOOL 由 12.7 校验
* @param e 输出 Expr 根(kind=Cmp 或透传 add
* @return true 成功;false 语法错误(err 已写)
*/
bool parse_cmp(Expr* e) {
std::unique_ptr<Expr> lhs = std::make_unique<Expr>();
if (!parse_add(lhs.get())) {
@@ -537,7 +752,7 @@ namespace {
case Tok::GT: op = BinOp::Gt; break;
case Tok::GE: op = BinOp::Ge; break;
default:
*e = std::move(*lhs);
*e = std::move(*lhs); // 无比较符 → 原样上抛
return true;
}
advance();
@@ -545,6 +760,7 @@ namespace {
if (!parse_add(rhs.get())) {
return false;
}
// 右侧之后再出现比较符 → 链式,v1 拒绝
if (is_cmp_op(type())) {
return fail("chained comparison not supported");
}
@@ -555,11 +771,21 @@ namespace {
return true;
}
/**
* @brief 是否比较运算符 token= <> < <= > >=
* @param t token 类型
* @return true 是比较运算符
*/
bool is_cmp_op(Tok t) const {
return t == Tok::EQ || t == Tok::NE || t == Tok::LT || t == Tok::LE ||
t == Tok::GT || t == Tok::GE;
}
/**
* @brief 加减层:mul_expr (('+'|'-') mul_expr)*(左结合)
* @param e 输出 Expr 根(kind=Add/Sub 链或透传下层)
* @return true 成功;false 语法错误(err 已写)
*/
bool parse_add(Expr* e) {
std::unique_ptr<Expr> lhs = std::make_unique<Expr>();
if (!parse_mul(lhs.get())) {
@@ -588,6 +814,11 @@ namespace {
return true;
}
/**
* @brief 乘除层:unary (('*'|'/') unary)*(左结合)
* @param e 输出 Expr 根(kind=Mul/Div 链或透传下层)
* @return true 成功;false 语法错误(err 已写)
*/
bool parse_mul(Expr* e) {
std::unique_ptr<Expr> lhs = std::make_unique<Expr>();
if (!parse_unary(lhs.get())) {
@@ -616,6 +847,12 @@ namespace {
return true;
}
/**
* @brief 一元负号层:'-' unary | primary
* @details 可连写(- - x);注意 -5 不是字面量,而是 Neg(LitInt 5)
* @param e 输出 Expr 根(kind=Neg 或透传 primary
* @return true 成功;false 语法错误(err 已写)
*/
bool parse_unary(Expr* e) {
if (check(Tok::MINUS)) {
advance();
@@ -626,6 +863,13 @@ namespace {
return parse_primary(e);
}
/**
* @brief 原子:字面量 / 括号表达式 / ident
* @details ident 后的 '.' 或 '(' 决定形态(变量 | fb.field | 函数调用),
* 与语句层的区分逻辑一致
* @param e 输出 Expr
* @return true 成功;false 语法错误(err 已写)
*/
bool parse_primary(Expr* e) {
switch (type()) {
case Tok::INT_LIT:
@@ -662,7 +906,7 @@ namespace {
}
advance();
if (check(Tok::DOT)) {
// fb.field
// fb.field(字段存在性由 12.6 校验)
advance();
e->kind = ExprKind::Field;
e->name = word;
@@ -672,7 +916,7 @@ namespace {
return true;
}
if (check(Tok::LPAREN)) {
// 函数调用(FUNCTION,有返回值)
// 函数调用(FUNCTION,有返回值;与语句层 FB 调用区分
e->kind = ExprKind::Call;
e->name = word;
advance();
@@ -700,6 +944,12 @@ namespace {
}
}
/**
* @brief 解析标识符
* @details 必须是 IDENT 且非保留名
* @param out 输出小写标识符文本
* @return true 成功;false 语法错误(err 已写)
*/
bool parse_ident(std::string* out) {
if (!check(Tok::IDENT)) {
return fail("expected identifier");
@@ -714,15 +964,24 @@ namespace {
// ---- 成员 ----
std::string* err_;
std::vector<Token> tokens_;
Unit* out_;
size_t pos_ = 0;
bool ok_ = true;
std::string* err_; // 错误输出(可空)
std::vector<Token> tokens_; // 整段 token 流(构造时一次性 lex 完成)
Unit* out_; // 解析结果
size_t pos_ = 0; // 当前 token 下标
bool ok_ = true; // 词法阶段是否成功
};
} // namespace
/**
* @brief 对源文本做语法分析(对外入口)
* @details 成功时 out 填满 globals/pous;失败时 out 内容不保证(以返回值为准)
* @param source_file 源文件名(报错用)
* @param content 源文本
* @param out 输出 Unitglobals / pous,先清空)
* @param err 错误输出;可为 nullptr(静默)
* @return true 成功;false 失败(err 前缀 "syntax error"
*/
bool parse_pous(const std::string& source_file, const std::string& content,
Unit* out, std::string* err) {
out->globals.clear();
@@ -731,6 +990,13 @@ bool parse_pous(const std::string& source_file, const std::string& content,
return p.run();
}
/**
* @brief 读取文件后语法分析
* @param path 文件路径
* @param out 输出 Unitglobals / pous
* @param err 错误输出;可为 nullptr(静默)
* @return true 成功;false 失败(文件打不开也算 syntax error
*/
bool parse_pous_file(const std::string& path, Unit* out, std::string* err) {
std::ifstream in(path, std::ios::binary);
if (!in) {