From 55aab85f46241ec6f49c0efef6423163728cd039 Mon Sep 17 00:00:00 2001 From: Frederick Roth Date: Wed, 24 Apr 2024 16:15:48 +0200 Subject: [PATCH 1/3] Start of graphviz ast exporter --- Cargo.lock | 270 ++++++++++++++++++++++++--- Cargo.toml | 2 + src/args.rs | 6 + src/graphviz_converter/expression.rs | 57 ++++++ src/graphviz_converter/mod.rs | 66 +++++++ src/graphviz_converter/statement.rs | 82 ++++++++ src/lox.rs | 12 +- src/main.rs | 7 +- 8 files changed, 477 insertions(+), 25 deletions(-) create mode 100644 src/graphviz_converter/expression.rs create mode 100644 src/graphviz_converter/mod.rs create mode 100644 src/graphviz_converter/statement.rs diff --git a/Cargo.lock b/Cargo.lock index 4b0ba0b..c00f1a9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -128,6 +128,15 @@ version = "2.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + [[package]] name = "cc" version = "1.0.90" @@ -177,7 +186,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn", + "syn 2.0.55", ] [[package]] @@ -201,6 +210,15 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" +[[package]] +name = "cpufeatures" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" +dependencies = [ + "libc", +] + [[package]] name = "crc32fast" version = "1.4.0" @@ -210,6 +228,16 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + [[package]] name = "datadriven" version = "0.8.0" @@ -229,6 +257,16 @@ dependencies = [ "powerfmt", ] +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + [[package]] name = "directories" version = "5.0.1" @@ -250,6 +288,21 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "dot-generator" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0aaac7ada45f71873ebce336491d1c1bc4a7c8042c7cea978168ad59e805b871" +dependencies = [ + "dot-structures", +] + +[[package]] +name = "dot-structures" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "675e35c02a51bb4d4618cb4885b3839ce6d1787c97b664474d9208d074742e20" + [[package]] name = "endian-type" version = "0.1.2" @@ -278,6 +331,12 @@ version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a0474425d51df81997e2f90a21591180b38eccf27292d755f3e30750225c175b" +[[package]] +name = "fastrand" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "658bd65b1cf4c852a3cc96f18a8ce7b5640f6b703f905c7d74532294c2a63984" + [[package]] name = "fd-lock" version = "4.0.2" @@ -367,7 +426,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.55", ] [[package]] @@ -400,11 +459,21 @@ dependencies = [ "slab", ] +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + [[package]] name = "getrandom" -version = "0.2.12" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" +checksum = "94b22e06ecb0110981051723910cbf0b5f5e09a2062dd7663334ee79a9d1286c" dependencies = [ "cfg-if", "libc", @@ -417,6 +486,22 @@ version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" +[[package]] +name = "graphviz-rust" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c33d03804e2ce21db5821f2beb4e54f844a8f90326e6bd99a1771dc54aef427" +dependencies = [ + "dot-generator", + "dot-structures", + "into-attr", + "into-attr-derive", + "pest", + "pest_derive", + "rand", + "tempfile", +] + [[package]] name = "hashbrown" version = "0.14.3" @@ -454,6 +539,28 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "into-attr" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18b48c537e49a709e678caec3753a7dba6854661a1eaa27675024283b3f8b376" +dependencies = [ + "dot-structures", +] + +[[package]] +name = "into-attr-derive" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecac7c1ae6cd2c6a3a64d1061a8bdc7f52ff62c26a831a2301e54c1b5d70d5b1" +dependencies = [ + "dot-generator", + "dot-structures", + "into-attr", + "quote", + "syn 1.0.109", +] + [[package]] name = "is_ci" version = "1.2.0" @@ -474,13 +581,12 @@ checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" [[package]] name = "libredox" -version = "0.0.1" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85c833ca1e66078851dba29046874e38f08b2c883700aa29a03ddd3b23814ee8" +checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ "bitflags 2.4.2", "libc", - "redox_syscall", ] [[package]] @@ -545,7 +651,7 @@ checksum = "dcf09caffaac8068c346b6df2a7fc27a177fd20b39421a39ce0a211bde679a6c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.55", ] [[package]] @@ -633,6 +739,51 @@ version = "4.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "caff54706df99d2a78a5a4e3455ff45448d81ef1bb63c22cd14052ca0e993a3f" +[[package]] +name = "pest" +version = "2.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "311fb059dee1a7b802f036316d790138c613a4e8b180c822e3925a662e9f0c95" +dependencies = [ + "memchr", + "thiserror", + "ucd-trie", +] + +[[package]] +name = "pest_derive" +version = "2.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f73541b156d32197eecda1a4014d7f868fd2bcb3c550d5386087cfba442bf69c" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c35eeed0a3fab112f75165fdc026b3913f4183133f19b49be773ac9ea966e8bd" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn 2.0.55", +] + +[[package]] +name = "pest_meta" +version = "2.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2adbf29bb9776f28caece835398781ab24435585fe0d4dc1374a61db5accedca" +dependencies = [ + "once_cell", + "pest", + "sha2", +] + [[package]] name = "phf" version = "0.11.2" @@ -663,7 +814,7 @@ dependencies = [ "phf_shared", "proc-macro2", "quote", - "syn", + "syn 2.0.55", ] [[package]] @@ -713,6 +864,12 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + [[package]] name = "proc-macro2" version = "1.0.79" @@ -756,29 +913,35 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ + "libc", + "rand_chacha", "rand_core", ] [[package]] -name = "rand_core" -version = "0.6.4" +name = "rand_chacha" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] [[package]] -name = "redox_syscall" -version = "0.4.1" +name = "rand_core" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "bitflags 1.3.2", + "getrandom", ] [[package]] name = "redox_users" -version = "0.4.4" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a18479200779601e498ada4e8c1e1f50e3ee19deb0259c25825a98b5603b2cb4" +checksum = "bd283d9651eeda4b2a83a43c1c91b266c40fd76ecd39a50a8c630ae69dc72891" dependencies = [ "getrandom", "libredox", @@ -800,12 +963,14 @@ dependencies = [ "datadriven", "directories", "float_eq", + "graphviz-rust", "miette", "phf", "rustyline", "serde_json", "strum", "thiserror", + "uuid", ] [[package]] @@ -864,7 +1029,7 @@ checksum = "e5af959c8bf6af1aff6d2b463a57f71aae53d1332da58419e30ad8dc7011d951" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.55", ] [[package]] @@ -905,7 +1070,7 @@ checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.55", ] [[package]] @@ -919,6 +1084,17 @@ dependencies = [ "serde", ] +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + [[package]] name = "siphasher" version = "0.3.11" @@ -971,7 +1147,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn", + "syn 2.0.55", ] [[package]] @@ -995,6 +1171,17 @@ version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7401a30af6cb5818bb64852270bb722533397edcfc7344954a38f420819ece2" +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "syn" version = "2.0.55" @@ -1028,6 +1215,18 @@ dependencies = [ "yaml-rust", ] +[[package]] +name = "tempfile" +version = "3.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" +dependencies = [ + "cfg-if", + "fastrand", + "rustix", + "windows-sys 0.52.0", +] + [[package]] name = "terminal_size" version = "0.3.0" @@ -1066,7 +1265,7 @@ checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.55", ] [[package]] @@ -1100,6 +1299,18 @@ dependencies = [ "time-core", ] +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "ucd-trie" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" + [[package]] name = "unicode-ident" version = "1.0.12" @@ -1130,6 +1341,21 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" +[[package]] +name = "uuid" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a183cf7feeba97b4dd1c0d46788634f6221d87fa961b305bed08c851829efcc0" +dependencies = [ + "getrandom", +] + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + [[package]] name = "walkdir" version = "2.5.0" diff --git a/Cargo.toml b/Cargo.toml index 6cfe579..654f7f2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/src/args.rs b/src/args.rs index a1a0e6d..c32a995 100644 --- a/src/args.rs +++ b/src/args.rs @@ -5,14 +5,20 @@ use std::path::PathBuf; #[derive(Debug, Parser)] #[command(version, about)] pub struct Args { + /// #[arg()] pub file: Option, #[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 { diff --git a/src/graphviz_converter/expression.rs b/src/graphviz_converter/expression.rs new file mode 100644 index 0000000..184c71c --- /dev/null +++ b/src/graphviz_converter/expression.rs @@ -0,0 +1,57 @@ +use graphviz_rust::{dot_generator::*, dot_structures::*}; + +use crate::{ + ast::expr::{Expr, ExprType}, + 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(_, _, _) => todo!(), + ExprType::Logical(_, _, _) => todo!(), + 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(_, _) => todo!(), + ExprType::Get(expr, name) => { + single_child(format!("Get expression \"{}\"", name.name).as_str(), expr) + } + ExprType::Set(_, _, _) => todo!(), + 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 mut expr_repr = expression.to_graphviz(); + node.stmts.append(&mut expr_repr.stmts); + node.push(edge!(node.id.clone() => expr_repr.id.clone())); + node +} diff --git a/src/graphviz_converter/mod.rs b/src/graphviz_converter/mod.rs new file mode 100644 index 0000000..33653f2 --- /dev/null +++ b/src/graphviz_converter/mod.rs @@ -0,0 +1,66 @@ +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, + 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) { + self.stmts.append(&mut stmts); + } + + fn push>(&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) { + let graph = to_graphviz(statements); + print!("{}", graph.print(&mut PrinterContext::default())); +} + +fn to_graphviz(statements: Vec) -> Graph { + let nodes = 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(); + Graph::DiGraph { + id: id!("id"), + strict: true, + stmts: nodes, + } +} diff --git a/src/graphviz_converter/statement.rs b/src/graphviz_converter/statement.rs new file mode 100644 index 0000000..efcaca3 --- /dev/null +++ b/src/graphviz_converter/statement.rs @@ -0,0 +1,82 @@ +use crate::ast::{ + expr::Expr, + stmt::{self, Function, StmtType}, +}; +use graphviz_rust::{ + attributes::{rank, SubgraphAttributes}, + 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 } => todo!(), + StmtType::Function(f) => function(f, "fun"), + StmtType::Return(_) => todo!(), + StmtType::Block(_) => todo!(), + StmtType::If { + condition, + then_stmt, + else_stmt, + } => todo!(), + StmtType::While { condition, body } => todo!(), + StmtType::Class { + name, + methods, + superclass, + } => todo!(), + } + } +} + +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 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})"); + let mut node = GraphvizRepr::single(stmt(label.as_str())); + let node_id = node.id.clone(); + let (ids, stmts): (Vec<_>, Vec<_>) = function + .body + .iter() + .map(|s| s.to_graphviz()) + .map(|g| (g.id, g.stmts)) + .unzip(); + let mut body_stmts: Vec = stmts.into_iter().flatten().rev().collect(); + let mut subgraph: Subgraph = subgraph!(esc random_cluster_id()); + subgraph.stmts.append(&mut body_stmts); + subgraph.stmts.push(attr!("style", "dotted").into()); + subgraph.stmts.push(attr!("label", "body").into()); + node.push(subgraph); + ids.into_iter() + .for_each(|id| node.push(edge!(node_id.clone() => id; attr!("style", "dotted")))); + node +} + +fn stmt(label: &str) -> Node { + node!(esc random_id(); attr!("shape", "rectangle"), attr!("label", esc label)) +} diff --git a/src/lox.rs b/src/lox.rs index 2eacf31..bf74d3f 100644 --- a/src/lox.rs +++ b/src/lox.rs @@ -1,6 +1,7 @@ use miette::NamedSource; use crate::{ + graphviz_converter, interpreter::{value::Value, Interpreter}, parser::{parser_error::ParserError::ExpectedSemicolon, Parser}, resolver::Resolver, @@ -10,13 +11,15 @@ use crate::{ pub struct Lox { interpreter: Interpreter, verbose: bool, + graphviz: bool, } impl Lox { - pub fn new(verbose: bool) -> Self { + pub fn new(verbose: bool, graphviz: bool) -> Self { Self { interpreter: Interpreter::new(), verbose, + graphviz, } } @@ -25,7 +28,11 @@ impl Lox { let statements = Parser::parse(tokens, self.verbose)?; let locals = Resolver::resolve(&statements, self.verbose)?; self.interpreter.add_locals(locals); - self.interpreter.interpret(&statements)?; + if self.graphviz { + graphviz_converter::print_graphviz(statements); + } else { + self.interpreter.interpret(&statements)?; + } Ok(()) } @@ -80,6 +87,7 @@ mod lox_tests { Self { interpreter: Interpreter::from_printer(printer), verbose: false, + graphviz: false, } } } diff --git a/src/main.rs b/src/main.rs index 8cd3de6..666c057 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,6 @@ mod args; mod ast; +mod graphviz_converter; mod interpreter; mod lox; mod parser; @@ -21,7 +22,11 @@ use rustyline::{ fn main() { let args = Args::parse(); - let lox = Lox::new(args.verbose); + if args.file.is_none() && args.graphviz { + eprintln!("graphviz works only with input file"); + std::process::exit(5) + } + let lox = Lox::new(args.verbose, args.graphviz); let result = match args.file { Some(file) => run_file(lox, file), None => run_prompt(lox, args).into_diagnostic(), From a9b04d910ad99d4777fbfdc9d7140547532861cb Mon Sep 17 00:00:00 2001 From: Frederick Roth Date: Wed, 8 May 2024 16:17:33 +0200 Subject: [PATCH 2/3] Fix node ordering --- src/graphviz_converter/mod.rs | 3 +- src/graphviz_converter/statement.rs | 47 +++++++++++++++++++++++------ src/interpreter/statement.rs | 6 ++-- 3 files changed, 42 insertions(+), 14 deletions(-) diff --git a/src/graphviz_converter/mod.rs b/src/graphviz_converter/mod.rs index 33653f2..5adcfc7 100644 --- a/src/graphviz_converter/mod.rs +++ b/src/graphviz_converter/mod.rs @@ -50,7 +50,7 @@ pub fn print_graphviz(statements: Vec) { } fn to_graphviz(statements: Vec) -> Graph { - let nodes = statements + let mut nodes: Vec = statements .iter() .map(|s| { let mut stmts = s.to_graphviz().stmts; @@ -58,6 +58,7 @@ fn to_graphviz(statements: Vec) -> Graph { subgraph!(esc random_cluster_id(), stmts).into() }) .collect(); + nodes.push(attr!("nodesep", ".6").into()); Graph::DiGraph { id: id!("id"), strict: true, diff --git a/src/graphviz_converter/statement.rs b/src/graphviz_converter/statement.rs index efcaca3..0d5dd56 100644 --- a/src/graphviz_converter/statement.rs +++ b/src/graphviz_converter/statement.rs @@ -1,4 +1,5 @@ use crate::ast::{ + self, expr::Expr, stmt::{self, Function, StmtType}, }; @@ -30,7 +31,7 @@ impl GraphvizConverter for StmtType { condition, then_stmt, else_stmt, - } => todo!(), + } => convert_if(condition, then_stmt, else_stmt.as_deref()), StmtType::While { condition, body } => todo!(), StmtType::Class { name, @@ -60,20 +61,46 @@ fn function(function: &Function, function_type: &str) -> GraphvizRepr { let label = format!("{function_type} {name}({parameters})"); let mut node = GraphvizRepr::single(stmt(label.as_str())); let node_id = node.id.clone(); - let (ids, stmts): (Vec<_>, Vec<_>) = function + let (body_ids, body_stmts): (Vec<_>, Vec<_>) = function .body .iter() .map(|s| s.to_graphviz()) .map(|g| (g.id, g.stmts)) .unzip(); - let mut body_stmts: Vec = stmts.into_iter().flatten().rev().collect(); - let mut subgraph: Subgraph = subgraph!(esc random_cluster_id()); - subgraph.stmts.append(&mut body_stmts); - subgraph.stmts.push(attr!("style", "dotted").into()); - subgraph.stmts.push(attr!("label", "body").into()); - node.push(subgraph); - ids.into_iter() - .for_each(|id| node.push(edge!(node_id.clone() => id; attr!("style", "dotted")))); + let mut body_stmts: Vec = body_stmts.into_iter().flatten().collect(); + let mut body_subgraph = subgraph!(esc random_cluster_id()); + body_subgraph.stmts.append(&mut body_stmts); + body_ids.windows(2).for_each(|window| { + body_subgraph + .stmts + .push(edge!(window[0].clone() => window[1].clone(); attr!("style", "invis")).into()) + }); + body_subgraph.stmts.push(attr!("style", "dotted").into()); + body_subgraph.stmts.push(attr!("label", "body").into()); + let mut rank_subgraph = subgraph!(esc random_id()); + body_ids.iter().for_each(|id| { + rank_subgraph + .stmts + .push(Node::new(id.clone(), vec![]).into()) + }); + rank_subgraph.stmts.push(attr!("rank", "min").into()); + body_subgraph.stmts.push(rank_subgraph.into()); + node.push(body_subgraph); + body_ids.into_iter().for_each(|id| { + node.push(edge!(node_id.clone() => id; attr!("style", "dashed"), attr!("weight","0.9"))) + }); + node +} + +fn convert_if( + condition: &Expr, + then_stmt: &stmt::Stmt, + else_stmt: Option<&stmt::Stmt>, +) -> GraphvizRepr { + let mut node = GraphvizRepr::single(stmt("if")); + let condition = condition.to_graphviz(); + node.append(condition.stmts); + node.push(edge!(node.id.clone() => condition.id; attr!("label", "condition"))); node } diff --git a/src/interpreter/statement.rs b/src/interpreter/statement.rs index acb36af..4797c2a 100644 --- a/src/interpreter/statement.rs +++ b/src/interpreter/statement.rs @@ -36,7 +36,7 @@ impl Interpreter { condition, then_stmt, else_stmt, - } => self.execute_if(condition, then_stmt, else_stmt)?, + } => self.execute_if(condition, then_stmt, else_stmt.as_deref())?, While { condition, body } => self.execute_while(condition, body.as_ref())?, Function(function) => { self.define_function(&function.name, &function.parameters, &function.body)? @@ -157,12 +157,12 @@ impl Interpreter { &mut self, condition: &Expr, then_stmt: &Stmt, - else_stmt: &Option>, + else_stmt: Option<&Stmt>, ) -> OrReturnResult<()> { if self.interpret_expr(condition)?.is_truthy() { self.interpret_stmt(then_stmt)?; } else if let Some(else_stmt) = else_stmt { - self.interpret_stmt(else_stmt.as_ref())?; + self.interpret_stmt(else_stmt)?; } Ok(()) } From 756a42d9336d4103cc4465857efcfb6ab4106b07 Mon Sep 17 00:00:00 2001 From: Frederick Roth Date: Wed, 29 May 2024 15:25:08 +0200 Subject: [PATCH 3/3] Implement missing graphviz --- src/graphviz_converter/expression.rs | 60 +++++++++-- src/graphviz_converter/statement.rs | 154 +++++++++++++++++++-------- 2 files changed, 165 insertions(+), 49 deletions(-) diff --git a/src/graphviz_converter/expression.rs b/src/graphviz_converter/expression.rs index 184c71c..1dedb7b 100644 --- a/src/graphviz_converter/expression.rs +++ b/src/graphviz_converter/expression.rs @@ -1,7 +1,11 @@ use graphviz_rust::{dot_generator::*, dot_structures::*}; use crate::{ - ast::expr::{Expr, ExprType}, + ast::{ + expr::{Expr, ExprType}, + name::Name, + token::Token, + }, graphviz_converter::random_id, }; @@ -19,8 +23,8 @@ impl GraphvizConverter for ExprType { ExprType::Assign(name, expr) => { single_child(format!("Assignment to \"{}\"", name.name).as_str(), expr) } - ExprType::Binary(_, _, _) => todo!(), - ExprType::Logical(_, _, _) => todo!(), + 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())) @@ -31,11 +35,11 @@ impl GraphvizConverter for ExprType { ExprType::Variable(name_expr) => { GraphvizRepr::single(expr(format!("Variable: {}", name_expr.name).as_str())) } - ExprType::Call(_, _) => todo!(), + ExprType::Call(callee, arguments) => call(callee, arguments), ExprType::Get(expr, name) => { single_child(format!("Get expression \"{}\"", name.name).as_str(), expr) } - ExprType::Set(_, _, _) => todo!(), + 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())) @@ -50,8 +54,50 @@ fn expr(label: &str) -> Node { fn single_child(label: &str, expression: &Expr) -> GraphvizRepr { let mut node = GraphvizRepr::single(expr(label)); - let mut expr_repr = expression.to_graphviz(); - node.stmts.append(&mut expr_repr.stmts); + 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 +} diff --git a/src/graphviz_converter/statement.rs b/src/graphviz_converter/statement.rs index 0d5dd56..d89951c 100644 --- a/src/graphviz_converter/statement.rs +++ b/src/graphviz_converter/statement.rs @@ -1,13 +1,9 @@ use crate::ast::{ - self, expr::Expr, + name::{Name, NameExpr}, stmt::{self, Function, StmtType}, }; -use graphviz_rust::{ - attributes::{rank, SubgraphAttributes}, - dot_generator::*, - dot_structures::*, -}; +use graphviz_rust::{dot_generator::*, dot_structures::*}; use std::fmt::Write; use super::{random_cluster_id, random_id, GraphvizConverter, GraphvizRepr}; @@ -23,21 +19,23 @@ impl GraphvizConverter for StmtType { match self { StmtType::Expression(expr) => single_expr("Expr", expr), StmtType::Print(expr) => single_expr("print", expr), - StmtType::Var { name, initializer } => todo!(), + StmtType::Var { name, initializer } => { + single_with_option_expr(format!("var {}", name).as_str(), initializer) + } StmtType::Function(f) => function(f, "fun"), - StmtType::Return(_) => todo!(), - StmtType::Block(_) => todo!(), + 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 } => todo!(), + StmtType::While { condition, body } => convert_while(condition, body), StmtType::Class { name, methods, superclass, - } => todo!(), + } => class(name, methods, superclass), } } } @@ -50,6 +48,42 @@ fn single_expr(label: &str, expr: &Expr) -> GraphvizRepr { print } +fn rank_subgraph(ids: &[NodeId]) -> (Subgraph, Vec) { + 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 = 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(); @@ -59,37 +93,7 @@ fn function(function: &Function, function_type: &str) -> GraphvizRepr { .for_each(|arg| write!(&mut parameters, "{arg}, ").unwrap()); let parameters = parameters.trim_end_matches(", "); let label = format!("{function_type} {name}({parameters})"); - let mut node = GraphvizRepr::single(stmt(label.as_str())); - let node_id = node.id.clone(); - let (body_ids, body_stmts): (Vec<_>, Vec<_>) = function - .body - .iter() - .map(|s| s.to_graphviz()) - .map(|g| (g.id, g.stmts)) - .unzip(); - let mut body_stmts: Vec = body_stmts.into_iter().flatten().collect(); - let mut body_subgraph = subgraph!(esc random_cluster_id()); - body_subgraph.stmts.append(&mut body_stmts); - body_ids.windows(2).for_each(|window| { - body_subgraph - .stmts - .push(edge!(window[0].clone() => window[1].clone(); attr!("style", "invis")).into()) - }); - body_subgraph.stmts.push(attr!("style", "dotted").into()); - body_subgraph.stmts.push(attr!("label", "body").into()); - let mut rank_subgraph = subgraph!(esc random_id()); - body_ids.iter().for_each(|id| { - rank_subgraph - .stmts - .push(Node::new(id.clone(), vec![]).into()) - }); - rank_subgraph.stmts.push(attr!("rank", "min").into()); - body_subgraph.stmts.push(rank_subgraph.into()); - node.push(body_subgraph); - body_ids.into_iter().for_each(|id| { - node.push(edge!(node_id.clone() => id; attr!("style", "dashed"), attr!("weight","0.9"))) - }); - node + block(&function.body, label.as_str()) } fn convert_if( @@ -98,9 +102,75 @@ fn convert_if( 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) -> 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) -> 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 = 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 }