use rucc_base::Interner;
use rucc_lex::{PpTokenKind, TokenFlags};
use crate::include::spelling;
use crate::macros::{Builtin, MacroDef, MacroTable};
use crate::token::Tok;
#[must_use]
pub fn macros(table: &MacroTable, interner: &Interner) -> String {
let mut all = table.sorted();
all.sort_by_key(|m| interner.resolve(m.name));
let mut out = String::new();
for def in all {
define(&mut out, def, interner);
out.push('\n');
}
out
}
fn define(out: &mut String, def: &MacroDef, interner: &Interner) {
out.push_str("#define ");
out.push_str(interner.resolve(def.name));
if def.function_like {
out.push('(');
for (at, param) in def.params.iter().enumerate() {
if at > 0 {
out.push_str(", ");
}
out.push_str(interner.resolve(*param));
}
if let Some(rest) = def.variadic {
if !def.params.is_empty() {
out.push_str(", ");
}
let name = interner.resolve(rest);
if name != "__VA_ARGS__" {
out.push_str(name);
}
out.push_str("...");
}
out.push(')');
}
if let Some(builtin) = def.builtin {
out.push(' ');
out.push_str(match builtin {
Builtin::File => "__FILE__",
Builtin::FileName => "__FILE_NAME__",
Builtin::BaseFile => "__BASE_FILE__",
Builtin::Line => "__LINE__",
Builtin::IncludeLevel => "__INCLUDE_LEVEL__",
Builtin::Counter => "__COUNTER__",
});
return;
}
for (at, token) in def.body.iter().enumerate() {
if at == 0 || token.flags.has(TokenFlags::LEADING_SPACE) {
out.push(' ');
}
out.push_str(text(*token, interner));
}
}
fn text(token: rucc_lex::PpToken, interner: &Interner) -> &str {
match token.kind {
PpTokenKind::Eof => "",
_ => spelling(Tok::new(token), interner),
}
}
#[cfg(test)]
mod tests {
use rucc_diag::Span;
use super::*;
use crate::macros::parse_define;
fn table(lines: &[&str]) -> (MacroTable, Interner) {
let mut interner = Interner::new();
let mut table = MacroTable::new();
for line in lines {
let mut lexer = rucc_lex::Lexer::new(line.as_bytes(), 0, rucc_lex::Options::new());
let mut tokens = Vec::new();
loop {
let token = lexer.next_token(&mut interner);
if token.is_eof() {
break;
}
tokens.push(token);
}
let (def, _) = parse_define(&tokens, &mut interner);
table.define(def.expect("the test wrote a valid define"), &interner);
}
(table, interner)
}
fn dump(lines: &[&str]) -> String {
let (table, interner) = table(lines);
macros(&table, &interner)
}
#[test]
fn an_object_like_macro_comes_back_the_way_it_went_in() {
assert_eq!(dump(&["N 2"]), "#define N 2\n");
assert_eq!(dump(&["EMPTY"]), "#define EMPTY\n");
}
#[test]
fn the_output_is_sorted_by_name_because_the_point_of_it_is_being_diffed() {
assert_eq!(dump(&["Z 1", "A 2", "M 3"]), "#define A 2\n#define M 3\n#define Z 1\n");
}
#[test]
fn a_function_like_macro_keeps_its_parameters_and_the_two_variadic_spellings_apart() {
assert_eq!(dump(&["ADD(a, b) a + b"]), "#define ADD(a, b) a + b\n");
assert_eq!(dump(&["F() 1"]), "#define F() 1\n");
assert_eq!(dump(&["V(...) __VA_ARGS__"]), "#define V(...) __VA_ARGS__\n");
assert_eq!(dump(&["W(a, ...) __VA_ARGS__"]), "#define W(a, ...) __VA_ARGS__\n");
assert_eq!(dump(&["G(rest...) rest"]), "#define G(rest...) rest\n");
}
#[test]
fn the_spacing_inside_a_body_is_the_spacing_that_was_written() {
assert_eq!(dump(&["A 1+2"]), "#define A 1+2\n");
assert_eq!(dump(&["B 1 + 2"]), "#define B 1 + 2\n");
assert_eq!(dump(&["C (x)"]), "#define C (x)\n");
}
#[test]
fn a_builtin_has_no_body_and_says_its_own_name() {
let mut interner = Interner::new();
let mut table = MacroTable::new();
for (name, builtin) in Builtin::ALL {
table.define_builtin(interner.intern(name), builtin, Span::new(0, 1));
}
let text = macros(&table, &interner);
assert!(text.contains("#define __FILE__ __FILE__\n"), "{text}");
assert!(text.contains("#define __COUNTER__ __COUNTER__\n"), "{text}");
assert_eq!(text.lines().count(), Builtin::ALL.len());
}
}