Skip to content

Commit

Permalink
Refactor: extract bindings and graph renderers into modules
Browse files Browse the repository at this point in the history
  • Loading branch information
ggiraldez committed Jul 17, 2024
1 parent 8516631 commit 5af73e4
Show file tree
Hide file tree
Showing 4 changed files with 184 additions and 175 deletions.
94 changes: 94 additions & 0 deletions crates/solidity/outputs/cargo/tests/src/bindings_output/graph.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
use std::fmt;

use anyhow::Result;
use metaslang_bindings::builder::{self, ROOT_NODE_VAR};
use metaslang_graph_builder::ast::File;
use metaslang_graph_builder::graph::{Graph, Value};
use metaslang_graph_builder::{ExecutionConfig, NoCancellation, Variables};
use semver::Version;
use slang_solidity::bindings;
use slang_solidity::cst::KindTypes;
use slang_solidity::parse_output::ParseOutput;

const VARIABLE_DEBUG_ATTR: &str = "__variable";
const LOCATION_DEBUG_ATTR: &str = "__location";
const MATCH_DEBUG_ATTR: &str = "__match";

pub(crate) fn render_graph(version: &Version, parse_output: &ParseOutput) -> Result<String> {
let graph_builder = File::from_str(bindings::get_binding_rules())?;

let tree = parse_output.create_tree_cursor();
let mut graph = Graph::new();
let root_node = graph.add_graph_node();
graph[root_node]
.attributes
.add(VARIABLE_DEBUG_ATTR.into(), ROOT_NODE_VAR.to_string())
.unwrap();

let functions = builder::default_functions(version.clone());
let mut variables = Variables::new();
variables.add(ROOT_NODE_VAR.into(), root_node.into())?;
let execution_config = ExecutionConfig::new(&functions, &variables).debug_attributes(
LOCATION_DEBUG_ATTR.into(),
VARIABLE_DEBUG_ATTR.into(),
MATCH_DEBUG_ATTR.into(),
);

graph_builder.execute_into(&mut graph, &tree, &execution_config, &NoCancellation)?;

let note = if parse_output.is_valid() {
""
} else {
"%% WARNING: Parsing failed, graph may be incomplete\n"
};
Ok(format!(
"{note}{graph}",
graph = print_graph_as_mermaid(&graph)
))
}

fn print_graph_as_mermaid(graph: &Graph<KindTypes>) -> impl fmt::Display + '_ {
struct DisplayGraph<'a>(&'a Graph<KindTypes>);

impl<'a> fmt::Display for DisplayGraph<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let graph = self.0;
writeln!(f, "graph TD")?;
for node in graph.iter_nodes() {
let gn = &graph[node];
let node_label = if let Some(symbol) = gn.attributes.get("symbol") {
symbol.to_string()
} else {
format!("{}", node.index())
};
let source = gn
.attributes
.get(MATCH_DEBUG_ATTR)
.and_then(|source| source.as_syntax_node_ref().ok());
let location = gn.attributes.get(LOCATION_DEBUG_ATTR);

let node_label = format!(
"\"`**{node_label}** @{source}\n{variable}\n{location}`\"",
source = source.map(|s| s.location()).unwrap_or_default(),
variable = gn
.attributes
.get(VARIABLE_DEBUG_ATTR)
.unwrap_or(&Value::Null),
location = location.unwrap_or(&Value::Null),
);
let node_type = gn.attributes.get("type").and_then(|x| x.as_str().ok());
match node_type {
Some("push_symbol") => writeln!(f, "\tN{}[/{}\\]", node.index(), node_label)?,
Some("pop_symbol") => writeln!(f, "\tN{}[\\{}/]", node.index(), node_label)?,
_ => writeln!(f, "\tN{}[{}]", node.index(), node_label)?,
}
for (sink, _edge) in gn.iter_edges() {
writeln!(f, "\tN{} --> N{}", node.index(), sink.index())?;
}
}
Ok(())
}
}

DisplayGraph(graph)
}
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
mod generated;
mod graph;
mod renderer;
mod runner;
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
use std::ops::Range;
use std::path::Path;

use anyhow::Result;
use ariadne::{Color, Config, Label, Report, ReportBuilder, ReportKind, Source};
use infra_utils::paths::PathExtensions;
use semver::Version;
use slang_solidity::bindings::{self, Handle};
use slang_solidity::parse_output::ParseOutput;

