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}