Skip to content

Commit

Permalink
add Compilation API (#1194)
Browse files Browse the repository at this point in the history
And along, a few fixes to the parser and binding APIs (more details in
the added changesets).
  • Loading branch information
OmarTawfik authored Dec 17, 2024
1 parent 2ffc8f5 commit 7a25d63
Show file tree
Hide file tree
Showing 189 changed files with 5,208 additions and 727 deletions.
5 changes: 5 additions & 0 deletions .changeset/fluffy-elephants-beg.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@nomicfoundation/slang": minor
---

rename `parser/Parser.supportedVersions()` API to `utils/LanguageFacts.supportedVersions()`.
5 changes: 5 additions & 0 deletions .changeset/gentle-onions-drum.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@nomicfoundation/slang": minor
---

expose the `BingingGraph` API to allow querying definitions/references between source files.
5 changes: 5 additions & 0 deletions .changeset/pink-flowers-draw.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@nomicfoundation/slang": minor
---

add a `CompilationBuilder` API to incrementally load and resolve source files and their imports.
1 change: 1 addition & 0 deletions crates/codegen/runtime/cargo/crate/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ description = "Cargo runtime copied over by codegen"
default = []
__experimental_bindings_api = ["dep:metaslang_bindings"]
__private_ariadne_errors = ["dep:ariadne"]
__private_compilation_api = []
__private_testing_utils = []

[build-dependencies]
Expand Down
12 changes: 12 additions & 0 deletions crates/codegen/runtime/cargo/crate/src/extensions/bindings/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
use semver::Version;

use crate::bindings::BindingGraph;
use crate::parser::ParserInitializationError;

#[allow(clippy::needless_pass_by_value)]
pub fn add_built_ins(
_binding_graph: &mut BindingGraph,
_version: Version,
) -> Result<(), ParserInitializationError> {
unreachable!("Built-ins are Solidity-specific")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
use crate::cst::Cursor;

pub struct ImportPathsExtractor;

impl ImportPathsExtractor {
pub fn new() -> Self {
Self
}

#[allow(clippy::unused_self)]
#[allow(clippy::needless_pass_by_value)]
pub fn extract(&self, _: Cursor) -> Vec<Cursor> {
unreachable!("Import paths are Solidity-specific")
}
}
8 changes: 8 additions & 0 deletions crates/codegen/runtime/cargo/crate/src/extensions/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#[cfg(all(
feature = "__experimental_bindings_api",
feature = "__private_compilation_api"
))]
pub mod compilation;

#[cfg(feature = "__experimental_bindings_api")]
pub mod bindings;
1 change: 1 addition & 0 deletions crates/codegen/runtime/cargo/crate/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// The final code (generated in output crates) is checked for dead-code anyways.
#![allow(dead_code, unused_imports)]

mod extensions;
mod runtime;

pub use runtime::*;
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
use semver::Version;

// TODO: This should be moved to the Solidity-specific 'extensions' sub-module.
#[allow(unused_variables)]
pub fn get_contents(version: &Version) -> &'static str {
pub fn get_built_ins_contents(version: &Version) -> &'static str {
{%- if not rendering_in_stubs -%}
{%- for version in model.bindings.built_ins_versions %}
{%- if not loop.first -%}
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

37 changes: 26 additions & 11 deletions crates/codegen/runtime/cargo/crate/src/runtime/bindings/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,46 @@
mod binding_rules;

#[path = "generated/built_ins.rs"]
mod built_ins;
pub mod built_ins;

use std::sync::Arc;
use std::rc::Rc;

use metaslang_bindings::{self, PathResolver};
use semver::Version;

use crate::cst::KindTypes;

pub type Bindings = metaslang_bindings::Bindings<KindTypes>;
pub type BindingGraph = metaslang_bindings::BindingGraph<KindTypes>;
pub type Definition<'a> = metaslang_bindings::Definition<'a, KindTypes>;
pub type Reference<'a> = metaslang_bindings::Reference<'a, KindTypes>;
pub type BindingLocation = metaslang_bindings::BindingLocation<KindTypes>;
pub type UserFileLocation = metaslang_bindings::UserFileLocation<KindTypes>;

pub use metaslang_bindings::{BuiltInLocation, PathResolver};

use crate::parser::ParserInitializationError;

#[derive(thiserror::Error, Debug)]
pub enum BindingGraphInitializationError {
#[error(transparent)]
ParserInitialization(#[from] ParserInitializationError),
}

pub fn create_with_resolver(
version: Version,
resolver: Arc<dyn PathResolver + Sync + Send>,
) -> Bindings {
Bindings::create(version, binding_rules::BINDING_RULES_SOURCE, resolver)
resolver: Rc<dyn PathResolver<KindTypes>>,
) -> Result<BindingGraph, ParserInitializationError> {
let mut binding_graph = BindingGraph::create(
version.clone(),
binding_rules::BINDING_RULES_SOURCE,
resolver,
);

crate::extensions::bindings::add_built_ins(&mut binding_graph, version)?;

Ok(binding_graph)
}

#[cfg(feature = "__private_testing_utils")]
pub fn get_binding_rules() -> &'static str {
binding_rules::BINDING_RULES_SOURCE
}

pub fn get_built_ins(version: &semver::Version) -> &'static str {
built_ins::get_contents(version)
}
45 changes: 45 additions & 0 deletions crates/codegen/runtime/cargo/crate/src/runtime/compilation/file.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
use std::collections::BTreeMap;

use metaslang_cst::text_index::TextIndex;

use crate::cst::{Cursor, Node};

#[derive(Clone)]
pub struct File {
id: String,
tree: Node,

resolved_imports: BTreeMap<usize, String>,
}

