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    /// This set with every flag in `other` taken off.
85    #[inline]
86    #[must_use]
87    pub const fn without(self, other: TokenFlags) -> TokenFlags {
88        TokenFlags(self.0 & !other.0)
89    }
90}
91
92/// One preprocessing token.
93///
94/// Sixteen bytes, which `spec/05-preprocessor.md` section 5.2 asks for, and there is a test
95/// below that says so. The size is not vanity: a large translation unit is tens of millions
96/// of these and they are walked repeatedly by the macro expander.
97#[derive(Debug, Clone, Copy, PartialEq, Eq)]
98pub struct PpToken {
99    /// The category.
100    pub kind: PpTokenKind,
101    /// Facts about the token that its bytes do not carry.
102    pub flags: TokenFlags,
103    /// The spelling, interned, for the categories that have one. `None` for punctuators,
104    /// whose spelling is recoverable from the kind, and for end of file.
105    pub value: Option<Symbol>,
106    /// Where the token sits in the source, in real file bytes.
107    pub span: Span,
108}
109
110impl PpToken {
111    /// Whether this is the end of file marker.
112    #[inline]
113    #[must_use]
114    pub const fn is_eof(self) -> bool {
115        matches!(self.kind, PpTokenKind::Eof)
116    }
117
118    /// The punctuator, when this is one.
119    #[inline]
120    #[must_use]
121    pub const fn punct(self) -> Option<Punct> {
122        match self.kind {
123            PpTokenKind::Punct(p) => Some(p),
124            _ => None,
125        }
126    }
127}
128
129/// A punctuator.
130///
131/// The C23 set, including `::`, plus the digraphs, which map onto the punctuator they stand
132/// for rather than getting their own variants. A digraph is a spelling, not a meaning, so the
133/// spelling lives in [`TokenFlags::DIGRAPH`] and everything that reads a punctuator sees one
134/// kind of `[` rather than two.
135#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
136#[repr(u8)]
137pub enum Punct {
138    /// `[`, or the digraph `<:`.
139    LBracket,
140    /// `]`, or the digraph `:>`.
141    RBracket,
142    /// `(`.
143    LParen,
144    /// `)`.
145    RParen,
146    /// `{`, or the digraph `<%`.
147    LBrace,
148    /// `}`, or the digraph `%>`.
149    RBrace,
150    /// `.`.
151    Dot,
152    /// `...`.
153    Ellipsis,
154    /// `->`.
155    Arrow,
156    /// `++`.
157    PlusPlus,
158    /// `--`.
159    MinusMinus,
160    /// `&`.
161    Amp,
162    /// `*`.
163    Star,
164    /// `+`.
165    Plus,
166    /// `-`.
167    Minus,
168    /// `~`.
169    Tilde,
170    /// `!`.
171    Bang,
172    /// `/`.
173    Slash,
174    /// `%`.
175    Percent,
176    /// `<<`.
177    Shl,
178    /// `>>`.
179    Shr,
180    /// `<`.
181    Lt,
182    /// `>`.
183    Gt,
184    /// `<=`.
185    Le,
186    /// `>=`.
187    Ge,
188    /// `==`.
189    EqEq,
190    /// `!=`.
191    Ne,
192    /// `^`.
193    Caret,
194    /// `|`.
195    Pipe,
196    /// `&&`.
197    AmpAmp,
198    /// `||`.
199    PipePipe,
200    /// `?`.
201    Question,
202    /// `:`.
203    Colon,
204    /// `::`, which C23 added for attribute namespaces.
205    ColonColon,
206    /// `;`.
207    Semi,
208    /// `=`.
209    Eq,
210    /// `*=`.
211    StarEq,
212    /// `/=`.
213    SlashEq,
214    /// `%=`.
215    PercentEq,
216    /// `+=`.
217    PlusEq,
218    /// `-=`.
219    MinusEq,
220    /// `<<=`.
221    ShlEq,
222    /// `>>=`.
223    ShrEq,
224    /// `&=`.
225    AmpEq,
226    /// `^=`.
227    CaretEq,
228    /// `|=`.
229    PipeEq,
230    /// `,`.
231    Comma,
232    /// `#`, or the digraph `%:`.
233    Hash,
234    /// `##`, or the digraph `%:%:`.
235    HashHash,
236}
237
238impl Punct {
239    /// The canonical spelling, which is the primary one rather than the digraph.
240    #[must_use]
241    pub const fn as_str(self) -> &'static str {
242        match self {
243            Punct::LBracket => "[",
244            Punct::RBracket => "]",
245            Punct::LParen => "(",
246            Punct::RParen => ")",
247            Punct::LBrace => "{",
248            Punct::RBrace => "}",
249            Punct::Dot => ".",
250            Punct::Ellipsis => "...",
251            Punct::Arrow => "->",
252            Punct::PlusPlus => "++",
253            Punct::MinusMinus => "--",
254            Punct::Amp => "&",
255            Punct::Star => "*",
256            Punct::Plus => "+",
257            Punct::Minus => "-",
258            Punct::Tilde => "~",
259            Punct::Bang => "!",
260            Punct::Slash => "/",
261            Punct::Percent => "%",
262            Punct::Shl => "<<",
263            Punct::Shr => ">>",
264            Punct::Lt => "<",
265            Punct::Gt => ">",
266            Punct::Le => "<=",
267            Punct::Ge => ">=",
268            Punct::EqEq => "==",
269            Punct::Ne => "!=",
270            Punct::Caret => "^",
271            Punct::Pipe => "|",
272            Punct::AmpAmp => "&&",
273            Punct::PipePipe => "||",
274            Punct::Question => "?",
275            Punct::Colon => ":",
276            Punct::ColonColon => "::",
277            Punct::Semi => ";",
278            Punct::Eq => "=",
279            Punct::StarEq => "*=",
280            Punct::SlashEq => "/=",
281            Punct::PercentEq => "%=",
282            Punct::PlusEq => "+=",
283            Punct::MinusEq => "-=",
284            Punct::ShlEq => "<<=",
285            Punct::ShrEq => ">>=",
286            Punct::AmpEq => "&=",
287            Punct::CaretEq => "^=",
288            Punct::PipeEq => "|=",
289            Punct::Comma => ",",
290            Punct::Hash => "#",
291            Punct::HashHash => "##",
292        }
293    }
294}
295
296#[cfg(test)]
297mod tests {
298    use super::*;
299
300    #[test]
301    fn a_pp_token_is_sixteen_bytes() {
302        // spec/05-preprocessor.md section 5.2. A large translation unit holds tens of
303        // millions of these, so this is a real budget rather than a decoration.
304        assert_eq!(size_of::<PpToken>(), 16);
305    }
306
307    #[test]
308    fn flags_are_a_set() {
309        let f = TokenFlags::EMPTY.with(TokenFlags::START_OF_LINE).with(TokenFlags::LEADING_SPACE);
310        assert!(f.has(TokenFlags::START_OF_LINE));
311        assert!(f.has(TokenFlags::LEADING_SPACE));
312        assert!(!f.has(TokenFlags::SPLICED));
313    }
314
315    #[test]
316    fn every_punctuator_spells_something() {
317        // Catches a variant added without a spelling, which would otherwise only show up as
318        // wrong `-E` output on the one line that used it.
319        for p in [Punct::LBracket, Punct::HashHash, Punct::ColonColon, Punct::ShrEq] {
320            assert!(!p.as_str().is_empty());
321        }
322    }
323}