cairo-lang-macro 0.2.2

Cairo procedural macro interface primitives.
Documentation
//! <br>
//!
//! **A library for writing Cairo procedural macros in Rust.**
//! <br>
//!
//! # Cairo procedural macro
//!
//! A Cairo procedural macro is a dynamic library that can be loaded by
//! [Scarb](https://github.com/software-mansion/scarb) package manager during the project build.
//! The goal of procedural macros is to provide dynamic code generation capabilities to Cairo
//! programmers.
//!
//! The code generation should be implemented as a Rust function, that takes [`TokenStream`] as
//! input and returns [`ProcMacroResult`] as output.
//! The function implementing the macro should be wrapped with [`attribute_macro`].
//!

pub use cairo_lang_macro_attributes::*;
pub use cairo_lang_quote::*;

#[doc(hidden)]
pub use linkme;

use std::cell::RefCell;

use cairo_lang_macro_stable::ffi::StableSlice;
use cairo_lang_macro_stable::{
    StableExpansionsList, StablePostProcessContext, StableProcMacroResult, StableTextSpan,
};
use std::ffi::{CStr, CString, c_char};
use std::num::NonZeroU8;
use std::ops::Deref;

mod types;
pub use types::*;

#[no_mangle]
pub static CAIRO_LANG_MACRO_API_VERSION: NonZeroU8 = unsafe { NonZeroU8::new_unchecked(2) };

// A thread-local allocation context for allocating tokens on proc macro side.
thread_local!(static CONTEXT: RefCell<AllocationContext> =  RefCell::default() );

thread_local!(static CALL_SITE: RefCell<(u32, u32)> = RefCell::default());

#[doc(hidden)]
#[derive(Clone)]
pub struct ExpansionDefinition {
    pub name: &'static str,
    pub doc: &'static str,
    pub kind: ExpansionKind,
    pub fun: ExpansionFunc,
}

#[derive(Clone)]
pub enum ExpansionFunc {
    Attr(fn(TokenStream, TokenStream) -> ProcMacroResult),
    Other(fn(TokenStream) -> ProcMacroResult),
}

/// Distributed slice for storing procedural macro code expansion capabilities.
///
/// Each element denotes name of the macro, and the expand function pointer.
#[doc(hidden)]
#[linkme::distributed_slice]
pub static MACRO_DEFINITIONS_SLICE: [ExpansionDefinition];

/// This function discovers expansion capabilities defined by the procedural macro.
///
/// This function needs to be accessible through the FFI interface,
/// of the dynamic library re-exporting it.
///
/// # Safety
#[doc(hidden)]
#[no_mangle]
pub unsafe extern "C" fn list_expansions_v2() -> StableExpansionsList {
    let list = MACRO_DEFINITIONS_SLICE
        .iter()
        .map(|m| m.clone().into_stable())
        .collect();
    StableSlice::new(list)
}

/// Free the memory allocated for the [`StableProcMacroResult`].
///
/// This function needs to be accessible through the FFI interface,
/// of the dynamic library re-exporting it.
///
/// # Safety
#[doc(hidden)]
#[no_mangle]
pub unsafe extern "C" fn free_expansions_list_v2(list: StableExpansionsList) {
    let v = list.into_owned();
    v.into_iter().for_each(|v| {
        ExpansionDefinition::free_owned(v);
    });
}

/// The code expansion callback.
///
/// This function needs to be accessible through the FFI interface,
/// of the dynamic library re-exporting it.
///
/// The function will be called for each code expansion by the procedural macro.
///
/// # Safety
#[doc(hidden)]
#[no_mangle]
pub unsafe extern "C" fn expand_v2(
    item_name: *const c_char,
    call_site: StableTextSpan,
    stable_attr: cairo_lang_macro_stable::StableTokenStream,
    stable_token_stream: cairo_lang_macro_stable::StableTokenStream,
) -> cairo_lang_macro_stable::StableResultWrapper {
    CONTEXT.with(|ctx_cell| {
        // Read size hint from stable token stream. This will be used to create a sufficiently
        // large bump allocation buffer.
        let size_hint: usize = stable_token_stream.size_hint + stable_attr.size_hint;
        // Replace the allocation context with a new one.
        // If there is no interned string guards, the old context will be de-allocated.
        ctx_cell.replace(AllocationContext::with_capacity(size_hint));
        let ctx_borrow = ctx_cell.borrow();
        let ctx: &AllocationContext = ctx_borrow.deref();
        // Set the call site for the current expand call.
        CALL_SITE.replace((call_site.start, call_site.end));
        // Copy the stable token stream into current context.
        let token_stream = TokenStream::from_stable_in(&stable_token_stream, ctx);
        let attr_token_stream = TokenStream::from_stable_in(&stable_attr, ctx);
        let item_name = CStr::from_ptr(item_name)
            .to_str()
            .expect("item name must be a valid string");
        let fun = MACRO_DEFINITIONS_SLICE
            .iter()
            .find_map(|m| {
                if m.name == item_name {
                    Some(m.fun.clone())
                } else {
                    None
                }
            })
            .expect("procedural macro not found");
        let result = match fun {
            ExpansionFunc::Attr(fun) => fun(attr_token_stream, token_stream),
            ExpansionFunc::Other(fun) => fun(token_stream),
        };
        let result: StableProcMacroResult = result.into_stable();
        cairo_lang_macro_stable::StableResultWrapper {
            input: stable_token_stream,
            input_attr: stable_attr,
            output: result,
        }
    })
}

