1#![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
75pub const MILESTONE: &str = "M1";
77
78#[cfg(test)]
79mod tests {
80 use rucc_base::Interner;
81
82 use super::*;
83
84 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 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 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 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 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 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 #[test]
292 fn a_line_comment_ends_where_a_splice_says_it_does() {
293 assert_eq!(spellings("a //b\\\nc\nd"), vec!["a", "d"]);
297 assert_eq!(spellings("a //b\\\r\nc\nd"), vec!["a", "d"]);
298 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 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 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 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 assert_eq!(spellings(&format!("a /*{body}*{body}*/ b")), vec!["a", "b"]);
331 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 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 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 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 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 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 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 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 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 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}