Skip to main content

rucc_pp/
token.rs

1//! The token the expander works on, which is a pp-token plus expansion bookkeeping.
2//!
3//! Design: `spec/05-preprocessor.md` section 5.3.
4//!
5//! `rucc_lex::PpToken` is sixteen bytes and `spec/05-preprocessor.md` section 5.2 wants it to
6//! stay that way, because there is one of them per token of every header in the build and
7//! the lexer is the hottest loop in the compiler. Expansion needs two more things per token:
8//! a hide set, and the place the outermost macro was invoked so that a diagnostic inside a
9//! macro can point at the call rather than at the definition.
10//!
11//! Those go on a separate, wider type rather than on `PpToken`. The working set of the
12//! expander is one translation unit's worth of live tokens, not every token of every header,
13//! so the extra eight bytes are affordable here and not there.
14
15use rucc_base::Symbol;
16use rucc_diag::Span;
17use rucc_lex::{PpToken, PpTokenKind, Punct, TokenFlags};
18
19use crate::hide::HideSet;
20use crate::trace::TraceId;
21
22/// A token in flight through macro expansion.
23///
24/// Construct one from a lexed token with [`Tok::new`]. The fields are readable because
25/// everything downstream matches on them, but the placemarker flag is not settable from
26/// outside the crate, because a placemarker escaping the expander would be a token with no
27/// spelling and no meaning.
28#[derive(Debug, Clone, Copy, PartialEq, Eq)]
29pub struct Tok {
30    /// What kind of token this is.
31    pub kind: PpTokenKind,
32    /// Whitespace and origin flags, carried through from the lexer.
33    pub flags: TokenFlags,
34    /// The interned spelling, or `None` for a punctuator, whose spelling is fixed.
35    pub value: Option<Symbol>,
36    /// Where the token is spelled: in a macro body for a token that came from one, in the
37    /// user's file for a token that did not.
38    pub span: Span,
39    /// Where the outermost macro invocation that produced this token was written, or
40    /// [`Span::DUMMY`] for a token the user wrote directly.
41    ///
42    /// This is the span a diagnostic points at, with `span` becoming a note, which is what
43    /// makes an error three macros deep readable.
44    pub expansion: Span,
45    /// The macro names that must not expand this token again.
46    pub hides: HideSet,
47    /// The chain of macros this token came out of, innermost first, or [`TraceId::NONE`] for a
48    /// token the user wrote.
49    ///
50    /// `span` says where the text is and `expansion` says where the user was standing. This
51    /// says how one became the other, which is the part a reader cannot reconstruct by hand
52    /// once there is more than one macro involved.
53    pub trace: TraceId,
54    /// True for a placemarker, the empty token that `##` needs so that pasting an empty
55    /// argument onto something yields the something rather than an error.
56    ///
57    /// Placemarkers exist only inside substitution and are dropped before the result leaves
58    /// the expander, per `spec/05-preprocessor.md` section 5.3.
59    pub(crate) placemarker: bool,
60}
61
62impl Tok {
63    /// A token straight from the lexer, with an empty hide set and no expansion point.
64    #[inline]
65    pub fn new(pp: PpToken) -> Tok {
66        Tok {
67            kind: pp.kind,
68            flags: pp.flags,
69            value: pp.value,
70            span: pp.span,
71            expansion: Span::DUMMY,
72            trace: TraceId::NONE,
73            hides: HideSet::EMPTY,
74            placemarker: false,
75        }
76    }
77
78    /// The punctuator this token is, if it is one.
79    #[inline]
80    pub fn punct(self) -> Option<Punct> {
81        match self.kind {
82            PpTokenKind::Punct(p) if !self.placemarker => Some(p),
83            _ => None,
84        }
85    }
86
87    /// Whether this token is the punctuator `p`.
88    #[inline]
89    pub fn is(self, p: Punct) -> bool {
90        self.punct() == Some(p)
91    }
92
93    /// The identifier this token is, if it is one.
94    #[inline]
95    pub fn ident(self) -> Option<Symbol> {
96        match self.kind {
97            PpTokenKind::Ident => self.value,
98            _ => None,
99        }
100    }
101
102    /// Whether this is the empty token `##` uses for an absent argument.
103    #[inline]
104    pub fn is_placemarker(self) -> bool {
105        self.placemarker
106    }
107
108    /// The span to report a diagnostic about this token at.
109    ///
110    /// The invocation point when there is one, because a user reading an error wants the
111    /// line they wrote, not a line in a header they have never opened.
112    #[inline]
113    pub fn report_span(self) -> Span {
114        if self.expansion.is_dummy() { self.span } else { self.expansion }
115    }
116
117    /// A token the preprocessor made up rather than read.
118    ///
119    /// The `1` and `0` that `defined` turns into, and the tokens a `_Pragma` string
120    /// destringizes to. They point at the construct that produced them, because there is no
121    /// file byte to point at instead.
122    pub(crate) fn synthetic(
123        kind: PpTokenKind,
124        value: Option<Symbol>,
125        flags: TokenFlags,
126        span: Span,
127    ) -> Tok {
128        Tok {
129            kind,
130            flags,
131            value,
132            span,
133            expansion: Span::DUMMY,
134            trace: TraceId::NONE,
135            hides: HideSet::EMPTY,
136            placemarker: false,
137        }
138    }
139
140    /// A placemarker at `span`.
141    pub(crate) fn placemarker_at(span: Span) -> Tok {
142        Tok {
143            kind: PpTokenKind::Other,
144            flags: TokenFlags::EMPTY,
145            value: None,
146            span,
147            expansion: Span::DUMMY,
148            trace: TraceId::NONE,
149            hides: HideSet::EMPTY,
150            placemarker: true,
151        }
152    }
153}
154
155#[cfg(test)]
156mod tests {
157    use rucc_diag::Span;
158
159    use super::*;
160
161    fn pp(kind: PpTokenKind) -> PpToken {
162        PpToken { kind, flags: TokenFlags::EMPTY, value: None, span: Span::new(0, 1) }
163    }
164
165    #[test]
166    fn a_lexed_token_starts_with_an_empty_hide_set() {
167        let t = Tok::new(pp(PpTokenKind::Punct(Punct::Plus)));
168        assert_eq!(t.hides, HideSet::EMPTY);
169        assert!(t.expansion.is_dummy());
170        assert!(!t.is_placemarker());
171    }
172
173    #[test]
174    fn a_placemarker_is_not_a_punctuator() {
175        let t = Tok::placemarker_at(Span::new(3, 3));
176        assert!(t.is_placemarker());
177        assert_eq!(t.punct(), None);
178        assert_eq!(t.ident(), None);
179    }
180
181    #[test]
182    fn a_token_from_a_macro_is_reported_at_the_call() {
183        let mut t = Tok::new(pp(PpTokenKind::Ident));
184        assert_eq!(t.report_span(), Span::new(0, 1));
185        t.expansion = Span::new(40, 44);
186        assert_eq!(t.report_span(), Span::new(40, 44));
187    }
188}