diff --git a/Cargo.lock b/Cargo.lock index e89432c435..8b65c1c98d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -175,6 +175,18 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + [[package]] name = "block-buffer" version = "0.10.4" @@ -211,6 +223,12 @@ version = "3.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + [[package]] name = "bytes" version = "1.5.0" @@ -310,7 +328,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn", + "syn 2.0.68", ] [[package]] @@ -337,14 +355,14 @@ dependencies = [ "codegen_language_internal_macros", "indexmap", "infra_utils", - "itertools", + "itertools 0.13.0", "proc-macro2", "quote", "semver", "serde", "strum", "strum_macros", - "syn", + "syn 2.0.68", "thiserror", ] @@ -352,10 +370,10 @@ dependencies = [ name = "codegen_language_internal_macros" version = "0.15.1" dependencies = [ - "itertools", + "itertools 0.13.0", "proc-macro2", "quote", - "syn", + "syn 2.0.68", ] [[package]] @@ -385,8 +403,8 @@ dependencies = [ "ariadne", "clap", "codegen_runtime_generator", + "metaslang_bindings", "metaslang_cst", - "metaslang_graph_builder", "napi", "napi-derive", "semver", @@ -448,7 +466,7 @@ dependencies = [ "codegen_ebnf", "codegen_language_definition", "infra_utils", - "itertools", + "itertools 0.13.0", "serde", ] @@ -481,6 +499,26 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "controlled-option" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95abc95db25411571f40a8b0af30a3c386f3927fe6f1460c70e1f49f01bac3ac" +dependencies = [ + "controlled-option-macros", +] + +[[package]] +name = "controlled-option-macros" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "305024255a6c456333e130da2559b7aedd5c2e15388f51dae69f7507517cfbfb" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "convert_case" version = "0.6.0" @@ -565,7 +603,41 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f34ba9a9bcb8645379e9de8cb3ecfcf4d1c85ba66d90deb3259206fa5aa193b" dependencies = [ "quote", - "syn", + "syn 2.0.68", +] + +[[package]] +name = "darling" +version = "0.20.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83b2eb4d90d12bdda5ed17de686c2acb4c57914f8f921b8da7e112b5a36f3fe1" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "622687fe0bac72a04e5599029151f5796111b90f1baaa9b544d807a5e31cd120" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "syn 2.0.68", +] + +[[package]] +name = "darling_macro" +version = "0.20.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "733cabb43482b1a1b53eee8583c2b9e8684d592215ea83efd305dd31bc2f0178" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.68", ] [[package]] @@ -576,7 +648,7 @@ checksum = "d150dea618e920167e5973d70ae6ece4385b7164e0d799fe7c122dd0a5d912ad" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.68", ] [[package]] @@ -616,6 +688,27 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "enumset" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "226c0da7462c13fb57e5cc9e0dc8f0635e7d27f276a3a7fd30054647f669007d" +dependencies = [ + "enumset_derive", +] + +[[package]] +name = "enumset_derive" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08b6c6ab82d70f08844964ba10c7babb716de2ecaeab9be5717918a5177d3af" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.68", +] + [[package]] name = "env_filter" version = "0.1.0" @@ -691,6 +784,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + [[package]] name = "futures-channel" version = "0.3.28" @@ -741,6 +840,15 @@ dependencies = [ "slab", ] +[[package]] +name = "fxhash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +dependencies = [ + "byteorder", +] + [[package]] name = "generic-array" version = "0.14.7" @@ -983,6 +1091,12 @@ dependencies = [ "cc", ] +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + [[package]] name = "idna" version = "0.4.0" @@ -1048,7 +1162,7 @@ dependencies = [ "clap", "clap_complete", "infra_utils", - "itertools", + "itertools 0.13.0", "markdown", "regex", "semver", @@ -1070,7 +1184,7 @@ dependencies = [ "cargo-emit", "console", "ignore", - "itertools", + "itertools 0.13.0", "num-format", "rayon", "regex", @@ -1097,6 +1211,15 @@ version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + [[package]] name = "itertools" version = "0.13.0" @@ -1161,6 +1284,16 @@ version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +[[package]] +name = "lsp-positions" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa61ce94f83d24eba829bbba054b40e9996d8531c87670799fd0c43dea97be37" +dependencies = [ + "memchr", + "unicode-segmentation", +] + [[package]] name = "markdown" version = "0.3.0" @@ -1187,6 +1320,18 @@ dependencies = [ "autocfg", ] +[[package]] +name = "metaslang_bindings" +version = "0.15.1" +dependencies = [ + "metaslang_cst", + "metaslang_graph_builder", + "once_cell", + "semver", + "stack-graphs", + "thiserror", +] + [[package]] name = "metaslang_cst" version = "0.15.1" @@ -1278,7 +1423,7 @@ dependencies = [ "napi-derive-backend", "proc-macro2", "quote", - "syn", + "syn 2.0.68", ] [[package]] @@ -1293,7 +1438,7 @@ dependencies = [ "quote", "regex", "semver", - "syn", + "syn 2.0.68", ] [[package]] @@ -1396,7 +1541,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.68", ] [[package]] @@ -1462,7 +1607,7 @@ dependencies = [ "pest_meta", "proc-macro2", "quote", - "syn", + "syn 2.0.68", ] [[package]] @@ -1531,7 +1676,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.68", ] [[package]] @@ -1588,6 +1733,12 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + [[package]] name = "rand" version = "0.8.5" @@ -1876,7 +2027,7 @@ checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.68", ] [[package]] @@ -1967,8 +2118,8 @@ dependencies = [ "clap", "codegen_runtime_generator", "infra_utils", + "metaslang_bindings", "metaslang_cst", - "metaslang_graph_builder", "semver", "serde", "serde_json", @@ -2001,8 +2152,8 @@ dependencies = [ "anyhow", "codegen_runtime_generator", "infra_utils", + "metaslang_bindings", "metaslang_cst", - "metaslang_graph_builder", "semver", "serde", "strum", @@ -2107,7 +2258,7 @@ dependencies = [ "console", "indicatif", "infra_utils", - "itertools", + "itertools 0.13.0", "once_cell", "rayon", "semver", @@ -2133,7 +2284,7 @@ dependencies = [ "console", "indicatif", "infra_utils", - "itertools", + "itertools 0.13.0", "rayon", "reqwest", "semver", @@ -2150,6 +2301,24 @@ version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +[[package]] +name = "stack-graphs" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af63a628b6666b3ce870cac60623d71d1575a9ccc5b2f0a2f83143c3e7da9a3a" +dependencies = [ + "bitvec", + "controlled-option", + "either", + "enumset", + "fxhash", + "itertools 0.10.5", + "libc", + "lsp-positions", + "smallvec", + "thiserror", +] + [[package]] name = "string-interner" version = "0.17.0" @@ -2183,7 +2352,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn", + "syn 2.0.68", ] [[package]] @@ -2192,6 +2361,17 @@ version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" +[[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.68" @@ -2230,6 +2410,12 @@ dependencies = [ "libc", ] +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + [[package]] name = "tempfile" version = "3.10.1" @@ -2287,6 +2473,7 @@ dependencies = [ name = "testlang_cargo_tests" version = "0.15.1" dependencies = [ + "metaslang_graph_builder", "semver", "slang_testlang", ] @@ -2327,7 +2514,7 @@ checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.68", ] [[package]] @@ -2706,7 +2893,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn", + "syn 2.0.68", "wasm-bindgen-shared", ] @@ -2740,7 +2927,7 @@ checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.68", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -2952,6 +3139,15 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + [[package]] name = "yansi" version = "0.5.1" @@ -2975,7 +3171,7 @@ checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.68", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 3e828c8c16..a54828f6d6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,6 +22,7 @@ members = [ "crates/infra/cli", "crates/infra/utils", + "crates/metaslang/bindings", "crates/metaslang/cst", "crates/metaslang/graph_builder", @@ -61,6 +62,7 @@ codegen_testing = { path = "crates/codegen/testing", version = "0.15.1" } infra_cli = { path = "crates/infra/cli", version = "0.15.1" } infra_utils = { path = "crates/infra/utils", version = "0.15.1" } +metaslang_bindings = { path = "crates/metaslang/bindings", version = "0.15.1" } metaslang_graph_builder = { path = "crates/metaslang/graph_builder", version = "0.15.1" } metaslang_cst = { path = "crates/metaslang/cst", version = "0.15.1" } diff --git a/crates/codegen/runtime/cargo/Cargo.toml b/crates/codegen/runtime/cargo/Cargo.toml index 4a48e196da..3ede85ec15 100644 --- a/crates/codegen/runtime/cargo/Cargo.toml +++ b/crates/codegen/runtime/cargo/Cargo.toml @@ -14,8 +14,8 @@ codegen_runtime_generator = { workspace = true } [dependencies] ariadne = { workspace = true, optional = true } clap = { workspace = true, optional = true } +metaslang_bindings = { workspace = true, optional = true } metaslang_cst = { workspace = true } -metaslang_graph_builder = { workspace = true, optional = true } napi = { workspace = true, optional = true } napi-derive = { workspace = true, optional = true } semver = { workspace = true } @@ -34,7 +34,7 @@ cli = ["dep:clap", "dep:serde_json", "__private_ariadne"] # Only used by the `slang_solidity` CLI __private_ariadne = ["dep:ariadne"] # For internal development only -__experimental_bindings_api = ["dep:metaslang_graph_builder"] +__experimental_bindings_api = ["dep:metaslang_bindings"] [lints] workspace = true diff --git a/crates/codegen/runtime/cargo/src/runtime/bindings/mod.rs b/crates/codegen/runtime/cargo/src/runtime/bindings/mod.rs index c3976caf8f..e45b819a3a 100644 --- a/crates/codegen/runtime/cargo/src/runtime/bindings/mod.rs +++ b/crates/codegen/runtime/cargo/src/runtime/bindings/mod.rs @@ -1,10 +1,14 @@ #[path = "generated/binding_rules.rs"] mod binding_rules; -use metaslang_graph_builder::ast; -pub use metaslang_graph_builder::functions::Functions; -pub use metaslang_graph_builder::{ExecutionConfig, ExecutionError, NoCancellation, Variables}; +use metaslang_bindings; +use semver::Version; use crate::cst::KindTypes; -pub type File = ast::File; +pub type Bindings = metaslang_bindings::Bindings; +pub type Handle<'a> = metaslang_bindings::Handle<'a, KindTypes>; + +pub fn create(version: Version) -> Bindings { + Bindings::create(version, binding_rules::BINDING_RULES_SOURCE) +} diff --git a/crates/codegen/runtime/cargo/src/runtime/cli/commands/bindings.rs b/crates/codegen/runtime/cargo/src/runtime/cli/commands/bindings.rs new file mode 100644 index 0000000000..3b842d14ab --- /dev/null +++ b/crates/codegen/runtime/cargo/src/runtime/cli/commands/bindings.rs @@ -0,0 +1,86 @@ +use core::fmt; + +use semver::Version; + +use super::CommandError; +use crate::bindings::{self, Bindings, Handle}; +use crate::cursor::Cursor; + +pub fn execute(file_path_string: &str, version: Version) -> Result<(), CommandError> { + let mut bindings = bindings::create(version.clone()); + let parse_output = super::parse::parse_source_file(file_path_string, version, |_| ())?; + let tree_cursor = parse_output.create_tree_cursor(); + + bindings.add_file(file_path_string, tree_cursor); + + print_definitions(&bindings); + print_references(&bindings); + + Ok(()) +} + +fn print_definitions(bindings: &Bindings) { + println!("\nAll definitions found:"); + for definition in bindings.all_definitions() { + println!("{}", DisplayDefinition(&definition)); + } +} + +fn print_references(bindings: &Bindings) { + println!("\nAll references found:"); + for reference in bindings.all_references() { + println!("{}", DisplayReference(&reference)); + if let Some(def) = reference.jump_to_definition() { + println!(" -> {}", DisplayDefinition(&def)); + } else { + println!(" -> No definition found"); + } + } +} + +struct DisplayRange<'a>(&'a Cursor); + +impl<'a> fmt::Display for DisplayRange<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let range = self.0.text_range(); + write!(f, "{}..{}", range.start, range.end) + } +} + +struct DisplayDefinition<'a>(&'a Handle<'a>); + +impl<'a> fmt::Display for DisplayDefinition<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let file = self.0.get_file().unwrap_or(""); + if let Some(cursor) = self.0.get_cursor() { + let location = DisplayRange(&cursor); + let identifier = cursor.node().unparse(); + write!(f, "`{identifier}` defined at {location} in {file}") + } else { + write!( + f, + "Definition without available cursor: {definition:?} in {file}", + definition = self.0 + ) + } + } +} + +struct DisplayReference<'a>(&'a Handle<'a>); + +impl<'a> fmt::Display for DisplayReference<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let file = self.0.get_file().unwrap_or(""); + if let Some(cursor) = self.0.get_cursor() { + let location = DisplayRange(&cursor); + let identifier = cursor.node().unparse(); + write!(f, "`{identifier}` referenced at {location} in {file}") + } else { + write!( + f, + "Reference without available cursor: {reference:?} in {file}", + reference = self.0 + ) + } + } +} diff --git a/crates/codegen/runtime/cargo/src/runtime/cli/commands/build_graph.rs b/crates/codegen/runtime/cargo/src/runtime/cli/commands/build_graph.rs deleted file mode 100644 index 37970d5d30..0000000000 --- a/crates/codegen/runtime/cargo/src/runtime/cli/commands/build_graph.rs +++ /dev/null @@ -1,57 +0,0 @@ -use std::fs; -use std::path::PathBuf; - -use semver::Version; - -use super::parse::parse_source_file; -use super::CommandError; -use crate::bindings::{ - ExecutionConfig, File as GraphBuilderFile, Functions, NoCancellation, Variables, -}; - -pub fn execute( - file_path_string: &str, - version: Version, - msgb_path_string: &str, - output_json: bool, - debug: bool, -) -> Result<(), CommandError> { - let parse_output = parse_source_file(file_path_string, version, |_| ())?; - let msgb = parse_graph_builder(msgb_path_string)?; - - let functions = Functions::stdlib(); - let variables = Variables::new(); - let mut execution_config = ExecutionConfig::new(&functions, &variables); - if debug { - execution_config = execution_config.debug_attributes( - "_location".into(), - "_variable".into(), - "_match".into(), - ); - } - - let tree = parse_output.create_tree_cursor(); - let graph = msgb.execute(&tree, &execution_config, &NoCancellation)?; - - if output_json { - graph.display_json(None)?; - } else { - print!("{}", graph.pretty_print()); - } - - Ok(()) -} - -fn parse_graph_builder(msgb_path_string: &str) -> Result { - let msgb_path = PathBuf::from(&msgb_path_string) - .canonicalize() - .map_err(|_| CommandError::FileNotFound(msgb_path_string.to_string()))?; - - let msgb_source = fs::read_to_string(&msgb_path)?; - GraphBuilderFile::from_str(&msgb_source).map_err(|parser_error| { - let error_message = parser_error - .display_pretty(&msgb_path, &msgb_source) - .to_string(); - CommandError::ParseFailed(error_message) - }) -} diff --git a/crates/codegen/runtime/cargo/src/runtime/cli/commands/mod.rs b/crates/codegen/runtime/cargo/src/runtime/cli/commands/mod.rs index 92ec8e7c87..83168715ac 100644 --- a/crates/codegen/runtime/cargo/src/runtime/cli/commands/mod.rs +++ b/crates/codegen/runtime/cargo/src/runtime/cli/commands/mod.rs @@ -1,7 +1,7 @@ use thiserror::Error; #[cfg(feature = "__experimental_bindings_api")] -pub mod build_graph; +pub mod bindings; pub mod parse; #[derive(Debug, Error)] @@ -17,8 +17,4 @@ pub enum CommandError { #[error("Parsing failed: {0}")] ParseFailed(String), - - #[cfg(feature = "__experimental_bindings_api")] - #[error(transparent)] - ExecutionFailed(#[from] crate::bindings::ExecutionError), } diff --git a/crates/codegen/runtime/cargo/src/runtime/cli/mod.rs b/crates/codegen/runtime/cargo/src/runtime/cli/mod.rs index 6243b22f86..370ff6a9cb 100644 --- a/crates/codegen/runtime/cargo/src/runtime/cli/mod.rs +++ b/crates/codegen/runtime/cargo/src/runtime/cli/mod.rs @@ -21,27 +21,15 @@ pub enum Commands { json: bool, }, - // This is only intended for internal development + /// This is only intended for internal development #[cfg(feature = "__experimental_bindings_api")] - /// Parses a source file and builds a graph executing the instructions from the builder file (*.msgb) - BuildGraph { + Bindings { /// File path to the source file to parse file_path: String, /// The language version to use for parsing #[arg(short, long)] version: Version, - - /// The graph buider (.msgb) file to use - msgb_path: String, - - /// Print the graph as JSON - #[clap(long)] - json: bool, - - /// Include debug info (location, variable and match) in the built graph - #[clap(long)] - debug: bool, }, } @@ -54,13 +42,9 @@ impl Commands { json, } => commands::parse::execute(&file_path, version, json), #[cfg(feature = "__experimental_bindings_api")] - Commands::BuildGraph { - file_path, - version, - msgb_path, - json, - debug, - } => commands::build_graph::execute(&file_path, version, &msgb_path, json, debug), + Commands::Bindings { file_path, version } => { + commands::bindings::execute(&file_path, version) + } }; match command_result { Ok(()) => ExitCode::SUCCESS, diff --git a/crates/infra/cli/src/commands/publish/cargo/mod.rs b/crates/infra/cli/src/commands/publish/cargo/mod.rs index 803225f186..f67cbad9b6 100644 --- a/crates/infra/cli/src/commands/publish/cargo/mod.rs +++ b/crates/infra/cli/src/commands/publish/cargo/mod.rs @@ -15,6 +15,7 @@ const USER_FACING_CRATES: &[&str] = &[ // Sorted by dependency order (from dependencies to dependents): "metaslang_cst", "metaslang_graph_builder", + "metaslang_bindings", "slang_solidity", ]; diff --git a/crates/metaslang/bindings/Cargo.toml b/crates/metaslang/bindings/Cargo.toml new file mode 100644 index 0000000000..a97e0d04bd --- /dev/null +++ b/crates/metaslang/bindings/Cargo.toml @@ -0,0 +1,34 @@ +[package] +version.workspace = true +rust-version.workspace = true +edition.workspace = true +publish = true + +name = "metaslang_bindings" +description = "Computes semantic language bindings from parsed source code" +homepage = "https://nomicfoundation.github.io/slang/" +repository = "https://github.com/NomicFoundation/slang/" +authors = [ + "Nomic Foundation ", + "Antony Blakey ", + "Igor Matuszewski ", + "Omar Tawfik ", +] + +readme = "README.md" +license = "MIT" +keywords = ["parser"] +categories = ["compilers", "parsing", "parser-implementations"] + +[dependencies] +metaslang_cst = { workspace = true } +metaslang_graph_builder = { workspace = true } +once_cell = { workspace = true } +semver = { workspace = true } +stack-graphs = { workspace = true } +thiserror = { workspace = true } + +[dev-dependencies] + +[lints] +workspace = true diff --git a/crates/metaslang/bindings/LICENSE b/crates/metaslang/bindings/LICENSE new file mode 100644 index 0000000000..8ff8c8bb7c --- /dev/null +++ b/crates/metaslang/bindings/LICENSE @@ -0,0 +1,22 @@ +MIT License + +Copyright (c) 2021 stack-graphs authors +Copyright (c) 2024 Nomic Foundation + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/crates/metaslang/bindings/README.md b/crates/metaslang/bindings/README.md new file mode 100644 index 0000000000..30cf663c4b --- /dev/null +++ b/crates/metaslang/bindings/README.md @@ -0,0 +1,22 @@ + + +# metaslang_bindings + +The `metaslang_bindings` library allows computing language semantic bindings +by building stack graphs (see crate `stack-graphs`) from graphs constructed +using the `metaslang_graph_builder` library from source code parsed by parsers +generated by `metaslang`. + +# metaslang_bindings + + + +[![release](https://img.shields.io/github/v/tag/NomicFoundation/slang?label=GitHub%20Release&logo=github&sort=semver&logoColor=white)](https://github.com/NomicFoundation/slang/releases) +[![crate](https://img.shields.io/crates/v/metaslang_bindings?label=Rust%20Crate&logo=rust&logoColor=white)](https://crates.io/crates/metaslang_bindings) + +## Solidity compiler tooling by [@NomicFoundation](https://github.com/NomicFoundation) + +
+ +> ❗ This project is still in alpha, and is under active development. +> If you are planning on using it, please reach out to us on [Telegram](https://t.me/+pxApdT-Ssn5hMTFh) so we can help you get started. diff --git a/crates/metaslang/bindings/src/builder/cancellation.rs b/crates/metaslang/bindings/src/builder/cancellation.rs new file mode 100644 index 0000000000..7cb6c9140a --- /dev/null +++ b/crates/metaslang/bindings/src/builder/cancellation.rs @@ -0,0 +1,123 @@ +// -*- coding: utf-8 -*- +// ------------------------------------------------------------------------------------------------ +// Copyright © 2021, stack-graphs authors. +// Copyright © 2024, slang authors. +// Licensed under MIT license +// Please see the LICENSE file in the root of this crate for license details. +// ------------------------------------------------------------------------------------------------ + +use std::ops::BitOr; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::Arc; +use std::time::{Duration, Instant}; + +use metaslang_graph_builder::{ + CancellationError as GraphCancellationError, CancellationFlag as GraphCancellationFlag, +}; +use thiserror::Error; + +/// Trait to signal that the execution is cancelled +pub trait CancellationFlag: Sync { + fn check(&self, at: &'static str) -> Result<(), CancellationError>; +} + +#[derive(Clone, Debug, Error)] +#[error("Cancelled at \"{0}\"")] +pub struct CancellationError(pub &'static str); + +impl stack_graphs::CancellationFlag for &dyn CancellationFlag { + fn check(&self, at: &'static str) -> Result<(), stack_graphs::CancellationError> { + CancellationFlag::check(*self, at).map_err(|err| stack_graphs::CancellationError(err.0)) + } +} + +impl GraphCancellationFlag for &dyn CancellationFlag { + fn check(&self, at: &'static str) -> Result<(), GraphCancellationError> { + CancellationFlag::check(*self, at).map_err(|err| GraphCancellationError(err.0)) + } +} + +impl<'a> BitOr for &'a dyn CancellationFlag { + type Output = OrCancellationFlag<'a>; + fn bitor(self, rhs: Self) -> Self::Output { + OrCancellationFlag(self, rhs) + } +} + +pub struct OrCancellationFlag<'a>(&'a dyn CancellationFlag, &'a dyn CancellationFlag); + +impl CancellationFlag for OrCancellationFlag<'_> { + fn check(&self, at: &'static str) -> Result<(), CancellationError> { + self.0.check(at)?; + self.1.check(at)?; + Ok(()) + } +} + +pub struct NoCancellation; + +impl CancellationFlag for NoCancellation { + fn check(&self, _at: &'static str) -> Result<(), CancellationError> { + Ok(()) + } +} + +pub struct CancelAfterDuration { + start: Instant, + limit: Duration, +} + +impl CancelAfterDuration { + #[allow(dead_code)] + pub fn new(limit: Duration) -> Self { + Self { + start: Instant::now(), + limit, + } + } + + #[allow(dead_code)] + pub fn from_option(limit: Option) -> Box { + match limit { + Some(limit) => Box::new(Self::new(limit)), + None => Box::new(NoCancellation), + } + } +} + +impl CancellationFlag for CancelAfterDuration { + fn check(&self, at: &'static str) -> Result<(), CancellationError> { + if self.start.elapsed().ge(&self.limit) { + return Err(CancellationError(at)); + } + Ok(()) + } +} + +#[derive(Clone)] +pub struct AtomicCancellationFlag { + flag: Arc, +} + +impl AtomicCancellationFlag { + #[allow(dead_code)] + pub fn new() -> Self { + Self { + flag: Arc::new(AtomicBool::new(false)), + } + } + + #[allow(dead_code)] + pub fn cancel(&self) { + self.flag.store(true, Ordering::Relaxed); + } +} + +impl CancellationFlag for AtomicCancellationFlag { + fn check(&self, at: &'static str) -> Result<(), CancellationError> { + if self.flag.load(Ordering::Relaxed) { + return Err(CancellationError(at)); + } + Ok(()) + } +} diff --git a/crates/metaslang/bindings/src/builder/mod.rs b/crates/metaslang/bindings/src/builder/mod.rs new file mode 100644 index 0000000000..e8017f74d0 --- /dev/null +++ b/crates/metaslang/bindings/src/builder/mod.rs @@ -0,0 +1,884 @@ +// -*- coding: utf-8 -*- +// ------------------------------------------------------------------------------------------------ +// Copyright © 2021, stack-graphs authors. +// Copyright © 2024, slang authors. +// Licensed under MIT license +// Please see the LICENSE file in the root of this crate for license details. +// ------------------------------------------------------------------------------------------------ + +//! This module lets you construct [stack graphs][] using this crate's [graph construction DSL][]. +//! The graph DSL lets you construct arbitrary graph structures from the parsed syntax tree of a +//! source file. If you construct a graph using the vocabulary of attributes described below, then +//! the result of executing the graph DSL will be a valid stack graph, which we can then use for +//! name binding lookups. +//! +//! ## Prerequisites +//! +//! [stack graphs]: https://docs.rs/stack-graphs/*/ +//! [graph construction DSL]: https://docs.rs/metaslang_graph_builder/*/ +//! +//! To process a particular source language, you'll need to first get the CST from the source, using +//! the parser constructed from the language definition that uses the `metaslang_cst` crate. +//! +//! You will then need to create _stack graph construction rules_ for your language. These rules +//! are implemented using metaslang's [graph construction DSL][], which is based from tree-sitter's +//! graph construction DSL. They define the particular stack graph nodes and edges that should be +//! created for each part of the parsed syntax tree of a source file. +//! +//! ## Graph DSL vocabulary +//! +//! **Please note**: This documentation assumes you are already familiar with stack graphs, and how +//! to use different stack graph node types, and the connectivity between nodes, to implement the +//! name binding semantics of your language. We assume that you know what kind of stack graph you +//! want to produce; this documentation focuses only on the mechanics of _how_ to create that stack +//! graph content. +//! +//! As mentioned above, your stack graph construction rules should create stack graph nodes and +//! edges from the parsed content of a source file. You will use MSGB [stanzas][] to match on +//! different parts of the parsed syntax tree, and create stack graph content for each match. +//! +//! ### Creating stack graph nodes +//! +//! To create a stack graph node for each identifier in a Solidity file, you could use the following +//! MSGB stanza: +//! +//! ``` skip +//! [Identifier] { +//! node new_node +//! } +//! ``` +//! +//! (Here, `node` is a MSGB statement that creates a new node, and `new_node` is the name of a local +//! variable that the new node is assigned to, letting you refer to the new node in the rest of the +//! stanza.) +//! +//! [stanzas]: https://docs.rs/tree-sitter-graph/*/tree_sitter_graph/reference/index.html#high-level-structure +//! +//! By default, this new node will be a _scope node_. If you need to create a different kind of stack +//! graph node, set the `type` attribute on the new node: +//! +//! ``` skip +//! [Identifier] { +//! node new_node +//! attr (new_node) type = "push_symbol" +//! } +//! ``` +//! +//! The valid `type` values are: +//! +//! - `drop_scopes`: a _drop scopes_ node +//! - `pop_symbol`: a _pop symbol_ node +//! - `pop_scoped_symbol`: a _pop scoped symbol_ node +//! - `push_symbol`: a _push symbol_ node +//! - `push_scoped_symbol`: a _push scoped symbol_ node +//! - `scope`: a _scope_ node +//! +//! A node without an explicit `type` attribute is assumed to be of type `scope`. +//! +//! Certain node types — `pop_symbol`, `pop_scoped_symbol`, `push_symbol` and `push_scoped_symbol` — +//! also require you to provide a `symbol` attribute. Its value must be a string, but will typically +//! come from the content of a parsed syntax node using the [`source-text`][] function and a syntax +//! capture: +//! +//! [`source-text`]: https://docs.rs/tree-sitter-graph/*/tree_sitter_graph/reference/functions/index.html#source-text +//! +//! ``` skip +//! @id [Identifier] { +//! node new_node +//! attr (new_node) type = "push_symbol", symbol = (source-text @id) +//! } +//! ``` +//! +//! Node types `pop_symbol` and `pop_scoped_symbol` allow an optional `is_definition` attribute, +//! which marks that node as a proper definition. Node types `push_symbol` and `push_scoped_symbol` +//! allow an optional `is_reference` attribute, which marks the node as a proper reference. When +//! `is_definition` or `is_reference` are set, the `source_node` attribute is required. +//! +//! ``` skip +//! @id [Identifier] { +//! node new_node +//! attr (new_node) type = "push_symbol", symbol = (source-text @id), is_reference, source_node = @id +//! } +//! ``` +//! +//! A _push scoped symbol_ node requires a `scope` attribute. Its value must be a reference to an +//! `exported` node that you've already created. (This is the exported scope node that will be +//! pushed onto the scope stack.) For instance: +//! +//! ``` skip +//! @id [Identifier] { +//! node new_exported_scope_node +//! attr (new_exported_scope_node) is_exported +//! node new_push_scoped_symbol_node +//! attr (new_push_scoped_symbol_node) +//! type = "push_scoped_symbol", +//! symbol = (source-text @id), +//! scope = new_exported_scope_node +//! } +//! ``` +//! +//! Nodes of type `scope` allow an optional `is_exported` attribute, that is required to use the +//! scope in a `push_scoped_symbol` node. +//! +//! +//! ### Annotating nodes with location information +//! +//! You can annotate any stack graph node that you create with location information, identifying +//! the portion of the source file that the node "belongs to". This is _required_ for definition +//! and reference nodes, since the location information determines which parts of the source file +//! the user can _click on_, and the _destination_ of any code navigation queries the user makes. +//! To do this, add a `source_node` attribute, whose value is a syntax node capture: +//! +//! ``` skip +//! @func [FunctionDefinition ... [FunctionName @id [Identifier]] ...] { +//! node def +//! attr (def) type = "pop_symbol", symbol = (source-text @id), source_node = @func, is_definition +//! } +//! ``` +//! +//! Note how in this example, we use a different syntax node for the _target_ of the definition +//! (the entirety of the function definition) and for the _name_ of the definition (the content of +//! the function's `name`). +//! +//! Adding the `empty_source_span` attribute will use an empty source span located at the start of +//! the span of the `source_node`. This can be useful when a proper reference or definition is +//! desired, and thus `source_node` is required, but the span of the available source node is too +//! large. For example, a module definition which is located at the start of the program, but does +//! span the whole program: +//! +//! ``` skip +//! @unit [SourceUnit] { +//! ; ... +//! node mod_def +//! attr mod_def type = "pop_symbol", symbol = mod_name, is_definition, source_node = @unit, empty_source_span +//! ; ... +//! } +//! ``` +//! +//! ### Annotating nodes with syntax type information +//! +//! You can annotate any stack graph node with information about its syntax type. To do this, add a +//! `syntax_type` attribute, whose value is a string indicating the syntax type. +//! +//! ``` skip +//! @func [FunctionDefinition ... [FunctionName @id [Identifier]] ...] { +//! node def +//! ; ... +//! attr (def) syntax_type = "function" +//! } +//! ``` +//! +//! ### Annotating definitions with definiens information +//! +//! You cannot annotate definitions with a definiens, which is the thing the definition covers. For +//! example, for a function definition, the definiens would be the function body. To do this, add a +//! `definiens_node` attribute, whose value is a syntax node that spans the definiens. +//! +//! ``` skip +//! @func [FunctionDefinition ... [FunctionName @id [Identifier]] ... @body [FunctionBody] ...] { +//! node def +//! ; ... +//! attr (def) definiens_node = @body +//! } +//! ``` +//! +//! Definiens are optional and setting them to `#null` explicitly is allowed. +//! +//! ### Connecting stack graph nodes with edges +//! +//! To connect two stack graph nodes, use the `edge` statement to add an edge between them: +//! +//! ``` skip +//! @func [FunctionDefinition ... [FunctionName @id [Identifier]] ...] { +//! node def +//! attr (def) type = "pop_symbol", symbol = (source-text @id), source_node = @func, is_definition +//! node body +//! edge def -> body +//! } +//! ``` +//! +//! To implement shadowing (which determines which definitions are selected when multiple are available), +//! you can add a `precedence` attribute to each edge to indicate which paths are prioritized: +//! +//! ``` skip +//! @func [FunctionDefinition ... [FunctionName @id [Identifier]] ...] { +//! node def +//! attr (def) type = "pop_symbol", symbol = (source-text @id), source_node = @func, is_definition +//! node body +//! edge def -> body +//! attr (def -> body) precedence = 1 +//! } +//! ``` +//! +//! (If you don't specify a `precedence`, the default is 0.) +//! +//! ### Referring to the singleton nodes +//! +//! The _root node_ and _jump to scope node_ are singleton nodes that always exist for all stack +//! graphs. You can refer to them using the `ROOT_NODE` and `JUMP_TO_SCOPE_NODE` global variables: +//! +//! ``` skip +//! global ROOT_NODE +//! +//! @func [FunctionDefinition ... [FunctionName @id [Identifier]] ...] { +//! node def +//! attr (def) type = "pop_symbol", symbol = (source-text @id), source_node = @func, is_definition +//! edge ROOT_NODE -> def +//! } +//! ``` +//! +//! ### Attaching debug information to nodes +//! +//! It is possible to attach extra information to nodes for debugging purposes. This is done by adding +//! `debug_*` attributes to nodes. Each attribute defines a debug entry, with the key derived from the +//! attribute name, and the value the string representation of the attribute value. For example, mark +//! a scope node with a kind as follows: +//! +//! ``` skip +//! @func [FunctionDefinition ... [FunctionName @id [Identifier]] ...] { +//! ; ... +//! node param_scope +//! attr (param_scope) debug_kind = "param_scope" +//! ; ... +//! } +//! ``` +//! + +mod cancellation; + +use std::collections::{HashMap, HashSet}; +use std::path::Path; + +pub use cancellation::{CancellationFlag, NoCancellation}; +use metaslang_cst::cursor::Cursor; +use metaslang_cst::KindTypes; +use metaslang_graph_builder::ast::File as GraphBuilderFile; +use metaslang_graph_builder::functions::Functions; +use metaslang_graph_builder::graph::{Edge, Graph, GraphNode, GraphNodeRef, Value}; +use metaslang_graph_builder::{ExecutionConfig, ExecutionError, Variables}; +use once_cell::sync::Lazy; +use semver::Version; +use stack_graphs::arena::Handle; +use stack_graphs::graph::{File, Node, NodeID, StackGraph}; +use thiserror::Error; + +// Node type values +static DROP_SCOPES_TYPE: &str = "drop_scopes"; +static POP_SCOPED_SYMBOL_TYPE: &str = "pop_scoped_symbol"; +static POP_SYMBOL_TYPE: &str = "pop_symbol"; +static PUSH_SCOPED_SYMBOL_TYPE: &str = "push_scoped_symbol"; +static PUSH_SYMBOL_TYPE: &str = "push_symbol"; +static SCOPE_TYPE: &str = "scope"; + +// Node attribute names +static DEBUG_ATTR_PREFIX: &str = "debug_"; +static DEFINIENS_NODE_ATTR: &str = "definiens_node"; +static EMPTY_SOURCE_SPAN_ATTR: &str = "empty_source_span"; +static IS_DEFINITION_ATTR: &str = "is_definition"; +static IS_ENDPOINT_ATTR: &str = "is_endpoint"; +static IS_EXPORTED_ATTR: &str = "is_exported"; +static IS_REFERENCE_ATTR: &str = "is_reference"; +static SCOPE_ATTR: &str = "scope"; +static SOURCE_NODE_ATTR: &str = "source_node"; +static SYMBOL_ATTR: &str = "symbol"; +static SYNTAX_TYPE_ATTR: &str = "syntax_type"; +static TYPE_ATTR: &str = "type"; + +// Expected attributes per node type +static POP_SCOPED_SYMBOL_ATTRS: Lazy> = Lazy::new(|| { + HashSet::from([ + TYPE_ATTR, + SYMBOL_ATTR, + IS_DEFINITION_ATTR, + DEFINIENS_NODE_ATTR, + SYNTAX_TYPE_ATTR, + ]) +}); +static POP_SYMBOL_ATTRS: Lazy> = Lazy::new(|| { + HashSet::from([ + TYPE_ATTR, + SYMBOL_ATTR, + IS_DEFINITION_ATTR, + DEFINIENS_NODE_ATTR, + SYNTAX_TYPE_ATTR, + ]) +}); +static PUSH_SCOPED_SYMBOL_ATTRS: Lazy> = + Lazy::new(|| HashSet::from([TYPE_ATTR, SYMBOL_ATTR, SCOPE_ATTR, IS_REFERENCE_ATTR])); +static PUSH_SYMBOL_ATTRS: Lazy> = + Lazy::new(|| HashSet::from([TYPE_ATTR, SYMBOL_ATTR, IS_REFERENCE_ATTR])); +static SCOPE_ATTRS: Lazy> = + Lazy::new(|| HashSet::from([TYPE_ATTR, IS_EXPORTED_ATTR, IS_ENDPOINT_ATTR])); + +// Edge attribute names +static PRECEDENCE_ATTR: &str = "precedence"; + +// Global variables +/// Name of the variable used to pass the root node. +pub const ROOT_NODE_VAR: &str = "ROOT_NODE"; +/// Name of the variable used to pass the file path. +pub const FILE_PATH_VAR: &str = "FILE_PATH"; +/// Version of the language being processed, to apply different semantic rules +pub const VERSION_VAR: &str = "VERSION"; + +pub struct Globals<'a> { + pub version: &'a Version, + pub file_path: &'a str, +} + +pub struct Builder<'a, KT: KindTypes> { + msgb: &'a GraphBuilderFile, + functions: &'a Functions, + stack_graph: &'a mut StackGraph, + file: Handle, + tree_cursor: Cursor, + graph: Graph, + remapped_nodes: HashMap, + injected_node_count: usize, +} + +impl<'a, KT: KindTypes + 'static> Builder<'a, KT> { + pub fn new( + msgb: &'a GraphBuilderFile, + functions: &'a Functions, + stack_graph: &'a mut StackGraph, + file: Handle, + tree_cursor: Cursor, + ) -> Self { + Builder { + msgb, + functions, + stack_graph, + file, + tree_cursor, + graph: Graph::new(), + remapped_nodes: HashMap::new(), + injected_node_count: 0, + } + } + + fn build_global_variables(&mut self, globals: &Globals<'_>) -> Variables<'a> { + let mut variables = Variables::new(); + variables + .add(FILE_PATH_VAR.into(), globals.file_path.into()) + .expect("failed to add FILE_PATH variable"); + variables + .add(VERSION_VAR.into(), globals.version.to_string().into()) + .expect("failed to add VERSION_VAR variable"); + + let root_node = self.inject_node(NodeID::root()); + variables + .add(ROOT_NODE_VAR.into(), root_node.into()) + .expect("Failed to set ROOT_NODE"); + + variables + } + + /// Executes this builder. + pub fn build( + &mut self, + globals: &Globals<'_>, + cancellation_flag: &dyn CancellationFlag, + on_added_node: impl FnMut(Handle, &Cursor), + ) -> Result<(), BuildError> { + let variables = self.build_global_variables(globals); + + let config = ExecutionConfig::new(self.functions, &variables) + .lazy(true) + .debug_attributes( + [DEBUG_ATTR_PREFIX, "msgb_location"] + .concat() + .as_str() + .into(), + [DEBUG_ATTR_PREFIX, "msgb_variable"] + .concat() + .as_str() + .into(), + [DEBUG_ATTR_PREFIX, "msgb_match_node"] + .concat() + .as_str() + .into(), + ); + + self.msgb.execute_into( + &mut self.graph, + &self.tree_cursor, + &config, + &(cancellation_flag as &dyn CancellationFlag), + )?; + + self.load(cancellation_flag, on_added_node) + } + + /// Create a graph node to represent the stack graph node. It is the callers responsibility to + /// ensure the stack graph node exists. + pub fn inject_node(&mut self, id: NodeID) -> GraphNodeRef { + let node = self.graph.add_graph_node(); + self.remapped_nodes.insert(node.index(), id); + self.injected_node_count += 1; + node + } +} + +/// An error that can occur while loading a stack graph from a TSG file +#[derive(Debug, Error)] +pub enum BuildError { + #[error("{0}")] + Cancelled(&'static str), + #[error("Missing ‘type’ attribute on graph node")] + MissingNodeType(GraphNodeRef), + #[error("Missing ‘symbol’ attribute on graph node")] + MissingSymbol(GraphNodeRef), + #[error("Missing ‘scope’ attribute on graph node")] + MissingScope(GraphNodeRef), + #[error("Unknown ‘{0}’ flag type {1}")] + UnknownFlagType(String, String), + #[error("Unknown node type {0}")] + UnknownNodeType(String), + #[error("Unknown symbol type {0}")] + UnknownSymbolType(String), + #[error(transparent)] + ExecutionError(ExecutionError), + #[error("Error converting shorthand ‘{0}’ on {1} with value {2}")] + ConversionError(String, String, String), + #[error("Expected exported symbol scope in {0}, got {1}")] + SymbolScopeError(String, String), +} + +impl From for BuildError { + fn from(value: stack_graphs::CancellationError) -> Self { + Self::Cancelled(value.0) + } +} + +impl From for BuildError { + fn from(value: ExecutionError) -> Self { + match value { + ExecutionError::Cancelled(err) => Self::Cancelled(err.0), + err => Self::ExecutionError(err), + } + } +} + +impl BuildError { + pub fn display_pretty<'a>( + &'a self, + source_path: &'a Path, + source: &'a str, + tsg_path: &'a Path, + tsg: &'a str, + ) -> impl std::fmt::Display + 'a { + DisplayBuildErrorPretty { + error: self, + source_path, + source, + tsg_path, + tsg, + } + } +} + +struct DisplayBuildErrorPretty<'a> { + error: &'a BuildError, + source_path: &'a Path, + source: &'a str, + tsg_path: &'a Path, + tsg: &'a str, +} + +impl std::fmt::Display for DisplayBuildErrorPretty<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self.error { + BuildError::ExecutionError(err) => write!( + f, + "{}", + err.display_pretty(self.source_path, self.source, self.tsg_path, self.tsg) + ), + err => err.fmt(f), + } + } +} + +impl<'a, KT: KindTypes + 'static> Builder<'a, KT> { + fn load( + &mut self, + cancellation_flag: &dyn CancellationFlag, + mut on_added_node: impl FnMut(Handle, &Cursor), + ) -> Result<(), BuildError> { + let cancellation_flag: &dyn stack_graphs::CancellationFlag = &cancellation_flag; + + // By default graph ids are used for stack graph local_ids. A remapping is computed + // for local_ids that already exist in the graph---all other graph ids are mapped to + // the same local_id. See [`self.node_id_for_index`] for more details. + let mut next_local_id = u32::try_from(self.graph.node_count() - self.injected_node_count) + .expect("nodes local_id to fit in u32"); + for node in self.stack_graph.nodes_for_file(self.file) { + let local_id = self.stack_graph[node].id().local_id(); + let index = (local_id as usize) + self.injected_node_count; + // find next available local_id for which no stack graph node exists yet + while self + .stack_graph + .node_for_id(NodeID::new_in_file(self.file, next_local_id)) + .is_some() + { + next_local_id += 1; + } + // remap graph node index to the available stack graph node local_id + if self + .remapped_nodes + .insert(index, NodeID::new_in_file(self.file, next_local_id)) + .is_some() + { + panic!("index already remapped"); + } + } + + // First create a stack graph node for each TSG node. (The skip(...) is because the first + // DSL nodes that we create are the proxies for the injected stack graph nodes.) + for node_ref in self.graph.iter_nodes().skip(self.injected_node_count) { + cancellation_flag.check("loading graph nodes")?; + let node_type = self.get_node_type(node_ref)?; + let handle = match node_type { + NodeType::DropScopes => self.load_drop_scopes(node_ref), + NodeType::PopScopedSymbol => self.load_pop_scoped_symbol(node_ref)?, + NodeType::PopSymbol => self.load_pop_symbol(node_ref)?, + NodeType::PushScopedSymbol => self.load_push_scoped_symbol(node_ref)?, + NodeType::PushSymbol => self.load_push_symbol(node_ref)?, + NodeType::Scope => self.load_scope(node_ref)?, + }; + self.load_source_info(node_ref, handle)?; + self.load_node_debug_info(node_ref, handle); + + // For every added graph node which links to a corresponding source + // node, invoke the callback so our caller can link the newly built + // node with the matching CST cursor. + let node = &self.graph[node_ref]; + if let Some(source_node) = node.attributes.get(SOURCE_NODE_ATTR) { + let syntax_node_ref = source_node.as_syntax_node_ref()?; + let source_node = &self.graph[syntax_node_ref]; + on_added_node(handle, source_node); + } + } + + for node in self.stack_graph.nodes_for_file(self.file) { + self.verify_node(node)?; + } + + // Then add stack graph edges for each TSG edge. Note that we _don't_ skip(...) here because + // there might be outgoing nodes from the “root” node that we need to process. + // (Technically the caller could add outgoing nodes from “jump to scope” as well, but those + // are invalid according to the stack graph semantics and will never be followed. + for source_ref in self.graph.iter_nodes() { + let source = &self.graph[source_ref]; + let source_node_id = self.node_id_for_graph_node(source_ref); + let source_handle = self.stack_graph.node_for_id(source_node_id).unwrap(); + for (sink_ref, edge) in source.iter_edges() { + cancellation_flag.check("loading graph edges")?; + let precedence = match edge.attributes.get(PRECEDENCE_ATTR) { + Some(precedence) => precedence.as_integer()?, + None => 0, + } + .try_into() + .map_err(|_| ExecutionError::ExpectedInteger("integer does not fit".to_string()))?; + let sink_node_id = self.node_id_for_graph_node(sink_ref); + let sink_handle = self.stack_graph.node_for_id(sink_node_id).unwrap(); + self.stack_graph + .add_edge(source_handle, sink_handle, precedence); + Self::load_edge_debug_info(self.stack_graph, source_handle, sink_handle, edge); + } + } + + Ok(()) + } + + fn get_node_type(&self, node_ref: GraphNodeRef) -> Result { + let node = &self.graph[node_ref]; + let node_type = match node.attributes.get(TYPE_ATTR) { + Some(node_type) => node_type.as_str()?, + None => return Ok(NodeType::Scope), + }; + if node_type == DROP_SCOPES_TYPE { + Ok(NodeType::DropScopes) + } else if node_type == POP_SCOPED_SYMBOL_TYPE { + Ok(NodeType::PopScopedSymbol) + } else if node_type == POP_SYMBOL_TYPE { + Ok(NodeType::PopSymbol) + } else if node_type == PUSH_SCOPED_SYMBOL_TYPE { + Ok(NodeType::PushScopedSymbol) + } else if node_type == PUSH_SYMBOL_TYPE { + Ok(NodeType::PushSymbol) + } else if node_type == SCOPE_TYPE { + Ok(NodeType::Scope) + } else { + Err(BuildError::UnknownNodeType(node_type.to_string())) + } + } + + fn verify_node(&self, node: Handle) -> Result<(), BuildError> { + if let Node::PushScopedSymbol(node) = &self.stack_graph[node] { + let scope = &self.stack_graph[self.stack_graph.node_for_id(node.scope).unwrap()]; + if !scope.is_exported_scope() { + return Err(BuildError::SymbolScopeError( + format!("{}", node.display(self.stack_graph)), + format!("{}", scope.display(self.stack_graph)), + )); + } + } + Ok(()) + } +} + +enum NodeType { + DropScopes, + PopSymbol, + PopScopedSymbol, + PushSymbol, + PushScopedSymbol, + Scope, +} + +impl<'a, KT: KindTypes> Builder<'a, KT> { + /// Get the `NodeID` corresponding to a `Graph` node. + /// + /// By default, graph nodes get their index shifted by [`self.injected_node_count`] as their + /// `local_id`, unless they have a corresponding entry in the [`self.remapped_nodes`] map. This + /// is the case if: + /// 1. The node was injected, in which case it is mapped to the `NodeID` of the injected node. + /// 2. The node's default `local_id` clashes with a preexisting node, in which case it is mapped to + /// an available `local_id` beyond the range of default `local_ids`. + fn node_id_for_graph_node(&self, node_ref: GraphNodeRef) -> NodeID { + let index = node_ref.index(); + self.remapped_nodes.get(&index).map_or_else( + || { + NodeID::new_in_file( + self.file, + u32::try_from(index - self.injected_node_count) + .expect("local_id to fit in u32"), + ) + }, + |id| *id, + ) + } + + fn load_drop_scopes(&mut self, node_ref: GraphNodeRef) -> Handle { + let id = self.node_id_for_graph_node(node_ref); + self.stack_graph.add_drop_scopes_node(id).unwrap() + } + + fn load_pop_scoped_symbol( + &mut self, + node_ref: GraphNodeRef, + ) -> Result, BuildError> { + let node = &self.graph[node_ref]; + let symbol = match node.attributes.get(SYMBOL_ATTR) { + Some(symbol) => Self::load_symbol(symbol)?, + None => return Err(BuildError::MissingSymbol(node_ref)), + }; + let symbol = self.stack_graph.add_symbol(&symbol); + let id = self.node_id_for_graph_node(node_ref); + let is_definition = Self::load_flag(node, IS_DEFINITION_ATTR)?; + Self::verify_attributes(node, POP_SCOPED_SYMBOL_TYPE, &POP_SCOPED_SYMBOL_ATTRS); + let node_handle = self + .stack_graph + .add_pop_scoped_symbol_node(id, symbol, is_definition) + .unwrap(); + if is_definition { + self.load_definiens_info(node_ref, node_handle)?; + } + Ok(node_handle) + } + + fn load_pop_symbol(&mut self, node_ref: GraphNodeRef) -> Result, BuildError> { + let node = &self.graph[node_ref]; + let symbol = match node.attributes.get(SYMBOL_ATTR) { + Some(symbol) => Self::load_symbol(symbol)?, + None => return Err(BuildError::MissingSymbol(node_ref)), + }; + let symbol = self.stack_graph.add_symbol(&symbol); + let id = self.node_id_for_graph_node(node_ref); + let is_definition = Self::load_flag(node, IS_DEFINITION_ATTR)?; + Self::verify_attributes(node, POP_SYMBOL_TYPE, &POP_SYMBOL_ATTRS); + let node_handle = self + .stack_graph + .add_pop_symbol_node(id, symbol, is_definition) + .unwrap(); + if is_definition { + self.load_definiens_info(node_ref, node_handle)?; + } + Ok(node_handle) + } + + fn load_push_scoped_symbol( + &mut self, + node_ref: GraphNodeRef, + ) -> Result, BuildError> { + let node = &self.graph[node_ref]; + let symbol = match node.attributes.get(SYMBOL_ATTR) { + Some(symbol) => Self::load_symbol(symbol)?, + None => return Err(BuildError::MissingSymbol(node_ref)), + }; + let symbol = self.stack_graph.add_symbol(&symbol); + let id = self.node_id_for_graph_node(node_ref); + let scope = match node.attributes.get(SCOPE_ATTR) { + Some(scope) => self.node_id_for_graph_node(scope.as_graph_node_ref()?), + None => return Err(BuildError::MissingScope(node_ref)), + }; + let is_reference = Self::load_flag(node, IS_REFERENCE_ATTR)?; + Self::verify_attributes(node, PUSH_SCOPED_SYMBOL_TYPE, &PUSH_SCOPED_SYMBOL_ATTRS); + Ok(self + .stack_graph + .add_push_scoped_symbol_node(id, symbol, scope, is_reference) + .unwrap()) + } + + fn load_push_symbol(&mut self, node_ref: GraphNodeRef) -> Result, BuildError> { + let node = &self.graph[node_ref]; + let symbol = match node.attributes.get(SYMBOL_ATTR) { + Some(symbol) => Self::load_symbol(symbol)?, + None => return Err(BuildError::MissingSymbol(node_ref)), + }; + let symbol = self.stack_graph.add_symbol(&symbol); + let id = self.node_id_for_graph_node(node_ref); + let is_reference = Self::load_flag(node, IS_REFERENCE_ATTR)?; + Self::verify_attributes(node, PUSH_SYMBOL_TYPE, &PUSH_SYMBOL_ATTRS); + Ok(self + .stack_graph + .add_push_symbol_node(id, symbol, is_reference) + .unwrap()) + } + + fn load_scope(&mut self, node_ref: GraphNodeRef) -> Result, BuildError> { + let node = &self.graph[node_ref]; + let id = self.node_id_for_graph_node(node_ref); + let is_exported = + Self::load_flag(node, IS_EXPORTED_ATTR)? || Self::load_flag(node, IS_ENDPOINT_ATTR)?; + Self::verify_attributes(node, SCOPE_TYPE, &SCOPE_ATTRS); + Ok(self.stack_graph.add_scope_node(id, is_exported).unwrap()) + } + + fn load_symbol(value: &Value) -> Result { + match value { + Value::Integer(i) => Ok(i.to_string()), + Value::String(s) => Ok(s.clone()), + _ => Err(BuildError::UnknownSymbolType(value.to_string())), + } + } + + fn load_flag(node: &GraphNode, attribute: &str) -> Result { + match node.attributes.get(attribute) { + Some(value) => value + .as_boolean() + .map_err(|_| BuildError::UnknownFlagType(attribute.to_string(), value.to_string())), + None => Ok(false), + } + } + + fn load_source_info( + &mut self, + node_ref: GraphNodeRef, + node_handle: Handle, + ) -> Result<(), BuildError> { + let node = &self.graph[node_ref]; + + if let Some(source_node) = node.attributes.get(SOURCE_NODE_ATTR) { + let syntax_node_ref = source_node.as_syntax_node_ref()?; + let source_node = &self.graph[syntax_node_ref]; + let mut source_range = source_node.text_range(); + if match node.attributes.get(EMPTY_SOURCE_SPAN_ATTR) { + Some(empty_source_span) => empty_source_span.as_boolean()?, + None => false, + } { + source_range.end = source_range.start; + } + let _source_info = self.stack_graph.source_info_mut(node_handle); + // TODO: map node's TextRange into a Span + // source_info.span = text_range_into_span(source_range); + } + + if let Some(syntax_type) = node.attributes.get(SYNTAX_TYPE_ATTR) { + let syntax_type = syntax_type.as_str()?; + let syntax_type = self.stack_graph.add_string(syntax_type); + let source_info = self.stack_graph.source_info_mut(node_handle); + source_info.syntax_type = syntax_type.into(); + } + + Ok(()) + } + + // TODO: we can probably remove this and all references to definiens since + // we're not gonna need it? + fn load_definiens_info( + &mut self, + node_ref: GraphNodeRef, + node_handle: Handle, + ) -> Result<(), BuildError> { + let node = &self.graph[node_ref]; + let definiens_node = match node.attributes.get(DEFINIENS_NODE_ATTR) { + Some(Value::Null) => return Ok(()), + Some(definiens_node) => &self.graph[definiens_node.as_syntax_node_ref()?], + None => return Ok(()), + }; + let _definiens_range = definiens_node.text_range(); + let _source_info = self.stack_graph.source_info_mut(node_handle); + // TODO: map node's TextRange into a Span + // source_info.definiens_span = text_range_into_span(definiens_range); + Ok(()) + } + + fn load_node_debug_info(&mut self, node_ref: GraphNodeRef, node_handle: Handle) { + let node = &self.graph[node_ref]; + for (name, value) in node.attributes.iter() { + let name = name.to_string(); + if let Some(name_without_prefix) = name.strip_prefix(DEBUG_ATTR_PREFIX) { + let value = match value { + Value::String(value) => value.clone(), + value => value.to_string(), + }; + let key = self.stack_graph.add_string(name_without_prefix); + let value = self.stack_graph.add_string(&value); + self.stack_graph + .node_debug_info_mut(node_handle) + .add(key, value); + } + } + } + + fn load_edge_debug_info( + stack_graph: &mut StackGraph, + source_handle: Handle, + sink_handle: Handle, + edge: &Edge, + ) { + for (name, value) in edge.attributes.iter() { + let name = name.to_string(); + if let Some(name_without_prefix) = name.strip_prefix(DEBUG_ATTR_PREFIX) { + let value = match value { + Value::String(value) => value.clone(), + value => value.to_string(), + }; + let key = stack_graph.add_string(name_without_prefix); + let value = stack_graph.add_string(&value); + stack_graph + .edge_debug_info_mut(source_handle, sink_handle) + .add(key, value); + } + } + } + + fn verify_attributes( + node: &GraphNode, + node_type: &str, + allowed_attributes: &HashSet<&'static str>, + ) { + for (id, _) in node.attributes.iter() { + let id = id.as_str(); + if !allowed_attributes.contains(id) + && id != SOURCE_NODE_ATTR + && id != EMPTY_SOURCE_SPAN_ATTR + && !id.starts_with(DEBUG_ATTR_PREFIX) + { + eprintln!("Unexpected attribute {id} on node of type {node_type}"); + } + } + } +} diff --git a/crates/metaslang/bindings/src/lib.rs b/crates/metaslang/bindings/src/lib.rs new file mode 100644 index 0000000000..8ceaca1309 --- /dev/null +++ b/crates/metaslang/bindings/src/lib.rs @@ -0,0 +1,140 @@ +pub mod builder; + +use std::collections::{BTreeSet, HashMap}; +use std::fmt; +use std::fmt::Debug; +use std::iter::once; + +use metaslang_cst::cursor::Cursor; +use metaslang_cst::KindTypes; +use metaslang_graph_builder::ast::File; +use metaslang_graph_builder::functions::Functions; +use semver::Version; +use stack_graphs::graph::StackGraph; +use stack_graphs::partial::PartialPaths; +use stack_graphs::stitching::{ForwardPartialPathStitcher, GraphEdgeCandidates, StitcherConfig}; + +type Builder<'a, KT> = builder::Builder<'a, KT>; +type GraphHandle = stack_graphs::arena::Handle; + +pub struct Bindings { + version: Version, + graph_builder_file: File, + functions: Functions, + stack_graph: StackGraph, + cursors: HashMap>, +} + +impl Bindings { + pub fn create(version: Version, binding_rules: &str) -> Self { + let graph_builder_file = + File::from_str(binding_rules).expect("Bindings stack graph builder parse error"); + let stack_graph = StackGraph::new(); + let functions = Functions::stdlib(); + let cursors = HashMap::new(); + + Self { + version, + graph_builder_file, + functions, + stack_graph, + cursors, + } + } + + pub fn add_file(&mut self, file_path: &str, tree_cursor: Cursor) { + let globals = builder::Globals { + version: &self.version, + file_path, + }; + let file = self.stack_graph.get_or_create_file(file_path); + + let mut builder = Builder::new( + &self.graph_builder_file, + &self.functions, + &mut self.stack_graph, + file, + tree_cursor, + ); + builder + .build(&globals, &builder::NoCancellation, |handle, cursor| { + self.cursors.insert(handle, cursor.clone()); + }) + .expect("Internal error while building bindings"); + } + + pub fn all_definitions(&self) -> impl Iterator> + '_ { + self.stack_graph + .iter_nodes() + .filter(|handle| self.stack_graph[*handle].is_definition()) + .map(|handle| Handle { + owner: self, + handle, + }) + } + + pub fn all_references(&self) -> impl Iterator> + '_ { + self.stack_graph + .iter_nodes() + .filter(|handle| self.stack_graph[*handle].is_reference()) + .map(|handle| Handle { + owner: self, + handle, + }) + } +} + +pub struct Handle<'a, KT: KindTypes + 'static> { + owner: &'a Bindings, + handle: GraphHandle, +} + +impl Handle<'_, KT> { + pub fn is_definition(&self) -> bool { + self.owner.stack_graph[self.handle].is_definition() + } + + pub fn is_reference(&self) -> bool { + self.owner.stack_graph[self.handle].is_reference() + } + + pub fn get_cursor(&self) -> Option> { + self.owner.cursors.get(&self.handle).cloned() + } + + pub fn get_file(&self) -> Option<&str> { + self.owner.stack_graph[self.handle] + .file() + .map(|file| self.owner.stack_graph[file].name()) + } + + pub fn jump_to_definition(&self) -> Option { + let mut paths = PartialPaths::new(); + let mut results = BTreeSet::new(); + if self.is_reference() { + ForwardPartialPathStitcher::find_all_complete_partial_paths( + &mut GraphEdgeCandidates::new(&self.owner.stack_graph, &mut paths, None), + once(self.handle), + StitcherConfig::default(), + &stack_graphs::NoCancellation, + |_graph, _paths, path| { + results.insert(path.end_node); + }, + ) + .expect("should never be cancelled"); + } + if results.len() > 1 { + println!("WARN: More than one definition found for {self:?}"); + } + results.first().map(|handle| Handle { + owner: self.owner, + handle: *handle, + }) + } +} + +impl Debug for Handle<'_, KT> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_tuple("BindingsHandle").field(&self.handle).finish() + } +} diff --git a/crates/solidity/inputs/language/bindings/rules.msgb b/crates/solidity/inputs/language/bindings/rules.msgb index 4254c58268..9466adb5f6 100644 --- a/crates/solidity/inputs/language/bindings/rules.msgb +++ b/crates/solidity/inputs/language/bindings/rules.msgb @@ -1 +1,363 @@ -// TODO(#982): add rules for Solidity here... +attribute node_definition = node => type = "pop_symbol", node_symbol = node, is_definition +attribute node_reference = node => type = "push_symbol", node_symbol = node, is_reference +attribute node_symbol = node => symbol = (source-text node), source_node = node +attribute pop_symbol = symbol => type = "pop_symbol", symbol = symbol +attribute push_symbol = symbol => type = "push_symbol", symbol = symbol +attribute symbol_definition = symbol => type = "pop_symbol", symbol = symbol, is_definition +attribute symbol_reference = symbol => type = "push_symbol", symbol = symbol, is_reference + +;; Generalities +;; - we will define two nodes for all meaningful CST nodes +;; - a lexical_scope node which will connect "upwards" towards the root of the CST +;; - a defs node to access the definitions reachable from each node (usually connecting "downwards") +;; - the pair will not be created for every CST node, as there is a lot of redundancy in the tree +;; - identifier nodes that are part of the definition of an artifact +;; will create graph nodes with the node_definition attributes +;; - identifier nodes that are references will create graph nodes with the node_reference attributes + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; Source unit (aka .sol file) +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +@source_unit [SourceUnit] { + node @source_unit.lexical_scope + node @source_unit.defs +} + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; Contract definitions +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +@contract [ContractDefinition] { + node @contract.lexical_scope + node @contract.defs +} + +@contract [ContractDefinition ... @name name: [Identifier] ...] { + node def + attr (def) node_definition = @name + + edge @contract.lexical_scope -> def + edge @contract.defs -> def +} + +;; Connect the contract to its containing source unit +@source_unit [SourceUnit ... [SourceUnitMembers + ... + [SourceUnitMember @contract [ContractDefinition]] + ... +] ...] { + edge @source_unit.defs -> @contract.defs + edge @contract.lexical_scope -> @source_unit.lexical_scope +} + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; Interface definitions +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +@interface [InterfaceDefinition] { + node @interface.lexical_scope + node @interface.defs +} + +@interface [InterfaceDefinition ... @name name: [Identifier] ...] { + node def + attr (def) node_definition = @name + + edge @interface.lexical_scope -> def + edge @interface.defs -> def +} + +;; Connect the interface to its containing source unit +@source_unit [SourceUnit [SourceUnitMembers + ... + [SourceUnitMember @interface [InterfaceDefinition]] + ... +]] { + edge @source_unit.defs -> @interface.defs + edge @interface.lexical_scope -> @source_unit.lexical_scope +} + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; Library definitions +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +@library [LibraryDefinition] { + node @library.lexical_scope + node @library.defs +} + +@library [LibraryDefinition ... @name name: [Identifier] ...] { + node def + attr (def) node_definition = @name + + edge @library.lexical_scope -> def + edge @library.defs -> def +} + +;; Connect the library to its containing source unit +@source_unit [SourceUnit [SourceUnitMembers + ... + [SourceUnitMember @library [LibraryDefinition]] + ... +]] { + edge @source_unit.defs -> @library.defs + edge @library.lexical_scope -> @source_unit.lexical_scope +} + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; Function definitions +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +@function [FunctionDefinition] { + node @function.lexical_scope + node @function.defs +} + +@function [FunctionDefinition ... name: [FunctionName ... @name [Identifier] ...] ...] { + node def + attr (def) node_definition = @name + + edge @function.lexical_scope -> def + edge @function.defs -> def +} + +@param [Parameter] { + node @param.lexical_scope + node @param.defs +} + +@param [Parameter ... @name [Identifier]] { + node def + attr (def) node_definition = @name + + edge @param.lexical_scope -> def + edge @param.defs -> def +} + +;; Connect the parameters to the functions they belong to +@function [FunctionDefinition + ... + parameters: [_ ... parameters: [Parameters ... @param item: [Parameter] ...] ...] + ... +] { + edge @function.lexical_scope -> @param.defs + edge @function.defs -> @param.defs +} + +;; Connect the function to the contract/interface/library they belong to +[SourceUnitMember @unit_member variant: [_ + ... + members: [_ + ... + item: [_ @function variant: [FunctionDefinition]] + ... + ] + ... + ] +] { + edge @unit_member.lexical_scope -> @function.defs + edge @unit_member.defs -> @function.defs + edge @function.lexical_scope -> @unit_member.lexical_scope +} + +@body [FunctionBody] { + node @body.lexical_scope + node @body.defs +} + +;; Connect the function body to the function definition +@function [FunctionDefinition ... @body body: [FunctionBody] ...] { + edge @body.lexical_scope -> @function.lexical_scope + edge @function.defs -> @body.defs +} + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; Blocks +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +@block [Block] { + node @block.lexical_scope + node @block.defs +} + +@stmt [Statement] { + node @stmt.lexical_scope + node @stmt.defs +} + +@block [Block ... statements: [_ ... @stmt [Statement]...] ...] { + edge @stmt.lexical_scope -> @block.lexical_scope + edge @block.defs -> @stmt.defs +} + +@body [FunctionBody @block variant: [Block]] { + edge @block.lexical_scope -> @body.lexical_scope + edge @body.defs -> @block.defs +} + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; Declaration Statements +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +@stmt [Statement [VariableDeclarationStatement ... @name name: [Identifier] ...]] { + node def + attr (def) node_definition = @name + + edge @stmt.lexical_scope -> def + edge @stmt.defs -> def +} + +@stmt [Statement [TupleDeconstructionStatement + ... + [TupleDeconstructionElements + ... + item: [_ member: [_ variant: [_ ... @name name: [Identifier]]]] + ... + ] + ... +]] { + node def + attr (def) node_definition = @name + + edge @stmt.lexical_scope -> def + edge @stmt.defs -> def +} + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; State Variables +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +@state_var [StateVariableDefinition] { + node @state_var.lexical_scope + node @state_var.defs +} + +@state_var [StateVariableDefinition ... @name name: [Identifier] ...] { + node def + attr (def) node_definition = @name + + edge @state_var.lexical_scope -> def + edge @state_var.defs -> def +} + +[SourceUnitMember @unit_member variant: [_ + ... + members: [_ + ... + item: [_ @state_var variant: [StateVariableDefinition]] + ... + ] + ... + ] +] { + edge @unit_member.lexical_scope -> @state_var.defs + edge @unit_member.defs -> @state_var.defs + edge @state_var.lexical_scope -> @unit_member.lexical_scope +} + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; Structure definitions +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +@struct [StructDefinition] { + node @struct.lexical_scope + node @struct.defs +} + +@struct [StructDefinition ... @name name: [Identifier] ...] { + node def + attr (def) node_definition = @name + + edge @struct.lexical_scope -> def + edge @struct.defs -> def +} + +;; Connect the struct to the contract/interface/library they belong to +[SourceUnitMember @unit_member variant: [_ + ... + members: [_ + ... + item: [_ @struct variant: [StructDefinition]] + ... + ] + ... + ] +] { + edge @unit_member.lexical_scope -> @struct.defs + edge @unit_member.defs -> @struct.defs +} + +@member [StructMember] { + node @member.lexical_scope + node @member.defs +} + +@member item: [StructMember ... @name name: [Identifier] ...] { + node member + attr (member) pop_symbol = "." + edge @member.defs -> member + + node def + attr (def) node_definition = @name + edge member -> def +} + +;; TODO: missing connection between the struct field declarations and the struct + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; Expressions +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +@expr [Expression] { + node @expr.lexical_scope +} + +@expr [Expression ... @name variant: [Identifier]] { + node ref + attr (ref) node_reference = @name + + edge ref -> @expr.lexical_scope +} + +@expr [Expression ... variant: [_ ... @child [Expression] ...] ...] { + edge @child.lexical_scope -> @expr.lexical_scope +} + +@stmt [Statement ... variant: [_ ... @expr [Expression] ...] ...] { + edge @expr.lexical_scope -> @stmt.lexical_scope +} + +@member [MemberAccess] { + node @member.lexical_scope +} + +@member [MemberAccess @name [Identifier]] { + node ref + attr (ref) node_reference = @name + + edge ref -> @member.lexical_scope +} + +[MemberAccessExpression + ... + @expr operand: [Expression] + ... + @member member: [MemberAccess] + ... +] { + node member + attr (member) push_symbol = "." + + edge @member.lexical_scope -> member + edge member -> @expr.lexical_scope +} diff --git a/crates/solidity/outputs/cargo/slang_solidity/Cargo.toml b/crates/solidity/outputs/cargo/slang_solidity/Cargo.toml index 98e1f55650..a11d7e0ea4 100644 --- a/crates/solidity/outputs/cargo/slang_solidity/Cargo.toml +++ b/crates/solidity/outputs/cargo/slang_solidity/Cargo.toml @@ -37,7 +37,7 @@ cli = ["dep:clap", "dep:serde_json", "__private_ariadne"] # This is meant to be used by the CLI or internally only. __private_ariadne = ["dep:ariadne"] # For internal development only -__experimental_bindings_api = ["dep:metaslang_graph_builder"] +__experimental_bindings_api = ["dep:metaslang_bindings"] [build-dependencies] # __REMOVE_THIS_LINE_DURING_CARGO_PUBLISH__ anyhow = { workspace = true } # __REMOVE_THIS_LINE_DURING_CARGO_PUBLISH__ @@ -48,8 +48,8 @@ solidity_language = { workspace = true } # __REMOVE_THIS_LINE_DURING_CAR [dependencies] ariadne = { workspace = true, optional = true } clap = { workspace = true, optional = true } +metaslang_bindings = { workspace = true, optional = true } metaslang_cst = { workspace = true } -metaslang_graph_builder = { workspace = true, optional = true } semver = { workspace = true } serde = { workspace = true } serde_json = { workspace = true, optional = true } diff --git a/crates/solidity/outputs/cargo/slang_solidity/src/generated/bindings/generated/binding_rules.rs b/crates/solidity/outputs/cargo/slang_solidity/src/generated/bindings/generated/binding_rules.rs index 49adbd1481..7715b748c1 100644 --- a/crates/solidity/outputs/cargo/slang_solidity/src/generated/bindings/generated/binding_rules.rs +++ b/crates/solidity/outputs/cargo/slang_solidity/src/generated/bindings/generated/binding_rules.rs @@ -3,6 +3,368 @@ #[allow(clippy::needless_raw_string_hashes)] #[allow(dead_code)] // TODO(#982): use to create the graph pub const BINDING_RULES_SOURCE: &str = r#####" - // TODO(#982): add rules for Solidity here... + attribute node_definition = node => type = "pop_symbol", node_symbol = node, is_definition +attribute node_reference = node => type = "push_symbol", node_symbol = node, is_reference +attribute node_symbol = node => symbol = (source-text node), source_node = node +attribute pop_symbol = symbol => type = "pop_symbol", symbol = symbol +attribute push_symbol = symbol => type = "push_symbol", symbol = symbol +attribute symbol_definition = symbol => type = "pop_symbol", symbol = symbol, is_definition +attribute symbol_reference = symbol => type = "push_symbol", symbol = symbol, is_reference + +;; Generalities +;; - we will define two nodes for all meaningful CST nodes +;; - a lexical_scope node which will connect "upwards" towards the root of the CST +;; - a defs node to access the definitions reachable from each node (usually connecting "downwards") +;; - the pair will not be created for every CST node, as there is a lot of redundancy in the tree +;; - identifier nodes that are part of the definition of an artifact +;; will create graph nodes with the node_definition attributes +;; - identifier nodes that are references will create graph nodes with the node_reference attributes + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; Source unit (aka .sol file) +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +@source_unit [SourceUnit] { + node @source_unit.lexical_scope + node @source_unit.defs +} + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; Contract definitions +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +@contract [ContractDefinition] { + node @contract.lexical_scope + node @contract.defs +} + +@contract [ContractDefinition ... @name name: [Identifier] ...] { + node def + attr (def) node_definition = @name + + edge @contract.lexical_scope -> def + edge @contract.defs -> def +} + +;; Connect the contract to its containing source unit +@source_unit [SourceUnit ... [SourceUnitMembers + ... + [SourceUnitMember @contract [ContractDefinition]] + ... +] ...] { + edge @source_unit.defs -> @contract.defs + edge @contract.lexical_scope -> @source_unit.lexical_scope +} + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; Interface definitions +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +@interface [InterfaceDefinition] { + node @interface.lexical_scope + node @interface.defs +} + +@interface [InterfaceDefinition ... @name name: [Identifier] ...] { + node def + attr (def) node_definition = @name + + edge @interface.lexical_scope -> def + edge @interface.defs -> def +} + +;; Connect the interface to its containing source unit +@source_unit [SourceUnit [SourceUnitMembers + ... + [SourceUnitMember @interface [InterfaceDefinition]] + ... +]] { + edge @source_unit.defs -> @interface.defs + edge @interface.lexical_scope -> @source_unit.lexical_scope +} + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; Library definitions +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +@library [LibraryDefinition] { + node @library.lexical_scope + node @library.defs +} + +@library [LibraryDefinition ... @name name: [Identifier] ...] { + node def + attr (def) node_definition = @name + + edge @library.lexical_scope -> def + edge @library.defs -> def +} + +;; Connect the library to its containing source unit +@source_unit [SourceUnit [SourceUnitMembers + ... + [SourceUnitMember @library [LibraryDefinition]] + ... +]] { + edge @source_unit.defs -> @library.defs + edge @library.lexical_scope -> @source_unit.lexical_scope +} + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; Function definitions +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +@function [FunctionDefinition] { + node @function.lexical_scope + node @function.defs +} + +@function [FunctionDefinition ... name: [FunctionName ... @name [Identifier] ...] ...] { + node def + attr (def) node_definition = @name + + edge @function.lexical_scope -> def + edge @function.defs -> def +} + +@param [Parameter] { + node @param.lexical_scope + node @param.defs +} + +@param [Parameter ... @name [Identifier]] { + node def + attr (def) node_definition = @name + + edge @param.lexical_scope -> def + edge @param.defs -> def +} + +;; Connect the parameters to the functions they belong to +@function [FunctionDefinition + ... + parameters: [_ ... parameters: [Parameters ... @param item: [Parameter] ...] ...] + ... +] { + edge @function.lexical_scope -> @param.defs + edge @function.defs -> @param.defs +} + +;; Connect the function to the contract/interface/library they belong to +[SourceUnitMember @unit_member variant: [_ + ... + members: [_ + ... + item: [_ @function variant: [FunctionDefinition]] + ... + ] + ... + ] +] { + edge @unit_member.lexical_scope -> @function.defs + edge @unit_member.defs -> @function.defs + edge @function.lexical_scope -> @unit_member.lexical_scope +} + +@body [FunctionBody] { + node @body.lexical_scope + node @body.defs +} + +;; Connect the function body to the function definition +@function [FunctionDefinition ... @body body: [FunctionBody] ...] { + edge @body.lexical_scope -> @function.lexical_scope + edge @function.defs -> @body.defs +} + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; Blocks +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +@block [Block] { + node @block.lexical_scope + node @block.defs +} + +@stmt [Statement] { + node @stmt.lexical_scope + node @stmt.defs +} + +@block [Block ... statements: [_ ... @stmt [Statement]...] ...] { + edge @stmt.lexical_scope -> @block.lexical_scope + edge @block.defs -> @stmt.defs +} + +@body [FunctionBody @block variant: [Block]] { + edge @block.lexical_scope -> @body.lexical_scope + edge @body.defs -> @block.defs +} + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; Declaration Statements +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +@stmt [Statement [VariableDeclarationStatement ... @name name: [Identifier] ...]] { + node def + attr (def) node_definition = @name + + edge @stmt.lexical_scope -> def + edge @stmt.defs -> def +} + +@stmt [Statement [TupleDeconstructionStatement + ... + [TupleDeconstructionElements + ... + item: [_ member: [_ variant: [_ ... @name name: [Identifier]]]] + ... + ] + ... +]] { + node def + attr (def) node_definition = @name + + edge @stmt.lexical_scope -> def + edge @stmt.defs -> def +} + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; State Variables +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +@state_var [StateVariableDefinition] { + node @state_var.lexical_scope + node @state_var.defs +} + +@state_var [StateVariableDefinition ... @name name: [Identifier] ...] { + node def + attr (def) node_definition = @name + + edge @state_var.lexical_scope -> def + edge @state_var.defs -> def +} + +[SourceUnitMember @unit_member variant: [_ + ... + members: [_ + ... + item: [_ @state_var variant: [StateVariableDefinition]] + ... + ] + ... + ] +] { + edge @unit_member.lexical_scope -> @state_var.defs + edge @unit_member.defs -> @state_var.defs + edge @state_var.lexical_scope -> @unit_member.lexical_scope +} + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; Structure definitions +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +@struct [StructDefinition] { + node @struct.lexical_scope + node @struct.defs +} + +@struct [StructDefinition ... @name name: [Identifier] ...] { + node def + attr (def) node_definition = @name + + edge @struct.lexical_scope -> def + edge @struct.defs -> def +} + +;; Connect the struct to the contract/interface/library they belong to +[SourceUnitMember @unit_member variant: [_ + ... + members: [_ + ... + item: [_ @struct variant: [StructDefinition]] + ... + ] + ... + ] +] { + edge @unit_member.lexical_scope -> @struct.defs + edge @unit_member.defs -> @struct.defs +} + +@member [StructMember] { + node @member.lexical_scope + node @member.defs +} + +@member item: [StructMember ... @name name: [Identifier] ...] { + node member + attr (member) pop_symbol = "." + edge @member.defs -> member + + node def + attr (def) node_definition = @name + edge member -> def +} + +;; TODO: missing connection between the struct field declarations and the struct + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; Expressions +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +@expr [Expression] { + node @expr.lexical_scope +} + +@expr [Expression ... @name variant: [Identifier]] { + node ref + attr (ref) node_reference = @name + + edge ref -> @expr.lexical_scope +} + +@expr [Expression ... variant: [_ ... @child [Expression] ...] ...] { + edge @child.lexical_scope -> @expr.lexical_scope +} + +@stmt [Statement ... variant: [_ ... @expr [Expression] ...] ...] { + edge @expr.lexical_scope -> @stmt.lexical_scope +} + +@member [MemberAccess] { + node @member.lexical_scope +} + +@member [MemberAccess @name [Identifier]] { + node ref + attr (ref) node_reference = @name + + edge ref -> @member.lexical_scope +} + +[MemberAccessExpression + ... + @expr operand: [Expression] + ... + @member member: [MemberAccess] + ... +] { + node member + attr (member) push_symbol = "." + + edge @member.lexical_scope -> member + edge member -> @expr.lexical_scope +} "#####; diff --git a/crates/solidity/outputs/cargo/slang_solidity/src/generated/bindings/mod.rs b/crates/solidity/outputs/cargo/slang_solidity/src/generated/bindings/mod.rs index 47ccc571ee..78c2b74f82 100644 --- a/crates/solidity/outputs/cargo/slang_solidity/src/generated/bindings/mod.rs +++ b/crates/solidity/outputs/cargo/slang_solidity/src/generated/bindings/mod.rs @@ -3,10 +3,14 @@ #[path = "generated/binding_rules.rs"] mod binding_rules; -use metaslang_graph_builder::ast; -pub use metaslang_graph_builder::functions::Functions; -pub use metaslang_graph_builder::{ExecutionConfig, ExecutionError, NoCancellation, Variables}; +use metaslang_bindings; +use semver::Version; use crate::cst::KindTypes; -pub type File = ast::File; +pub type Bindings = metaslang_bindings::Bindings; +pub type Handle<'a> = metaslang_bindings::Handle<'a, KindTypes>; + +pub fn create(version: Version) -> Bindings { + Bindings::create(version, binding_rules::BINDING_RULES_SOURCE) +} diff --git a/crates/solidity/outputs/cargo/slang_solidity/src/generated/cli/commands/bindings.rs b/crates/solidity/outputs/cargo/slang_solidity/src/generated/cli/commands/bindings.rs new file mode 100644 index 0000000000..56279e2b34 --- /dev/null +++ b/crates/solidity/outputs/cargo/slang_solidity/src/generated/cli/commands/bindings.rs @@ -0,0 +1,88 @@ +// This file is generated automatically by infrastructure scripts. Please don't edit by hand. + +use core::fmt; + +use semver::Version; + +use super::CommandError; +use crate::bindings::{self, Bindings, Handle}; +use crate::cursor::Cursor; + +pub fn execute(file_path_string: &str, version: Version) -> Result<(), CommandError> { + let mut bindings = bindings::create(version.clone()); + let parse_output = super::parse::parse_source_file(file_path_string, version, |_| ())?; + let tree_cursor = parse_output.create_tree_cursor(); + + bindings.add_file(file_path_string, tree_cursor); + + print_definitions(&bindings); + print_references(&bindings); + + Ok(()) +} + +fn print_definitions(bindings: &Bindings) { + println!("\nAll definitions found:"); + for definition in bindings.all_definitions() { + println!("{}", DisplayDefinition(&definition)); + } +} + +fn print_references(bindings: &Bindings) { + println!("\nAll references found:"); + for reference in bindings.all_references() { + println!("{}", DisplayReference(&reference)); + if let Some(def) = reference.jump_to_definition() { + println!(" -> {}", DisplayDefinition(&def)); + } else { + println!(" -> No definition found"); + } + } +} + +struct DisplayRange<'a>(&'a Cursor); + +impl<'a> fmt::Display for DisplayRange<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let range = self.0.text_range(); + write!(f, "{}..{}", range.start, range.end) + } +} + +struct DisplayDefinition<'a>(&'a Handle<'a>); + +impl<'a> fmt::Display for DisplayDefinition<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let file = self.0.get_file().unwrap_or(""); + if let Some(cursor) = self.0.get_cursor() { + let location = DisplayRange(&cursor); + let identifier = cursor.node().unparse(); + write!(f, "`{identifier}` defined at {location} in {file}") + } else { + write!( + f, + "Definition without available cursor: {definition:?} in {file}", + definition = self.0 + ) + } + } +} + +struct DisplayReference<'a>(&'a Handle<'a>); + +impl<'a> fmt::Display for DisplayReference<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let file = self.0.get_file().unwrap_or(""); + if let Some(cursor) = self.0.get_cursor() { + let location = DisplayRange(&cursor); + let identifier = cursor.node().unparse(); + write!(f, "`{identifier}` referenced at {location} in {file}") + } else { + write!( + f, + "Reference without available cursor: {reference:?} in {file}", + reference = self.0 + ) + } + } +} diff --git a/crates/solidity/outputs/cargo/slang_solidity/src/generated/cli/commands/build_graph.rs b/crates/solidity/outputs/cargo/slang_solidity/src/generated/cli/commands/build_graph.rs deleted file mode 100644 index 45799e4816..0000000000 --- a/crates/solidity/outputs/cargo/slang_solidity/src/generated/cli/commands/build_graph.rs +++ /dev/null @@ -1,59 +0,0 @@ -// This file is generated automatically by infrastructure scripts. Please don't edit by hand. - -use std::fs; -use std::path::PathBuf; - -use semver::Version; - -use super::parse::parse_source_file; -use super::CommandError; -use crate::bindings::{ - ExecutionConfig, File as GraphBuilderFile, Functions, NoCancellation, Variables, -}; - -pub fn execute( - file_path_string: &str, - version: Version, - msgb_path_string: &str, - output_json: bool, - debug: bool, -) -> Result<(), CommandError> { - let parse_output = parse_source_file(file_path_string, version, |_| ())?; - let msgb = parse_graph_builder(msgb_path_string)?; - - let functions = Functions::stdlib(); - let variables = Variables::new(); - let mut execution_config = ExecutionConfig::new(&functions, &variables); - if debug { - execution_config = execution_config.debug_attributes( - "_location".into(), - "_variable".into(), - "_match".into(), - ); - } - - let tree = parse_output.create_tree_cursor(); - let graph = msgb.execute(&tree, &execution_config, &NoCancellation)?; - - if output_json { - graph.display_json(None)?; - } else { - print!("{}", graph.pretty_print()); - } - - Ok(()) -} - -fn parse_graph_builder(msgb_path_string: &str) -> Result { - let msgb_path = PathBuf::from(&msgb_path_string) - .canonicalize() - .map_err(|_| CommandError::FileNotFound(msgb_path_string.to_string()))?; - - let msgb_source = fs::read_to_string(&msgb_path)?; - GraphBuilderFile::from_str(&msgb_source).map_err(|parser_error| { - let error_message = parser_error - .display_pretty(&msgb_path, &msgb_source) - .to_string(); - CommandError::ParseFailed(error_message) - }) -} diff --git a/crates/solidity/outputs/cargo/slang_solidity/src/generated/cli/commands/mod.rs b/crates/solidity/outputs/cargo/slang_solidity/src/generated/cli/commands/mod.rs index 19611de6cc..9427cfd313 100644 --- a/crates/solidity/outputs/cargo/slang_solidity/src/generated/cli/commands/mod.rs +++ b/crates/solidity/outputs/cargo/slang_solidity/src/generated/cli/commands/mod.rs @@ -3,7 +3,7 @@ use thiserror::Error; #[cfg(feature = "__experimental_bindings_api")] -pub mod build_graph; +pub mod bindings; pub mod parse; #[derive(Debug, Error)] @@ -19,8 +19,4 @@ pub enum CommandError { #[error("Parsing failed: {0}")] ParseFailed(String), - - #[cfg(feature = "__experimental_bindings_api")] - #[error(transparent)] - ExecutionFailed(#[from] crate::bindings::ExecutionError), } diff --git a/crates/solidity/outputs/cargo/slang_solidity/src/generated/cli/mod.rs b/crates/solidity/outputs/cargo/slang_solidity/src/generated/cli/mod.rs index a7a8f3e084..78852ab54d 100644 --- a/crates/solidity/outputs/cargo/slang_solidity/src/generated/cli/mod.rs +++ b/crates/solidity/outputs/cargo/slang_solidity/src/generated/cli/mod.rs @@ -23,27 +23,15 @@ pub enum Commands { json: bool, }, - // This is only intended for internal development + /// This is only intended for internal development #[cfg(feature = "__experimental_bindings_api")] - /// Parses a source file and builds a graph executing the instructions from the builder file (*.msgb) - BuildGraph { + Bindings { /// File path to the source file to parse file_path: String, /// The language version to use for parsing #[arg(short, long)] version: Version, - - /// The graph buider (.msgb) file to use - msgb_path: String, - - /// Print the graph as JSON - #[clap(long)] - json: bool, - - /// Include debug info (location, variable and match) in the built graph - #[clap(long)] - debug: bool, }, } @@ -56,13 +44,9 @@ impl Commands { json, } => commands::parse::execute(&file_path, version, json), #[cfg(feature = "__experimental_bindings_api")] - Commands::BuildGraph { - file_path, - version, - msgb_path, - json, - debug, - } => commands::build_graph::execute(&file_path, version, &msgb_path, json, debug), + Commands::Bindings { file_path, version } => { + commands::bindings::execute(&file_path, version) + } }; match command_result { Ok(()) => ExitCode::SUCCESS, diff --git a/crates/solidity/outputs/cargo/slang_solidity/src/main.rs b/crates/solidity/outputs/cargo/slang_solidity/src/main.rs index efb4c26f6a..8677536706 100644 --- a/crates/solidity/outputs/cargo/slang_solidity/src/main.rs +++ b/crates/solidity/outputs/cargo/slang_solidity/src/main.rs @@ -9,7 +9,7 @@ use slang_solidity::cli::Commands; // https://github.com/rust-lang/cargo/issues/1982 mod supress_api_dependencies { #[cfg(feature = "__experimental_bindings_api")] - use metaslang_graph_builder as _; + use metaslang_bindings as _; use { ariadne as _, metaslang_cst as _, semver as _, serde as _, serde_json as _, strum as _, strum_macros as _, thiserror as _, diff --git a/crates/solidity/outputs/cargo/tests/Cargo.toml b/crates/solidity/outputs/cargo/tests/Cargo.toml index d411646ff9..a00b4faa13 100644 --- a/crates/solidity/outputs/cargo/tests/Cargo.toml +++ b/crates/solidity/outputs/cargo/tests/Cargo.toml @@ -19,7 +19,7 @@ infra_utils = { workspace = true } once_cell = { workspace = true } regex = { workspace = true } semver = { workspace = true } -slang_solidity = { workspace = true, features = ["__private_ariadne"] } +slang_solidity = { workspace = true, features = ["__private_ariadne", "__experimental_bindings_api"] } solidity_language = { workspace = true } strum_macros = { workspace = true } diff --git a/crates/testlang/outputs/cargo/slang_testlang/Cargo.toml b/crates/testlang/outputs/cargo/slang_testlang/Cargo.toml index d2bb2f5cb5..639f3428fc 100644 --- a/crates/testlang/outputs/cargo/slang_testlang/Cargo.toml +++ b/crates/testlang/outputs/cargo/slang_testlang/Cargo.toml @@ -13,8 +13,8 @@ infra_utils = { workspace = true } testlang_language = { workspace = true } [dependencies] +metaslang_bindings = { workspace = true, optional = true } metaslang_cst = { workspace = true } -metaslang_graph_builder = { workspace = true, optional = true } semver = { workspace = true } serde = { workspace = true } strum = { workspace = true } @@ -25,4 +25,4 @@ thiserror = { workspace = true } workspace = true [features] -__experimental_bindings_api = ["dep:metaslang_graph_builder"] +__experimental_bindings_api = ["dep:metaslang_bindings"] diff --git a/crates/testlang/outputs/cargo/slang_testlang/src/generated/bindings/mod.rs b/crates/testlang/outputs/cargo/slang_testlang/src/generated/bindings/mod.rs index 47ccc571ee..78c2b74f82 100644 --- a/crates/testlang/outputs/cargo/slang_testlang/src/generated/bindings/mod.rs +++ b/crates/testlang/outputs/cargo/slang_testlang/src/generated/bindings/mod.rs @@ -3,10 +3,14 @@ #[path = "generated/binding_rules.rs"] mod binding_rules; -use metaslang_graph_builder::ast; -pub use metaslang_graph_builder::functions::Functions; -pub use metaslang_graph_builder::{ExecutionConfig, ExecutionError, NoCancellation, Variables}; +use metaslang_bindings; +use semver::Version; use crate::cst::KindTypes; -pub type File = ast::File; +pub type Bindings = metaslang_bindings::Bindings; +pub type Handle<'a> = metaslang_bindings::Handle<'a, KindTypes>; + +pub fn create(version: Version) -> Bindings { + Bindings::create(version, binding_rules::BINDING_RULES_SOURCE) +} diff --git a/crates/testlang/outputs/cargo/slang_testlang/src/generated/cli/commands/bindings.rs b/crates/testlang/outputs/cargo/slang_testlang/src/generated/cli/commands/bindings.rs new file mode 100644 index 0000000000..56279e2b34 --- /dev/null +++ b/crates/testlang/outputs/cargo/slang_testlang/src/generated/cli/commands/bindings.rs @@ -0,0 +1,88 @@ +// This file is generated automatically by infrastructure scripts. Please don't edit by hand. + +use core::fmt; + +use semver::Version; + +use super::CommandError; +use crate::bindings::{self, Bindings, Handle}; +use crate::cursor::Cursor; + +pub fn execute(file_path_string: &str, version: Version) -> Result<(), CommandError> { + let mut bindings = bindings::create(version.clone()); + let parse_output = super::parse::parse_source_file(file_path_string, version, |_| ())?; + let tree_cursor = parse_output.create_tree_cursor(); + + bindings.add_file(file_path_string, tree_cursor); + + print_definitions(&bindings); + print_references(&bindings); + + Ok(()) +} + +fn print_definitions(bindings: &Bindings) { + println!("\nAll definitions found:"); + for definition in bindings.all_definitions() { + println!("{}", DisplayDefinition(&definition)); + } +} + +fn print_references(bindings: &Bindings) { + println!("\nAll references found:"); + for reference in bindings.all_references() { + println!("{}", DisplayReference(&reference)); + if let Some(def) = reference.jump_to_definition() { + println!(" -> {}", DisplayDefinition(&def)); + } else { + println!(" -> No definition found"); + } + } +} + +struct DisplayRange<'a>(&'a Cursor); + +impl<'a> fmt::Display for DisplayRange<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let range = self.0.text_range(); + write!(f, "{}..{}", range.start, range.end) + } +} + +struct DisplayDefinition<'a>(&'a Handle<'a>); + +impl<'a> fmt::Display for DisplayDefinition<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let file = self.0.get_file().unwrap_or(""); + if let Some(cursor) = self.0.get_cursor() { + let location = DisplayRange(&cursor); + let identifier = cursor.node().unparse(); + write!(f, "`{identifier}` defined at {location} in {file}") + } else { + write!( + f, + "Definition without available cursor: {definition:?} in {file}", + definition = self.0 + ) + } + } +} + +struct DisplayReference<'a>(&'a Handle<'a>); + +impl<'a> fmt::Display for DisplayReference<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let file = self.0.get_file().unwrap_or(""); + if let Some(cursor) = self.0.get_cursor() { + let location = DisplayRange(&cursor); + let identifier = cursor.node().unparse(); + write!(f, "`{identifier}` referenced at {location} in {file}") + } else { + write!( + f, + "Reference without available cursor: {reference:?} in {file}", + reference = self.0 + ) + } + } +} diff --git a/crates/testlang/outputs/cargo/slang_testlang/src/generated/cli/commands/build_graph.rs b/crates/testlang/outputs/cargo/slang_testlang/src/generated/cli/commands/build_graph.rs deleted file mode 100644 index 45799e4816..0000000000 --- a/crates/testlang/outputs/cargo/slang_testlang/src/generated/cli/commands/build_graph.rs +++ /dev/null @@ -1,59 +0,0 @@ -// This file is generated automatically by infrastructure scripts. Please don't edit by hand. - -use std::fs; -use std::path::PathBuf; - -use semver::Version; - -use super::parse::parse_source_file; -use super::CommandError; -use crate::bindings::{ - ExecutionConfig, File as GraphBuilderFile, Functions, NoCancellation, Variables, -}; - -pub fn execute( - file_path_string: &str, - version: Version, - msgb_path_string: &str, - output_json: bool, - debug: bool, -) -> Result<(), CommandError> { - let parse_output = parse_source_file(file_path_string, version, |_| ())?; - let msgb = parse_graph_builder(msgb_path_string)?; - - let functions = Functions::stdlib(); - let variables = Variables::new(); - let mut execution_config = ExecutionConfig::new(&functions, &variables); - if debug { - execution_config = execution_config.debug_attributes( - "_location".into(), - "_variable".into(), - "_match".into(), - ); - } - - let tree = parse_output.create_tree_cursor(); - let graph = msgb.execute(&tree, &execution_config, &NoCancellation)?; - - if output_json { - graph.display_json(None)?; - } else { - print!("{}", graph.pretty_print()); - } - - Ok(()) -} - -fn parse_graph_builder(msgb_path_string: &str) -> Result { - let msgb_path = PathBuf::from(&msgb_path_string) - .canonicalize() - .map_err(|_| CommandError::FileNotFound(msgb_path_string.to_string()))?; - - let msgb_source = fs::read_to_string(&msgb_path)?; - GraphBuilderFile::from_str(&msgb_source).map_err(|parser_error| { - let error_message = parser_error - .display_pretty(&msgb_path, &msgb_source) - .to_string(); - CommandError::ParseFailed(error_message) - }) -} diff --git a/crates/testlang/outputs/cargo/slang_testlang/src/generated/cli/commands/mod.rs b/crates/testlang/outputs/cargo/slang_testlang/src/generated/cli/commands/mod.rs index 19611de6cc..9427cfd313 100644 --- a/crates/testlang/outputs/cargo/slang_testlang/src/generated/cli/commands/mod.rs +++ b/crates/testlang/outputs/cargo/slang_testlang/src/generated/cli/commands/mod.rs @@ -3,7 +3,7 @@ use thiserror::Error; #[cfg(feature = "__experimental_bindings_api")] -pub mod build_graph; +pub mod bindings; pub mod parse; #[derive(Debug, Error)] @@ -19,8 +19,4 @@ pub enum CommandError { #[error("Parsing failed: {0}")] ParseFailed(String), - - #[cfg(feature = "__experimental_bindings_api")] - #[error(transparent)] - ExecutionFailed(#[from] crate::bindings::ExecutionError), } diff --git a/crates/testlang/outputs/cargo/slang_testlang/src/generated/cli/mod.rs b/crates/testlang/outputs/cargo/slang_testlang/src/generated/cli/mod.rs index a7a8f3e084..78852ab54d 100644 --- a/crates/testlang/outputs/cargo/slang_testlang/src/generated/cli/mod.rs +++ b/crates/testlang/outputs/cargo/slang_testlang/src/generated/cli/mod.rs @@ -23,27 +23,15 @@ pub enum Commands { json: bool, }, - // This is only intended for internal development + /// This is only intended for internal development #[cfg(feature = "__experimental_bindings_api")] - /// Parses a source file and builds a graph executing the instructions from the builder file (*.msgb) - BuildGraph { + Bindings { /// File path to the source file to parse file_path: String, /// The language version to use for parsing #[arg(short, long)] version: Version, - - /// The graph buider (.msgb) file to use - msgb_path: String, - - /// Print the graph as JSON - #[clap(long)] - json: bool, - - /// Include debug info (location, variable and match) in the built graph - #[clap(long)] - debug: bool, }, } @@ -56,13 +44,9 @@ impl Commands { json, } => commands::parse::execute(&file_path, version, json), #[cfg(feature = "__experimental_bindings_api")] - Commands::BuildGraph { - file_path, - version, - msgb_path, - json, - debug, - } => commands::build_graph::execute(&file_path, version, &msgb_path, json, debug), + Commands::Bindings { file_path, version } => { + commands::bindings::execute(&file_path, version) + } }; match command_result { Ok(()) => ExitCode::SUCCESS, diff --git a/crates/testlang/outputs/cargo/tests/Cargo.toml b/crates/testlang/outputs/cargo/tests/Cargo.toml index 637d232b03..e014cc4c4d 100644 --- a/crates/testlang/outputs/cargo/tests/Cargo.toml +++ b/crates/testlang/outputs/cargo/tests/Cargo.toml @@ -6,6 +6,7 @@ edition.workspace = true publish = false [dev-dependencies] +metaslang_graph_builder = { workspace = true } semver = { workspace = true } slang_testlang = { workspace = true, features = [ "__experimental_bindings_api", diff --git a/crates/testlang/outputs/cargo/tests/src/graph/mod.rs b/crates/testlang/outputs/cargo/tests/src/graph/mod.rs index 2e2b82f076..a8eaa45605 100644 --- a/crates/testlang/outputs/cargo/tests/src/graph/mod.rs +++ b/crates/testlang/outputs/cargo/tests/src/graph/mod.rs @@ -1,5 +1,7 @@ +use metaslang_graph_builder::ast::File; +use metaslang_graph_builder::functions::Functions; +use metaslang_graph_builder::{ExecutionConfig, NoCancellation, Variables}; use semver::Version; -use slang_testlang::bindings::{ExecutionConfig, File, Functions, NoCancellation, Variables}; use slang_testlang::kinds::NonterminalKind; use slang_testlang::language::Language;