fix(ast): 规范AST输出

This commit is contained in:
jing
2026-02-28 21:03:40 +08:00
parent cd235e60cc
commit d08b23276a
6 changed files with 184 additions and 38 deletions

View File

@@ -1,6 +1,7 @@
#pragma once #pragma once
#include <iosfwd>
#include <memory> #include <memory>
#include <string> #include <string>
#include <vector> #include <vector>
@@ -66,5 +67,7 @@ struct CompUnit {
// 调试打印 // 调试打印
void PrintAST(const CompUnit& cu); void PrintAST(const CompUnit& cu);
// 导出 AST 为 Graphviz DOT。
void PrintASTDot(const CompUnit& cu, std::ostream& os);
} // namespace ast } // namespace ast

View File

@@ -3,70 +3,194 @@
#include "ast/AstNodes.h" #include "ast/AstNodes.h"
#include <iostream> #include <iostream>
#include <ostream>
#include <string>
namespace ast { namespace ast {
static void PrintExpr(const Expr* expr);
static void PrintIndent(int depth) { static void PrintIndent(int depth) {
for (int i = 0; i < depth; ++i) std::cout << " "; for (int i = 0; i < depth; ++i) std::cout << " ";
} }
static void PrintExpr(const Expr* expr) { static void PrintLine(int depth, const std::string& text) {
PrintIndent(depth);
std::cout << text << "\n";
}
static void DumpExpr(const Expr* expr, int depth) {
if (!expr) {
PrintLine(depth, "NullExpr");
return;
}
if (auto num = dynamic_cast<const NumberExpr*>(expr)) { if (auto num = dynamic_cast<const NumberExpr*>(expr)) {
std::cout << num->value; PrintLine(depth, "NumberExpr value=" + std::to_string(num->value));
} else if (auto var = dynamic_cast<const VarExpr*>(expr)) { return;
std::cout << var->name; }
} else if (auto bin = dynamic_cast<const BinaryExpr*>(expr)) { if (auto var = dynamic_cast<const VarExpr*>(expr)) {
std::cout << "("; PrintLine(depth, "VarExpr name=" + var->name);
PrintExpr(bin->lhs.get()); return;
}
if (auto bin = dynamic_cast<const BinaryExpr*>(expr)) {
const char* op = "?"; const char* op = "?";
switch (bin->op) { switch (bin->op) {
case BinaryOp::Add: case BinaryOp::Add:
op = "+"; op = "Add";
break; break;
case BinaryOp::Sub: case BinaryOp::Sub:
op = "-"; op = "Sub";
break; break;
case BinaryOp::Mul: case BinaryOp::Mul:
op = "*"; op = "Mul";
break; break;
case BinaryOp::Div: case BinaryOp::Div:
op = "/"; op = "Div";
break; break;
} }
std::cout << " " << op << " "; PrintLine(depth, std::string("BinaryExpr op=") + op);
PrintExpr(bin->rhs.get()); DumpExpr(bin->lhs.get(), depth + 1);
std::cout << ")"; DumpExpr(bin->rhs.get(), depth + 1);
return;
} }
PrintLine(depth, "UnknownExpr");
} }
void PrintAST(const CompUnit& cu) { void PrintAST(const CompUnit& cu) {
if (!cu.func) return; PrintLine(0, "CompUnit");
std::cout << "func " << cu.func->name << " () {\n"; if (!cu.func) {
const auto& body = cu.func->body; PrintLine(1, "<empty>");
if (!body) {
std::cout << "}\n";
return; return;
} }
PrintLine(1, "FuncDef name=" + cu.func->name);
const auto& body = cu.func->body;
if (!body) {
PrintLine(2, "Block <null>");
return;
}
PrintLine(2, "Block");
for (const auto& decl : body->varDecls) { for (const auto& decl : body->varDecls) {
PrintIndent(1); PrintLine(3, "VarDecl name=" + decl->name);
std::cout << "var " << decl->name;
if (decl->init) { if (decl->init) {
std::cout << " = "; PrintLine(4, "Init");
PrintExpr(decl->init.get()); DumpExpr(decl->init.get(), 5);
} }
std::cout << ";\n";
} }
for (const auto& stmt : body->stmts) { for (const auto& stmt : body->stmts) {
if (auto ret = dynamic_cast<ReturnStmt*>(stmt.get())) { if (auto ret = dynamic_cast<ReturnStmt*>(stmt.get())) {
PrintIndent(1); PrintLine(3, "ReturnStmt");
std::cout << "return "; DumpExpr(ret->value.get(), 4);
PrintExpr(ret->value.get()); } else {
std::cout << ";\n"; PrintLine(3, "UnknownStmt");
} }
} }
std::cout << "}\n"; }
namespace {
std::string EscapeDotLabel(const std::string& s) {
std::string out;
out.reserve(s.size());
for (char c : s) {
if (c == '\\' || c == '"') out.push_back('\\');
out.push_back(c);
}
return out;
}
class DotWriter {
public:
explicit DotWriter(std::ostream& os) : os_(os) {}
int AddNode(const std::string& label) {
int id = next_id_++;
os_ << " n" << id << " [label=\"" << EscapeDotLabel(label) << "\"];\n";
return id;
}
void AddEdge(int from, int to) { os_ << " n" << from << " -> n" << to << ";\n"; }
private:
std::ostream& os_;
int next_id_ = 0;
};
int EmitExpr(const Expr* expr, DotWriter& dot) {
if (!expr) return dot.AddNode("NullExpr");
if (auto num = dynamic_cast<const NumberExpr*>(expr)) {
return dot.AddNode("Number(" + std::to_string(num->value) + ")");
}
if (auto var = dynamic_cast<const VarExpr*>(expr)) {
return dot.AddNode("Var(" + var->name + ")");
}
if (auto bin = dynamic_cast<const BinaryExpr*>(expr)) {
const char* op = "?";
switch (bin->op) {
case BinaryOp::Add:
op = "Add";
break;
case BinaryOp::Sub:
op = "Sub";
break;
case BinaryOp::Mul:
op = "Mul";
break;
case BinaryOp::Div:
op = "Div";
break;
}
int id = dot.AddNode(std::string("Binary(") + op + ")");
int lhs = EmitExpr(bin->lhs.get(), dot);
int rhs = EmitExpr(bin->rhs.get(), dot);
dot.AddEdge(id, lhs);
dot.AddEdge(id, rhs);
return id;
}
return dot.AddNode("UnknownExpr");
}
} // namespace
void PrintASTDot(const CompUnit& cu, std::ostream& os) {
os << "digraph AST {\n";
os << " rankdir=TB;\n";
os << " node [shape=box, fontname=\"Courier\"];\n";
DotWriter dot(os);
int cu_id = dot.AddNode("CompUnit");
if (cu.func) {
int func_id = dot.AddNode("FuncDef(" + cu.func->name + ")");
dot.AddEdge(cu_id, func_id);
if (cu.func->body) {
int block_id = dot.AddNode("Block");
dot.AddEdge(func_id, block_id);
for (const auto& decl : cu.func->body->varDecls) {
int decl_id = dot.AddNode("VarDecl(" + decl->name + ")");
dot.AddEdge(block_id, decl_id);
if (decl->init) {
int init_id = EmitExpr(decl->init.get(), dot);
dot.AddEdge(decl_id, init_id);
}
}
for (const auto& stmt : cu.func->body->stmts) {
if (auto ret = dynamic_cast<ReturnStmt*>(stmt.get())) {
int ret_id = dot.AddNode("Return");
dot.AddEdge(block_id, ret_id);
int value_id = EmitExpr(ret->value.get(), dot);
dot.AddEdge(ret_id, value_id);
} else {
int stmt_id = dot.AddNode("Stmt");
dot.AddEdge(block_id, stmt_id);
}
}
}
}
os << "}\n";
} }
} // namespace ast } // namespace ast

View File

@@ -1,5 +1,7 @@
#include <exception> #include <exception>
#include <fstream>
#include <iostream> #include <iostream>
#include <stdexcept>
#include "frontend/AntlrDriver.h" #include "frontend/AntlrDriver.h"
#include "frontend/AstBuilder.h" #include "frontend/AstBuilder.h"
@@ -20,8 +22,15 @@ int main(int argc, char** argv) {
auto antlr = ParseFileWithAntlr(opts.input); auto antlr = ParseFileWithAntlr(opts.input);
auto ast = BuildAst(antlr.tree); auto ast = BuildAst(antlr.tree);
ast::PrintAST(*ast); // 调试 AST ast::PrintAST(*ast); // 调试 AST
if (!opts.ast_dot_output.empty()) {
std::ofstream dot_out(opts.ast_dot_output);
if (!dot_out) {
throw std::runtime_error("无法写入 AST DOT 文件: " + opts.ast_dot_output);
}
ast::PrintASTDot(*ast, dot_out);
}
ast = RunSema(std::move(ast)); ast = RunSema(std::move(ast));
// IR 生成:当前使用 IRGenDriver.cpp 里的最小参考实现, // IR 生成:当前使用 IRGenDriver.cpp 里的最小实现,
// 同学们可以在 irgen/ 目录下按提示自行完善或替换实现。 // 同学们可以在 irgen/ 目录下按提示自行完善或替换实现。
auto module = GenerateIR(*ast); auto module = GenerateIR(*ast);

View File

@@ -5,15 +5,15 @@
#include "utils/CLI.h" #include "utils/CLI.h"
#include <cstring>
#include <string>
#include <stdexcept> #include <stdexcept>
#include <string>
#include <cstring>
CLIOptions ParseCLI(int argc, char** argv) { CLIOptions ParseCLI(int argc, char** argv) {
CLIOptions opt; CLIOptions opt;
if (argc <= 1) { if (argc <= 1) {
throw std::runtime_error("用法: compiler [--help] <input.sy>"); throw std::runtime_error("用法: compiler [--help] [--ast-dot <file.dot>] <input.sy>");
} }
for (int i = 1; i < argc; ++i) { for (int i = 1; i < argc; ++i) {
@@ -23,6 +23,14 @@ CLIOptions ParseCLI(int argc, char** argv) {
return opt; return opt;
} }
if (std::strcmp(arg, "--ast-dot") == 0) {
if (i + 1 >= argc) {
throw std::runtime_error("参数错误: --ast-dot 需要一个输出路径");
}
opt.ast_dot_output = argv[++i];
continue;
}
if (arg[0] == '-') { if (arg[0] == '-') {
throw std::runtime_error(std::string("未知参数: ") + arg + throw std::runtime_error(std::string("未知参数: ") + arg +
"(使用 --help 查看用法)"); "(使用 --help 查看用法)");
@@ -35,7 +43,7 @@ CLIOptions ParseCLI(int argc, char** argv) {
opt.input = arg; opt.input = arg;
} }
if (opt.input.empty()) { if (opt.input.empty() && !opt.show_help) {
throw std::runtime_error("缺少输入文件:请提供 <input.sy>(使用 --help 查看用法)"); throw std::runtime_error("缺少输入文件:请提供 <input.sy>(使用 --help 查看用法)");
} }
return opt; return opt;

View File

@@ -1,10 +1,11 @@
// 简易命令行解析:支持输入文件路径 // 简易命令行解析:支持帮助、输入文件和 AST DOT 导出
#pragma once #pragma once
#include <string> #include <string>
struct CLIOptions { struct CLIOptions {
std::string input; std::string input;
std::string ast_dot_output;
bool show_help = false; bool show_help = false;
}; };

View File

@@ -10,10 +10,11 @@ void PrintHelp(std::ostream& os) {
os << "SysY Compiler (课程实验最小可运行示例)\n" os << "SysY Compiler (课程实验最小可运行示例)\n"
<< "\n" << "\n"
<< "用法:\n" << "用法:\n"
<< " compiler [--help] <input.sy>\n" << " compiler [--help] [--ast-dot <file.dot>] <input.sy>\n"
<< "\n" << "\n"
<< "选项:\n" << "选项:\n"
<< " -h, --help 打印帮助信息并退出\n" << " -h, --help 打印帮助信息并退出\n"
<< " --ast-dot <path> 导出 AST Graphviz DOT 到指定文件\n"
<< "\n" << "\n"
<< "说明:\n" << "说明:\n"
<< " - 当前默认将 IR 输出到标准输出,可使用重定向写入文件:\n" << " - 当前默认将 IR 输出到标准输出,可使用重定向写入文件:\n"