diff --git a/Cargo.lock b/Cargo.lock index f92ef71de..3a8d0d0b9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4485,6 +4485,7 @@ dependencies = [ "redb", "reqwest", "scarb-build-metadata", + "scarb-macro-interface", "scarb-metadata 1.11.1", "scarb-test-support", "scarb-ui", @@ -4578,6 +4579,25 @@ dependencies = [ "test-for-each-example", ] +[[package]] +name = "scarb-macro-attributes" +version = "0.0.1" +dependencies = [ + "camino", + "quote", + "syn 2.0.48", +] + +[[package]] +name = "scarb-macro-interface" +version = "0.0.1" +dependencies = [ + "anyhow", + "libc", + "serde", + "serde_json", +] + [[package]] name = "scarb-metadata" version = "1.10.0" diff --git a/Cargo.toml b/Cargo.toml index 2e75e26ef..fe4b6bb77 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,6 +6,8 @@ members = [ "extensions/scarb-cairo-run", "extensions/scarb-cairo-test", "extensions/scarb-snforge-test-collector", + "plugins/scarb-macro-attributes", + "plugins/scarb-macro-interface", "utils/create-output-dir", "utils/scarb-build-metadata", "utils/scarb-test-support", diff --git a/plugins/scarb-macro-attributes/Cargo.toml b/plugins/scarb-macro-attributes/Cargo.toml new file mode 100644 index 000000000..daea3f6ae --- /dev/null +++ b/plugins/scarb-macro-attributes/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "scarb-macro-attributes" +version = "0.0.1" +edition.workspace = true + +authors.workspace = true +categories = ["development-tools"] +description = "Cairo procedural macro interface primitives." +homepage.workspace = true +keywords = ["scarb"] +license.workspace = true +readme = "README.md" +repository.workspace = true + +[lib] +proc-macro = true + +[dependencies] +camino.workspace = true +quote.workspace = true +syn = { workspace = true, features = ["full", "extra-traits"] } diff --git a/plugins/scarb-macro-attributes/README.md b/plugins/scarb-macro-attributes/README.md new file mode 100644 index 000000000..916030207 --- /dev/null +++ b/plugins/scarb-macro-attributes/README.md @@ -0,0 +1,3 @@ +# scarb-macro-interface + +Shared interface for Scarb procedural macros. diff --git a/plugins/scarb-macro-attributes/src/lib.rs b/plugins/scarb-macro-attributes/src/lib.rs new file mode 100644 index 000000000..f2eacdd0d --- /dev/null +++ b/plugins/scarb-macro-attributes/src/lib.rs @@ -0,0 +1,26 @@ +use proc_macro::TokenStream; +use quote::quote; +use syn::{parse_macro_input, ItemFn}; + +/// Inline macro helper. +/// +/// This macro hides the conversion to stable ABI structs from the user. +/// +/// # Safety +/// Note that token stream deserialization may fail. +#[proc_macro_attribute] +pub fn attribute_macro(_args: TokenStream, input: TokenStream) -> TokenStream { + let item: ItemFn = parse_macro_input!(input as ItemFn); + let item_name = &item.sig.ident; + let expanded = quote! { + #item + + #[no_mangle] + pub unsafe extern "C" fn expand(token_stream: scarb_macro_interface::stable_abi::StableTokenStream) -> scarb_macro_interface::stable_abi::StableProcMacroResult { + let token_stream = token_stream.into_token_stream(); + let result = #item_name(token_stream); + scarb_macro_interface::stable_abi::StableProcMacroResult::from_proc_macro_result(result) + } + }; + TokenStream::from(expanded) +} diff --git a/plugins/scarb-macro-interface/Cargo.toml b/plugins/scarb-macro-interface/Cargo.toml new file mode 100644 index 000000000..79883624d --- /dev/null +++ b/plugins/scarb-macro-interface/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "scarb-macro-interface" +version = "0.0.1" +edition.workspace = true + +authors.workspace = true +categories = ["development-tools"] +description = "Cairo procedural macro interface primitives." +homepage.workspace = true +keywords = ["scarb"] +license.workspace = true +readme = "README.md" +repository.workspace = true + +[dependencies] +anyhow.workspace = true +serde.workspace = true +serde_json.workspace = true +libc.workspace = true diff --git a/plugins/scarb-macro-interface/README.md b/plugins/scarb-macro-interface/README.md new file mode 100644 index 000000000..916030207 --- /dev/null +++ b/plugins/scarb-macro-interface/README.md @@ -0,0 +1,3 @@ +# scarb-macro-interface + +Shared interface for Scarb procedural macros. diff --git a/plugins/scarb-macro-interface/src/lib.rs b/plugins/scarb-macro-interface/src/lib.rs new file mode 100644 index 000000000..d6a22de34 --- /dev/null +++ b/plugins/scarb-macro-interface/src/lib.rs @@ -0,0 +1,30 @@ +use std::fmt::Display; + +#[doc(hidden)] +pub mod stable_abi; + +#[derive(Debug)] +pub enum ProcMacroResult { + /// Plugin has not taken any action. + Leave, + /// Plugin generated [`TokenStream`] replacement. + Replace(TokenStream), + /// Plugin ordered item removal. + Remove, +} + +#[derive(Debug, Default, Clone)] +pub struct TokenStream(String); + +impl TokenStream { + #[doc(hidden)] + pub fn new(s: String) -> Self { + Self(s) + } +} + +impl Display for TokenStream { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.0) + } +} diff --git a/plugins/scarb-macro-interface/src/stable_abi.rs b/plugins/scarb-macro-interface/src/stable_abi.rs new file mode 100644 index 000000000..55748b4f4 --- /dev/null +++ b/plugins/scarb-macro-interface/src/stable_abi.rs @@ -0,0 +1,81 @@ +use crate::{ProcMacroResult, TokenStream}; +use std::ffi::CString; +use std::os::raw::c_char; + +/// Token stream. +/// +/// This struct implements FFI-safe stable ABI. +#[repr(C)] +#[derive(Debug)] +pub struct StableTokenStream(pub *mut c_char); + +/// Procedural macro result. +/// +/// This struct implements FFI-safe stable ABI. +#[repr(C)] +#[derive(Debug)] +pub enum StableProcMacroResult { + /// Plugin has not taken any action. + Leave, + /// Plugin generated [`TokenStream`] replacement. + Replace(StableTokenStream), + /// Plugin ordered item removal. + Remove, +} + +impl StableTokenStream { + /// Convert to String. + /// + /// # Safety + pub unsafe fn to_string(&self) -> String { + if self.0.is_null() { + String::default() + } else { + let cstr = CString::from_raw(self.0); + cstr.to_string_lossy().to_string() + } + } + + /// Convert to native Rust representation. + /// + /// # Safety + pub unsafe fn into_token_stream(self) -> TokenStream { + TokenStream::new(self.to_string()) + } + + /// Convert to FFI-safe representation. + /// + /// # Safety + pub unsafe fn from_token_stream(token_stream: TokenStream) -> Self { + let cstr = CString::new(token_stream.0).unwrap(); + StableTokenStream(cstr.into_raw()) + } +} + +impl StableProcMacroResult { + /// Convert to native Rust representation. + /// + /// # Safety + pub unsafe fn into_proc_macro_result(self) -> ProcMacroResult { + match self { + Self::Leave => ProcMacroResult::Leave, + Self::Remove => ProcMacroResult::Remove, + Self::Replace(token_stream) => { + ProcMacroResult::Replace(token_stream.into_token_stream()) + } + } + } + + /// Convert to FFI-safe representation. + /// + /// # Safety + pub unsafe fn from_proc_macro_result(result: ProcMacroResult) -> Self { + match result { + ProcMacroResult::Leave => StableProcMacroResult::Leave, + ProcMacroResult::Remove => StableProcMacroResult::Remove, + ProcMacroResult::Replace(token_stream) => { + StableProcMacroResult::Replace(StableTokenStream::from_token_stream(token_stream)) + } + } + } +} diff --git a/scarb/Cargo.toml b/scarb/Cargo.toml index 92d883bb3..6cd166518 100644 --- a/scarb/Cargo.toml +++ b/scarb/Cargo.toml @@ -24,8 +24,8 @@ cairo-lang-formatter.workspace = true cairo-lang-semantic.workspace = true cairo-lang-sierra-to-casm.workspace = true cairo-lang-sierra.workspace = true -cairo-lang-starknet.workspace = true cairo-lang-starknet-classes.workspace = true +cairo-lang-starknet.workspace = true cairo-lang-test-plugin.workspace = true cairo-lang-utils.workspace = true camino.workspace = true @@ -51,6 +51,7 @@ petgraph.workspace = true redb.workspace = true reqwest.workspace = true scarb-build-metadata = { path = "../utils/scarb-build-metadata" } +scarb-macro-interface = { path = "../plugins/scarb-macro-interface" } scarb-metadata = { path = "../scarb-metadata", default-features = false, features = ["builder"] } scarb-ui = { path = "../utils/scarb-ui" } semver.workspace = true