Skip to content

Commit

Permalink
First implementation of the Bindings API (NomicFoundation#1011)
Browse files Browse the repository at this point in the history
This PR adds the beginnings of a Bindings API using `stack-graphs` for
the resolution:

- add a new crate `metaslang_bindings` that contains:
- a builder to construct a stack graph from a CST given the binding
rules for the language, based on the tree-sitter-stack-graph crate
- a `Bindings` struct where the Bindings API will be implemented and
exposes methods to add process and add parsed source files into the
underlying stack graph, query definitions and references found in the
stack graph, and jump from a reference to a definition
- adds a command to the `slang_solidity` CLI to test the new API
- adds an incomplete `rules.msgb` for Solidity
- removes the `build-graph` command from `slang_solidity`
- adds a new `bindings` command to list the definitions and references
found in a source file

All the new API and functionality is still hidden under the
`__experimental_bindings_api` feature flag.

With this PR, given the sample file `lexical.sol`:

```solidity
contract Foo {
  uint y;
  
  function bar(uint z) returns (uint) {
    uint x = 10;
    return x + y + z;
  }
}
```

Running `cargo run --all-features --bin slang_solidity -- bindings -v
0.8.22 lexical.sol` will produce the output:

```
All definitions found:
`Foo` defined at 9..12 in lexical.sol
`y` defined at 22..23 in lexical.sol
`bar` defined at 39..42 in lexical.sol
`z` defined at 48..49 in lexical.sol
`x` defined at 77..78 in lexical.sol

All references found:
`x` referenced at 96..97 in lexical.sol
  -> `x` defined at 77..78 in lexical.sol
`y` referenced at 100..101 in lexical.sol
  -> `y` defined at 22..23 in lexical.sol
`z` referenced at 104..105 in lexical.sol
  -> `z` defined at 48..49 in lexical.sol
```

---------

Co-authored-by: Omar Tawfik <[email protected]>
  • Loading branch information
ggiraldez and OmarTawfik authored Jul 10, 2024
1 parent e28edb2 commit f6a2bf9
Show file tree
Hide file tree
Showing 33 changed files with 2,492 additions and 302 deletions.
248 changes: 222 additions & 26 deletions Cargo.lock

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ members = [
"crates/infra/cli",
"crates/infra/utils",

"crates/metaslang/bindings",
"crates/metaslang/cst",
"crates/metaslang/graph_builder",

Expand Down Expand Up @@ -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" }

Expand Down
4 changes: 2 additions & 2 deletions crates/codegen/runtime/cargo/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand All @@ -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
12 changes: 8 additions & 4 deletions crates/codegen/runtime/cargo/src/runtime/bindings/mod.rs
Original file line number Diff line number Diff line change
@@ -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<KindTypes>;
pub type Bindings = metaslang_bindings::Bindings<KindTypes>;
pub type Handle<'a> = metaslang_bindings::Handle<'a, KindTypes>;

pub fn create(version: Version) -> Bindings {
Bindings::create(version, binding_rules::BINDING_RULES_SOURCE)
}
86 changes: 86 additions & 0 deletions crates/codegen/runtime/cargo/src/runtime/cli/commands/bindings.rs
Original file line number Diff line number Diff line change
@@ -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("<unkwown>");
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("<unkwown>");
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
)
}
}
}

This file was deleted.

6 changes: 1 addition & 5 deletions crates/codegen/runtime/cargo/src/runtime/cli/commands/mod.rs
Original file line number Diff line number Diff line change
@@ -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)]
Expand All @@ -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),
}
26 changes: 5 additions & 21 deletions crates/codegen/runtime/cargo/src/runtime/cli/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
},
}

Expand All @@ -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,
Expand Down
1 change: 1 addition & 0 deletions crates/infra/cli/src/commands/publish/cargo/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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",
];

Expand Down
34 changes: 34 additions & 0 deletions crates/metaslang/bindings/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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 <[email protected]>",
"Antony Blakey <[email protected]>",
"Igor Matuszewski <[email protected]>",
"Omar Tawfik <[email protected]>",
]

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
22 changes: 22 additions & 0 deletions crates/metaslang/bindings/LICENSE
Original file line number Diff line number Diff line change
@@ -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.
22 changes: 22 additions & 0 deletions crates/metaslang/bindings/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<!-- markdownlint-disable -->

# 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

<!-- _PRODUCT_README_ (keep in sync) -->

[![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)

<br/>

> ❗ 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.
Loading

0 comments on commit f6a2bf9

Please sign in to comment.