fix(frontend): 修复部分实现

master
jing 1 week ago
parent 8aec500b5b
commit e01995a33d

@ -1,7 +1,7 @@
# SysY 编译器课程实验C++
本仓库为“并行编译课程实验”提供一个 SysY 编译器的最小可运行示例,实验按 Lab1Lab6 逐步完成:
从前端(ANTLR 词法/语法分析与 AST 构建到中端IR 生成与优化再到后端ARM64/AArch64 汇编生成与寄存器分配),最后进行循环/并行相关优化。
从前端(词法/语法分析与语法树处理到中端IR 生成与优化再到后端ARM64/AArch64 汇编生成与寄存器分配),最后进行循环/并行相关优化。
## 1. 实验介绍
@ -9,8 +9,8 @@
| 实验 | 名称 | 任务/目标 |
| --- | --- | --- |
| Lab1 | 语法树构建 | 基于 SysY 源程序完成语法分析与 AST 构建,并按约定输出 ASTJSON 形式) |
| 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` 的完整链路已经跑通。
如果最终看到 `退出码: 3`,说明当前最小子集示例 `return a + b` 的完整链路已经跑通。

@ -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 相关 |

@ -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 <case.sy>` 检查解析是否成功。
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 <case.sy>` 检查解析是否成功。
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
```
直接查看图片即可

@ -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

