1#![doc(
2 html_logo_url = "https://raw.githubusercontent.com/max-m/rust-libretro/master/media/logo.png",
3 html_favicon_url = "https://raw.githubusercontent.com/max-m/rust-libretro/master/media/favicon.png"
4)]
5
6use proc_macro::{self, TokenStream};
7use quote::{quote, ToTokens};
8use rust_libretro_sys::RETRO_NUM_CORE_OPTION_VALUES_MAX;
9use syn::{
10 braced, parenthesized,
11 parse::{discouraged::Speculative, Parse, ParseStream, Result},
12 parse2, parse_macro_input, parse_quote,
13 punctuated::Punctuated,
14 Attribute, DeriveInput, LitByteStr, LitStr, NestedMeta, Token,
15};
16
17mod util;
18use util::*;
19
20trait Concat<T> {
21 fn concat(self) -> T;
22}
23
24#[derive(Debug)]
25struct CoreOptionValue {
26 value: LitStr,
27 label: Option<LitStr>,
28}
29
30impl Parse for CoreOptionValue {
31 fn parse(input: ParseStream) -> Result<Self> {
32 let content;
33 braced!(content in input);
34
35 let value = content.parse()?;
36
37 if !content.is_empty() {
38 content.parse::<Token![,]>()?;
39 }
40
41 let label = if !content.is_empty() {
42 Some(content.parse()?)
43 } else {
44 None
45 };
46
47 if !content.is_empty() {
48 content.parse::<Token![,]>()?;
49 }
50
51 Ok(Self { value, label })
52 }
53}
54
55#[derive(Debug)]
56struct CoreOption {
57 key: LitStr,
58 desc: LitStr,
59 info: LitStr,
60 values: Vec<CoreOptionValue>,
61 default_value: Option<LitStr>,
62}
63
64impl Parse for CoreOption {
65 fn parse(input: ParseStream) -> Result<Self> {
66 let key: LitStr = input.parse()?;
67 input.parse::<Token![,]>()?;
68
69 let desc: LitStr = input.parse()?;
70 input.parse::<Token![,]>()?;
71
72 let info: LitStr = input.parse()?;
73 input.parse::<Token![,]>()?;
74
75 let options_content;
76 braced!(options_content in input);
77
78 let default_value: Option<LitStr> = if !input.is_empty() {
79 input.parse::<Token![,]>()?;
80 input.parse()?
81 } else {
82 None
83 };
84
85 let mut values = Vec::new();
86 while !options_content.is_empty() {
87 let value = options_content.parse::<CoreOptionValue>()?;
88 values.push(value);
89
90 if !options_content.is_empty() {
91 options_content.parse::<Token![,]>()?;
92 }
93 }
94
95 Ok(Self {
96 key,
97 desc,
98 info,
99 values,
100 default_value,
101 })
102 }
103}
104
105#[derive(Debug)]
106struct CoreOptionV2 {
107 key: LitStr,
108 desc: LitStr,
109 desc_categorized: Option<LitStr>,
110 info: LitStr,
111 info_categorized: Option<LitStr>,
112 category_key: Option<LitStr>,
113 values: Vec<CoreOptionValue>,
114 default_value: Option<LitStr>,
115}
116
117impl Parse for CoreOptionV2 {
118 fn parse(input: ParseStream) -> Result<Self> {
119 let key: LitStr = input.parse()?;
120 input.parse::<Token![,]>()?;
121
122 let desc: LitStr = input.parse()?;
123 input.parse::<Token![,]>()?;
124
125 let desc_categorized: LitStr = input.parse()?;
126 input.parse::<Token![,]>()?;
127
128 let info: LitStr = input.parse()?;
129 input.parse::<Token![,]>()?;
130
131 let info_categorized: LitStr = input.parse()?;
132 input.parse::<Token![,]>()?;
133
134 let category_key: LitStr = input.parse()?;
135 input.parse::<Token![,]>()?;
136
137 let options_content;
138 braced!(options_content in input);
139
140 if !input.is_empty() {
141 input.parse::<Token![,]>()?;
142 }
143
144 let default_value: Option<LitStr> = if !input.is_empty() {
145 input.parse()?
146 } else {
147 None
148 };
149
150 let mut values = Vec::new();
151 while !options_content.is_empty() {
152 let value = options_content.parse::<CoreOptionValue>()?;
153 values.push(value);
154
155 if !options_content.is_empty() {
156 options_content.parse::<Token![,]>()?;
157 }
158 }
159
160 let ret = Ok(Self {
161 key,
162 desc,
163 desc_categorized: Some(desc_categorized),
164 info,
165 info_categorized: Some(info_categorized),
166 category_key: Some(category_key),
167 values,
168 default_value,
169 });
170
171 if input.is_empty() {
173 return ret;
174 }
175 input.parse::<Token![,]>()?;
176
177 ret
178 }
179}
180
181impl From<CoreOption> for CoreOptionV2 {
182 fn from(option: CoreOption) -> Self {
183 Self {
184 key: option.key,
185 desc: option.desc,
186 desc_categorized: None,
187 info: option.info,
188 info_categorized: None,
189 category_key: None,
190 values: option.values,
191 default_value: option.default_value,
192 }
193 }
194}
195
196#[derive(Debug, Default)]
197struct CoreOptions(Vec<CoreOptionV2>);
198
199impl Parse for CoreOptions {
200 fn parse(outer: ParseStream) -> Result<Self> {
201 let input;
202 parenthesized!(input in outer);
203
204 let mut options = Self::default();
205
206 while !input.is_empty() {
207 let option;
208 braced!(option in input);
209
210 let core_option = {
211 let fork = option.fork();
212 if let Ok(option_v2) = fork.parse::<CoreOptionV2>() {
213 option.advance_to(&fork);
214 option_v2
215 } else {
216 option.parse::<CoreOption>()?.into()
217 }
218 };
219
220 options.0.push(core_option);
221
222 if input.is_empty() {
224 break;
225 }
226 input.parse::<Token![,]>()?;
227 }
228
229 Ok(options)
230 }
231}
232
233impl Concat<CoreOptions> for Vec<CoreOptions> {
234 fn concat(self) -> CoreOptions {
235 CoreOptions(self.into_iter().flat_map(|x| x.0).collect::<Vec<_>>())
236 }
237}
238
239#[derive(Debug)]
240struct CoreOptionCategory {
241 key: LitStr,
242 desc: LitStr,
243 info: LitStr,
244}
245
246impl Parse for CoreOptionCategory {
247 fn parse(input: ParseStream) -> Result<Self> {
248 let key: LitStr = input.parse()?;
249 input.parse::<Token![,]>()?;
250
251 let desc: LitStr = input.parse()?;
252 input.parse::<Token![,]>()?;
253
254 let info: LitStr = input.parse()?;
255
256 let ret = Ok(Self { key, desc, info });
257
258 if input.is_empty() {
260 return ret;
261 }
262 input.parse::<Token![,]>()?;
263
264 ret
265 }
266}
267
268#[derive(Debug, Default)]
269struct CoreOptionCategories(Vec<CoreOptionCategory>);
270
271impl Parse for CoreOptionCategories {
272 fn parse(outer: ParseStream) -> Result<Self> {
273 let input;
274 parenthesized!(input in outer);
275
276 let mut categories = Self::default();
277
278 while !input.is_empty() {
279 let category;
280 braced!(category in input);
281
282 let category = category.parse::<CoreOptionCategory>()?;
283
284 categories.0.push(category);
285
286 if input.is_empty() {
288 break;
289 }
290 input.parse::<Token![,]>()?;
291 }
292
293 Ok(categories)
294 }
295}
296
297impl Concat<CoreOptionCategories> for Vec<CoreOptionCategories> {
298 fn concat(self) -> CoreOptionCategories {
299 CoreOptionCategories(self.into_iter().flat_map(|x| x.0).collect::<Vec<_>>())
300 }
301}
302
303#[proc_macro_derive(CoreOptions, attributes(options, categories))]
345pub fn derive_core_options(input: TokenStream) -> TokenStream {
346 let input = parse_macro_input!(input as DeriveInput);
347
348 impl_derive_core_options(input)
349}
350
351fn impl_derive_core_options(input: DeriveInput) -> TokenStream {
352 let name = &input.ident;
353 let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
354 let attrs = &input.attrs;
355
356 let options = attrs
357 .iter()
358 .filter(|attr| attr.path.is_ident("options"))
359 .map(|attr| -> Result<CoreOptions> { parse2(attr.tokens.clone()) })
360 .collect::<Result<Vec<_>>>();
361
362 let options = match options {
363 Ok(options) => options.concat(),
364 Err(err) => return TokenStream::from(err.to_compile_error()),
365 };
366
367 let categories = attrs
368 .iter()
369 .filter(|attr| attr.path.is_ident("categories"))
370 .map(|attr| -> Result<CoreOptionCategories> { parse2(attr.tokens.clone()) })
371 .collect::<Result<Vec<_>>>();
372
373 let categories = match categories {
374 Ok(categories) => categories.concat(),
375 Err(err) => return TokenStream::from(err.to_compile_error()),
376 };
377
378 let option_count = options.0.len();
379 let category_count = categories.0.len();
380
381 fn lit_byte_str(lit: &LitStr) -> LitByteStr {
382 let span = lit.span();
383 let mut bytes = lit.value().into_bytes();
384 bytes.push(0x00); LitByteStr::new(&bytes, span)
387 }
388
389 fn get_option_values(option: &CoreOptionV2) -> proc_macro2::TokenStream {
390 let mut values = Vec::new();
391
392 for index in 0..(RETRO_NUM_CORE_OPTION_VALUES_MAX as usize - 1) {
393 values.push(if index < option.values.len() {
394 let value = lit_byte_str(&option.values[index].value);
395
396 if let Some(label) = &option.values[index].label {
397 let label = lit_byte_str(label);
398
399 quote! {
400 retro_core_option_value {
401 value: #value as *const u8 as *const libc::c_char,
402 label: #label as *const u8 as *const libc::c_char,
403 }
404 }
405 } else {
406 quote! {
407 retro_core_option_value {
408 value: #value as *const u8 as *const libc::c_char,
409 label: 0 as *const libc::c_char,
410 }
411 }
412 }
413 } else {
414 quote! {
415 retro_core_option_value {
416 value: 0 as *const libc::c_char,
417 label: 0 as *const libc::c_char,
418 }
419 }
420 });
421 }
422
423 values.push(quote! {
424 retro_core_option_value {
425 value: 0 as *const libc::c_char,
426 label: 0 as *const libc::c_char,
427 }
428 });
429
430 quote! {
431 [ #(#values),* ]
432 }
433 }
434
435 fn get_option_default_value(option: &CoreOptionV2) -> proc_macro2::TokenStream {
436 if let Some(ref default_value) = option.default_value {
437 let default_value = lit_byte_str(default_value);
438
439 quote! {
440 #default_value as *const u8 as *const libc::c_char
441 }
442 } else {
443 quote! {
444 0 as *const libc::c_char
445 }
446 }
447 }
448
449 let core_options = options
450 .0
451 .iter()
452 .map(|option| {
453 let key = lit_byte_str(&option.key);
454 let desc = lit_byte_str(&option.desc);
455 let info = lit_byte_str(&option.info);
456 let values = get_option_values(option);
457 let default_value = get_option_default_value(option);
458
459 quote! {
460 retro_core_option_definition {
461 key: #key as *const u8 as *const libc::c_char,
462 desc: #desc as *const u8 as *const libc::c_char,
463 info: #info as *const u8 as *const libc::c_char,
464 values: #values,
465 default_value: #default_value,
466 }
467 }
468 })
469 .collect::<Vec<_>>();
470
471 let core_variables = options
472 .0
473 .iter()
474 .map(|option| {
475 let key = lit_byte_str(&option.key);
476
477 let value = &format!(
478 "{}; {}",
479 &option.desc.value(),
480 option
481 .values
482 .iter()
483 .map(|value| value.value.value())
484 .collect::<Vec<_>>()
485 .join("|")
486 )
487 .into_bytes();
488 let value = LitByteStr::new(value, option.desc.span());
489
490 quote! {
491 retro_variable {
492 key: #key as *const u8 as *const libc::c_char,
493 value: #value as *const u8 as *const libc::c_char,
494 }
495 }
496 })
497 .collect::<Vec<_>>();
498
499 let core_options_v2 = options
500 .0
501 .iter()
502 .map(|option| {
503 let key = lit_byte_str(&option.key);
504 let desc = lit_byte_str(&option.desc);
505 let info = lit_byte_str(&option.info);
506 let values = get_option_values(option);
507 let default_value = get_option_default_value(option);
508
509 let desc_categorized = lit_byte_str(
510 option
511 .desc_categorized
512 .as_ref()
513 .unwrap_or(&LitStr::new("", proc_macro2::Span::call_site())),
514 );
515 let info_categorized = lit_byte_str(
516 option
517 .info_categorized
518 .as_ref()
519 .unwrap_or(&LitStr::new("", proc_macro2::Span::call_site())),
520 );
521 let category_key = lit_byte_str(
522 option
523 .category_key
524 .as_ref()
525 .unwrap_or(&LitStr::new("", proc_macro2::Span::call_site())),
526 );
527
528 quote! {
529 retro_core_option_v2_definition {
530 key: #key as *const u8 as *const libc::c_char,
531 desc: #desc as *const u8 as *const libc::c_char,
532 info: #info as *const u8 as *const libc::c_char,
533
534 desc_categorized: #desc_categorized as *const u8 as *const libc::c_char,
535 info_categorized: #info_categorized as *const u8 as *const libc::c_char,
536 category_key: #category_key as *const u8 as *const libc::c_char,
537
538 values: #values,
539 default_value: #default_value,
540 }
541 }
542 })
543 .collect::<Vec<_>>();
544
545 let core_option_categories = categories
546 .0
547 .iter()
548 .map(|category| {
549 let key = lit_byte_str(&category.key);
550 let desc = lit_byte_str(&category.desc);
551 let info = lit_byte_str(&category.info);
552
553 quote! {
554 retro_core_option_v2_category {
555 key: #key as *const u8 as *const libc::c_char,
556 desc: #desc as *const u8 as *const libc::c_char,
557 info: #info as *const u8 as *const libc::c_char,
558 }
559 }
560 })
561 .collect::<Vec<_>>();
562
563 let expanded = quote! {
564 impl #impl_generics ::rust_libretro::core::CoreOptions for #name #ty_generics #where_clause {
565 fn set_core_options(&self, ctx: &SetEnvironmentContext) -> bool {
566 let gctx: GenericContext = ctx.into();
567
568 match gctx.get_core_options_version() {
572 n if n >= 2 => ctx.set_core_options_v2(&Self::__RETRO_CORE_OPTIONS_V2),
573 n if n >= 1 => ctx.set_core_options(&Self::__RETRO_CORE_OPTIONS),
574 _ => ctx.set_variables(&Self::__RETRO_CORE_VARIABLES)
575 }
576 }
577 }
578
579 impl #impl_generics #name #ty_generics #where_clause {
580 #[doc(hidden)]
581 const __RETRO_CORE_OPTIONS: [retro_core_option_definition; #option_count + 1] = [
582 #(#core_options,)*
583
584 retro_core_option_definition {
586 key: 0 as *const libc::c_char,
587 desc: 0 as *const libc::c_char,
588 info: 0 as *const libc::c_char,
589 values: [retro_core_option_value {
590 value: 0 as *const libc::c_char,
591 label: 0 as *const libc::c_char,
592 }; #RETRO_NUM_CORE_OPTION_VALUES_MAX as usize],
593 default_value: 0 as *const libc::c_char,
594 }
595 ];
596
597 #[doc(hidden)]
598 const __RETRO_CORE_VARIABLES: [retro_variable; #option_count + 1] = [
599 #(#core_variables,)*
600
601 retro_variable {
603 key: 0 as *const libc::c_char,
604 value: 0 as *const libc::c_char,
605 }
606 ];
607
608 #[doc(hidden)]
609 const __RETRO_CORE_OPTION_V2_CATEGORIES: [retro_core_option_v2_category; 1 + #category_count] = [
610 #(#core_option_categories,)*
611
612 retro_core_option_v2_category {
613 key: 0 as *const libc::c_char,
614 desc: 0 as *const libc::c_char,
615 info: 0 as *const libc::c_char,
616 }
617 ];
618
619 #[doc(hidden)]
620 const __RETRO_CORE_OPTION_V2_DEFINITIONS: [retro_core_option_v2_definition; #option_count + 1] = [
621 #(#core_options_v2,)*
622
623 retro_core_option_v2_definition {
625 key: 0 as *const libc::c_char,
626 desc: 0 as *const libc::c_char,
627 desc_categorized: 0 as *const libc::c_char,
628 info: 0 as *const libc::c_char,
629 info_categorized: 0 as *const libc::c_char,
630 category_key: 0 as *const libc::c_char,
631 values: [retro_core_option_value {
632 value: 0 as *const libc::c_char,
633 label: 0 as *const libc::c_char,
634 }; 128],
635 default_value: 0 as *const libc::c_char,
636 }
637 ];
638
639 #[doc(hidden)]
640 const __RETRO_CORE_OPTIONS_V2: retro_core_options_v2 = retro_core_options_v2 {
641 categories: &Self::__RETRO_CORE_OPTION_V2_CATEGORIES as *const _ as *mut _,
643 definitions: &Self::__RETRO_CORE_OPTION_V2_DEFINITIONS as *const _ as *mut _,
645 };
646 }
647 };
648
649 TokenStream::from(expanded)
650}
651
652const UNSTABLE_TAG: &str = "<span class='stab unstable'>Unstable</span>";
653
654fn get_unstable_text(feature_name: &str) -> String {
655 format!(
656 "# This feature is unstable and guarded by the `{}` feature flag.\
657 \n\
658 Please be advised that this feature might change without further notice \
659 and no guarantees about its stability can be made.",
660 feature_name
661 )
662}
663
664fn add_unstable_text(attrs: &mut Vec<Attribute>, feature_name: &str) {
665 prepend_doc(attrs, UNSTABLE_TAG);
666
667 let unstable_doc = get_unstable_text(feature_name);
668
669 attrs.push(syn::parse_quote! {
670 #[doc = #unstable_doc]
671 });
672}
673
674#[proc_macro_attribute]
697pub fn unstable(args: TokenStream, input: TokenStream) -> TokenStream {
698 use syn::{AttributeArgs, Item, Lit, Meta, MetaList, Visibility};
699
700 let args = parse_macro_input!(args as AttributeArgs);
701 let mut item = parse_macro_input!(input as Item);
702
703 if let Item::Struct(ref mut item) = item {
705 if args.is_empty() {
706 if let syn::Fields::Named(fields) = &mut item.fields {
707 let len = fields.named.len();
708
709 for index in 0..len {
710 let field = &mut fields.named[index];
711 let metas = field
712 .attrs
713 .iter()
714 .filter(|attr| attr.path.is_ident("unstable"))
715 .filter_map(|attr| attr.parse_meta().ok())
716 .collect::<Vec<_>>();
717
718 field.attrs.retain(|attr| !attr.path.is_ident("unstable"));
719
720 if matches!(field.vis, Visibility::Public(_)) && !metas.is_empty() {
721 let mut private_item = field.clone();
722 private_item.vis = parse_quote!(pub(crate));
723
724 for meta in &metas {
725 let mut feature_name = "unstable".to_owned();
726
727 if let Meta::List(MetaList { nested, .. }) = meta {
728 if let NestedMeta::Meta(Meta::NameValue(ref named_value)) =
729 nested[0]
730 {
731 if let Lit::Str(custom_name) = &named_value.lit {
732 feature_name = format!("unstable-{}", custom_name.value());
733 }
734 }
735 }
736
737 add_unstable_text(&mut field.attrs, &feature_name);
738 field.attrs.push(syn::parse_quote! {
739 #[cfg(feature = #feature_name)]
740 });
741
742 add_unstable_text(&mut private_item.attrs, &feature_name);
743 private_item.attrs.push(syn::parse_quote! {
744 #[cfg(not(feature = #feature_name))]
745 });
746 }
747
748 fields.named.push(private_item);
749 }
750 }
751 }
752
753 return item.into_token_stream().into();
754 }
755 }
756
757 let feature_name = {
758 let mut name = "unstable".to_owned();
759
760 for arg in args.iter() {
761 if let NestedMeta::Lit(Lit::Str(custom_name)) = arg {
762 name = format!("unstable-{}", custom_name.value());
763 break;
764 } else if let NestedMeta::Meta(Meta::NameValue(named_value)) = arg {
765 if let Lit::Str(custom_name) = &named_value.lit {
766 name = format!("unstable-{}", custom_name.value());
767 break;
768 }
769 }
770 }
771
772 name
773 };
774
775 if let Item::Fn(ref mut item) = item {
776 item.sig.unsafety = Some(parse_quote!(unsafe));
778 }
779
780 if is_public(&item) {
781 if let Some(attrs) = get_attrs_mut(&mut item) {
782 add_unstable_text(attrs, &feature_name);
783 }
784
785 let mut private_item = item.clone();
786 if let Some(vis) = get_visibility_mut(&mut private_item) {
787 *vis = parse_quote!(pub(crate));
788 }
789
790 return TokenStream::from(quote! {
791 #[cfg(feature = #feature_name)]
792 #[allow(unused_unsafe)]
793 #item
794
795 #[cfg(not(feature = #feature_name))]
796 #[allow(unused_unsafe)]
797 #[allow(dead_code)]
798 #private_item
799 });
800 }
801
802 item.into_token_stream().into()
803}
804
805#[doc(hidden)]
806#[proc_macro_attribute]
807pub fn context(args: TokenStream, input: TokenStream) -> TokenStream {
808 let ctx_name = parse_macro_input!(args as syn::Ident);
809
810 let item = parse_macro_input!(input as syn::ItemFn);
811 let mut fun = item.clone();
812
813 fun.sig.unsafety = None;
815
816 let mut inputs: Punctuated<syn::FnArg, Token![,]> = Punctuated::new();
817 inputs.push(parse_quote!(&self));
818
819 for arg in fun.sig.inputs.iter().filter(|input| {
821 if let syn::FnArg::Typed(arg) = input {
822 if let syn::Type::Path(ty) = &*arg.ty {
823 if ty.path.is_ident("retro_environment_t")
824 || ty.path.segments.last().unwrap().ident == "retro_environment_t"
825 {
826 return false;
827 }
828 }
829 }
830
831 true
832 }) {
833 inputs.push(arg.clone());
834 }
835
836 fun.attrs
838 .retain(|attr| attr.path.segments.last().unwrap().ident != "context");
839
840 fun.sig.inputs = inputs;
842
843 let fun_name = &fun.sig.ident;
845 let mut fun_call_args: Punctuated<syn::Expr, Token![,]> = Punctuated::new();
846 fun_call_args.push(parse_quote!(*self.environment_callback));
847
848 for arg in fun.sig.inputs.iter().skip(1) {
850 if let syn::FnArg::Typed(arg) = arg {
851 if let syn::Pat::Ident(pat_ident) = &*arg.pat {
852 let ident = &pat_ident.ident;
853 fun_call_args.push(parse_quote!(#ident));
854 }
855 }
856 }
857
858 fun.block = parse_quote! {{
859 unsafe {
860 environment::#fun_name(#fun_call_args)
861 }
862 }};
863
864 let ctx_impl = quote! {
865 #item
866
867 impl #ctx_name<'_> {
868 #[inline]
869 #[allow(deprecated)]
870 #fun
871 }
872 };
873
874 TokenStream::from(ctx_impl)
875}