From 5af73e4fddc935e432a751155798ea27a716ff11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gustavo=20Gir=C3=A1ldez?= Date: Wed, 17 Jul 2024 17:01:07 -0400 Subject: [PATCH] Refactor: extract bindings and graph renderers into modules --- .../cargo/tests/src/bindings_output/graph.rs | 94 +++++++++ .../cargo/tests/src/bindings_output/mod.rs | 2 + .../tests/src/bindings_output/renderer.rs | 84 ++++++++ .../cargo/tests/src/bindings_output/runner.rs | 179 +----------------- 4 files changed, 184 insertions(+), 175 deletions(-) create mode 100644 crates/solidity/outputs/cargo/tests/src/bindings_output/graph.rs create mode 100644 crates/solidity/outputs/cargo/tests/src/bindings_output/renderer.rs diff --git a/crates/solidity/outputs/cargo/tests/src/bindings_output/graph.rs b/crates/solidity/outputs/cargo/tests/src/bindings_output/graph.rs new file mode 100644 index 0000000000..22e8a059a6 --- /dev/null +++ b/crates/solidity/outputs/cargo/tests/src/bindings_output/graph.rs @@ -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 { + 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) -> impl fmt::Display + '_ { + struct DisplayGraph<'a>(&'a Graph); + + 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) +} diff --git a/crates/solidity/outputs/cargo/tests/src/bindings_output/mod.rs b/crates/solidity/outputs/cargo/tests/src/bindings_output/mod.rs index cce71d6609..5816afb3be 100644 --- a/crates/solidity/outputs/cargo/tests/src/bindings_output/mod.rs +++ b/crates/solidity/outputs/cargo/tests/src/bindings_output/mod.rs @@ -1,2 +1,4 @@ mod generated; +mod graph; +mod renderer; mod runner; diff --git a/crates/solidity/outputs/cargo/tests/src/bindings_output/renderer.rs b/crates/solidity/outputs/cargo/tests/src/bindings_output/renderer.rs new file mode 100644 index 0000000000..47b4f6df89 --- /dev/null +++ b/crates/solidity/outputs/cargo/tests/src/bindings_output/renderer.rs @@ -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 { + 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)> = 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> = 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) +} diff --git a/crates/solidity/outputs/cargo/tests/src/bindings_output/runner.rs b/crates/solidity/outputs/cargo/tests/src/bindings_output/runner.rs index 7542115a53..ed5af25515 100644 --- a/crates/solidity/outputs/cargo/tests/src/bindings_output/runner.rs +++ b/crates/solidity/outputs/cargo/tests/src/bindings_output/runner.rs @@ -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<()> { @@ -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 => (), _ => { @@ -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 => (), _ => { @@ -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 { - 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) -> impl fmt::Display + '_ { - struct DisplayGraph<'a>(&'a Graph); - - 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 { - 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)> = 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> = 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) -}