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    /// What `#pragma push_macro` put aside, innermost last, one stack per name.
135    ///
136    /// A name with nothing saved has no entry, so the common case of a file that never uses the
137    /// pragma pays for one empty map. `None` in a stack is a real value and not an absence: it
138    /// records that the name had no definition when it was pushed, which is what a matching pop
139    /// has to restore.
140    saved: HashMap<Symbol, Vec<Option<MacroDef>>>,
141}
142
143impl MacroTable {
144    /// An empty table.
145    pub fn new() -> MacroTable {
146        MacroTable::default()
147    }
148
149    /// The definition of `name`, if it has one.
150    #[inline]
151    pub fn lookup(&self, name: Symbol) -> Option<&MacroDef> {
152        self.by_name.get(&name)
153    }
154
155    /// Whether `name` is defined, which is what `#ifdef` and `defined` ask.
156    #[inline]
157    pub fn is_defined(&self, name: Symbol) -> bool {
158        self.by_name.contains_key(&name)
159    }
160
161    /// How many macros are defined.
162    pub fn len(&self) -> usize {
163        self.by_name.len()
164    }
165
166    /// Whether no macros are defined.
167    pub fn is_empty(&self) -> bool {
168        self.by_name.is_empty()
169    }
170
171    /// Adds a definition, returning a warning if it replaces a different one.
172    ///
173    /// Redefining a macro to the same thing is legal and extremely common, because a header
174    /// included twice through two paths does it. Redefining it to something else is a
175    /// constraint violation, which GCC reports as a warning and accepts, and we match that
176    /// because rejecting it would break real builds.
177    pub fn define(&mut self, def: MacroDef, interner: &Interner) -> Option<Diagnostic> {
178        let complaint =
179            self.by_name.get(&def.name).filter(|old| !old.same_definition_as(&def)).map(|old| {
180                Diagnostic::warning(format!("`{}` redefined", interner.resolve(def.name)), def.span)
181                    .with_code("W0301")
182                    .note("previous definition was here", old.span)
183            });
184        self.by_name.insert(def.name, def);
185        complaint
186    }
187
188    /// Defines one of the macros whose value is a question.
189    ///
190    /// `span` is where to say the macro came from, which is the start of `<built-in>`, so that
191    /// a warning about redefining `__FILE__` has somewhere to point.
192    pub fn define_builtin(&mut self, name: Symbol, builtin: Builtin, span: Span) {
193        let def = MacroDef {
194            name,
195            function_like: false,
196            params: Vec::new(),
197            variadic: None,
198            body: Vec::new(),
199            span,
200            builtin: Some(builtin),
201        };
202        self.by_name.insert(name, def);
203    }
204
205    /// Removes a definition. Undefining a macro that is not defined is legal and silent.
206    pub fn undef(&mut self, name: Symbol) -> Option<MacroDef> {
207        self.by_name.remove(&name)
208    }
209
210    /// Puts the current definition of `name` aside, per `#pragma push_macro`.
211    ///
212    /// A name with no definition pushes the absence, because the pragma is about restoring the
213    /// state and "not defined" is a state. This is what lets the idiom work at all: a header
214    /// pushes a name, defines it for its own use, and pops it, and the caller gets back exactly
215    /// what it had whether that was a definition or nothing.
216    pub fn push_macro(&mut self, name: Symbol) {
217        let current = self.by_name.get(&name).cloned();
218        self.saved.entry(name).or_default().push(current);
219    }
220
221    /// Restores what the last `#pragma push_macro` on `name` put aside.
222    ///
223    /// A pop with nothing pushed does nothing and says nothing, which is what gcc does. The
224    /// pragma is written in pairs across headers that do not know about each other, so a
225    /// diagnostic here would fire on code that is not wrong.
226    pub fn pop_macro(&mut self, name: Symbol) {
227        let Some(stack) = self.saved.get_mut(&name) else {
228            return;
229        };
230        let Some(was) = stack.pop() else {
231            return;
232        };
233        if stack.is_empty() {
234            self.saved.remove(&name);
235        }
236        match was {
237            Some(def) => {
238                self.by_name.insert(name, def);
239            }
240            None => {
241                self.by_name.remove(&name);
242            }
243        }
244    }
245
246    /// Every defined macro, sorted by symbol.
247    ///
248    /// Sorted because `-dM` output has to be byte identical across runs and hash order is
249    /// not, per `spec/02-the-goal.md`.
250    pub fn sorted(&self) -> Vec<&MacroDef> {
251        let mut all: Vec<&MacroDef> = self.by_name.values().collect();
252        all.sort_by_key(|m| m.name);
253        all
254    }
255}
256
257/// Parses the tokens after `#define` into a definition.
258///
259/// `tokens` is the rest of the directive line with no end marker, exactly as the lexer
260/// produced it. Diagnostics are returned alongside the definition where the definition is
261/// still usable, and alone where it is not.
262///
263/// # Panics
264///
265/// Panics if `tokens` did not come from `rucc_lex`, which interns the spelling of every
266/// identifier it produces. There is no other source of preprocessing tokens.
267pub fn parse_define(
268    tokens: &[PpToken],
269    interner: &mut Interner,
270) -> (Option<MacroDef>, Vec<Diagnostic>) {
271    let mut diagnostics = Vec::new();
272    let Some(&first) = tokens.first() else {
273        return (None, vec![Diagnostic::error("no macro name given in `#define`", Span::DUMMY)]);
274    };
275    if first.kind != PpTokenKind::Ident {
276        diagnostics.push(
277            Diagnostic::error("macro name must be an identifier", first.span).with_code("E0300"),
278        );
279        return (None, diagnostics);
280    }
281    let name = first.value.expect("the lexer interns every identifier");
282    let span = first.span;
283    let rest = &tokens[1..];
284
285    // A parenthesis touching the name introduces parameters. The same parenthesis with a
286    // space before it is the first token of the replacement list, which is the difference
287    // between `#define A (x)` and `#define A(x)` and the reason the flag exists.
288    let opens_params = rest.first().is_some_and(|t| {
289        t.punct() == Some(Punct::LParen) && !t.flags.has(TokenFlags::LEADING_SPACE)
290    });
291
292    let (function_like, params, variadic, body) = if opens_params {
293        match parse_params(&rest[1..], interner, &mut diagnostics) {
294            Some((params, variadic, consumed)) => (true, params, variadic, &rest[1 + consumed..]),
295            None => return (None, diagnostics),
296        }
297    } else {
298        (false, Vec::new(), None, rest)
299    };
300
301    let def = MacroDef {
302        name,
303        function_like,
304        params,
305        variadic,
306        body: body.to_vec(),
307        span,
308        builtin: None,
309    };
310    check_body(&def, interner, &mut diagnostics);
311    (Some(def), diagnostics)
312}
313
314/// Parses a parameter list, `tokens` starting just after the opening parenthesis.
315///
316/// Returns the parameters, the variadic parameter if there is one, and how many tokens were
317/// consumed including the closing parenthesis.
318fn parse_params(
319    tokens: &[PpToken],
320    interner: &mut Interner,
321    diagnostics: &mut Vec<Diagnostic>,
322) -> Option<(Vec<Symbol>, Option<Symbol>, usize)> {
323    let va_args = interner.intern("__VA_ARGS__");
324    let mut params: Vec<Symbol> = Vec::new();
325    let mut variadic = None;
326    let mut at = 0;
327
328    if tokens.first().is_some_and(|t| t.punct() == Some(Punct::RParen)) {
329        return Some((params, None, 1));
330    }
331
332    loop {
333        let Some(&tok) = tokens.get(at) else {
334            diagnostics.push(
335                Diagnostic::error("missing `)` in macro parameter list", last_span(tokens))
336                    .with_code("E0301"),
337            );
338            return None;
339        };
340        at += 1;
341
342        if tok.punct() == Some(Punct::Ellipsis) {
343            variadic = Some(va_args);
344        } else if tok.kind == PpTokenKind::Ident {
345            let sym = tok.value.expect("the lexer interns every identifier");
346            // The GNU named variadic form, `args...`, which the kernel uses everywhere.
347            if tokens.get(at).is_some_and(|t| t.punct() == Some(Punct::Ellipsis)) {
348                at += 1;
349                variadic = Some(sym);
350            } else if sym == va_args {
351                diagnostics.push(
352                    Diagnostic::error("`__VA_ARGS__` cannot be used as a parameter name", tok.span)
353                        .with_code("E0302"),
354                );
355                return None;
356            } else if params.contains(&sym) {
357                diagnostics.push(
358                    Diagnostic::error(
359                        format!("duplicate macro parameter `{}`", interner.resolve(sym)),
360                        tok.span,
361                    )
362                    .with_code("E0303"),
363                );
364                return None;
365            } else {
366                params.push(sym);
367            }
368        } else {
369            diagnostics.push(
370                Diagnostic::error("macro parameter must be an identifier", tok.span)
371                    .with_code("E0301"),
372            );
373            return None;
374        }
375
376        match tokens.get(at).and_then(|t| t.punct()) {
377            Some(Punct::RParen) => return Some((params, variadic, at + 1)),
378            Some(Punct::Comma) if variadic.is_none() => at += 1,
379            Some(Punct::Comma) => {
380                diagnostics.push(
381                    Diagnostic::error("`...` must be the last macro parameter", tokens[at].span)
382                        .with_code("E0301"),
383                );
384                return None;
385            }
386            _ => {
387                diagnostics.push(
388                    Diagnostic::error("missing `)` in macro parameter list", last_span(tokens))
389                        .with_code("E0301"),
390                );
391                return None;
392            }
393        }
394    }
395}
396
397/// The constraint checks on a replacement list that do not need the expander to run.
398fn check_body(def: &MacroDef, interner: &mut Interner, diagnostics: &mut Vec<Diagnostic>) {
399    let va_opt = interner.intern("__VA_OPT__");
400    let va_args = interner.intern("__VA_ARGS__");
401
402    if let Some(first) = def.body.first().filter(|t| t.punct() == Some(Punct::HashHash)) {
403        diagnostics.push(
404            Diagnostic::error("`##` cannot appear at the start of a replacement list", first.span)
405                .with_code("E0304"),
406        );
407    }
408    // Guarded on length so that a body of exactly `##` is reported once rather than twice.
409    let trailing =
410        def.body.last().filter(|t| def.body.len() > 1 && t.punct() == Some(Punct::HashHash));
411    if let Some(last) = trailing {
412        diagnostics.push(
413            Diagnostic::error("`##` cannot appear at the end of a replacement list", last.span)
414                .with_code("E0304"),
415        );
416    }
417
418    for (at, tok) in def.body.iter().enumerate() {
419        // `#` in a function-like macro must stringify a parameter. In an object-like macro
420        // it is just a token, which is how `#define HASH #` works.
421        if def.function_like && tok.punct() == Some(Punct::Hash) {
422            let operand = def.body.get(at + 1);
423            let names_param = operand.is_some_and(|t| {
424                t.value.is_some_and(|v| def.param_index(v).is_some())
425                    || (def.is_variadic() && t.value == Some(va_opt))
426            });
427            if !names_param {
428                diagnostics.push(
429                    Diagnostic::error("`#` must be followed by a macro parameter", tok.span)
430                        .with_code("E0305"),
431                );
432            }
433        }
434
435        if tok.kind != PpTokenKind::Ident {
436            continue;
437        }
438        if tok.value == Some(va_args) && !def.is_variadic() {
439            diagnostics.push(
440                Diagnostic::error("`__VA_ARGS__` can only appear in a variadic macro", tok.span)
441                    .with_code("E0306"),
442            );
443        }
444        if tok.value == Some(va_opt) {
445            if !def.is_variadic() {
446                diagnostics.push(
447                    Diagnostic::error("`__VA_OPT__` can only appear in a variadic macro", tok.span)
448                        .with_code("E0306"),
449                );
450            } else if !def.body.get(at + 1).is_some_and(|t| t.punct() == Some(Punct::LParen)) {
451                diagnostics.push(
452                    Diagnostic::error("`__VA_OPT__` must be followed by `(`", tok.span)
453                        .with_code("E0307"),
454                );
455            }
456        }
457    }
458}
459
460/// A span to hang an unterminated-construct error on when there is no token left to point at.
461fn last_span(tokens: &[PpToken]) -> Span {
462    tokens.last().map_or(Span::DUMMY, |t| t.span)
463}
464
465#[cfg(test)]
466mod tests {
467    use rucc_diag::Severity;
468    use rucc_lex::{Options, tokenize};
469
470    use super::*;
471
472    fn define(src: &str, interner: &mut Interner) -> (Option<MacroDef>, Vec<Diagnostic>) {
473        let (tokens, lex_errors) = tokenize(src.as_bytes(), 0, Options::new(), interner);
474        assert!(lex_errors.is_empty(), "the test input should lex cleanly");
475        let body: Vec<PpToken> =
476            tokens.into_iter().filter(|t| t.kind != PpTokenKind::Eof).collect();
477        parse_define(&body, interner)
478    }
479
480    #[test]
481    fn an_object_like_macro_has_no_parameter_list() {
482        let mut i = Interner::new();
483        let (def, errors) = define("PI 3.14", &mut i);
484        let def = def.expect("should parse");
485        assert!(errors.is_empty());
486        assert!(!def.function_like);
487        assert_eq!(def.body.len(), 1);
488    }
489
490    #[test]
491    fn a_space_before_the_parenthesis_makes_it_object_like() {
492        let mut i = Interner::new();
493        let (def, _) = define("A (x)", &mut i);
494        let def = def.expect("should parse");
495        assert!(!def.function_like, "`#define A (x)` defines A as the token sequence `(x)`");
496        assert_eq!(def.body.len(), 3);
497    }
498
499    #[test]
500    fn a_function_like_macro_with_no_parameters_is_not_object_like() {
501        let mut i = Interner::new();
502        let (def, _) = define("A() 1", &mut i);
503        let def = def.expect("should parse");
504        assert!(def.function_like);
505        assert_eq!(def.arity(), 0);
506    }
507
508    #[test]
509    fn the_standard_ellipsis_names_the_variadic_va_args() {
510        let mut i = Interner::new();
511        let (def, errors) = define("F(a, ...) a", &mut i);
512        let def = def.expect("should parse");
513        assert!(errors.is_empty());
514        assert_eq!(def.arity(), 1);
515        assert_eq!(def.variadic, Some(i.intern("__VA_ARGS__")));
516    }
517
518    #[test]
519    fn the_gnu_form_names_the_variadic_itself() {
520        let mut i = Interner::new();
521        let (def, errors) = define("F(a, rest...) a", &mut i);
522        let def = def.expect("should parse");
523        assert!(errors.is_empty());
524        assert_eq!(def.variadic, Some(i.intern("rest")));
525        assert_eq!(def.param_index(i.intern("rest")), Some(1));
526    }
527
528    #[test]
529    fn a_duplicate_parameter_is_rejected() {
530        let mut i = Interner::new();
531        let (def, errors) = define("F(a, a) a", &mut i);
532        assert!(def.is_none());
533        assert_eq!(errors[0].code, Some("E0303"));
534    }
535
536    #[test]
537    fn paste_cannot_start_or_end_a_replacement_list() {
538        let mut i = Interner::new();
539        let (_, start) = define("A ## b", &mut i);
540        assert_eq!(start[0].code, Some("E0304"));
541        let (_, end) = define("A b ##", &mut i);
542        assert_eq!(end[0].code, Some("E0304"));
543    }
544
545    #[test]
546    fn stringify_must_name_a_parameter_but_only_in_a_function_like_macro() {
547        let mut i = Interner::new();
548        let (_, bad) = define("F(a) # b", &mut i);
549        assert_eq!(bad[0].code, Some("E0305"));
550        let (_, fine) = define("HASH #", &mut i);
551        assert!(fine.is_empty(), "a bare `#` in an object-like macro is just a token");
552    }
553
554    #[test]
555    fn va_args_outside_a_variadic_macro_is_rejected() {
556        let mut i = Interner::new();
557        let (_, errors) = define("F(a) __VA_ARGS__", &mut i);
558        assert_eq!(errors[0].code, Some("E0306"));
559    }
560
561    #[test]
562    fn va_opt_must_be_called() {
563        let mut i = Interner::new();
564        let (_, errors) = define("F(...) __VA_OPT__", &mut i);
565        assert_eq!(errors[0].code, Some("E0307"));
566    }
567
568    #[test]
569    fn redefining_a_macro_to_the_same_thing_is_silent() {
570        let mut i = Interner::new();
571        let mut table = MacroTable::new();
572        let (first, _) = define("A 1 + 2", &mut i);
573        let (again, _) = define("A 1 + 2", &mut i);
574        assert!(table.define(first.expect("should parse"), &i).is_none());
575        assert!(table.define(again.expect("should parse"), &i).is_none());
576        assert_eq!(table.len(), 1);
577    }
578
579    #[test]
580    fn redefining_a_macro_differently_warns_and_takes_the_new_one() {
581        let mut i = Interner::new();
582        let mut table = MacroTable::new();
583        let (first, _) = define("A 1", &mut i);
584        let (again, _) = define("A 2", &mut i);
585        table.define(first.expect("should parse"), &i);
586        let warning = table.define(again.expect("should parse"), &i).expect("should warn");
587        assert_eq!(warning.severity, Severity::Warning);
588        assert_eq!(warning.code, Some("W0301"));
589        assert_eq!(table.lookup(i.intern("A")).expect("still defined").body.len(), 1);
590    }
591
592    #[test]
593    fn whitespace_inside_the_replacement_list_is_part_of_the_definition() {
594        let mut i = Interner::new();
595        let (a, _) = define("A x+y", &mut i);
596        let (b, _) = define("A x + y", &mut i);
597        assert!(
598            !a.expect("should parse").same_definition_as(&b.expect("should parse")),
599            "the standard compares spelling including whitespace separation"
600        );
601    }
602
603    #[test]
604    fn a_builtin_macro_is_defined_like_any_other() {
605        let mut i = Interner::new();
606        let mut table = MacroTable::new();
607        let name = i.intern("__LINE__");
608        table.define_builtin(name, Builtin::Line, Span::new(0, 0));
609        assert!(table.is_defined(name), "`#ifdef __LINE__` is true");
610        assert_eq!(table.lookup(name).and_then(|d| d.builtin), Some(Builtin::Line));
611        assert!(table.undef(name).is_some(), "`#undef __LINE__` is allowed, as it is in GCC");
612    }
613
614    #[test]
615    fn redefining_a_builtin_is_a_redefinition() {
616        let mut i = Interner::new();
617        let mut table = MacroTable::new();
618        let name = i.intern("__FILE__");
619        table.define_builtin(name, Builtin::File, Span::new(0, 0));
620        // An empty body is not the same definition as a question, which is the whole point of
621        // the warning: somebody has just taken `__FILE__` away from every header below them.
622        let (def, _) = define("__FILE__", &mut i);
623        let warning = table.define(def.expect("should parse"), &i).expect("should warn");
624        assert_eq!(warning.code, Some("W0301"));
625        assert!(table.lookup(name).expect("still defined").builtin.is_none());
626    }
627
628    #[test]
629    fn undefining_something_that_was_never_defined_is_fine() {
630        let mut i = Interner::new();
631        let mut table = MacroTable::new();
632        assert!(table.undef(i.intern("nothing")).is_none());
633    }
634
635    #[test]
636    fn a_push_and_a_pop_leave_the_table_where_they_found_it() {
637        let mut i = Interner::new();
638        let mut table = MacroTable::new();
639        let name = i.intern("X");
640        let (first, _) = define("X 1", &mut i);
641        table.define(first.expect("should parse"), &i);
642        table.push_macro(name);
643        table.undef(name);
644        let (second, _) = define("X 2", &mut i);
645        table.define(second.expect("should parse"), &i);
646        assert_eq!(table.lookup(name).expect("defined").body.len(), 1);
647        table.pop_macro(name);
648        assert!(table.is_defined(name), "the pop brought the first definition back");
649        assert_eq!(table.len(), 1, "and did not leave the second one behind as well");
650    }
651
652    #[test]
653    fn pushing_a_name_that_is_not_defined_pushes_the_absence() {
654        let mut i = Interner::new();
655        let mut table = MacroTable::new();
656        let name = i.intern("X");
657        table.push_macro(name);
658        let (def, _) = define("X 1", &mut i);
659        table.define(def.expect("should parse"), &i);
660        table.pop_macro(name);
661        assert!(!table.is_defined(name), "the state restored is the one that was saved");
662    }
663
664    #[test]
665    fn a_pop_with_nothing_pushed_changes_nothing() {
666        let mut i = Interner::new();
667        let mut table = MacroTable::new();
668        let name = i.intern("X");
669        let (def, _) = define("X 1", &mut i);
670        table.define(def.expect("should parse"), &i);
671        table.pop_macro(name);
672        table.pop_macro(i.intern("never_seen"));
673        assert!(table.is_defined(name));
674    }
675}