三个 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
+146 -19
View File
@@ -3,6 +3,28 @@
* @brief ST 子集词法分析 * @brief ST 子集词法分析
* @author * @author
* @date 2026-08-21 * @date 2026-08-21
*
* @details 设计说明(详见 Doc/compiler/词法.md):
* - 主循环:跳过空白/注释 → 文件末尾推 END → 否则 lex_one 识别一个 token
* - 大小写:关键字匹配与标识符存储统一折小写(与 IEC 一致)
* - 行列:line/col 从 1 起;token 记录【起始】位置(消费会推进行列,
* 故 lex_one 入口先捕获 tok_line_/tok_col_
* - 错误:稳定前缀 "lex error" + 文件 + 行列,供测试按类别匹配
*
* 函数清单:
* - time_unit_ms TIME 字面量单位 → 毫秒倍数(未知返回 -1)
* - Lexer::Lexer (构造)存源文本/文件名/输出指针,行列初始 (1,1)
* - Lexer::run 主循环:跳空白注释 → 文件尾推 END → lex_one
* - Lexer::fail 组装 "lex error: <msg> (file, line, col)" 返回 false
* - Lexer::cur 当前字符(不消费)
* - Lexer::at_end 是否已到源文末尾
* - Lexer::advance 消费一个字符并推进行列(\n 行+1 列复位)
* - Lexer::skip_space_and_comments 跳过空白与 (* *) 注释(不嵌套,未闭合报错)
* - Lexer::push 用 token 起始位置产出 token 入流
* - Lexer::lex_one 识别一个 token(T# → 标识符/关键字 → 整数 → 符号 → 报错)
* - Lexer::lex_time 解析 T#<int><单位>... 段式 TIME 字面量,累加毫秒
* - lex 对外入口:清空输出 → 构造 Lexer → run
* - lex_file 读文件后调 lex;打不开算 lex error
*/ */
#include "compiler/Lexer.h" #include "compiler/Lexer.h"
@@ -15,61 +37,91 @@
namespace compiler { namespace compiler {
namespace { namespace {
// 25 个关键字:小写键 → Tok(大小写不敏感,见 Doc/compiler/词法.md // 关键字:小写键 → Tok
// 与 Doc/compiler/词法.md 的冻结表一致(12.5 修订补入 then/do);
// 数值即 token 类型,只追加不删改。
const struct { const struct {
const char* key; const char* key;
Tok tok; Tok tok;
} kKeywords[] = { } kKeywords[] = {
// POU 外壳
{"program", Tok::PROGRAM}, {"program", Tok::PROGRAM},
{"function", Tok::FUNCTION}, {"function", Tok::FUNCTION},
{"function_block", Tok::FUNCTION_BLOCK}, {"function_block", Tok::FUNCTION_BLOCK},
{"end_program", Tok::END_PROGRAM}, {"end_program", Tok::END_PROGRAM},
{"end_function", Tok::END_FUNCTION}, {"end_function", Tok::END_FUNCTION},
{"end_function_block", Tok::END_FUNCTION_BLOCK}, {"end_function_block", Tok::END_FUNCTION_BLOCK},
// 变量段
{"var", Tok::VAR}, {"var", Tok::VAR},
{"var_input", Tok::VAR_INPUT}, {"var_input", Tok::VAR_INPUT},
{"var_output", Tok::VAR_OUTPUT}, {"var_output", Tok::VAR_OUTPUT},
{"var_global", Tok::VAR_GLOBAL}, {"var_global", Tok::VAR_GLOBAL},
{"var_external", Tok::VAR_EXTERNAL}, {"var_external", Tok::VAR_EXTERNAL},
{"end_var", Tok::END_VAR}, {"end_var", Tok::END_VAR},
// 类型
{"bool", Tok::BOOL}, {"bool", Tok::BOOL},
{"int", Tok::INT}, {"int", Tok::INT},
{"time", Tok::TIME}, {"time", Tok::TIME},
// 控制
{"if", Tok::IF}, {"if", Tok::IF},
{"elsif", Tok::ELSIF}, {"elsif", Tok::ELSIF},
{"else", Tok::ELSE}, {"else", Tok::ELSE},
{"end_if", Tok::END_IF}, {"end_if", Tok::END_IF},
{"while", Tok::WHILE}, {"while", Tok::WHILE},
{"end_while", Tok::END_WHILE}, {"end_while", Tok::END_WHILE},
{"then", Tok::THEN}, {"then", Tok::THEN}, // 12.5 修订:IF…THEN 语法需要
{"do", Tok::DO}, {"do", Tok::DO}, // 12.5 修订:WHILE…DO 语法需要
// 逻辑
{"and", Tok::AND}, {"and", Tok::AND},
{"or", Tok::OR}, {"or", Tok::OR},
{"not", Tok::NOT}, {"not", Tok::NOT},
// 字面量
{"true", Tok::TRUE}, {"true", Tok::TRUE},
{"false", Tok::FALSE}, {"false", Tok::FALSE},
// 内建 FB 类型名(v1 冻结三件套)
{"ton", Tok::TON}, {"ton", Tok::TON},
{"tof", Tok::TOF}, {"tof", Tok::TOF},
{"ctu", Tok::CTU}, {"ctu", Tok::CTU},
}; };
// TIME 字面量单位 → 毫秒倍数(不敏感) /**
* @brief TIME 字面量单位换算为毫秒倍数
* @param unit 单位字符串(小写):ms / s / m / h / d
* @return 对应毫秒倍数;未知单位返回 -1
*/
int64_t time_unit_ms(const std::string& unit) { int64_t time_unit_ms(const std::string& unit) {
if (unit == "ms") return 1; if (unit == "ms") return 1;
if (unit == "s") return 1000; if (unit == "s") return 1000;
if (unit == "m") return 60000; if (unit == "m") return 60000;
if (unit == "h") return 3600000; if (unit == "h") return 3600000;
if (unit == "d") return 86400000; if (unit == "d") return 86400000;
return -1; return -1; // 未知单位
} }
/**
* @brief ST 单遍扫描器
*
* @details pos_ 指向当前字符,line_/col_ 是它所在的 1 起行列。
* 所有成员函数不抛异常,出错即返回 false 并写 err_。
*/
class Lexer { class Lexer {
public: public:
/**
* @brief 构造扫描器
* @param source_file 源文件名(报错与 token 的 source_file 用)
* @param content 源文本(引用外部存储,不拷贝)
* @param out token 流输出(run 前应为空)
* @param err 错误输出;可为 nullptr(静默)
*/
Lexer(const std::string& source_file, const std::string& content, Lexer(const std::string& source_file, const std::string& content,
std::vector<Token>* out, std::string* err) std::vector<Token>* out, std::string* err)
: src_(content), sf_(source_file), out_(out), err_(err), : src_(content), sf_(source_file), out_(out), err_(err),
pos_(0), line_(1), col_(1) {} pos_(0), line_(1), col_(1) {}
/**
* @brief 主循环:跳空白/注释 → 文件尾推 END → lex_one
* @return true 成功(out 以 END 收尾);false 词法错误(err 已写)
*/
bool run() { bool run() {
while (true) { while (true) {
if (!skip_space_and_comments()) { if (!skip_space_and_comments()) {
@@ -86,6 +138,11 @@ namespace {
} }
private: private:
/**
* @brief 组装 "lex error: <msg> (file, line, col)" 写入 err
* @param msg 错误描述(不含前缀与位置)
* @return 恒 false(便于 return fail(...) 一行退出)
*/
bool fail(const std::string& msg) { bool fail(const std::string& msg) {
if (err_) { if (err_) {
char buf[128]; char buf[128];
@@ -96,9 +153,24 @@ namespace {
return false; return false;
} }
/**
* @brief 当前字符(不消费)
* @return src_[pos_];调用前需保证 !at_end()
*/
char cur() const { return src_[pos_]; } char cur() const { return src_[pos_]; }
/**
* @brief 是否已到源文末尾
* @return pos_ >= src_.size()
*/
bool at_end() const { return pos_ >= src_.size(); } bool at_end() const { return pos_ >= src_.size(); }
/**
* @brief 消费一个字符并推进行列
*
* @details '\n' → 行 +1、列复位 1;其他字符 → 列 +1。
* 注意:token 的起始行列要在此前捕获(见 lex_one)。
*/
void advance() { void advance() {
if (cur() == '\n') { if (cur() == '\n') {
++line_; ++line_;
@@ -109,7 +181,13 @@ namespace {
++pos_; ++pos_;
} }
// 跳过空白与 (* *) 注释(不嵌套);未闭合返回 false /**
* @brief 跳过空白与 (* *) 注释
*
* @details 第一版注释不嵌套:遇到第一个 "*) 即结束
* (IEC 允许嵌套,留待以后)。未闭合 → lex error。
* @return true 正常;false 未闭合注释(err 已写)
*/
bool skip_space_and_comments() { bool skip_space_and_comments() {
while (!at_end()) { while (!at_end()) {
if (std::isspace(static_cast<unsigned char>(cur()))) { if (std::isspace(static_cast<unsigned char>(cur()))) {
@@ -139,6 +217,15 @@ namespace {
return true; return true;
} }
/**
* @brief 产出 token 入流
*
* @details 位置用 tok_line_/tok_col_(本 token 起始位置,
* 而非 push 时刻的位置——此时消费已推进了行列)。
* @param type token 类型
* @param text 文本(标识符/关键字的小写形;字面量原文)
* @param value 字面量值(INT_LIT / TIME_LIT 毫秒;其他为 0
*/
void push(Tok type, const std::string& text, int64_t value) { void push(Tok type, const std::string& text, int64_t value) {
Token t; Token t;
t.type = type; t.type = type;
@@ -150,6 +237,17 @@ namespace {
out_->push_back(t); out_->push_back(t);
} }
/**
* @brief 识别一个 token(当前字符开始)
*
* @details 分支顺序有讲究:
* 1. T#(TIME 字面量)必须优先于标识符——'T' 是字母,否则会被
* 标识符分支吃掉;"timer" 这类普通词不受影响(只匹配 T#)。
* 2. 标识符/关键字(含下划线,字母/数字/下划线组成)。
* 3. 整数(纯数字;负数不是字面量,-5 是 MINUS + 5)。
* 4. 符号::= <> <= >= 最长匹配优先。
* @return true 成功(已消费并 push);false 非法字符(err 已写)
*/
bool lex_one() { bool lex_one() {
// 记录本 token 起始位置(push 时消费已经推进了行列) // 记录本 token 起始位置(push 时消费已经推进了行列)
tok_line_ = line_; tok_line_ = line_;
@@ -161,7 +259,8 @@ namespace {
return lex_time(); return lex_time();
} }
// 标识符 / 关键字统一折小写 // 标识符 / 关键字统一折小写存入 text(内部形),
// 关键字命中即分类,否则 IDENT(用户定义名 / 内建类型名都走这里)。
if (std::isalpha(static_cast<unsigned char>(c)) || c == '_') { if (std::isalpha(static_cast<unsigned char>(c)) || c == '_') {
const uint32_t start = pos_; const uint32_t start = pos_;
while (!at_end() && while (!at_end() &&
@@ -182,7 +281,8 @@ namespace {
return true; return true;
} }
// 整数 // 整数:十进制 [0-9]+int64_t 存储;
// 溢出检查在累加前做(v 上限 INT64_MAX)。
if (std::isdigit(static_cast<unsigned char>(c))) { if (std::isdigit(static_cast<unsigned char>(c))) {
const uint32_t start = pos_; const uint32_t start = pos_;
while (!at_end() && std::isdigit(static_cast<unsigned char>(cur()))) { while (!at_end() && std::isdigit(static_cast<unsigned char>(cur()))) {
@@ -201,7 +301,8 @@ namespace {
return true; return true;
} }
// 符号(最长匹配优先) // 符号:两字符符(:= <> <= >=)优先于单字符;
// 其余单字符各归一类,非法字符直接报错。
switch (c) { switch (c) {
case ':': case ':':
if (pos_ + 1 < src_.size() && src_[pos_ + 1] == '=') { if (pos_ + 1 < src_.size() && src_[pos_ + 1] == '=') {
@@ -282,7 +383,14 @@ namespace {
} }
} }
// T#<int><unit>... 段式解析,累加毫秒 /**
* @brief 解析 T#<整数><单位> 段式 TIME 字面量
*
* @details 段可多个,每段 = 数字 + 单位(ms/s/m/h/d,大小写不敏感):
* T#10ms → 10T#1s250ms → 1250T#2h → 7200000。
* @return true 成功(push TIME_LITint_value 为毫秒);
* false 缺段 / 未知单位 / 数字溢出(err 已写)
*/
bool lex_time() { bool lex_time() {
const uint32_t start = pos_; const uint32_t start = pos_;
advance(); // T advance(); // T
@@ -319,19 +427,31 @@ namespace {
return true; return true;
} }
const std::string& src_; // ---- 状态 ----
const std::string& sf_; const std::string& src_; // 源文本(外部所有,不拷贝)
std::vector<Token>* out_; const std::string& sf_; // 源文件名(报错用)
std::string* err_; std::vector<Token>* out_; // token 流输出
size_t pos_; std::string* err_; // 错误输出(可空)
uint32_t line_; size_t pos_; // 当前字符下标
uint32_t col_; uint32_t line_; // 当前行(1 起)
uint32_t tok_line_ = 1; uint32_t col_; // 当前列(1 起)
uint32_t tok_col_ = 1; uint32_t tok_line_ = 1; // 本 token 起始行(push 时用)
uint32_t tok_col_ = 1; // 本 token 起始列(push 时用)
}; };
} // namespace } // namespace
/**
* @brief 对源文本做词法分析(对外入口)
*
* @details 成功时 out 以 END token 结尾;失败时 out 内容不保证
* (调用方以返回值为准)。
* @param source_file 源文件名(写入每个 token 的 source_file
* @param content 源文本
* @param out 输出 token 流(先清空)
* @param err 错误输出;可为 nullptr(静默)
* @return true 成功;false 失败(err 前缀 "lex error"
*/
bool lex(const std::string& source_file, const std::string& content, bool lex(const std::string& source_file, const std::string& content,
std::vector<Token>* out, std::string* err) { std::vector<Token>* out, std::string* err) {
out->clear(); out->clear();
@@ -339,6 +459,13 @@ bool lex(const std::string& source_file, const std::string& content,
return l.run(); return l.run();
} }
/**
* @brief 读取文件后词法分析
* @param path 文件路径
* @param out 输出 token 流
* @param err 错误输出;可为 nullptr(静默)
* @return true 成功;false 失败(文件打不开也算 lex error)
*/
bool lex_file(const std::string& path, std::vector<Token>* out, std::string* err) { bool lex_file(const std::string& path, std::vector<Token>* out, std::string* err) {
std::ifstream in(path, std::ios::binary); std::ifstream in(path, std::ios::binary);
if (!in) { if (!in) {
+201 -6
View File
@@ -3,6 +3,38 @@
* @brief 符号表与链接 * @brief 符号表与链接
* @author * @author
* @date 2026-08-21 * @date 2026-08-21
*
* @details 设计说明(详见 Doc/compiler/符号表与链接.md):
* - 流程:收集导出(gvl 校验/全局定槽/POU 登记)→ 用户 FB 类型布局
* → 逐 POU 局部符号与引用解析 → 函数调用图环检测 → io 校验 → 入口校验
* - 数据区定址:v1 单一数据区,全局(含 I/Q/M)按声明顺序编槽号
* - 错误:稳定前缀 "link error" + 文件(+ 行列可选)
*
* 函数清单:
* - kind_name 符号种类 → 文本名(global/external/local/…)
* - type_str 类型引用 → 文本名(bool/int/time/fb 名)
* - is_scalar 是否为标量类型(BOOL/INT/TIME
* - builtin_layout 内建 FB 名 → 冻结布局(ton/tof/ctu),未知返回 nullptr
* - Linker::Linker (构造)存工程/源文件/输出指针,规范化 gvl 路径
* - Linker::run 链接主流程(6 步,任一步失败即停)
* - Linker::fail 组装 "link error: <msg> (file[, line, col])" 返回 false
* - Linker::is_gvl_file 路径是否工程指定的 gvl 文件(规范化比较)
* - collect_exports 收集全局(仅 gvl 顶层)与 POU 导出,重复名拒绝
* - collect_fb_types 收集用户 FUNCTION_BLOCK 的字段布局
* - resolve_scopes 逐 POU 建局部符号并解析语句引用
* - scope_of 按 POU 名取作用域(找不到返回静态空作用域)
* - build_scope_syms 建一个 POU 的局部符号(含 EXTERNAL 接槽 / FB 实例布局)
* - find_sym 在 POU 局部符号里按名查找
* - check_stmt 校验一条语句(赋值/FB 调用/IF/WHILE 递归)
* - check_expr 校验一个表达式(变量/字段/函数调用/递归子树)
* - find_function 按名查 POU 表并回传种类
* - check_cycles 函数依赖图 DFS 环检测入口
* - dfs 单个函数的环检测(visiting/done 三色标记)
* - check_io io 绑定必须已在 GVL(大小写折叠比较)
* - has_program_main 必须存在 PROGRAM main(入口)
* - link_project 对外入口:清空输出 → 构造 Linker → run
* - load_unit 读取并解析一个 .st 成 SourceUnit
* - dump_symbols 符号表文本:每行 "name kind type address file"
*/ */
#include "compiler/Linker.h" #include "compiler/Linker.h"
@@ -19,6 +51,12 @@
namespace compiler { namespace compiler {
namespace { namespace {
/**
* @brief 符号种类 → 文本名(dump_symbols 用)
* @param k 符号种类
* @return "global" / "external" / "local" / "input" / "output" /
* "pou" / "fb_instance" / "const";未知返回 "?"
*/
const char* kind_name(SymbolKind k) { const char* kind_name(SymbolKind k) {
switch (k) { switch (k) {
case SymbolKind::Global: return "global"; case SymbolKind::Global: return "global";
@@ -33,6 +71,11 @@ namespace {
return "?"; return "?";
} }
/**
* @brief 类型引用 → 文本名(符号表 / 类型比较用)
* @param t 类型引用
* @return "bool" / "int" / "time" / FB 类型名(小写);未知返回 "?"
*/
std::string type_str(const TypeRef& t) { std::string type_str(const TypeRef& t) {
switch (t.kind) { switch (t.kind) {
case TypeKind::Bool: return "bool"; case TypeKind::Bool: return "bool";
@@ -44,6 +87,11 @@ namespace {
return "?"; return "?";
} }
/**
* @brief 是否为标量类型(BOOL / INT / TIME
* @param k 类型种类
* @return true 标量(FB 字段只允许标量,见 collect_fb_types
*/
bool is_scalar(TypeKind k) { bool is_scalar(TypeKind k) {
return k == TypeKind::Bool || k == TypeKind::Int || k == TypeKind::Time; return k == TypeKind::Bool || k == TypeKind::Int || k == TypeKind::Time;
} }
@@ -57,6 +105,11 @@ namespace {
{"pv", TypeKind::Int}, {"q", TypeKind::Bool}, {"pv", TypeKind::Int}, {"q", TypeKind::Bool},
{"cv", TypeKind::Int}}}; {"cv", TypeKind::Int}}};
/**
* @brief 内建 FB 名 → 冻结布局
* @param name FB 类型名(小写)
* @return 布局指针(ton / tof / ctu);未知名字返回 nullptr
*/
const FbLayout* builtin_layout(const std::string& name) { const FbLayout* builtin_layout(const std::string& name) {
if (name == "ton") return &kTonLayout; if (name == "ton") return &kTonLayout;
if (name == "tof") return &kTofLayout; if (name == "tof") return &kTofLayout;
@@ -64,15 +117,34 @@ namespace {
return nullptr; return nullptr;
} }
/**
* @brief 链接器
*
* @details 六步流程见 run()。错误即停(首个错误优先,不做恢复)。
*/
class Linker { class Linker {
public: public:
/**
* @brief 构造链接器
* @param proj 工程定义(toml 解析结果;io 绑定 / gvl 文件 / base_dir
* @param units 全部源文件的 ASTcompile_files 集合逐一 load_unit
* @param out 链接结果(调用方先构造空 LinkResult
* @param err 错误输出;可为 nullptr(静默)
*/
Linker(const Project& proj, const std::vector<SourceUnit>& units, Linker(const Project& proj, const std::vector<SourceUnit>& units,
LinkResult* out, std::string* err) LinkResult* out, std::string* err)
: proj_(proj), units_(units), out_(out), err_(err) { : proj_(proj), units_(units), out_(out), err_(err) {
// gvl 路径规范化一次,供 is_gvl_file 比较(容忍相对/绝对写法差异)
gvl_path_ = std::filesystem::weakly_canonical( gvl_path_ = std::filesystem::weakly_canonical(
std::filesystem::path(proj.base_dir) / proj.gvl_file); std::filesystem::path(proj.base_dir) / proj.gvl_file);
} }
/**
* @brief 链接主流程(6 步)
* @details 1. 收集导出 → 2. 用户 FB 类型布局 → 3. 逐 POU 符号与引用
* → 4. 循环调用检测 → 5. io 校验 → 6. 入口校验
* @return true 链接成功;false 任一步失败(err 已写)
*/
bool run() { bool run() {
if (!collect_exports()) return false; if (!collect_exports()) return false;
if (!collect_fb_types()) return false; if (!collect_fb_types()) return false;
@@ -86,6 +158,14 @@ namespace {
} }
private: private:
/**
* @brief 组装 "link error: <msg> (file[, line, col])" 写入 err
* @param file 出错文件(可为空串,用于无位置错误)
* @param msg 错误描述(不含前缀与位置)
* @param line 行列(1 起);0 表示不带行列只带文件
* @param col 列号
* @return 恒 false(便于 return fail(...) 一行退出)
*/
bool fail(const std::string& file, const std::string& msg, bool fail(const std::string& file, const std::string& msg,
uint32_t line, uint32_t col) { uint32_t line, uint32_t col) {
if (err_) { if (err_) {
@@ -101,6 +181,12 @@ namespace {
return false; return false;
} }
/**
* @brief 路径是否工程指定的 gvl 文件
* @param path 待比较路径
* @return true 是 gvl 文件(规范化后与 gvl_path_ 相同);
* 工程未配置 gvl 时恒 false
*/
bool is_gvl_file(const std::string& path) const { bool is_gvl_file(const std::string& path) const {
if (proj_.gvl_file.empty()) { if (proj_.gvl_file.empty()) {
return false; return false;
@@ -110,6 +196,11 @@ namespace {
// ---- 1. 收集导出 ---- // ---- 1. 收集导出 ----
/**
* @brief 收集导出:顶层全局(仅 gvl 文件)与 POU 登记
* @details 全局按声明顺序编槽号(下标即槽号);同名全局 / 同名 POU 拒绝
* @return true 成功;falseerr 已写)
*/
bool collect_exports() { bool collect_exports() {
for (const SourceUnit& u : units_) { for (const SourceUnit& u : units_) {
const bool is_gvl = is_gvl_file(u.path); const bool is_gvl = is_gvl_file(u.path);
@@ -157,6 +248,12 @@ namespace {
// ---- 2. 用户 FB 类型布局 ---- // ---- 2. 用户 FB 类型布局 ----
/**
* @brief 收集用户 FUNCTION_BLOCK 的字段布局
* @details 字段 = input / output / 内部 var(段序);
* external 与 global 不是字段;字段类型仅允许标量
* @return true 成功;falseFB 字段类型非法,err 已写)
*/
bool collect_fb_types() { bool collect_fb_types() {
for (const SourceUnit& u : units_) { for (const SourceUnit& u : units_) {
for (const POU& p : u.ast.pous) { for (const POU& p : u.ast.pous) {
@@ -192,6 +289,10 @@ namespace {
// ---- 3. 逐 POU 建局部符号并解析引用 ---- // ---- 3. 逐 POU 建局部符号并解析引用 ----
/**
* @brief 逐 POU 建局部符号并解析语句引用
* @return true 成功;falseerr 已写)
*/
bool resolve_scopes() { bool resolve_scopes() {
for (SourceUnit& u : const_cast<std::vector<SourceUnit>&>(units_)) { for (SourceUnit& u : const_cast<std::vector<SourceUnit>&>(units_)) {
for (const POU& p : u.ast.pous) { for (const POU& p : u.ast.pous) {
@@ -209,6 +310,11 @@ namespace {
return true; return true;
} }
/**
* @brief 按 POU 名取作用域
* @param name POU 名(小写)
* @return 对应 PouScope 引用;找不到返回静态空作用域(不应发生)
*/
LinkResult::PouScope& scope_of(const std::string& name) { LinkResult::PouScope& scope_of(const std::string& name) {
for (LinkResult::PouScope& sc : out_->scopes) { for (LinkResult::PouScope& sc : out_->scopes) {
if (sc.name == name) { if (sc.name == name) {
@@ -219,6 +325,16 @@ namespace {
return kEmpty; return kEmpty;
} }
/**
* @brief 建一个 POU 的局部符号
* @details VAR/INPUT/OUTPUT → 对应种类;VAR_EXTERNAL → 查全局表接同一槽
* (类型必须一致);FB 类型 → FbInstance + 布局(内建取冻结表,
* 用户类型查 fb_types);POU 内 VAR_GLOBAL → 拒绝
* @param file 出错文件
* @param p POU AST
* @param sc 输出作用域(syms / fb_instances 被填充)
* @return true 成功;falseerr 已写)
*/
bool build_scope_syms(const std::string& file, const POU& p, bool build_scope_syms(const std::string& file, const POU& p,
LinkResult::PouScope& sc) { LinkResult::PouScope& sc) {
for (const VarBlock& b : p.blocks) { for (const VarBlock& b : p.blocks) {
@@ -294,6 +410,12 @@ namespace {
// ---- 引用解析 ---- // ---- 引用解析 ----
/**
* @brief 在 POU 局部符号里按名查找
* @param sc 作用域
* @param name 符号名(小写)
* @return 符号指针;未找到返回 nullptr
*/
const Symbol* find_sym(const LinkResult::PouScope& sc, const Symbol* find_sym(const LinkResult::PouScope& sc,
const std::string& name) const { const std::string& name) const {
for (const Symbol& s : sc.syms) { for (const Symbol& s : sc.syms) {
@@ -304,6 +426,17 @@ namespace {
return nullptr; return nullptr;
} }
/**
* @brief 校验一条语句
* @details Assign:左值必须已声明(FUNCTION 内函数名赋值除外);
* FbCall:实例必须存在且是 FB,命名实参必须是该实例的字段;
* If / While:递归校验条件与各分支体
* @param file 出错文件
* @param sc 当前 POU 作用域
* @param pou 当前 POU(函数名赋值判定用)
* @param st 语句 AST
* @return true 合法;falseerr 已写)
*/
bool check_stmt(const std::string& file, const LinkResult::PouScope& sc, bool check_stmt(const std::string& file, const LinkResult::PouScope& sc,
const POU& pou, const Stmt& st) { const POU& pou, const Stmt& st) {
switch (st.kind) { switch (st.kind) {
@@ -379,6 +512,16 @@ namespace {
return true; return true;
} }
/**
* @brief 校验一个表达式
* @details VarRef:局部或全局表查得到;Field:对象必须是 FB 实例
* 且字段存在;Call:目标必须存在且是 FUNCTION(记调用边);
* 其他节点递归校验子树
* @param file 出错文件
* @param sc 当前 POU 作用域
* @param e 表达式 AST
* @return true 合法;falseerr 已写)
*/
bool check_expr(const std::string& file, const LinkResult::PouScope& sc, bool check_expr(const std::string& file, const LinkResult::PouScope& sc,
const Expr& e) { const Expr& e) {
switch (e.kind) { switch (e.kind) {
@@ -431,6 +574,12 @@ namespace {
return true; return true;
} }
/**
* @brief 按名查 POU 表并回传种类
* @param name POU 名(小写)
* @param kind 输出该 POU 的种类(PROGRAM / FUNCTION / FUNCTION_BLOCK
* @return true 找到;false 未找到
*/
bool find_function(const std::string& name, PouKind* kind) const { bool find_function(const std::string& name, PouKind* kind) const {
for (const LinkResult::PouScope& sc : out_->scopes) { for (const LinkResult::PouScope& sc : out_->scopes) {
if (sc.name == name) { if (sc.name == name) {
@@ -443,6 +592,11 @@ namespace {
// ---- 4. 循环调用检测(函数依赖图 DFS)---- // ---- 4. 循环调用检测(函数依赖图 DFS)----
/**
* @brief 函数依赖图环检测入口
* @details 对每个有出边的函数做 DFS;任一条路径回到栈上节点即环
* @return true 无环;false 发现环(err 已写)
*/
bool check_cycles() { bool check_cycles() {
std::set<std::string> visiting; std::set<std::string> visiting;
std::set<std::string> done; std::set<std::string> done;
@@ -454,6 +608,13 @@ namespace {
return true; return true;
} }
/**
* @brief 单个函数的环检测(三色标记 DFS)
* @param fn 当前函数名
* @param visiting 栈上节点(灰色,正在展开)
* @param done 已确认无环的节点(黑色)
* @return true 无环;false 命中灰色节点 = 环(err 已写)
*/
bool dfs(const std::string& fn, std::set<std::string>& visiting, bool dfs(const std::string& fn, std::set<std::string>& visiting,
std::set<std::string>& done) { std::set<std::string>& done) {
if (done.count(fn)) { if (done.count(fn)) {
@@ -478,6 +639,12 @@ namespace {
// ---- 5. io 校验 ---- // ---- 5. io 校验 ----
/**
* @brief io 绑定必须已在 GVL 声明
* @details 绑定名来自 toml(原始大小写),与全局表比较前折小写;
* io 不创造变量,只做 channel/bit → 槽号映射
* @return true 全部绑定合法;falseerr 已写)
*/
bool check_io() { bool check_io() {
for (const isa::IoBinding& b : proj_.io) { for (const isa::IoBinding& b : proj_.io) {
std::string key = b.var; std::string key = b.var;
@@ -494,6 +661,10 @@ namespace {
// ---- 6. 入口 ---- // ---- 6. 入口 ----
/**
* @brief 必须存在 PROGRAM main(与 toml entry = "program MAIN" 呼应)
* @return true 存在
*/
bool has_program_main() const { bool has_program_main() const {
for (const LinkResult::PouScope& sc : out_->scopes) { for (const LinkResult::PouScope& sc : out_->scopes) {
if (sc.kind == PouKind::Program && sc.name == "main") { if (sc.kind == PouKind::Program && sc.name == "main") {
@@ -503,16 +674,27 @@ namespace {
return false; return false;
} }
const Project& proj_; // ---- 成员 ----
const std::vector<SourceUnit>& units_; const Project& proj_; // 工程定义(toml
LinkResult* out_; const std::vector<SourceUnit>& units_; // 全部源文件 AST
std::string* err_; LinkResult* out_; // 链接结果
std::filesystem::path gvl_path_; std::string* err_; // 错误输出(可空)
std::map<std::string, std::set<std::string>> call_edges_; std::filesystem::path gvl_path_; // 规范化后的 gvl 路径
std::map<std::string, std::set<std::string>> call_edges_; // 调用者 → 被调函数集
}; };
} // namespace } // namespace
/**
* @brief 链接一个工程(对外入口)
* @details 校验 GVL、定全局槽、解析 VAR_EXTERNAL、FB 实例与字段、
* 函数循环调用检测、io 校验、入口校验
* @param proj 工程定义(toml 解析结果)
* @param units 全部源文件的 ASTcompile_files 集合逐一 load_unit 得到)
* @param out 链接结果(各字段先清空后填充)
* @param err 错误输出;可为 nullptr(静默)
* @return true 成功;false 失败(err 前缀 "link error"
*/
bool link_project(const Project& proj, const std::vector<SourceUnit>& units, bool link_project(const Project& proj, const std::vector<SourceUnit>& units,
LinkResult* out, std::string* err) { LinkResult* out, std::string* err) {
out->globals.clear(); out->globals.clear();
@@ -524,11 +706,24 @@ bool link_project(const Project& proj, const std::vector<SourceUnit>& units,
return l.run(); return l.run();
} }
/**
* @brief 读取并解析一个 .st 文件成 SourceUnit
* @param path 文件路径
* @param out 输出 SourceUnitpath + ast
* @param err 错误输出;可为 nullptr(静默)
* @return true 成功;false 失败(文件打不开 / 语法错误,透传 lex/syntax error
*/
bool load_unit(const std::string& path, SourceUnit* out, std::string* err) { bool load_unit(const std::string& path, SourceUnit* out, std::string* err) {
out->path = path; out->path = path;
return parse_pous_file(path, &out->ast, err); return parse_pous_file(path, &out->ast, err);
} }
/**
* @brief 符号表文本(完成标准:能打印 name kind type address file
* @param r 链接结果
* @return 多行文本:全局表逐行 "name kind type address file"
* 每个 POU 以 "[名字]" 开头后接其局部符号行
*/
std::string dump_symbols(const LinkResult& r) { std::string dump_symbols(const LinkResult& r) {
std::string out; std::string out;
for (const Symbol& s : r.globals) { for (const Symbol& s : r.globals) {
+280 -14
View File
@@ -3,6 +3,47 @@
* @brief ST 子集递归下降语法分析 * @brief ST 子集递归下降语法分析
* @author * @author
* @date 2026-08-21 * @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" #include "compiler/Parser.h"
@@ -16,12 +57,19 @@
namespace compiler { namespace compiler {
namespace { namespace {
// 明确拒绝的保留名(小写命中即报错,见 Doc/compiler/语法.md // 明确拒绝的保留名(小写命中即报错,见 Doc/compiler/语法.md
// 这些词在词法层不是关键字(lex 成 IDENT),必须在语法层显式拦截,
// 避免 VAR_IN_OUT / REF / CLASS 等被静默当作用户标识符。
const char* const kForbidden[] = { const char* const kForbidden[] = {
"var_in_out", "var_temp", "ref", "class", "any", "var_in_out", "var_temp", "ref", "class", "any",
"pointer", "interface", "method", "pointer", "interface", "method",
}; };
/**
* @brief 判断单词是否命中保留名清单
* @param word 小写单词
* @return true 命中(var_in_out / ref / class / any / pointer / interface / method
*/
bool is_forbidden(const std::string& word) { bool is_forbidden(const std::string& word) {
for (const char* f : kForbidden) { for (const char* f : kForbidden) {
if (word == f) { if (word == f) {
@@ -31,8 +79,21 @@ namespace {
return false; return false;
} }
/**
* @brief ST 递归下降解析器
*
* @details 两阶段:构造时一次性 lex 成 token 流,再逐层解析;
* 不做流式交互。token 流只前移不回溯。
*/
class Parser { class Parser {
public: 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, Parser(const std::string& source_file, const std::string& content,
Unit* out, std::string* err) Unit* out, std::string* err)
: err_(err), out_(out) { : err_(err), out_(out) {
@@ -45,6 +106,13 @@ namespace {
pos_ = 0; pos_ = 0;
} }
/**
* @brief 顶层循环:解析直至 END
*
* @details 每轮要么是 GVL 形态的顶层 VAR_GLOBAL 段(无 POU 的文件),
* 要么是一个 POU。
* @return true 成功(out 填满);false 语法错误(err 已写)
*/
bool run() { bool run() {
if (!ok_) { if (!ok_) {
return false; return false;
@@ -74,6 +142,12 @@ namespace {
private: private:
// ---- 错误 ---- // ---- 错误 ----
/**
* @brief 组装 "syntax error: <msg> (file, line, col)" 写入 err
* @details 位置取当前 token(指向出错处,不回溯到行首)
* @param msg 错误描述(不含前缀与位置)
* @return 恒 false(便于 return fail(...) 一行退出)
*/
bool fail(const std::string& msg) { bool fail(const std::string& msg) {
if (err_) { if (err_) {
const Token& t = cur(); const Token& t = cur();
@@ -87,10 +161,30 @@ namespace {
// ---- token 视图 ---- // ---- token 视图 ----
/**
* @brief 当前 token(不消费)
* @return tokens_[pos_] 的引用
*/
const Token& cur() const { return tokens_[pos_]; } const Token& cur() const { return tokens_[pos_]; }
/**
* @brief 当前 token 的类型
* @return cur().type
*/
Tok type() const { return cur().type; } Tok type() const { return cur().type; }
/**
* @brief 当前 token 是否为指定类型
* @param t 期望的 token 类型
* @return true 匹配
*/
bool check(Tok t) const { return type() == t; } bool check(Tok t) const { return type() == t; }
/**
* @brief 前进一个 token
* @details END 处原地不动(pos_ 永不越界)
* @return 前进前的 token 引用
*/
const Token& advance() { const Token& advance() {
const Token& t = tokens_[pos_]; const Token& t = tokens_[pos_];
if (pos_ + 1 < tokens_.size()) { if (pos_ + 1 < tokens_.size()) {
@@ -99,6 +193,12 @@ namespace {
return t; return t;
} }
/**
* @brief 断言当前 token 是 t,否则报错
* @param t 期望的 token 类型
* @param what 错误信息中的名字(如 "END_IF"
* @return true 匹配并前进;false 不匹配(err 已写)
*/
bool expect(Tok t, const char* what) { bool expect(Tok t, const char* what) {
if (!check(t)) { if (!check(t)) {
return fail(std::string("expected '") + what + "'"); return fail(std::string("expected '") + what + "'");
@@ -109,6 +209,14 @@ namespace {
// ---- POU ---- // ---- POU ----
/**
* @brief 解析 POU 外壳
*
* @details PROGRAM / FUNCTION [: 返回类型] / FUNCTION_BLOCK
* 后接 0+ 变量段、0+ 语句,最后必须是配对 END_*。
* @param p 输出 POU
* @return true 成功;false 语法错误(err 已写)
*/
bool parse_pou(POU* p) { bool parse_pou(POU* p) {
if (check(Tok::PROGRAM)) { if (check(Tok::PROGRAM)) {
advance(); advance();
@@ -127,6 +235,7 @@ namespace {
return false; return false;
} }
// 仅 FUNCTION 有返回类型:FUNCTION Add : INT
if (p->kind == PouKind::Function) { if (p->kind == PouKind::Function) {
if (!expect(Tok::COLON, ":")) { if (!expect(Tok::COLON, ":")) {
return false; return false;
@@ -136,6 +245,7 @@ namespace {
} }
} }
// 变量段可多个、可缺省,按声明顺序保留
while (is_var_section()) { while (is_var_section()) {
VarBlock b; VarBlock b;
if (!parse_var_block(&b)) { if (!parse_var_block(&b)) {
@@ -144,6 +254,7 @@ namespace {
p->blocks.push_back(std::move(b)); p->blocks.push_back(std::move(b));
} }
// 语句体直到配对 END_*
while (!check_end_for(p->kind)) { while (!check_end_for(p->kind)) {
Stmt s; Stmt s;
if (!parse_stmt(&s)) { if (!parse_stmt(&s)) {
@@ -161,6 +272,10 @@ namespace {
return expect(end_tok_for(p->kind), end_kw); 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 { bool is_var_section() const {
switch (type()) { switch (type()) {
case Tok::VAR: 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 { Tok end_tok_for(PouKind k) const {
switch (k) { switch (k) {
case PouKind::Program: return Tok::END_PROGRAM; case PouKind::Program: return Tok::END_PROGRAM;
@@ -183,10 +304,20 @@ namespace {
return Tok::END; return Tok::END;
} }
/**
* @brief 当前 token 是否已到本 POU 的配对结束关键字
* @param k POU 种类
* @return true 语句体循环应停止
*/
bool check_end_for(PouKind k) const { return check(end_tok_for(k)); } 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) { bool parse_var_block(VarBlock* b) {
switch (type()) { switch (type()) {
case Tok::VAR: b->section = VarSection::Local; break; case Tok::VAR: b->section = VarSection::Local; break;
@@ -212,7 +343,12 @@ namespace {
return true; 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) { bool parse_vars_line(std::vector<VarDecl>* out) {
std::vector<VarDecl> names; std::vector<VarDecl> names;
while (true) { while (true) {
@@ -258,7 +394,12 @@ namespace {
return true; 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) { bool parse_literal_expr(Expr* e) {
switch (type()) { switch (type()) {
case Tok::TRUE: 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) { bool parse_type(TypeRef* t) {
switch (type()) { switch (type()) {
case Tok::BOOL: 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) { bool parse_stmt(Stmt* s) {
if (check(Tok::IF)) { if (check(Tok::IF)) {
return parse_if(s); return parse_if(s);
@@ -352,6 +507,12 @@ namespace {
return is_call ? parse_fb_call(s) : parse_assign(s); 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) { bool parse_assign(Stmt* s) {
s->kind = StmtKind::Assign; s->kind = StmtKind::Assign;
if (!parse_ident(&s->target)) { if (!parse_ident(&s->target)) {
@@ -370,6 +531,13 @@ namespace {
return expect(Tok::SEMI, ";"); 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) { bool parse_fb_call(Stmt* s) {
s->kind = StmtKind::FbCall; s->kind = StmtKind::FbCall;
if (!parse_ident(&s->instance)) { if (!parse_ident(&s->instance)) {
@@ -404,6 +572,11 @@ namespace {
return expect(Tok::SEMI, ";"); 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) { bool parse_if(Stmt* s) {
s->kind = StmtKind::If; s->kind = StmtKind::If;
advance(); // IF advance(); // IF
@@ -441,6 +614,12 @@ namespace {
return expect(Tok::END_IF, "END_IF"); 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) { bool parse_while(Stmt* s) {
s->kind = StmtKind::While; s->kind = StmtKind::While;
advance(); // WHILE advance(); // WHILE
@@ -457,6 +636,13 @@ namespace {
return expect(Tok::END_WHILE, "END_WHILE"); 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) { bool parse_body(std::vector<Stmt>* out) {
while (!check(Tok::ELSIF) && !check(Tok::ELSE) && !check(Tok::END_IF) && while (!check(Tok::ELSIF) && !check(Tok::ELSE) && !check(Tok::END_IF) &&
!check(Tok::END_WHILE) && !check(Tok::END)) { !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); } 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) { bool parse_or(Expr* e) {
std::unique_ptr<Expr> lhs = std::make_unique<Expr>(); std::unique_ptr<Expr> lhs = std::make_unique<Expr>();
if (!parse_and(lhs.get())) { if (!parse_and(lhs.get())) {
@@ -481,7 +679,7 @@ namespace {
while (check(Tok::OR)) { while (check(Tok::OR)) {
advance(); advance();
auto node = std::make_unique<Expr>(); auto node = std::make_unique<Expr>();
node->kind = ExprKind::Or; node->kind = ExprKind::Or; // 短路语义保留到 12.8 编跳转
node->lhs = std::move(lhs); node->lhs = std::move(lhs);
node->rhs = std::make_unique<Expr>(); node->rhs = std::make_unique<Expr>();
if (!parse_and(node->rhs.get())) { if (!parse_and(node->rhs.get())) {
@@ -493,6 +691,11 @@ namespace {
return true; return true;
} }
/**
* @brief AND 层:not_expr (AND not_expr)*(左结合)
* @param e 输出 Expr 根(kind=And 链或透传下层)
* @return true 成功;false 语法错误(err 已写)
*/
bool parse_and(Expr* e) { bool parse_and(Expr* e) {
std::unique_ptr<Expr> lhs = std::make_unique<Expr>(); std::unique_ptr<Expr> lhs = std::make_unique<Expr>();
if (!parse_not(lhs.get())) { if (!parse_not(lhs.get())) {
@@ -501,7 +704,7 @@ namespace {
while (check(Tok::AND)) { while (check(Tok::AND)) {
advance(); advance();
auto node = std::make_unique<Expr>(); auto node = std::make_unique<Expr>();
node->kind = ExprKind::And; node->kind = ExprKind::And; // 短路语义保留到 12.8 编跳转
node->lhs = std::move(lhs); node->lhs = std::move(lhs);
node->rhs = std::make_unique<Expr>(); node->rhs = std::make_unique<Expr>();
if (!parse_not(node->rhs.get())) { if (!parse_not(node->rhs.get())) {
@@ -513,6 +716,12 @@ namespace {
return true; 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) { bool parse_not(Expr* e) {
if (check(Tok::NOT)) { if (check(Tok::NOT)) {
advance(); advance();
@@ -523,6 +732,12 @@ namespace {
return parse_cmp(e); 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) { bool parse_cmp(Expr* e) {
std::unique_ptr<Expr> lhs = std::make_unique<Expr>(); std::unique_ptr<Expr> lhs = std::make_unique<Expr>();
if (!parse_add(lhs.get())) { if (!parse_add(lhs.get())) {
@@ -537,7 +752,7 @@ namespace {
case Tok::GT: op = BinOp::Gt; break; case Tok::GT: op = BinOp::Gt; break;
case Tok::GE: op = BinOp::Ge; break; case Tok::GE: op = BinOp::Ge; break;
default: default:
*e = std::move(*lhs); *e = std::move(*lhs); // 无比较符 → 原样上抛
return true; return true;
} }
advance(); advance();
@@ -545,6 +760,7 @@ namespace {
if (!parse_add(rhs.get())) { if (!parse_add(rhs.get())) {
return false; return false;
} }
// 右侧之后再出现比较符 → 链式,v1 拒绝
if (is_cmp_op(type())) { if (is_cmp_op(type())) {
return fail("chained comparison not supported"); return fail("chained comparison not supported");
} }
@@ -555,11 +771,21 @@ namespace {
return true; return true;
} }
/**
* @brief 是否比较运算符 token= <> < <= > >=
* @param t token 类型
* @return true 是比较运算符
*/
bool is_cmp_op(Tok t) const { bool is_cmp_op(Tok t) const {
return t == Tok::EQ || t == Tok::NE || t == Tok::LT || t == Tok::LE || return t == Tok::EQ || t == Tok::NE || t == Tok::LT || t == Tok::LE ||
t == Tok::GT || t == Tok::GE; 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) { bool parse_add(Expr* e) {
std::unique_ptr<Expr> lhs = std::make_unique<Expr>(); std::unique_ptr<Expr> lhs = std::make_unique<Expr>();
if (!parse_mul(lhs.get())) { if (!parse_mul(lhs.get())) {
@@ -588,6 +814,11 @@ namespace {
return true; return true;
} }
/**
* @brief 乘除层:unary (('*'|'/') unary)*(左结合)
* @param e 输出 Expr 根(kind=Mul/Div 链或透传下层)
* @return true 成功;false 语法错误(err 已写)
*/
bool parse_mul(Expr* e) { bool parse_mul(Expr* e) {
std::unique_ptr<Expr> lhs = std::make_unique<Expr>(); std::unique_ptr<Expr> lhs = std::make_unique<Expr>();
if (!parse_unary(lhs.get())) { if (!parse_unary(lhs.get())) {
@@ -616,6 +847,12 @@ namespace {
return true; 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) { bool parse_unary(Expr* e) {
if (check(Tok::MINUS)) { if (check(Tok::MINUS)) {
advance(); advance();
@@ -626,6 +863,13 @@ namespace {
return parse_primary(e); return parse_primary(e);
} }
/**
* @brief 原子:字面量 / 括号表达式 / ident
* @details ident 后的 '.' 或 '(' 决定形态(变量 | fb.field | 函数调用),
* 与语句层的区分逻辑一致
* @param e 输出 Expr
* @return true 成功;false 语法错误(err 已写)
*/
bool parse_primary(Expr* e) { bool parse_primary(Expr* e) {
switch (type()) { switch (type()) {
case Tok::INT_LIT: case Tok::INT_LIT:
@@ -662,7 +906,7 @@ namespace {
} }
advance(); advance();
if (check(Tok::DOT)) { if (check(Tok::DOT)) {
// fb.field // fb.field(字段存在性由 12.6 校验)
advance(); advance();
e->kind = ExprKind::Field; e->kind = ExprKind::Field;
e->name = word; e->name = word;
@@ -672,7 +916,7 @@ namespace {
return true; return true;
} }
if (check(Tok::LPAREN)) { if (check(Tok::LPAREN)) {
// 函数调用(FUNCTION,有返回值) // 函数调用(FUNCTION,有返回值;与语句层 FB 调用区分
e->kind = ExprKind::Call; e->kind = ExprKind::Call;
e->name = word; e->name = word;
advance(); advance();
@@ -700,6 +944,12 @@ namespace {
} }
} }
/**
* @brief 解析标识符
* @details 必须是 IDENT 且非保留名
* @param out 输出小写标识符文本
* @return true 成功;false 语法错误(err 已写)
*/
bool parse_ident(std::string* out) { bool parse_ident(std::string* out) {
if (!check(Tok::IDENT)) { if (!check(Tok::IDENT)) {
return fail("expected identifier"); return fail("expected identifier");
@@ -714,15 +964,24 @@ namespace {
// ---- 成员 ---- // ---- 成员 ----
std::string* err_; std::string* err_; // 错误输出(可空)
std::vector<Token> tokens_; std::vector<Token> tokens_; // 整段 token 流(构造时一次性 lex 完成)
Unit* out_; Unit* out_; // 解析结果
size_t pos_ = 0; size_t pos_ = 0; // 当前 token 下标
bool ok_ = true; bool ok_ = true; // 词法阶段是否成功
}; };
} // namespace } // 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, bool parse_pous(const std::string& source_file, const std::string& content,
Unit* out, std::string* err) { Unit* out, std::string* err) {
out->globals.clear(); out->globals.clear();
@@ -731,6 +990,13 @@ bool parse_pous(const std::string& source_file, const std::string& content,
return p.run(); 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) { bool parse_pous_file(const std::string& path, Unit* out, std::string* err) {
std::ifstream in(path, std::ios::binary); std::ifstream in(path, std::ios::binary);
if (!in) { if (!in) {