use crate::Assembler;
use crate::tree::AsmNode::*;
use crate::tree::*;
#[derive(Clone, Debug, PartialEq, Hash, Eq)]
pub struct MacroID {
name: String,
number_of_arguments: usize,
}
impl MacroID {
fn new(name: &str, number_of_arguments: usize) -> Self {
MacroID {name: name.to_string(), number_of_arguments}
}
}
pub fn register_macros(asm: &mut Assembler) -> bool {
let mut registered_macro = false;
let mut in_macro = false;
let mut new_macro_id = MacroID::new("", 0);
let mut new_macro_content = "".to_string();
let mut register_macros_closure = | node: &AsmNode | -> Option<AsmNode> {
match node {
Source{code, meta} => {
match code[0].as_str() {
"@macro" => {
if in_macro {
Some(Error{msg: "Error, macro definitions can't be nested.".to_string(), meta: meta.clone()})
} else if code.len() == 3 {
in_macro = true;
new_macro_id.name = code[1].clone();
let number_of_arguments: Result<usize, _> = code[2].parse();
match number_of_arguments {
Ok(x) => {
new_macro_id.number_of_arguments = x;
Some(Empty)
},
Err(_) =>
Some(Error{msg: "Error, macro definitions should have the form `@macro <macro name> <number_of_arguments>`.".to_string(), meta: meta.clone()})
}
} else {
Some(Error{msg: "Error, macro definitions should have the form `@macro <macro name> <number_of_arguments>`.".to_string(), meta: meta.clone()})
}
},
"@end" => {
if in_macro {
in_macro = false;
asm.macros.insert(new_macro_id.clone(), new_macro_content.clone());
new_macro_content = "".to_string();
registered_macro = true;
Some(Empty)
} else {
Some(Error{msg: "Error, @end should only be used to end macro definitions.".to_string(), meta: meta.clone()})
}
},
_ => {
if in_macro {
new_macro_content.push_str(&meta.raw);
new_macro_content.push('\n');
Some(Empty)
} else {
None
}
},
}
},
_ => None,
}
};
asm.root.traverse_tree(&mut register_macros_closure);
if in_macro {
asm.root.error_on_top(format!("Error, macro {} is not closed with `@end` directive", new_macro_id.name));
}
registered_macro
}
fn substitution_for_expansion(expanded_text: &str, pattern_from: &str, pattern_to: &str) -> String {
let replacement_slashes_escaped = pattern_to.replace("\\", "\\\\");
let replacement_all_escaped = replacement_slashes_escaped.replace("\"", "\\\"");
let replacement_quoted = format!("\"{}\"", replacement_all_escaped);
let replacement_mono_line = replacement_quoted.replace("\n", "\\n");
expanded_text.replace(pattern_from, &replacement_mono_line)
}
pub fn expand_macros(asm: &mut Assembler) -> bool {
let mut expansion_counter = asm.macro_expansion_count;
let ret = expand_macros_with_explicit_counter(asm, &mut expansion_counter);
asm.macro_expansion_count = expansion_counter;
ret
}
fn expand_macros_with_explicit_counter(asm: &mut Assembler, expansion_counter: &mut usize) -> bool {
let mut expanded_any_macro = false;
let mut expand_macros_closure = | node: &AsmNode | -> Option<AsmNode> {
match node {
Source{code, meta} => {
let number_of_arguments = code.len() - 1;
let macro_id = MacroID::new(&code[0], number_of_arguments);
match asm.macros.get(¯o_id) {
Some(macro_txt) => {
let mut macro_name = "macro_".to_string();
macro_name.push_str(&code[0]);
let mut expanded_text = macro_txt.clone();
for i in 0..number_of_arguments {
let pattern = format!("${}", i+1);
expanded_text = substitution_for_expansion(&expanded_text, &pattern, &code[i+1]);
}
let unique_symbol = format!("usx_{expansion_counter:x}");
expanded_text = substitution_for_expansion(&expanded_text, "$?", &unique_symbol);
*expansion_counter += 1;
let mut expanded_macro = Assembler::from_named_text(&expanded_text, &format!("`macro '{}' expanded from file {} at line {}`", macro_name, meta.source_file, meta.line));
expanded_macro.macros = asm.macros.clone();
expanded_macro.macros.remove(¯o_id); expand_macros_with_explicit_counter(&mut expanded_macro, expansion_counter);
expanded_any_macro = true;
Some(expanded_macro.root)
},
None => None,
}
},
_ => None,
}
};
asm.root.traverse_tree(&mut expand_macros_closure);
expanded_any_macro
}
#[test]
fn test_register_macros() {
let mut assembler = Assembler::from_text("@macro my_macro 3\nmacromacro\ntxttxt\n@end");
let expected_hash_map = std::collections::HashMap::from([
(MacroID{name: "my_macro".to_string(), number_of_arguments: 3}, "macromacro\ntxttxt\n".to_string()),
]);
register_macros(&mut assembler);
assert_eq!(expected_hash_map, assembler.macros);
}
#[test]
fn test_expand_macro_no_arg_substitution() {
let mut assembler = Assembler::from_text("@macro m 0\nx x\n@end\nm\nm\n");
register_macros(&mut assembler);
expand_macros(&mut assembler);
assert_eq!(assembler.root.to_string(), "x x\nx x\n");
}
#[test]
fn test_expand_macro() {
let mut assembler = Assembler::from_text("@macro m 2\nx $1 $2\n@end\nm a A\nm b B\n");
register_macros(&mut assembler);
expand_macros(&mut assembler);
assert_eq!(assembler.root.to_string(), "x a A\nx b B\n");
}
#[test]
fn test_expand_macro_recursive() {
let mut assembler = Assembler::from_text("@macro m 2
$1 $2
@end
@macro in 1
x $1
@end
m in a
m in b");
register_macros(&mut assembler);
expand_macros(&mut assembler);
assert_eq!(assembler.root.to_string(), "x a\nx b\n");
}
#[test]
fn test_multiple_macro_with_the_same_name() {
let mut assembler = Assembler::from_text("@macro dub 1
dub $1 $1
@end
@macro dub 2
$1 $2 $1 $2
@end
dub a b
dub c");
register_macros(&mut assembler);
expand_macros(&mut assembler);
assert_eq!(assembler.root.to_string(), "a b a b\nc c c c\n");
}
#[test]
fn test_expansion_unique_symbol() {
let mut assembler = Assembler::from_text("@macro m1 0
m2
$?
@end
@macro m2 0
$?
@end
m1
m2
m1");
register_macros(&mut assembler);
expand_macros(&mut assembler);
assert_eq!(assembler.root.to_string(), "usx_1\nusx_0\nusx_2\nusx_4\nusx_3\n");
}