Skip to content

Commit

Permalink
Implement inline macros expansion
Browse files Browse the repository at this point in the history
commit-id:b7466c6a
  • Loading branch information
maciektr committed Mar 28, 2024
1 parent cb354c6 commit d95fa20
Show file tree
Hide file tree
Showing 4 changed files with 218 additions and 23 deletions.
40 changes: 40 additions & 0 deletions plugins/cairo-lang-macro-attributes/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,46 @@ pub fn attribute_macro(_args: TokenStream, input: TokenStream) -> TokenStream {
TokenStream::from(expanded)
}

#[proc_macro_attribute]
pub fn inline_macro(_args: TokenStream, input: TokenStream) -> TokenStream {
let item: ItemFn = parse_macro_input!(input as ItemFn);
let original_item_name = item.sig.ident.to_string();
let item = hide_name(item);
let item_name = &item.sig.ident;

let callback_link = format!(
"EXPANSIONS_DESERIALIZE_{}",
item_name.to_string().to_uppercase()
);
let callback_link = syn::Ident::new(callback_link.as_str(), item.span());

// We wrap original function with `InlineProcMacroResult` to `ProcMacroResult` conversion.
// This stems from the fact, that `ProcMacroResult::Remove` does not really make sense for inline macros.
let wrapper_name = format!("{item_name}_inline_wrapper");
let wrapper_name = syn::Ident::new(wrapper_name.as_str(), item.span());

let expanded = quote! {
#item

fn #wrapper_name(token_stream: ::cairo_lang_macro::TokenStream) -> ::cairo_lang_macro::ProcMacroResult {
// Assign function pointer, to validate type.
let f: fn(::cairo_lang_macro::TokenStream) -> ::cairo_lang_macro::InlineProcMacroResult = #item_name;
f(token_stream).into()
}

#[::cairo_lang_macro::linkme::distributed_slice(::cairo_lang_macro::MACRO_DEFINITIONS_SLICE)]
#[linkme(crate = ::cairo_lang_macro::linkme)]
static #callback_link: ::cairo_lang_macro::ExpansionDefinition =
::cairo_lang_macro::ExpansionDefinition{
name: #original_item_name,
kind: ::cairo_lang_macro::ExpansionKind::Inline,
fun: #wrapper_name,
};
};

TokenStream::from(expanded)
}

/// Constructs the post-processing callback.
///
/// This callback will be called after the source code compilation (and thus after all the procedural
Expand Down
58 changes: 58 additions & 0 deletions plugins/cairo-lang-macro/src/types/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,20 @@ pub enum ProcMacroResult {
Remove { diagnostics: Vec<Diagnostic> },
}

/// Result of inline procedural macro code generation.
///
/// This enum differs from `ProcMacroResult` by not having `Remove` variant.
pub enum InlineProcMacroResult {
/// Plugin has not taken any action.
Leave { diagnostics: Vec<Diagnostic> },
/// Plugin generated [`TokenStream`] replacement.
Replace {
token_stream: TokenStream,
aux_data: Option<AuxData>,
diagnostics: Vec<Diagnostic>,
},
}

/// An abstract stream of Cairo tokens.
///
/// This is both input and part of an output of a procedural macro.
Expand Down Expand Up @@ -278,6 +292,50 @@ impl ProcMacroResult {
}
}

impl InlineProcMacroResult {
/// Create new [`InlineProcMacroResult::Leave`] variant, empty diagnostics set.
pub fn leave() -> Self {
Self::Leave {
diagnostics: Vec::new(),
}
}

/// Create new [`InlineProcMacroResult::Replace`] variant, empty diagnostics set.
pub fn replace(token_stream: TokenStream, aux_data: Option<AuxData>) -> Self {
Self::Replace {
aux_data,
token_stream,
diagnostics: Vec::new(),
}
}

/// Append diagnostics to the [`InlineProcMacroResult`] diagnostics set.
pub fn with_diagnostics(mut self, diagnostics: Diagnostics) -> Self {
match &mut self {
Self::Leave { diagnostics: d } => d.extend(diagnostics),
Self::Replace { diagnostics: d, .. } => d.extend(diagnostics),
};
self
}
}

