Skip to main content

reifydb_macro_impl/
parse.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) 2025 ReifyDB
3
4use proc_macro2::{Delimiter, Group, Ident, TokenStream, TokenTree};
5
6use crate::generate::compile_error;
7
8/// Parsed struct information.
9pub struct ParsedStruct {
10	pub name: Ident,
11	pub fields: Vec<ParsedField>,
12	pub crate_path: String,
13}
14
15/// Parsed field information.
16pub struct ParsedField {
17	pub name: Ident,
18	pub ty: Vec<TokenTree>,
19	pub attrs: FieldAttrs,
20}
21
22/// Field attributes from #[frame(...)].
23#[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
31/// Parse a derive macro input into a ParsedStruct.
32pub fn parse_struct(input: TokenStream) -> Result<ParsedStruct, TokenStream> {
33	parse_struct_with_crate(input, "reifydb_type")
34}
35
36/// Parse a derive macro input into a ParsedStruct with a specific crate path.
37pub 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	// Skip attributes on the struct itself
43	while let Some(TokenTree::Punct(p)) = iter.peek() {
44		if p.as_char() == '#' {
45			iter.next(); // #
46			if let Some(TokenTree::Group(_)) = iter.peek() {
47				iter.next(); // [...]
48			}
49		} else {
50			break;
51		}
52	}
53
54	// Skip visibility (pub, pub(crate), etc.)
55	if let Some(TokenTree::Ident(i)) = iter.peek()
56		&& *i == "pub"
57	{
58		iter.next();
59		// Handle pub(crate), pub(super), etc.
60		if let Some(TokenTree::Group(g)) = iter.peek()
61			&& g.delimiter() == Delimiter::Parenthesis
62		{
63			iter.next();
64		}
65	}
66
67	// Expect "struct"
68	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	// Get struct name
74	let name = match iter.next() {
75		Some(TokenTree::Ident(i)) => i.clone(),
76		_ => return Err(compile_error("expected struct name")),
77	};
78
79	// Find the fields group (skip generics if present)
80	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				// Skip generics - for now we don't support generic structs
87				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				// Skip where clause until we hit the brace
101				continue;
102			}
103			None => return Err(compile_error("expected struct body")),
104			_ => continue,
105		}
106	};
107
108	// Parse fields
109	let fields = parse_fields(fields_group)?;
110
111	// Check for duplicate column aliases
112	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
139/// Parse the fields from a struct body group.
140fn 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		// Collect attributes
147		let mut attrs_tokens = Vec::new();
148		while let Some(TokenTree::Punct(p)) = iter.peek() {
149			if p.as_char() == '#' {
150				iter.next(); // #
151				if let Some(TokenTree::Group(g)) = iter.next() {
152					attrs_tokens.push(g.clone());
153				}
154			} else {
155				break;
156			}
157		}
158
159		// Skip visibility
160		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		// Get field name
172		let field_name = match iter.next() {
173			Some(TokenTree::Ident(i)) => i.clone(),
174			None => break, // End of fields
175			_ => continue, // Skip unexpected tokens
176		};
177
178		// Expect colon
179		match iter.next() {
180			Some(TokenTree::Punct(p)) if p.as_char() == ':' => {}
181			_ => return Err(compile_error("expected ':' after field name")),
182		}
183
184		// Collect type tokens until comma or end
185		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(); // consume comma
191					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
225/// Parse #[frame(...)] attributes from a list of attribute groups.
226fn 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		// Check if this is a #[frame(...)] attribute
234		match iter.next() {
235			Some(TokenTree::Ident(i)) if *i == "frame" => {}
236			_ => continue,
237		}
238
239		// Get the inner group
240		let inner = match iter.next() {
241			Some(TokenTree::Group(g)) if g.delimiter() == Delimiter::Parenthesis => g,
242			_ => continue,
243		};
244
245		// Parse inner tokens
246		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			// Get attribute name
251			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					// Expect = "value"
259					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						// Remove quotes
265						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			// Skip comma if present
277			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	/// Get the column name, using the field name if not explicitly set.
290	/// Strips r# prefix from raw identifiers.
291	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	/// Get a safe variable name (strips r# prefix from raw identifiers).
300	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}