Skip to main content

rucc_pp/
dump.rs

1//! `-dM`: the macro table written back out as the `#define` lines that would produce it.
2//!
3//! Design: `spec/04-driver-and-cli.md` section 4.4, and `spec/04-driver-and-cli.md` section
4//! 4.5 for why the predefined set has to be printable at all.
5//!
6//! This is the check on the predefined set that a person can actually run. `rucc -dM -E -x c
7//! /dev/null | sort` against `gcc -dM -E -x c /dev/null | sort` is one diff, and every entry
8//! in it is either a macro we get wrong or a promise we have not made yet. Nothing else gives
9//! that list, and a list nobody can produce is a list nobody checks.
10//!
11//! The output is sorted by name rather than printed in table order. GCC prints its hash order,
12//! which is stable for GCC and means nothing to us, and the whole point of the output is being
13//! diffed, so the order that makes a diff readable wins.
14
15use rucc_base::Interner;
16use rucc_lex::{PpTokenKind, TokenFlags};
17
18use crate::include::spelling;
19use crate::macros::{Builtin, MacroDef, MacroTable};
20use crate::token::Tok;
21
22/// Every macro that is defined, as `#define` lines, sorted by name and newline terminated.
23///
24/// Empty when nothing is defined, which cannot happen in a real compilation but is what a
25/// caller building a table by hand will see.
26#[must_use]
27pub fn macros(table: &MacroTable, interner: &Interner) -> String {
28    let mut all = table.sorted();
29    all.sort_by_key(|m| interner.resolve(m.name));
30    let mut out = String::new();
31    for def in all {
32        define(&mut out, def, interner);
33        out.push('\n');
34    }
35    out
36}
37
38/// One `#define` line, without the newline.
39fn define(out: &mut String, def: &MacroDef, interner: &Interner) {
40    out.push_str("#define ");
41    out.push_str(interner.resolve(def.name));
42    if def.function_like {
43        out.push('(');
44        for (at, param) in def.params.iter().enumerate() {
45            if at > 0 {
46                out.push_str(", ");
47            }
48            out.push_str(interner.resolve(*param));
49        }
50        if let Some(rest) = def.variadic {
51            if !def.params.is_empty() {
52                out.push_str(", ");
53            }
54            // `...` for the standard spelling and `name...` for the GNU one. The two are not
55            // interchangeable, because the GNU form is what `__VA_ARGS__` is not called in the
56            // body, and a dump that printed the standard form for both would not read back as
57            // the same macro.
58            let name = interner.resolve(rest);
59            if name != "__VA_ARGS__" {
60                out.push_str(name);
61            }
62            out.push_str("...");
63        }
64        out.push(')');
65    }
66    if let Some(builtin) = def.builtin {
67        // A builtin has no body to print. GCC prints the name again, which reads oddly but is
68        // the honest answer: there is no text, and what the macro stands for depends on where
69        // it is used.
70        out.push(' ');
71        out.push_str(match builtin {
72            Builtin::File => "__FILE__",
73            Builtin::FileName => "__FILE_NAME__",
74            Builtin::BaseFile => "__BASE_FILE__",
75            Builtin::Line => "__LINE__",
76            Builtin::IncludeLevel => "__INCLUDE_LEVEL__",
77            Builtin::Counter => "__COUNTER__",
78        });
79        return;
80    }
81    for (at, token) in def.body.iter().enumerate() {
82        // A space before the body, then whatever spacing the definition had. The first token
83        // of a body never carries a leading space flag, because the space after the name is
84        // what separated it from the name.
85        if at == 0 || token.flags.has(TokenFlags::LEADING_SPACE) {
86            out.push(' ');
87        }
88        out.push_str(text(*token, interner));
89    }
90}
91
92/// A body token as it was written.
93fn text(token: rucc_lex::PpToken, interner: &Interner) -> &str {
94    match token.kind {
95        // A macro body is never empty of meaning at the end, so end of file cannot appear
96        // here, but matching on it rather than assuming keeps this total.
97        PpTokenKind::Eof => "",
98        _ => spelling(Tok::new(token), interner),
99    }
100}
101
102#[cfg(test)]
103mod tests {
104    use rucc_diag::Span;
105
106    use super::*;
107    use crate::macros::parse_define;
108
109    /// Builds a table from `#define` bodies written the way a user writes them.
110    fn table(lines: &[&str]) -> (MacroTable, Interner) {
111        let mut interner = Interner::new();
112        let mut table = MacroTable::new();
113        for line in lines {
114            let mut lexer = rucc_lex::Lexer::new(line.as_bytes(), 0, rucc_lex::Options::new());
115            let mut tokens = Vec::new();
116            loop {
117                let token = lexer.next_token(&mut interner);
118                if token.is_eof() {
119                    break;
120                }
121                tokens.push(token);
122            }
123            let (def, _) = parse_define(&tokens, &mut interner);
124            table.define(def.expect("the test wrote a valid define"), &interner);
125        }
126        (table, interner)
127    }
128
129    fn dump(lines: &[&str]) -> String {
130        let (table, interner) = table(lines);
131        macros(&table, &interner)
132    }
133
134    #[test]
135    fn an_object_like_macro_comes_back_the_way_it_went_in() {
136        assert_eq!(dump(&["N 2"]), "#define N 2\n");
137        assert_eq!(dump(&["EMPTY"]), "#define EMPTY\n");
138    }
139
140    #[test]
141    fn the_output_is_sorted_by_name_because_the_point_of_it_is_being_diffed() {
142        // Not table order and not definition order. A diff against GCC is the reason this
143        // output exists, and a diff whose lines moved is a diff nobody reads.
144        assert_eq!(dump(&["Z 1", "A 2", "M 3"]), "#define A 2\n#define M 3\n#define Z 1\n");
145    }
146
147    #[test]
148    fn a_function_like_macro_keeps_its_parameters_and_the_two_variadic_spellings_apart() {
149        assert_eq!(dump(&["ADD(a, b) a + b"]), "#define ADD(a, b) a + b\n");
150        assert_eq!(dump(&["F() 1"]), "#define F() 1\n");
151        assert_eq!(dump(&["V(...) __VA_ARGS__"]), "#define V(...) __VA_ARGS__\n");
152        assert_eq!(dump(&["W(a, ...) __VA_ARGS__"]), "#define W(a, ...) __VA_ARGS__\n");
153        // The GNU form names the variadic parameter, and printing it as `...` would be a
154        // different macro: the body says `rest`, which the standard spelling does not have.
155        assert_eq!(dump(&["G(rest...) rest"]), "#define G(rest...) rest\n");
156    }
157
158    #[test]
159    fn the_spacing_inside_a_body_is_the_spacing_that_was_written() {
160        // Not reformatted. This output is diffed against GCC's, and GCC prints what it stored,
161        // so a tidier body here would be a difference on every line that has an operator in it.
162        assert_eq!(dump(&["A 1+2"]), "#define A 1+2\n");
163        assert_eq!(dump(&["B 1 + 2"]), "#define B 1 + 2\n");
164        assert_eq!(dump(&["C (x)"]), "#define C (x)\n");
165    }
166
167    #[test]
168    fn a_builtin_has_no_body_and_says_its_own_name() {
169        let mut interner = Interner::new();
170        let mut table = MacroTable::new();
171        for (name, builtin) in Builtin::ALL {
172            table.define_builtin(interner.intern(name), builtin, Span::new(0, 1));
173        }
174        let text = macros(&table, &interner);
175        assert!(text.contains("#define __FILE__ __FILE__\n"), "{text}");
176        assert!(text.contains("#define __COUNTER__ __COUNTER__\n"), "{text}");
177        assert_eq!(text.lines().count(), Builtin::ALL.len());
178    }
179}