syn_unnamed_struct/
expr.rs1use proc_macro2::TokenStream;
2use quote::ToTokens;
3use syn::{
4 braced,
5 parse::{Parse, ParseStream},
6 punctuated::Punctuated,
7 token, Expr, Member, Token,
8};
9
10pub 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
53pub 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
89pub 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}