diff --git a/Cargo.lock b/Cargo.lock index 80155b364..f9d3a2216 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -730,7 +730,6 @@ version = "0.0.1" dependencies = [ "anyhow", "cairo-lang-macro-attributes", - "libc", "serde", "serde_json", ] @@ -4476,6 +4475,7 @@ dependencies = [ "cairo-lang-sierra-to-casm", "cairo-lang-starknet", "cairo-lang-starknet-classes", + "cairo-lang-syntax", "cairo-lang-test-plugin", "cairo-lang-utils", "camino", diff --git a/plugins/cairo-lang-macro/Cargo.toml b/plugins/cairo-lang-macro/Cargo.toml index 176eb6d1e..1cb19eb9d 100644 --- a/plugins/cairo-lang-macro/Cargo.toml +++ b/plugins/cairo-lang-macro/Cargo.toml @@ -14,7 +14,6 @@ repository.workspace = true [dependencies] anyhow.workspace = true -libc.workspace = true cairo-lang-macro-attributes = { path = "../cairo-lang-macro-attributes" } serde.workspace = true serde_json.workspace = true diff --git a/scarb/Cargo.toml b/scarb/Cargo.toml index 4f73f8182..e6dd02936 100644 --- a/scarb/Cargo.toml +++ b/scarb/Cargo.toml @@ -26,6 +26,7 @@ cairo-lang-sierra-to-casm.workspace = true cairo-lang-sierra.workspace = true cairo-lang-starknet-classes.workspace = true cairo-lang-starknet.workspace = true +cairo-lang-syntax.workspace = true cairo-lang-test-plugin.workspace = true cairo-lang-utils.workspace = true camino.workspace = true diff --git a/scarb/src/compiler/db.rs b/scarb/src/compiler/db.rs index 2a61e2fe3..7eeba0611 100644 --- a/scarb/src/compiler/db.rs +++ b/scarb/src/compiler/db.rs @@ -1,5 +1,5 @@ use anyhow::{anyhow, Result}; -use cairo_lang_compiler::db::RootDatabase; +use cairo_lang_compiler::db::{RootDatabase, RootDatabaseBuilder}; use cairo_lang_compiler::project::{AllCratesConfig, ProjectConfig, ProjectConfigContent}; use cairo_lang_defs::db::DefsGroup; use cairo_lang_defs::ids::ModuleId; @@ -11,6 +11,7 @@ use smol_str::SmolStr; use std::sync::Arc; use tracing::trace; +use crate::compiler::plugin::proc_macro::ProcMacroHost; use crate::compiler::{CompilationUnit, CompilationUnitComponent}; use crate::core::Workspace; use crate::DEFAULT_MODULE_MAIN_FILE; @@ -23,19 +24,32 @@ pub(crate) fn build_scarb_root_database( let mut b = RootDatabase::builder(); b.with_project_config(build_project_config(unit)?); b.with_cfg(unit.cfg_set.clone()); - - for plugin_info in &unit.cairo_plugins { - let package_id = plugin_info.package.id; - let plugin = ws.config().cairo_plugins().fetch(package_id)?; - let instance = plugin.instantiate()?; - b.with_plugin_suite(instance.plugin_suite()); - } - + load_plugins(unit, ws, &mut b)?; let mut db = b.build()?; inject_virtual_wrapper_lib(&mut db, unit)?; Ok(db) } +fn load_plugins( + unit: &CompilationUnit, + ws: &Workspace<'_>, + builder: &mut RootDatabaseBuilder, +) -> Result<()> { + let mut proc_macros = ProcMacroHost::default(); + for plugin_info in &unit.cairo_plugins { + if plugin_info.builtin { + let package_id = plugin_info.package.id; + let plugin = ws.config().cairo_plugins().fetch(package_id)?; + let instance = plugin.instantiate()?; + builder.with_plugin_suite(instance.plugin_suite()); + } else { + proc_macros.register(plugin_info.package.clone())?; + } + } + builder.with_plugin_suite(proc_macros.into_plugin_suite()); + Ok(()) +} + /// Generates a wrapper lib file for appropriate compilation units. /// /// This approach allows compiling crates that do not define `lib.cairo` file. diff --git a/scarb/src/compiler/plugin/mod.rs b/scarb/src/compiler/plugin/mod.rs index 3eea2e11e..6f7fe3c3a 100644 --- a/scarb/src/compiler/plugin/mod.rs +++ b/scarb/src/compiler/plugin/mod.rs @@ -11,6 +11,7 @@ use crate::core::PackageId; use self::builtin::{BuiltinStarkNetPlugin, BuiltinTestPlugin}; pub mod builtin; +pub mod proc_macro; pub trait CairoPlugin: Sync { fn id(&self) -> PackageId; diff --git a/scarb/src/compiler/plugin/proc_macro/ffi.rs b/scarb/src/compiler/plugin/proc_macro/ffi.rs new file mode 100644 index 000000000..f8b751a67 --- /dev/null +++ b/scarb/src/compiler/plugin/proc_macro/ffi.rs @@ -0,0 +1,25 @@ +use crate::core::{Package, PackageId}; +use anyhow::Result; + +/// Representation of a single procedural macro. +/// +/// This struct is a wrapper around a shared library containing the procedural macro implementation. +/// It is responsible for loading the shared library and providing a safe interface for code expansion. +#[derive(Debug, Clone)] +pub struct ProcMacroInstance { + package_id: PackageId, +} + +impl ProcMacroInstance { + pub fn try_new(package: Package) -> Result { + // Load shared library + // TODO(maciektr): Implement + Ok(Self { + package_id: package.id, + }) + } + + pub fn declared_attributes(&self) -> Vec { + vec![self.package_id.name.to_string()] + } +} diff --git a/scarb/src/compiler/plugin/proc_macro/host.rs b/scarb/src/compiler/plugin/proc_macro/host.rs new file mode 100644 index 000000000..bf54dc895 --- /dev/null +++ b/scarb/src/compiler/plugin/proc_macro/host.rs @@ -0,0 +1,69 @@ +use crate::compiler::plugin::proc_macro::ProcMacroInstance; +use crate::core::Package; +use anyhow::Result; +use cairo_lang_defs::plugin::{MacroPlugin, MacroPluginMetadata, PluginResult}; +use cairo_lang_semantic::plugin::PluginSuite; +use cairo_lang_syntax::node::ast::ModuleItem; +use cairo_lang_syntax::node::db::SyntaxGroup; +use std::sync::Arc; + +/// A Cairo compiler plugin controlling the procedural macro execution. +/// +/// This plugin decides which macro plugins (if any) should be applied to the processed AST item. +/// It then redirects the item to the appropriate macro plugin for code expansion. +#[derive(Debug)] +pub struct ProcMacroHostPlugin { + macros: Vec>, +} + +impl ProcMacroHostPlugin { + pub fn new(macros: Vec>) -> Self { + Self { macros } + } +} + +impl MacroPlugin for ProcMacroHostPlugin { + fn generate_code( + &self, + _db: &dyn SyntaxGroup, + _item_ast: ModuleItem, + _metadata: &MacroPluginMetadata<'_>, + ) -> PluginResult { + // Apply expansion to `item_ast` where needed. + // TODO(maciektr): Implement + PluginResult::default() + } + + fn declared_attributes(&self) -> Vec { + self.macros + .iter() + .flat_map(|m| m.declared_attributes()) + .collect() + } +} + +/// A Scarb wrapper around the `ProcMacroHost` compiler plugin. +/// +/// This struct represent the compiler plugin in terms of Scarb data model. +/// It also builds a plugin suite that enables the compiler plugin. +#[derive(Default)] +pub struct ProcMacroHost { + macros: Vec>, +} + +impl ProcMacroHost { + pub fn register(&mut self, package: Package) -> Result<()> { + // Create instance + // Register instance in hash map + let instance = ProcMacroInstance::try_new(package)?; + self.macros.push(Arc::new(instance)); + Ok(()) + } + + pub fn into_plugin_suite(self) -> PluginSuite { + let macro_host = ProcMacroHostPlugin::new(self.macros); + let mut suite = PluginSuite::default(); + suite.add_plugin_ex(Arc::new(macro_host)); + suite + } +} diff --git a/scarb/src/compiler/plugin/proc_macro/mod.rs b/scarb/src/compiler/plugin/proc_macro/mod.rs new file mode 100644 index 000000000..7e563afe8 --- /dev/null +++ b/scarb/src/compiler/plugin/proc_macro/mod.rs @@ -0,0 +1,5 @@ +mod ffi; +mod host; + +pub use ffi::*; +pub use host::*;