pub(crate) fn render_bindings(
version: &Version,
parse_output: &ParseOutput,
source: &str,
source_path: &Path,
) -> Result<String> {
let mut bindings = bindings::create(version.clone());
bindings.add_file(
source_path.to_str().unwrap(),
parse_output.create_tree_cursor(),
);

let source_id = source_path.strip_repo_root()?.unwrap_str();
let mut builder: ReportBuilder<'_, (&str, Range<usize>)> = Report::build(
ReportKind::Custom("References and definitions", Color::Unset),
source_id,
0,
)
.with_config(Config::default().with_color(false));

if !parse_output.is_valid() {
builder = builder.with_note("WARNING: Parsing failed. Results may be incomplete.");
}

let mut definitions: Vec<Handle<'_>> = Vec::new();

for definition in bindings.all_definitions() {
let Some(cursor) = definition.get_cursor() else {
continue;
};

let range = {
let range = cursor.text_range();
let start = source[..range.start.utf8].chars().count();
let end = source[..range.end.utf8].chars().count();
start..end
};

definitions.push(definition);
let message = format!("def: {}", definitions.len());
builder = builder.with_label(Label::new((source_id, range)).with_message(message));
}

for reference in bindings.all_references() {
let Some(cursor) = reference.get_cursor() else {
continue;
};

let range = {
let range = cursor.text_range();
let start = source[..range.start.utf8].chars().count();
let end = source[..range.end.utf8].chars().count();
start..end
};

let definition = reference.jump_to_definition();
let message = match definition {
None => "unresolved".to_string(),
Some(definition) => {
let def_id = definitions.iter().position(|d| *d == definition).unwrap();
format!("ref: {}", def_id + 1)
}
};

builder = builder.with_label(Label::new((source_id, range)).with_message(message));
}

let report = builder.finish();
let mut buffer = Vec::new();
report.write((source_id, Source::from(source)), &mut buffer)?;

let result = String::from_utf8(buffer)?;
Ok(result)
}
179 changes: 4 additions & 175 deletions crates/solidity/outputs/cargo/tests/src/bindings_output/runner.rs
Original file line number Diff line number Diff line change
@@ -1,23 +1,12 @@
use std::fmt;
use std::ops::Range;
use std::path::Path;

use anyhow::Result;
use ariadne::{Color, Config, Label, Report, ReportBuilder, ReportKind, Source};
use infra_utils::cargo::CargoWorkspace;
use infra_utils::codegen::CodegenFileSystem;
use infra_utils::github::GitHub;
use infra_utils::paths::PathExtensions;
use metaslang_bindings::builder;
use metaslang_graph_builder::ast::File;
use metaslang_graph_builder::graph::{Graph, Value};
use metaslang_graph_builder::{ExecutionConfig, NoCancellation, Variables};
use semver::Version;
use slang_solidity::bindings::{self, Handle};
use slang_solidity::cst::KindTypes;
use slang_solidity::language::Language;
use slang_solidity::parse_output::ParseOutput;

use super::graph::render_graph;
use super::renderer::render_bindings;
use crate::generated::VERSION_BREAKS;

