duktape_macros/
lib.rs

1use proc_macro::TokenStream;
2use proc_macro2::Span;
3use quote::quote;
4use syn::parse::Parse;
5use syn::{Ident, ItemFn};
6
7#[derive(Debug, Clone)]
8enum FieldId {
9    Name(Ident),
10    Index(u32),
11}
12
13impl FieldId {
14    fn field_name(&self) -> Ident {
15        match self {
16            FieldId::Name(ident) => ident.clone(),
17            FieldId::Index(i) => Ident::new(&format!("field_{}", i), Span::call_site()),
18        }
19    }
20}
21
22impl<'a> quote::ToTokens for FieldId {
23    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
24        let q = match self {
25            FieldId::Name(name) => quote!(#name),
26            FieldId::Index(idx) => {
27                let index = syn::Index {
28                    index: *idx,
29                    span: Span::call_site(),
30                };
31                quote!(#index)
32            }
33        };
34        tokens.extend(q);
35    }
36}
37
38struct FieldMeta {
39    name: FieldId,
40    ty: syn::Type,
41    is_data: bool,
42    is_hidden: bool,
43    serde_attrs: Vec<syn::Attribute>,
44}
45
46impl FieldMeta {
47    fn from_field(field: &syn::Field, idx: usize) -> Self {
48        let mut serde_attrs = Vec::new();
49        let mut is_data = false;
50        let mut is_hidden = false;
51        for attr in &field.attrs {
52            if let Ok(meta) = attr.parse_meta() {
53                if let Some(ident) = meta.path().get_ident() {
54                    match ident.to_string().as_str() {
55                        "serde" => {
56                            serde_attrs.push(attr.clone());
57                        }
58                        "data" => {
59                            is_data = true;
60                        }
61                        "hidden" => {
62                            is_hidden = true;
63                        }
64                        _ => {}
65                    }
66                }
67            }
68        }
69        FieldMeta {
70            name: field
71                .ident
72                .clone()
73                .map(FieldId::Name)
74                .unwrap_or(FieldId::Index(idx as u32)),
75            ty: field.ty.clone(),
76            is_data,
77            is_hidden,
78            serde_attrs,
79        }
80    }
81
82    fn prop_name(&self) -> proc_macro2::TokenStream {
83        let name = &self.name.field_name();
84        if self.is_hidden {
85            let name = name.to_string();
86            let mut buf = Vec::new();
87            buf.push(0xff);
88            buf.extend_from_slice(name.as_bytes());
89            let name = syn::LitByteStr::new(&buf, Span::call_site());
90            quote!(#name)
91        } else {
92            let name = name.to_string();
93            quote!(#name.as_bytes())
94        }
95    }
96}
97
98struct PushField<'a>(&'a FieldMeta);
99struct PeekField<'a>(&'a FieldMeta);
100
101impl<'a> quote::ToTokens for PushField<'a> {
102    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
103        let name = &self.0.name;
104        let prop_name = self.0.prop_name();
105        let q = if self.0.is_data {
106            let wrapper_name = Ident::new(
107                &format!("{}Wrapper", self.0.name.field_name().to_string()),
108                Span::call_site(),
109            );
110            let serde_attrs = &self.0.serde_attrs;
111            let ty = &self.0.ty;
112
113            quote! {
114                {
115                #[derive(serde::Serialize, serde::Deserialize)]
116                struct #wrapper_name(#( #serde_attrs )* #ty);
117
118                impl dukt::PushValue for #wrapper_name {
119                    fn push_to(self, ctx: &mut dukt::Context) -> u32 {
120                        use ::serde::Serialize;
121                        let mut serializer = dukt::serialize::DuktapeSerializer::from_ctx(ctx);
122                        self.serialize(&mut serializer).unwrap();
123                        ctx.stack_top()
124                    }
125                }
126
127                #wrapper_name(self.#name).push_to(ctx);
128                ctx.put_prop_bytes(idx.try_into().unwrap(), #prop_name);
129                }
130            }
131        } else {
132            quote! {
133                self.#name.push_to(ctx);
134                ctx.put_prop_bytes(idx.try_into().unwrap(), #prop_name);
135            }
136        };
137        tokens.extend(q);
138    }
139}
140
141impl<'a> quote::ToTokens for PeekField<'a> {
142    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
143        let ty = &self.0.ty;
144        let q = if self.0.is_data {
145            let wrapper_name = Ident::new(
146                &format!("{}Wrapper", self.0.name.field_name().to_string()),
147                Span::call_site(),
148            );
149            let serde_attrs = &self.0.serde_attrs;
150
151            quote! {
152                {
153                    #[derive(serde::Serialize, serde::Deserialize)]
154                    struct #wrapper_name(#( #serde_attrs )* #ty);
155
156                    impl dukt::PeekValue for #wrapper_name {
157                        fn peek_at(ctx: &mut dukt::Context, idx: i32) -> Result<Self, dukt::value::PeekError> {
158                            use ::serde::Deserialize;
159                            let mut serializer = dukt::serialize::DuktapeDeserializer::from_ctx(ctx, idx);
160                            Self::deserialize(&mut serializer).map_err(Into::into)
161                        }
162                    }
163
164                    ctx.pop_value::<#wrapper_name>().map(|w| w.0)
165                }
166            }
167        } else {
168            quote! {
169               ctx.pop_value::<#ty>()
170            }
171        };
172        tokens.extend(q);
173    }
174}
175
176#[proc_macro_derive(Value, attributes(dukt, data, hidden))]
177pub fn value(input: TokenStream) -> TokenStream {
178    let input = syn::parse_macro_input!(input as syn::DeriveInput);
179    let ident = input.ident.clone();
180    let fields = match input.data {
181        syn::Data::Struct(data) => data.fields,
182        _ => todo!("not (yet) supported"),
183    };
184    let mut fields_meta = Vec::new();
185    match fields {
186        syn::Fields::Named(fields) => {
187            for (i, field) in fields.named.iter().enumerate() {
188                let meta = FieldMeta::from_field(field, i);
189                fields_meta.push(meta);
190            }
191        }
192        syn::Fields::Unnamed(fields) => {
193            for (i, field) in fields.unnamed.iter().enumerate() {
194                let meta = FieldMeta::from_field(field, i);
195                fields_meta.push(meta);
196            }
197        }
198        _ => todo!("nameless fields not (yet) supported"),
199    }
200
201    enum Option {
202        Single(Ident),
203        Methods(Vec<String>),
204    }
205
206    let options = input
207        .attrs
208        .iter()
209        .filter(|attr| {
210            if let Some(ident) = attr.path.get_ident() {
211                ident.to_string() == "dukt"
212            } else {
213                false
214            }
215        })
216        .filter_map(|attr| attr.parse_meta().ok())
217        .filter_map(|meta| match meta {
218            syn::Meta::List(list) => Some(list),
219            _ => None,
220        })
221        .flat_map(|list| list.nested)
222        .flat_map(|val| {
223            match val {
224                syn::NestedMeta::Meta(meta) => match meta {
225                    syn::Meta::Path(path) => {
226                        return Some(Option::Single(path.get_ident().unwrap().clone()))
227                    }
228                    syn::Meta::List(list) => {
229                        let mut methods = vec![];
230                        for meta in list.nested {
231                            match meta {
232                                syn::NestedMeta::Meta(_meta) => {
233                                    panic!("unexpected");
234                                }
235                                syn::NestedMeta::Lit(lit) => match lit {
236                                    syn::Lit::Str(s) => methods.push(s.value()),
237                                    _ => {}
238                                },
239                            }
240                        }
241                        return Some(Option::Methods(methods));
242                    }
243                    _ => {}
244                },
245                syn::NestedMeta::Lit(_) => {}
246            }
247            None
248        })
249        .collect::<Vec<Option>>();
250
251    const GENERATE_PEEK: u8 = 1;
252    const GENERATE_PUSH: u8 = 2;
253    const GENERATE_AS_SERIALIZE: u8 = 4;
254    const DEFAULT: u8 = GENERATE_PEEK | GENERATE_PUSH;
255
256    let (flags, methods) = if options.is_empty() {
257        (DEFAULT, Vec::new())
258    } else {
259        let mut flags = 0;
260        let mut methods = vec![];
261        for option in &options {
262            match option {
263                Option::Single(option) => {
264                    flags |= match option.to_string().as_str() {
265                        "Peek" => GENERATE_PEEK,
266                        "Push" => GENERATE_PUSH,
267                        "Serialize" => GENERATE_AS_SERIALIZE,
268                        val => panic!(
269                            "unknown attribute value: {}, expected Peek, Push, Serialize",
270                            val
271                        ),
272                    }
273                }
274                Option::Methods(ms) => {
275                    methods = ms.to_vec();
276                }
277            }
278        }
279        (flags, methods)
280    };
281
282    let methods = methods.into_iter().map(|name| {
283        let register = format!("register_{}", inflections::case::to_snake_case(&name));
284        let register = Ident::new(&register, Span::call_site());
285
286        quote! {
287            Self::#register(ctx, idx, #name);
288        }
289    });
290    let register_all_methods = quote! {
291        fn register_methods(ctx: &mut dukt::Context, idx: u32) {
292            #( #methods )*
293        }
294    };
295
296    let ser = if flags & GENERATE_AS_SERIALIZE != 0 {
297        quote! {
298            impl #ident {
299                fn push_value<'a>(&'a self) -> impl dukt::value::PushValue + 'a {
300                    use dukt::value::SerdeValue;
301                    SerdeValue(self)
302                }
303            }
304        }
305    } else {
306        quote!()
307    };
308
309    let field_names: Vec<_> = fields_meta.iter().map(|meta| meta.name.clone()).collect();
310    let field_vars: Vec<_> = fields_meta
311        .iter()
312        .map(|meta| meta.name.field_name().clone())
313        .collect();
314    let field_names_str: Vec<_> = fields_meta
315        .iter()
316        .map(|meta| meta.name.field_name().to_string())
317        .collect();
318    let prop_names_str: Vec<_> = fields_meta.iter().map(|meta| meta.prop_name()).collect();
319    let fields_push: Vec<_> = fields_meta.iter().map(|meta| PushField(meta)).collect();
320    let fields_peek: Vec<_> = fields_meta.iter().map(|meta| PeekField(meta)).collect();
321
322    let push = if flags & GENERATE_PUSH != 0 {
323        quote! {
324            impl dukt::PushValue for #ident {
325                fn push_to(self, ctx: &mut dukt::Context) -> u32 {
326                    use std::convert::TryInto;
327                    let idx = ctx.push_object();
328                    #(
329                        #fields_push
330                    )*
331                    Self::register_methods(ctx, idx);
332                    idx
333                }
334
335                #register_all_methods
336            }
337        }
338    } else {
339        quote!()
340    };
341    let peek = if flags & GENERATE_PEEK != 0 {
342        quote! {
343            impl dukt::PeekValue for #ident {
344                fn peek_at(ctx: &mut Context, idx: i32) -> Result<Self, dukt::value::PeekError> {
345                    ctx.get_object(idx);
346                    #(
347                        if !ctx.get_prop_bytes(idx, #prop_names_str) {
348                            return Err(dukt::value::PeekError::Prop(#field_names_str));
349                        }
350                        let #field_vars = #fields_peek?;
351                    )*
352                    Ok(Self {
353                        #( #field_names: #field_vars ),*
354                    })
355                }
356            }
357        }
358    } else {
359        quote!()
360    };
361    let res = quote!( #peek #push #ser );
362    //println!(">>> {}", res);
363    res.into()
364}
365
366struct Args {
367    this: Option<Ident>,
368    vararg: bool,
369}
370
371struct KV {
372    name: Ident,
373    value: Option<String>,
374}
375
376impl Parse for KV {
377    fn parse(input: syn::parse::ParseStream) -> syn::parse::Result<Self> {
378        let name = Ident::parse(input)?;
379        let value = if let Ok(_) = syn::token::Eq::parse(input) {
380            let lit = syn::Lit::parse(input)?;
381            match lit {
382                syn::Lit::Str(str) => Some(str.value()),
383                _ => panic!(),
384            }
385        } else {
386            None
387        };
388        Ok(KV { name, value })
389    }
390}
391
392impl Parse for Args {
393    fn parse(input: syn::parse::ParseStream) -> syn::parse::Result<Self> {
394        let vars = syn::punctuated::Punctuated::<KV, syn::Token![,]>::parse_terminated(input)?;
395        let mut this = None;
396        let mut vararg = false;
397        for var in vars {
398            match var.name.to_string().as_str() {
399                "this" => this = Some(Ident::new(&var.value.unwrap(), Span::call_site())),
400                "vararg" => {
401                    vararg = true;
402                }
403                attr => {
404                    panic!("unknown attribute {}", attr);
405                }
406            }
407        }
408        Ok(Args { this, vararg })
409    }
410}
411
412#[proc_macro_attribute]
413pub fn dukt(attr: TokenStream, input: TokenStream) -> TokenStream {
414    let parsed_attr = syn::parse_macro_input!(attr as Args);
415    //println!("attrs: {:?}", parsed_attr);
416    let parsed: ItemFn = syn::parse_macro_input!(input);
417    let mut args = Vec::new();
418    let fn_name = parsed.sig.ident.clone();
419    let struct_name = Ident::new(
420        &inflections::case::to_pascal_case(&fn_name.to_string()),
421        Span::call_site(),
422    );
423    let (return_count, return_type) = match &parsed.sig.output {
424        syn::ReturnType::Default => (0, None),
425        syn::ReturnType::Type(_, typ) => {
426            let ident = match &**typ {
427                syn::Type::Path(path) => {
428                    quote!(#path)
429                }
430                syn::Type::Array(arr) => {
431                    quote!(#arr)
432                }
433                syn::Type::Reference(type_ref) => quote!(#type_ref),
434                _ => panic!("unsupported return type"),
435            };
436            (1, Some(ident))
437        }
438    };
439    let mut is_method = false;
440    for (i, param) in parsed.sig.inputs.iter().enumerate() {
441        match param {
442            syn::FnArg::Receiver(receiver) => {
443                if receiver.reference.is_none() {
444                    panic!("self not supported")
445                }
446                is_method = true;
447                continue;
448            }
449            syn::FnArg::Typed(pat_typ) => match &*pat_typ.ty {
450                syn::Type::Path(path) => {
451                    args.push(path);
452                }
453                syn::Type::Reference(_re) => {
454                    if i > 0 {
455                        panic!("unsupported reference");
456                    }
457                }
458                _ => panic!("unsupported"),
459            },
460        }
461    }
462    let args_count = args.len() as i32;
463    let raw_args_count = args_count - 1;
464
465    let args_names: Vec<_> = args
466        .iter()
467        .enumerate()
468        .map(|(i, _typ)| Ident::new(&format!("arg_{}", i), Span::call_site()))
469        .collect();
470
471    let args_getters: Vec<_> = args
472        .iter()
473        .zip(args_names.iter())
474        .enumerate()
475        .map(|(i, (typ, name))| {
476            let name_str = name.to_string();
477            let arg_idx = -(args_count as i32) + i as i32;
478            quote!(
479                let #name = ctx.peek::<#typ>(#arg_idx).expect(concat!("failed to peek ", #name_str));
480            )
481        })
482        .collect();
483    let push_result = match return_type {
484        Some(_) => {
485            quote!(
486                use dukt::value::PushValue;
487                result.push_to(ctx);
488            )
489        }
490        None => quote!(),
491    };
492
493    let bare_func = {
494        let func_args_count = if parsed_attr.vararg {
495            -1
496        } else {
497            raw_args_count
498        };
499        quote!(
500            struct #struct_name;
501
502            impl dukt::Function for #struct_name {
503                const ARGS: i32 = #func_args_count;
504
505                fn ptr(&self) -> unsafe extern "C" fn(*mut dukt::sys::duk_context) -> i32 {
506                    Self::#fn_name
507                }
508            }
509
510            impl #struct_name {
511                pub unsafe extern "C" fn #fn_name(raw: *mut dukt::sys::duk_context) -> i32 {
512                    #parsed
513
514                    // prevent drop
515                    let ctx = &mut std::mem::ManuallyDrop::new(dukt::Context::from_raw(raw));
516                    let n = ctx.stack_len();
517                    if n < #raw_args_count {
518                        return -1;
519                    }
520                    #(#args_getters)*
521                    if #raw_args_count > 0 {
522                        ctx.pop_n(#raw_args_count);
523                    }
524                    let result = #fn_name(ctx, #(#args_names),*);
525                    #push_result
526                    #return_count
527                }
528            }
529        )
530    };
531    let res = if !is_method {
532        bare_func
533    } else {
534        let method_args_count = if parsed_attr.vararg {
535            -1
536        } else {
537            raw_args_count + 1 /* self */
538        };
539        let register_fn = Ident::new(
540            &format!("register_{}", fn_name.to_string()),
541            Span::call_site(),
542        );
543        let outer_type = parsed_attr.this.unwrap();
544        quote!(
545
546        #parsed
547
548        pub fn #register_fn(ctx: &mut dukt::Context, idx: u32, name: &str) {
549            use ::std::convert::TryInto;
550            struct #struct_name;
551
552            impl dukt::Function for #struct_name {
553                const ARGS: i32 = #method_args_count;
554
555                fn ptr(&self) -> unsafe extern "C" fn(*mut ::dukt::sys::duk_context) -> i32 {
556                    Self::#fn_name
557                }
558            }
559
560            impl #struct_name {
561                pub unsafe extern "C" fn #fn_name(raw: *mut ::dukt::sys::duk_context) -> i32 {
562                    // prevent drop
563                    let ctx = &mut std::mem::ManuallyDrop::new(dukt::Context::from_raw(raw));
564                    let n = ctx.stack_len();
565                    if n < #method_args_count {
566                        return -1;
567                    }
568                    #(#args_getters)*
569                    ctx.push_this();
570                    let this: #outer_type = ctx.peek(-1).expect("failed to peek this");;
571                    if #method_args_count > 0 {
572                        ctx.pop_n(#method_args_count);
573                    }
574                    let result = this.#fn_name(#(#args_names),*);
575                    #push_result
576                    #return_count
577                }
578            }
579            //println!("registering method `{}` of {} args", name, #method_args_count);
580            ctx.push_function(#struct_name);
581            ctx.put_prop_string(idx.try_into().unwrap(), name);
582            }
583        )
584    };
585    //println!("{}", res);
586    res.into()
587}