reifydb_macro_impl/
parse.rs1use proc_macro2::{Delimiter, Group, Ident, TokenStream, TokenTree};
5
6use crate::generate::compile_error;
7
8pub struct ParsedStruct {
9 pub name: Ident,
10 pub fields: Vec<ParsedField>,
11 pub crate_path: String,
12}
13
14pub struct ParsedField {
15 pub name: Ident,
16 pub ty: Vec<TokenTree>,
17 pub attrs: FieldAttrs,
18}
19
20#[derive(Default)]
21pub struct FieldAttrs {
22 pub column_name: Option<String>,
23 pub optional: bool,
24 pub coerce: bool,
25 pub skip: bool,
26}
27
28pub fn parse_struct(input: TokenStream) -> Result<ParsedStruct, TokenStream> {
29 parse_struct_with_crate(input, "reifydb_value")
30}
31
32pub fn parse_struct_with_crate(input: TokenStream, crate_path: &str) -> Result<ParsedStruct, TokenStream> {
33 let tokens: Vec<TokenTree> = input.into_iter().collect();
34 let mut iter = tokens.iter().peekable();
35 let crate_path = crate_path.to_string();
36
37 while let Some(TokenTree::Punct(p)) = iter.peek() {
38 if p.as_char() == '#' {
39 iter.next();
40 if let Some(TokenTree::Group(_)) = iter.peek() {
41 iter.next();
42 }
43 } else {
44 break;
45 }
46 }
47
48 if let Some(TokenTree::Ident(i)) = iter.peek()
49 && *i == "pub"
50 {
51 iter.next();
52
53 if let Some(TokenTree::Group(g)) = iter.peek()
54 && g.delimiter() == Delimiter::Parenthesis
55 {
56 iter.next();
57 }
58 }
59
60 match iter.next() {
61 Some(TokenTree::Ident(i)) if *i == "struct" => {}
62 _ => return Err(compile_error("FromFrame can only be derived for structs")),
63 }
64
65 let name = match iter.next() {
66 Some(TokenTree::Ident(i)) => i.clone(),
67 _ => return Err(compile_error("expected struct name")),
68 };
69
70 let fields_group = loop {
71 match iter.next() {
72 Some(TokenTree::Group(g)) if g.delimiter() == Delimiter::Brace => {
73 break g.clone();
74 }
75 Some(TokenTree::Punct(p)) if p.as_char() == '<' => {
76 let mut depth = 1;
77 while depth > 0 {
78 match iter.next() {
79 Some(TokenTree::Punct(p)) if p.as_char() == '<' => depth += 1,
80 Some(TokenTree::Punct(p)) if p.as_char() == '>' => depth -= 1,
81 None => {
82 return Err(compile_error("unclosed generic parameters"));
83 }
84 _ => {}
85 }
86 }
87 }
88 Some(TokenTree::Ident(i)) if *i == "where" => {
89 continue;
90 }
91 None => return Err(compile_error("expected struct body")),
92 _ => continue,
93 }
94 };
95
96 let fields = parse_fields(fields_group)?;
97
98 for (i, field) in fields.iter().enumerate() {
99 if field.attrs.skip {
100 continue;
101 }
102 let col_name = field.column_name();
103 for other in fields.iter().skip(i + 1) {
104 if other.attrs.skip {
105 continue;
106 }
107 if other.column_name() == col_name {
108 return Err(compile_error(&format!(
109 "duplicate column alias '{}': used by both '{}' and '{}'",
110 col_name,
111 field.safe_name(),
112 other.safe_name()
113 )));
114 }
115 }
116 }
117
118 Ok(ParsedStruct {
119 name,
120 fields,
121 crate_path,
122 })
123}
124
125fn parse_fields(group: Group) -> Result<Vec<ParsedField>, TokenStream> {
126 let tokens: Vec<TokenTree> = group.stream().into_iter().collect();
127 let mut fields = Vec::new();
128 let mut iter = tokens.iter().peekable();
129
130 while iter.peek().is_some() {
131 let mut attrs_tokens = Vec::new();
132 while let Some(TokenTree::Punct(p)) = iter.peek() {
133 if p.as_char() == '#' {
134 iter.next();
135 if let Some(TokenTree::Group(g)) = iter.next() {
136 attrs_tokens.push(g.clone());
137 }
138 } else {
139 break;
140 }
141 }
142
143 if let Some(TokenTree::Ident(i)) = iter.peek()
144 && *i == "pub"
145 {
146 iter.next();
147 if let Some(TokenTree::Group(g)) = iter.peek()
148 && g.delimiter() == Delimiter::Parenthesis
149 {
150 iter.next();
151 }
152 }
153
154 let field_name = match iter.next() {
155 Some(TokenTree::Ident(i)) => i.clone(),
156 None => break,
157 _ => continue,
158 };
159
160 match iter.next() {
161 Some(TokenTree::Punct(p)) if p.as_char() == ':' => {}
162 _ => return Err(compile_error("expected ':' after field name")),
163 }
164
165 let mut ty_tokens = Vec::new();
166 let mut depth = 0;
167 loop {
168 match iter.peek() {
169 Some(TokenTree::Punct(p)) if p.as_char() == ',' && depth == 0 => {
170 iter.next();
171 break;
172 }
173 Some(TokenTree::Punct(p)) if p.as_char() == '<' => {
174 depth += 1;
175 ty_tokens.push(iter.next().unwrap().clone());
176 }
177 Some(TokenTree::Punct(p)) if p.as_char() == '>' => {
178 depth -= 1;
179 ty_tokens.push(iter.next().unwrap().clone());
180 }
181 Some(t) => {
182 ty_tokens.push((*t).clone());
183 iter.next();
184 }
185 None => break,
186 }
187 }
188
189 if ty_tokens.is_empty() {
190 return Err(compile_error("expected field type"));
191 }
192
193 let attrs = parse_field_attrs(&attrs_tokens);
194
195 fields.push(ParsedField {
196 name: field_name,
197 ty: ty_tokens,
198 attrs,
199 });
200 }
201
202 Ok(fields)
203}
204
205fn parse_field_attrs(attr_groups: &[Group]) -> FieldAttrs {
206 let mut result = FieldAttrs::default();
207
208 for group in attr_groups {
209 let tokens: Vec<TokenTree> = group.stream().into_iter().collect();
210 let mut iter = tokens.iter().peekable();
211
212 match iter.next() {
213 Some(TokenTree::Ident(i)) if *i == "frame" => {}
214 _ => continue,
215 }
216
217 let inner = match iter.next() {
218 Some(TokenTree::Group(g)) if g.delimiter() == Delimiter::Parenthesis => g,
219 _ => continue,
220 };
221
222 let inner_tokens: Vec<TokenTree> = inner.stream().into_iter().collect();
223 let mut inner_iter = inner_tokens.iter().peekable();
224
225 while inner_iter.peek().is_some() {
226 let attr_name = match inner_iter.next() {
227 Some(TokenTree::Ident(i)) => i.to_string(),
228 _ => continue,
229 };
230
231 match attr_name.as_str() {
232 "column" => {
233 if let Some(TokenTree::Punct(p)) = inner_iter.next()
234 && p.as_char() == '=' && let Some(TokenTree::Literal(lit)) =
235 inner_iter.next()
236 {
237 let s = lit.to_string();
238
239 if s.starts_with('"') && s.ends_with('"') {
240 result.column_name = Some(s[1..s.len() - 1].to_string());
241 }
242 }
243 }
244 "optional" => result.optional = true,
245 "coerce" => result.coerce = true,
246 "skip" => result.skip = true,
247 _ => {}
248 }
249
250 if let Some(TokenTree::Punct(p)) = inner_iter.peek()
251 && p.as_char() == ','
252 {
253 inner_iter.next();
254 }
255 }
256 }
257
258 result
259}
260
261impl ParsedField {
262 pub fn column_name(&self) -> String {
263 if let Some(ref name) = self.attrs.column_name {
264 name.clone()
265 } else {
266 self.safe_name()
267 }
268 }
269
270 pub fn safe_name(&self) -> String {
271 let name = self.name.to_string();
272 name.strip_prefix("r#").unwrap_or(&name).to_string()
273 }
274}