-
Notifications
You must be signed in to change notification settings - Fork 20
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
add Compilation
API
#1194
add Compilation
API
#1194
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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()`. |
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. |
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. |
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") | ||
} | ||
} |
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; |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
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); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we check if the file was already added? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sure. We do that check in TS, but wouldn't hurt to add it here as well. |
||
|
||
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; |
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() | ||
} | ||
} |
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; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this be called
CompilationBuilder
? I assume theInternal
prefix is because it's only used for the Typescript interface, but looks like a useful component for Rust users as well.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
After reading the whole PR I got to the actual
CompilationBuilder
. I still find theInternal
prefix a bit confusing here, but it's more a nit than anything else.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was thinking that we would then expose a similar callback-based API in Rust as well using a new
CompilationBuilder
that would wrap theInternalCompilationBuilder
here. WDYT?I'm happy to rename it as well if you have a better idea.