Skip to content

Commit a2dc720

Browse files
committed
render paths & resolved paths on scopegraphs
1 parent e6eaa73 commit a2dc720

File tree

4 files changed

+255
-22
lines changed

4 files changed

+255
-22
lines changed

scopegraphs-render-docs/src/attrs.rs

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -96,13 +96,20 @@ impl quote::ToTokens for Attrs {
9696
.map(Attr::expect_diagram_entry_text)
9797
.collect::<Vec<_>>();
9898

99-
tokens.extend(quote! {#[doc = "```rust"]});
100-
for i in &diagram {
101-
tokens.extend(quote! {
102-
#[doc = #i]
103-
});
99+
if !diagram
100+
.iter()
101+
.filter(|i| !i.trim().is_empty())
102+
.all(|i| i.trim().starts_with('#'))
103+
&& !diagram.is_empty()
104+
{
105+
tokens.extend(quote! {#[doc = "```rust"]});
106+
for i in &diagram {
107+
tokens.extend(quote! {
108+
#[doc = #i]
109+
});
110+
}
111+
tokens.extend(quote! {#[doc = "```"]});
104112
}
105-
tokens.extend(quote! {#[doc = "```"]});
106113

107114
match generate_diagram_rustdoc(&diagram) {
108115
Ok(i) => {

scopegraphs/src/concepts/mod.rs

Lines changed: 150 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -479,7 +479,7 @@ pub mod scope_data {}
479479
/// Note, it's common to create new scopes for each variable definition like this.
480480
///
481481
/// A rough approximation of a program which would have such a scope structure would be:
482-
/// ```rust, no_run
482+
/// ```ignore
483483
/// // in the global scope
484484
/// let bar = 3;
485485
/// fn foo() {
@@ -513,7 +513,7 @@ pub mod scope_data {}
513513
///
514514
/// Let's go for the simple case first. Let's say we now write the following example:
515515
///
516-
/// ```rust, no_run
516+
/// ```ignore
517517
/// // in the global scope
518518
/// let bar = 3;
519519
/// fn foo() {
@@ -528,10 +528,83 @@ pub mod scope_data {}
528528
/// So first, we look in the current scope: `foo`'s scope. We immediately find a `Definition`
529529
/// edge which brings us to a variable definition with the name `baz`. So we're done!
530530
///
531+
/// ```rust
532+
/// # use scopegraphs::*;
533+
/// # use completeness::{UncheckedCompleteness};
534+
/// # use resolve::{DataWellformedness, Resolve, ResolvedPath};
535+
/// # use render::{RenderSettings, RenderScopeData, RenderScopeLabel};
536+
/// #
537+
/// # #[derive(Label, Hash, PartialEq, Eq, Debug, Clone, Copy)]
538+
/// # enum Lbl {
539+
/// # Lexical,
540+
/// # Definition,
541+
/// # }
542+
/// #
543+
/// # #[derive(Hash, PartialEq, Eq, Debug, Default, Clone)]
544+
/// # enum Data<'a> {
545+
/// # #[default]
546+
/// # NoData,
547+
/// # Variable {
548+
/// # name: &'a str,
549+
/// # },
550+
/// # }
551+
/// #
552+
/// # impl RenderScopeData for Data<'_> {
553+
/// # fn render_node(&self) -> Option<String> {
554+
/// # match self {
555+
/// # Self::Variable {..} => Some(format!("{self:?}")),
556+
/// # _ => None,
557+
/// # }
558+
/// # }
559+
/// #
560+
/// # fn definition(&self) -> bool {
561+
/// # matches!(self, Self::Variable {..})
562+
/// # }
563+
/// # }
564+
/// #
565+
/// # impl RenderScopeLabel for Lbl {
566+
/// # fn render(&self) -> String {
567+
/// # match self {
568+
/// # Self::Lexical => "lexical",
569+
/// # Self::Definition => "definition",
570+
/// # }.to_string()
571+
/// # }
572+
/// # }
573+
/// #
574+
/// # let storage = Storage::new();
575+
/// # let sg: ScopeGraph<Lbl, Data, UncheckedCompleteness> =
576+
/// # unsafe { ScopeGraph::raw(&storage) };
577+
/// #
578+
/// # let global = sg.add_scope_default();
579+
/// # let fn_foo = sg.add_scope_default();
580+
/// #
581+
/// # // create a scope in which the variable `bar` is defined
582+
/// # let declares_a_global = sg.add_scope(Data::Variable {name: "bar"});
583+
/// #
584+
/// # // create another scope in which the variable `bar` is defined inside foo
585+
/// # let declares_a_local_in_foo = sg.add_scope(Data::Variable {name: "baz"});
586+
/// #
587+
/// # // Add some edges
588+
/// # sg.add_edge(fn_foo, Lbl::Lexical, global);
589+
/// #
590+
/// # sg.add_edge(global, Lbl::Definition, declares_a_global);
591+
/// # sg.add_edge(fn_foo, Lbl::Definition, declares_a_local_in_foo);
592+
/// #
593+
/// # let res = sg.query()
594+
/// # .with_path_wellformedness(query_regex!(Lbl: Lexical* Definition))
595+
/// # .with_data_wellformedness(|a: &Data| matches!(a, Data::Variable {name: "baz"}))
596+
/// # .resolve(fn_foo);
597+
/// #
598+
/// # sg.render_to("output.mmd", RenderSettings {
599+
/// # path: Some(res.get_only_item().unwrap()),
600+
/// # ..Default::default()
601+
/// # }).unwrap()
602+
/// ```
603+
///
531604
/// ## Example 2: in the enclosing (global) scope
532605
/// Alright, now for a slightly more complicated example:
533606
///
534-
/// ```rust, no_run
607+
/// ```ignore
535608
/// // in the global scope
536609
/// let bar = 3;
537610
/// fn foo() {
@@ -546,12 +619,85 @@ pub mod scope_data {}
546619
/// So, we can choose to instead traverse the `Lexical` edge to look in the global scope.
547620
/// Now we *can* find a definition of `bar` (using a `Definition` edge), so we're done.
548621
///
622+
/// ```rust
623+
/// # use scopegraphs::*;
624+
/// # use completeness::{UncheckedCompleteness};
625+
/// # use resolve::{DataWellformedness, Resolve, ResolvedPath};
626+
/// # use render::{RenderSettings, RenderScopeData, RenderScopeLabel};
627+
/// #
628+
/// # #[derive(Label, Hash, PartialEq, Eq, Debug, Clone, Copy)]
629+
/// # enum Lbl {
630+
/// # Lexical,
631+
/// # Definition,
632+
/// # }
633+
/// #
634+
/// # #[derive(Hash, PartialEq, Eq, Debug, Default, Clone)]
635+
/// # enum Data<'a> {
636+
/// # #[default]
637+
/// # NoData,
638+
/// # Variable {
639+
/// # name: &'a str,
640+
/// # },
641+
/// # }
642+
/// #
643+
/// # impl RenderScopeData for Data<'_> {
644+
/// # fn render_node(&self) -> Option<String> {
645+
/// # match self {
646+
/// # Self::Variable {..} => Some(format!("{self:?}")),
647+
/// # _ => None,
648+
/// # }
649+
/// # }
650+
/// #
651+
/// # fn definition(&self) -> bool {
652+
/// # matches!(self, Self::Variable {..})
653+
/// # }
654+
/// # }
655+
/// #
656+
/// # impl RenderScopeLabel for Lbl {
657+
/// # fn render(&self) -> String {
658+
/// # match self {
659+
/// # Self::Lexical => "lexical",
660+
/// # Self::Definition => "definition",
661+
/// # }.to_string()
662+
/// # }
663+
/// # }
664+
/// #
665+
/// # let storage = Storage::new();
666+
/// # let sg: ScopeGraph<Lbl, Data, UncheckedCompleteness> =
667+
/// # unsafe { ScopeGraph::raw(&storage) };
668+
/// #
669+
/// # let global = sg.add_scope_default();
670+
/// # let fn_foo = sg.add_scope_default();
671+
/// #
672+
/// # // create a scope in which the variable `bar` is defined
673+
/// # let declares_a_global = sg.add_scope(Data::Variable {name: "bar"});
674+
/// #
675+
/// # // create another scope in which the variable `bar` is defined inside foo
676+
/// # let declares_a_local_in_foo = sg.add_scope(Data::Variable {name: "baz"});
677+
/// #
678+
/// # // Add some edges
679+
/// # sg.add_edge(fn_foo, Lbl::Lexical, global);
680+
/// #
681+
/// # sg.add_edge(global, Lbl::Definition, declares_a_global);
682+
/// # sg.add_edge(fn_foo, Lbl::Definition, declares_a_local_in_foo);
683+
/// #
684+
/// # let res = sg.query()
685+
/// # .with_path_wellformedness(query_regex!(Lbl: Lexical* Definition))
686+
/// # .with_data_wellformedness(|a: &Data| matches!(a, Data::Variable {name: "bar"}))
687+
/// # .resolve(fn_foo);
688+
/// #
689+
/// # sg.render_to("output.mmd", RenderSettings {
690+
/// # path: Some(res.get_only_item().unwrap()),
691+
/// # ..Default::default()
692+
/// # }).unwrap()
693+
/// ```
694+
///
549695
/// ## Example 3: when name resolution fails
550696
///
551697
/// Finally, let's look at an example in which name resolution should obviously fail,
552698
/// and discuss why it does, using the scope graph we constructed.
553699
///
554-
/// ```rust, no_run
700+
/// ```ignore
555701
/// // in the global scope
556702
/// let bar = 3;
557703
/// fn foo() {

scopegraphs/src/render/mod.rs

Lines changed: 51 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
//! Generally, use `sg.render_to(filename, Settings::default()` for the most basic rendering.
44
55
use crate::completeness::Completeness;
6+
use crate::resolve::ResolvedPath;
67
use crate::{Scope, ScopeGraph};
78
use std::fs::File;
89
use std::io;
@@ -22,7 +23,7 @@ pub enum Target {
2223
}
2324

2425
/// Global settings related to rendering scope graphs.
25-
pub struct RenderSettings {
26+
pub struct RenderSettings<'sg, LABEL, DATA> {
2627
/// Whether to display label text next to edges
2728
pub show_edge_labels: bool,
2829
/// The title which should be displayed above the graph.
@@ -31,22 +32,25 @@ pub struct RenderSettings {
3132
pub title: Option<String>,
3233
/// The output format to use for the visualization.
3334
pub target: Target,
35+
/// A resolved path that should also be rendered. Useful for debugging queries.
36+
pub path: Option<ResolvedPath<'sg, LABEL, DATA>>,
3437
}
3538

36-
impl RenderSettings {
39+
impl<'sg, LABEL, DATA> RenderSettings<'sg, LABEL, DATA> {
3740
/// Sets the name of the scope graph
3841
pub fn with_name(mut self, name: impl AsRef<str>) -> Self {
3942
self.title = Some(name.as_ref().to_string());
4043
self
4144
}
4245
}
4346

44-
impl Default for RenderSettings {
47+
impl<'sg, LABEL, DATA> Default for RenderSettings<'sg, LABEL, DATA> {
4548
fn default() -> Self {
4649
Self {
4750
show_edge_labels: true,
4851
title: None,
4952
target: Default::default(),
53+
path: None,
5054
}
5155
}
5256
}
@@ -136,14 +140,22 @@ impl<
136140
/// Visualize the entire scope graph as a graph, by emitting a graphviz dot file.
137141
///
138142
/// Note: you can also visualize a [single regular expression this way](crate::Automaton::render)
139-
pub fn render<W: Write>(&self, output: &mut W, settings: RenderSettings) -> io::Result<()> {
143+
pub fn render<W: Write>(
144+
&self,
145+
output: &mut W,
146+
settings: RenderSettings<'_, LABEL, DATA>,
147+
) -> io::Result<()> {
140148
match settings.target {
141149
Target::Dot => self.render_dot(output, settings),
142150
Target::Mermaid => self.render_mermaid(output, settings),
143151
}
144152
}
145153

146-
fn render_mermaid<W: Write>(&self, output: &mut W, settings: RenderSettings) -> io::Result<()> {
154+
fn render_mermaid<W: Write>(
155+
&self,
156+
output: &mut W,
157+
settings: RenderSettings<'_, LABEL, DATA>,
158+
) -> io::Result<()> {
147159
let (mut edges, nodes) = traverse::traverse(self);
148160

149161
if let Some(ref i) = settings.title {
@@ -193,22 +205,49 @@ impl<
193205
}
194206

195207
// edges
196-
for edge in edges {
197-
let from = scope_to_node_name(edge.from);
198-
let to = scope_to_node_name(edge.to.to);
208+
for (idx, edge) in edges.iter().enumerate() {
209+
let (from, to) = (edge.from, edge.to.to);
210+
211+
let from_str = scope_to_node_name(from);
212+
let to_str = scope_to_node_name(to);
199213
let label = escape_text_mermaid(&edge.to.label_text);
200214

201215
if settings.show_edge_labels {
202-
writeln!(output, r#"{from} ==>|"{label}"| {to}"#)?
216+
writeln!(output, r#"{from_str} ==>|"{label}"| {to_str}"#)?
203217
} else {
204-
writeln!(output, " {from} ==> {to}")?
218+
writeln!(output, " {from_str} ==> {to_str}")?
219+
}
220+
221+
if let Some(ref i) = settings.path {
222+
let mut prev = None;
223+
let mut part_of_path = false;
224+
for curr in i.scopes() {
225+
if let Some(ref mut prev) = prev {
226+
if (to, from) == (*prev, curr) {
227+
part_of_path = true;
228+
break;
229+
}
230+
231+
*prev = curr;
232+
} else {
233+
prev = Some(curr);
234+
}
235+
}
236+
237+
if part_of_path {
238+
writeln!(output, "linkStyle {idx} stroke: red")?;
239+
}
205240
}
206241
}
207242

208243
Ok(())
209244
}
210245

211-
fn render_dot<W: Write>(&self, output: &mut W, settings: RenderSettings) -> io::Result<()> {
246+
fn render_dot<W: Write>(
247+
&self,
248+
output: &mut W,
249+
settings: RenderSettings<'_, LABEL, DATA>,
250+
) -> io::Result<()> {
212251
let (mut edges, nodes) = traverse::traverse(self);
213252

214253
writeln!(output, "digraph {{")?;
@@ -278,7 +317,7 @@ impl<
278317
pub fn render_to(
279318
&self,
280319
path: impl AsRef<Path>,
281-
mut settings: RenderSettings,
320+
mut settings: RenderSettings<'_, LABEL, DATA>,
282321
) -> io::Result<()> {
283322
let path = path.as_ref();
284323
let mut w = File::create(path)?;

0 commit comments

Comments
 (0)