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 /// The punctuator this token is, if it is one.
79 #[inline]
80 pub fn punct(self) -> Option<Punct> {
81 match self.kind {
82 PpTokenKind::Punct(p) if !self.placemarker => Some(p),
83 _ => None,
84 }
85 }
86
87 /// Whether this token is the punctuator `p`.
88 #[inline]
89 pub fn is(self, p: Punct) -> bool {
90 self.punct() == Some(p)
91 }
92
93 /// The identifier this token is, if it is one.
94 #[inline]
95 pub fn ident(self) -> Option<Symbol> {
96 match self.kind {
97 PpTokenKind::Ident => self.value,
98 _ => None,
99 }
100 }
101
102 /// Whether this is the empty token `##` uses for an absent argument.
103 #[inline]
104 pub fn is_placemarker(self) -> bool {
105 self.placemarker
106 }
107
108 /// The span to report a diagnostic about this token at.
109 ///
110 /// The invocation point when there is one, because a user reading an error wants the
111 /// line they wrote, not a line in a header they have never opened.
112 #[inline]
113 pub fn report_span(self) -> Span {
114 if self.expansion.is_dummy() { self.span } else { self.expansion }
115 }
116
117 /// A token the preprocessor made up rather than read.
118 ///
119 /// The `1` and `0` that `defined` turns into, and the tokens a `_Pragma` string
120 /// destringizes to. They point at the construct that produced them, because there is no
121 /// file byte to point at instead.
122 pub(crate) fn synthetic(
123 kind: PpTokenKind,
124 value: Option<Symbol>,
125 flags: TokenFlags,
126 span: Span,
127 ) -> Tok {
128 Tok {
129 kind,
130 flags,
131 value,
132 span,
133 expansion: Span::DUMMY,
134 trace: TraceId::NONE,
135 hides: HideSet::EMPTY,
136 placemarker: false,
137 }
138 }
139
140 /// A placemarker at `span`.
141 pub(crate) fn placemarker_at(span: Span) -> Tok {
142 Tok {
143 kind: PpTokenKind::Other,
144 flags: TokenFlags::EMPTY,
145 value: None,
146 span,
147 expansion: Span::DUMMY,
148 trace: TraceId::NONE,
149 hides: HideSet::EMPTY,
150 placemarker: true,
151 }
152 }
153}
154
155#[cfg(test)]
156mod tests {
157 use rucc_diag::Span;
158
159 use super::*;
160
161 fn pp(kind: PpTokenKind) -> PpToken {
162 PpToken { kind, flags: TokenFlags::EMPTY, value: None, span: Span::new(0, 1) }
163 }
164
165 #[test]
166 fn a_lexed_token_starts_with_an_empty_hide_set() {
167 let t = Tok::new(pp(PpTokenKind::Punct(Punct::Plus)));
168 assert_eq!(t.hides, HideSet::EMPTY);
169 assert!(t.expansion.is_dummy());
170 assert!(!t.is_placemarker());
171 }
172
173 #[test]
174 fn a_placemarker_is_not_a_punctuator() {
175 let t = Tok::placemarker_at(Span::new(3, 3));
176 assert!(t.is_placemarker());
177 assert_eq!(t.punct(), None);
178 assert_eq!(t.ident(), None);
179 }
180
181 #[test]
182 fn a_token_from_a_macro_is_reported_at_the_call() {
183 let mut t = Tok::new(pp(PpTokenKind::Ident));
184 assert_eq!(t.report_span(), Span::new(0, 1));
185 t.expansion = Span::new(40, 44);
186 assert_eq!(t.report_span(), Span::new(40, 44));
187 }
188}