From e01995a33dacb3cefe4d82f339592afd2b6e09c9 Mon Sep 17 00:00:00 2001 From: jing <3030349106@qq.com> Date: Mon, 9 Mar 2026 15:37:36 +0800 Subject: [PATCH] =?UTF-8?q?fix(frontend):=20=E4=BF=AE=E5=A4=8D=E9=83=A8?= =?UTF-8?q?=E5=88=86=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 8 +- doc/Git Commit Message 规范.md | 5 +- doc/Lab1-语法树构建.md | 58 ++------- doc/Lab2-中间表示生成.md | 16 ++- doc/目录结构设计.md | 42 +++---- src/CMakeLists.txt | 2 + src/frontend/AntlrDriver.cpp | 2 +- src/frontend/CMakeLists.txt | 1 + src/frontend/SyntaxTreePrinter.cpp | 183 +++++++++++++++++++++++++++++ src/frontend/SyntaxTreePrinter.h | 9 ++ src/ir/IR.h | 2 +- src/irgen/IRGen.h | 4 +- src/irgen/IRGenDecl.cpp | 21 ++-- src/irgen/IRGenDriver.cpp | 4 +- src/irgen/IRGenStmt.cpp | 6 +- src/main.cpp | 11 +- src/sem/CMakeLists.txt | 2 +- src/sem/Sema.cpp | 93 +++++++++------ src/sem/Sema.h | 14 +-- src/utils/Log.cpp | 2 +- test/run_tests.sh | 1 - 21 files changed, 321 insertions(+), 165 deletions(-) create mode 100644 src/frontend/SyntaxTreePrinter.cpp create mode 100644 src/frontend/SyntaxTreePrinter.h mode change 100644 => 100755 test/run_tests.sh diff --git a/README.md b/README.md index f02c22a..fd6d293 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # SysY 编译器课程实验(C++) 本仓库为“并行编译课程实验”提供一个 SysY 编译器的最小可运行示例,实验按 Lab1–Lab6 逐步完成: -从前端(ANTLR 词法/语法分析与 AST 构建)到中端(IR 生成与优化),再到后端(ARM64/AArch64 汇编生成与寄存器分配),最后进行循环/并行相关优化。 +从前端(词法/语法分析与语法树处理)到中端(IR 生成与优化),再到后端(ARM64/AArch64 汇编生成与寄存器分配),最后进行循环/并行相关优化。 ## 1. 实验介绍 @@ -9,8 +9,8 @@ | 实验 | 名称 | 任务/目标 | | --- | --- | --- | -| Lab1 | 语法树构建 | 基于 SysY 源程序完成语法分析与 AST 构建,并按约定输出 AST(JSON 形式) | -| Lab2 | 中间表示生成 | 将 AST 翻译为 LLVM 风格的中间表示(IR),并输出 IR | +| Lab1 | 语法树构建 | 基于 SysY 源程序完成语法分析与语法树构建,并按约定输出语法树 | +| Lab2 | 中间表示生成 | 将语法树翻译为 LLVM 风格的中间表示(IR),并输出 IR | | Lab3 | 指令选择与汇编生成 | 将 IR 翻译为目标平台汇编代码(本项目以 ARM64/AArch64 为主) | | Lab4 | 寄存器分配 | 为后端生成的虚拟寄存器分配物理寄存器,完成 spill/reload 等必要处理 | | Lab5 | 基本标量优化 | 实现常见的标量优化(如常量传播、死代码删除、简化 CFG 等) | @@ -94,4 +94,4 @@ cmake --build build -j "$(nproc)" ./scripts/verify_asm_with_qemu.sh test/test_case/simple_add.sy out/asm --run ``` -如果最终看到 `退出码: 3`,说明当前最小子集示例 `return a + b` 的完整链路已经跑通。 \ No newline at end of file +如果最终看到 `退出码: 3`,说明当前最小子集示例 `return a + b` 的完整链路已经跑通。 diff --git a/doc/Git Commit Message 规范.md b/doc/Git Commit Message 规范.md index 92f6366..319319d 100644 --- a/doc/Git Commit Message 规范.md +++ b/doc/Git Commit Message 规范.md @@ -40,11 +40,10 @@ | scope | 含义 | |---|---| -| `frontend` | 前端(ANTLR 驱动、AST 构建入口等) | -| `ast` | AST 相关 | +| `frontend` | 前端(语法解析、语法树打印等) | | `sema` | 语义分析(符号表、常量求值等) | | `ir` | IR 核心结构 | -| `irgen` | AST → IR 生成 | +| `irgen` | 语法树 → IR 生成 | | `mir` | Machine IR(指令选择、寄存器分配、栈帧等) | | `backend` | 后端目标相关(如需要可细化 `aarch64`) | | `antlr` | 语法文件/ANTLR 相关 | diff --git a/doc/Lab1-语法树构建.md b/doc/Lab1-语法树构建.md index ed1cda1..688eafd 100644 --- a/doc/Lab1-语法树构建.md +++ b/doc/Lab1-语法树构建.md @@ -1,4 +1,4 @@ -# Lab1:语法树构建 +# Lab1:构建antlr规则生成语法树 ## 1. 本实验定位 @@ -16,13 +16,10 @@ Lab1 聚焦前端第一步:词法/语法分析。 ## 3. Lab1 需要补充的内容 -1. 必须修改的文件 +1. 需要修改的文件 - `src/antlr4/SysY.g4`:补全文法规则。 - - `src/frontend/AstBuilder.cpp`:同步扩展 parse tree 到 AST 的构建逻辑。 - - `src/ast/AstNodes.h`、`src/ast/AstNodes.cpp`:按新增语法补充或调整 AST 节点定义。 - -2. 建议同步修改的文件 - - `src/ast/AstPrinter.cpp`:为新增节点补充文本 AST 输出(`--ast-dot` 仅调试辅助,不是必做要求)。 + - `src/frontend/AntlrDriver.cpp`:解析入口与错误处理。 + - `src/frontend/SyntaxTreePrinter.cpp`:语法树打印逻辑(用于调试验证)。 @@ -64,50 +61,11 @@ java -jar third_party/antlr-4.13.2-complete.jar \ 按提供的测试输入回归验证: -1. 运行 `./build/bin/compiler ` 检查解析是否成功。 -2. 出现报错时优先回查 `SysY.g4` 与对应 AST 构建逻辑。 - -可选输出控制命令: - -```bash -# 仅输出 AST 文本 -./build/bin/compiler --emit-ast test/test_case/simple_add.sy - -# 仅输出 IR -./build/bin/compiler --emit-ir test/test_case/simple_add.sy - -# 同时输出 AST 与 IR(默认行为) -./build/bin/compiler --emit-ast --emit-ir test/test_case/simple_add.sy -``` - -## 7. AST 输出相关说明(辅助) - -AST 输出是调试手段,可以根据 AST 结果检查语法/词法实现逻辑问题。 -**此功能完善与否不会影响整个流程的功能,可以选择性的实现**。 - -当前仓库中的 AST 文本输出与 `AST -> dot` 导出仅覆盖“已实现的最小语法子集”,并非完整 SysY 通用版本。 后续每新增语法/节点类型时,需要在 `src/ast/AstPrinter.cpp` 中同步补充文本输出与 dot 导出逻辑。 +1. 运行 `./build/bin/compiler --emit-parse-tree ` 检查解析是否成功。 +2. 出现报错时优先回查 `SysY.g4` 逻辑。 -1. 基础文本 AST 输出(默认) ```bash -./build/bin/compiler test/test_case/simple_add.sy +# 仅输出语法树 +./build/bin/compiler --emit-parse-tree test/test_case/simple_add.sy ``` - -2. 图形化 AST 输出(可选) - -依赖安装(Ubuntu/WSL): - -```bash -sudo apt update -sudo apt install -y graphviz -``` - -生成 DOT 与 PNG: - -```bash -mkdir -p test/test_result/ast -./build/bin/compiler --emit-ast --ast-dot test/test_result/ast/simple_add.ast.dot test/test_case/simple_add.sy -dot -Tpng test/test_result/ast/simple_add.ast.dot -o test/test_result/ast/simple_add.ast.png -``` - -直接查看图片即可 diff --git a/doc/Lab2-中间表示生成.md b/doc/Lab2-中间表示生成.md index 60c4593..864181a 100644 --- a/doc/Lab2-中间表示生成.md +++ b/doc/Lab2-中间表示生成.md @@ -1,8 +1,7 @@ -# Lab2:从 ANTLR 解析结果生成中间表示(IR) +# Lab2:生成中间表示(IR) ## 1. 本实验定位 - -本仓库当前提供了一个“最小可运行”的 ANTLR 解析结果 -> IR 示例链路。 + Lab2 的目标是在该示例基础上扩展语义覆盖范围,逐步把更多 SysY 语法正确翻译为 IR。 ## 2. Lab2 要求 @@ -10,11 +9,10 @@ Lab2 的目标是在该示例基础上扩展语义覆盖范围,逐步把更多 需要同学完成: 1. 熟悉 IR 相关数据结构与构建接口。 -2. 理解当前 ANTLR 解析结果 -> IR 的最小实现流程。 +2. 理解当前语法树 -> IR 的最小实现流程。 3. 在现有框架上扩展 IR 生成能力,使其覆盖课程要求的Sysy语法。 - ## 3. 当前代码框架(与 Lab2 直接相关) 1. IR 定义与打印 @@ -22,7 +20,7 @@ Lab2 的目标是在该示例基础上扩展语义覆盖范围,逐步把更多 - `src/ir/IRBuilder.cpp` - `src/ir/IRPrinter.cpp` -2. ParseTree -> IR 生成器 +2. 语法树 -> IR 生成器 - `src/irgen/IRGen.h` - `src/irgen/IRGenDriver.cpp` - `src/irgen/IRGenFunc.cpp` @@ -49,7 +47,7 @@ Lab2 的目标是在该示例基础上扩展语义覆盖范围,逐步把更多 ## 5. 当前最小示例实现说明 -当前 ParseTree -> IR 仅覆盖最小子集: +当前语法树 -> IR 仅覆盖最小子集: 1. 常量整数、变量引用、二元加法表达式。 2. 局部变量声明(当前采用 LLVM 前端常见的 `alloca/load/store` 内存模型)。 @@ -74,14 +72,14 @@ cmake --build build -j "$(nproc)" ./build/bin/compiler --emit-ir test/test_case/simple_add.sy ``` -如需打印 ANTLR 语法树: +如需打印语法树: ```bash ./build/bin/compiler --emit-parse-tree test/test_case/simple_add.sy ``` 推荐使用统一脚本验证 “IR -> LLVM 后端 -> 可执行程序” 整体链路,用于验证 IR 的正确性: -推荐使用统一脚本验证 “IR -> LLVM 后端 -> 可执行程序” 整体链路,用于验证 IR 的正确性: + ```bash ./scripts/verify_ir_with_llvm.sh test/test_case/simple_add.sy out/ir --run diff --git a/doc/目录结构设计.md b/doc/目录结构设计.md index a9db4a6..016f664 100644 --- a/doc/目录结构设计.md +++ b/doc/目录结构设计.md @@ -8,12 +8,12 @@ ↓ Lexer / Parser(ANTLR) ↓ - AST 构建 + 语义分析(Sema) + 语法树构建 + 语义分析(Sema) ↓ - AST(带类型 / 符号 / 常量) + 语法树(附加语义信息) [ 中端 Middle-end ] - AST + 语法树 ↓ IR 构建(平台无关 IR,LLVM 风格) ↓ @@ -51,10 +51,7 @@ │ │ └── SysY.g4 │ ├── frontend/ │ │ ├── AntlrDriver.cpp -│ │ └── AstBuilder.cpp -│ ├── ast/ -│ │ ├── AstNodes.cpp -│ │ └── AstPrinter.cpp +│ │ └── SyntaxTreePrinter.cpp │ ├── sem/ │ │ ├── Sema.cpp │ │ ├── SymbolTable.cpp @@ -103,7 +100,6 @@ │ └── CLI.cpp ├── include/ │ ├── frontend/ -│ ├── ast/ │ ├── sem/ │ ├── irgen/ │ ├── ir/ @@ -154,36 +150,28 @@ - `SysYBaseVisitor.cpp/.h`、`SysYVisitor.cpp/.h` - `*.tokens`、`*.interp` -#### 3.2.3 `src/frontend/`:ANTLR 调用与 AST 构建 +#### 3.2.3 `src/frontend/`:语法解析与语法树输出 - `src/frontend/AntlrDriver.cpp` - - 读取源代码并调用 ANTLR 生成的 lexer/parser,得到 parse tree。 + - 读取源代码并调用解析流程,得到语法树。 - 由于语法正确性由测试保证,该模块只需提供“可用的解析入口”,不需要实现复杂的语法错误报告体系。 -- `src/frontend/AstBuilder.cpp` - - 将 parse tree 转换为 AST(`src/ast/*`),并在 AST 节点上保留必要的定位信息(可选,用于调试/日志)。 +- `src/frontend/SyntaxTreePrinter.cpp` + - 语法树调试打印:用于验证语法规则与前端解析行为是否符合预期。 -#### 3.2.4 `src/ast/`:抽象语法树(AST) - -- `src/ast/AstNodes.cpp` - - AST 节点定义与实现:表达式、语句、声明、函数、类型等。 - - AST 节点应当能够承载后续阶段附加信息(类型、符号绑定、常量值等)。 -- `src/ast/AstPrinter.cpp` - - AST 调试打印:用于验证 AST 构建是否符合预期,便于定位前端/语义阶段问题。 - -#### 3.2.5 `src/sem/`:语义分析(Sema) +#### 3.2.4 `src/sem/`:语义分析(Sema) - `src/sem/Sema.cpp` - 语义分析主流程:符号解析、类型检查、控制流规则检查、必要的隐式转换插入/记录等。 - - 输出为“带类型 / 符号 / 常量信息”的 AST(可通过在 AST 节点上附加属性实现)。 + - 在语法树基础上完成语义检查,并为后续 IR 生成提供约束。 - `src/sem/SymbolTable.cpp` - 符号表与作用域管理:支持嵌套作用域、变量/函数/参数/常量的注册与查找。 - `src/sem/ConstEval.cpp` - 常量求值:用于数组维度、全局初始化、`const` 表达式等需要编译期计算的场景。 -#### 3.2.6 `src/irgen/`:AST → IR(平台无关,LLVM 风格) +#### 3.2.5 `src/irgen/`:语法树 → IR(平台无关,LLVM 风格) - `src/irgen/IRGenDriver.cpp` - - 驱动 Visitor 遍历 AST,调度各子模块完成翻译。 + - 驱动语法树遍历,调度各子模块完成翻译。 - `src/irgen/IRGenFunc.cpp` - 函数翻译:函数定义、参数列表与返回值翻译;创建对应 IR 函数对象。 - `src/irgen/IRGenStmt.cpp` @@ -193,7 +181,7 @@ - `src/irgen/IRGenDecl.cpp` - 声明翻译:处理全局变量、局部变量、数组初始化与空间分配等。 -#### 3.2.7 `src/ir/`:LLVM 风格 IR 核心 +#### 3.2.6 `src/ir/`:LLVM 风格 IR 核心 - `src/ir/Context.cpp` - IR 上下文:管理类型/常量创建与复用、字符串/符号等公共资源。 @@ -228,7 +216,7 @@ - `src/ir/passes/CFGSimplify.cpp` - CFG 简化:删除不可达块、合并空块、简化分支等。 -#### 3.2.8 `src/mir/`:Machine IR +#### 3.2.7 `src/mir/`:Machine IR - `src/mir/MIRContext.cpp` - MIR 上下文:保存目标约束、指令集信息等。 @@ -254,7 +242,7 @@ - `src/mir/passes/Peephole.cpp` - 窥孔优化:删除冗余 move、合并常见指令模式,提升最终汇编质量。 -#### 3.2.9 `src/utils/`:日志与命令行 +#### 3.2.8 `src/utils/`:日志与命令行 - `src/utils/Log.cpp` - 日志输出:统一调试信息、阶段信息与错误信息输出。 diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 2cf20cb..6dcebdc 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -3,6 +3,7 @@ add_subdirectory(utils) add_subdirectory(ir) add_subdirectory(frontend) +add_subdirectory(sem) add_subdirectory(irgen) add_subdirectory(mir) @@ -11,6 +12,7 @@ add_executable(compiler ) target_link_libraries(compiler PRIVATE frontend + sem irgen mir utils diff --git a/src/frontend/AntlrDriver.cpp b/src/frontend/AntlrDriver.cpp index e1d05db..296197c 100644 --- a/src/frontend/AntlrDriver.cpp +++ b/src/frontend/AntlrDriver.cpp @@ -1,4 +1,4 @@ -// 调用 ANTLR 生成的 Lexer/Parser,返回 parse tree。 +// 调用前端解析流程,返回语法树。 #include "frontend/AntlrDriver.h" #include diff --git a/src/frontend/CMakeLists.txt b/src/frontend/CMakeLists.txt index 0b54f5b..a9d5cfe 100644 --- a/src/frontend/CMakeLists.txt +++ b/src/frontend/CMakeLists.txt @@ -1,5 +1,6 @@ add_library(frontend STATIC AntlrDriver.cpp + SyntaxTreePrinter.cpp ) target_link_libraries(frontend PUBLIC diff --git a/src/frontend/SyntaxTreePrinter.cpp b/src/frontend/SyntaxTreePrinter.cpp new file mode 100644 index 0000000..0593105 --- /dev/null +++ b/src/frontend/SyntaxTreePrinter.cpp @@ -0,0 +1,183 @@ +#include "frontend/SyntaxTreePrinter.h" + +#include +#include + +#include "SysYParser.h" + +namespace { + +std::string GetTokenName(const antlr4::Token* tok, antlr4::Parser* parser) { + if (!tok || !parser) { + return "UNKNOWN"; + } + const int token_type = tok->getType(); + const auto& vocab = parser->getVocabulary(); + std::string token_name(vocab.getSymbolicName(token_type)); + if (token_name.empty()) { + token_name = std::string(vocab.getLiteralName(token_type)); + } + if (token_name.empty()) { + token_name = std::to_string(token_type); + } + return token_name; +} + +bool KeepImportantToken(const std::string& token_name) { + return token_name == "Ident" || token_name == "Number" || + token_name == "Assign" || token_name == "AddOp"; +} + +std::string PrettyPrimary(SysYParser::PrimaryContext* primary) { + if (!primary) { + return ""; + } + if (primary->Number()) { + return primary->Number()->getText(); + } + if (primary->Ident()) { + return primary->Ident()->getText(); + } + if (primary->exp()) { + return "(" + primary->exp()->getText() + ")"; + } + return primary->getText(); +} + +std::string PrettyAddExp(SysYParser::AddExpContext* add_exp) { + if (!add_exp) { + return ""; + } + const auto terms = add_exp->primary(); + if (terms.empty()) { + return ""; + } + std::string out = PrettyPrimary(terms[0]); + for (size_t i = 1; i < terms.size(); ++i) { + out += " + " + PrettyPrimary(terms[i]); + } + return out; +} + +std::string PrettyExp(SysYParser::ExpContext* exp) { + if (!exp || !exp->addExp()) { + return ""; + } + return PrettyAddExp(exp->addExp()); +} + +std::string PrettyRuleText(antlr4::ParserRuleContext* rule) { + if (!rule) { + return ""; + } + if (auto* var_decl = dynamic_cast(rule)) { + std::string out = "int " + var_decl->Ident()->getText(); + if (var_decl->exp()) { + out += " = " + PrettyExp(var_decl->exp()); + } + out += ";"; + return out; + } + if (auto* ret = dynamic_cast(rule)) { + return "return " + PrettyExp(ret->exp()) + ";"; + } + if (dynamic_cast(rule) != nullptr) { + return "int main()"; + } + if (auto* stmt = dynamic_cast(rule)) { + if (stmt->varDecl()) { + return PrettyRuleText(stmt->varDecl()); + } + if (stmt->returnStmt()) { + return PrettyRuleText(stmt->returnStmt()); + } + } + if (auto* exp = dynamic_cast(rule)) { + return PrettyExp(exp); + } + if (auto* add_exp = dynamic_cast(rule)) { + return PrettyAddExp(add_exp); + } + if (auto* primary = dynamic_cast(rule)) { + return PrettyPrimary(primary); + } + return ""; +} + +bool HasVisibleNode(antlr4::tree::ParseTree* node, antlr4::Parser* parser) { + auto* terminal = dynamic_cast(node); + if (terminal) { + const std::string token_name = GetTokenName(terminal->getSymbol(), parser); + return KeepImportantToken(token_name); + } + for (auto* child : node->children) { + if (HasVisibleNode(child, parser)) { + return true; + } + } + return false; +} + +std::string RuleName(antlr4::tree::ParseTree* node, antlr4::Parser* parser) { + auto* rule = dynamic_cast(node); + if (!parser || !rule) { + return "unknown"; + } + const int idx = rule->getRuleIndex(); + const auto& names = parser->getRuleNames(); + if (idx >= 0 && idx < static_cast(names.size())) { + return names[static_cast(idx)]; + } + return "unknown"; +} + +std::string NodeLabel(antlr4::tree::ParseTree* node, antlr4::Parser* parser) { + auto* terminal = dynamic_cast(node); + if (terminal) { + return GetTokenName(terminal->getSymbol(), parser) + ": " + node->getText(); + } + + const std::string rule_name = RuleName(node, parser); + auto* rule = dynamic_cast(node); + const std::string pretty = PrettyRuleText(rule); + if (!pretty.empty()) { + return rule_name + " (" + pretty + ")"; + } + return rule_name; +} + +void PrintSyntaxTreeImpl(antlr4::tree::ParseTree* node, antlr4::Parser* parser, + std::ostream& os, const std::string& prefix, + bool is_last, bool is_root) { + if (!HasVisibleNode(node, parser)) { + return; + } + + if (is_root) { + os << NodeLabel(node, parser) << "\n"; + } else { + os << prefix << (is_last ? "└── " : "├── ") << NodeLabel(node, parser) + << "\n"; + } + + std::vector children; + for (auto* child : node->children) { + if (HasVisibleNode(child, parser)) { + children.push_back(child); + } + } + + const std::string child_prefix = + is_root ? "" : prefix + (is_last ? " " : "│ "); + for (size_t i = 0; i < children.size(); ++i) { + PrintSyntaxTreeImpl(children[i], parser, os, child_prefix, + i + 1 == children.size(), false); + } +} + +} // namespace + +void PrintSyntaxTree(antlr4::tree::ParseTree* tree, antlr4::Parser* parser, + std::ostream& os) { + PrintSyntaxTreeImpl(tree, parser, os, "", true, true); +} diff --git a/src/frontend/SyntaxTreePrinter.h b/src/frontend/SyntaxTreePrinter.h new file mode 100644 index 0000000..5dd6265 --- /dev/null +++ b/src/frontend/SyntaxTreePrinter.h @@ -0,0 +1,9 @@ +#pragma once + +#include + +#include "antlr4-runtime.h" + +// 以树状缩进形式打印语法树(仅保留关键节点/记号)。 +void PrintSyntaxTree(antlr4::tree::ParseTree* tree, antlr4::Parser* parser, + std::ostream& os); diff --git a/src/ir/IR.h b/src/ir/IR.h index 6cfe95a..4d2f9f2 100644 --- a/src/ir/IR.h +++ b/src/ir/IR.h @@ -162,7 +162,7 @@ class Function : public Value { class Module { public: - // 创建函数时显式传入返回类型,便于在 IRGen 中根据 AST 选择类型。 + // 创建函数时显式传入返回类型,便于在 IRGen 中根据语法树信息选择类型。 Function* CreateFunction(const std::string& name, std::shared_ptr ret_type); const std::vector>& functions() const { diff --git a/src/irgen/IRGen.h b/src/irgen/IRGen.h index fde34c5..8da583f 100644 --- a/src/irgen/IRGen.h +++ b/src/irgen/IRGen.h @@ -1,4 +1,4 @@ -// 将 ANTLR parse tree 翻译为极简 IR。 +// 将语法树翻译为极简 IR。 // 实现拆分在 IRGenFunc/IRGenStmt/IRGenExp/IRGenDecl。 #pragma once @@ -32,7 +32,7 @@ class IRGenImpl { private: void GenFuncDef(SysYParser::FuncDefContext& func); void GenBlock(SysYParser::BlockContext& block); - void GenStmt(SysYParser::StmtContext& stmt); + bool GenStmt(SysYParser::StmtContext& stmt); void GenVarDecl(SysYParser::VarDeclContext& decl); void GenReturnStmt(SysYParser::ReturnStmtContext& ret); diff --git a/src/irgen/IRGenDecl.cpp b/src/irgen/IRGenDecl.cpp index d8e54b2..4572767 100644 --- a/src/irgen/IRGenDecl.cpp +++ b/src/irgen/IRGenDecl.cpp @@ -6,27 +6,22 @@ #include "ir/IR.h" void IRGenImpl::GenBlock(SysYParser::BlockContext& block) { - for (auto* stmt : block.stmt()) { - if (stmt && stmt->varDecl()) { - const std::string name = stmt->varDecl()->Ident()->getText(); - auto* slot = builder_.CreateAllocaI32(ir::DefaultContext().NextTemp()); - locals_[name] = slot; - } - } - for (auto* stmt : block.stmt()) { if (stmt) { - GenStmt(*stmt); + if (GenStmt(*stmt)) { + break; + } } } } void IRGenImpl::GenVarDecl(SysYParser::VarDeclContext& decl) { const std::string name = decl.Ident()->getText(); - auto it = locals_.find(name); - if (it == locals_.end()) { - throw std::runtime_error("[irgen] 变量栈槽未创建: " + name); + if (locals_.find(name) != locals_.end()) { + throw std::runtime_error("[irgen] 重复定义变量: " + name); } + auto* slot = builder_.CreateAllocaI32(ir::DefaultContext().NextTemp()); + locals_[name] = slot; ir::Value* init = nullptr; if (decl.exp()) { @@ -34,5 +29,5 @@ void IRGenImpl::GenVarDecl(SysYParser::VarDeclContext& decl) { } else { init = ir::DefaultContext().GetConstInt(0); } - builder_.CreateStore(init, it->second); + builder_.CreateStore(init, slot); } diff --git a/src/irgen/IRGenDriver.cpp b/src/irgen/IRGenDriver.cpp index 2c585f8..014b60a 100644 --- a/src/irgen/IRGenDriver.cpp +++ b/src/irgen/IRGenDriver.cpp @@ -9,12 +9,12 @@ std::unique_ptr GenerateIR(antlr4::tree::ParseTree* tree) { if (!tree) { - throw std::runtime_error("[irgen] parse tree 为空"); + throw std::runtime_error("[irgen] 语法树为空"); } auto* cu = dynamic_cast(tree); if (!cu) { - throw std::runtime_error("[irgen] parse tree 根节点不是 compUnit"); + throw std::runtime_error("[irgen] 语法树根节点不是 compUnit"); } auto module = std::make_unique(); diff --git a/src/irgen/IRGenStmt.cpp b/src/irgen/IRGenStmt.cpp index bf9a605..8914705 100644 --- a/src/irgen/IRGenStmt.cpp +++ b/src/irgen/IRGenStmt.cpp @@ -5,14 +5,14 @@ #include "SysYParser.h" #include "ir/IR.h" -void IRGenImpl::GenStmt(SysYParser::StmtContext& stmt) { +bool IRGenImpl::GenStmt(SysYParser::StmtContext& stmt) { if (stmt.varDecl()) { GenVarDecl(*stmt.varDecl()); - return; + return false; } if (stmt.returnStmt()) { GenReturnStmt(*stmt.returnStmt()); - return; + return true; } throw std::runtime_error("[irgen] 暂不支持的语句类型"); } diff --git a/src/main.cpp b/src/main.cpp index 609ea67..da355f6 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,10 +1,13 @@ #include #include +#include #include "frontend/AntlrDriver.h" +#include "frontend/SyntaxTreePrinter.h" #include "ir/IR.h" #include "irgen/IRGen.h" #include "mir/MIR.h" +#include "sem/Sema.h" #include "utils/CLI.h" #include "utils/Log.h" @@ -19,10 +22,16 @@ int main(int argc, char** argv) { auto antlr = ParseFileWithAntlr(opts.input); bool need_blank_line = false; if (opts.emit_parse_tree) { - std::cout << antlr.tree->toStringTree(antlr.parser.get()) << "\n"; + PrintSyntaxTree(antlr.tree, antlr.parser.get(), std::cout); need_blank_line = true; } + auto* comp_unit = dynamic_cast(antlr.tree); + if (!comp_unit) { + throw std::runtime_error("[main] 语法树根节点不是 compUnit"); + } + RunSema(*comp_unit); + auto module = GenerateIR(antlr.tree); if (opts.emit_ir) { ir::IRPrinter printer; diff --git a/src/sem/CMakeLists.txt b/src/sem/CMakeLists.txt index 485e830..b3bc011 100644 --- a/src/sem/CMakeLists.txt +++ b/src/sem/CMakeLists.txt @@ -6,5 +6,5 @@ add_library(sem STATIC target_link_libraries(sem PUBLIC build_options - ast + ${ANTLR4_RUNTIME_TARGET} ) diff --git a/src/sem/Sema.cpp b/src/sem/Sema.cpp index 92b960c..2d7ae22 100644 --- a/src/sem/Sema.cpp +++ b/src/sem/Sema.cpp @@ -1,58 +1,75 @@ -// 极简语义分析:只检查变量是否先声明再使用。 -// 如需扩展,可在此基础上加入: -// - 常量折叠/类型检查 -// - 函数签名/参数数量校验 -// - 控制流相关检查(return 覆盖、break/continue 合法性等) #include "sem/Sema.h" #include #include -#include -#include "ast/AstNodes.h" #include "sem/SymbolTable.h" namespace { -class SemaVisitor { - public: - explicit SemaVisitor(SymbolTable& table) : table_(table) {} +void CheckExpr(SysYParser::ExpContext& exp, const SymbolTable& table); - void CheckBlock(const ast::Block& block) { - for (const auto& item : block.items) { - if (auto decl = dynamic_cast(item.get())) { - table_.Add(decl->name); - if (decl->init) CheckExpr(*decl->init); - continue; - } - if (auto ret = dynamic_cast(item.get())) { - CheckExpr(*ret->value); - } - } +void CheckPrimary(SysYParser::PrimaryContext& primary, + const SymbolTable& table) { + if (primary.Number()) { + return; } - void CheckExpr(const ast::Expr& expr) { - if (auto var = dynamic_cast(&expr)) { - if (!table_.Contains(var->name)) { - throw std::runtime_error("[sema] 使用了未定义的变量: " + var->name); - } - } else if (auto bin = dynamic_cast(&expr)) { - CheckExpr(*bin->lhs); - CheckExpr(*bin->rhs); - + if (primary.Ident()) { + const std::string name = primary.Ident()->getText(); + if (!table.Contains(name)) { + throw std::runtime_error("[sema] 使用了未定义的变量: " + name); } + return; + } + + if (primary.exp()) { + CheckExpr(*primary.exp(), table); + return; } - private: - SymbolTable& table_; -}; + throw std::runtime_error("[sema] 暂不支持的 primary 形式"); +} + +void CheckExpr(SysYParser::ExpContext& exp, const SymbolTable& table) { + if (!exp.addExp()) { + throw std::runtime_error("[sema] 非法表达式"); + } + const auto& terms = exp.addExp()->primary(); + for (auto* term : terms) { + CheckPrimary(*term, table); + } +} } // namespace -std::shared_ptr RunSema(std::shared_ptr ast) { - if (!ast || !ast->func || !ast->func->body) return ast; +void RunSema(SysYParser::CompUnitContext& comp_unit) { + auto* func = comp_unit.funcDef(); + if (!func || !func->block()) { + throw std::runtime_error("[sema] 缺少 main 函数定义"); + } + SymbolTable table; - SemaVisitor visitor(table); - visitor.CheckBlock(*ast->func->body); - return ast; + + for (auto* stmt : func->block()->stmt()) { + if (!stmt) { + continue; + } + if (auto* decl = stmt->varDecl()) { + const std::string name = decl->Ident()->getText(); + if (table.Contains(name)) { + throw std::runtime_error("[sema] 重复定义变量: " + name); + } + if (decl->exp()) { + CheckExpr(*decl->exp(), table); + } + table.Add(name); + continue; + } + if (auto* ret = stmt->returnStmt()) { + CheckExpr(*ret->exp(), table); + break; + } + throw std::runtime_error("[sema] 暂不支持的语句类型"); + } } diff --git a/src/sem/Sema.h b/src/sem/Sema.h index 2886337..8f9d9a7 100644 --- a/src/sem/Sema.h +++ b/src/sem/Sema.h @@ -1,11 +1,9 @@ -// 语义检查 +// 基于语法树的极简语义检查。 #pragma once -#include +#include "SysYParser.h" -namespace ast { -struct CompUnit; -} - -// 返回经过检查的 AST(当前直接返回原 AST)。 -std::shared_ptr RunSema(std::shared_ptr ast); +// 目前仅检查: +// - 变量先声明后使用 +// - 局部变量不允许重复定义 +void RunSema(SysYParser::CompUnitContext& comp_unit); diff --git a/src/utils/Log.cpp b/src/utils/Log.cpp index 0b8ae78..6f32e80 100644 --- a/src/utils/Log.cpp +++ b/src/utils/Log.cpp @@ -14,7 +14,7 @@ void PrintHelp(std::ostream& os) { << "\n" << "选项:\n" << " -h, --help 打印帮助信息并退出\n" - << " --emit-parse-tree 仅在显式模式下启用 ANTLR 语法树输出\n" + << " --emit-parse-tree 仅在显式模式下启用语法树输出\n" << " --emit-ir 仅在显式模式下启用 IR 输出\n" << " --emit-asm 仅在显式模式下启用 AArch64 汇编输出\n" << "\n" diff --git a/test/run_tests.sh b/test/run_tests.sh old mode 100644 new mode 100755 index d5d64d0..dac31d3 --- a/test/run_tests.sh +++ b/test/run_tests.sh @@ -2,4 +2,3 @@ # - 批量编译 test/test_case/ 下的 *.sy 用例 # - 将产物与日志写入 test/test_result/(例如 .ll/.s、运行输出、diff 结果) # - 汇总通过/失败信息并给出统计 -