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