uwurandom_proc_macros/
lib.rs

1use convert_case::{Case, Casing};
2use proc_macro::{Span, TokenStream};
3use quote::quote;
4use syn::{Ident, LitChar};
5
6use crate::json::MarkovArr;
7
8mod json;
9
10#[proc_macro]
11pub fn gen_fsm(item: TokenStream) -> TokenStream {
12    let input: Vec<MarkovArr> = serde_json::from_str(&format!("[{}]", item)).unwrap();
13    let mut match_arms = quote!();
14    let mut variants = quote!();
15    let mut random_selection_match_arms = quote!();
16    for (idx, state) in input.iter().enumerate() {
17        let name = to_ident(&state.name);
18        let name_lit = &state.name;
19        let idx = idx as u32;
20        random_selection_match_arms = quote!(
21            #random_selection_match_arms
22            #idx => (Self::#name, #name_lit),
23        );
24        variants = quote!(
25            #variants
26            #name,
27        );
28        let mut inner_match_arms = quote!();
29        if state.total_probability == 1 {
30            let choice = &state.choices[0];
31            let next_state = to_ident(&input[choice.next_ngram].name);
32            let next_char = LitChar::new(choice.next_char, Span::call_site().into());
33            match_arms = quote!(
34                #match_arms
35                Self::#name => (Self::#next_state, #next_char),
36            );
37            continue;
38        }
39        for choice in &state.choices {
40            let next_state = to_ident(&input[choice.next_ngram].name);
41            let cumulative_probability = choice.cumulative_probability - 1;
42            let next_char = LitChar::new(choice.next_char, Span::call_site().into());
43            inner_match_arms = quote!(
44                #inner_match_arms
45                0..=#cumulative_probability => (Self::#next_state, #next_char),
46            )
47        }
48        let total_probability = state.total_probability;
49        match_arms = quote!(
50            #match_arms
51            Self::#name => match rng.next_u32() % #total_probability {
52                #inner_match_arms
53                _ => unreachable!(),
54            },
55        );
56    }
57    let variant_count = input.len() as u32;
58    quote!(
59        #[derive(Debug, Clone, Copy, PartialEq, Eq)]
60        pub enum StateMachine {
61            #variants
62        }
63        impl StateMachine {
64            pub fn generate(self, mut rng: impl ::rand_core::RngCore) -> (Self, char) {
65                match self {
66                    #match_arms
67                }
68            }
69            pub fn new_random(mut rng: impl ::rand_core::RngCore) -> (Self, &'static str) {
70                match rng.next_u32() % #variant_count {
71                    #random_selection_match_arms
72                    _ => unreachable!()
73                }
74            }
75        }
76    )
77    .into()
78}
79
80fn to_ident(name: &str) -> Ident {
81    Ident::new(
82        &name
83            .replace(' ', " space ")
84            .replace(';', " semicolon ") // Sanitize ident-unsafe characters
85            .replace('!', " exclamation ")
86            .replace(',', " comma ")
87            .replace('.', " period ") // I'm not calling it a full stop
88            .to_case(Case::Pascal),
89        Span::call_site().into(),
90    )
91}