1use proc_macro::TokenStream;
2use quote::{quote, quote_spanned};
3use syn::{Error, Token, parse::Parse, parse_macro_input, spanned::Spanned};
4
5#[proc_macro_derive(Context)]
6pub fn roto_context(item: TokenStream) -> TokenStream {
7 let item = parse_macro_input!(item as syn::DeriveInput);
8
9 let struct_name = &item.ident;
10
11 let syn::Data::Struct(s) = &item.data else {
12 panic!("Only structs can be used as context");
13 };
14
15 let syn::Fields::Named(fields) = &s.fields else {
16 panic!("Fields must be named");
17 };
18
19 let fields: Vec<_> = fields
20 .named
21 .iter()
22 .map(|f| {
23 if !matches!(f.vis, syn::Visibility::Public(_)) {
24 panic!("All fields must be marked pub")
25 }
26
27 let field_name = f.ident.as_ref().unwrap();
28 let field_ty = &f.ty;
29 let offset = quote!(std::mem::offset_of!(Self, #field_name));
30 let type_name = quote!(std::any::type_name::<#field_ty>());
31 let type_id = quote!(std::any::TypeId::of::<#field_ty>());
32 let docstring = gather_docstring(&f.attrs);
33
34 quote!(
35 roto::__internal::ContextField {
36 name: stringify!(#field_name),
37 offset: #offset,
38 type_name: #type_name,
39 type_id: #type_id,
40 docstring: #docstring,
41 }
42 )
43 })
44 .collect();
45
46 let expanded = quote!(
47 unsafe impl Context for #struct_name {
48 fn fields() -> Vec<roto::__internal::ContextField> {
49 vec![
50 #(#fields),*
51 ]
52 }
53 }
54 );
55
56 TokenStream::from(expanded)
57}
58
59struct ItemList {
60 items: Vec<ItemWithDocs>,
61}
62
63struct ItemWithDocs {
64 doc: proc_macro2::TokenStream,
65 item: Item,
66}
67
68enum Item {
69 Type(syn::ItemType),
70 Let(syn::ExprLet),
71 Fn(syn::ItemFn),
72 Mod(syn::Ident, ItemList),
73 Impl(proc_macro2::Span, syn::Type, ItemList),
74 Const(syn::ItemConst),
75 Include(proc_macro2::TokenStream),
76 Use(syn::ItemUse),
77}
78
79mod kw {
80 syn::custom_keyword!(include);
81}
82
83impl Parse for ItemList {
84 fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
85 let mut items = Vec::new();
86
87 while !input.is_empty() {
88 let attributes = syn::Attribute::parse_outer(input)?;
92 let doc = gather_docstring(&attributes);
93
94 let look = input.lookahead1();
95 let item = match () {
96 _ if look.peek(Token![fn]) => {
98 let mut item: syn::ItemFn = input.parse()?;
99 item.attrs = attributes;
100 Item::Fn(item)
101 }
102 _ if look.peek(Token![mod]) => {
104 input.parse::<Token![mod]>()?;
105 let ident: syn::Ident = input.parse()?;
106 let content;
107 syn::braced!(content in input);
108 let item_list: ItemList = content.parse()?;
109 Item::Mod(ident, item_list)
110 }
111 _ if look.peek(Token![let]) => {
113 let mut item: syn::ExprLet = input.parse()?;
114 input.parse::<Token![;]>()?;
115 item.attrs = attributes;
116 Item::Let(item)
117 }
118 _ if look.peek(Token![impl]) => {
120 let tok = input.parse::<Token![impl]>()?;
121 let ty: syn::Type = input.parse()?;
122
123 let content;
124 syn::braced!(content in input);
125 let item_list: ItemList = content.parse()?;
126 Item::Impl(tok.span, ty, item_list)
127 }
128 _ if look.peek(Token![const]) => {
130 let mut item: syn::ItemConst = input.parse()?;
131 item.attrs = attributes;
132 Item::Const(item)
133 }
134 _ if look.peek(Token![type]) => {
136 let mut item: syn::ItemType = input.parse()?;
137 item.attrs = attributes;
138 Item::Type(item)
139 }
140 _ if look.peek(Token![use]) => {
141 let item = input.parse()?;
142 Item::Use(item)
143 }
144 _ if look.peek(kw::include) => {
145 let m: syn::Macro = input.parse()?;
146 input.parse::<Token![;]>()?;
147 Item::Include(m.tokens)
148 }
149 _ => return Err(look.error()),
150 };
151
152 items.push(ItemWithDocs { doc, item });
153 }
154
155 Ok(ItemList { items })
156 }
157}
158
159#[proc_macro]
160pub fn library(input: TokenStream) -> TokenStream {
161 let parsed_items: ItemList = syn::parse_macro_input!(input);
162
163 let expanded = to_tokens(parsed_items, None)
164 .unwrap_or_else(Error::into_compile_error);
165
166 TokenStream::from(expanded)
167}
168
169fn location(s: proc_macro2::Span) -> proc_macro2::TokenStream {
170 let start = s.end();
171 let line = start.line as u32;
172 let column = start.column as u32;
173 quote! {
174 roto::Location {
175 file: file!(),
176 line: #line,
177 column: #column,
178 }
179 }
180}
181
182fn to_tokens(
183 item_list: ItemList,
184 ty: Option<&syn::Type>,
185) -> syn::Result<proc_macro2::TokenStream> {
186 let mut items = Vec::new();
187 for ItemWithDocs { doc, item } in item_list.items {
188 let new = match item {
189 Item::Type(item) => {
190 let ident = &item.ident;
191 let location = location(ident.span());
192 let ident_str = ident.to_string();
193 let ty = &item.ty;
194 let movability = get_movability(item.span(), &item.attrs)?;
195
196 quote! {
197 roto::Type::#movability::<#ty>(
198 #ident_str,
199 #doc,
200 #location,
201 ).unwrap()
202 }
203 }
204 Item::Let(mut item) => {
205 let pat = item.pat;
206
207 let syn::Pat::Ident(ident) = &*pat else {
208 return Err(syn::Error::new(
209 pat.span(),
210 "pattern must be an identifier",
211 ));
212 };
213 let location = location(ident.ident.span());
214 let ident_str = ident.ident.to_string();
215
216 let expr = item.expr;
217 let syn::Expr::Closure(closure) = &*expr else {
218 return Err(syn::Error::new(
219 expr.span(),
220 "expressions must be a closure",
221 ));
222 };
223
224 let params: Vec<_> = closure
225 .inputs
226 .iter()
227 .map(|p| param_name(p).unwrap())
228 .collect();
229
230 let roto_sig = get_sig(&item.attrs)?;
231 let vtables = get_vtables(&item.attrs)?;
232
233 item.attrs.retain(|a| {
236 let path = a.meta.path();
237 let our_attrs =
238 path.is_ident("sig") || path.is_ident("vtables");
239 !our_attrs
240 });
241
242 if let Some(roto_sig) = roto_sig {
243 quote! {
244 unsafe { roto::Function::new_generic(
245 #ident_str,
246 #doc,
247 { let x: Vec<&'static str> = vec![#(#params),*]; x },
248 #expr,
249 #roto_sig,
250 vec![#(#vtables),*],
251 #location,
252 )}.unwrap()
253 }
254 } else {
255 quote! {
256 roto::Function::new(
257 #ident_str,
258 #doc,
259 { let x: Vec<&'static str> = vec![#(#params),*]; x },
260 #expr,
261 #location,
262 ).unwrap()
263 }
264 }
265 }
266 Item::Fn(mut item) => {
267 let sig = &item.sig;
268 let ident = &sig.ident;
269 let location = location(ident.span());
270 let ident_str = ident.to_string();
271
272 let params: Vec<_> = item
273 .sig
274 .inputs
275 .iter()
276 .map(|arg| match arg {
277 syn::FnArg::Receiver(_) => "self".into(),
278 syn::FnArg::Typed(pat) => {
279 param_name(&pat.pat).unwrap()
280 }
281 })
282 .collect();
283
284 let value_checks: proc_macro2::TokenStream = item
285 .sig
286 .inputs
287 .iter()
288 .map(|arg| {
289 let ty = match arg {
290 syn::FnArg::Receiver(receiver) => {
291 let span = receiver.span();
292 let ident = syn::Ident::new("Self", span);
293 quote!(#ident)
294 }
295 syn::FnArg::Typed(pat_type) => {
296 let ty = &pat_type.ty;
297
298 if let syn::Type::Path(type_path) = &**ty
302 && let Some(segment) = type_path.path.segments.last()
303 && segment.ident == "OutPtr" {
304 return quote!();
305 }
306
307 quote!(#ty)
308 }
309 };
310 let span = ty.span();
311 quote_spanned!(span=> roto::__internal::implements_value::<#ty>();)
312 })
313 .chain(match &item.sig.output {
314 syn::ReturnType::Default => None,
315 syn::ReturnType::Type(_, ty) => {
316 let span = ty.span();
317 Some(quote_spanned!(span=>
318 roto::__internal::implements_value::<#ty>();
319 ))
320 }
321 })
322 .collect();
323
324 let roto_sig = get_sig(&item.attrs)?;
325 let vtables = get_vtables(&item.attrs)?;
326
327 item.attrs.retain(|a| {
330 let path = a.meta.path();
331 let our_attrs =
332 path.is_ident("sig") || path.is_ident("vtables");
333 !our_attrs
334 });
335
336 let expr = if let Some(ty) = ty {
337 let new_inputs = sig
346 .inputs
347 .iter()
348 .map(|arg| match arg {
349 syn::FnArg::Receiver(rec) => {
350 syn::FnArg::Receiver(syn::Receiver {
351 mutability: None,
352 ..rec.clone()
353 })
354 }
355 syn::FnArg::Typed(pat_type) => {
356 syn::FnArg::Typed(syn::PatType {
357 pat: Box::new(syn::Pat::Wild(
358 syn::PatWild {
359 attrs: Vec::new(),
360 underscore_token:
361 syn::token::Underscore {
362 spans: [pat_type.span()],
363 },
364 },
365 )),
366 ..pat_type.clone()
367 })
368 }
369 })
370 .collect();
371
372 let new_sig = syn::Signature {
373 inputs: new_inputs,
374 ident: syn::Ident::new("__ext__", sig.ident.span()),
375 ..sig.clone()
376 };
377
378 let mut new_item = item.clone();
379 new_item.sig.ident =
380 syn::Ident::new("__ext__", sig.ident.span());
381
382 quote!(
383 const {
384 trait Ext {
385 #new_sig;
386 fn value_checks();
387 }
388
389 impl Ext for #ty {
390 #new_item
391
392 fn value_checks() {
393 #value_checks
394 }
395 }
396
397 <#ty as Ext>::__ext__
398 }
399 )
400 } else {
401 quote!({ #value_checks #item #ident })
402 };
403
404 let span = ident.span();
405 if let Some(roto_sig) = roto_sig {
406 quote_spanned! {span=> {
407 let #ident = #expr;
408 unsafe { roto::Function::new_generic(
409 #ident_str,
410 #doc,
411 { let x: Vec<&'static str> = vec![#(#params),*]; x },
412 #ident,
413 #roto_sig,
414 vec![#(#vtables),*],
415 #location,
416 )}.unwrap()
417 }}
418 } else {
419 quote_spanned! {span=> {
420 let #ident = #expr;
421
422 roto::Function::new(
423 #ident_str,
424 #doc,
425 { let x: Vec<&'static str> = vec![#(#params),*]; x },
426 #ident,
427 #location,
428 ).unwrap()
429 }}
430 }
431 }
432 Item::Mod(ident, items) => {
433 let ident_str = ident.to_string();
434 let location = location(ident.span());
435 let items = to_tokens(items, None)?;
436 quote! {{
437 let mut module = roto::Module::new(
438 #ident_str,
439 #doc,
440 #location,
441 ).unwrap();
442 module.add(#items);
443 module
444 }}
445 }
446 Item::Impl(span, ty, items) => {
447 let items = to_tokens(items, Some(&ty))?;
448 let location = location(span);
449 quote! {{
450 let mut impl_block = roto::Impl::new::<#ty>(#location);
451 impl_block.add(#items);
452 impl_block
453 }}
454 }
455 Item::Const(item) => {
456 let ident = item.ident;
457 let location = location(ident.span());
458 let ident_str = ident.to_string();
459 let ty = item.ty;
460 let expr = item.expr;
461 quote! {
462 roto::Constant::new::<#ty>(
463 #ident_str,
464 #doc,
465 #expr,
466 #location,
467 ).unwrap()
468 }
469 }
470 Item::Include(item) => {
471 quote! { #item }
472 }
473 Item::Use(item) => {
474 let imports = flatten_use_tree(&item.tree);
475 quote! {
476 roto::Use::new(
477 vec![#(vec![#(#imports.to_string()),*]),*],
478 roto::location!(),
479 )
480 }
481 }
482 };
483
484 items.push(quote! { #new });
485 }
486
487 Ok(quote! {{
488 let mut lib = roto::Library::new();
489 #(roto::Registerable::add_to_lib(#items, &mut lib);)*
490 lib
491 }})
492}
493
494fn get_sig(attrs: &[syn::Attribute]) -> syn::Result<Option<String>> {
495 let mut sig = None;
496 for attr in attrs {
497 if attr.meta.path().is_ident("sig") {
498 let syn::Meta::NameValue(syn::MetaNameValue {
499 value:
500 syn::Expr::Lit(syn::ExprLit {
501 lit: syn::Lit::Str(lit_str),
502 ..
503 }),
504 ..
505 }) = &attr.meta
506 else {
507 return Err(syn::Error::new(
508 attr.meta.span(),
509 "sig attribute must contain an equals sign followed by a string literal",
510 ));
511 };
512 let value = lit_str.value();
513 if sig.is_some() {
514 return Err(syn::Error::new(
515 attr.span(),
516 "duplicate sig attribute found, only 1 sig attribute is allowed",
517 ));
518 }
519 sig = Some(value);
520 }
521 }
522
523 Ok(sig)
524}
525
526fn get_vtables(attrs: &[syn::Attribute]) -> syn::Result<Vec<String>> {
527 let mut vtables = Vec::new();
528 for attr in attrs {
529 if attr.meta.path().is_ident("vtables") {
530 attr.parse_nested_meta(|meta| {
531 let Some(ident) = meta.path.get_ident() else {
532 return Err(meta.error(
533 "argument of vtables attribute must be an identifier",
534 ));
535 };
536 if !meta.input.is_empty() {
537 return Err(meta.error(
538 "argument of vtables attribute must be an identifier",
539 ));
540 }
541 vtables.push(ident.to_string());
542 Ok(())
543 })?;
544 }
545 }
546
547 Ok(vtables)
548}
549
550fn get_movability(
551 span: proc_macro2::Span,
552 attrs: &[syn::Attribute],
553) -> syn::Result<syn::Ident> {
554 let mut clone = 0;
555 let mut copy = 0;
556 let mut value = 0;
557 let mut ident_span = None;
558
559 for attr in attrs {
560 if let syn::Meta::Path(p) = &attr.meta {
561 if p.is_ident("clone") {
562 clone += 1;
563 ident_span = Some(p.span());
564 } else if p.is_ident("copy") {
565 copy += 1;
566 ident_span = Some(p.span());
567 } else if p.is_ident("value") {
568 value += 1;
569 ident_span = Some(p.span());
570 }
571 }
572 }
573
574 let s = match (clone, copy, value) {
575 (1, 0, 0) => "clone",
576 (0, 1, 0) => "copy",
577 (0, 0, 1) => "value",
578 _ => {
579 return Err(syn::Error::new(
580 span,
581 "specify exactly 1 of `#[clone]`, `#[copy]` or `#[value]`",
582 ));
583 }
584 };
585
586 Ok(syn::Ident::new(s, ident_span.unwrap()))
587}
588
589fn flatten_use_tree(tree: &syn::UseTree) -> Vec<Vec<String>> {
590 match tree {
591 syn::UseTree::Path(p) => {
592 let recursive = flatten_use_tree(&p.tree);
593 recursive
594 .into_iter()
595 .map(|v| {
596 let mut new_v = vec![p.ident.to_string()];
597 new_v.extend(v);
598 new_v
599 })
600 .collect()
601 }
602 syn::UseTree::Name(name) => {
603 vec![vec![name.ident.to_string()]]
604 }
605 syn::UseTree::Group(g) => {
606 g.items.iter().flat_map(flatten_use_tree).collect()
607 }
608 syn::UseTree::Rename(_) => panic!(),
609 syn::UseTree::Glob(_) => panic!(),
610 }
611}
612
613fn param_name(pat: &syn::Pat) -> Option<String> {
614 match pat {
615 syn::Pat::Ident(ident) => {
616 let s = ident.ident.to_string();
617 Some(match s.strip_suffix('_') {
618 Some(s) => s.to_string(),
619 None => s,
620 })
621 }
622 syn::Pat::Paren(paren) => param_name(&paren.pat),
623 syn::Pat::Reference(reference) => param_name(&reference.pat),
624 syn::Pat::TupleStruct(pat) => {
625 let elems: Vec<_> = pat.elems.iter().collect();
626 let [elem] = &*elems else { return None };
627 param_name(elem)
628 }
629 syn::Pat::Type(p) => param_name(&p.pat),
630 syn::Pat::Wild(_) => Some("_".to_string()),
631
632 syn::Pat::Or(p) => param_name(p.cases.first()?),
636
637 syn::Pat::Verbatim(_) => None,
639 syn::Pat::Tuple(_) => None,
640 syn::Pat::Struct(_) => None,
641 syn::Pat::Slice(_) => None,
642 syn::Pat::Rest(_) => None,
643 syn::Pat::Range(_) => None,
644 syn::Pat::Path(_) => None,
645 syn::Pat::Const(_) => None,
646 syn::Pat::Lit(_) => None,
647 syn::Pat::Macro(_) => None,
648 _ => None,
649 }
650}
651
652struct Intermediate {
653 function: proc_macro2::TokenStream,
654 ident: syn::Ident,
655 docstring: proc_macro2::TokenStream,
656 parameter_names: proc_macro2::TokenStream,
657}
658
659struct FunctionArgs {
660 runtime_ident: syn::Ident,
661 name: Option<syn::Ident>,
662}
663
664impl syn::parse::Parse for FunctionArgs {
665 fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
666 let runtime_ident = input.parse()?;
667
668 let mut name = None;
669 if input.peek(Token![,]) {
670 input.parse::<Token![,]>()?;
671 if input.peek(syn::Ident) {
672 name = input.parse()?;
673 }
674 }
675
676 Ok(Self {
677 runtime_ident,
678 name,
679 })
680 }
681}
682
683#[proc_macro_attribute]
684pub fn roto_function(attr: TokenStream, item: TokenStream) -> TokenStream {
685 let item = parse_macro_input!(item as syn::ItemFn);
686 let Intermediate {
687 function,
688 ident,
689 docstring,
690 parameter_names,
691 } = generate_function(item);
692
693 let FunctionArgs {
694 runtime_ident,
695 name,
696 } = syn::parse(attr).unwrap();
697
698 let name = name.unwrap_or(ident.clone());
699
700 let expanded = quote! {
701 #function
702
703 #runtime_ident.register_fn(
704 stringify!(#name),
705 #docstring,
706 #parameter_names,
707 #ident,
708 ).unwrap();
709 };
710
711 TokenStream::from(expanded)
712}
713
714struct MethodArgs {
715 runtime_ident: syn::Ident,
716 ty: syn::Type,
717 name: Option<syn::Ident>,
718}
719
720impl syn::parse::Parse for MethodArgs {
721 fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
722 let runtime_ident = input.parse()?;
723 input.parse::<Token![,]>()?;
724 let ty = input.parse()?;
725
726 let mut name = None;
727 if input.peek(Token![,]) {
728 input.parse::<Token![,]>()?;
729 if input.peek(syn::Ident) {
730 name = input.parse()?;
731 }
732 }
733 Ok(Self {
734 runtime_ident,
735 ty,
736 name,
737 })
738 }
739}
740
741#[proc_macro_attribute]
742pub fn roto_method(attr: TokenStream, item: TokenStream) -> TokenStream {
743 let item = parse_macro_input!(item as syn::ItemFn);
744 let Intermediate {
745 function,
746 ident,
747 docstring,
748 parameter_names,
749 } = generate_function(item);
750
751 let MethodArgs {
752 runtime_ident,
753 ty,
754 name,
755 } = parse_macro_input!(attr as MethodArgs);
756
757 let name = name.unwrap_or(ident.clone());
758
759 let expanded = quote! {
760 #function
761
762 #runtime_ident.register_method::<#ty, _, _>(
763 stringify!(#name),
764 #docstring,
765 #parameter_names,
766 #ident
767 ).unwrap();
768 };
769
770 TokenStream::from(expanded)
771}
772
773#[proc_macro_attribute]
774pub fn roto_static_method(
775 attr: TokenStream,
776 item: TokenStream,
777) -> TokenStream {
778 let item = parse_macro_input!(item as syn::ItemFn);
779 let Intermediate {
780 function,
781 ident,
782 docstring,
783 parameter_names,
784 } = generate_function(item);
785
786 let MethodArgs {
787 runtime_ident,
788 ty,
789 name,
790 } = parse_macro_input!(attr as MethodArgs);
791
792 let name = name.unwrap_or(ident.clone());
793
794 let expanded = quote! {
795 #function
796
797 #runtime_ident.register_static_method::<#ty, _, _>(
798 stringify!(#name),
799 #docstring.to_string(),
800 #parameter_names,
801 #ident
802 ).unwrap();
803 };
804
805 TokenStream::from(expanded)
806}
807
808fn gather_docstring(attrs: &[syn::Attribute]) -> proc_macro2::TokenStream {
809 let mut docstring = Vec::new();
810
811 for attr in attrs {
812 if attr.path().is_ident("doc") {
813 let value = match &attr.meta {
814 syn::Meta::NameValue(name_value) => &name_value.value,
815 _ => panic!("doc attribute must be a name and a value"),
816 };
817 docstring.push(value);
818 }
819 }
820
821 quote! {{
822 let x: Vec<String> = vec![#({
823 let s: String = #docstring.to_string();
824 s.strip_prefix(" ").unwrap_or(&s).to_string()
825 }),*];
826 x.join("\n")
827 }}
828}
829
830fn generate_function(item: syn::ItemFn) -> Intermediate {
831 let syn::ItemFn {
832 attrs,
833 vis: _,
834 sig,
835 block: _,
836 } = item.clone();
837
838 let docstring = gather_docstring(&attrs);
839
840 assert!(sig.unsafety.is_none());
841 assert!(sig.variadic.is_none());
842
843 let ident = sig.ident;
844 let args: Vec<_> = sig
845 .inputs
846 .iter()
847 .map(|i| {
848 let syn::FnArg::Typed(syn::PatType { pat, .. }) = i else {
849 panic!()
850 };
851 pat
852 })
853 .collect();
854
855 let parameter_names = quote!( [#(stringify!(#args)),*] );
856
857 Intermediate {
858 function: quote!(#item),
859 ident,
860 docstring,
861 parameter_names,
862 }
863}