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}