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    /// This token as phase 7 wants it, which is without the bookkeeping phases 3 to 4 needed.
79    ///
80    /// The hide set and the trace are finished with once the token leaves the expander, and
81    /// what is left is a pp-token with one span rather than two. The span kept is
82    /// [`Tok::report_span`], so a constant that will not convert points at the line the user
83    /// wrote rather than at the macro body it was spelled in.
84    #[inline]
85    #[must_use]
86    pub fn to_pp(self) -> PpToken {
87        PpToken { kind: self.kind, flags: self.flags, value: self.value, span: self.report_span() }
88    }
89
90    /// The punctuator this token is, if it is one.
91    #[inline]
92    pub fn punct(self) -> Option<Punct> {
93        match self.kind {
94            PpTokenKind::Punct(p) if !self.placemarker => Some(p),
95            _ => None,
96        }
97    }
98
99    /// Whether this token is the punctuator `p`.
100    #[inline]
101    pub fn is(self, p: Punct) -> bool {
102        self.punct() == Some(p)
103    }
104
105    /// The identifier this token is, if it is one.
106    #[inline]
107    pub fn ident(self) -> Option<Symbol> {
108        match self.kind {
109            PpTokenKind::Ident => self.value,
110            _ => None,
111        }
112    }
113
114    /// Whether this is the empty token `##` uses for an absent argument.
115    #[inline]
116    pub fn is_placemarker(self) -> bool {
117        self.placemarker
118    }
119
120    /// The span to report a diagnostic about this token at.
121    ///
122    /// The invocation point when there is one, because a user reading an error wants the
123    /// line they wrote, not a line in a header they have never opened.
124    #[inline]
125    pub fn report_span(self) -> Span {
126        if self.expansion.is_dummy() { self.span } else { self.expansion }
127    }
128
129    /// A token the preprocessor made up rather than read.
130    ///
131    /// The `1` and `0` that `defined` turns into, and the tokens a `_Pragma` string
132    /// destringizes to. They point at the construct that produced them, because there is no
133    /// file byte to point at instead.
134    pub(crate) fn synthetic(
135        kind: PpTokenKind,
136        value: Option<Symbol>,
137        flags: TokenFlags,
138        span: Span,
139    ) -> Tok {
140        Tok {
141            kind,
142            flags,
143            value,
144            span,
145            expansion: Span::DUMMY,
146            trace: TraceId::NONE,
147            hides: HideSet::EMPTY,
148            placemarker: false,
149        }
150    }
151
152    /// A placemarker at `span`.
153    pub(crate) fn placemarker_at(span: Span) -> Tok {
154        Tok {
155            kind: PpTokenKind::Other,
156            flags: TokenFlags::EMPTY,
157            value: None,
158            span,
159            expansion: Span::DUMMY,
160            trace: TraceId::NONE,
161            hides: HideSet::EMPTY,
162            placemarker: true,
163        }
164    }
165}
166
167#[cfg(test)]
168mod tests {
169    use rucc_diag::Span;
170
171    use super::*;
172
173    fn pp(kind: PpTokenKind) -> PpToken {
174        PpToken { kind, flags: TokenFlags::EMPTY, value: None, span: Span::new(0, 1) }
175    }
176
177    #[test]
178    fn a_lexed_token_starts_with_an_empty_hide_set() {
179        let t = Tok::new(pp(PpTokenKind::Punct(Punct::Plus)));
180        assert_eq!(t.hides, HideSet::EMPTY);
181        assert!(t.expansion.is_dummy());
182        assert!(!t.is_placemarker());
183    }
184
185    #[test]
186    fn a_placemarker_is_not_a_punctuator() {
187        let t = Tok::placemarker_at(Span::new(3, 3));
188        assert!(t.is_placemarker());
189        assert_eq!(t.punct(), None);
190        assert_eq!(t.ident(), None);
191    }
192
193    #[test]
194    fn a_token_from_a_macro_is_reported_at_the_call() {
195        let mut t = Tok::new(pp(PpTokenKind::Ident));
196        assert_eq!(t.report_span(), Span::new(0, 1));
197        t.expansion = Span::new(40, 44);
198        assert_eq!(t.report_span(), Span::new(40, 44));
199    }
200}