-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #56 from froth/graphviz
Implement graphviz printer
- Loading branch information
Showing
9 changed files
with
624 additions
and
28 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
use graphviz_rust::{dot_generator::*, dot_structures::*}; | ||
|
||
use crate::{ | ||
ast::{ | ||
expr::{Expr, ExprType}, | ||
name::Name, | ||
token::Token, | ||
}, | ||
graphviz_converter::random_id, | ||
}; | ||
|
||
use super::{GraphvizConverter, GraphvizRepr}; | ||
|
||
impl GraphvizConverter for Expr { | ||
fn to_graphviz(&self) -> GraphvizRepr { | ||
self.expr_type.to_graphviz() | ||
} | ||
} | ||
|
||
impl GraphvizConverter for ExprType { | ||
fn to_graphviz(&self) -> GraphvizRepr { | ||
match self { | ||
ExprType::Assign(name, expr) => { | ||
single_child(format!("Assignment to \"{}\"", name.name).as_str(), expr) | ||
} | ||
ExprType::Binary(left, token, right) => binary(token, left, right), | ||
ExprType::Logical(left, token, right) => binary(token, left, right), | ||
ExprType::Grouping(expr) => single_child("Gouping", expr), | ||
ExprType::Literal(literal) => { | ||
GraphvizRepr::single(expr(format!("Literal: {}", literal).as_str())) | ||
} | ||
ExprType::Unary(operator, expr) => { | ||
single_child(format!("Unary {}", operator.token_type).as_str(), expr) | ||
} | ||
ExprType::Variable(name_expr) => { | ||
GraphvizRepr::single(expr(format!("Variable: {}", name_expr.name).as_str())) | ||
} | ||
ExprType::Call(callee, arguments) => call(callee, arguments), | ||
ExprType::Get(expr, name) => { | ||
single_child(format!("Get expression \"{}\"", name.name).as_str(), expr) | ||
} | ||
ExprType::Set(object, name, value) => set(object, &name.name, value), | ||
ExprType::This => GraphvizRepr::single(expr("this")), | ||
ExprType::Super(name) => { | ||
GraphvizRepr::single(expr(format!("super.{}", name.name).as_str())) | ||
} | ||
} | ||
} | ||
} | ||
|
||
fn expr(label: &str) -> Node { | ||
node!(esc random_id(); attr!("label", esc label.replace('\"', "\\\""))) | ||
} | ||
|
||
fn single_child(label: &str, expression: &Expr) -> GraphvizRepr { | ||
let mut node = GraphvizRepr::single(expr(label)); | ||
let expr_repr = expression.to_graphviz(); | ||
node.stmts.extend(expr_repr.stmts); | ||
node.push(edge!(node.id.clone() => expr_repr.id.clone())); | ||
node | ||
} | ||
|
||
fn binary(token: &Token, left: &Expr, right: &Expr) -> GraphvizRepr { | ||
let mut node = GraphvizRepr::single(expr(token.token_type.to_string().as_str())); | ||
let left = left.to_graphviz(); | ||
node.stmts.extend(left.stmts); | ||
node.push(edge!(node.id.clone() => left.id.clone(); attr!("label", "left"))); | ||
|
||
let right = right.to_graphviz(); | ||
node.stmts.extend(right.stmts); | ||
node.push(edge!(node.id.clone() => right.id.clone(); attr!("label", "right"))); | ||
node | ||
} | ||
|
||
fn call(callee: &Expr, arguments: &[Expr]) -> GraphvizRepr { | ||
let mut node = GraphvizRepr::single(expr("call")); | ||
let callee = callee.to_graphviz(); | ||
node.stmts.extend(callee.stmts); | ||
node.push(edge!(node.id.clone() => callee.id.clone(); attr!("label", "callee"))); | ||
|
||
let mut args = GraphvizRepr::single(expr("arguments")); | ||
arguments.iter().for_each(|a| { | ||
let a = a.to_graphviz(); | ||
args.append(a.stmts); | ||
args.push(edge!(args.id.clone() => a.id)) | ||
}); | ||
|
||
node.stmts.extend(args.stmts); | ||
node.push(edge!(node.id.clone() => args.id.clone(); attr!("label", "arguments"))); | ||
node | ||
} | ||
|
||
fn set(object: &Expr, name: &Name, value: &Expr) -> GraphvizRepr { | ||
let mut node = GraphvizRepr::single(expr(format!("set {}", name).as_str())); | ||
let object = object.to_graphviz(); | ||
node.stmts.extend(object.stmts); | ||
node.push(edge!(node.id.clone() => object.id.clone(); attr!("label", "object"))); | ||
|
||
let value = value.to_graphviz(); | ||
node.stmts.extend(value.stmts); | ||
node.push(edge!(node.id.clone() => value.id.clone(); attr!("label", "value"))); | ||
node | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
mod expression; | ||
mod statement; | ||
use graphviz_rust::{ | ||
dot_generator::*, | ||
dot_structures::{Stmt as GVStmt, *}, | ||
printer::{DotPrinter, PrinterContext}, | ||
}; | ||
use uuid::Uuid; | ||
|
||
use crate::ast::stmt::Stmt; | ||
|
||
struct GraphvizRepr { | ||
stmts: Vec<GVStmt>, | ||
id: NodeId, | ||
} | ||
|
||
impl GraphvizRepr { | ||
fn single(node: Node) -> Self { | ||
let id = node.id.clone(); | ||
Self { | ||
stmts: vec![node.into()], | ||
id, | ||
} | ||
} | ||
|
||
fn append(&mut self, mut stmts: Vec<GVStmt>) { | ||
self.stmts.append(&mut stmts); | ||
} | ||
|
||
fn push<A: Into<GVStmt>>(&mut self, value: A) { | ||
self.stmts.push(value.into()) | ||
} | ||
} | ||
|
||
trait GraphvizConverter { | ||
fn to_graphviz(&self) -> GraphvizRepr; | ||
} | ||
|
||
fn random_id() -> String { | ||
Uuid::new_v4().to_string() | ||
} | ||
|
||
fn random_cluster_id() -> String { | ||
format!("cluster_{}", Uuid::new_v4()) | ||
} | ||
|
||
pub fn print_graphviz(statements: Vec<Stmt>) { | ||
let graph = to_graphviz(statements); | ||
print!("{}", graph.print(&mut PrinterContext::default())); | ||
} | ||
|
||
fn to_graphviz(statements: Vec<Stmt>) -> Graph { | ||
let mut nodes: Vec<GVStmt> = statements | ||
.iter() | ||
.map(|s| { | ||
let mut stmts = s.to_graphviz().stmts; | ||
stmts.push(attr!("style", "dotted").into()); | ||
subgraph!(esc random_cluster_id(), stmts).into() | ||
}) | ||
.collect(); | ||
nodes.push(attr!("nodesep", ".6").into()); | ||
Graph::DiGraph { | ||
id: id!("id"), | ||
strict: true, | ||
stmts: nodes, | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,179 @@ | ||
use crate::ast::{ | ||
expr::Expr, | ||
name::{Name, NameExpr}, | ||
stmt::{self, Function, StmtType}, | ||
}; | ||
use graphviz_rust::{dot_generator::*, dot_structures::*}; | ||
use std::fmt::Write; | ||
|
||
use super::{random_cluster_id, random_id, GraphvizConverter, GraphvizRepr}; | ||
|
||
impl GraphvizConverter for stmt::Stmt { | ||
fn to_graphviz(&self) -> GraphvizRepr { | ||
self.stmt_type.to_graphviz() | ||
} | ||
} | ||
|
||
impl GraphvizConverter for StmtType { | ||
fn to_graphviz(&self) -> GraphvizRepr { | ||
match self { | ||
StmtType::Expression(expr) => single_expr("Expr", expr), | ||
StmtType::Print(expr) => single_expr("print", expr), | ||
StmtType::Var { name, initializer } => { | ||
single_with_option_expr(format!("var {}", name).as_str(), initializer) | ||
} | ||
StmtType::Function(f) => function(f, "fun"), | ||
StmtType::Return(expr) => single_with_option_expr("return", expr), | ||
StmtType::Block(stmts) => block(stmts, "block"), | ||
StmtType::If { | ||
condition, | ||
then_stmt, | ||
else_stmt, | ||
} => convert_if(condition, then_stmt, else_stmt.as_deref()), | ||
StmtType::While { condition, body } => convert_while(condition, body), | ||
StmtType::Class { | ||
name, | ||
methods, | ||
superclass, | ||
} => class(name, methods, superclass), | ||
} | ||
} | ||
} | ||
|
||
fn single_expr(label: &str, expr: &Expr) -> GraphvizRepr { | ||
let mut print = GraphvizRepr::single(stmt(label)); | ||
let expr = expr.to_graphviz(); | ||
print.append(expr.stmts); | ||
print.push(edge!(print.id.clone() => expr.id)); | ||
} | ||
|
||
fn rank_subgraph(ids: &[NodeId]) -> (Subgraph, Vec<Stmt>) { | ||
let mut rank_subgraph = subgraph!(esc random_id()); | ||
ids.iter().for_each(|id| { | ||
rank_subgraph | ||
.stmts | ||
.push(Node::new(id.clone(), vec![]).into()) | ||
}); | ||
rank_subgraph.stmts.push(attr!("rank", "same").into()); | ||
let invisible_edges = ids | ||
.windows(2) | ||
.map(|window| edge!(window[0].clone() => window[1].clone(); attr!("style", "invis")).into()) | ||
.collect(); | ||
(rank_subgraph, invisible_edges) | ||
} | ||
|
||
fn block(block: &[stmt::Stmt], label: &str) -> GraphvizRepr { | ||
let mut node = GraphvizRepr::single(stmt(label)); | ||
let node_id = node.id.clone(); | ||
let (ids, stmts): (Vec<_>, Vec<_>) = block | ||
.iter() | ||
.map(|s| s.to_graphviz()) | ||
.map(|g| (g.id, g.stmts)) | ||
.unzip(); | ||
let mut stmts: Vec<Stmt> = stmts.into_iter().flatten().collect(); | ||
let mut subgraph = subgraph!(esc random_cluster_id()); | ||
subgraph.stmts.append(&mut stmts); | ||
subgraph.stmts.push(attr!("style", "dotted").into()); | ||
let (rank_subgraph, mut rank_edges) = rank_subgraph(&ids); | ||
subgraph.stmts.push(rank_subgraph.into()); | ||
subgraph.stmts.append(&mut rank_edges); | ||
node.push(subgraph); | ||
ids.into_iter() | ||
.for_each(|id| node.push(edge!(node_id.clone() => id; attr!("style", "dashed")))); | ||
node | ||
} | ||
|
||
fn function(function: &Function, function_type: &str) -> GraphvizRepr { | ||
let name = &function.name; | ||
let mut parameters = String::new(); | ||
function | ||
.parameters | ||
.iter() | ||
.for_each(|arg| write!(&mut parameters, "{arg}, ").unwrap()); | ||
let parameters = parameters.trim_end_matches(", "); | ||
let label = format!("{function_type} {name}({parameters})"); | ||
block(&function.body, label.as_str()) | ||
} | ||
|
||
fn convert_if( | ||
condition: &Expr, | ||
then_stmt: &stmt::Stmt, | ||
else_stmt: Option<&stmt::Stmt>, | ||
) -> GraphvizRepr { | ||
let mut node = GraphvizRepr::single(stmt("if")); | ||
let mut ids = vec![]; | ||
let condition = condition.to_graphviz(); | ||
ids.push(condition.id.clone()); | ||
node.append(condition.stmts); | ||
node.push(edge!(node.id.clone() => condition.id; attr!("label", "condition"))); | ||
let then = then_stmt.to_graphviz(); | ||
ids.push(then.id.clone()); | ||
node.append(then.stmts); | ||
node.push(edge!(node.id.clone() => then.id; attr!("label", "then"))); | ||
else_stmt.into_iter().for_each(|e| { | ||
let else_node = e.to_graphviz(); | ||
ids.push(else_node.id.clone()); | ||
node.append(else_node.stmts); | ||
node.push(edge!(node.id.clone() => else_node.id; attr!("label", "else"))); | ||
}); | ||
node | ||
} | ||
|
||
fn convert_while(condition: &Expr, body: &stmt::Stmt) -> GraphvizRepr { | ||
let mut node = GraphvizRepr::single(stmt("while")); | ||
let mut ids = vec![]; | ||
let condition = condition.to_graphviz(); | ||
let mut subgraph = subgraph!(esc random_id()); | ||
ids.push(condition.id.clone()); | ||
subgraph.stmts.extend(condition.stmts); | ||
let body = body.to_graphviz(); | ||
ids.push(body.id.clone()); | ||
subgraph.stmts.extend(body.stmts); | ||
let (rank_subgraph, mut rank_edges) = rank_subgraph(&ids); | ||
subgraph.stmts.push(rank_subgraph.into()); | ||
node.push(edge!(node.id.clone() => condition.id; attr!("label", "condition"))); | ||
node.push(edge!(node.id.clone() => body.id; attr!("label", "body"))); | ||
node.stmts.push(subgraph.into()); | ||
node.stmts.append(&mut rank_edges); | ||
node | ||
} | ||
|
||
fn single_with_option_expr(label: &str, expr: &Option<Expr>) -> GraphvizRepr { | ||
let mut node = GraphvizRepr::single(stmt(label)); | ||
expr.iter().for_each(|e| { | ||
let e = e.to_graphviz(); | ||
node.append(e.stmts); | ||
node.stmts.push(edge!(node.id.clone() => e.id).into()); | ||
}); | ||
node | ||
} | ||
|
||
fn class(name: &Name, methods: &[Function], superclass: &Option<NameExpr>) -> GraphvizRepr { | ||
let label = match superclass { | ||
Some(superclass) => format!("class {} < {}", name, superclass.name), | ||
None => format!("class {}", name), | ||
}; | ||
let mut node = GraphvizRepr::single(stmt(label.as_str())); | ||
let node_id = node.id.clone(); | ||
let (ids, stmts): (Vec<_>, Vec<_>) = methods | ||
.iter() | ||
.map(|f| function(f, "method")) | ||
.map(|g| (g.id, g.stmts)) | ||
.unzip(); | ||
let mut stmts: Vec<Stmt> = stmts.into_iter().flatten().collect(); | ||
let mut subgraph = subgraph!(esc random_cluster_id()); | ||
subgraph.stmts.append(&mut stmts); | ||
subgraph.stmts.push(attr!("style", "dotted").into()); | ||
let (rank_subgraph, mut rank_edges) = rank_subgraph(&ids); | ||
subgraph.stmts.push(rank_subgraph.into()); | ||
subgraph.stmts.append(&mut rank_edges); | ||
node.push(subgraph); | ||
ids.into_iter() | ||
.for_each(|id| node.push(edge!(node_id.clone() => id; attr!("style", "dashed")))); | ||
node | ||
} | ||
|
||
fn stmt(label: &str) -> Node { | ||
node!(esc random_id(); attr!("shape", "rectangle"), attr!("label", esc label)) | ||
} |
Oops, something went wrong.