1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
//! Utility methods for processing singleton references: `#my_var`.

use itertools::Itertools;
use proc_macro2::{Group, Ident, TokenStream, TokenTree};
use quote::quote_spanned;
use syn::punctuated::Punctuated;
use syn::{Expr, Token};

use crate::parse::parse_terminated;

/// Finds all the singleton references `#my_var` and appends them to `found_idents`. Returns the
/// `TokenStream` but with the hashes removed from the varnames.
///
/// The returned tokens are used for "preflight" parsing, to check that the rest of the syntax is
/// OK. However the returned tokens are not used in the codegen as we need to use [`postprocess_singletons`]
/// later to substitute-in the context referencing code for each singleton
pub fn preprocess_singletons(tokens: TokenStream, found_idents: &mut Vec<Ident>) -> TokenStream {
    process_singletons(tokens, &mut |singleton_ident| {
        found_idents.push(singleton_ident.clone());
        TokenTree::Ident(singleton_ident)
    })
}

/// Replaces singleton references `#my_var` with the code needed to actually get the value inside.
///
/// * `tokens` - The tokens to update singleton references within.
/// * `resolved_idents` - The context `StateHandle` varnames that correspond 1:1 and in the same
///   order as the singleton references within `tokens` (found in-order via [`preprocess_singletons`]).
///
/// Generates borrowing code ([`std::cell::RefCell::borrow_mut`]). Use
/// [`postprocess_singletons_handles`] for just the `StateHandle`s.
pub fn postprocess_singletons(
    tokens: TokenStream,
    resolved_idents: impl IntoIterator<Item = Ident>,
    context: &Ident,
) -> Punctuated<Expr, Token![,]> {
    let mut resolved_idents_iter = resolved_idents.into_iter();
    let processed = process_singletons(tokens, &mut |singleton_ident| {
        let span = singleton_ident.span();
        let context = Ident::new(&context.to_string(), span.resolved_at(context.span()));
        let mut resolved_ident = resolved_idents_iter.next().unwrap();
        resolved_ident.set_span(span);
        let mut group = Group::new(
            proc_macro2::Delimiter::Parenthesis,
            quote_spanned! {span=>
                *#context.state_ref(#resolved_ident).borrow_mut()
            },
        );
        group.set_span(singleton_ident.span());
        TokenTree::Group(group)
    });
    parse_terminated(processed).unwrap()
}

/// Same as [`postprocess_singletons`] but generates just the `StateHandle` ident rather than full
/// `RefCell` borrowing code.
pub fn postprocess_singletons_handles(
    tokens: TokenStream,
    resolved_idents: impl IntoIterator<Item = Ident>,
) -> Punctuated<Expr, Token![,]> {
    let mut resolved_idents_iter = resolved_idents.into_iter();
    let processed = process_singletons(tokens, &mut |singleton_ident| {
        let mut resolved_ident = resolved_idents_iter.next().unwrap();
        resolved_ident.set_span(singleton_ident.span().resolved_at(resolved_ident.span()));
        TokenTree::Ident(resolved_ident)
    });
    parse_terminated(processed).unwrap()
}

/// Traverse the token stream, applying the `map_singleton_fn` whenever a singleton is found,
/// returning the transformed token stream.
fn process_singletons(
    tokens: TokenStream,
    map_singleton_fn: &mut impl FnMut(Ident) -> TokenTree,
) -> TokenStream {
    tokens
        .into_iter()
        .peekable()
        .batching(|iter| {
            let out = match iter.next()? {
                TokenTree::Group(group) => {
                    let mut new_group = Group::new(
                        group.delimiter(),
                        process_singletons(group.stream(), map_singleton_fn),
                    );
                    new_group.set_span(group.span());
                    TokenTree::Group(new_group)
                }
                TokenTree::Ident(ident) => TokenTree::Ident(ident),
                TokenTree::Punct(punct) => {
                    if '#' == punct.as_char() && matches!(iter.peek(), Some(TokenTree::Ident(_))) {
                        // Found a singleton.
                        let Some(TokenTree::Ident(mut singleton_ident)) = iter.next() else {
                            unreachable!()
                        };
                        {
                            // Include the `#` in the span.
                            let span = singleton_ident
                                .span()
                                .join(punct.span())
                                .unwrap_or(singleton_ident.span());
                            singleton_ident.set_span(span.resolved_at(singleton_ident.span()));
                        }
                        (map_singleton_fn)(singleton_ident)
                    } else {
                        TokenTree::Punct(punct)
                    }
                }
                TokenTree::Literal(lit) => TokenTree::Literal(lit),
            };
            Some(out)
        })
        .collect()
}