@ -8,12 +8,12 @@
Lexer / ParserANTLR
AST 构建 + 语义分析Sema
语法树构建 + 语义分析Sema
AST带类型 / 符号 / 常量
语法树(附加语义信息
[ 中端 Middle-end ]
AST
语法树
IR 构建(平台无关 IRLLVM 风格)
@ -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`
- 日志输出:统一调试信息、阶段信息与错误信息输出。

@ -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

@ -1,4 +1,4 @@
// 调用 ANTLR 生成的 Lexer/Parser返回 parse tree
// 调用前端解析流程,返回语法树
#include "frontend/AntlrDriver.h"
#include <fstream>

@ -1,5 +1,6 @@
add_library(frontend STATIC
AntlrDriver.cpp
SyntaxTreePrinter.cpp
)
target_link_libraries(frontend PUBLIC

@ -0,0 +1,183 @@
#include "frontend/SyntaxTreePrinter.h"
#include <string>
#include <vector>
#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<SysYParser::VarDeclContext*>(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<SysYParser::ReturnStmtContext*>(rule)) {
return "return " + PrettyExp(ret->exp()) + ";";
}
if (dynamic_cast<SysYParser::FuncDefContext*>(rule) != nullptr) {
return "int main()";
}
if (auto* stmt = dynamic_cast<SysYParser::StmtContext*>(rule)) {
if (stmt->varDecl()) {
return PrettyRuleText(stmt->varDecl());
}
if (stmt->returnStmt()) {
return PrettyRuleText(stmt->returnStmt());
}
}
if (auto* exp = dynamic_cast<SysYParser::ExpContext*>(rule)) {
return PrettyExp(exp);
}
if (auto* add_exp = dynamic_cast<SysYParser::AddExpContext*>(rule)) {
return PrettyAddExp(add_exp);
}
if (auto* primary = dynamic_cast<SysYParser::PrimaryContext*>(rule)) {
return PrettyPrimary(primary);
}
return "";
}
bool HasVisibleNode(antlr4::tree::ParseTree* node, antlr4::Parser* parser) {
auto* terminal = dynamic_cast<antlr4::tree::TerminalNode*>(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<antlr4::ParserRuleContext*>(node);
if (!parser || !rule) {
return "unknown";
}
const int idx = rule->getRuleIndex();
const auto& names = parser->getRuleNames();
if (idx >= 0 && idx < static_cast<int>(names.size())) {
return names[static_cast<size_t>(idx)];
}
return "unknown";
}
std::string NodeLabel(antlr4::tree::ParseTree* node, antlr4::Parser* parser) {
auto* terminal = dynamic_cast<antlr4::tree::TerminalNode*>(node);
if (terminal) {
return GetTokenName(terminal->getSymbol(), parser) + ": " + node->getText();
}
const std::string rule_name = RuleName(node, parser);
auto* rule = dynamic_cast<antlr4::ParserRuleContext*>(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<antlr4::tree::ParseTree*> 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);
}

@ -0,0 +1,9 @@
#pragma once
#include <iosfwd>
#include "antlr4-runtime.h"
// 以树状缩进形式打印语法树(仅保留关键节点/记号)。
void PrintSyntaxTree(antlr4::tree::ParseTree* tree, antlr4::Parser* parser,
std::ostream& os);

@ -162,7 +162,7 @@ class Function : public Value {
class Module {
public:
// 创建函数时显式传入返回类型,便于在 IRGen 中根据 AST 选择类型。
// 创建函数时显式传入返回类型,便于在 IRGen 中根据语法树信息选择类型。
Function* CreateFunction(const std::string& name,
std::shared_ptr<Type> ret_type);
const std::vector<std::unique_ptr<Function>>& functions() const {

@ -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);

@ -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);
}

@ -9,12 +9,12 @@
std::unique_ptr<ir::Module> GenerateIR(antlr4::tree::ParseTree* tree) {
if (!tree) {
throw std::runtime_error("[irgen] parse tree 为空");
throw std::runtime_error("[irgen] 语法树为空");
}
auto* cu = dynamic_cast<SysYParser::CompUnitContext*>(tree);
if (!cu) {
throw std::runtime_error("[irgen] parse tree 根节点不是 compUnit");
throw std::runtime_error("[irgen] 语法树根节点不是 compUnit");
}
auto module = std::make_unique<ir::Module>();

@ -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] 暂不支持的语句类型");
}

@ -1,10 +1,13 @@
#include <exception>
#include <iostream>
#include <stdexcept>
#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<SysYParser::CompUnitContext*>(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;

@ -6,5 +6,5 @@ add_library(sem STATIC
target_link_libraries(sem PUBLIC
build_options
ast
${ANTLR4_RUNTIME_TARGET}
)

@ -1,58 +1,75 @@
// 极简语义分析:只检查变量是否先声明再使用。
// 如需扩展,可在此基础上加入:
// - 常量折叠/类型检查
// - 函数签名/参数数量校验
// - 控制流相关检查return 覆盖、break/continue 合法性等)
#include "sem/Sema.h"
#include <stdexcept>
#include <string>
#include <unordered_set>
#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<ast::VarDecl*>(item.get())) {
table_.Add(decl->name);
if (decl->init) CheckExpr(*decl->init);
continue;
}
if (auto ret = dynamic_cast<ast::ReturnStmt*>(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<const ast::VarExpr*>(&expr)) {
if (!table_.Contains(var->name)) {
throw std::runtime_error("[sema] 使用了未定义的变量: " + var->name);
}
} else if (auto bin = dynamic_cast<const ast::BinaryExpr*>(&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<ast::CompUnit> RunSema(std::shared_ptr<ast::CompUnit> 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] 暂不支持的语句类型");
}
}

@ -1,11 +1,9 @@
// 语义检查
// 基于语法树的极简语义检查
#pragma once
#include <memory>
#include "SysYParser.h"
namespace ast {
struct CompUnit;
}
// 返回经过检查的 AST当前直接返回原 AST
std::shared_ptr<ast::CompUnit> RunSema(std::shared_ptr<ast::CompUnit> ast);
// 目前仅检查:
// - 变量先声明后使用
// - 局部变量不允许重复定义
void RunSema(SysYParser::CompUnitContext& comp_unit);

@ -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"

@ -2,4 +2,3 @@
# - 批量编译 test/test_case/ 下的 *.sy 用例
# - 将产物与日志写入 test/test_result/(例如 .ll/.s、运行输出、diff 结果)
# - 汇总通过/失败信息并给出统计

Loading…
Cancel
Save