1#![doc(html_root_url = "https://docs.rs/rucc-lex/0.2.0")]
44
45mod class;
46mod cursor;
47mod lexer;
48mod swar;
49mod token;
50
51pub use crate::lexer::{Lexer, Options, tokenize};
52pub use crate::token::{PpToken, PpTokenKind, Punct, TokenFlags};
53
54pub const MILESTONE: &str = "M1";
56
57#[cfg(test)]
58mod tests {
59 use rucc_base::Interner;
60
61 use super::*;
62
63 fn scan(src: &str) -> (Vec<(PpTokenKind, String)>, Vec<String>) {
66 let mut interner = Interner::new();
67 let (tokens, diagnostics) = tokenize(src.as_bytes(), 0, Options::new(), &mut interner);
68 let out = tokens
69 .iter()
70 .filter(|t| !t.is_eof())
71 .map(|t| {
72 let text = match t.value {
73 Some(sym) => interner.resolve(sym).to_owned(),
74 None => t.punct().map_or_else(String::new, |p| p.as_str().to_owned()),
75 };
76 (t.kind, text)
77 })
78 .collect();
79 (out, diagnostics.iter().map(|d| d.message.clone()).collect())
80 }
81
82 fn spellings(src: &str) -> Vec<String> {
83 scan(src).0.into_iter().map(|(_, text)| text).collect()
84 }
85
86 #[test]
87 fn a_declaration_lexes_into_the_tokens_it_looks_like() {
88 let (tokens, diagnostics) = scan("int x = 1;");
89 assert!(diagnostics.is_empty());
90 assert_eq!(
91 tokens,
92 vec![
93 (PpTokenKind::Ident, "int".to_owned()),
94 (PpTokenKind::Ident, "x".to_owned()),
95 (PpTokenKind::Punct(Punct::Eq), "=".to_owned()),
96 (PpTokenKind::Number, "1".to_owned()),
97 (PpTokenKind::Punct(Punct::Semi), ";".to_owned()),
98 ]
99 );
100 }
101
102 #[test]
103 fn punctuators_take_the_longest_match() {
104 assert_eq!(spellings(">>="), vec![">>="]);
105 assert_eq!(spellings(">> ="), vec![">>", "="]);
106 assert_eq!(spellings("a->b"), vec!["a", "->", "b"]);
107 assert_eq!(spellings("x+++y"), vec!["x", "++", "+", "y"]);
108 assert_eq!(spellings("..."), vec!["..."]);
109 assert_eq!(spellings(".."), vec![".", "."]);
110 assert_eq!(spellings("[[gnu::packed]]"), vec!["[", "[", "gnu", "::", "packed", "]", "]"]);
111 }
112
113 #[test]
114 fn digraphs_mean_the_same_thing_as_what_they_stand_for() {
115 let (tokens, _) = scan("<% <: %: %:%: :> %>");
116 let kinds: Vec<_> = tokens.iter().map(|(k, _)| *k).collect();
117 assert_eq!(
118 kinds,
119 vec![
120 PpTokenKind::Punct(Punct::LBrace),
121 PpTokenKind::Punct(Punct::LBracket),
122 PpTokenKind::Punct(Punct::Hash),
123 PpTokenKind::Punct(Punct::HashHash),
124 PpTokenKind::Punct(Punct::RBracket),
125 PpTokenKind::Punct(Punct::RBrace),
126 ]
127 );
128 }
129
130 #[test]
131 fn a_digraph_says_it_was_written_as_one() {
132 let mut interner = Interner::new();
133 let (tokens, _) = tokenize(b"<: [", 0, Options::new(), &mut interner);
134 assert!(tokens[0].flags.has(TokenFlags::DIGRAPH));
135 assert!(!tokens[1].flags.has(TokenFlags::DIGRAPH));
136 }
137
138 #[test]
139 fn a_pp_number_is_looser_than_a_constant() {
140 assert_eq!(spellings("0x1p+3"), vec!["0x1p+3"]);
143 assert_eq!(spellings("1.2.3"), vec!["1.2.3"]);
144 assert_eq!(spellings(".5f"), vec![".5f"]);
145 assert_eq!(spellings("1e-9"), vec!["1e-9"]);
146 assert_eq!(spellings("0b1010"), vec!["0b1010"]);
147 assert_eq!(spellings("42wb"), vec!["42wb"]);
148 }
149
150 #[test]
151 fn c23_digit_separators_stay_inside_the_number() {
152 assert_eq!(spellings("1'000'000"), vec!["1'000'000"]);
153 assert_eq!(spellings("1 'a'"), vec!["1", "'a'"]);
156 }
157
158 #[test]
159 fn literal_prefixes_belong_to_the_literal() {
160 let (tokens, _) = scan(r#"L"wide" u8"utf8" u'c' U"big" L'w' u8'x'"#);
161 let kinds: Vec<_> = tokens.iter().map(|(k, _)| *k).collect();
162 assert_eq!(
163 kinds,
164 vec![
165 PpTokenKind::StringLit,
166 PpTokenKind::StringLit,
167 PpTokenKind::CharConst,
168 PpTokenKind::StringLit,
169 PpTokenKind::CharConst,
170 PpTokenKind::CharConst,
171 ]
172 );
173 assert_eq!(tokens[0].1, "L\"wide\"");
174 }
175
176 #[test]
177 fn an_escaped_quote_does_not_end_a_literal() {
178 assert_eq!(spellings(r#""a\"b" x"#), vec![r#""a\"b""#, "x"]);
179 assert_eq!(spellings(r"'\\' y"), vec![r"'\\'", "y"]);
180 }
181
182 #[test]
183 fn a_literal_does_not_run_past_the_end_of_its_line() {
184 let (tokens, diagnostics) = scan("char *s = \"oops;\nint x;");
187 assert_eq!(diagnostics.len(), 1);
188 assert!(diagnostics[0].contains("missing terminating quote"));
189 assert!(tokens.iter().any(|(k, text)| *k == PpTokenKind::Ident && text == "int"));
190 }
191
192 #[test]
193 fn comments_are_whitespace_and_leave_a_space_behind() {
194 assert_eq!(spellings("a/*b*/c"), vec!["a", "c"]);
195 assert_eq!(spellings("a//b\nc"), vec!["a", "c"]);
196 let mut interner = Interner::new();
197 let (tokens, _) = tokenize(b"a/*b*/c", 0, Options::new(), &mut interner);
198 assert!(tokens[1].flags.has(TokenFlags::LEADING_SPACE));
199 }
200
201 #[test]
202 fn an_unterminated_comment_is_reported_once() {
203 let (_, diagnostics) = scan("int x; /* and then nothing");
204 assert_eq!(diagnostics, vec!["unterminated comment".to_owned()]);
205 }
206
207 #[test]
208 fn a_token_after_a_comment_that_crossed_a_line_still_starts_a_line() {
209 let mut interner = Interner::new();
212 let (tokens, _) = tokenize(b"x /*\n*/ #define F 1", 0, Options::new(), &mut interner);
213 assert!(!tokens[0].flags.has(TokenFlags::START_OF_LINE) || tokens[0].span.lo == 0);
214 assert!(tokens[1].flags.has(TokenFlags::START_OF_LINE));
215 assert_eq!(tokens[1].punct(), Some(Punct::Hash));
216 }
217
218 #[test]
219 fn the_first_token_of_a_line_says_so() {
220 let mut interner = Interner::new();
221 let (tokens, _) = tokenize(b"a b\nc", 0, Options::new(), &mut interner);
222 assert!(tokens[0].flags.has(TokenFlags::START_OF_LINE));
223 assert!(!tokens[1].flags.has(TokenFlags::START_OF_LINE));
224 assert!(tokens[2].flags.has(TokenFlags::START_OF_LINE));
225 }
226
227 #[test]
228 fn a_splice_joins_one_identifier_and_the_span_still_covers_real_bytes() {
229 let mut interner = Interner::new();
230 let (tokens, diagnostics) = tokenize(b"in\\\nt", 0, Options::new(), &mut interner);
231 assert!(diagnostics.is_empty());
232 assert_eq!(interner.resolve(tokens[0].value.unwrap()), "int");
233 assert!(tokens[0].flags.has(TokenFlags::SPLICED));
234 assert_eq!(tokens[0].span.lo, 0);
237 assert_eq!(tokens[0].span.hi, 5);
238 }
239
240 #[test]
241 fn a_splice_inside_a_punctuator_still_makes_one_punctuator() {
242 let mut interner = Interner::new();
243 let (tokens, _) = tokenize(b">\\\n>=", 0, Options::new(), &mut interner);
244 assert_eq!(tokens[0].punct(), Some(Punct::ShrEq));
245 assert!(tokens[0].flags.has(TokenFlags::SPLICED));
246 }
247
248 #[test]
249 fn a_clean_token_is_not_marked_spliced() {
250 let mut interner = Interner::new();
251 let (tokens, _) = tokenize(b"int", 0, Options::new(), &mut interner);
252 assert!(!tokens[0].flags.has(TokenFlags::SPLICED));
253 }
254
255 #[test]
256 fn trigraphs_are_off_by_default() {
257 let (tokens, _) = scan("??=define");
258 assert_eq!(tokens[0].0, PpTokenKind::Punct(Punct::Question));
259 let mut interner = Interner::new();
260 let opts = Options { trigraphs: true };
261 let (on, _) = tokenize(b"??=define", 0, opts, &mut interner);
262 assert_eq!(on[0].punct(), Some(Punct::Hash));
263 assert_eq!(interner.resolve(on[1].value.unwrap()), "define");
264 }
265
266 #[test]
271 fn a_line_comment_ends_where_a_splice_says_it_does() {
272 assert_eq!(spellings("a //b\\\nc\nd"), vec!["a", "d"]);
276 assert_eq!(spellings("a //b\\\r\nc\nd"), vec!["a", "d"]);
277 assert_eq!(spellings("a //bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\\\nc\nd"), vec!["a", "d"]);
280 }
281
282 #[test]
283 fn a_trigraph_backslash_still_continues_a_comment_it_is_at_the_end_of() {
284 let mut interner = Interner::new();
288 let src = b"a //bbbbbbbbbbbbbbbb??/\nc\nd";
289 let (on, _) = tokenize(src, 0, Options { trigraphs: true }, &mut interner);
290 let text: Vec<_> = on
291 .iter()
292 .filter(|t| !t.is_eof())
293 .filter_map(|t| t.value.map(|s| interner.resolve(s).to_owned()))
294 .collect();
295 assert_eq!(text, vec!["a", "d"]);
296 assert_eq!(spellings("a //bbbbbbbbbbbbbbbb??/\nc\nd"), vec!["a", "c", "d"]);
299 }
300
301 #[test]
302 fn a_block_comment_is_still_terminated_when_the_stars_are_a_long_way_in() {
303 let body = "x".repeat(200);
306 assert_eq!(spellings(&format!("a /*{body}*/ b")), vec!["a", "b"]);
307 assert_eq!(spellings(&format!("a /*{body}\n{body}*/ b")), vec!["a", "b"]);
308 assert_eq!(spellings(&format!("a /*{body}*{body}*/ b")), vec!["a", "b"]);
310 let (_, diagnostics) = scan(&format!("a /*{body}"));
312 assert_eq!(diagnostics, vec!["unterminated comment".to_owned()]);
313 }
314
315 #[test]
316 fn a_spliced_comment_opener_is_not_missed_by_the_whitespace_scan() {
317 assert_eq!(spellings("a /\\\n* body *\\\n/ b"), vec!["a", "b"]);
321 }
322
323 #[test]
324 fn a_long_run_of_indentation_leaves_exactly_one_space_behind() {
325 let mut interner = Interner::new();
328 let src = format!("a{}b", " ".repeat(100));
329 let (tokens, _) = tokenize(src.as_bytes(), 0, Options::new(), &mut interner);
330 assert!(tokens[1].flags.has(TokenFlags::LEADING_SPACE));
331 assert!(!tokens[1].flags.has(TokenFlags::START_OF_LINE));
332 let src = format!("a{}\nb", "\t".repeat(100));
335 let (tokens, _) = tokenize(src.as_bytes(), 0, Options::new(), &mut interner);
336 assert!(tokens[1].flags.has(TokenFlags::START_OF_LINE));
337 }
338
339 #[test]
340 fn a_stray_byte_is_a_token_rather_than_a_hard_stop() {
341 let (tokens, diagnostics) = scan("a ` b");
344 assert!(diagnostics.is_empty());
345 assert_eq!(tokens[1].0, PpTokenKind::Other);
346 assert_eq!(tokens[1].1, "`");
347 }
348
349 #[test]
350 fn a_file_that_is_only_whitespace_lexes_to_end_of_file() {
351 let mut interner = Interner::new();
352 let (tokens, diagnostics) = tokenize(b" \n\t\n", 0, Options::new(), &mut interner);
353 assert!(diagnostics.is_empty());
354 assert_eq!(tokens.len(), 1);
355 assert!(tokens[0].is_eof());
356 }
357
358 #[test]
359 fn the_empty_file_lexes_to_end_of_file() {
360 let mut interner = Interner::new();
361 let (tokens, _) = tokenize(b"", 0, Options::new(), &mut interner);
362 assert_eq!(tokens.len(), 1);
363 assert!(tokens[0].is_eof());
364 }
365
366 #[test]
367 fn spans_are_offset_by_where_the_file_sits() {
368 let mut interner = Interner::new();
371 let (tokens, _) = tokenize(b"ab", 1000, Options::new(), &mut interner);
372 assert_eq!(tokens[0].span.lo, 1000);
373 assert_eq!(tokens[0].span.hi, 1002);
374 }
375
376 #[test]
377 fn a_header_name_is_only_scanned_when_a_directive_asks_for_one() {
378 let mut interner = Interner::new();
379 let mut lexer = Lexer::new(b"<stdio.h>", 0, Options::new());
380 let header = lexer.header_name(&mut interner).expect("a header name starts here");
381 assert_eq!(header.kind, PpTokenKind::HeaderName);
382 assert_eq!(interner.resolve(header.value.unwrap()), "<stdio.h>");
383
384 let (tokens, _) = scan("<stdio.h>");
387 assert_eq!(tokens[0].0, PpTokenKind::Punct(Punct::Lt));
388 }
389
390 #[test]
391 fn a_quoted_header_name_works_and_a_computed_one_declines() {
392 let mut interner = Interner::new();
393 let mut lexer = Lexer::new(b" \"local.h\"", 0, Options::new());
394 let header = lexer.header_name(&mut interner).expect("a header name starts here");
395 assert_eq!(interner.resolve(header.value.unwrap()), "\"local.h\"");
396
397 let mut lexer = Lexer::new(b"MACRO_NAME", 0, Options::new());
398 assert!(lexer.header_name(&mut interner).is_none());
399 }
400
401 #[test]
402 fn identifiers_are_interned_during_the_scan_and_repeat_for_free() {
403 let mut interner = Interner::new();
404 let (tokens, _) = tokenize(b"foo bar foo", 0, Options::new(), &mut interner);
405 assert_eq!(tokens[0].value, tokens[2].value);
406 assert_ne!(tokens[0].value, tokens[1].value);
407 assert_eq!(interner.len(), 2);
408 }
409
410 #[test]
411 fn a_universal_character_name_is_part_of_the_identifier() {
412 let (tokens, diagnostics) = scan(r"café = 1;");
416 assert!(diagnostics.is_empty());
417 assert_eq!(tokens[0], (PpTokenKind::Ident, r"café".to_owned()));
418 assert_eq!(tokens[1].0, PpTokenKind::Punct(Punct::Eq));
419 }
420
421 #[test]
422 fn a_backslash_with_a_trailing_space_splices_and_says_so() {
423 let (tokens, diagnostics) = scan("in\\ \nt x;");
427 assert_eq!(tokens[0], (PpTokenKind::Ident, "int".to_owned()));
428 assert_eq!(
429 diagnostics,
430 vec!["backslash and line ending separated by whitespace".to_owned()]
431 );
432 }
433
434 #[test]
435 fn utf8_in_an_identifier_survives_the_scan() {
436 let (tokens, diagnostics) = scan("café = 1;");
437 assert!(diagnostics.is_empty());
438 assert_eq!(tokens[0].1, "café");
439 }
440
441 #[test]
442 fn every_byte_of_the_file_ends_up_in_exactly_one_span_or_in_trivia() {
443 let src = "int main(void) { return 0; } /* c */ \"s\" 'c' 1.5e+3 // end\n";
445 let mut interner = Interner::new();
446 let (tokens, _) = tokenize(src.as_bytes(), 0, Options::new(), &mut interner);
447 let mut last = 0;
448 for t in &tokens {
449 assert!(t.span.lo >= last, "spans went backwards at {:?}", t.kind);
450 assert!(t.span.hi >= t.span.lo);
451 last = t.span.hi;
452 }
453 assert_eq!(last as usize, src.len());
454 }
455
456 #[test]
457 fn milestone_is_recorded() {
458 assert!(MILESTONE.starts_with('M'));
459 }
460}