syn_unnamed_struct/
expr.rs

1use proc_macro2::TokenStream;
2use quote::ToTokens;
3use syn::{
4    braced,
5    parse::{Parse, ParseStream},
6    punctuated::Punctuated,
7    token, Expr, Member, Token,
8};
9
10//essentially a syn::ExprStruct but with no name, attrs or rest
11pub struct ExprUnnamedStruct<T: Parse + ToTokens> {
12    pub brace_token: token::Brace,
13    pub fields: Punctuated<FieldValue<T>, Token![,]>,
14}
15
16impl<T: Parse + ToTokens> Parse for ExprUnnamedStruct<T> {
17    fn parse(input: ParseStream) -> syn::Result<Self> {
18        let content;
19        let brace_token = braced!(content in input);
20
21        let mut fields = Punctuated::new();
22        while !content.is_empty() {
23            if content.peek(Token![..]) {
24                return Ok(ExprUnnamedStruct {
25                    brace_token,
26                    fields,
27                });
28            }
29
30            fields.push(content.parse()?);
31            if content.is_empty() {
32                break;
33            }
34            let punct: Token![,] = content.parse()?;
35            fields.push_punct(punct);
36        }
37
38        Ok(ExprUnnamedStruct {
39            brace_token,
40            fields,
41        })
42    }
43}
44
45impl<T: Parse + ToTokens> ToTokens for ExprUnnamedStruct<T> {
46    fn to_tokens(&self, tokens: &mut TokenStream) {
47        self.brace_token.surround(tokens, |tokens| {
48            self.fields.to_tokens(tokens);
49        });
50    }
51}
52
53//change FieldValue too so we can extend the field values with other structures
54pub struct FieldValue<T: Parse + ToTokens> {
55    pub member: Member,
56    pub colon_token: Option<Token![:]>,
57    pub expr: T,
58}
59
60impl<T: Parse + ToTokens> Parse for FieldValue<T> {
61    fn parse(input: ParseStream) -> syn::Result<Self> {
62        let member: Member = input.parse()?;
63
64        if input.peek(Token![:]) || !matches!(member, Member::Named(_)) {
65            let colon_token: Token![:] = input.parse()?;
66            let value: T = input.parse()?;
67
68            Ok(FieldValue {
69                member,
70                colon_token: Some(colon_token),
71                expr: value,
72            })
73        } else {
74            unreachable!()
75        }
76    }
77}
78
79impl<T: Parse + ToTokens> ToTokens for FieldValue<T> {
80    fn to_tokens(&self, tokens: &mut TokenStream) {
81        self.member.to_tokens(tokens);
82        if let Some(colon_token) = &self.colon_token {
83            colon_token.to_tokens(tokens);
84            self.expr.to_tokens(tokens);
85        }
86    }
87}
88
89//our own enumeration of possible values for the field values
90pub enum CustomExpr {
91    Expr(Box<Expr>),
92    ExprUnnamedStruct(ExprUnnamedStruct<CustomExpr>),
93}
94
95impl Parse for CustomExpr {
96    fn parse(input: ParseStream) -> syn::Result<Self> {
97        if input.peek(token::Brace) {
98            let obj = input.parse::<ExprUnnamedStruct<CustomExpr>>()?;
99            Ok(CustomExpr::ExprUnnamedStruct(obj))
100        } else {
101            let expr = input.parse::<Expr>()?;
102            Ok(CustomExpr::Expr(Box::new(expr)))
103        }
104    }
105}
106
107impl ToTokens for CustomExpr {
108    fn to_tokens(&self, tokens: &mut TokenStream) {
109        match self {
110            CustomExpr::Expr(value) => value.to_tokens(tokens),
111            CustomExpr::ExprUnnamedStruct(value) => value.to_tokens(tokens),
112        }
113    }
114}
115
116#[cfg(test)]
117mod tests {
118    use super::*;
119    use syn::{parse::Parser, Attribute};
120
121    #[test]
122    fn test_attribute() {
123        let attrs = Parser::parse_str(
124            Attribute::parse_outer,
125            "#[blah({ name: \"MyVal\", age: 33, props: [1,2,3]})]",
126        )
127        .expect("attribute");
128        let elem = attrs
129            .first()
130            .unwrap()
131            .parse_args::<ExprUnnamedStruct<CustomExpr>>()
132            .expect("Could not parse the args");
133        let elem_str = elem.to_token_stream().to_string();
134
135        assert_eq!(
136            elem_str,
137            "{ name : \"MyVal\" , age : 33 , props : [1 , 2 , 3] }"
138        );
139    }
140
141    #[test]
142    fn test_attribute_nested() {
143        let attrs = Parser::parse_str(
144            Attribute::parse_outer,
145            "#[blah({ name: \"MyVal\", age: 33, props: [1,2,3], other: { name: \"ok\" }})]",
146        )
147        .expect("attribute");
148        let elem = attrs
149            .first()
150            .unwrap()
151            .parse_args::<ExprUnnamedStruct<CustomExpr>>()
152            .expect("Could not parse the args");
153        let elem_str = elem.to_token_stream().to_string();
154
155        assert_eq!(
156            elem_str,
157            "{ name : \"MyVal\" , age : 33 , props : [1 , 2 , 3] , other : { name : \"ok\" } }"
158        );
159    }
160}