Skip to content

Commit

Permalink
Rewrite cairo-lang-macro docs
Browse files Browse the repository at this point in the history
commit-id:1ca84df1
  • Loading branch information
maciektr committed Mar 22, 2024
1 parent 5abf421 commit e6688b5
Show file tree
Hide file tree
Showing 6 changed files with 137 additions and 24 deletions.
24 changes: 13 additions & 11 deletions plugins/cairo-lang-macro-attributes/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,11 @@ use quote::quote;
use scarb_stable_hash::short_hash;
use syn::{parse_macro_input, ItemFn};

/// Inline macro helper.
/// Constructs the attribute macro implementation.
///
/// This macro hides the conversion to stable ABI structs from the user.
///
/// # Safety
/// Note that token stream deserialization may fail.
/// Note, that this macro can be used multiple times, to define multiple independent attribute macros.
#[proc_macro_attribute]
pub fn attribute_macro(_args: TokenStream, input: TokenStream) -> TokenStream {
let item: ItemFn = parse_macro_input!(input as ItemFn);
Expand All @@ -32,22 +31,25 @@ pub fn attribute_macro(_args: TokenStream, input: TokenStream) -> TokenStream {

/// Constructs the post-processing callback.
///
/// The procedural macro can emit additional auxiliary data alongside the generated [`TokenStream`]
/// during the code expansion. This data can be used to collect additional information from the
/// source code of a project that is being compiled during the macro execution.
/// This callback will be called after the source code compilation (and thus after all the procedural
/// macro expansion calls).
/// The post-processing callback is the only function defined by the procedural macro that is
/// allowed to have side effects.
///
/// This macro will be called with a list of all auxiliary data emitted by the macro during code expansion.
///
/// This data can be used to collect additional information from the source code of a project
/// that is being compiled during the macro execution.
/// For instance, you can create a procedural macro that collects some information stored by
/// the Cairo programmer as attributes in the project source code.
///
/// This should be used to implement a collection callback for the auxiliary data.
/// This callback will be called after the source code compilation (and thus after all the procedural
/// macro executions). All auxiliary data emitted by the procedural macro during source code compilation
/// will be passed to the callback as an argument.
///
/// The callback can be used to process or persist the data collected during the compilation.
///
/// This macro hides the conversion to stable ABI structs from the user.
///
/// # Safety
/// If multiple callbacks are defined within the macro, all the implementations will be executed.
/// No guarantees can be made regarding the order of execution.
#[proc_macro_attribute]
pub fn post_process(_args: TokenStream, input: TokenStream) -> TokenStream {
let item: ItemFn = parse_macro_input!(input as ItemFn);
Expand Down
28 changes: 23 additions & 5 deletions plugins/cairo-lang-macro/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,20 @@
//! <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::*;
#[doc(hidden)]
pub use linkme;
Expand All @@ -11,6 +28,7 @@ mod types;

pub use types::*;

#[doc(hidden)]
#[derive(Clone)]
pub struct ExpansionDefinition {
pub name: &'static str,
Expand All @@ -33,8 +51,8 @@ pub static MACRO_DEFINITIONS_SLICE: [ExpansionDefinition];
/// of the dynamic library re-exporting it.
///
/// # Safety
#[no_mangle]
#[doc(hidden)]
#[no_mangle]
pub unsafe extern "C" fn list_expansions() -> StableExpansionsList {
let list = MACRO_DEFINITIONS_SLICE
.iter()
Expand All @@ -49,8 +67,8 @@ pub unsafe extern "C" fn list_expansions() -> StableExpansionsList {
/// of the dynamic library re-exporting it.
///
/// # Safety
#[no_mangle]
#[doc(hidden)]
#[no_mangle]
pub unsafe extern "C" fn free_expansions_list(list: StableExpansionsList) {
let v = list.into_owned();
v.into_iter().for_each(|v| {
Expand All @@ -66,8 +84,8 @@ pub unsafe extern "C" fn free_expansions_list(list: StableExpansionsList) {
/// The function will be called for each code expansion by the procedural macro.
///
/// # Safety
#[no_mangle]
#[doc(hidden)]
#[no_mangle]
pub unsafe extern "C" fn expand(
item_name: *const c_char,
stable_token_stream: cairo_lang_macro_stable::StableTokenStream,
Expand Down Expand Up @@ -101,8 +119,8 @@ pub unsafe extern "C" fn expand(
/// by the Rust compiler and the corresponding symbol will be available by the name of the function as id.
///
/// # Safety
#[no_mangle]
#[doc(hidden)]
#[no_mangle]
pub unsafe extern "C" fn free_result(result: StableProcMacroResult) {
ProcMacroResult::from_owned_stable(result);
}
Expand All @@ -121,8 +139,8 @@ pub static AUX_DATA_CALLBACKS: [fn(Vec<AuxData>)];
/// behaviour or not. In case no custom behaviour is defined, this is a no-op.
///
/// # Safety
#[no_mangle]
#[doc(hidden)]
#[no_mangle]
pub unsafe extern "C" fn aux_data_callback(
stable_aux_data: StableSlice<StableAuxData>,
) -> StableSlice<StableAuxData> {
Expand Down
1 change: 1 addition & 0 deletions plugins/cairo-lang-macro/src/types/conversion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,7 @@ impl AuxData {
/// Convert to FFI-safe representation.
///
/// # Safety
#[doc(hidden)]
pub fn maybe_into_stable(aux_data: Option<Self>) -> StableAuxData {
if let Some(aux_data) = aux_data {
aux_data.into_stable()
Expand Down
1 change: 1 addition & 0 deletions plugins/cairo-lang-macro/src/types/expansions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use cairo_lang_macro_stable::StableExpansionKind;
use std::num::NonZeroU8;

/// Representation of a macro expansion kind.
#[doc(hidden)]
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum ExpansionKind {
/// `#[proc_macro_name]`
Expand Down
101 changes: 98 additions & 3 deletions plugins/cairo-lang-macro/src/types/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ mod expansions;

pub use expansions::*;

/// Result of procedural macro code generation.
#[derive(Debug)]
pub enum ProcMacroResult {
/// Plugin has not taken any action.
Expand All @@ -20,16 +21,26 @@ pub enum ProcMacroResult {
Remove { diagnostics: Vec<Diagnostic> },
}

/// An abstract stream of Cairo tokens.
///
/// This is both input and part of an output of a procedural macro.
#[derive(Debug, Default, Clone)]
pub struct TokenStream {
value: String,
metadata: TokenStreamMetadata,
}

/// Metadata of [`TokenStream`].
///
/// This struct can be used to describe the origin of the [`TokenStream`].
#[derive(Debug, Default, Clone)]
pub struct TokenStreamMetadata {
original_file_path: Option<String>,
file_id: Option<String>,
/// The path to the file from which the [`TokenStream`] has been created.
pub original_file_path: Option<String>,
/// ID of the file from which the [`TokenStream`] has been created.
///
/// It is guaranteed, that the `file_id` will be unique for each file.
pub file_id: Option<String>,
}

impl TokenStream {
Expand All @@ -47,6 +58,9 @@ impl TokenStream {
self
}

/// Get `[TokenStreamMetadata`] associated with this [`TokenStream`].
///
/// The metadata struct can be used to describe the [`TokenStream`] origin.
pub fn metadata(&self) -> &TokenStreamMetadata {
&self.metadata
}
Expand All @@ -59,6 +73,7 @@ impl Display for TokenStream {
}

impl TokenStreamMetadata {
#[doc(hidden)]
pub fn new(file_path: impl ToString, file_id: impl ToString) -> Self {
Self {
original_file_path: Some(file_path.to_string()),
Expand All @@ -67,11 +82,61 @@ impl TokenStreamMetadata {
}
}

/// Auxiliary data returned by procedural macro.
/// **Auxiliary data** returned by procedural macro code generation.
///
/// This struct can be used to collect additional information from the Cairo source code of
/// compiled project.
/// For instance, you can create a procedural macro that collects some information stored by
/// the Cairo programmer as attributes in the project source code.
///
/// The auxiliary data struct stores `Vec<u8>` leaving the serialization and deserialization
/// of the data as user responsibility. No assumptions regarding the serialization algorithm
/// are made.
///
/// For instance, auxiliary data can be serialized as JSON.
///
/// ```
/// use cairo_lang_macro::{AuxData, ProcMacroResult, TokenStream, attribute_macro, post_process_callback}
/// use serde::{Serialize, Deserialize};
/// #[derive(Debug, Serialize, Deserialize)]
/// struct SomeAuxDataFormat {
/// some_message: String
/// }
///
/// #[attribute_macro]
/// pub fn some_macro(token_stream: TokenStream) -> ProcMacroResult {
/// let token_stream = TokenStream::new(
/// token_stream.to_string()
/// // Remove macro call to avoid infinite loop.
/// .replace("#[some]", "")
/// );
/// let value = SomeAuxDataFormat { some_message: "Hello from some macro!".to_string() };
/// let value = serde_json::to_string(&value).unwrap();
/// let value: Vec<u8> = value.into_bytes();
/// let aux_data = AuxData::new(value);
/// ProcMacroResult::replace(token_stream, Some(aux_data))
/// }
///
/// #[post_process_callback]
/// pub fn callback(aux_data: Vec<AuxData>) {
/// let aux_data = aux_data.into_iter()
/// .map(|aux_data| {
/// let value: Vec<u8> = aux_data.into();
/// let aux_data: SomeAuxDataFormat = serde_json::from_slice(&value).unwrap();
/// aux_data
/// })
/// .collect::<Vec<_>>();
/// println!("{:?}", aux_data);
/// }
/// ```
///
/// All auxiliary data emitted during compilation can be consumed
/// in the `post_process_callback` implementation.
#[derive(Debug, Clone)]
pub struct AuxData(Vec<u8>);

impl AuxData {
/// Create new [`AuxData`] struct from serialized data.
pub fn new(data: Vec<u8>) -> Self {
Self(data)
}
Expand All @@ -92,28 +157,48 @@ impl From<AuxData> for Vec<u8> {
/// Diagnostic returned by the procedural macro.
#[derive(Debug)]
pub struct Diagnostic {
/// A human addressed message associated with the [`Diagnostic`].
///
/// This message will not be parsed by the compiler,
/// but rather shown to the user as an explanation.
pub message: String,
/// The severity of the [`Diagnostic`].
///
/// Defines how this diagnostic should influence the compilation.
pub severity: Severity,
}

/// The severity of a diagnostic.
///
/// This should be roughly equivalent to the severity of Cairo diagnostics.
///
/// The appropriate action for each diagnostic kind will be taken by `Scarb`.
#[derive(Debug)]
pub enum Severity {
/// An error has occurred.
///
/// Emitting diagnostic with [`Severity::Error`] severity will fail the source code compilation.
Error = 1,
/// A warning suggestion will be shown to the user.
///
/// Emitting diagnostic with [`Severity::Warning`] severity does not stop the compilation.
Warning = 2,
}

/// A set of diagnostics that arose during the computation.
#[derive(Debug)]
pub struct Diagnostics(Vec<Diagnostic>);

impl Diagnostic {
/// Create new diagnostic with severity [`Severity::Error`].
pub fn error(message: impl ToString) -> Self {
Self {
message: message.to_string(),
severity: Severity::Error,
}
}

/// Create new diagnostic with severity [`Severity::Warning`].
pub fn warn(message: impl ToString) -> Self {
Self {
message: message.to_string(),
Expand All @@ -127,16 +212,22 @@ impl From<Vec<Diagnostic>> for Diagnostics {
Self(diagnostics)
}
}

impl Diagnostics {
/// Create new [`Diagnostics`] from a vector of diagnostics.
pub fn new(diagnostics: Vec<Diagnostic>) -> Self {
Self(diagnostics)
}

/// Create new diagnostic with severity [`Severity::Error`]
/// and push to the vector.
pub fn error(mut self, message: impl ToString) -> Self {
self.0.push(Diagnostic::error(message));
self
}

/// Create new diagnostic with severity [`Severity::Warning`]
/// and push to the vector.
pub fn warn(mut self, message: impl ToString) -> Self {
self.0.push(Diagnostic::warn(message));
self
Expand All @@ -153,18 +244,21 @@ impl IntoIterator for Diagnostics {
}

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

/// Create new [`ProcMacroResult::Remove`] variant, empty diagnostics set.
pub fn remove() -> Self {
Self::Remove {
diagnostics: Vec::new(),
}
}

/// Create new [`ProcMacroResult::Replace`] variant, empty diagnostics set.
pub fn replace(token_stream: TokenStream, aux_data: Option<AuxData>) -> Self {
Self::Replace {
aux_data,
Expand All @@ -173,6 +267,7 @@ impl ProcMacroResult {
}
}

/// Append diagnostics to the [`ProcMacroResult`] diagnostics set.
pub fn with_diagnostics(mut self, diagnostics: Diagnostics) -> Self {
match &mut self {
Self::Leave { diagnostics: d } => d.extend(diagnostics),
Expand Down
6 changes: 1 addition & 5 deletions scarb/tests/build_cairo_plugin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -484,11 +484,7 @@ fn can_return_aux_data_from_plugin() {
let value: Vec<u8> = value.into_bytes();
let aux_data = AuxData::new(value);
ProcMacroResult::Replace {
token_stream,
aux_data: Some(aux_data),
diagnostics: Vec::new()
}
ProcMacroResult::replace(token_stream, Some(aux_data))
}
#[post_process]
Expand Down

0 comments on commit e6688b5

Please sign in to comment.