cairo_lang_macro_attributes/
lib.rs1use proc_macro::TokenStream;
2use quote::{ToTokens, quote};
3use scarb_stable_hash::short_hash;
4use syn::spanned::Spanned;
5use syn::{
6 Expr, Ident, ItemFn, LitStr, Meta, Result, Token, parse::Parse, parse::ParseStream,
7 parse_macro_input,
8};
9
10#[proc_macro_attribute]
16pub fn attribute_macro(args: TokenStream, input: TokenStream) -> TokenStream {
17 macro_helper(
18 input,
19 parse_macro_input!(args as AttributeArgs),
20 quote!(::cairo_lang_macro::ExpansionKind::Attr),
21 quote!(::cairo_lang_macro::ExpansionFunc::Attr),
22 )
23}
24
25#[proc_macro_attribute]
31pub fn inline_macro(args: TokenStream, input: TokenStream) -> TokenStream {
32 let attribute_args = parse_macro_input!(args as AttributeArgs);
34 if let Some(path) = attribute_args.parent_module_path {
35 return syn::Error::new(path.span(), "inline macro cannot use `parent` argument")
36 .to_compile_error()
37 .into();
38 }
39 macro_helper(
41 input,
42 Default::default(),
43 quote!(::cairo_lang_macro::ExpansionKind::Inline),
44 quote!(::cairo_lang_macro::ExpansionFunc::Other),
45 )
46}
47
48#[proc_macro_attribute]
54pub fn derive_macro(args: TokenStream, input: TokenStream) -> TokenStream {
55 macro_helper(
56 input,
57 parse_macro_input!(args as AttributeArgs),
58 quote!(::cairo_lang_macro::ExpansionKind::Derive),
59 quote!(::cairo_lang_macro::ExpansionFunc::Other),
60 )
61}
62
63fn macro_helper(
64 input: TokenStream,
65 args: AttributeArgs,
66 kind: impl ToTokens,
67 func: impl ToTokens,
68) -> TokenStream {
69 let item: ItemFn = parse_macro_input!(input as ItemFn);
70
71 let original_item_name = item.sig.ident.to_string();
72 let expansion_name = if let Some(path) = args.parent_module_path {
73 let value = path.value();
74 if !is_valid_path(&value) {
75 return syn::Error::new(path.span(), "`parent` argument is not a valid path")
76 .to_compile_error()
77 .into();
78 }
79 format!("{value}::{original_item_name}")
80 } else {
81 original_item_name
82 };
83 let doc = item
84 .attrs
85 .iter()
86 .filter_map(|attr| match &attr.meta {
87 Meta::NameValue(meta) => meta.path.is_ident("doc").then(|| match &meta.value {
88 Expr::Lit(lit) => match &lit.lit {
89 syn::Lit::Str(lit) => Some(lit.value().trim().to_string()),
90 _ => None,
91 },
92 _ => None,
93 }),
94 _ => None,
95 })
96 .flatten()
97 .collect::<Vec<_>>()
98 .join("\n");
99 let item = hide_name(item);
100 let item_name = &item.sig.ident;
101
102 let callback_link = format!(
103 "EXPANSIONS_DESERIALIZE_{}",
104 item_name.to_string().to_uppercase()
105 );
106
107 let callback_link = Ident::new(callback_link.as_str(), item.span());
108
109 let expanded = quote! {
110 #item
111
112 #[::cairo_lang_macro::linkme::distributed_slice(::cairo_lang_macro::MACRO_DEFINITIONS_SLICE)]
113 #[linkme(crate = ::cairo_lang_macro::linkme)]
114 static #callback_link: ::cairo_lang_macro::ExpansionDefinition =
115 ::cairo_lang_macro::ExpansionDefinition{
116 name: #expansion_name,
117 doc: #doc,
118 kind: #kind,
119 fun: #func(#item_name),
120 };
121 };
122 TokenStream::from(expanded)
123}
124
125#[derive(Default)]
126struct AttributeArgs {
127 parent_module_path: Option<LitStr>,
128}
129
130impl Parse for AttributeArgs {
131 fn parse(input: ParseStream) -> Result<Self> {
132 if input.is_empty() {
133 return Ok(Self {
134 parent_module_path: None,
135 });
136 }
137 let parent_identifier: Ident = input.parse()?;
138 if parent_identifier != "parent" {
139 return Err(input.error("only `parent` argument is supported"));
140 }
141 let _eq_token: Token![=] = input.parse()?;
142 let parent_module_path: LitStr = input.parse()?;
143 Ok(Self {
144 parent_module_path: Some(parent_module_path),
145 })
146 }
147}
148
149fn is_valid_path(path: &str) -> bool {
150 let mut chars = path.chars().peekable();
151 let mut last_was_colon = false;
152 while let Some(c) = chars.next() {
153 if c.is_alphanumeric() || c == '_' {
154 last_was_colon = false;
155 } else if c == ':' {
156 if last_was_colon {
157 last_was_colon = false;
159 } else {
160 if chars.peek() != Some(&':') {
162 return false;
163 }
164 last_was_colon = true;
165 }
166 } else {
167 return false;
168 }
169 }
170 !last_was_colon
172}
173
174#[proc_macro_attribute]
196pub fn post_process(_args: TokenStream, input: TokenStream) -> TokenStream {
197 let item: ItemFn = parse_macro_input!(input as ItemFn);
198 let item = hide_name(item);
199 let item_name = &item.sig.ident;
200
201 let callback_link = format!(
202 "POST_PROCESS_DESERIALIZE_{}",
203 item_name.to_string().to_uppercase()
204 );
205 let callback_link = Ident::new(callback_link.as_str(), item.span());
206
207 let expanded = quote! {
208 #item
209
210 #[::cairo_lang_macro::linkme::distributed_slice(::cairo_lang_macro::CALLBACKS)]
211 #[linkme(crate = ::cairo_lang_macro::linkme)]
212 static #callback_link: ::cairo_lang_macro::Callback = ::cairo_lang_macro::Callback::PostProcess(#item_name);
213 };
214
215 TokenStream::from(expanded)
216}
217
218#[proc_macro_attribute]
231pub fn fingerprint(_args: TokenStream, input: TokenStream) -> TokenStream {
232 let item: ItemFn = parse_macro_input!(input as ItemFn);
233 let item = hide_name(item);
234 let item_name = &item.sig.ident;
235
236 let callback_link = format!(
237 "FINGERPRINT_DESERIALIZE_{}",
238 item_name.to_string().to_uppercase()
239 );
240 let callback_link = Ident::new(callback_link.as_str(), item.span());
241
242 let expanded = quote! {
243 #item
244
245 #[::cairo_lang_macro::linkme::distributed_slice(::cairo_lang_macro::CALLBACKS)]
246 #[linkme(crate = ::cairo_lang_macro::linkme)]
247 static #callback_link: ::cairo_lang_macro::Callback = ::cairo_lang_macro::Callback::Fingerprint(#item_name);
248 };
249
250 TokenStream::from(expanded)
251}
252
253fn hide_name(mut item: ItemFn) -> ItemFn {
255 let id = short_hash(item.sig.ident.to_string());
256 let item_name = format!("{}_{}", item.sig.ident, id);
257 item.sig.ident = Ident::new(item_name.as_str(), item.sig.ident.span());
258 item
259}
260
261const EXEC_ATTR_PREFIX: &str = "__exec_attr_";
262
263#[proc_macro]
264pub fn executable_attribute(input: TokenStream) -> TokenStream {
265 let input: LitStr = parse_macro_input!(input as LitStr);
266 let callback_link = format!("EXEC_ATTR_DESERIALIZE{}", input.value().to_uppercase());
267 let callback_link = Ident::new(callback_link.as_str(), input.span());
268 let item_name = format!("{EXEC_ATTR_PREFIX}{}", input.value());
269 let org_name = Ident::new(item_name.as_str(), input.span());
270 let expanded = quote! {
271 fn #org_name() {
272 }
274
275 #[::cairo_lang_macro::linkme::distributed_slice(::cairo_lang_macro::MACRO_DEFINITIONS_SLICE)]
276 #[linkme(crate = ::cairo_lang_macro::linkme)]
277 static #callback_link: ::cairo_lang_macro::ExpansionDefinition =
278 ::cairo_lang_macro::ExpansionDefinition{
279 name: #item_name,
280 doc: "",
281 kind: ::cairo_lang_macro::ExpansionKind::Attr,
282 fun: ::cairo_lang_macro::ExpansionFunc::Attr(::cairo_lang_macro::no_op_attr),
283 };
284 };
285 TokenStream::from(expanded)
286}