1#![doc(html_root_url = "https://docs.rs/rucc-lex/0.11.13")]
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
106pub 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 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 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 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 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_comment_that_crossed_a_line_starts_one_only_if_it_began_one() {
262 let mut interner = Interner::new();
266 let (tokens, _) = tokenize(b"/*\n*/ #define F 1", 0, Options::new(), &mut interner);
267 assert!(tokens[0].flags.has(TokenFlags::START_OF_LINE));
268 assert_eq!(tokens[0].punct(), Some(Punct::Hash));
269 let (tokens, _) = tokenize(b"x /*\n*/ #define F 1", 0, Options::new(), &mut interner);
270 assert!(!tokens[1].flags.has(TokenFlags::START_OF_LINE));
271 assert_eq!(tokens[1].punct(), Some(Punct::Hash));
272 }
273
274 #[test]
275 fn the_first_token_of_a_line_says_so() {
276 let mut interner = Interner::new();
277 let (tokens, _) = tokenize(b"a b\nc", 0, Options::new(), &mut interner);
278 assert!(tokens[0].flags.has(TokenFlags::START_OF_LINE));
279 assert!(!tokens[1].flags.has(TokenFlags::START_OF_LINE));
280 assert!(tokens[2].flags.has(TokenFlags::START_OF_LINE));
281 }
282
283 #[test]
284 fn a_splice_joins_one_identifier_and_the_span_still_covers_real_bytes() {
285 let mut interner = Interner::new();
286 let (tokens, diagnostics) = tokenize(b"in\\\nt", 0, Options::new(), &mut interner);
287 assert!(diagnostics.is_empty());
288 assert_eq!(interner.resolve(tokens[0].value.unwrap()), "int");
289 assert!(tokens[0].flags.has(TokenFlags::SPLICED));
290 assert_eq!(tokens[0].span.lo, 0);
293 assert_eq!(tokens[0].span.hi, 5);
294 }
295
296 #[test]
297 fn a_splice_inside_a_punctuator_still_makes_one_punctuator() {
298 let mut interner = Interner::new();
299 let (tokens, _) = tokenize(b">\\\n>=", 0, Options::new(), &mut interner);
300 assert_eq!(tokens[0].punct(), Some(Punct::ShrEq));
301 assert!(tokens[0].flags.has(TokenFlags::SPLICED));
302 }
303
304 #[test]
305 fn a_clean_token_is_not_marked_spliced() {
306 let mut interner = Interner::new();
307 let (tokens, _) = tokenize(b"int", 0, Options::new(), &mut interner);
308 assert!(!tokens[0].flags.has(TokenFlags::SPLICED));
309 }
310
311 #[test]
315 fn a_line_comment_is_refused_in_the_one_dialect_that_has_no_such_thing() {
316 let mut interner = Interner::new();
317 let opts = Options::for_dialect(Std::C89, false);
318 let (tokens, errors) = tokenize(b"// gone\nint x;", 0, opts, &mut interner);
319 assert_eq!(errors.len(), 1);
320 assert!(errors[0].message.contains("C++ style comments"));
321 let text: Vec<_> = tokens
324 .iter()
325 .filter(|t| !t.is_eof())
326 .filter_map(|t| t.value.map(|s| interner.resolve(s).to_owned()))
327 .collect();
328 assert_eq!(text, vec!["int", "x"]);
329 let mut interner = Interner::new();
331 let (_, errors) = tokenize(b"// one\n// two\n// three\n", 0, opts, &mut interner);
332 assert_eq!(errors.len(), 1);
333 let mut interner = Interner::new();
335 let opts = Options::for_dialect(Std::C89, true);
336 let (_, errors) = tokenize(b"// gone\nint x;", 0, opts, &mut interner);
337 assert!(errors.is_empty());
338 }
339
340 #[test]
344 fn a_digit_separator_is_part_of_the_number_only_in_c23() {
345 let mut interner = Interner::new();
346 let opts = Options::for_dialect(Std::C23, false);
347 let (tokens, _) = tokenize(b"1'000'000", 0, opts, &mut interner);
348 assert_eq!(tokens[0].kind, PpTokenKind::Number);
349 assert_eq!(interner.resolve(tokens[0].value.unwrap()), "1'000'000");
350
351 let mut interner = Interner::new();
352 let opts = Options::for_dialect(Std::C17, true);
353 let (tokens, _) = tokenize(b"1'000'000", 0, opts, &mut interner);
354 assert_eq!(tokens[0].kind, PpTokenKind::Number);
355 assert_eq!(interner.resolve(tokens[0].value.unwrap()), "1");
356 assert_eq!(tokens[1].kind, PpTokenKind::CharConst);
357 assert_eq!(tokens[2].kind, PpTokenKind::Number);
358 }
359
360 #[test]
361 fn trigraphs_are_off_by_default() {
362 let (tokens, _) = scan("??=define");
363 assert_eq!(tokens[0].0, PpTokenKind::Punct(Punct::Question));
364 let mut interner = Interner::new();
365 let opts = Options { trigraphs: true, ..Options::new() };
366 let (on, _) = tokenize(b"??=define", 0, opts, &mut interner);
367 assert_eq!(on[0].punct(), Some(Punct::Hash));
368 assert_eq!(interner.resolve(on[1].value.unwrap()), "define");
369 }
370
371 #[test]
376 fn a_line_comment_ends_where_a_splice_says_it_does() {
377 assert_eq!(spellings("a //b\\\nc\nd"), vec!["a", "d"]);
381 assert_eq!(spellings("a //b\\\r\nc\nd"), vec!["a", "d"]);
382 assert_eq!(spellings("a //bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\\\nc\nd"), vec!["a", "d"]);
385 }
386
387 #[test]
388 fn a_trigraph_backslash_still_continues_a_comment_it_is_at_the_end_of() {
389 let mut interner = Interner::new();
393 let src = b"a //bbbbbbbbbbbbbbbb??/\nc\nd";
394 let (on, _) =
395 tokenize(src, 0, Options { trigraphs: true, ..Options::new() }, &mut interner);
396 let text: Vec<_> = on
397 .iter()
398 .filter(|t| !t.is_eof())
399 .filter_map(|t| t.value.map(|s| interner.resolve(s).to_owned()))
400 .collect();
401 assert_eq!(text, vec!["a", "d"]);
402 assert_eq!(spellings("a //bbbbbbbbbbbbbbbb??/\nc\nd"), vec!["a", "c", "d"]);
405 }
406
407 #[test]
408 fn a_block_comment_is_still_terminated_when_the_stars_are_a_long_way_in() {
409 let body = "x".repeat(200);
412 assert_eq!(spellings(&format!("a /*{body}*/ b")), vec!["a", "b"]);
413 assert_eq!(spellings(&format!("a /*{body}\n{body}*/ b")), vec!["a", "b"]);
414 assert_eq!(spellings(&format!("a /*{body}*{body}*/ b")), vec!["a", "b"]);
416 let (_, diagnostics) = scan(&format!("a /*{body}"));
418 assert_eq!(diagnostics, vec!["unterminated comment".to_owned()]);
419 }
420
421 #[test]
422 fn a_spliced_comment_opener_is_not_missed_by_the_whitespace_scan() {
423 assert_eq!(spellings("a /\\\n* body *\\\n/ b"), vec!["a", "b"]);
427 }
428
429 #[test]
430 fn a_newline_inside_a_comment_does_not_start_a_line() {
431 let mut interner = Interner::new();
432 let (tokens, _) = tokenize(b"a /* x\ny */ b", 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 let (tokens, _) = tokenize(b"a\n/* x\ny */ # b", 0, Options::new(), &mut interner);
438 assert!(tokens[1].flags.has(TokenFlags::START_OF_LINE));
439 }
440
441 #[test]
442 fn a_long_run_of_indentation_leaves_exactly_one_space_behind() {
443 let mut interner = Interner::new();
446 let src = format!("a{}b", " ".repeat(100));
447 let (tokens, _) = tokenize(src.as_bytes(), 0, Options::new(), &mut interner);
448 assert!(tokens[1].flags.has(TokenFlags::LEADING_SPACE));
449 assert!(!tokens[1].flags.has(TokenFlags::START_OF_LINE));
450 let src = format!("a{}\nb", "\t".repeat(100));
453 let (tokens, _) = tokenize(src.as_bytes(), 0, Options::new(), &mut interner);
454 assert!(tokens[1].flags.has(TokenFlags::START_OF_LINE));
455 }
456
457 #[test]
458 fn a_stray_byte_is_a_token_rather_than_a_hard_stop() {
459 let (tokens, diagnostics) = scan("a ` b");
462 assert!(diagnostics.is_empty());
463 assert_eq!(tokens[1].0, PpTokenKind::Other);
464 assert_eq!(tokens[1].1, "`");
465 }
466
467 #[test]
468 fn a_file_that_is_only_whitespace_lexes_to_end_of_file() {
469 let mut interner = Interner::new();
470 let (tokens, diagnostics) = tokenize(b" \n\t\n", 0, Options::new(), &mut interner);
471 assert!(diagnostics.is_empty());
472 assert_eq!(tokens.len(), 1);
473 assert!(tokens[0].is_eof());
474 }
475
476 #[test]
477 fn the_empty_file_lexes_to_end_of_file() {
478 let mut interner = Interner::new();
479 let (tokens, _) = tokenize(b"", 0, Options::new(), &mut interner);
480 assert_eq!(tokens.len(), 1);
481 assert!(tokens[0].is_eof());
482 }
483
484 #[test]
485 fn spans_are_offset_by_where_the_file_sits() {
486 let mut interner = Interner::new();
489 let (tokens, _) = tokenize(b"ab", 1000, Options::new(), &mut interner);
490 assert_eq!(tokens[0].span.lo, 1000);
491 assert_eq!(tokens[0].span.hi, 1002);
492 }
493
494 #[test]
495 fn a_header_name_is_only_scanned_when_a_directive_asks_for_one() {
496 let mut interner = Interner::new();
497 let mut lexer = Lexer::new(b"<stdio.h>", 0, Options::new());
498 let header = lexer.header_name(&mut interner).expect("a header name starts here");
499 assert_eq!(header.kind, PpTokenKind::HeaderName);
500 assert_eq!(interner.resolve(header.value.unwrap()), "<stdio.h>");
501
502 let (tokens, _) = scan("<stdio.h>");
505 assert_eq!(tokens[0].0, PpTokenKind::Punct(Punct::Lt));
506 }
507
508 #[test]
509 fn a_quoted_header_name_works_and_a_computed_one_declines() {
510 let mut interner = Interner::new();
511 let mut lexer = Lexer::new(b" \"local.h\"", 0, Options::new());
512 let header = lexer.header_name(&mut interner).expect("a header name starts here");
513 assert_eq!(interner.resolve(header.value.unwrap()), "\"local.h\"");
514
515 let mut lexer = Lexer::new(b"MACRO_NAME", 0, Options::new());
516 assert!(lexer.header_name(&mut interner).is_none());
517 }
518
519 #[test]
520 fn identifiers_are_interned_during_the_scan_and_repeat_for_free() {
521 let mut interner = Interner::new();
522 let before = interner.len();
523 let (tokens, _) = tokenize(b"foo bar foo", 0, Options::new(), &mut interner);
524 assert_eq!(tokens[0].value, tokens[2].value);
525 assert_ne!(tokens[0].value, tokens[1].value);
526 assert_eq!(interner.len() - before, 2);
527 }
528
529 #[test]
530 fn a_universal_character_name_is_part_of_the_identifier() {
531 let (tokens, diagnostics) = scan(r"café = 1;");
535 assert!(diagnostics.is_empty());
536 assert_eq!(tokens[0], (PpTokenKind::Ident, r"café".to_owned()));
537 assert_eq!(tokens[1].0, PpTokenKind::Punct(Punct::Eq));
538 }
539
540 #[test]
541 fn a_backslash_with_a_trailing_space_splices_and_says_so() {
542 let (tokens, diagnostics) = scan("in\\ \nt x;");
546 assert_eq!(tokens[0], (PpTokenKind::Ident, "int".to_owned()));
547 assert_eq!(
548 diagnostics,
549 vec!["backslash and line ending separated by whitespace".to_owned()]
550 );
551 }
552
553 #[test]
554 fn utf8_in_an_identifier_survives_the_scan() {
555 let (tokens, diagnostics) = scan("café = 1;");
556 assert!(diagnostics.is_empty());
557 assert_eq!(tokens[0].1, "café");
558 }
559
560 #[test]
561 fn every_byte_of_the_file_ends_up_in_exactly_one_span_or_in_trivia() {
562 let src = "int main(void) { return 0; } /* c */ \"s\" 'c' 1.5e+3 // end\n";
564 let mut interner = Interner::new();
565 let (tokens, _) = tokenize(src.as_bytes(), 0, Options::new(), &mut interner);
566 let mut last = 0;
567 for t in &tokens {
568 assert!(t.span.lo >= last, "spans went backwards at {:?}", t.kind);
569 assert!(t.span.hi >= t.span.lo);
570 last = t.span.hi;
571 }
572 assert_eq!(last as usize, src.len());
573 }
574
575 #[test]
576 fn milestone_is_recorded() {
577 assert!(MILESTONE.starts_with('M'));
578 }
579}