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(®ister, 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 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 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 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 };
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 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 ctx.push_function(#struct_name);
581 ctx.put_prop_string(idx.try_into().unwrap(), name);
582 }
583 )
584 };
585 res.into()
587}