xq_derive/
lib.rs

1//#![no_std]
2//extern crate alloc;
3//use alloc::{format, vec};
4
5//extern crate pro_macro;
6//extern crate syn;
7#[macro_use]
8extern crate quote;
9
10//use xq_std::*;
11use proc_macro::TokenStream;
12use proc_macro2::Span;
13use quote::{quote, ToTokens};
14
15use syn::{parse_macro_input, spanned::Spanned, AttributeArgs, Item, Meta, NestedMeta};
16
17#[allow(dead_code)]
18fn attach_error<A>(mut v: syn::Result<A>, msg: &str) -> syn::Result<A> {
19    if let Err(e) = v.as_mut() {
20        let span = e.span();
21        e.combine(syn::Error::new(span, msg));
22    }
23    v
24}
25
26#[allow(dead_code)]
27fn get_attribute_value<'a, I: IntoIterator<Item = &'a Meta>>(
28    iter: I,
29    name: &str,
30) -> syn::Result<Option<&'a syn::LitStr>> {
31    for attr in iter.into_iter() {
32        match attr {
33            Meta::NameValue(mnv) => {
34                if mnv.path.is_ident(name) {
35                    if let syn::Lit::Str(lit) = &mnv.lit {
36                        return Ok(Some(lit));
37                    } else {
38                        return Err(syn::Error::new(
39                            mnv.span(),
40                            format!("The `{}` attribute must be a string literal.", name),
41                        ));
42                    }
43                }
44            }
45            Meta::Path(p) => {
46                if p.is_ident(name) {
47                    return Err(syn::Error::new(
48                        attr.span(),
49                        format!("The `{}` attribute must have a string literal value.", name),
50                    ));
51                }
52            }
53            Meta::List(p) => {
54                if p.path.is_ident(name) {
55                    return Err(syn::Error::new(
56                        attr.span(),
57                        format!("The `{}` attribute must have a string literal value.", name),
58                    ));
59                }
60            }
61        }
62    }
63    Ok(None)
64}
65
66#[allow(dead_code)]
67fn contains_attribute<'a, I: IntoIterator<Item = &'a Meta>>(iter: I, name: &str) -> bool {
68    iter.into_iter().any(|attr| attr.path().is_ident(name))
69}
70
71#[allow(dead_code)]
72fn contract_function_optional_args_tokens<'a, I: Copy + IntoIterator<Item = &'a Meta>>(
73    attrs: I,
74    amount_ident: &syn::Ident,
75    required_args: &mut Vec<&str>,
76) -> (proc_macro2::TokenStream, Vec<proc_macro2::TokenStream>) {
77    let mut setup_fn_args = proc_macro2::TokenStream::new();
78    let mut fn_args = vec![];
79    if contains_attribute(attrs, "payable") {
80        required_args.push("amount: Amount");
81        fn_args.push(quote!(#amount_ident));
82    } else {
83        setup_fn_args.extend(quote! {
84            if #amount_ident != 0 {
85                return -1;
86            }
87        });
88    };
89
90    if contains_attribute(attrs, "enable_logger") {
91        required_args.push("logger: &mut impl HasLogger");
92        let logger_ident = format_ident!("logger");
93        setup_fn_args.extend(quote!(let mut #logger_ident = xq_std::Logger::init();));
94        fn_args.push(quote!(&mut #logger_ident));
95    }
96    (setup_fn_args, fn_args)
97}
98
99fn get_attribute(metas: Vec<NestedMeta>, value: &str) -> syn::Result<Option<syn::LitStr>> {
100    for item in metas {
101        match item {
102            NestedMeta::Meta(meta) => match meta {
103                Meta::NameValue(nv) => {
104                    if nv.path.is_ident(value) {
105                        if let syn::Lit::Str(lit) = nv.lit {
106                            return Ok(Some(lit));
107                        } else {
108                            return Err(syn::Error::new(
109                                nv.span(),
110                                format!("the attribute must be a string literal."),
111                            ));
112                        }
113                    }
114                }
115                Meta::Path(_) => {
116                    return Err(syn::Error::new(
117                        meta.span(),
118                        format!("the attribute must be a string literal."),
119                    ))
120                }
121                Meta::List(_) => {
122                    return Err(syn::Error::new(
123                        meta.span(),
124                        format!("the attribute must be a string literal."),
125                    ))
126                }
127            },
128            NestedMeta::Lit(_) => {
129                return Err(syn::Error::new(
130                    item.span(),
131                    format!("the attribute must be a string literal."),
132                ))
133            }
134        }
135    }
136    return Ok(None);
137}
138
139fn contains_attribute2(metas: Vec<NestedMeta>, value: &str) -> bool {
140    for attr in metas.iter() {
141        match attr {
142            NestedMeta::Meta(meta) => {
143                if meta.path().is_ident(value) {
144                    return true;
145                }
146            }
147            NestedMeta::Lit(_) => {}
148        }
149    }
150    //metas.into_iter().any(|attr| attr.path().is_ident(name));
151    false
152}
153
154#[proc_macro_attribute]
155pub fn init(attr: TokenStream, item: TokenStream) -> TokenStream {
156    let attrs = parse_macro_input!(attr as AttributeArgs);
157    let contract = get_attribute(attrs.clone(), "contract").unwrap().unwrap();
158    //let pay = contains_attribute2(attrs.clone(), "payable");
159
160    let mut setup_function_args = proc_macro2::TokenStream::new();
161    let mut function_args = vec![];
162    let amount_ident = format_ident!("amount");
163
164    if contains_attribute2(attrs.clone(), "payable") {
165        function_args.push(quote!(#amount_ident));
166    } else {
167        setup_function_args.extend(quote! {
168            if #amount_ident != 0 {
169                return -1;
170            }
171        });
172    };
173
174    let ast = parse_macro_input!(item as Item);
175    let init_function_name = format_ident!("init_{}", contract.value());
176    let init_name = format!("init_{}", contract.value());
177    //let mut  output = proc_macro2::TokenStream::new();
178
179    let function_name = if let syn::Item::Fn(itemfn) = ast.clone() {
180        itemfn.sig.ident
181    } else {
182        return syn::Error::new(Span::call_site(), format!("#[init] must be function."))
183            .into_compile_error()
184            .into();
185    };
186    let output = quote! {
187        #ast
188        #[export_name = #init_name]
189        pub extern "C" fn #init_function_name(amount:u64)->i32{
190            use xq_std::{ContractContext,serde_json};
191            let initctx =  ContractContext;
192            #setup_function_args
193            match #function_name(initctx, #(#function_args),*){
194                Ok(o)=>{
195                    let r = serde_json::to_string(&o).unwrap();
196                    ContractContext.return_data(r.clone());
197                    return 1
198                }
199                Err(e)=>{
200                    let err = e.to_string();
201                    ContractContext.error(err.clone());
202                    return 0
203                }
204            }
205        }
206    };
207
208    TokenStream::from(output)
209}
210
211#[proc_macro_attribute]
212pub fn call(attr: TokenStream, item: TokenStream) -> TokenStream {
213    let attrs = parse_macro_input!(attr as AttributeArgs);
214    let contract = get_attribute(attrs.clone(), "contract").unwrap().unwrap();
215    let function = get_attribute(attrs.clone(), "function").unwrap().unwrap();
216    //let payable = contains_attribute2(attrs.clone(), "payable");
217
218    let mut setup_function_args = proc_macro2::TokenStream::new();
219    let mut function_args = vec![];
220    let amount_ident = format_ident!("amount");
221
222    if contains_attribute2(attrs.clone(), "payable") {
223        function_args.push(quote!(#amount_ident));
224    } else {
225        setup_function_args.extend(quote! {
226            if #amount_ident != 0 {
227                return -1;
228            }
229        });
230    };
231
232    let ast = parse_macro_input!(item as Item);
233    let call_function_name = format_ident!("{}_{}", contract.value(), function.value());
234    //let call_function_name = format_ident!("call_{}", "abc");
235    //let mut  output = proc_macro2::TokenStream::new();
236
237    let function_name = if let syn::Item::Fn(itemfn) = ast.clone() {
238        itemfn.sig.ident
239    } else {
240        return syn::Error::new(Span::call_site(), format!("#[call] must be function."))
241            .into_compile_error()
242            .into();
243    };
244
245    let output = quote! {
246        #ast
247        #[no_mangle]
248        pub extern "C" fn #call_function_name(amount:u64)->i32{
249
250            use xq_std::{ContractContext, serde_json};
251            let initctx =  ContractContext;
252            //let state = ContractContext.state_get();
253            #setup_function_args
254            match #function_name(initctx, #(#function_args),*){
255                Ok(o)=>{
256                    let r = serde_json::to_string(&o).unwrap();
257                    ContractContext.return_data(r.clone());
258                    return 1
259                }
260                Err(e)=>{
261                    let err = e.to_string();
262                    ContractContext.error(err.clone());
263                    return 0
264                }
265            }
266
267        }
268    };
269
270    TokenStream::from(output)
271}
272
273#[proc_macro_derive(Output)]
274pub fn output_derive(input: TokenStream) -> TokenStream {
275    let ast: syn::DeriveInput = syn::parse(input.clone()).unwrap();
276    let enum_ident = ast.clone().ident;
277    let enum_data = match &ast.data {
278        syn::Data::Enum(data) => data,
279        _ => {
280            return syn::Error::new(ast.span(), "Output can only be derived for enums.")
281                .to_compile_error()
282                .into()
283        }
284    };
285    let mut field_fmt = vec![];
286    let mut field_string = vec![];
287    for field in &enum_data.variants {
288        field_fmt.push(field.ident.clone());
289        field_string.push(field.ident.to_string());
290    }
291
292    let display = quote! {
293        impl core::fmt::Display for #enum_ident{
294            fn fmt(&self, f:&mut core::fmt::Formatter) -> core::fmt::Result{
295                //#(core::fmt::write!(f, "{}", #field_fmt.clone().unwrap());)*
296                //use core::fmt::write;
297                match self{
298                    #(#enum_ident::#field_fmt => core::write!(f, "{}", #field_string),)*
299                }
300            }
301        }
302    };
303
304    let ge = quote! {
305        #display
306    };
307    return ge.into();
308}
309
310#[proc_macro_attribute]
311pub fn state(_attr: TokenStream, item: TokenStream) -> TokenStream {
312    let mut output = proc_macro2::TokenStream::new();
313    let data_ident = if let Ok(ast) = syn::parse::<syn::ItemStruct>(item.clone()) {
314        ast.to_tokens(&mut output);
315        ast.ident
316    } else {
317        return syn::Error::new_spanned(
318            proc_macro2::TokenStream::from(item),
319            "#[state] only supports structs.",
320        )
321        .to_compile_error()
322        .into();
323    };
324
325    let impl_state = quote! {
326        impl #data_ident {
327            fn contract_state_set(&mut self,){
328                return
329            }
330
331            fn contract_state_get(&self){
332                return
333            }
334        }
335
336    };
337
338    impl_state.to_tokens(&mut output);
339
340    output.into()
341}