Skip to main content

rucc_pp/
macros.rs

1//! Macro definitions and the table they live in.
2//!
3//! Design: `spec/05-preprocessor.md` sections 5.3 and 5.4.
4//!
5//! Parsing a definition and expanding one are separate concerns and the constraint checks
6//! belong here, at definition time, because that is where the user's `#define` line is and
7//! where the error is worth reading. By the time the expander runs, a definition is known
8//! good and it can concentrate on the substitution rules.
9
10use std::collections::HashMap;
11
12use rucc_base::{Interner, Symbol};
13use rucc_diag::{Diagnostic, Span};
14use rucc_lex::{PpToken, PpTokenKind, Punct, TokenFlags};
15
16/// A predefined macro whose value is a question rather than a replacement list.
17///
18/// `__FILE__` and its relatives cannot be written as a body, because what they stand for
19/// depends on where they are used rather than on what the target is. GCC calls these builtin
20/// macros and answers them while expanding, and this is the same arrangement: the table holds
21/// the name and which question it is, and `crate::expand` asks the source map when it meets
22/// one. Everything else about them is ordinary, so `#ifdef __FILE__` is true, `#undef
23/// __FILE__` works, and redefining one warns the way redefining anything else does.
24#[derive(Debug, Clone, Copy, PartialEq, Eq)]
25pub enum Builtin {
26    /// `__FILE__`, the file the use is in, spelled as a string literal.
27    File,
28    /// `__FILE_NAME__`, the same file with the directories taken off.
29    FileName,
30    /// `__BASE_FILE__`, the file at the bottom of the include stack.
31    BaseFile,
32    /// `__LINE__`, the line the use is on.
33    Line,
34    /// `__INCLUDE_LEVEL__`, how many `#include` directives deep the use is.
35    IncludeLevel,
36    /// `__COUNTER__`, a number that is different every time it is expanded.
37    Counter,
38}
39
40impl Builtin {
41    /// Every builtin macro and its spelling.
42    ///
43    /// One list, so that the set the preprocessor defines and the set `-dM` prints cannot
44    /// drift apart.
45    pub const ALL: [(&'static str, Builtin); 6] = [
46        ("__FILE__", Builtin::File),
47        ("__FILE_NAME__", Builtin::FileName),
48        ("__BASE_FILE__", Builtin::BaseFile),
49        ("__LINE__", Builtin::Line),
50        ("__INCLUDE_LEVEL__", Builtin::IncludeLevel),
51        ("__COUNTER__", Builtin::Counter),
52    ];
53}
54
55/// A `#define`.
56#[derive(Debug, Clone)]
57pub struct MacroDef {
58    /// The macro's name.
59    pub name: Symbol,
60    /// Whether the macro takes arguments. A function-like macro with no parameters is not
61    /// the same thing as an object-like macro, so this cannot be inferred from `params`.
62    pub function_like: bool,
63    /// The named parameters, in order, not including the variadic one.
64    pub params: Vec<Symbol>,
65    /// The variadic parameter: `__VA_ARGS__` for the standard `...` spelling, or the given
66    /// name for the GNU `args...` form. `None` for a macro that is not variadic.
67    pub variadic: Option<Symbol>,
68    /// The replacement list.
69    pub body: Vec<PpToken>,
70    /// The `#define` line, for the note attached to a redefinition or an arity error.
71    pub span: Span,
72    /// Which question this macro asks, for the handful that ask one instead of having a body.
73    pub builtin: Option<Builtin>,
74}
75
76impl MacroDef {
77    /// Whether the macro takes a variable number of arguments.
78    #[inline]
79    pub fn is_variadic(&self) -> bool {
80        self.variadic.is_some()
81    }
82
83    /// How many arguments an invocation must supply at a minimum.
84    #[inline]
85    pub fn arity(&self) -> usize {
86        self.params.len()
87    }
88
89    /// The parameter position `name` refers to, with the variadic parameter counting as one
90    /// past the named ones.
91    pub fn param_index(&self, name: Symbol) -> Option<usize> {
92        if let Some(at) = self.params.iter().position(|&p| p == name) {
93            return Some(at);
94        }
95        if self.variadic == Some(name) { Some(self.params.len()) } else { None }
96    }
97
98    /// Whether `name` is this macro's variadic parameter.
99    #[inline]
100    pub fn is_variadic_param(&self, name: Symbol) -> bool {
101        self.variadic == Some(name)
102    }
103
104    /// Whether two definitions are the same one, which is what decides whether a
105    /// redefinition is silently allowed.
106    ///
107    /// The standard's rule is spelling equivalence including whitespace separation, not just
108    /// the same tokens, which is why the leading space flag is part of the comparison.
109    pub fn same_definition_as(&self, other: &MacroDef) -> bool {
110        if self.function_like != other.function_like
111            || self.params != other.params
112            || self.variadic != other.variadic
113            || self.builtin != other.builtin
114            || self.body.len() != other.body.len()
115        {
116            return false;
117        }
118        self.body.iter().zip(&other.body).enumerate().all(|(at, (a, b))| {
119            a.kind == b.kind
120                && a.value == b.value
121                // The first token of a replacement list has whitespace before it whether or
122                // not the user typed any, so only interior separation is compared.
123                && (at == 0
124                    || a.flags.has(TokenFlags::LEADING_SPACE)
125                        == b.flags.has(TokenFlags::LEADING_SPACE))
126        })
127    }
128}
129
130/// Every macro currently defined.
131#[derive(Debug, Default)]
132pub struct MacroTable {
133    by_name: HashMap<Symbol, MacroDef>,
134}
135
136impl MacroTable {
137    /// An empty table.
138    pub fn new() -> MacroTable {
139        MacroTable::default()
140    }
141
142    /// The definition of `name`, if it has one.
143    #[inline]
144    pub fn lookup(&self, name: Symbol) -> Option<&MacroDef> {
145        self.by_name.get(&name)
146    }
147
148    /// Whether `name` is defined, which is what `#ifdef` and `defined` ask.
149    #[inline]
150    pub fn is_defined(&self, name: Symbol) -> bool {
151        self.by_name.contains_key(&name)
152    }
153
154    /// How many macros are defined.
155    pub fn len(&self) -> usize {
156        self.by_name.len()
157    }
158
159    /// Whether no macros are defined.
160    pub fn is_empty(&self) -> bool {
161        self.by_name.is_empty()
162    }
163
164    /// Adds a definition, returning a warning if it replaces a different one.
165    ///
166    /// Redefining a macro to the same thing is legal and extremely common, because a header
167    /// included twice through two paths does it. Redefining it to something else is a
168    /// constraint violation, which GCC reports as a warning and accepts, and we match that
169    /// because rejecting it would break real builds.
170    pub fn define(&mut self, def: MacroDef, interner: &Interner) -> Option<Diagnostic> {
171        let complaint =
172            self.by_name.get(&def.name).filter(|old| !old.same_definition_as(&def)).map(|old| {
173                Diagnostic::warning(format!("`{}` redefined", interner.resolve(def.name)), def.span)
174                    .with_code("W0301")
175                    .note("previous definition was here", old.span)
176            });
177        self.by_name.insert(def.name, def);
178        complaint
179    }
180
181    /// Defines one of the macros whose value is a question.
182    ///
183    /// `span` is where to say the macro came from, which is the start of `<built-in>`, so that
184    /// a warning about redefining `__FILE__` has somewhere to point.
185    pub fn define_builtin(&mut self, name: Symbol, builtin: Builtin, span: Span) {
186        let def = MacroDef {
187            name,
188            function_like: false,
189            params: Vec::new(),
190            variadic: None,
191            body: Vec::new(),
192            span,
193            builtin: Some(builtin),
194        };
195        self.by_name.insert(name, def);
196    }
197
198    /// Removes a definition. Undefining a macro that is not defined is legal and silent.
199    pub fn undef(&mut self, name: Symbol) -> Option<MacroDef> {
200        self.by_name.remove(&name)
201    }
202
203    /// Every defined macro, sorted by symbol.
204    ///
205    /// Sorted because `-dM` output has to be byte identical across runs and hash order is
206    /// not, per `spec/02-the-goal.md`.
207    pub fn sorted(&self) -> Vec<&MacroDef> {
208        let mut all: Vec<&MacroDef> = self.by_name.values().collect();
209        all.sort_by_key(|m| m.name);
210        all
211    }
212}
213
214/// Parses the tokens after `#define` into a definition.
215///
216/// `tokens` is the rest of the directive line with no end marker, exactly as the lexer
217/// produced it. Diagnostics are returned alongside the definition where the definition is
218/// still usable, and alone where it is not.
219///
220/// # Panics
221///
222/// Panics if `tokens` did not come from `rucc_lex`, which interns the spelling of every
223/// identifier it produces. There is no other source of preprocessing tokens.
224pub fn parse_define(
225    tokens: &[PpToken],
226    interner: &mut Interner,
227) -> (Option<MacroDef>, Vec<Diagnostic>) {
228    let mut diagnostics = Vec::new();
229    let Some(&first) = tokens.first() else {
230        return (None, vec![Diagnostic::error("no macro name given in `#define`", Span::DUMMY)]);
231    };
232    if first.kind != PpTokenKind::Ident {
233        diagnostics.push(
234            Diagnostic::error("macro name must be an identifier", first.span).with_code("E0300"),
235        );
236        return (None, diagnostics);
237    }
238    let name = first.value.expect("the lexer interns every identifier");
239    let span = first.span;
240    let rest = &tokens[1..];
241
242    // A parenthesis touching the name introduces parameters. The same parenthesis with a
243    // space before it is the first token of the replacement list, which is the difference
244    // between `#define A (x)` and `#define A(x)` and the reason the flag exists.
245    let opens_params = rest.first().is_some_and(|t| {
246        t.punct() == Some(Punct::LParen) && !t.flags.has(TokenFlags::LEADING_SPACE)
247    });
248
249    let (function_like, params, variadic, body) = if opens_params {
250        match parse_params(&rest[1..], interner, &mut diagnostics) {
251            Some((params, variadic, consumed)) => (true, params, variadic, &rest[1 + consumed..]),
252            None => return (None, diagnostics),
253        }
254    } else {
255        (false, Vec::new(), None, rest)
256    };
257
258    let def = MacroDef {
259        name,
260        function_like,
261        params,
262        variadic,
263        body: body.to_vec(),
264        span,
265        builtin: None,
266    };
267    check_body(&def, interner, &mut diagnostics);
268    (Some(def), diagnostics)
269}
270
271/// Parses a parameter list, `tokens` starting just after the opening parenthesis.
272///
273/// Returns the parameters, the variadic parameter if there is one, and how many tokens were
274/// consumed including the closing parenthesis.
275fn parse_params(
276    tokens: &[PpToken],
277    interner: &mut Interner,
278    diagnostics: &mut Vec<Diagnostic>,
279) -> Option<(Vec<Symbol>, Option<Symbol>, usize)> {
280    let va_args = interner.intern("__VA_ARGS__");
281    let mut params: Vec<Symbol> = Vec::new();
282    let mut variadic = None;
283    let mut at = 0;
284
285    if tokens.first().is_some_and(|t| t.punct() == Some(Punct::RParen)) {
286        return Some((params, None, 1));
287    }
288
289    loop {
290        let Some(&tok) = tokens.get(at) else {
291            diagnostics.push(
292                Diagnostic::error("missing `)` in macro parameter list", last_span(tokens))
293                    .with_code("E0301"),
294            );
295            return None;
296        };
297        at += 1;
298
299        if tok.punct() == Some(Punct::Ellipsis) {
300            variadic = Some(va_args);
301        } else if tok.kind == PpTokenKind::Ident {
302            let sym = tok.value.expect("the lexer interns every identifier");
303            // The GNU named variadic form, `args...`, which the kernel uses everywhere.
304            if tokens.get(at).is_some_and(|t| t.punct() == Some(Punct::Ellipsis)) {
305                at += 1;
306                variadic = Some(sym);
307            } else if sym == va_args {
308                diagnostics.push(
309                    Diagnostic::error("`__VA_ARGS__` cannot be used as a parameter name", tok.span)
310                        .with_code("E0302"),
311                );
312                return None;
313            } else if params.contains(&sym) {
314                diagnostics.push(
315                    Diagnostic::error(
316                        format!("duplicate macro parameter `{}`", interner.resolve(sym)),
317                        tok.span,
318                    )
319                    .with_code("E0303"),
320                );
321                return None;
322            } else {
323                params.push(sym);
324            }
325        } else {
326            diagnostics.push(
327                Diagnostic::error("macro parameter must be an identifier", tok.span)
328                    .with_code("E0301"),
329            );
330            return None;
331        }
332
333        match tokens.get(at).and_then(|t| t.punct()) {
334            Some(Punct::RParen) => return Some((params, variadic, at + 1)),
335            Some(Punct::Comma) if variadic.is_none() => at += 1,
336            Some(Punct::Comma) => {
337                diagnostics.push(
338                    Diagnostic::error("`...` must be the last macro parameter", tokens[at].span)
339                        .with_code("E0301"),
340                );
341                return None;
342            }
343            _ => {
344                diagnostics.push(
345                    Diagnostic::error("missing `)` in macro parameter list", last_span(tokens))
346                        .with_code("E0301"),
347                );
348                return None;
349            }
350        }
351    }
352}
353
354/// The constraint checks on a replacement list that do not need the expander to run.
355fn check_body(def: &MacroDef, interner: &mut Interner, diagnostics: &mut Vec<Diagnostic>) {
356    let va_opt = interner.intern("__VA_OPT__");
357    let va_args = interner.intern("__VA_ARGS__");
358
359    if let Some(first) = def.body.first().filter(|t| t.punct() == Some(Punct::HashHash)) {
360        diagnostics.push(
361            Diagnostic::error("`##` cannot appear at the start of a replacement list", first.span)
362                .with_code("E0304"),
363        );
364    }
365    // Guarded on length so that a body of exactly `##` is reported once rather than twice.
366    let trailing =
367        def.body.last().filter(|t| def.body.len() > 1 && t.punct() == Some(Punct::HashHash));
368    if let Some(last) = trailing {
369        diagnostics.push(
370            Diagnostic::error("`##` cannot appear at the end of a replacement list", last.span)
371                .with_code("E0304"),
372        );
373    }
374
375    for (at, tok) in def.body.iter().enumerate() {
376        // `#` in a function-like macro must stringify a parameter. In an object-like macro
377        // it is just a token, which is how `#define HASH #` works.
378        if def.function_like && tok.punct() == Some(Punct::Hash) {
379            let operand = def.body.get(at + 1);
380            let names_param = operand.is_some_and(|t| {
381                t.value.is_some_and(|v| def.param_index(v).is_some())
382                    || (def.is_variadic() && t.value == Some(va_opt))
383            });
384            if !names_param {
385                diagnostics.push(
386                    Diagnostic::error("`#` must be followed by a macro parameter", tok.span)
387                        .with_code("E0305"),
388                );
389            }
390        }
391
392        if tok.kind != PpTokenKind::Ident {
393            continue;
394        }
395        if tok.value == Some(va_args) && !def.is_variadic() {
396            diagnostics.push(
397                Diagnostic::error("`__VA_ARGS__` can only appear in a variadic macro", tok.span)
398                    .with_code("E0306"),
399            );
400        }
401        if tok.value == Some(va_opt) {
402            if !def.is_variadic() {
403                diagnostics.push(
404                    Diagnostic::error("`__VA_OPT__` can only appear in a variadic macro", tok.span)
405                        .with_code("E0306"),
406                );
407            } else if !def.body.get(at + 1).is_some_and(|t| t.punct() == Some(Punct::LParen)) {
408                diagnostics.push(
409                    Diagnostic::error("`__VA_OPT__` must be followed by `(`", tok.span)
410                        .with_code("E0307"),
411                );
412            }
413        }
414    }
415}
416
417/// A span to hang an unterminated-construct error on when there is no token left to point at.
418fn last_span(tokens: &[PpToken]) -> Span {
419    tokens.last().map_or(Span::DUMMY, |t| t.span)
420}
421
422#[cfg(test)]
423mod tests {
424    use rucc_diag::Severity;
425    use rucc_lex::{Options, tokenize};
426
427    use super::*;
428
429    fn define(src: &str, interner: &mut Interner) -> (Option<MacroDef>, Vec<Diagnostic>) {
430        let (tokens, lex_errors) = tokenize(src.as_bytes(), 0, Options::new(), interner);
431        assert!(lex_errors.is_empty(), "the test input should lex cleanly");
432        let body: Vec<PpToken> =
433            tokens.into_iter().filter(|t| t.kind != PpTokenKind::Eof).collect();
434        parse_define(&body, interner)
435    }
436
437    #[test]
438    fn an_object_like_macro_has_no_parameter_list() {
439        let mut i = Interner::new();
440        let (def, errors) = define("PI 3.14", &mut i);
441        let def = def.expect("should parse");
442        assert!(errors.is_empty());
443        assert!(!def.function_like);
444        assert_eq!(def.body.len(), 1);
445    }
446
447    #[test]
448    fn a_space_before_the_parenthesis_makes_it_object_like() {
449        let mut i = Interner::new();
450        let (def, _) = define("A (x)", &mut i);
451        let def = def.expect("should parse");
452        assert!(!def.function_like, "`#define A (x)` defines A as the token sequence `(x)`");
453        assert_eq!(def.body.len(), 3);
454    }
455
456    #[test]
457    fn a_function_like_macro_with_no_parameters_is_not_object_like() {
458        let mut i = Interner::new();
459        let (def, _) = define("A() 1", &mut i);
460        let def = def.expect("should parse");
461        assert!(def.function_like);
462        assert_eq!(def.arity(), 0);
463    }
464
465    #[test]
466    fn the_standard_ellipsis_names_the_variadic_va_args() {
467        let mut i = Interner::new();
468        let (def, errors) = define("F(a, ...) a", &mut i);
469        let def = def.expect("should parse");
470        assert!(errors.is_empty());
471        assert_eq!(def.arity(), 1);
472        assert_eq!(def.variadic, Some(i.intern("__VA_ARGS__")));
473    }
474
475    #[test]
476    fn the_gnu_form_names_the_variadic_itself() {
477        let mut i = Interner::new();
478        let (def, errors) = define("F(a, rest...) a", &mut i);
479        let def = def.expect("should parse");
480        assert!(errors.is_empty());
481        assert_eq!(def.variadic, Some(i.intern("rest")));
482        assert_eq!(def.param_index(i.intern("rest")), Some(1));
483    }
484
485    #[test]
486    fn a_duplicate_parameter_is_rejected() {
487        let mut i = Interner::new();
488        let (def, errors) = define("F(a, a) a", &mut i);
489        assert!(def.is_none());
490        assert_eq!(errors[0].code, Some("E0303"));
491    }
492
493    #[test]
494    fn paste_cannot_start_or_end_a_replacement_list() {
495        let mut i = Interner::new();
496        let (_, start) = define("A ## b", &mut i);
497        assert_eq!(start[0].code, Some("E0304"));
498        let (_, end) = define("A b ##", &mut i);
499        assert_eq!(end[0].code, Some("E0304"));
500    }
501
502    #[test]
503    fn stringify_must_name_a_parameter_but_only_in_a_function_like_macro() {
504        let mut i = Interner::new();
505        let (_, bad) = define("F(a) # b", &mut i);
506        assert_eq!(bad[0].code, Some("E0305"));
507        let (_, fine) = define("HASH #", &mut i);
508        assert!(fine.is_empty(), "a bare `#` in an object-like macro is just a token");
509    }
510
511    #[test]
512    fn va_args_outside_a_variadic_macro_is_rejected() {
513        let mut i = Interner::new();
514        let (_, errors) = define("F(a) __VA_ARGS__", &mut i);
515        assert_eq!(errors[0].code, Some("E0306"));
516    }
517
518    #[test]
519    fn va_opt_must_be_called() {
520        let mut i = Interner::new();
521        let (_, errors) = define("F(...) __VA_OPT__", &mut i);
522        assert_eq!(errors[0].code, Some("E0307"));
523    }
524
525    #[test]
526    fn redefining_a_macro_to_the_same_thing_is_silent() {
527        let mut i = Interner::new();
528        let mut table = MacroTable::new();
529        let (first, _) = define("A 1 + 2", &mut i);
530        let (again, _) = define("A 1 + 2", &mut i);
531        assert!(table.define(first.expect("should parse"), &i).is_none());
532        assert!(table.define(again.expect("should parse"), &i).is_none());
533        assert_eq!(table.len(), 1);
534    }
535
536    #[test]
537    fn redefining_a_macro_differently_warns_and_takes_the_new_one() {
538        let mut i = Interner::new();
539        let mut table = MacroTable::new();
540        let (first, _) = define("A 1", &mut i);
541        let (again, _) = define("A 2", &mut i);
542        table.define(first.expect("should parse"), &i);
543        let warning = table.define(again.expect("should parse"), &i).expect("should warn");
544        assert_eq!(warning.severity, Severity::Warning);
545        assert_eq!(warning.code, Some("W0301"));
546        assert_eq!(table.lookup(i.intern("A")).expect("still defined").body.len(), 1);
547    }
548
549    #[test]
550    fn whitespace_inside_the_replacement_list_is_part_of_the_definition() {
551        let mut i = Interner::new();
552        let (a, _) = define("A x+y", &mut i);
553        let (b, _) = define("A x + y", &mut i);
554        assert!(
555            !a.expect("should parse").same_definition_as(&b.expect("should parse")),
556            "the standard compares spelling including whitespace separation"
557        );
558    }
559
560    #[test]
561    fn a_builtin_macro_is_defined_like_any_other() {
562        let mut i = Interner::new();
563        let mut table = MacroTable::new();
564        let name = i.intern("__LINE__");
565        table.define_builtin(name, Builtin::Line, Span::new(0, 0));
566        assert!(table.is_defined(name), "`#ifdef __LINE__` is true");
567        assert_eq!(table.lookup(name).and_then(|d| d.builtin), Some(Builtin::Line));
568        assert!(table.undef(name).is_some(), "`#undef __LINE__` is allowed, as it is in GCC");
569    }
570
571    #[test]
572    fn redefining_a_builtin_is_a_redefinition() {
573        let mut i = Interner::new();
574        let mut table = MacroTable::new();
575        let name = i.intern("__FILE__");
576        table.define_builtin(name, Builtin::File, Span::new(0, 0));
577        // An empty body is not the same definition as a question, which is the whole point of
578        // the warning: somebody has just taken `__FILE__` away from every header below them.
579        let (def, _) = define("__FILE__", &mut i);
580        let warning = table.define(def.expect("should parse"), &i).expect("should warn");
581        assert_eq!(warning.code, Some("W0301"));
582        assert!(table.lookup(name).expect("still defined").builtin.is_none());
583    }
584
585    #[test]
586    fn undefining_something_that_was_never_defined_is_fine() {
587        let mut i = Interner::new();
588        let mut table = MacroTable::new();
589        assert!(table.undef(i.intern("nothing")).is_none());
590    }
591}