1use {
2 super::{
3 SourceLine,
4 macro_def::{MacroDef, scan_macro_definitions},
5 source_map::SourceOrigin,
6 },
7 crate::errors::CompileError,
8 std::{
9 collections::HashMap,
10 sync::atomic::{AtomicU64, Ordering},
11 },
12};
13
14const MAX_EXPANSION_DEPTH: u32 = 100;
15
16static EXPANSION_COUNTER: AtomicU64 = AtomicU64::new(0);
18
19#[cfg(test)]
21pub(crate) fn reset_expansion_counter() {
22 EXPANSION_COUNTER.store(0, Ordering::Relaxed);
23}
24
25#[derive(Debug)]
27pub(crate) struct ExpandError {
28 pub error: CompileError,
29 pub origin: Option<SourceOrigin>,
30}
31
32pub(crate) fn expand_macros(
36 lines: Vec<SourceLine>,
37) -> Result<(Vec<SourceLine>, Vec<ExpandError>), Vec<ExpandError>> {
38 let scan_result = scan_macro_definitions(lines);
40
41 if !scan_result.errors.is_empty() {
42 return Err(scan_result
43 .errors
44 .into_iter()
45 .map(|(error, origin)| ExpandError {
46 error,
47 origin: Some(origin),
48 })
49 .collect());
50 }
51
52 let macros = scan_result.macros;
53 let mut errors = Vec::new();
54 let mut output = Vec::new();
55
56 for line in scan_result.remaining_lines {
58 expand_line(&line, ¯os, &mut output, &mut errors, 0);
59 }
60
61 let output = expand_repetitions(output)?;
63
64 Ok((output, errors))
65}
66
67fn expand_line(
69 line: &SourceLine,
70 macros: &HashMap<String, MacroDef>,
71 output: &mut Vec<SourceLine>,
72 errors: &mut Vec<ExpandError>,
73 depth: u32,
74) {
75 if depth > MAX_EXPANSION_DEPTH {
76 errors.push(ExpandError {
77 error: CompileError::MacroRecursionLimit {
78 limit: MAX_EXPANSION_DEPTH,
79 span: 0..0,
80 custom_label: None,
81 },
82 origin: Some(line.origin.clone()),
83 });
84 return;
85 }
86
87 let trimmed = line.text.trim();
88
89 let first_token = first_token(trimmed);
91 if let Some(macro_def) = first_token.and_then(|name| macros.get(name)) {
92 let args_str = trimmed[macro_def.name.len()..].trim();
93 let args = split_args(args_str);
94
95 match bind_args(macro_def, &args) {
96 Ok(bindings) => {
97 let expansion_id = EXPANSION_COUNTER.fetch_add(1, Ordering::Relaxed);
98
99 for body_line in ¯o_def.body_lines {
101 let expanded_text = substitute(body_line, &bindings, expansion_id);
102 let expanded_line = SourceLine {
103 text: expanded_text,
104 origin: SourceOrigin::with_macro_expansion(
105 macro_def.defined_at.file_id,
106 macro_def.defined_at.line,
107 macro_def.name.clone(),
108 line.origin.clone(),
109 depth + 1,
110 ),
111 };
112
113 expand_line(&expanded_line, macros, output, errors, depth + 1);
115 }
116 }
117 Err(e) => errors.push(ExpandError {
118 error: e,
119 origin: Some(line.origin.clone()),
120 }),
121 }
122 } else {
123 output.push(line.clone());
125 }
126}
127
128fn first_token(line: &str) -> Option<&str> {
131 let trimmed = line.trim();
132 if trimmed.is_empty() || trimmed.starts_with('#') || trimmed.starts_with(';') {
133 return None;
134 }
135
136 if trimmed.starts_with('.') {
138 return None;
139 }
140
141 let end = trimmed
142 .find(|c: char| c.is_whitespace() || c == ',')
143 .unwrap_or(trimmed.len());
144 let token = &trimmed[..end];
145
146 if token.ends_with(':') {
148 let rest = trimmed[end..].trim();
150 if rest.is_empty() {
151 return None;
152 }
153 return first_token(rest);
154 }
155
156 Some(token)
157}
158
159fn split_args(args_str: &str) -> Vec<String> {
161 if args_str.is_empty() {
162 return Vec::new();
163 }
164
165 let mut args = Vec::new();
166 let mut current = String::new();
167 let mut depth = 0i32; let mut in_string = false;
169 let mut escape_next = false;
170
171 for ch in args_str.chars() {
172 if escape_next {
173 current.push(ch);
174 escape_next = false;
175 continue;
176 }
177
178 match ch {
179 '\\' if in_string => {
180 current.push(ch);
181 escape_next = true;
182 }
183 '"' => {
184 in_string = !in_string;
185 current.push(ch);
186 }
187 '(' | '[' if !in_string => {
188 depth += 1;
189 current.push(ch);
190 }
191 ')' | ']' if !in_string => {
192 depth -= 1;
193 current.push(ch);
194 }
195 ',' if !in_string && depth == 0 => {
196 args.push(current.trim().to_string());
197 current = String::new();
198 }
199 _ => {
200 current.push(ch);
201 }
202 }
203 }
204
205 let last = current.trim().to_string();
206 if !last.is_empty() {
207 args.push(last);
208 }
209
210 args
211}
212
213fn bind_args(
215 macro_def: &MacroDef,
216 args: &[String],
217) -> Result<HashMap<String, String>, CompileError> {
218 let mut bindings = HashMap::new();
219
220 let required_count = macro_def
221 .params
222 .iter()
223 .filter(|p| p.default.is_none() && !p.is_vararg)
224 .count();
225
226 let has_vararg = macro_def.params.iter().any(|p| p.is_vararg);
227
228 if !has_vararg && args.len() > macro_def.params.len() {
230 return Err(CompileError::MacroArgCount {
231 name: macro_def.name.clone(),
232 expected: macro_def.params.len(),
233 got: args.len(),
234 span: 0..0,
235 custom_label: None,
236 });
237 }
238 if args.len() < required_count {
239 return Err(CompileError::MacroArgCount {
240 name: macro_def.name.clone(),
241 expected: required_count,
242 got: args.len(),
243 span: 0..0,
244 custom_label: None,
245 });
246 }
247
248 for (i, param) in macro_def.params.iter().enumerate() {
249 if param.is_vararg {
250 let vararg_values: Vec<&str> = args[i..].iter().map(|s| s.as_str()).collect();
252 bindings.insert(param.name.clone(), vararg_values.join(", "));
253 } else if i < args.len() {
254 bindings.insert(param.name.clone(), args[i].clone());
255 } else if let Some(ref default) = param.default {
256 bindings.insert(param.name.clone(), default.clone());
257 }
258 }
260
261 for (i, arg) in args.iter().enumerate() {
263 bindings.insert((i + 1).to_string(), arg.clone());
264 }
265
266 Ok(bindings)
267}
268
269fn substitute(line: &str, bindings: &HashMap<String, String>, expansion_id: u64) -> String {
277 let mut result = String::with_capacity(line.len());
278 let chars: Vec<char> = line.chars().collect();
279 let len = chars.len();
280 let mut i = 0;
281
282 while i < len {
283 if chars[i] == '\\' && i + 1 < len {
284 let next = chars[i + 1];
285
286 if next == '\\' {
287 result.push('\\');
289 i += 2;
290 } else if next == '@' {
291 result.push_str(&expansion_id.to_string());
293 i += 2;
294 } else if next == '(' && i + 2 < len && chars[i + 2] == ')' {
295 i += 3;
297 } else if next.is_alphanumeric() || next == '_' {
298 let start = i + 1;
300 let mut end = start;
301 while end < len && (chars[end].is_alphanumeric() || chars[end] == '_') {
302 end += 1;
303 }
304 let param_name: String = chars[start..end].iter().collect();
305
306 if let Some(value) = bindings.get(¶m_name) {
307 result.push_str(value);
308 if end + 2 < len
310 && chars[end] == '\\'
311 && chars[end + 1] == '('
312 && chars[end + 2] == ')'
313 {
314 end += 3;
316 }
317 } else {
318 result.push('\\');
320 result.push_str(¶m_name);
321 }
322 i = end;
323 } else {
324 result.push('\\');
326 i += 1;
327 }
328 } else {
329 result.push(chars[i]);
330 i += 1;
331 }
332 }
333
334 result
335}
336
337fn expand_repetitions(lines: Vec<SourceLine>) -> Result<Vec<SourceLine>, Vec<ExpandError>> {
339 let mut output = Vec::new();
340 let mut errors = Vec::new();
341 let mut i = 0;
342
343 while i < lines.len() {
344 let trimmed = lines[i].text.trim();
345
346 if let Some(count_str) = trimmed.strip_prefix(".rept").and_then(|r| {
347 if r.starts_with(char::is_whitespace) {
348 Some(r.trim())
349 } else {
350 None
351 }
352 }) {
353 let (body, end_idx) = find_endr_block(&lines, i + 1, &lines[i].origin)?;
355
356 match count_str.parse::<usize>() {
357 Ok(count) => {
358 let expanded_body = expand_repetitions(body)?;
360 for _ in 0..count {
361 output.extend(expanded_body.iter().cloned());
362 }
363 }
364 Err(_) => {
365 errors.push(ExpandError {
366 error: CompileError::InvalidReptCount {
367 value: count_str.to_string(),
368 span: 0..0,
369 custom_label: None,
370 },
371 origin: Some(lines[i].origin.clone()),
372 });
373 }
374 }
375
376 i = end_idx + 1;
377 } else if let Some(rest) = trimmed.strip_prefix(".irp").and_then(|r| {
378 if r.starts_with(char::is_whitespace) {
379 Some(r.trim())
380 } else {
381 None
382 }
383 }) {
384 let (body, end_idx) = find_endr_block(&lines, i + 1, &lines[i].origin)?;
386
387 if let Some((var_name, values_str)) = rest.split_once(',') {
388 let var_name = var_name.trim();
389 let values = split_args(values_str.trim());
390
391 for value in &values {
392 let mut bindings = HashMap::new();
393 bindings.insert(var_name.to_string(), value.clone());
394
395 let mut iter_lines = Vec::with_capacity(body.len());
398 for body_line in &body {
399 let expansion_id = EXPANSION_COUNTER.fetch_add(1, Ordering::Relaxed);
400 let expanded_text = substitute(&body_line.text, &bindings, expansion_id);
401 iter_lines.push(SourceLine {
402 text: expanded_text,
403 origin: body_line.origin.clone(),
404 });
405 }
406 output.extend(expand_repetitions(iter_lines)?);
407 }
408 }
409
410 i = end_idx + 1;
411 } else {
412 output.push(lines[i].clone());
413 i += 1;
414 }
415 }
416
417 if errors.is_empty() {
418 Ok(output)
419 } else {
420 Err(errors)
421 }
422}
423
424fn find_endr_block(
426 lines: &[SourceLine],
427 start: usize,
428 directive_origin: &SourceOrigin,
429) -> Result<(Vec<SourceLine>, usize), Vec<ExpandError>> {
430 let mut depth = 1u32;
431 let mut i = start;
432
433 while i < lines.len() {
434 let trimmed = lines[i].text.trim();
435 if trimmed == ".endr" {
436 depth -= 1;
437 if depth == 0 {
438 let body = lines[start..i].to_vec();
439 return Ok((body, i));
440 }
441 } else if trimmed.starts_with(".rept") || trimmed.starts_with(".irp") {
442 depth += 1;
443 }
444 i += 1;
445 }
446
447 Err(vec![ExpandError {
448 error: CompileError::UnclosedRept {
449 span: 0..0,
450 custom_label: None,
451 },
452 origin: Some(directive_origin.clone()),
453 }])
454}
455
456#[cfg(test)]
457mod tests {
458 use {super::*, crate::preprocessor::source_map::FileId};
459
460 fn make_line(text: &str, line: u32) -> SourceLine {
461 SourceLine {
462 text: text.to_string(),
463 origin: SourceOrigin::new(FileId(0), line),
464 }
465 }
466
467 fn expand_and_collect(lines: Vec<SourceLine>) -> Vec<String> {
468 let (result, errors) = expand_macros(lines).unwrap();
469 assert!(errors.is_empty(), "Unexpected errors: {:?}", errors);
470 result.iter().map(|l| l.text.clone()).collect()
471 }
472
473 #[test]
474 fn test_first_token() {
475 assert_eq!(first_token("MY_MACRO arg1, arg2"), Some("MY_MACRO"));
476 assert_eq!(first_token(" MY_MACRO "), Some("MY_MACRO"));
477 assert_eq!(first_token(".globl entry"), None); assert_eq!(first_token("# comment"), None);
479 assert_eq!(first_token(""), None);
480 assert_eq!(first_token("label:"), None);
481 assert_eq!(first_token("label: MY_MACRO"), Some("MY_MACRO"));
482 }
483
484 #[test]
485 fn test_split_args() {
486 assert_eq!(split_args("a, b, c"), vec!["a", "b", "c"]);
487 assert_eq!(split_args("r1, 42"), vec!["r1", "42"]);
488 assert_eq!(
489 split_args("\"hello, world\", 1"),
490 vec!["\"hello, world\"", "1"]
491 );
492 assert_eq!(split_args(""), Vec::<String>::new());
493 assert_eq!(split_args("single"), vec!["single"]);
494 }
495
496 #[test]
497 fn test_substitute_simple() {
498 let mut bindings = HashMap::new();
499 bindings.insert("reg".to_string(), "r1".to_string());
500 bindings.insert("val".to_string(), "42".to_string());
501
502 assert_eq!(
503 substitute(" mov64 \\reg, \\val", &bindings, 0),
504 " mov64 r1, 42"
505 );
506 }
507
508 #[test]
509 fn test_substitute_unique_id() {
510 let bindings = HashMap::new();
511 assert_eq!(substitute("label_\\@:", &bindings, 5), "label_5:");
512 }
513
514 #[test]
515 fn test_substitute_concatenation() {
516 let mut bindings = HashMap::new();
517 bindings.insert("msg".to_string(), "e1".to_string());
518
519 assert_eq!(substitute("\\msg\\()_end", &bindings, 0), "e1_end");
520 }
521
522 #[test]
523 fn test_substitute_escaped_backslash() {
524 let bindings = HashMap::new();
525 assert_eq!(substitute("\\\\n", &bindings, 0), "\\n");
526 }
527
528 #[test]
529 fn test_simple_macro_expansion() {
530 reset_expansion_counter();
531 let lines = vec![
532 make_line(".macro NOP", 1),
533 make_line(" mov64 r0, 0", 2),
534 make_line(".endm", 3),
535 make_line("NOP", 4),
536 ];
537
538 let result = expand_and_collect(lines);
539 assert_eq!(result, vec![" mov64 r0, 0"]);
540 }
541
542 #[test]
543 fn test_macro_with_args() {
544 reset_expansion_counter();
545 let lines = vec![
546 make_line(".macro MOV dst, src", 1),
547 make_line(" mov64 \\dst, \\src", 2),
548 make_line(".endm", 3),
549 make_line("MOV r1, r2", 4),
550 ];
551
552 let result = expand_and_collect(lines);
553 assert_eq!(result, vec![" mov64 r1, r2"]);
554 }
555
556 #[test]
557 fn test_macro_with_default() {
558 reset_expansion_counter();
559 let lines = vec![
560 make_line(".macro SET reg, val=0", 1),
561 make_line(" mov64 \\reg, \\val", 2),
562 make_line(".endm", 3),
563 make_line("SET r1", 4),
564 make_line("SET r2, 42", 5),
565 ];
566
567 let result = expand_and_collect(lines);
568 assert_eq!(result, vec![" mov64 r1, 0", " mov64 r2, 42"]);
569 }
570
571 #[test]
572 fn test_macro_unique_id() {
573 reset_expansion_counter();
574 let lines = vec![
575 make_line(".macro LOOP", 1),
576 make_line("loop_\\@:", 2),
577 make_line(".endm", 3),
578 make_line("LOOP", 4),
579 make_line("LOOP", 5),
580 ];
581
582 let result = expand_and_collect(lines);
583 assert_eq!(result.len(), 2);
584 assert!(result.iter().all(|s| {
585 s.starts_with("loop_")
586 && s.ends_with(':')
587 && s["loop_".len()..s.len() - 1].parse::<u64>().is_ok()
588 }));
589 assert_ne!(result[0], result[1]);
590 }
591
592 #[test]
593 fn test_macro_concatenation() {
594 reset_expansion_counter();
595 let lines = vec![
596 make_line(".macro DEF_STR name, text", 1),
597 make_line("\\name:", 2),
598 make_line(" .ascii \\text", 3),
599 make_line("\\name\\()_end:", 4),
600 make_line(".endm", 5),
601 make_line("DEF_STR e1, \"error\"", 6),
602 ];
603
604 let result = expand_and_collect(lines);
605 assert_eq!(result, vec!["e1:", " .ascii \"error\"", "e1_end:"]);
606 }
607
608 #[test]
609 fn test_nested_macro_expansion() {
610 reset_expansion_counter();
611 let lines = vec![
612 make_line(".macro INNER val", 1),
613 make_line(" mov64 r0, \\val", 2),
614 make_line(".endm", 3),
615 make_line(".macro OUTER val", 4),
616 make_line("INNER \\val", 5),
617 make_line(".endm", 6),
618 make_line("OUTER 42", 7),
619 ];
620
621 let result = expand_and_collect(lines);
622 assert_eq!(result, vec![" mov64 r0, 42"]);
623 }
624
625 #[test]
626 fn test_recursion_limit() {
627 let lines = vec![
628 make_line(".macro FOREVER", 1),
629 make_line("FOREVER", 2),
630 make_line(".endm", 3),
631 make_line("FOREVER", 4),
632 ];
633
634 let (_, errors) = expand_macros(lines).unwrap();
635 assert!(!errors.is_empty());
636 assert!(matches!(
637 &errors[0].error,
638 CompileError::MacroRecursionLimit { .. }
639 ));
640 assert!(errors[0].origin.is_some());
642 }
643
644 #[test]
645 fn test_wrong_arg_count() {
646 let lines = vec![
647 make_line(".macro NEED_TWO a, b", 1),
648 make_line(" mov64 \\a, \\b", 2),
649 make_line(".endm", 3),
650 make_line("NEED_TWO r1", 4),
651 ];
652
653 let (_, errors) = expand_macros(lines).unwrap();
654 assert!(!errors.is_empty());
655 assert!(matches!(
656 &errors[0].error,
657 CompileError::MacroArgCount {
658 expected: 2,
659 got: 1,
660 ..
661 }
662 ));
663 assert!(errors[0].origin.is_some());
664 assert_eq!(errors[0].origin.as_ref().unwrap().line, 4);
665 }
666
667 #[test]
668 fn test_vararg() {
669 reset_expansion_counter();
670 let lines = vec![
671 make_line(".macro LOG fmt, args:vararg", 1),
672 make_line(" .ascii \\fmt", 2),
673 make_line(" .ascii \\args", 3),
674 make_line(".endm", 4),
675 make_line("LOG \"hello\", a, b, c", 5),
676 ];
677
678 let result = expand_and_collect(lines);
679 assert_eq!(result, vec![" .ascii \"hello\"", " .ascii a, b, c"]);
680 }
681
682 #[test]
683 fn test_rept() {
684 let lines = vec![
685 make_line(".rept 3", 1),
686 make_line(" nop", 2),
687 make_line(".endr", 3),
688 ];
689
690 let result = expand_and_collect(lines);
691 assert_eq!(result, vec![" nop", " nop", " nop"]);
692 }
693
694 #[test]
695 fn test_rept_zero() {
696 let lines = vec![
697 make_line(".rept 0", 1),
698 make_line(" nop", 2),
699 make_line(".endr", 3),
700 ];
701
702 let result = expand_and_collect(lines);
703 assert!(result.is_empty());
704 }
705
706 #[test]
707 fn test_irp() {
708 reset_expansion_counter();
709 let lines = vec![
710 make_line(".irp reg, r1, r2, r3", 1),
711 make_line(" mov64 \\reg, 0", 2),
712 make_line(".endr", 3),
713 ];
714
715 let result = expand_and_collect(lines);
716 assert_eq!(
717 result,
718 vec![" mov64 r1, 0", " mov64 r2, 0", " mov64 r3, 0"]
719 );
720 }
721
722 #[test]
723 fn test_nested_rept() {
724 let lines = vec![
725 make_line(".rept 2", 1),
726 make_line(" .rept 3", 2),
727 make_line(" mov64 r1, 0x1", 3),
728 make_line(" .endr", 4),
729 make_line(".endr", 5),
730 ];
731
732 let result = expand_and_collect(lines);
733 assert_eq!(result.len(), 6);
734 assert!(result.iter().all(|s| s == " mov64 r1, 0x1"));
735 }
736
737 #[test]
738 fn test_nested_irp() {
739 reset_expansion_counter();
740 let lines = vec![
741 make_line(".irp reg, r1, r2", 1),
742 make_line(" .irp val, 0x1, 0x2", 2),
743 make_line(" mov64 \\reg, \\val", 3),
744 make_line(" .endr", 4),
745 make_line(".endr", 5),
746 ];
747
748 let result = expand_and_collect(lines);
749 assert_eq!(
750 result,
751 vec![
752 " mov64 r1, 0x1",
753 " mov64 r1, 0x2",
754 " mov64 r2, 0x1",
755 " mov64 r2, 0x2",
756 ]
757 );
758 }
759
760 #[test]
761 fn test_rept_inside_irp() {
762 reset_expansion_counter();
763 let lines = vec![
764 make_line(".irp r, r1, r2", 1),
765 make_line(" .rept 2", 2),
766 make_line(" mov64 \\r, 0x1", 3),
767 make_line(" .endr", 4),
768 make_line(".endr", 5),
769 ];
770
771 let result = expand_and_collect(lines);
772 assert_eq!(
773 result,
774 vec![
775 " mov64 r1, 0x1",
776 " mov64 r1, 0x1",
777 " mov64 r2, 0x1",
778 " mov64 r2, 0x1",
779 ]
780 );
781 }
782
783 #[test]
784 fn test_irp_inside_rept() {
785 reset_expansion_counter();
786 let lines = vec![
787 make_line(".rept 2", 1),
788 make_line(" .irp r, r1, r2", 2),
789 make_line(" mov64 \\r, 0x1", 3),
790 make_line(" .endr", 4),
791 make_line(".endr", 5),
792 ];
793
794 let result = expand_and_collect(lines);
795 assert_eq!(
796 result,
797 vec![
798 " mov64 r1, 0x1",
799 " mov64 r2, 0x1",
800 " mov64 r1, 0x1",
801 " mov64 r2, 0x1",
802 ]
803 );
804 }
805
806 #[test]
807 fn test_rept_count_from_macro_param() {
808 reset_expansion_counter();
809 let lines = vec![
810 make_line(".macro TEST num", 1),
811 make_line(" .rept \\num", 2),
812 make_line(" mov64 r1, 0x123", 3),
813 make_line(" .endr", 4),
814 make_line(".endm", 5),
815 make_line("TEST 3", 7),
816 ];
817
818 let result = expand_and_collect(lines);
819 assert_eq!(
820 result,
821 vec![
822 " mov64 r1, 0x123",
823 " mov64 r1, 0x123",
824 " mov64 r1, 0x123",
825 ]
826 );
827 }
828
829 #[test]
830 fn test_irp_values_from_macro_vararg() {
831 reset_expansion_counter();
832 let lines = vec![
833 make_line(".macro LOAD regs:vararg", 1),
834 make_line(" .irp r, \\regs", 2),
835 make_line(" mov64 \\r, 0x1", 3),
836 make_line(" .endr", 4),
837 make_line(".endm", 5),
838 make_line("LOAD r1, r2, r3", 7),
839 ];
840
841 let result = expand_and_collect(lines);
842 assert_eq!(
843 result,
844 vec![
845 " mov64 r1, 0x1",
846 " mov64 r2, 0x1",
847 " mov64 r3, 0x1",
848 ]
849 );
850 }
851
852 #[test]
853 fn test_unclosed_rept() {
854 let lines = vec![make_line(".rept 3", 1), make_line(" nop", 2)];
855
856 let result = expand_macros(lines);
857 assert!(result.is_err());
858 let errors = result.unwrap_err();
859 assert!(matches!(
860 &errors[0].error,
861 CompileError::UnclosedRept { .. }
862 ));
863 assert!(errors[0].origin.is_some());
864 }
865
866 #[test]
867 fn test_full_example_from_spec() {
868 reset_expansion_counter();
869 let lines = vec![
870 make_line(".macro DEF_STR name, text", 1),
871 make_line("\\name:", 2),
872 make_line(" .ascii \\text", 3),
873 make_line("\\name\\()_end:", 4),
874 make_line(".endm", 5),
875 make_line("", 6),
876 make_line(".macro RETURN_ERR code, msg", 7),
877 make_line(" lddw r0, \\code", 8),
878 make_line(" lddw r1, \\msg", 9),
879 make_line(" lddw r2, \\msg\\()_end - \\msg", 10),
880 make_line(" call sol_log_", 11),
881 make_line(" exit", 12),
882 make_line(".endm", 13),
883 make_line("", 14),
884 make_line("DEF_STR e1, \"error\"", 15),
885 make_line("", 16),
886 make_line("RETURN_ERR 1, e1", 17),
887 ];
888
889 let result = expand_and_collect(lines);
890 assert_eq!(
891 result,
892 vec![
893 "", "", "e1:",
896 " .ascii \"error\"",
897 "e1_end:",
898 "", " lddw r0, 1",
900 " lddw r1, e1",
901 " lddw r2, e1_end - e1",
902 " call sol_log_",
903 " exit",
904 ]
905 );
906 }
907
908 #[test]
909 fn test_passthrough_no_macros() {
910 let lines = vec![
911 make_line(".globl entrypoint", 1),
912 make_line("entrypoint:", 2),
913 make_line(" mov64 r1, 42", 3),
914 make_line(" exit", 4),
915 ];
916
917 let result = expand_and_collect(lines);
918 assert_eq!(
919 result,
920 vec![
921 ".globl entrypoint",
922 "entrypoint:",
923 " mov64 r1, 42",
924 " exit",
925 ]
926 );
927 }
928}