pub fn run(group_name: &str, test_name: &str) -> Result<()> {
Expand All @@ -44,7 +33,7 @@ pub fn run(group_name: &str, test_name: &str) -> Result<()> {
// Don't run this in CI, since the graph outputs are not committed
// to the repository and hence we cannot verify their contents,
// which is what `fs.write_file` does in CI.
let graph_output = output_graph(version, &parse_output)?;
let graph_output = render_graph(version, &parse_output)?;
match last_graph_output {
Some(ref last) if last == &graph_output => (),
_ => {
Expand All @@ -56,7 +45,7 @@ pub fn run(group_name: &str, test_name: &str) -> Result<()> {
};
}

let bindings_output = output_bindings(version, &parse_output, &source, &input_path)?;
let bindings_output = render_bindings(version, &parse_output, &source, &input_path)?;
match last_bindings_output {
Some(ref last) if last == &bindings_output => (),
_ => {
Expand All @@ -70,163 +59,3 @@ pub fn run(group_name: &str, test_name: &str) -> Result<()> {

Ok(())
}

const ROOT_NODE_VAR: &str = "ROOT_NODE";

const VARIABLE_DEBUG_ATTR: &str = "__variable";
const LOCATION_DEBUG_ATTR: &str = "__location";
const MATCH_DEBUG_ATTR: &str = "__match";

fn output_graph(version: &Version, parse_output: &ParseOutput) -> Result<String> {
let graph_builder = File::from_str(bindings::get_binding_rules())?;

let tree = parse_output.create_tree_cursor();
let mut graph = Graph::new();
let root_node = graph.add_graph_node();
graph[root_node]
.attributes
.add(VARIABLE_DEBUG_ATTR.into(), ROOT_NODE_VAR.to_string())
.unwrap();

let functions = builder::default_functions(version.clone());
let mut variables = Variables::new();
variables.add(ROOT_NODE_VAR.into(), root_node.into())?;
let execution_config = ExecutionConfig::new(&functions, &variables).debug_attributes(
LOCATION_DEBUG_ATTR.into(),
VARIABLE_DEBUG_ATTR.into(),
MATCH_DEBUG_ATTR.into(),
);

graph_builder.execute_into(&mut graph, &tree, &execution_config, &NoCancellation)?;

let note = if parse_output.is_valid() {
""
} else {
"%% WARNING: Parsing failed, graph may be incomplete\n"
};
Ok(format!(
"{note}{graph}",
graph = print_graph_as_mermaid(&graph)
))
}

fn print_graph_as_mermaid(graph: &Graph<KindTypes>) -> impl fmt::Display + '_ {
struct DisplayGraph<'a>(&'a Graph<KindTypes>);

impl<'a> fmt::Display for DisplayGraph<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let graph = self.0;
writeln!(f, "graph TD")?;
for node in graph.iter_nodes() {
let gn = &graph[node];
let node_label = if let Some(symbol) = gn.attributes.get("symbol") {
symbol.to_string()
} else {
format!("{}", node.index())
};
let source = gn
.attributes
.get(MATCH_DEBUG_ATTR)
.and_then(|source| source.as_syntax_node_ref().ok());
let location = gn.attributes.get(LOCATION_DEBUG_ATTR);

let node_label = format!(
"\"`**{node_label}** @{source}\n{variable}\n{location}`\"",
source = source.map(|s| s.location()).unwrap_or_default(),
variable = gn
.attributes
.get(VARIABLE_DEBUG_ATTR)
.unwrap_or(&Value::Null),
location = location.unwrap_or(&Value::Null),
);
let node_type = gn.attributes.get("type").and_then(|x| x.as_str().ok());
match node_type {
Some("push_symbol") => writeln!(f, "\tN{}[/{}\\]", node.index(), node_label)?,
Some("pop_symbol") => writeln!(f, "\tN{}[\\{}/]", node.index(), node_label)?,
_ => writeln!(f, "\tN{}[{}]", node.index(), node_label)?,
}
for (sink, _edge) in gn.iter_edges() {
writeln!(f, "\tN{} --> N{}", node.index(), sink.index())?;
}
}
Ok(())
}
}

DisplayGraph(graph)
}

fn output_bindings(
version: &Version,
parse_output: &ParseOutput,
source: &str,
source_path: &Path,
) -> Result<String> {
let mut bindings = bindings::create(version.clone());
bindings.add_file(
source_path.to_str().unwrap(),
parse_output.create_tree_cursor(),
);

let source_id = source_path.strip_repo_root()?.unwrap_str();
let mut builder: ReportBuilder<'_, (&str, Range<usize>)> = Report::build(
ReportKind::Custom("References and definitions", Color::Unset),
source_id,
0,
)
.with_config(Config::default().with_color(false));

if !parse_output.is_valid() {
builder = builder.with_note("WARNING: Parsing failed. Results may be incomplete.");
}

let mut definitions: Vec<Handle<'_>> = Vec::new();

for definition in bindings.all_definitions() {
let Some(cursor) = definition.get_cursor() else {
continue;
};

let range = {
let range = cursor.text_range();
let start = source[..range.start.utf8].chars().count();
let end = source[..range.end.utf8].chars().count();
start..end
};

definitions.push(definition);
let message = format!("def: {}", definitions.len());
builder = builder.with_label(Label::new((source_id, range)).with_message(message));
}

for reference in bindings.all_references() {
let Some(cursor) = reference.get_cursor() else {
continue;
};

let range = {
let range = cursor.text_range();
let start = source[..range.start.utf8].chars().count();
let end = source[..range.end.utf8].chars().count();
start..end
};

let definition = reference.jump_to_definition();
let message = match definition {
None => "unresolved".to_string(),
Some(definition) => {
let def_id = definitions.iter().position(|d| *d == definition).unwrap();
format!("ref: {}", def_id + 1)
}
};

builder = builder.with_label(Label::new((source_id, range)).with_message(message));
}

let report = builder.finish();
let mut buffer = Vec::new();
report.write((source_id, Source::from(source)), &mut buffer)?;

let result = String::from_utf8(buffer)?;
Ok(result)
}

0 comments on commit 5af73e4

Please sign in to comment.