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`] and [`floating`] are the next piece of it. A preprocessing number is deliberately
27//! looser than a constant, so nothing before this point has asked what `0x1p+3` or `1.2.3`
28//! means. The type an integer constant ends up with is a table walk whose candidate list depends
29//! on the base, the suffix and the dialect, and the value is accumulated in a hundred and twenty
30//! eight bits with every step checked, so a constant too large for any type is a diagnostic
31//! rather than a number nobody wrote. A floating constant takes its type from its suffix, of
32//! which there are many more than the standard's three, and its value from the correctly rounded
33//! software conversion in `rucc-base`, so that the bits do not depend on the machine the
34//! compiler is running on.
35//!
36//! [`character`] and [`string`] finish the spellings. What an element of a literal is depends on
37//! the encoding prefix and, for a wide one, on the target, so a wide string is UTF-16 on Windows
38//! and UTF-32 everywhere else and is not even the same length in both. The escapes divide into
39//! the ones that name a character, which get encoded, and the ones that write a value, which do
40//! not and are truncated to the element instead.
41//!
42//! [`convert`] is the end of it. It walks a stream of pp-tokens and produces [`Token`]s: an
43//! identifier becomes a keyword when the dialect has that spelling, a number becomes a typed
44//! value, a run of adjacent string literals becomes the one literal it is, and a stray byte
45//! becomes the error it always was. A `Token` is sixteen bytes like a pp-token, so the values do
46//! not live in it; they live in vectors beside it and the token holds an index.
47//!
48//! ```
49//! use rucc_base::Interner;
50//! use rucc_lex::{Options, PpTokenKind, tokenize};
51//!
52//! let mut interner = Interner::new();
53//! let (tokens, diagnostics) = tokenize(b"int x = 1;", 0, Options::new(), &mut interner);
54//! assert!(diagnostics.is_empty());
55//! assert_eq!(tokens[0].kind, PpTokenKind::Ident);
56//! assert_eq!(interner.resolve(tokens[0].value.unwrap()), "int");
57//! ```
58//!
59//! # Status
60//!
61//! Phases 1 to 3 are real, along with the pp-token model, the dispatch table, interning during
62//! the scan, and the word at a time skips for whitespace and comment bodies. The bytes arrive
63//! as a memory mapping when the file is large enough for that to be worth it, which the driver
64//! decides and nothing here can tell. Phases 4 to 6, which is directives and macro expansion,
65//! belong to `rucc-pp`.
66//!
67//! Phase 7 is here too, all of it: the keywords and the dialect gate, the numeric constants, the
68//! literals with their escapes and encoding prefixes, the concatenation of adjacent literals, and
69//! [`convert`], which turns a stream of pp-tokens into the [`Token`]s the parser reads and is
70//! where a remark from a conversion becomes a diagnostic. Decimal floating constants are
71//! recognised and refused, because nothing in the compiler has a decimal floating value to put one
72//! in, and `\N{NAME}` is refused because GCC 13.3 only has it in C++. A universal character name
73//! above the end of Unicode is an error here and a warning in GCC, which is the one place this
74//! crate follows clang instead.
75//!
76//! Every crate in the workspace is published, and publishing implies a promise. This one is
77//! tier 3: its Rust API is explicitly unstable and will change without a major version bump.
78//! Depend on the `rucc` binary's behaviour, not on this.
79
80#![doc(html_root_url = "https://docs.rs/rucc-lex/0.2.21")]
81
82mod class;
83mod convert;
84mod cursor;
85mod keyword;
86mod lexer;
87mod literal;
88mod number;
89mod remarks;
90mod swar;
91mod token;
92
93pub use crate::convert::{Convert, Pragma, Token, TokenKind, Tokens, convert};
94pub use crate::keyword::{Keyword, Keywords};
95pub use crate::lexer::{Lexer, Options, tokenize};
96pub use crate::literal::{
97    CharConstant, Encoding, LiteralError, StringLiteral, character, string, strings,
98};
99pub use crate::number::{
100    FloatConstant, FloatConstantType, FloatError, IntConstant, IntConstantType, IntError, floating,
101    integer,
102};
103pub use crate::remarks::Remarks;
104pub use crate::token::{PpToken, PpTokenKind, Punct, TokenFlags};
105
106/// The milestone in `spec/17-milestones.md` that fills this crate in.
107pub const MILESTONE: &str = "M1";
108
109#[cfg(test)]
110mod tests {
111    use rucc_base::Interner;
112    use rucc_session::Std;
113
114    use super::*;
115
116    /// The kinds and spellings of every token in `src`, which is what almost every test here
117    /// wants to assert on.
118    fn scan(src: &str) -> (Vec<(PpTokenKind, String)>, Vec<String>) {
119        let mut interner = Interner::new();
120        let (tokens, diagnostics) = tokenize(src.as_bytes(), 0, Options::new(), &mut interner);
121        let out = tokens
122            .iter()
123            .filter(|t| !t.is_eof())
124            .map(|t| {
125                let text = match t.value {
126                    Some(sym) => interner.resolve(sym).to_owned(),
127                    None => t.punct().map_or_else(String::new, |p| p.as_str().to_owned()),
128                };
129                (t.kind, text)
130            })
131            .collect();
132        (out, diagnostics.iter().map(|d| d.message.clone()).collect())
133    }
134
135    fn spellings(src: &str) -> Vec<String> {
136        scan(src).0.into_iter().map(|(_, text)| text).collect()
137    }
138
139    #[test]
140    fn a_declaration_lexes_into_the_tokens_it_looks_like() {
141        let (tokens, diagnostics) = scan("int x = 1;");
142        assert!(diagnostics.is_empty());
143        assert_eq!(
144            tokens,
145            vec![
146                (PpTokenKind::Ident, "int".to_owned()),
147                (PpTokenKind::Ident, "x".to_owned()),
148                (PpTokenKind::Punct(Punct::Eq), "=".to_owned()),
149                (PpTokenKind::Number, "1".to_owned()),
150                (PpTokenKind::Punct(Punct::Semi), ";".to_owned()),
151            ]
152        );
153    }
154
155    #[test]
156    fn punctuators_take_the_longest_match() {
157        assert_eq!(spellings(">>="), vec![">>="]);
158        assert_eq!(spellings(">> ="), vec![">>", "="]);
159        assert_eq!(spellings("a->b"), vec!["a", "->", "b"]);
160        assert_eq!(spellings("x+++y"), vec!["x", "++", "+", "y"]);
161        assert_eq!(spellings("..."), vec!["..."]);
162        assert_eq!(spellings(".."), vec![".", "."]);
163        assert_eq!(spellings("[[gnu::packed]]"), vec!["[", "[", "gnu", "::", "packed", "]", "]"]);
164    }
165
166    #[test]
167    fn digraphs_mean_the_same_thing_as_what_they_stand_for() {
168        let (tokens, _) = scan("<% <: %: %:%: :> %>");
169        let kinds: Vec<_> = tokens.iter().map(|(k, _)| *k).collect();
170        assert_eq!(
171            kinds,
172            vec![
173                PpTokenKind::Punct(Punct::LBrace),
174                PpTokenKind::Punct(Punct::LBracket),
175                PpTokenKind::Punct(Punct::Hash),
176                PpTokenKind::Punct(Punct::HashHash),
177                PpTokenKind::Punct(Punct::RBracket),
178                PpTokenKind::Punct(Punct::RBrace),
179            ]
180        );
181    }
182
183    #[test]
184    fn a_digraph_says_it_was_written_as_one() {
185        let mut interner = Interner::new();
186        let (tokens, _) = tokenize(b"<: [", 0, Options::new(), &mut interner);
187        assert!(tokens[0].flags.has(TokenFlags::DIGRAPH));
188        assert!(!tokens[1].flags.has(TokenFlags::DIGRAPH));
189    }
190
191    #[test]
192    fn a_pp_number_is_looser_than_a_constant() {
193        // Both of these are one pp-token. Only phase 7 has an opinion about `1.2.3`, and
194        // splitting it here would break `##` pasting that assembles a number from pieces.
195        assert_eq!(spellings("0x1p+3"), vec!["0x1p+3"]);
196        assert_eq!(spellings("1.2.3"), vec!["1.2.3"]);
197        assert_eq!(spellings(".5f"), vec![".5f"]);
198        assert_eq!(spellings("1e-9"), vec!["1e-9"]);
199        assert_eq!(spellings("0b1010"), vec!["0b1010"]);
200        assert_eq!(spellings("42wb"), vec!["42wb"]);
201    }
202
203    #[test]
204    fn c23_digit_separators_stay_inside_the_number() {
205        assert_eq!(spellings("1'000'000"), vec!["1'000'000"]);
206        // The apostrophe only separates when an identifier character follows, so this is a
207        // number and then a character constant rather than one very confused number.
208        assert_eq!(spellings("1 'a'"), vec!["1", "'a'"]);
209    }
210
211    #[test]
212    fn literal_prefixes_belong_to_the_literal() {
213        let (tokens, _) = scan(r#"L"wide" u8"utf8" u'c' U"big" L'w' u8'x'"#);
214        let kinds: Vec<_> = tokens.iter().map(|(k, _)| *k).collect();
215        assert_eq!(
216            kinds,
217            vec![
218                PpTokenKind::StringLit,
219                PpTokenKind::StringLit,
220                PpTokenKind::CharConst,
221                PpTokenKind::StringLit,
222                PpTokenKind::CharConst,
223                PpTokenKind::CharConst,
224            ]
225        );
226        assert_eq!(tokens[0].1, "L\"wide\"");
227    }
228
229    #[test]
230    fn an_escaped_quote_does_not_end_a_literal() {
231        assert_eq!(spellings(r#""a\"b" x"#), vec![r#""a\"b""#, "x"]);
232        assert_eq!(spellings(r"'\\' y"), vec![r"'\\'", "y"]);
233    }
234
235    #[test]
236    fn a_literal_does_not_run_past_the_end_of_its_line() {
237        // One missing quote must not swallow the rest of the file, which is the difference
238        // between one error and a hundred.
239        let (tokens, diagnostics) = scan("char *s = \"oops;\nint x;");
240        assert_eq!(diagnostics.len(), 1);
241        assert!(diagnostics[0].contains("missing terminating quote"));
242        assert!(tokens.iter().any(|(k, text)| *k == PpTokenKind::Ident && text == "int"));
243    }
244
245    #[test]
246    fn comments_are_whitespace_and_leave_a_space_behind() {
247        assert_eq!(spellings("a/*b*/c"), vec!["a", "c"]);
248        assert_eq!(spellings("a//b\nc"), vec!["a", "c"]);
249        let mut interner = Interner::new();
250        let (tokens, _) = tokenize(b"a/*b*/c", 0, Options::new(), &mut interner);
251        assert!(tokens[1].flags.has(TokenFlags::LEADING_SPACE));
252    }
253
254    #[test]
255    fn an_unterminated_comment_is_reported_once() {
256        let (_, diagnostics) = scan("int x; /* and then nothing");
257        assert_eq!(diagnostics, vec!["unterminated comment".to_owned()]);
258    }
259
260    #[test]
261    fn a_token_after_a_comment_that_crossed_a_line_still_starts_a_line() {
262        // `# define` after a multi-line comment is a directive. GCC agrees, and real headers
263        // are written this way, so getting it wrong means silently dropping a definition.
264        let mut interner = Interner::new();
265        let (tokens, _) = tokenize(b"x /*\n*/ #define F 1", 0, Options::new(), &mut interner);
266        assert!(!tokens[0].flags.has(TokenFlags::START_OF_LINE) || tokens[0].span.lo == 0);
267        assert!(tokens[1].flags.has(TokenFlags::START_OF_LINE));
268        assert_eq!(tokens[1].punct(), Some(Punct::Hash));
269    }
270
271    #[test]
272    fn the_first_token_of_a_line_says_so() {
273        let mut interner = Interner::new();
274        let (tokens, _) = tokenize(b"a b\nc", 0, Options::new(), &mut interner);
275        assert!(tokens[0].flags.has(TokenFlags::START_OF_LINE));
276        assert!(!tokens[1].flags.has(TokenFlags::START_OF_LINE));
277        assert!(tokens[2].flags.has(TokenFlags::START_OF_LINE));
278    }
279
280    #[test]
281    fn a_splice_joins_one_identifier_and_the_span_still_covers_real_bytes() {
282        let mut interner = Interner::new();
283        let (tokens, diagnostics) = tokenize(b"in\\\nt", 0, Options::new(), &mut interner);
284        assert!(diagnostics.is_empty());
285        assert_eq!(interner.resolve(tokens[0].value.unwrap()), "int");
286        assert!(tokens[0].flags.has(TokenFlags::SPLICED));
287        // The span covers all five bytes of the file, backslash and newline included, which
288        // is what a caret under the identifier has to underline.
289        assert_eq!(tokens[0].span.lo, 0);
290        assert_eq!(tokens[0].span.hi, 5);
291    }
292
293    #[test]
294    fn a_splice_inside_a_punctuator_still_makes_one_punctuator() {
295        let mut interner = Interner::new();
296        let (tokens, _) = tokenize(b">\\\n>=", 0, Options::new(), &mut interner);
297        assert_eq!(tokens[0].punct(), Some(Punct::ShrEq));
298        assert!(tokens[0].flags.has(TokenFlags::SPLICED));
299    }
300
301    #[test]
302    fn a_clean_token_is_not_marked_spliced() {
303        let mut interner = Interner::new();
304        let (tokens, _) = tokenize(b"int", 0, Options::new(), &mut interner);
305        assert!(!tokens[0].flags.has(TokenFlags::SPLICED));
306    }
307
308    /// `//` is C99, and gcc has it in gnu89 as an extension. `-std=c89` is the one dialect that
309    /// does not, and there it is an error rather than two punctuators, because a file written
310    /// with line comments would otherwise produce a syntax error on every one of them.
311    #[test]
312    fn a_line_comment_is_refused_in_the_one_dialect_that_has_no_such_thing() {
313        let mut interner = Interner::new();
314        let opts = Options::for_dialect(Std::C89, false);
315        let (tokens, errors) = tokenize(b"// gone\nint x;", 0, opts, &mut interner);
316        assert_eq!(errors.len(), 1);
317        assert!(errors[0].message.contains("C++ style comments"));
318        // Skipped as a comment all the same, so the declaration after it is still one. The
319        // `;` is a punctuator and has no spelling to intern, which is why it is not here.
320        let text: Vec<_> = tokens
321            .iter()
322            .filter(|t| !t.is_eof())
323            .filter_map(|t| t.value.map(|s| interner.resolve(s).to_owned()))
324            .collect();
325        assert_eq!(text, vec!["int", "x"]);
326        // Once a file, however many are written.
327        let mut interner = Interner::new();
328        let (_, errors) = tokenize(b"// one\n// two\n// three\n", 0, opts, &mut interner);
329        assert_eq!(errors.len(), 1);
330        // And in gnu89 there is nothing to say.
331        let mut interner = Interner::new();
332        let opts = Options::for_dialect(Std::C89, true);
333        let (_, errors) = tokenize(b"// gone\nint x;", 0, opts, &mut interner);
334        assert!(errors.is_empty());
335    }
336
337    /// The digit separator is C23 and is not a GNU extension, so `1'000'000` is one number in
338    /// c23 and three tokens everywhere else, which is how gcc reads it and why it does not
339    /// parse there.
340    #[test]
341    fn a_digit_separator_is_part_of_the_number_only_in_c23() {
342        let mut interner = Interner::new();
343        let opts = Options::for_dialect(Std::C23, false);
344        let (tokens, _) = tokenize(b"1'000'000", 0, opts, &mut interner);
345        assert_eq!(tokens[0].kind, PpTokenKind::Number);
346        assert_eq!(interner.resolve(tokens[0].value.unwrap()), "1'000'000");
347
348        let mut interner = Interner::new();
349        let opts = Options::for_dialect(Std::C17, true);
350        let (tokens, _) = tokenize(b"1'000'000", 0, opts, &mut interner);
351        assert_eq!(tokens[0].kind, PpTokenKind::Number);
352        assert_eq!(interner.resolve(tokens[0].value.unwrap()), "1");
353        assert_eq!(tokens[1].kind, PpTokenKind::CharConst);
354        assert_eq!(tokens[2].kind, PpTokenKind::Number);
355    }
356
357    #[test]
358    fn trigraphs_are_off_by_default() {
359        let (tokens, _) = scan("??=define");
360        assert_eq!(tokens[0].0, PpTokenKind::Punct(Punct::Question));
361        let mut interner = Interner::new();
362        let opts = Options { trigraphs: true, ..Options::new() };
363        let (on, _) = tokenize(b"??=define", 0, opts, &mut interner);
364        assert_eq!(on[0].punct(), Some(Punct::Hash));
365        assert_eq!(interner.resolve(on[1].value.unwrap()), "define");
366    }
367
368    /// The whitespace and comment scans move the head over runs of bytes without reading them,
369    /// which is only allowed because no byte they pass can be one phases 1 and 2 rewrite. These
370    /// are the inputs that say whether that is actually true, and they are here rather than
371    /// next to the scans because what they check is the answer, not the arithmetic.
372    #[test]
373    fn a_line_comment_ends_where_a_splice_says_it_does() {
374        // A backslash at the end of a line continues the comment onto the next one, so `c` is
375        // still commented out and only `a` and `d` survive. A scan that ran to the newline
376        // without looking would bring `c` back.
377        assert_eq!(spellings("a //b\\\nc\nd"), vec!["a", "d"]);
378        assert_eq!(spellings("a //b\\\r\nc\nd"), vec!["a", "d"]);
379        // Long enough that the run is whole words rather than the tail, which is the path the
380        // short cases above never take.
381        assert_eq!(spellings("a //bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\\\nc\nd"), vec!["a", "d"]);
382    }
383
384    #[test]
385    fn a_trigraph_backslash_still_continues_a_comment_it_is_at_the_end_of() {
386        // `??/` is a backslash, and phase 1 runs before phase 2, so this splices as well. Worth
387        // its own test because the fast scan only stops on `?` when trigraphs are on, so this
388        // is the case where the two settings have to disagree.
389        let mut interner = Interner::new();
390        let src = b"a //bbbbbbbbbbbbbbbb??/\nc\nd";
391        let (on, _) =
392            tokenize(src, 0, Options { trigraphs: true, ..Options::new() }, &mut interner);
393        let text: Vec<_> = on
394            .iter()
395            .filter(|t| !t.is_eof())
396            .filter_map(|t| t.value.map(|s| interner.resolve(s).to_owned()))
397            .collect();
398        assert_eq!(text, vec!["a", "d"]);
399        // With trigraphs off the same bytes are just a comment ending at the newline, and `c`
400        // is a real token.
401        assert_eq!(spellings("a //bbbbbbbbbbbbbbbb??/\nc\nd"), vec!["a", "c", "d"]);
402    }
403
404    #[test]
405    fn a_block_comment_is_still_terminated_when_the_stars_are_a_long_way_in() {
406        // The body scan stops on `*` and on the newline, so this walks it in a few steps
407        // instead of a few hundred, and has to come out at the same place either way.
408        let body = "x".repeat(200);
409        assert_eq!(spellings(&format!("a /*{body}*/ b")), vec!["a", "b"]);
410        assert_eq!(spellings(&format!("a /*{body}\n{body}*/ b")), vec!["a", "b"]);
411        // A `*` that is not the end must not end it.
412        assert_eq!(spellings(&format!("a /*{body}*{body}*/ b")), vec!["a", "b"]);
413        // And an unterminated one is still reported once rather than run off the end.
414        let (_, diagnostics) = scan(&format!("a /*{body}"));
415        assert_eq!(diagnostics, vec!["unterminated comment".to_owned()]);
416    }
417
418    #[test]
419    fn a_spliced_comment_opener_is_not_missed_by_the_whitespace_scan() {
420        // `/\<newline>*` is a block comment opener spelled across two lines. The blank run
421        // before it must stop at the backslash rather than carry on, or the `/` and the `*`
422        // come out as two punctuators and the comment body becomes program text.
423        assert_eq!(spellings("a        /\\\n* body *\\\n/ b"), vec!["a", "b"]);
424    }
425
426    #[test]
427    fn a_long_run_of_indentation_leaves_exactly_one_space_behind() {
428        // Whatever the scan does to the head, the flag it sets has to be the same one the
429        // byte at a time loop set.
430        let mut interner = Interner::new();
431        let src = format!("a{}b", " ".repeat(100));
432        let (tokens, _) = tokenize(src.as_bytes(), 0, Options::new(), &mut interner);
433        assert!(tokens[1].flags.has(TokenFlags::LEADING_SPACE));
434        assert!(!tokens[1].flags.has(TokenFlags::START_OF_LINE));
435        // A tab run reaches the same conclusion, and a run that ends at a newline gives the
436        // next token a line start rather than a space.
437        let src = format!("a{}\nb", "\t".repeat(100));
438        let (tokens, _) = tokenize(src.as_bytes(), 0, Options::new(), &mut interner);
439        assert!(tokens[1].flags.has(TokenFlags::START_OF_LINE));
440    }
441
442    #[test]
443    fn a_stray_byte_is_a_token_rather_than_a_hard_stop() {
444        // A pp-token that is nothing else is legal here and only becomes an error in phase 7,
445        // because a macro is allowed to consume it first.
446        let (tokens, diagnostics) = scan("a ` b");
447        assert!(diagnostics.is_empty());
448        assert_eq!(tokens[1].0, PpTokenKind::Other);
449        assert_eq!(tokens[1].1, "`");
450    }
451
452    #[test]
453    fn a_file_that_is_only_whitespace_lexes_to_end_of_file() {
454        let mut interner = Interner::new();
455        let (tokens, diagnostics) = tokenize(b"  \n\t\n", 0, Options::new(), &mut interner);
456        assert!(diagnostics.is_empty());
457        assert_eq!(tokens.len(), 1);
458        assert!(tokens[0].is_eof());
459    }
460
461    #[test]
462    fn the_empty_file_lexes_to_end_of_file() {
463        let mut interner = Interner::new();
464        let (tokens, _) = tokenize(b"", 0, Options::new(), &mut interner);
465        assert_eq!(tokens.len(), 1);
466        assert!(tokens[0].is_eof());
467    }
468
469    #[test]
470    fn spans_are_offset_by_where_the_file_sits() {
471        // One flat coordinate space across the translation unit, per `rucc-diag`, so a file
472        // that is not the first one still produces spans nobody has to translate.
473        let mut interner = Interner::new();
474        let (tokens, _) = tokenize(b"ab", 1000, Options::new(), &mut interner);
475        assert_eq!(tokens[0].span.lo, 1000);
476        assert_eq!(tokens[0].span.hi, 1002);
477    }
478
479    #[test]
480    fn a_header_name_is_only_scanned_when_a_directive_asks_for_one() {
481        let mut interner = Interner::new();
482        let mut lexer = Lexer::new(b"<stdio.h>", 0, Options::new());
483        let header = lexer.header_name(&mut interner).expect("a header name starts here");
484        assert_eq!(header.kind, PpTokenKind::HeaderName);
485        assert_eq!(interner.resolve(header.value.unwrap()), "<stdio.h>");
486
487        // The same bytes read as ordinary tokens are comparisons, which is exactly why the
488        // scanner refuses to guess and the directive has to ask.
489        let (tokens, _) = scan("<stdio.h>");
490        assert_eq!(tokens[0].0, PpTokenKind::Punct(Punct::Lt));
491    }
492
493    #[test]
494    fn a_quoted_header_name_works_and_a_computed_one_declines() {
495        let mut interner = Interner::new();
496        let mut lexer = Lexer::new(b" \"local.h\"", 0, Options::new());
497        let header = lexer.header_name(&mut interner).expect("a header name starts here");
498        assert_eq!(interner.resolve(header.value.unwrap()), "\"local.h\"");
499
500        let mut lexer = Lexer::new(b"MACRO_NAME", 0, Options::new());
501        assert!(lexer.header_name(&mut interner).is_none());
502    }
503
504    #[test]
505    fn identifiers_are_interned_during_the_scan_and_repeat_for_free() {
506        let mut interner = Interner::new();
507        let before = interner.len();
508        let (tokens, _) = tokenize(b"foo bar foo", 0, Options::new(), &mut interner);
509        assert_eq!(tokens[0].value, tokens[2].value);
510        assert_ne!(tokens[0].value, tokens[1].value);
511        assert_eq!(interner.len() - before, 2);
512    }
513
514    #[test]
515    fn a_universal_character_name_is_part_of_the_identifier() {
516        // Whether `é` names something that may appear in an identifier depends on
517        // `-std=`, so phase 3 only has to keep it attached to the identifier it was written
518        // in. Splitting it here would turn one name into three tokens.
519        let (tokens, diagnostics) = scan(r"café = 1;");
520        assert!(diagnostics.is_empty());
521        assert_eq!(tokens[0], (PpTokenKind::Ident, r"café".to_owned()));
522        assert_eq!(tokens[1].0, PpTokenKind::Punct(Punct::Eq));
523    }
524
525    #[test]
526    fn a_backslash_with_a_trailing_space_splices_and_says_so() {
527        // GCC warns and splices. Both halves matter: a lot of existing code has a stray space
528        // after a backslash in a macro definition, and the space is invisible in an editor,
529        // so the one time it changes the meaning nobody can see why.
530        let (tokens, diagnostics) = scan("in\\  \nt x;");
531        assert_eq!(tokens[0], (PpTokenKind::Ident, "int".to_owned()));
532        assert_eq!(
533            diagnostics,
534            vec!["backslash and line ending separated by whitespace".to_owned()]
535        );
536    }
537
538    #[test]
539    fn utf8_in_an_identifier_survives_the_scan() {
540        let (tokens, diagnostics) = scan("café = 1;");
541        assert!(diagnostics.is_empty());
542        assert_eq!(tokens[0].1, "café");
543    }
544
545    #[test]
546    fn every_byte_of_the_file_ends_up_in_exactly_one_span_or_in_trivia() {
547        // The property that keeps `-E` honest: spans never overlap and never run backwards.
548        let src = "int main(void) { return 0; } /* c */ \"s\" 'c' 1.5e+3 // end\n";
549        let mut interner = Interner::new();
550        let (tokens, _) = tokenize(src.as_bytes(), 0, Options::new(), &mut interner);
551        let mut last = 0;
552        for t in &tokens {
553            assert!(t.span.lo >= last, "spans went backwards at {:?}", t.kind);
554            assert!(t.span.hi >= t.span.lo);
555            last = t.span.hi;
556        }
557        assert_eq!(last as usize, src.len());
558    }
559
560    #[test]
561    fn milestone_is_recorded() {
562        assert!(MILESTONE.starts_with('M'));
563    }
564}