Skip to content

Commit

Permalink
Merge pull request #56 from froth/graphviz
Browse files Browse the repository at this point in the history
Implement graphviz printer
  • Loading branch information
froth authored May 29, 2024
2 parents cd3eff8 + 756a42d commit cfe2fe1
Show file tree
Hide file tree
Showing 9 changed files with 624 additions and 28 deletions.
270 changes: 248 additions & 22 deletions Cargo.lock

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ thiserror = "1.0.58"
strum = { version = "0.26", features = ["derive"] }
rustyline = {version= "14.0.0", features = ["with-file-history", "derive"]}
directories = "5.0.1"
graphviz-rust = "0.9.0"
uuid = { version = "1.8.0", features = ["v4"]}

[dev-dependencies]
assert_matches = "1.5"
Expand Down
6 changes: 6 additions & 0 deletions src/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,20 @@ use std::path::PathBuf;
#[derive(Debug, Parser)]
#[command(version, about)]
pub struct Args {
///
#[arg()]
pub file: Option<String>,

#[clap(long, env="LOX_HISTORY_FILE", default_value = get_default_history_file().into_os_string())]
pub history_file: PathBuf,

/// Verbose debug information
#[clap(short, long, default_value_t)]
pub verbose: bool,

/// Convert ast to graphviz instead of interpreting the code (only works for file input)
#[clap(short, long, default_value_t)]
pub graphviz: bool,
}

fn get_default_history_file() -> PathBuf {
Expand Down
103 changes: 103 additions & 0 deletions src/graphviz_converter/expression.rs
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
}
67 changes: 67 additions & 0 deletions src/graphviz_converter/mod.rs
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,
}
}
179 changes: 179 additions & 0 deletions src/graphviz_converter/statement.rs
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));
print
}

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))
}
Loading

0 comments on commit cfe2fe1

Please sign in to comment.