Skip to content

Commit

Permalink
add Compilation API
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 committed Dec 13, 2024
1 parent 5cec87c commit 950d218
Show file tree
Hide file tree
Showing 170 changed files with 5,244 additions and 601 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
15 changes: 9 additions & 6 deletions crates/codegen/runtime/cargo/crate/src/runtime/bindings/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,25 @@ mod binding_rules;
#[path = "generated/built_ins.rs"]
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};

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>>,
) -> BindingGraph {
BindingGraph::create(version, binding_rules::BINDING_RULES_SOURCE, resolver)
}

#[cfg(feature = "__private_testing_utils")]
Expand Down
47 changes: 47 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,47 @@
// This file is generated automatically by infrastructure scripts. Please don't edit by hand.

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,213 @@
// This file is generated automatically by infrastructure scripts. Please don't edit by hand.

use std::collections::BTreeMap;
use std::rc::Rc;

use semver::Version;

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

pub struct InternalCompilationBuilder {
parser: Parser,
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 = match Parser::create(language_version) {
Ok(parser) => parser,
Err(error) => {
return Err(error.into());
}
};

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

pub fn add_file(
&mut self,
id: String,
contents: &str,
) -> Result<AddFileResponse, AddFileError> {
let parse_output = self.parser.parse(Parser::ROOT_KIND, contents);

let import_paths = extract_import_paths(parse_output.create_tree_cursor())
.map_err(AddFileError::Internal)?;

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

Ok(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 AddFileError {
#[error("Internal Error: {0}")]
Internal(String),
}

#[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),
}

fn extract_import_paths(cursor: Cursor) -> Result<Vec<Cursor>, String> {
let mut import_paths = Vec::new();

for query_match in cursor.query(vec![
Query::parse(
"[PathImport
path: [StringLiteral
@variant ([DoubleQuotedStringLiteral] | [SingleQuotedStringLiteral])
]
]",
)
.map_err(|e| e.to_string())?,
Query::parse(
"[NamedImport
path: [StringLiteral
@variant ([DoubleQuotedStringLiteral] | [SingleQuotedStringLiteral])
]
]",
)
.map_err(|e| e.to_string())?,
Query::parse(
"[ImportDeconstruction
path: [StringLiteral
@variant ([DoubleQuotedStringLiteral] | [SingleQuotedStringLiteral])
]
]",
)
.map_err(|e| e.to_string())?,
]) {
for (match_name, _, cursors) in query_match.captures() {
if match_name != "variant" {
return Err(format!("Unexpected match name: {match_name}"));
}

import_paths.extend(cursors);
}
}

Ok(import_paths)
}

#[cfg(test)]
mod tests {
use super::*;
use crate::utils::LanguageFacts;

#[test]
pub fn path_import() {
run(
r#"
import "foo-double";
import "bar-double" as bar;
import 'foo-single';
import 'bar-single' as bar;
"#,
&[
"\"foo-double\"",
"\"bar-double\"",
"\'foo-single\'",
"\'bar-single\'",
],
);
}

#[test]
pub fn named_import() {
run(
r#"
import * as foo from "foo-double";
import * as foo from 'foo-single';
"#,
&["\"foo-double\"", "\'foo-single\'"],
);
}

#[test]
pub fn import_deconstruction() {
run(
r#"
import {a, b} from "foo-double";
import {a, b} from 'foo-single';
"#,
&["\"foo-double\"", "\'foo-single\'"],
);
}

fn run(source: &str, expected: &[&str]) {
match LanguageFacts::NAME {
"Solidity" => {
// Run the test only for Solidity:
}
"CodegenRuntime" | "Testlang" => {
return;
}
other => {
panic!("Unexpected language name: {other}");
}
};

let parser = Parser::create(Version::new(0, 8, 0)).unwrap();
let parse_output = parser.parse(Parser::ROOT_KIND, source);

let actual: Vec<_> = extract_import_paths(parse_output.create_tree_cursor())
.unwrap()
.into_iter()
.map(|cursor| cursor.node().unparse())
.collect();

assert_eq!(actual, expected.to_vec());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// This file is generated automatically by infrastructure scripts. Please don't edit by hand.

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 @@
// This file is generated automatically by infrastructure scripts. Please don't edit by hand.

use std::cell::OnceCell;
use std::collections::BTreeMap;
use std::rc::Rc;

use semver::Version;

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

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

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) -> &Rc<BindingGraph> {
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());
}

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()
}
}
Loading

0 comments on commit 950d218

Please sign in to comment.