Skip to main content

rucc_lex/
lib.rs

1//! Translation phases 1 to 3, pp-tokens, the fast scanner, the keyword table and the constants.
2//!
3//! Design: `spec/05-preprocessor.md` sections 5.1 and 5.2, and `spec/06-lexer-and-parser.md`
4//! section 6.1 for what happens to these tokens next. Layer rank 4, see
5//! `spec/18-package-layout.md`.
6//!
7//! This is the hottest loop in the compiler at `-O0`, and it is also the place where being
8//! clever costs correctness, so the two shapes it takes are worth stating plainly.
9//!
10//! Phases 1 and 2 are resolved lazily by the cursor, never by rewriting the buffer. A span is
11//! always a range of real bytes in the file the user wrote, even when the token's spelling is
12//! not those bytes read in order, and a token that crossed a splice or a trigraph says so
13//! through [`TokenFlags::SPLICED`].
14//!
15//! Phase 3 is a loop over a 256-entry dispatch table, and identifiers are interned during the
16//! scan rather than in a second pass, so nothing after this crate ever compares identifier
17//! text. Whitespace and comment bodies, which are most of the bytes and none of the meaning,
18//! are skipped a word at a time rather than a byte at a time.
19//!
20//! [`Keywords`] is the first half of phase 7 and the reason the interner is here rather than
21//! in the parser. The keyword spellings are interned before any source is read, so they are
22//! one run of symbols at the bottom of the table and recognising one is a subtraction and a
23//! bounds check. Which of them the dialect actually has is resolved once, when the table is
24//! built, rather than at every identifier.
25//!
26//! [`integer`] is the next piece of it. A preprocessing number is deliberately looser than a
27//! constant, so nothing before this point has asked what `0x1p+3` or `1.2.3` means, and the
28//! type a constant ends up with is a table walk whose candidate list depends on the base, the
29//! suffix and the dialect. The value is accumulated in a hundred and twenty eight bits with
30//! every step checked, so a constant too large for any type is a diagnostic rather than a
31//! number nobody wrote.
32//!
33//! ```
34//! use rucc_base::Interner;
35//! use rucc_lex::{Options, PpTokenKind, tokenize};
36//!
37//! let mut interner = Interner::new();
38//! let (tokens, diagnostics) = tokenize(b"int x = 1;", 0, Options::new(), &mut interner);
39//! assert!(diagnostics.is_empty());
40//! assert_eq!(tokens[0].kind, PpTokenKind::Ident);
41//! assert_eq!(interner.resolve(tokens[0].value.unwrap()), "int");
42//! ```
43//!
44//! # Status
45//!
46//! Phases 1 to 3 are real, along with the pp-token model, the dispatch table, interning during
47//! the scan, and the word at a time skips for whitespace and comment bodies. The bytes arrive
48//! as a memory mapping when the file is large enough for that to be worth it, which the driver
49//! decides and nothing here can tell. Phases 4 to 6, which is directives and macro expansion,
50//! belong to `rucc-pp`.
51//!
52//! Of phase 7, the keywords, the dialect gate and the integer constants are here. The floating
53//! constants, the escape sequences and the encoding prefixes are not yet, and neither is the
54//! `Token` the parser will read.
55//!
56//! Every crate in the workspace is published, and publishing implies a promise. This one is
57//! tier 3: its Rust API is explicitly unstable and will change without a major version bump.
58//! Depend on the `rucc` binary's behaviour, not on this.
59
60#![doc(html_root_url = "https://docs.rs/rucc-lex/0.2.2")]
61
62mod class;
63mod cursor;
64mod keyword;
65mod lexer;
66mod number;
67mod swar;
68mod token;
69
70pub use crate::keyword::{Keyword, Keywords};
71pub use crate::lexer::{Lexer, Options, tokenize};
72pub use crate::number::{IntConstant, IntConstantType, IntError, Remarks, integer};
73pub use crate::token::{PpToken, PpTokenKind, Punct, TokenFlags};
74
75/// The milestone in `spec/17-milestones.md` that fills this crate in.
76pub const MILESTONE: &str = "M1";
77
78#[cfg(test)]
79mod tests {
80    use rucc_base::Interner;
81
82    use super::*;
83
84    /// The kinds and spellings of every token in `src`, which is what almost every test here
85    /// wants to assert on.
86    fn scan(src: &str) -> (Vec<(PpTokenKind, String)>, Vec<String>) {
87        let mut interner = Interner::new();
88        let (tokens, diagnostics) = tokenize(src.as_bytes(), 0, Options::new(), &mut interner);
89        let out = tokens
90            .iter()
91            .filter(|t| !t.is_eof())
92            .map(|t| {
93                let text = match t.value {
94                    Some(sym) => interner.resolve(sym).to_owned(),
95                    None => t.punct().map_or_else(String::new, |p| p.as_str().to_owned()),
96                };
97                (t.kind, text)
98            })
99            .collect();
100        (out, diagnostics.iter().map(|d| d.message.clone()).collect())
101    }
102
103    fn spellings(src: &str) -> Vec<String> {
104        scan(src).0.into_iter().map(|(_, text)| text).collect()
105    }
106
107    #[test]
108    fn a_declaration_lexes_into_the_tokens_it_looks_like() {
109        let (tokens, diagnostics) = scan("int x = 1;");
110        assert!(diagnostics.is_empty());
111        assert_eq!(
112            tokens,
113            vec![
114                (PpTokenKind::Ident, "int".to_owned()),
115                (PpTokenKind::Ident, "x".to_owned()),
116                (PpTokenKind::Punct(Punct::Eq), "=".to_owned()),
117                (PpTokenKind::Number, "1".to_owned()),
118                (PpTokenKind::Punct(Punct::Semi), ";".to_owned()),
119            ]
120        );
121    }
122
123    #[test]
124    fn punctuators_take_the_longest_match() {
125        assert_eq!(spellings(">>="), vec![">>="]);
126        assert_eq!(spellings(">> ="), vec![">>", "="]);
127        assert_eq!(spellings("a->b"), vec!["a", "->", "b"]);
128        assert_eq!(spellings("x+++y"), vec!["x", "++", "+", "y"]);
129        assert_eq!(spellings("..."), vec!["..."]);
130        assert_eq!(spellings(".."), vec![".", "."]);
131        assert_eq!(spellings("[[gnu::packed]]"), vec!["[", "[", "gnu", "::", "packed", "]", "]"]);
132    }
133
134    #[test]
135    fn digraphs_mean_the_same_thing_as_what_they_stand_for() {
136        let (tokens, _) = scan("<% <: %: %:%: :> %>");
137        let kinds: Vec<_> = tokens.iter().map(|(k, _)| *k).collect();
138        assert_eq!(
139            kinds,
140            vec![
141                PpTokenKind::Punct(Punct::LBrace),
142                PpTokenKind::Punct(Punct::LBracket),
143                PpTokenKind::Punct(Punct::Hash),
144                PpTokenKind::Punct(Punct::HashHash),
145                PpTokenKind::Punct(Punct::RBracket),
146                PpTokenKind::Punct(Punct::RBrace),
147            ]
148        );
149    }
150
151    #[test]
152    fn a_digraph_says_it_was_written_as_one() {
153        let mut interner = Interner::new();
154        let (tokens, _) = tokenize(b"<: [", 0, Options::new(), &mut interner);
155        assert!(tokens[0].flags.has(TokenFlags::DIGRAPH));
156        assert!(!tokens[1].flags.has(TokenFlags::DIGRAPH));
157    }
158
159    #[test]
160    fn a_pp_number_is_looser_than_a_constant() {
161        // Both of these are one pp-token. Only phase 7 has an opinion about `1.2.3`, and
162        // splitting it here would break `##` pasting that assembles a number from pieces.
163        assert_eq!(spellings("0x1p+3"), vec!["0x1p+3"]);
164        assert_eq!(spellings("1.2.3"), vec!["1.2.3"]);
165        assert_eq!(spellings(".5f"), vec![".5f"]);
166        assert_eq!(spellings("1e-9"), vec!["1e-9"]);
167        assert_eq!(spellings("0b1010"), vec!["0b1010"]);
168        assert_eq!(spellings("42wb"), vec!["42wb"]);
169    }
170
171    #[test]
172    fn c23_digit_separators_stay_inside_the_number() {
173        assert_eq!(spellings("1'000'000"), vec!["1'000'000"]);
174        // The apostrophe only separates when an identifier character follows, so this is a
175        // number and then a character constant rather than one very confused number.
176        assert_eq!(spellings("1 'a'"), vec!["1", "'a'"]);
177    }
178
179    #[test]
180    fn literal_prefixes_belong_to_the_literal() {
181        let (tokens, _) = scan(r#"L"wide" u8"utf8" u'c' U"big" L'w' u8'x'"#);
182        let kinds: Vec<_> = tokens.iter().map(|(k, _)| *k).collect();
183        assert_eq!(
184            kinds,
185            vec![
186                PpTokenKind::StringLit,
187                PpTokenKind::StringLit,
188                PpTokenKind::CharConst,
189                PpTokenKind::StringLit,
190                PpTokenKind::CharConst,
191                PpTokenKind::CharConst,
192            ]
193        );
194        assert_eq!(tokens[0].1, "L\"wide\"");
195    }
196
197    #[test]
198    fn an_escaped_quote_does_not_end_a_literal() {
199        assert_eq!(spellings(r#""a\"b" x"#), vec![r#""a\"b""#, "x"]);
200        assert_eq!(spellings(r"'\\' y"), vec![r"'\\'", "y"]);
201    }
202
203    #[test]
204    fn a_literal_does_not_run_past_the_end_of_its_line() {
205        // One missing quote must not swallow the rest of the file, which is the difference
206        // between one error and a hundred.
207        let (tokens, diagnostics) = scan("char *s = \"oops;\nint x;");
208        assert_eq!(diagnostics.len(), 1);
209        assert!(diagnostics[0].contains("missing terminating quote"));
210        assert!(tokens.iter().any(|(k, text)| *k == PpTokenKind::Ident && text == "int"));
211    }
212
213    #[test]
214    fn comments_are_whitespace_and_leave_a_space_behind() {
215        assert_eq!(spellings("a/*b*/c"), vec!["a", "c"]);
216        assert_eq!(spellings("a//b\nc"), vec!["a", "c"]);
217        let mut interner = Interner::new();
218        let (tokens, _) = tokenize(b"a/*b*/c", 0, Options::new(), &mut interner);
219        assert!(tokens[1].flags.has(TokenFlags::LEADING_SPACE));
220    }
221
222    #[test]
223    fn an_unterminated_comment_is_reported_once() {
224        let (_, diagnostics) = scan("int x; /* and then nothing");
225        assert_eq!(diagnostics, vec!["unterminated comment".to_owned()]);
226    }
227
228    #[test]
229    fn a_token_after_a_comment_that_crossed_a_line_still_starts_a_line() {
230        // `# define` after a multi-line comment is a directive. GCC agrees, and real headers
231        // are written this way, so getting it wrong means silently dropping a definition.
232        let mut interner = Interner::new();
233        let (tokens, _) = tokenize(b"x /*\n*/ #define F 1", 0, Options::new(), &mut interner);
234        assert!(!tokens[0].flags.has(TokenFlags::START_OF_LINE) || tokens[0].span.lo == 0);
235        assert!(tokens[1].flags.has(TokenFlags::START_OF_LINE));
236        assert_eq!(tokens[1].punct(), Some(Punct::Hash));
237    }
238
239    #[test]
240    fn the_first_token_of_a_line_says_so() {
241        let mut interner = Interner::new();
242        let (tokens, _) = tokenize(b"a b\nc", 0, Options::new(), &mut interner);
243        assert!(tokens[0].flags.has(TokenFlags::START_OF_LINE));
244        assert!(!tokens[1].flags.has(TokenFlags::START_OF_LINE));
245        assert!(tokens[2].flags.has(TokenFlags::START_OF_LINE));
246    }
247
248    #[test]
249    fn a_splice_joins_one_identifier_and_the_span_still_covers_real_bytes() {
250        let mut interner = Interner::new();
251        let (tokens, diagnostics) = tokenize(b"in\\\nt", 0, Options::new(), &mut interner);
252        assert!(diagnostics.is_empty());
253        assert_eq!(interner.resolve(tokens[0].value.unwrap()), "int");
254        assert!(tokens[0].flags.has(TokenFlags::SPLICED));
255        // The span covers all five bytes of the file, backslash and newline included, which
256        // is what a caret under the identifier has to underline.
257        assert_eq!(tokens[0].span.lo, 0);
258        assert_eq!(tokens[0].span.hi, 5);
259    }
260
261    #[test]
262    fn a_splice_inside_a_punctuator_still_makes_one_punctuator() {
263        let mut interner = Interner::new();
264        let (tokens, _) = tokenize(b">\\\n>=", 0, Options::new(), &mut interner);
265        assert_eq!(tokens[0].punct(), Some(Punct::ShrEq));
266        assert!(tokens[0].flags.has(TokenFlags::SPLICED));
267    }
268
269    #[test]
270    fn a_clean_token_is_not_marked_spliced() {
271        let mut interner = Interner::new();
272        let (tokens, _) = tokenize(b"int", 0, Options::new(), &mut interner);
273        assert!(!tokens[0].flags.has(TokenFlags::SPLICED));
274    }
275
276    #[test]
277    fn trigraphs_are_off_by_default() {
278        let (tokens, _) = scan("??=define");
279        assert_eq!(tokens[0].0, PpTokenKind::Punct(Punct::Question));
280        let mut interner = Interner::new();
281        let opts = Options { trigraphs: true };
282        let (on, _) = tokenize(b"??=define", 0, opts, &mut interner);
283        assert_eq!(on[0].punct(), Some(Punct::Hash));
284        assert_eq!(interner.resolve(on[1].value.unwrap()), "define");
285    }
286
287    /// The whitespace and comment scans move the head over runs of bytes without reading them,
288    /// which is only allowed because no byte they pass can be one phases 1 and 2 rewrite. These
289    /// are the inputs that say whether that is actually true, and they are here rather than
290    /// next to the scans because what they check is the answer, not the arithmetic.
291    #[test]
292    fn a_line_comment_ends_where_a_splice_says_it_does() {
293        // A backslash at the end of a line continues the comment onto the next one, so `c` is
294        // still commented out and only `a` and `d` survive. A scan that ran to the newline
295        // without looking would bring `c` back.
296        assert_eq!(spellings("a //b\\\nc\nd"), vec!["a", "d"]);
297        assert_eq!(spellings("a //b\\\r\nc\nd"), vec!["a", "d"]);
298        // Long enough that the run is whole words rather than the tail, which is the path the
299        // short cases above never take.
300        assert_eq!(spellings("a //bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\\\nc\nd"), vec!["a", "d"]);
301    }
302
303    #[test]
304    fn a_trigraph_backslash_still_continues_a_comment_it_is_at_the_end_of() {
305        // `??/` is a backslash, and phase 1 runs before phase 2, so this splices as well. Worth
306        // its own test because the fast scan only stops on `?` when trigraphs are on, so this
307        // is the case where the two settings have to disagree.
308        let mut interner = Interner::new();
309        let src = b"a //bbbbbbbbbbbbbbbb??/\nc\nd";
310        let (on, _) = tokenize(src, 0, Options { trigraphs: true }, &mut interner);
311        let text: Vec<_> = on
312            .iter()
313            .filter(|t| !t.is_eof())
314            .filter_map(|t| t.value.map(|s| interner.resolve(s).to_owned()))
315            .collect();
316        assert_eq!(text, vec!["a", "d"]);
317        // With trigraphs off the same bytes are just a comment ending at the newline, and `c`
318        // is a real token.
319        assert_eq!(spellings("a //bbbbbbbbbbbbbbbb??/\nc\nd"), vec!["a", "c", "d"]);
320    }
321
322    #[test]
323    fn a_block_comment_is_still_terminated_when_the_stars_are_a_long_way_in() {
324        // The body scan stops on `*` and on the newline, so this walks it in a few steps
325        // instead of a few hundred, and has to come out at the same place either way.
326        let body = "x".repeat(200);
327        assert_eq!(spellings(&format!("a /*{body}*/ b")), vec!["a", "b"]);
328        assert_eq!(spellings(&format!("a /*{body}\n{body}*/ b")), vec!["a", "b"]);
329        // A `*` that is not the end must not end it.
330        assert_eq!(spellings(&format!("a /*{body}*{body}*/ b")), vec!["a", "b"]);
331        // And an unterminated one is still reported once rather than run off the end.
332        let (_, diagnostics) = scan(&format!("a /*{body}"));
333        assert_eq!(diagnostics, vec!["unterminated comment".to_owned()]);
334    }
335
336    #[test]
337    fn a_spliced_comment_opener_is_not_missed_by_the_whitespace_scan() {
338        // `/\<newline>*` is a block comment opener spelled across two lines. The blank run
339        // before it must stop at the backslash rather than carry on, or the `/` and the `*`
340        // come out as two punctuators and the comment body becomes program text.
341        assert_eq!(spellings("a        /\\\n* body *\\\n/ b"), vec!["a", "b"]);
342    }
343
344    #[test]
345    fn a_long_run_of_indentation_leaves_exactly_one_space_behind() {
346        // Whatever the scan does to the head, the flag it sets has to be the same one the
347        // byte at a time loop set.
348        let mut interner = Interner::new();
349        let src = format!("a{}b", " ".repeat(100));
350        let (tokens, _) = tokenize(src.as_bytes(), 0, Options::new(), &mut interner);
351        assert!(tokens[1].flags.has(TokenFlags::LEADING_SPACE));
352        assert!(!tokens[1].flags.has(TokenFlags::START_OF_LINE));
353        // A tab run reaches the same conclusion, and a run that ends at a newline gives the
354        // next token a line start rather than a space.
355        let src = format!("a{}\nb", "\t".repeat(100));
356        let (tokens, _) = tokenize(src.as_bytes(), 0, Options::new(), &mut interner);
357        assert!(tokens[1].flags.has(TokenFlags::START_OF_LINE));
358    }
359
360    #[test]
361    fn a_stray_byte_is_a_token_rather_than_a_hard_stop() {
362        // A pp-token that is nothing else is legal here and only becomes an error in phase 7,
363        // because a macro is allowed to consume it first.
364        let (tokens, diagnostics) = scan("a ` b");
365        assert!(diagnostics.is_empty());
366        assert_eq!(tokens[1].0, PpTokenKind::Other);
367        assert_eq!(tokens[1].1, "`");
368    }
369
370    #[test]
371    fn a_file_that_is_only_whitespace_lexes_to_end_of_file() {
372        let mut interner = Interner::new();
373        let (tokens, diagnostics) = tokenize(b"  \n\t\n", 0, Options::new(), &mut interner);
374        assert!(diagnostics.is_empty());
375        assert_eq!(tokens.len(), 1);
376        assert!(tokens[0].is_eof());
377    }
378
379    #[test]
380    fn the_empty_file_lexes_to_end_of_file() {
381        let mut interner = Interner::new();
382        let (tokens, _) = tokenize(b"", 0, Options::new(), &mut interner);
383        assert_eq!(tokens.len(), 1);
384        assert!(tokens[0].is_eof());
385    }
386
387    #[test]
388    fn spans_are_offset_by_where_the_file_sits() {
389        // One flat coordinate space across the translation unit, per `rucc-diag`, so a file
390        // that is not the first one still produces spans nobody has to translate.
391        let mut interner = Interner::new();
392        let (tokens, _) = tokenize(b"ab", 1000, Options::new(), &mut interner);
393        assert_eq!(tokens[0].span.lo, 1000);
394        assert_eq!(tokens[0].span.hi, 1002);
395    }
396
397    #[test]
398    fn a_header_name_is_only_scanned_when_a_directive_asks_for_one() {
399        let mut interner = Interner::new();
400        let mut lexer = Lexer::new(b"<stdio.h>", 0, Options::new());
401        let header = lexer.header_name(&mut interner).expect("a header name starts here");
402        assert_eq!(header.kind, PpTokenKind::HeaderName);
403        assert_eq!(interner.resolve(header.value.unwrap()), "<stdio.h>");
404
405        // The same bytes read as ordinary tokens are comparisons, which is exactly why the
406        // scanner refuses to guess and the directive has to ask.
407        let (tokens, _) = scan("<stdio.h>");
408        assert_eq!(tokens[0].0, PpTokenKind::Punct(Punct::Lt));
409    }
410
411    #[test]
412    fn a_quoted_header_name_works_and_a_computed_one_declines() {
413        let mut interner = Interner::new();
414        let mut lexer = Lexer::new(b" \"local.h\"", 0, Options::new());
415        let header = lexer.header_name(&mut interner).expect("a header name starts here");
416        assert_eq!(interner.resolve(header.value.unwrap()), "\"local.h\"");
417
418        let mut lexer = Lexer::new(b"MACRO_NAME", 0, Options::new());
419        assert!(lexer.header_name(&mut interner).is_none());
420    }
421
422    #[test]
423    fn identifiers_are_interned_during_the_scan_and_repeat_for_free() {
424        let mut interner = Interner::new();
425        let (tokens, _) = tokenize(b"foo bar foo", 0, Options::new(), &mut interner);
426        assert_eq!(tokens[0].value, tokens[2].value);
427        assert_ne!(tokens[0].value, tokens[1].value);
428        assert_eq!(interner.len(), 2);
429    }
430
431    #[test]
432    fn a_universal_character_name_is_part_of_the_identifier() {
433        // Whether `é` names something that may appear in an identifier depends on
434        // `-std=`, so phase 3 only has to keep it attached to the identifier it was written
435        // in. Splitting it here would turn one name into three tokens.
436        let (tokens, diagnostics) = scan(r"café = 1;");
437        assert!(diagnostics.is_empty());
438        assert_eq!(tokens[0], (PpTokenKind::Ident, r"café".to_owned()));
439        assert_eq!(tokens[1].0, PpTokenKind::Punct(Punct::Eq));
440    }
441
442    #[test]
443    fn a_backslash_with_a_trailing_space_splices_and_says_so() {
444        // GCC warns and splices. Both halves matter: a lot of existing code has a stray space
445        // after a backslash in a macro definition, and the space is invisible in an editor,
446        // so the one time it changes the meaning nobody can see why.
447        let (tokens, diagnostics) = scan("in\\  \nt x;");
448        assert_eq!(tokens[0], (PpTokenKind::Ident, "int".to_owned()));
449        assert_eq!(
450            diagnostics,
451            vec!["backslash and line ending separated by whitespace".to_owned()]
452        );
453    }
454
455    #[test]
456    fn utf8_in_an_identifier_survives_the_scan() {
457        let (tokens, diagnostics) = scan("café = 1;");
458        assert!(diagnostics.is_empty());
459        assert_eq!(tokens[0].1, "café");
460    }
461
462    #[test]
463    fn every_byte_of_the_file_ends_up_in_exactly_one_span_or_in_trivia() {
464        // The property that keeps `-E` honest: spans never overlap and never run backwards.
465        let src = "int main(void) { return 0; } /* c */ \"s\" 'c' 1.5e+3 // end\n";
466        let mut interner = Interner::new();
467        let (tokens, _) = tokenize(src.as_bytes(), 0, Options::new(), &mut interner);
468        let mut last = 0;
469        for t in &tokens {
470            assert!(t.span.lo >= last, "spans went backwards at {:?}", t.kind);
471            assert!(t.span.hi >= t.span.lo);
472            last = t.span.hi;
473        }
474        assert_eq!(last as usize, src.len());
475    }
476
477    #[test]
478    fn milestone_is_recorded() {
479        assert!(MILESTONE.starts_with('M'));
480    }
481}