sbpf_assembler/preprocessor/
macro_def.rs1use {
2 super::{SourceLine, source_map::SourceOrigin},
3 crate::errors::CompileError,
4};
5
6#[derive(Debug, Clone)]
8pub struct Param {
9 pub name: String,
10 pub default: Option<String>,
11 pub is_vararg: bool,
12}
13
14#[derive(Debug, Clone)]
16pub struct MacroDef {
17 pub name: String,
18 pub params: Vec<Param>,
19 pub body_lines: Vec<String>,
20 pub defined_at: SourceOrigin,
21}
22
23pub(crate) struct MacroScanResult {
25 pub macros: std::collections::HashMap<String, MacroDef>,
27 pub remaining_lines: Vec<SourceLine>,
29 pub errors: Vec<(CompileError, SourceOrigin)>,
31}
32
33pub(crate) fn scan_macro_definitions(lines: Vec<SourceLine>) -> MacroScanResult {
38 let mut macros = std::collections::HashMap::new();
39 let mut remaining = Vec::new();
40 let mut errors = Vec::new();
41
42 let mut current_macro: Option<(String, Vec<Param>, SourceOrigin, Vec<String>)> = None;
43
44 let mut macro_start_origin: Option<SourceOrigin> = None;
46
47 for line in lines {
48 let trimmed = line.text.trim();
49
50 if let Some(ref mut macro_state) = current_macro {
51 if trimmed == ".endm" {
52 let (name, params, origin, body) = current_macro.take().unwrap();
54 macro_start_origin = None;
55
56 if macros.contains_key(&name) {
57 errors.push((
58 CompileError::DuplicateMacroDef {
59 name: name.clone(),
60 span: 0..0,
61 custom_label: None,
62 },
63 origin,
64 ));
65 } else {
66 macros.insert(
67 name.clone(),
68 MacroDef {
69 name,
70 params,
71 body_lines: body,
72 defined_at: origin,
73 },
74 );
75 }
76 } else {
77 macro_state.3.push(line.text.clone());
79 }
80 } else if let Some((name, params)) = parse_macro_directive(trimmed) {
81 let mut has_errors = false;
83 let mut seen_vararg = false;
84 for (i, param) in params.iter().enumerate() {
85 if param.is_vararg {
86 if seen_vararg {
87 errors.push((
88 CompileError::MultipleVararg {
89 name: name.clone(),
90 span: 0..0,
91 custom_label: None,
92 },
93 line.origin.clone(),
94 ));
95 has_errors = true;
96 break;
97 }
98 if i != params.len() - 1 {
99 errors.push((
100 CompileError::VarargNotLast {
101 name: name.clone(),
102 span: 0..0,
103 custom_label: None,
104 },
105 line.origin.clone(),
106 ));
107 has_errors = true;
108 break;
109 }
110 seen_vararg = true;
111 }
112 }
113 if !has_errors {
114 macro_start_origin = Some(line.origin.clone());
115 current_macro = Some((name, params, line.origin.clone(), Vec::new()));
116 }
117 } else {
118 remaining.push(line);
119 }
120 }
121
122 if let Some((name, _, _, _)) = current_macro {
124 let origin =
125 macro_start_origin.unwrap_or(SourceOrigin::new(super::source_map::FileId(0), 0));
126 errors.push((
127 CompileError::UnclosedMacro {
128 name,
129 span: 0..0,
130 custom_label: None,
131 },
132 origin,
133 ));
134 }
135
136 MacroScanResult {
137 macros,
138 remaining_lines: remaining,
139 errors,
140 }
141}
142
143fn parse_macro_directive(line: &str) -> Option<(String, Vec<Param>)> {
146 let rest = line.strip_prefix(".macro")?;
147
148 if !rest.starts_with(char::is_whitespace) {
150 return None;
151 }
152
153 let rest = rest.trim();
154 if rest.is_empty() {
155 return None;
156 }
157
158 let (name, params_str) = match rest.find(char::is_whitespace) {
160 Some(pos) => (&rest[..pos], rest[pos..].trim()),
161 None => (rest, ""),
162 };
163
164 let params = if params_str.is_empty() {
165 Vec::new()
166 } else {
167 parse_params(params_str)?
168 };
169
170 Some((name.to_string(), params))
171}
172
173fn parse_params(params_str: &str) -> Option<Vec<Param>> {
176 let mut params = Vec::new();
177
178 for part in params_str.split(',') {
179 let part = part.trim();
180 if part.is_empty() {
181 continue;
182 }
183
184 if let Some((name, _)) = part.split_once(":vararg") {
185 let name = name.trim();
186 params.push(Param {
187 name: name.to_string(),
188 default: None,
189 is_vararg: true,
190 });
191 } else if let Some((name, default)) = part.split_once('=') {
192 let name = name.trim();
193 let default = default.trim();
194 params.push(Param {
195 name: name.to_string(),
196 default: Some(default.to_string()),
197 is_vararg: false,
198 });
199 } else {
200 params.push(Param {
201 name: part.to_string(),
202 default: None,
203 is_vararg: false,
204 });
205 }
206 }
207
208 Some(params)
209}
210
211#[cfg(test)]
212mod tests {
213 use {
214 super::*,
215 crate::preprocessor::source_map::{FileId, SourceOrigin},
216 };
217
218 fn make_source_line(text: &str, line: u32) -> SourceLine {
219 SourceLine {
220 text: text.to_string(),
221 origin: SourceOrigin::new(FileId(0), line),
222 }
223 }
224
225 #[test]
226 fn test_parse_macro_directive_simple() {
227 let (name, params) = parse_macro_directive(".macro MY_MACRO").unwrap();
228 assert_eq!(name, "MY_MACRO");
229 assert!(params.is_empty());
230 }
231
232 #[test]
233 fn test_parse_macro_directive_with_params() {
234 let (name, params) = parse_macro_directive(".macro PUSH reg").unwrap();
235 assert_eq!(name, "PUSH");
236 assert_eq!(params.len(), 1);
237 assert_eq!(params[0].name, "reg");
238 assert!(!params[0].is_vararg);
239 assert!(params[0].default.is_none());
240 }
241
242 #[test]
243 fn test_parse_macro_directive_with_defaults() {
244 let (name, params) = parse_macro_directive(".macro FOO a, b=42").unwrap();
245 assert_eq!(name, "FOO");
246 assert_eq!(params.len(), 2);
247 assert_eq!(params[0].name, "a");
248 assert!(params[0].default.is_none());
249 assert_eq!(params[1].name, "b");
250 assert_eq!(params[1].default.as_deref(), Some("42"));
251 }
252
253 #[test]
254 fn test_parse_macro_directive_with_vararg() {
255 let (name, params) = parse_macro_directive(".macro LOG fmt, args:vararg").unwrap();
256 assert_eq!(name, "LOG");
257 assert_eq!(params.len(), 2);
258 assert!(!params[0].is_vararg);
259 assert!(params[1].is_vararg);
260 assert_eq!(params[1].name, "args");
261 }
262
263 #[test]
264 fn test_parse_macro_directive_not_macro() {
265 assert!(parse_macro_directive("mov64 r1, 1").is_none());
266 assert!(parse_macro_directive(".macros FOO").is_none());
267 assert!(parse_macro_directive(".macro").is_none());
268 }
269
270 #[test]
271 fn test_scan_simple_macro() {
272 let lines = vec![
273 make_source_line("before", 1),
274 make_source_line(".macro PUSH reg", 2),
275 make_source_line(" stxdw [r10-8], \\reg", 3),
276 make_source_line(" add64 r10, -8", 4),
277 make_source_line(".endm", 5),
278 make_source_line("after", 6),
279 ];
280
281 let result = scan_macro_definitions(lines);
282 assert!(result.errors.is_empty());
283 assert_eq!(result.macros.len(), 1);
284 assert!(result.macros.contains_key("PUSH"));
285
286 let macro_def = &result.macros["PUSH"];
287 assert_eq!(macro_def.params.len(), 1);
288 assert_eq!(macro_def.params[0].name, "reg");
289 assert_eq!(macro_def.body_lines.len(), 2);
290
291 assert_eq!(result.remaining_lines.len(), 2);
292 assert_eq!(result.remaining_lines[0].text, "before");
293 assert_eq!(result.remaining_lines[1].text, "after");
294 }
295
296 #[test]
297 fn test_scan_unclosed_macro() {
298 let lines = vec![
299 make_source_line(".macro BAD", 1),
300 make_source_line(" body", 2),
301 ];
302
303 let result = scan_macro_definitions(lines);
304 assert_eq!(result.errors.len(), 1);
305 assert!(matches!(
306 &result.errors[0].0,
307 CompileError::UnclosedMacro { name, .. } if name == "BAD"
308 ));
309 assert_eq!(result.errors[0].1.line, 1);
311 }
312
313 #[test]
314 fn test_scan_duplicate_macro() {
315 let lines = vec![
316 make_source_line(".macro FOO", 1),
317 make_source_line(".endm", 2),
318 make_source_line(".macro FOO", 3),
319 make_source_line(".endm", 4),
320 ];
321
322 let result = scan_macro_definitions(lines);
323 assert_eq!(result.errors.len(), 1);
324 assert!(matches!(
325 &result.errors[0].0,
326 CompileError::DuplicateMacroDef { name, .. } if name == "FOO"
327 ));
328 }
329
330 #[test]
331 fn test_scan_vararg_not_last() {
332 let lines = vec![
333 make_source_line(".macro BAD args:vararg, extra", 1),
334 make_source_line(".endm", 2),
335 ];
336
337 let result = scan_macro_definitions(lines);
338 assert_eq!(result.errors.len(), 1);
339 assert!(matches!(
340 &result.errors[0].0,
341 CompileError::VarargNotLast { .. }
342 ));
343 }
344}