Skip to main content

rucc_lex/
token.rs

1//! Preprocessing tokens, the category that phase 3 produces.
2//!
3//! Design: `spec/05-preprocessor.md` section 5.1 for what a pp-token is, and section 5.2 for
4//! why one is sixteen bytes.
5//!
6//! A pp-token is not a token. A pp-number is any sequence that looks vaguely numeric, so
7//! `1.2.3` and `0x1p+3` are both one pp-number and only phase 7 has an opinion about which of
8//! them is a constant. A string literal still has its escapes unresolved. Keeping the two
9//! categories apart is what lets the preprocessor paste and stringify text that is not valid
10//! C, which real headers do constantly.
11
12use rucc_base::Symbol;
13use rucc_diag::Span;
14
15/// What a preprocessing token is.
16///
17/// Deliberately not `#[non_exhaustive]`. A new pp-token category has to break every match
18/// that reads one, because there is no sensible default for "some category I have not heard
19/// of" in a preprocessor.
20#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
21pub enum PpTokenKind {
22    /// An identifier, or a keyword. The lexer does not know which; that is phase 7's job and
23    /// it depends on `-std=`, per `spec/06-lexer-and-parser.md` section 6.1.
24    Ident,
25    /// A preprocessing number, in the loose phase 3 grammar.
26    Number,
27    /// A character constant, including any `L`, `u`, `U` or `u8` prefix and both quotes.
28    CharConst,
29    /// A string literal, including any prefix and both quotes.
30    StringLit,
31    /// A `<stdio.h>` or `"local.h"` header name. Only produced by
32    /// [`Lexer::header_name`](crate::Lexer::header_name), because whether one is even
33    /// possible here is a fact about the directive being parsed and the scanner has no way to
34    /// know it.
35    HeaderName,
36    /// A punctuator.
37    Punct(Punct),
38    /// A byte that is not part of any other category, such as a stray backtick. Legal as a
39    /// pp-token, an error by phase 7 unless a macro ate it first.
40    Other,
41    /// End of the file. Carries an empty span at the end so that a diagnostic about a
42    /// truncated construct has somewhere to point.
43    Eof,
44}
45
46/// Things about a token that its own bytes do not say.
47///
48/// The two that matter are on every token: whether it started a line, which is how `#` is
49/// recognised as a directive introducer, and whether anything separated it from the previous
50/// token, which `#` stringification and `-E` output both need.
51#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Hash)]
52pub struct TokenFlags(u8);
53
54impl TokenFlags {
55    /// This token is the first on its logical line.
56    pub const START_OF_LINE: TokenFlags = TokenFlags(1);
57    /// Whitespace, a comment, or a line splice came before this token.
58    pub const LEADING_SPACE: TokenFlags = TokenFlags(2);
59    /// The spelling is not the bytes of the span read literally: a line splice or a trigraph
60    /// sits inside it. Rare enough that everything downstream can take a slow path when it is
61    /// set, and common enough in real headers that pretending it cannot happen is wrong.
62    pub const SPLICED: TokenFlags = TokenFlags(4);
63    /// The punctuator was written in its digraph spelling, `<:` for `[` and so on. The token
64    /// means the same thing either way, and `-E` has to print back what was written.
65    pub const DIGRAPH: TokenFlags = TokenFlags(8);
66
67    /// No flags.
68    pub const EMPTY: TokenFlags = TokenFlags(0);
69
70    /// Whether every flag in `other` is set here.
71    #[inline]
72    #[must_use]
73    pub const fn has(self, other: TokenFlags) -> bool {
74        self.0 & other.0 == other.0
75    }
76
77    /// This set with `other` added.
78    #[inline]
79    #[must_use]
80    pub const fn with(self, other: TokenFlags) -> TokenFlags {
81        TokenFlags(self.0 | other.0)
82    }
83}
84
85/// One preprocessing token.
86///
87/// Sixteen bytes, which `spec/05-preprocessor.md` section 5.2 asks for, and there is a test
88/// below that says so. The size is not vanity: a large translation unit is tens of millions
89/// of these and they are walked repeatedly by the macro expander.
90#[derive(Debug, Clone, Copy, PartialEq, Eq)]
91pub struct PpToken {
92    /// The category.
93    pub kind: PpTokenKind,
94    /// Facts about the token that its bytes do not carry.
95    pub flags: TokenFlags,
96    /// The spelling, interned, for the categories that have one. `None` for punctuators,
97    /// whose spelling is recoverable from the kind, and for end of file.
98    pub value: Option<Symbol>,
99    /// Where the token sits in the source, in real file bytes.
100    pub span: Span,
101}
102
103impl PpToken {
104    /// Whether this is the end of file marker.
105    #[inline]
106    #[must_use]
107    pub const fn is_eof(self) -> bool {
108        matches!(self.kind, PpTokenKind::Eof)
109    }
110
111    /// The punctuator, when this is one.
112    #[inline]
113    #[must_use]
114    pub const fn punct(self) -> Option<Punct> {
115        match self.kind {
116            PpTokenKind::Punct(p) => Some(p),
117            _ => None,
118        }
119    }
120}
121
122/// A punctuator.
123///
124/// The C23 set, including `::`, plus the digraphs, which map onto the punctuator they stand
125/// for rather than getting their own variants. A digraph is a spelling, not a meaning, so the
126/// spelling lives in [`TokenFlags::DIGRAPH`] and everything that reads a punctuator sees one
127/// kind of `[` rather than two.
128#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
129#[repr(u8)]
130pub enum Punct {
131    /// `[`, or the digraph `<:`.
132    LBracket,
133    /// `]`, or the digraph `:>`.
134    RBracket,
135    /// `(`.
136    LParen,
137    /// `)`.
138    RParen,
139    /// `{`, or the digraph `<%`.
140    LBrace,
141    /// `}`, or the digraph `%>`.
142    RBrace,
143    /// `.`.
144    Dot,
145    /// `...`.
146    Ellipsis,
147    /// `->`.
148    Arrow,
149    /// `++`.
150    PlusPlus,
151    /// `--`.
152    MinusMinus,
153    /// `&`.
154    Amp,
155    /// `*`.
156    Star,
157    /// `+`.
158    Plus,
159    /// `-`.
160    Minus,
161    /// `~`.
162    Tilde,
163    /// `!`.
164    Bang,
165    /// `/`.
166    Slash,
167    /// `%`.
168    Percent,
169    /// `<<`.
170    Shl,
171    /// `>>`.
172    Shr,
173    /// `<`.
174    Lt,
175    /// `>`.
176    Gt,
177    /// `<=`.
178    Le,
179    /// `>=`.
180    Ge,
181    /// `==`.
182    EqEq,
183    /// `!=`.
184    Ne,
185    /// `^`.
186    Caret,
187    /// `|`.
188    Pipe,
189    /// `&&`.
190    AmpAmp,
191    /// `||`.
192    PipePipe,
193    /// `?`.
194    Question,
195    /// `:`.
196    Colon,
197    /// `::`, which C23 added for attribute namespaces.
198    ColonColon,
199    /// `;`.
200    Semi,
201    /// `=`.
202    Eq,
203    /// `*=`.
204    StarEq,
205    /// `/=`.
206    SlashEq,
207    /// `%=`.
208    PercentEq,
209    /// `+=`.
210    PlusEq,
211    /// `-=`.
212    MinusEq,
213    /// `<<=`.
214    ShlEq,
215    /// `>>=`.
216    ShrEq,
217    /// `&=`.
218    AmpEq,
219    /// `^=`.
220    CaretEq,
221    /// `|=`.
222    PipeEq,
223    /// `,`.
224    Comma,
225    /// `#`, or the digraph `%:`.
226    Hash,
227    /// `##`, or the digraph `%:%:`.
228    HashHash,
229}
230
231impl Punct {
232    /// The canonical spelling, which is the primary one rather than the digraph.
233    #[must_use]
234    pub const fn as_str(self) -> &'static str {
235        match self {
236            Punct::LBracket => "[",
237            Punct::RBracket => "]",
238            Punct::LParen => "(",
239            Punct::RParen => ")",
240            Punct::LBrace => "{",
241            Punct::RBrace => "}",
242            Punct::Dot => ".",
243            Punct::Ellipsis => "...",
244            Punct::Arrow => "->",
245            Punct::PlusPlus => "++",
246            Punct::MinusMinus => "--",
247            Punct::Amp => "&",
248            Punct::Star => "*",
249            Punct::Plus => "+",
250            Punct::Minus => "-",
251            Punct::Tilde => "~",
252            Punct::Bang => "!",
253            Punct::Slash => "/",
254            Punct::Percent => "%",
255            Punct::Shl => "<<",
256            Punct::Shr => ">>",
257            Punct::Lt => "<",
258            Punct::Gt => ">",
259            Punct::Le => "<=",
260            Punct::Ge => ">=",
261            Punct::EqEq => "==",
262            Punct::Ne => "!=",
263            Punct::Caret => "^",
264            Punct::Pipe => "|",
265            Punct::AmpAmp => "&&",
266            Punct::PipePipe => "||",
267            Punct::Question => "?",
268            Punct::Colon => ":",
269            Punct::ColonColon => "::",
270            Punct::Semi => ";",
271            Punct::Eq => "=",
272            Punct::StarEq => "*=",
273            Punct::SlashEq => "/=",
274            Punct::PercentEq => "%=",
275            Punct::PlusEq => "+=",
276            Punct::MinusEq => "-=",
277            Punct::ShlEq => "<<=",
278            Punct::ShrEq => ">>=",
279            Punct::AmpEq => "&=",
280            Punct::CaretEq => "^=",
281            Punct::PipeEq => "|=",
282            Punct::Comma => ",",
283            Punct::Hash => "#",
284            Punct::HashHash => "##",
285        }
286    }
287}
288
289#[cfg(test)]
290mod tests {
291    use super::*;
292
293    #[test]
294    fn a_pp_token_is_sixteen_bytes() {
295        // spec/05-preprocessor.md section 5.2. A large translation unit holds tens of
296        // millions of these, so this is a real budget rather than a decoration.
297        assert_eq!(size_of::<PpToken>(), 16);
298    }
299
300    #[test]
301    fn flags_are_a_set() {
302        let f = TokenFlags::EMPTY.with(TokenFlags::START_OF_LINE).with(TokenFlags::LEADING_SPACE);
303        assert!(f.has(TokenFlags::START_OF_LINE));
304        assert!(f.has(TokenFlags::LEADING_SPACE));
305        assert!(!f.has(TokenFlags::SPLICED));
306    }
307
308    #[test]
309    fn every_punctuator_spells_something() {
310        // Catches a variant added without a spelling, which would otherwise only show up as
311        // wrong `-E` output on the one line that used it.
312        for p in [Punct::LBracket, Punct::HashHash, Punct::ColonColon, Punct::ShrEq] {
313            assert!(!p.as_str().is_empty());
314        }
315    }
316}