impl File {
pub(super) fn new(id: String, tree: Node) -> Self {
Self {
id,
tree,

resolved_imports: BTreeMap::new(),
}
}

pub fn id(&self) -> &str {
&self.id
}

pub fn tree(&self) -> &Node {
&self.tree
}

pub fn create_tree_cursor(&self) -> Cursor {
self.tree.clone().cursor_with_offset(TextIndex::ZERO)
}

pub(super) fn resolve_import(&mut self, import_path: &Cursor, destination_file_id: String) {
self.resolved_imports
.insert(import_path.node().id(), destination_file_id);
}

pub(super) fn resolved_import(&self, import_path: &Cursor) -> Option<&String> {
self.resolved_imports.get(&import_path.node().id())
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
use std::collections::BTreeMap;
use std::rc::Rc;

use semver::Version;

use crate::compilation::{CompilationUnit, File};
use crate::cst::Cursor;
use crate::extensions::compilation::ImportPathsExtractor;
use crate::parser::{Parser, ParserInitializationError};

pub struct InternalCompilationBuilder {
parser: Parser,
imports: ImportPathsExtractor,
files: BTreeMap<String, File>,
}

#[derive(thiserror::Error, Debug)]
pub enum CompilationInitializationError {
#[error(transparent)]
ParserInitialization(#[from] ParserInitializationError),
}

impl InternalCompilationBuilder {
pub fn create(language_version: Version) -> Result<Self, CompilationInitializationError> {
let parser = Parser::create(language_version)?;

Ok(Self {
parser,
imports: ImportPathsExtractor::new(),
files: BTreeMap::new(),
})
}

pub fn add_file(&mut self, id: String, contents: &str) -> AddFileResponse {
if self.files.contains_key(&id) {
// Already added. No need to process it again:
return AddFileResponse {
import_paths: vec![],
};
}

let parse_output = self.parser.parse(Parser::ROOT_KIND, contents);

let import_paths = self.imports.extract(parse_output.create_tree_cursor());

let file = File::new(id.clone(), parse_output.tree().clone());
self.files.insert(id, file);

AddFileResponse { import_paths }
}

pub fn resolve_import(
&mut self,
source_file_id: &str,
import_path: &Cursor,
destination_file_id: String,
) -> Result<(), ResolveImportError> {
self.files
.get_mut(source_file_id)
.ok_or_else(|| ResolveImportError::SourceFileNotFound(source_file_id.to_owned()))?
.resolve_import(import_path, destination_file_id);

Ok(())
}

pub fn build(&self) -> CompilationUnit {
let language_version = self.parser.language_version().to_owned();

let files = self
.files
.iter()
.map(|(id, file)| (id.to_owned(), Rc::new(file.to_owned())))
.collect();

CompilationUnit::new(language_version, files)
}
}

pub struct AddFileResponse {
pub import_paths: Vec<Cursor>,
}

#[derive(thiserror::Error, Debug)]
pub enum ResolveImportError {
#[error(
"Source file not found: '{0}'. Make sure to add it first, before resolving its imports."
)]
SourceFileNotFound(String),
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
mod file;
mod internal_builder;
mod unit;

pub use file::File;
pub use internal_builder::{AddFileResponse, InternalCompilationBuilder};
pub use unit::CompilationUnit;
69 changes: 69 additions & 0 deletions crates/codegen/runtime/cargo/crate/src/runtime/compilation/unit.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
use std::cell::OnceCell;
use std::collections::BTreeMap;
use std::rc::Rc;

use semver::Version;

use crate::bindings::{
create_with_resolver, BindingGraph, BindingGraphInitializationError, PathResolver,
};
use crate::compilation::File;
use crate::cst::{Cursor, KindTypes};

pub struct CompilationUnit {
language_version: Version,
files: BTreeMap<String, Rc<File>>,
binding_graph: OnceCell<Result<Rc<BindingGraph>, BindingGraphInitializationError>>,
}

impl CompilationUnit {
pub(super) fn new(language_version: Version, files: BTreeMap<String, Rc<File>>) -> Self {
Self {
language_version,
files,
binding_graph: OnceCell::new(),
}
}

pub fn language_version(&self) -> &Version {
&self.language_version
}

pub fn files(&self) -> Vec<Rc<File>> {
self.files.values().cloned().collect()
}

pub fn file(&self, id: &str) -> Option<Rc<File>> {
self.files.get(id).cloned()
}

pub fn binding_graph(&self) -> &Result<Rc<BindingGraph>, BindingGraphInitializationError> {
self.binding_graph.get_or_init(|| {
let resolver = Resolver {
files: self.files.clone(),
};

let mut binding_graph =
create_with_resolver(self.language_version.clone(), Rc::new(resolver))?;

for (id, file) in &self.files {
binding_graph.add_user_file(id, file.create_tree_cursor());
}

Ok(Rc::new(binding_graph))
})
}
}

struct Resolver {
files: BTreeMap<String, Rc<File>>,
}

impl PathResolver<KindTypes> for Resolver {
fn resolve_path(&self, context_path: &str, path_to_resolve: &Cursor) -> Option<String> {
self.files
.get(context_path)?
.resolved_import(path_to_resolve)
.cloned()
}
}
6 changes: 6 additions & 0 deletions crates/codegen/runtime/cargo/crate/src/runtime/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
#[cfg(feature = "__experimental_bindings_api")]
pub mod bindings;
#[cfg(all(
feature = "__experimental_bindings_api",
feature = "__private_compilation_api"
))]
pub mod compilation;
pub mod cst;
pub mod diagnostic;
pub mod parser;
pub mod utils;
Loading

0 comments on commit 7a25d63

Please sign in to comment.