impl From<InlineProcMacroResult> for ProcMacroResult {
fn from(result: InlineProcMacroResult) -> Self {
match result {
InlineProcMacroResult::Leave { diagnostics } => ProcMacroResult::Leave { diagnostics },
InlineProcMacroResult::Replace {
token_stream,
aux_data,
diagnostics,
} => ProcMacroResult::Replace {
token_stream,
aux_data,
diagnostics,
},
}
}
}

#[cfg(test)]
mod tests {
use crate::types::TokenStream;
Expand Down
15 changes: 10 additions & 5 deletions scarb/src/compiler/plugin/proc_macro/ffi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use cairo_lang_macro_stable::{
StableResultWrapper, StableTokenStream,
};
use cairo_lang_syntax::node::db::SyntaxGroup;
use cairo_lang_syntax::node::{ast, TypedSyntaxNode};
use cairo_lang_syntax::node::{ast, SyntaxNode, TypedSyntaxNode};
use camino::Utf8PathBuf;
use libloading::{Library, Symbol};
use std::ffi::{c_char, CStr, CString};
Expand All @@ -23,16 +23,21 @@ use libloading::os::unix::Symbol as RawSymbol;
use libloading::os::windows::Symbol as RawSymbol;
use smol_str::SmolStr;

pub trait FromItemAst {
pub trait FromSyntaxNode {
fn from_syntax_node(db: &dyn SyntaxGroup, node: SyntaxNode) -> Self;
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 {
impl FromSyntaxNode for TokenStream {
fn from_syntax_node(db: &dyn SyntaxGroup, node: SyntaxNode) -> Self {
let mut builder = PatchBuilder::new(db);
builder.add_node(item_ast.as_syntax_node());
builder.add_node(node);
Self::new(builder.code)
}

fn from_item_ast(db: &dyn SyntaxGroup, item_ast: ast::ModuleItem) -> Self {
Self::from_syntax_node(db, item_ast.as_syntax_node())
}
}

/// Representation of a single procedural macro.
Expand Down
128 changes: 110 additions & 18 deletions scarb/src/compiler/plugin/proc_macro/host.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
use crate::compiler::plugin::proc_macro::{Expansion, FromItemAst, ProcMacroInstance};
use crate::compiler::plugin::proc_macro::{Expansion, FromSyntaxNode, ProcMacroInstance};
use crate::core::{Config, Package, PackageId};
use anyhow::{ensure, Result};
use cairo_lang_defs::db::DefsGroup;
use cairo_lang_defs::plugin::PluginDiagnostic;
use cairo_lang_defs::plugin::{
DynGeneratedFileAuxData, GeneratedFileAuxData, MacroPlugin, MacroPluginMetadata,
PluginGeneratedFile, PluginResult,
};
use cairo_lang_defs::plugin::{InlineMacroExprPlugin, InlinePluginResult, PluginDiagnostic};
use cairo_lang_macro::{
AuxData, Diagnostic, ExpansionKind, ProcMacroResult, Severity, TokenStream, TokenStreamMetadata,
};
Expand Down Expand Up @@ -129,12 +129,6 @@ impl ProcMacroHostPlugin {
Ok(Self { macros })
}

/// Handle `proc_macro_name!` expression.
fn handle_macro(&self, _db: &dyn SyntaxGroup, _item_ast: ast::ModuleItem) -> Vec<ProcMacroId> {
// Todo(maciektr): Implement.
Vec::new()
}

/// Handle `#[proc_macro_name]` attribute.
fn handle_attribute(
&self,
Expand Down Expand Up @@ -176,9 +170,24 @@ impl ProcMacroHostPlugin {
.map(|package_id| ProcMacroId::new(package_id, expansion.clone()))
}

pub fn build_plugin_suite(macr_host: Arc<Self>) -> PluginSuite {
pub fn build_plugin_suite(macro_host: Arc<Self>) -> PluginSuite {
let mut suite = PluginSuite::default();
suite.add_plugin_ex(macr_host);
// Register inline macro plugins.
for proc_macro in &macro_host.macros {
let expansions = proc_macro
.get_expansions()
.iter()
.filter(|exp| matches!(exp.kind, ExpansionKind::Inline));
for expansion in expansions {
let plugin = Arc::new(ProcMacroInlinePlugin::new(
proc_macro.clone(),
expansion.clone(),
));
suite.add_inline_macro_plugin_ex(expansion.name.as_str(), plugin);
}
}
// Register procedural macro host plugin.
suite.add_plugin_ex(macro_host);
suite
}

Expand Down Expand Up @@ -219,6 +228,13 @@ impl ProcMacroHostPlugin {
}
Ok(())
}

pub fn instance(&self, package_id: PackageId) -> &ProcMacroInstance {
self.macros
.iter()
.find(|m| m.package_id() == package_id)
.expect("procedural macro must be registered in proc macro host")
}
}

impl MacroPlugin for ProcMacroHostPlugin {
Expand All @@ -230,9 +246,8 @@ impl MacroPlugin for ProcMacroHostPlugin {
) -> PluginResult {
// Apply expansion to `item_ast` where needed.
let expansions = self
.handle_macro(db, item_ast.clone())
.handle_attribute(db, item_ast.clone())
.into_iter()
.chain(self.handle_attribute(db, item_ast.clone()))
.chain(self.handle_derive(db, item_ast.clone()));
let stable_ptr = item_ast.clone().stable_ptr().untyped();
let file_path = stable_ptr.file_id(db).full_path(db.upcast());
Expand All @@ -244,12 +259,10 @@ impl MacroPlugin for ProcMacroHostPlugin {
let mut modified = false;
let mut all_diagnostics: Vec<Diagnostic> = Vec::new();
for input in expansions {
let instance = self
.macros
.iter()
.find(|m| m.package_id() == input.package_id)
.expect("procedural macro must be registered in proc macro host");
match instance.generate_code(input.expansion.name.clone(), token_stream.clone()) {
match self
.instance(input.package_id)
.generate_code(input.expansion.name.clone(), token_stream.clone())
{
ProcMacroResult::Replace {
token_stream: new_token_stream,
aux_data: new_aux_data,
Expand Down Expand Up @@ -310,6 +323,85 @@ impl MacroPlugin for ProcMacroHostPlugin {
}
}

/// A Cairo compiler inline macro plugin controlling the inline procedural macro execution.
///
/// This plugin represents a single expansion capable of handling inline procedural macros.
/// The plugin triggers code expansion in a corresponding procedural macro instance.
#[derive(Debug)]
pub struct ProcMacroInlinePlugin {
instance: Arc<ProcMacroInstance>,
expansion: Expansion,
}

impl ProcMacroInlinePlugin {
pub fn new(instance: Arc<ProcMacroInstance>, expansion: Expansion) -> Self {
assert!(instance.get_expansions().contains(&expansion));
Self {
instance,
expansion,
}
}

pub fn name(&self) -> &str {
self.expansion.name.as_str()
}

fn instance(&self) -> &ProcMacroInstance {
&self.instance
}
}

impl InlineMacroExprPlugin for ProcMacroInlinePlugin {
fn generate_code(
&self,
db: &dyn SyntaxGroup,
syntax: &ast::ExprInlineMacro,
) -> InlinePluginResult {
let stable_ptr = syntax.clone().stable_ptr().untyped();

let token_stream = TokenStream::from_syntax_node(db, syntax.as_syntax_node());
match self
.instance()
.generate_code(self.expansion.name.clone(), token_stream)
{
ProcMacroResult::Replace {
token_stream,
aux_data,
diagnostics,
} => {
let aux_data = aux_data.map(|aux_data| {
let aux_data = ProcMacroAuxData::new(
aux_data.into(),
ProcMacroId::new(self.instance.package_id(), self.expansion.clone()),
);
let mut emitted = EmittedAuxData::default();
emitted.push(aux_data);
DynGeneratedFileAuxData::new(emitted)
});

InlinePluginResult {
code: Some(PluginGeneratedFile {
name: "inline_proc_macro".into(),
content: token_stream.to_string(),
code_mappings: Default::default(),
aux_data,
}),
diagnostics: into_cairo_diagnostics(diagnostics, stable_ptr),
}
}
ProcMacroResult::Remove { diagnostics } => InlinePluginResult {
code: None,
diagnostics: into_cairo_diagnostics(diagnostics, stable_ptr),
},
ProcMacroResult::Leave { .. } => {
// Safe to panic, as all inline macros should originally return `InlineProcMacroResult`.
// Which is validated inside the inline macro helper attribute.
panic!("inline macro cannot return `Leave` result");
}
}
}
}

fn into_cairo_diagnostics(
diagnostics: Vec<Diagnostic>,
stable_ptr: SyntaxStablePtrId,
Expand Down

0 comments on commit d95fa20

Please sign in to comment.