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}