Skip to content

Commit a57a525

Browse files
committed
render to graphviz dot
1 parent fb0ddc7 commit a57a525

File tree

5 files changed

+122
-4
lines changed

5 files changed

+122
-4
lines changed

scopegraphs-lib/Cargo.toml

+4
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ description = "A port of [scopegraphs](https://pl.ewi.tudelft.nl/research/projec
1111

1212

1313
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
14+
[features]
15+
default = []
16+
dot = ["dep:dot"]
1417

1518
[dependencies]
1619
log = "0.4.20"
@@ -21,6 +24,7 @@ scopegraphs-regular-expressions = {path = "../scopegraphs-regular-expressions",
2124
scopegraphs-macros = {path = "../scopegraphs-macros", version = "0.1.0"}
2225
futures = "0.3.30"
2326
bumpalo = "3.14.0"
27+
dot = { version = "0.1.4", optional = true }
2428

2529
[dev-dependencies]
2630
scopegraphs = {path = "../scopegraphs"}

scopegraphs-lib/src/lib.rs

+3
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,6 @@ pub mod storage;
99
pub use label::Label;
1010
pub use scopegraph::*;
1111
pub use scopegraphs_macros;
12+
13+
#[cfg(feature = "dot")]
14+
pub mod render;

scopegraphs-lib/src/render.rs

+111
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
use crate::completeness::Completeness;
2+
use crate::{Scope, ScopeGraph};
3+
use dot::LabelText::LabelStr;
4+
use dot::{Edges, Id, LabelText, Nodes};
5+
use std::borrow::Cow;
6+
use std::fmt::Debug;
7+
use std::fs::File;
8+
use std::io;
9+
use std::io::Write;
10+
use std::path::Path;
11+
12+
pub trait RenderScopeData {
13+
/// Renders a scope (or probably rather, the data in a scope).
14+
/// Can return None if there's no data to render.
15+
fn render(&self) -> Option<String>;
16+
17+
/// Returns whether this scope is a definition of some variable
18+
///
19+
/// Defaults to whether the outcome of [`render`](RenderScopeData::render) is Some,
20+
/// because often non-definition scopes have no data associated with them.
21+
fn definition(&self) -> bool {
22+
self.render().is_some()
23+
}
24+
}
25+
26+
impl<LABEL: Clone + Debug, DATA: RenderScopeData + Clone, CMPL: Completeness<LABEL, DATA>>
27+
ScopeGraph<'_, LABEL, DATA, CMPL>
28+
{
29+
pub fn render<W: Write>(&self, output: &mut W, name: &str) -> io::Result<()> {
30+
dot::render(&(self, name), output)
31+
}
32+
33+
pub fn render_to(&self, path: impl AsRef<Path>) -> io::Result<()> {
34+
let path = path.as_ref();
35+
let mut w = File::create(path)?;
36+
let name = path
37+
.file_stem()
38+
.expect("path must have filename for File::create to work")
39+
.to_string_lossy();
40+
self.render(&mut w, &name)
41+
}
42+
}
43+
44+
impl<'a, LABEL: Clone + Debug, DATA: Clone + RenderScopeData, CMPL: Completeness<LABEL, DATA>>
45+
dot::Labeller<'a, Scope, (Scope, LABEL, Scope)> for (&ScopeGraph<'_, LABEL, DATA, CMPL>, &str)
46+
{
47+
fn graph_id(&'a self) -> Id<'a> {
48+
Id::new(self.1).unwrap()
49+
}
50+
51+
fn node_id(&'a self, n: &Scope) -> Id<'a> {
52+
Id::new(format!("s{}", n.0)).unwrap()
53+
}
54+
55+
fn node_label(&self, n: &Scope) -> LabelText {
56+
let data = self.0.get_data(*n);
57+
LabelText::label(data.render().unwrap_or_else(|| format!("scope {}", n.0)))
58+
}
59+
fn edge_label(&self, edge: &(Scope, LABEL, Scope)) -> LabelText {
60+
LabelText::label(format!("{:?}", edge.1))
61+
}
62+
63+
fn node_shape(&'a self, node: &Scope) -> Option<LabelText<'a>> {
64+
let data = self.0.get_data(*node);
65+
if data.definition() {
66+
Some(LabelStr(Cow::Borrowed("box")))
67+
} else {
68+
Some(LabelStr(Cow::Borrowed("circle")))
69+
}
70+
}
71+
}
72+
73+
impl<'a, LABEL: Clone, DATA: Clone, CMPL> dot::GraphWalk<'a, Scope, (Scope, LABEL, Scope)>
74+
for (&ScopeGraph<'_, LABEL, DATA, CMPL>, &str)
75+
{
76+
fn nodes(&'a self) -> Nodes<'a, Scope> {
77+
(0..self.0.inner_scope_graph.data.borrow().len())
78+
.map(Scope)
79+
.collect()
80+
}
81+
82+
fn edges(&'a self) -> Edges<'a, (Scope, LABEL, Scope)> {
83+
self.0
84+
.inner_scope_graph
85+
.edges
86+
.borrow()
87+
.iter()
88+
.enumerate()
89+
.flat_map(|(scope, edges)| {
90+
(*edges)
91+
.borrow()
92+
.iter()
93+
.flat_map(|(lbl, edges_with_lbl)| {
94+
edges_with_lbl
95+
.iter()
96+
.map(|edge| (Scope(scope), lbl.clone(), *edge))
97+
.collect::<Vec<_>>()
98+
})
99+
.collect::<Vec<_>>()
100+
})
101+
.collect()
102+
}
103+
104+
fn source(&'a self, edge: &(Scope, LABEL, Scope)) -> Scope {
105+
edge.0
106+
}
107+
108+
fn target(&'a self, edge: &(Scope, LABEL, Scope)) -> Scope {
109+
edge.2
110+
}
111+
}

scopegraphs-lib/src/scopegraph.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,10 @@ impl Debug for Scope {
2323

2424
#[derive(Debug)]
2525
pub struct InnerScopeGraph<'sg, LABEL, DATA> {
26-
storage: &'sg Storage,
26+
pub(crate) storage: &'sg Storage,
2727
#[allow(clippy::type_complexity)]
28-
edges: RefCell<Vec<&'sg RefCell<HashMap<LABEL, HashSet<Scope>>>>>, // FIXME: BTreeMap? Vectors? Whatever?
29-
data: RefCell<Vec<&'sg DATA>>,
28+
pub(crate) edges: RefCell<Vec<&'sg RefCell<HashMap<LABEL, HashSet<Scope>>>>>, // FIXME: BTreeMap? Vectors? Whatever?
29+
pub(crate) data: RefCell<Vec<&'sg DATA>>,
3030
}
3131

3232
impl<'sg, LABEL, DATA> InnerScopeGraph<'sg, LABEL, DATA> {

scopegraphs/Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,5 +19,5 @@ scopegraphs-regular-expressions = {path="../scopegraphs-regular-expressions", ve
1919

2020
[features]
2121
default = ["dot", "dynamic-regex"]
22-
dot = ["scopegraphs-regular-expressions/dot", "scopegraphs-macros/dot"]
22+
dot = ["scopegraphs-regular-expressions/dot", "scopegraphs-macros/dot", "scopegraphs-lib/dot"]
2323
dynamic-regex = ["scopegraphs-regular-expressions/dynamic"]

0 commit comments

Comments
 (0)