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..821ff0e1e --- /dev/null +++ b/scarb/src/compiler/plugin/proc_macro/ffi.rs @@ -0,0 +1,36 @@ +use crate::core::Package; +use cairo_lang_defs::patcher::PatchBuilder; +use cairo_lang_syntax::node::db::SyntaxGroup; +use cairo_lang_syntax::node::{ast, TypedSyntaxNode}; +use scarb_macro_interface::plugin::{ProcMacroResult, TokenStream}; +use std::fmt::Debug; + +pub trait FromItemAst { + fn from_item_ast(db: &dyn SyntaxGroup, item_ast: ast::ModuleItem) -> Self; +} + +impl FromItemAst for TokenStream { + fn from_item_ast(db: &dyn SyntaxGroup, item_ast: ast::ModuleItem) -> Self { + let mut builder = PatchBuilder::new(db); + builder.add_node(item_ast.as_syntax_node()); + let cairo = builder.code.clone(); + Self::from(cairo) + } +} + +#[derive(Debug, Clone)] +pub struct ProcMacroInstance {} + +impl ProcMacroInstance { + pub fn new(_package: Package) -> Self { + // Load shared library + // TODO(maciektr): Implement + Self {} + } + + pub(crate) fn generate_code(&self, _token_stream: TokenStream) -> ProcMacroResult { + // Apply expansion to token stream. + // TODO(maciektr): Implement + ProcMacroResult::Leave + } +} diff --git a/scarb/src/compiler/plugin/proc_macro/host.rs b/scarb/src/compiler/plugin/proc_macro/host.rs index e473317b0..725a3e8df 100644 --- a/scarb/src/compiler/plugin/proc_macro/host.rs +++ b/scarb/src/compiler/plugin/proc_macro/host.rs @@ -1,42 +1,144 @@ +use crate::compiler::plugin::proc_macro::{FromItemAst, ProcMacroInstance}; use crate::compiler::plugin::{CairoPlugin, CairoPluginInstance}; use crate::core::{Package, PackageId, PackageName, SourceId}; use crate::internal::to_version::ToVersion; use anyhow::Result; -use cairo_lang_defs::plugin::{MacroPlugin, MacroPluginMetadata, PluginResult}; +use cairo_lang_defs::plugin::{ + MacroPlugin, MacroPluginMetadata, PluginGeneratedFile, PluginResult, +}; use cairo_lang_semantic::plugin::PluginSuite; -use cairo_lang_syntax::node::ast::ModuleItem; +use cairo_lang_syntax::attribute::structured::AttributeListStructurize; +use cairo_lang_syntax::node::ast; use cairo_lang_syntax::node::db::SyntaxGroup; +use itertools::Itertools; +use scarb_macro_interface::plugin::{ProcMacroResult, TokenStream}; use smol_str::SmolStr; use std::collections::HashMap; use std::sync::Arc; use typed_builder::TypedBuilder; -#[derive(Debug, Clone)] -pub struct ProcMacroInstance {} - -impl ProcMacroInstance { - pub fn new(_package: Package) -> Self { - // Load shared library - // TODO(maciektr): Implement - Self {} - } -} - #[derive(Debug, TypedBuilder)] pub struct ProcMacroHost { macros: HashMap>, } +pub type ProcMacroId = SmolStr; + +#[derive(Debug)] +#[allow(dead_code)] +pub enum ProcMacroKind { + /// `proc_macro_name!(...)` + MacroCall, + /// `#[proc_macro_name]` + Attribute, + /// `#[derive(...)]` + Derive, +} + +#[derive(Debug)] +pub struct ProcMacroInput { + pub id: ProcMacroId, + pub kind: ProcMacroKind, +} + +impl ProcMacroHost { + /// Handle `proc_macro_name!` expression. + fn handle_macro( + &self, + _db: &dyn SyntaxGroup, + _item_ast: ast::ModuleItem, + ) -> Vec { + // Todo(maciektr): Implement. + Vec::new() + } + + /// Handle `#[proc_macro_name]` attribute. + fn handle_attribute( + &self, + db: &dyn SyntaxGroup, + item_ast: ast::ModuleItem, + ) -> Vec { + let attrs = match item_ast { + ast::ModuleItem::Struct(struct_ast) => Some(struct_ast.attributes(db)), + ast::ModuleItem::Enum(enum_ast) => Some(enum_ast.attributes(db)), + ast::ModuleItem::ExternType(extern_type_ast) => Some(extern_type_ast.attributes(db)), + ast::ModuleItem::ExternFunction(extern_func_ast) => { + Some(extern_func_ast.attributes(db)) + } + ast::ModuleItem::FreeFunction(free_func_ast) => Some(free_func_ast.attributes(db)), + _ => None, + }; + + attrs + .map(|attrs| attrs.structurize(db)) + .unwrap_or_default() + .iter() + .filter(|attr| self.macros.contains_key(&attr.id)) + .map(|attr| ProcMacroInput { + id: attr.id.clone(), + kind: ProcMacroKind::Attribute, + }) + .collect_vec() + } + + /// Handle `#[derive(...)]` attribute. + fn handle_derive( + &self, + _db: &dyn SyntaxGroup, + _item_ast: ast::ModuleItem, + ) -> Vec { + // Todo(maciektr): Implement. + Vec::new() + } +} + impl MacroPlugin for ProcMacroHost { fn generate_code( &self, - _db: &dyn SyntaxGroup, - _item_ast: ModuleItem, + db: &dyn SyntaxGroup, + item_ast: ast::ModuleItem, _metadata: &MacroPluginMetadata<'_>, ) -> PluginResult { // Apply expansion to `item_ast` where needed. - // TODO(maciektr): Implement - PluginResult::default() + let expansions = self + .handle_macro(db, item_ast.clone()) + .into_iter() + .chain(self.handle_attribute(db, item_ast.clone())) + .chain(self.handle_derive(db, item_ast.clone())); + + let mut token_stream = TokenStream::from_item_ast(db, item_ast); + let mut modified = false; + for input in expansions { + let instance = self.macros.get(&input.id).unwrap(); + match instance.generate_code(token_stream.clone()) { + ProcMacroResult::Replace(new_token_stream) => { + token_stream = new_token_stream; + modified = true; + } + ProcMacroResult::Remove => { + return PluginResult { + code: None, + diagnostics: Vec::new(), + remove_original_item: true, + } + } + ProcMacroResult::Leave => {} + }; + } + if modified { + PluginResult { + code: Some(PluginGeneratedFile { + name: "proc_macro".into(), + content: token_stream.to_string(), + code_mappings: Default::default(), + aux_data: Default::default(), + }), + diagnostics: Vec::new(), + remove_original_item: true, + } + } else { + PluginResult::default() + } } fn declared_attributes(&self) -> Vec { diff --git a/scarb/src/compiler/plugin/proc_macro/mod.rs b/scarb/src/compiler/plugin/proc_macro/mod.rs index b4662903f..7e563afe8 100644 --- a/scarb/src/compiler/plugin/proc_macro/mod.rs +++ b/scarb/src/compiler/plugin/proc_macro/mod.rs @@ -1,3 +1,5 @@ +mod ffi; mod host; +pub use ffi::*; pub use host::*;