From 3c728fb2b8b5106144f93f3b4fcdb11aa553c790 Mon Sep 17 00:00:00 2001 From: Hydrostic Date: Sat, 9 May 2026 12:29:59 +0800 Subject: [PATCH] feat(ast): Add graph output --- .gitignore | 3 +- Cargo.lock | 56 +++++++++++++++ Cargo.toml | 1 + src/ast/graph.rs | 155 +++++++++++++++++++++++++++++++++++++++++ src/ast/mod.rs | 1 + src/ast/types.rs | 111 ++++++++++++++++++++++++++++- src/frontend/parser.rs | 6 +- 7 files changed, 330 insertions(+), 3 deletions(-) create mode 100644 src/ast/graph.rs diff --git a/.gitignore b/.gitignore index 26ceea5..8774c22 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ /target -/testcases \ No newline at end of file +/testcases +/output \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index db97c61..037fc18 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -28,12 +28,55 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "fixedbitset" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash", +] + +[[package]] +name = "hashbrown" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51" + [[package]] name = "heck" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "indexmap" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +dependencies = [ + "equivalent", + "hashbrown 0.17.0", +] + [[package]] name = "memchr" version = "2.8.0" @@ -113,6 +156,18 @@ dependencies = [ "autocfg", ] +[[package]] +name = "petgraph" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8701b58ea97060d5e5b155d383a69952a60943f0e6dfe30b04c287beb0b27455" +dependencies = [ + "fixedbitset", + "hashbrown 0.15.5", + "indexmap", + "serde", +] + [[package]] name = "proc-macro2" version = "1.0.106" @@ -166,6 +221,7 @@ version = "0.1.0" dependencies = [ "codespan-reporting", "num", + "petgraph", "regex", "strum", "thiserror", diff --git a/Cargo.toml b/Cargo.toml index 61533d4..71ecf51 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,6 +6,7 @@ edition = "2024" [dependencies] codespan-reporting = "0.13.1" num = "0.4.3" +petgraph = "0.8.3" regex = "1.12.3" strum = { version = "0.28.0", features = ["derive"] } thiserror = "2.0.18" diff --git a/src/ast/graph.rs b/src/ast/graph.rs new file mode 100644 index 0000000..8a0e302 --- /dev/null +++ b/src/ast/graph.rs @@ -0,0 +1,155 @@ +use petgraph::dot::{Config, Dot}; +use petgraph::graph::{Graph, NodeIndex}; + +use crate::ast::types::{ + BlockStmt, CompileUnit, Expr, ExprValue, FuncDeclStmt, GlobalDeclStmt, Param, ReturnStmt, + Statement, VarDeclStmt, VarDeclStmtValue, +}; + +pub type AstGraph = Graph; + +pub trait AstGraphExt { + fn to_graph(&self) -> AstGraph; + fn to_dot(&self) -> String { + format!("{}", Dot::with_config(&self.to_graph(), &[Config::EdgeNoLabel])) + } +} + +impl AstGraphExt for CompileUnit { + fn to_graph(&self) -> AstGraph { + let mut builder = AstGraphBuilder::new(); + builder.add_compile_unit(self); + builder.graph + } +} + +struct AstGraphBuilder { + graph: AstGraph, +} + +impl AstGraphBuilder { + fn new() -> Self { + Self { graph: Graph::new() } + } + + fn node(&mut self, label: impl Into) -> NodeIndex { + self.graph.add_node(label.into()) + } + + fn child(&mut self, parent: NodeIndex, label: impl Into) -> NodeIndex { + let child = self.node(label); + self.graph.add_edge(parent, child, String::new()); + child + } + + fn add_compile_unit(&mut self, compile_unit: &CompileUnit) -> NodeIndex { + let root = self.node(compile_unit.to_string()); + for decl in &compile_unit.global_decls { + self.add_global_decl(root, decl); + } + root + } + + fn add_global_decl(&mut self, parent: NodeIndex, decl: &GlobalDeclStmt) -> NodeIndex { + match decl { + GlobalDeclStmt::VarDecl(var_decl) => { + let node = self.child(parent, decl.to_string()); + self.add_var_decl(node, var_decl); + node + } + GlobalDeclStmt::FuncDecl(func_decl) => { + let node = self.child(parent, decl.to_string()); + self.add_func_decl(node, func_decl); + node + } + } + } + + fn add_var_decl(&mut self, parent: NodeIndex, var_decl: &VarDeclStmt) -> NodeIndex { + let node = self.child(parent, var_decl.to_string()); + for value in &var_decl.values { + self.add_var_decl_value(node, value); + } + node + } + + fn add_var_decl_value(&mut self, parent: NodeIndex, value: &VarDeclStmtValue) -> NodeIndex { + self.child(parent, value.to_string()) + } + + fn add_func_decl(&mut self, parent: NodeIndex, func_decl: &FuncDeclStmt) -> NodeIndex { + let node = self.child(parent, func_decl.to_string()); + let params = self.child(node, "Params"); + for param in &func_decl.params { + self.add_param(params, param); + } + self.add_block_stmt(node, &func_decl.body); + node + } + + fn add_param(&mut self, parent: NodeIndex, param: &Param) -> NodeIndex { + self.child(parent, param.to_string()) + } + + fn add_block_stmt(&mut self, parent: NodeIndex, block_stmt: &BlockStmt) -> NodeIndex { + let node = self.child(parent, block_stmt.to_string()); + for stmt in &block_stmt.statements { + self.add_statement(node, stmt); + } + node + } + + fn add_statement(&mut self, parent: NodeIndex, stmt: &Statement) -> NodeIndex { + match stmt { + Statement::Return(return_stmt) => { + let node = self.child(parent, stmt.to_string()); + self.add_return_stmt(node, return_stmt); + node + } + Statement::Block(block_stmt) => self.add_block_stmt(parent, block_stmt), + Statement::Expr(expr) => { + let node = self.child(parent, stmt.to_string()); + self.add_expr(node, expr); + node + } + Statement::VarDecl(var_decl) => { + let node = self.child(parent, stmt.to_string()); + self.add_var_decl(node, var_decl); + node + } + } + } + + fn add_return_stmt(&mut self, parent: NodeIndex, return_stmt: &ReturnStmt) -> NodeIndex { + match &return_stmt.value { + Some(expr) => self.add_expr(parent, expr), + None => self.child(parent, "Void"), + } + } + + fn add_expr(&mut self, parent: NodeIndex, expr: &Expr) -> NodeIndex { + match &expr.value { + ExprValue::IntLit(_) | ExprValue::Var(_) => self.child(parent, expr.value.to_string()), + ExprValue::BinaryOp { lhs, op: _, rhs } => { + let node = self.child(parent, expr.value.to_string()); + self.add_expr(node, lhs); + self.add_expr(node, rhs); + node + } + ExprValue::FuncCall(_, args) => { + let node = self.child(parent, expr.value.to_string()); + let args_node = self.child(node, "Args"); + for arg in args { + self.add_expr(args_node, arg); + } + node + } + ExprValue::Assign { lvalue, rvalue } => { + let node = self.child(parent, expr.value.to_string()); + self.add_expr(node, lvalue); + self.add_expr(node, rvalue); + node + } + } + } +} diff --git a/src/ast/mod.rs b/src/ast/mod.rs index cd40856..6d20066 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -1 +1,2 @@ +pub mod graph; pub mod types; diff --git a/src/ast/types.rs b/src/ast/types.rs index 6c15b08..623432f 100644 --- a/src/ast/types.rs +++ b/src/ast/types.rs @@ -1,4 +1,5 @@ use crate::{diagnostic::span::Span, frontend::types::{TokenValue, TypeIdent}}; +use std::fmt; pub struct CompileUnit { pub global_decls: Vec, @@ -109,4 +110,112 @@ pub struct Param { pub name: String, pub param_type: Type, pub span: Span, -} \ No newline at end of file +} + +impl fmt::Display for CompileUnit { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "CompileUnit") + } +} + +impl fmt::Display for GlobalDeclStmt { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + GlobalDeclStmt::VarDecl(_) => write!(f, "GlobalVarDecl"), + GlobalDeclStmt::FuncDecl(_) => write!(f, "FuncDecl"), + } + } +} + +impl fmt::Display for VarDeclStmt { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "VarDecl") + } +} + +impl fmt::Display for VarDeclStmtValue { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{} {}", self.var_type, self.name) + } +} + +impl fmt::Display for FuncDeclStmt { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{} {}", self.return_type, self.name) + } +} + +impl fmt::Display for BlockStmt { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "Block") + } +} + +impl fmt::Display for Statement { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Statement::Return(_) => write!(f, "ReturnStmt"), + Statement::Block(_) => write!(f, "BlockStmt"), + Statement::Expr(_) => write!(f, "ExprStmt"), + Statement::VarDecl(_) => write!(f, "VarDeclStmt"), + } + } +} + +impl fmt::Display for ReturnStmt { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "ReturnStmt") + } +} + +impl fmt::Display for Expr { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.value) + } +} + +impl fmt::Display for ExprValue { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + ExprValue::IntLit(value) => write!(f, "IntLit({})", value), + ExprValue::Var(name) => write!(f, "Var({})", name), + ExprValue::BinaryOp { op, .. } => write!(f, "BinaryOp({})", op), + ExprValue::FuncCall(name, _) => write!(f, "FuncCall({})", name), + ExprValue::Assign { .. } => write!(f, "Assign"), + } + } +} + +impl fmt::Display for Param { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{} {}", self.param_type, self.name) + } +} + +impl fmt::Display for BinaryOp { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let op = match self { + BinaryOp::Add => "+", + BinaryOp::Sub => "-", + BinaryOp::Mul => "*", + BinaryOp::Div => "/", + BinaryOp::Mod => "%", + BinaryOp::Equal => "==", + BinaryOp::NotEqual => "!=", + BinaryOp::Less => "<", + BinaryOp::LessEqual => "<=", + BinaryOp::Greater => ">", + BinaryOp::GreaterEqual => ">=", + }; + write!(f, "{}", op) + } +} + +impl fmt::Display for Type { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Type::Int => write!(f, "int"), + Type::Void => write!(f, "void"), + } + } +} diff --git a/src/frontend/parser.rs b/src/frontend/parser.rs index c96ec70..7359101 100644 --- a/src/frontend/parser.rs +++ b/src/frontend/parser.rs @@ -654,6 +654,7 @@ mod tests { use std::io::BufRead; use std::path::Path; use std::fs::File; + use crate::ast::graph::AstGraphExt; use crate::frontend::lexer::Lexer; use crate::utils::case_list::CaseList; use crate::utils::num_sequence::NumberSequence; @@ -686,7 +687,10 @@ mod tests { is_error = true; } let mut parser = Parser::new(tokens, diagnostics); - let _compile_unit = parser.parse_compile_unit(); + let compile_unit = parser.parse_compile_unit(); + let dot = compile_unit.to_dot(); + let case_name = case_list.get_case_name(case_no).unwrap().strip_suffix(".c").unwrap(); + std::fs::write(format!("output/{}.dot", case_name), dot).unwrap(); if !parser.diagnostics.is_empty() { parser.diagnostics.print(&format!("{}", case_path.display()), &full_text); is_error = true;