Skip to main content

reifydb_macro_impl/
parse.rs

1// SPDX-License-Identifier: MIT
2// Copyright (c) 2026 ReifyDB
3
4use 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}