Skip to main content

pyro_macro/module/
parse.rs

1use proc_macro2;
2use syn::{
3    Expr, Ident, Meta, Result, Token,
4    parse::{Parse, ParseStream},
5    punctuated::Punctuated,
6};
7
8/// The three supported output patterns
9pub enum OutputSpec {
10    /// Single field: `output = "field_name"`
11    SingleField(Ident),
12    /// Tuple fields: `output = (field1, field2, ...)`  
13    TupleFields(Vec<Ident>),
14    /// Existing struct: `output = StructName`
15    Struct,
16}
17
18/// Parsed attributes for #[module(...)]
19pub struct ModuleAttrs {
20    pub session: bool,
21    pub output: OutputSpec,
22}
23
24impl ModuleAttrs {
25    fn from_metas(metas: Punctuated<Meta, Token![,]>) -> Result<Self> {
26        let mut session = false;
27        let mut output = None;
28
29        for meta in metas {
30            match meta {
31                Meta::Path(path) if path.is_ident("session") => {
32                    session = true;
33                }
34                Meta::NameValue(nv) if nv.path.is_ident("output") => {
35                    output = Some(parse_output_spec(&nv.value)?);
36                }
37                _ => {
38                    return Err(syn::Error::new_spanned(
39                        meta,
40                        "Unexpected attribute. Expected 'session' or 'output = ...'",
41                    ));
42                }
43            }
44        }
45
46        let output = output.ok_or_else(|| {
47            syn::Error::new(proc_macro2::Span::call_site(), "Missing `output` attribute")
48        })?;
49
50        Ok(ModuleAttrs { session, output })
51    }
52}
53
54fn parse_output_spec(expr: &Expr) -> Result<OutputSpec> {
55    match expr {
56        Expr::Tuple(tuple) => {
57            let mut fields = Vec::new();
58            for e in &tuple.elems {
59                if let Expr::Path(path) = e {
60                    if let Some(ident) = path.path.get_ident() {
61                        fields.push(ident.clone());
62                    } else {
63                        return Err(syn::Error::new_spanned(
64                            path,
65                            "Expected identifier in tuple",
66                        ));
67                    }
68                } else {
69                    return Err(syn::Error::new_spanned(e, "Expected identifier in tuple"));
70                }
71            }
72            Ok(OutputSpec::TupleFields(fields))
73        }
74        Expr::Path(path) => {
75            if let Some(ident) = path.path.get_ident() {
76                let name_str = ident.to_string();
77                if name_str
78                    .chars()
79                    .next()
80                    .map(|c| c.is_uppercase())
81                    .unwrap_or(false)
82                {
83                    Ok(OutputSpec::Struct)
84                } else {
85                    Ok(OutputSpec::SingleField(ident.clone()))
86                }
87            } else {
88                Err(syn::Error::new_spanned(
89                    path,
90                    "Expected identifier for output",
91                ))
92            }
93        }
94        _ => Err(syn::Error::new_spanned(
95            expr,
96            "Expected identifier or tuple of identifiers for output",
97        )),
98    }
99}
100
101impl Parse for ModuleAttrs {
102    fn parse(input: ParseStream) -> Result<Self> {
103        let metas = Punctuated::<Meta, Token![,]>::parse_terminated(input)?;
104        Self::from_metas(metas)
105    }
106}