/// Free the memory allocated for the [`StableProcMacroResult`].
///
/// This function needs to be accessible through the FFI interface,
/// of the dynamic library re-exporting it.
/// The name of this function will not be mangled by the Rust compiler (through the `no_mangle` attribute).
/// This means that the name will not be extended with neither additional prefixes nor suffixes
/// by the Rust compiler and the corresponding symbol will be available by the name of the function as id.
///
/// # Safety
#[doc(hidden)]
#[no_mangle]
pub unsafe extern "C" fn free_result_v2(result: StableProcMacroResult) {
    ProcMacroResult::free_owned_stable(result);
}

/// Distributed slice for storing auxiliary data collection callback pointers.
#[doc(hidden)]
#[linkme::distributed_slice]
pub static CALLBACKS: [Callback];

#[doc(hidden)]
#[derive(Clone)]
#[non_exhaustive]
pub enum Callback {
    PostProcess(fn(PostProcessContext)),
    Fingerprint(fn() -> u64),
}

/// The auxiliary data collection callback.
///
/// This function needs to be accessible through the FFI interface,
/// of the dynamic library re-exporting it.
///
/// The function will be called for each procedural macro, regardless if it redefines the callback
/// behaviour or not. In case no custom behaviour is defined, this is a no-op.
///
/// # Safety
#[doc(hidden)]
#[no_mangle]
pub unsafe extern "C" fn post_process_callback_v2(
    context: StablePostProcessContext,
) -> StablePostProcessContext {
    if !CALLBACKS.is_empty() {
        // Callback has been defined, applying the aux data collection.
        let context = PostProcessContext::from_stable(&context);
        for callback in CALLBACKS {
            if let Callback::PostProcess(fun) = callback {
                fun(context.clone());
            }
        }
    }
    context
}

/// Return documentation string associated with this procedural macro expansion.
///
/// # Safety
///
#[doc(hidden)]
#[no_mangle]
pub unsafe extern "C" fn doc_v2(item_name: *mut c_char) -> *mut c_char {
    let item_name = CStr::from_ptr(item_name).to_string_lossy().to_string();
    let doc = MACRO_DEFINITIONS_SLICE
        .iter()
        .find_map(|m| {
            if m.name == item_name.as_str() {
                Some(m.doc)
            } else {
                None
            }
        })
        .expect("procedural macro not found");
    CString::new(doc).unwrap().into_raw()
}

/// Free the memory allocated for the documentation.
///
/// # Safety
///
#[doc(hidden)]
#[no_mangle]
pub unsafe extern "C" fn free_doc_v2(doc: *mut c_char) {
    if !doc.is_null() {
        let _ = CString::from_raw(doc);
    }
}

/// The fingerprint calculation callback.
///
/// This function needs to be accessible through the FFI interface,
/// of the dynamic library re-exporting it.
///
/// A fingerprint is an `u64` value used to determine if Cairo code depending on this
/// procedural macro should be recompiled or can use the incremental cache artifacts from
/// the previous build. User can define their own fingerprint callback, otherwise this will return
/// a constant value by default.
///
/// The function will be called for each procedural macro, regardless if it redefines the callback
/// behaviour or not.
///
/// # Safety
#[doc(hidden)]
#[no_mangle]
pub unsafe extern "C" fn fingerprint_v2() -> u64 {
    let mut result: u64 = 0;
    for callback in CALLBACKS {
        if let Callback::Fingerprint(fun) = callback {
            // This formula for combining hash values is taken from `boost::hash_combine` implementation.
            result ^= fun() + 0x9e3779b9 + (result << 6) + (result >> 2)
        }
    }
    result
}

/// A no-op Cairo attribute macro implementation.
///
/// This macro implementation does not produce any changes.
/// Can be exposed as a placeholder macro for the internal purposes.
#[doc(hidden)]
pub fn no_op_attr(_attr: TokenStream, input: TokenStream) -> ProcMacroResult {
    ProcMacroResult::new(input)
}