lets_expect_core/core/
when.rs

1use proc_macro2::{Ident, Span, TokenStream};
2use syn::punctuated::Punctuated;
3use syn::spanned::Spanned;
4use syn::token::{Brace, Comma, Paren};
5use syn::{braced, parenthesized, parse::Parse};
6
7use crate::utils::to_ident::local_to_ident;
8
9use super::context::Context;
10use super::create_module::create_module;
11use super::keyword;
12use super::runtime::Runtime;
13use syn::{Attribute, Expr, Local, Pat, Type};
14use syn::{PatType, Token};
15
16const WHEN_IDENT_PREFIX: &str = "when_";
17
18struct WhenLet {
19    pub attrs: Vec<Attribute>,
20    pub pat: Pat,
21    pub init: (Token![=], Box<Expr>),
22}
23
24impl Parse for WhenLet {
25    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
26        let attrs = input.call(Attribute::parse_outer)?;
27        let mut pat = input.parse()?;
28
29        if input.peek(Token![:]) {
30            let colon_token: Token![:] = input.parse()?;
31            let ty: Type = input.parse()?;
32            pat = Pat::Type(PatType {
33                attrs: Vec::new(),
34                pat: Box::new(pat),
35                colon_token,
36                ty: Box::new(ty),
37            });
38        }
39
40        let init = (input.parse()?, input.parse()?);
41        Ok(Self { attrs, pat, init })
42    }
43}
44
45impl WhenLet {
46    pub fn to_local(&self) -> Local {
47        Local {
48            attrs: self.attrs.clone(),
49            let_token: Default::default(),
50            pat: self.pat.clone(),
51            init: Some(self.init.clone()),
52            semi_token: Default::default(),
53        }
54    }
55}
56
57pub struct When {
58    context: Context,
59    identifier: Ident,
60    string: String,
61    lets: Vec<Local>,
62}
63
64impl Parse for When {
65    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
66        let (lets, identifier, string) = if input.peek(Paren) {
67            parse_lets_in_parentheses(input)?
68        } else {
69            let identifier = input.parse::<Ident>()?;
70            (
71                Vec::new(),
72                Ident::new(
73                    &format!("{}{}", WHEN_IDENT_PREFIX, identifier),
74                    identifier.span(),
75                ),
76                identifier.to_string(),
77            )
78        };
79
80        let identifier = if input.peek(Token![as]) {
81            input.parse::<Token![as]>()?;
82            let ident = input.parse::<Ident>()?;
83            Ident::new(&format!("{}{}", WHEN_IDENT_PREFIX, ident), ident.span())
84        } else {
85            identifier
86        };
87
88        let context = if input.peek(Brace) {
89            let content;
90            braced!(content in input);
91            content.parse::<Context>()?
92        } else {
93            Context::from_single_item(input)?
94        };
95
96        Ok(Self {
97            lets,
98            identifier,
99            string,
100            context,
101        })
102    }
103}
104
105fn parse_lets_in_parentheses(
106    input: &syn::parse::ParseBuffer,
107) -> Result<(Vec<Local>, Ident, String), syn::Error> {
108    let content;
109    parenthesized!(content in input);
110
111    let string = content.to_string();
112    let when_lets: Punctuated<WhenLet, Comma> = Punctuated::parse_separated_nonempty(&content)?;
113    let lets: Vec<Local> = when_lets.iter().map(WhenLet::to_local).collect();
114
115    if lets.is_empty() {
116        return Err(syn::Error::new(
117            Span::call_site(),
118            "Expected at least one assignment",
119        ));
120    }
121
122    let name = WHEN_IDENT_PREFIX.to_string()
123        + lets
124            .iter()
125            .map(local_to_ident)
126            .collect::<Vec<String>>()
127            .join("_")
128            .as_str();
129    let identifier = Ident::new(name.as_str(), input.span());
130    Ok((lets, identifier, string))
131}
132
133impl When {
134    pub fn to_tokens(&self, keyword: &keyword::when, runtime: &Runtime) -> TokenStream {
135        let runtime = runtime.add_when(self.string.clone()).add_lets(&self.lets);
136        let context = self.context.to_tokens(&keyword.span(), &runtime);
137        create_module(&keyword.span(), &self.identifier, &context)
138    }
139}