1#![doc(html_root_url = "https://docs.rs/rucc-lex/0.2.3")]
73
74mod class;
75mod cursor;
76mod keyword;
77mod lexer;
78mod literal;
79mod number;
80mod remarks;
81mod swar;
82mod token;
83
84pub use crate::keyword::{Keyword, Keywords};
85pub use crate::lexer::{Lexer, Options, tokenize};
86pub use crate::literal::{CharConstant, Encoding, LiteralError, StringLiteral, character, string};
87pub use crate::number::{
88 FloatConstant, FloatConstantType, FloatError, IntConstant, IntConstantType, IntError, floating,
89 integer,
90};
91pub use crate::remarks::Remarks;
92pub use crate::token::{PpToken, PpTokenKind, Punct, TokenFlags};
93
94pub const MILESTONE: &str = "M1";
96
97#[cfg(test)]
98mod tests {
99 use rucc_base::Interner;
100
101 use super::*;
102
103 fn scan(src: &str) -> (Vec<(PpTokenKind, String)>, Vec<String>) {
106 let mut interner = Interner::new();
107 let (tokens, diagnostics) = tokenize(src.as_bytes(), 0, Options::new(), &mut interner);
108 let out = tokens
109 .iter()
110 .filter(|t| !t.is_eof())
111 .map(|t| {
112 let text = match t.value {
113 Some(sym) => interner.resolve(sym).to_owned(),
114 None => t.punct().map_or_else(String::new, |p| p.as_str().to_owned()),
115 };
116 (t.kind, text)
117 })
118 .collect();
119 (out, diagnostics.iter().map(|d| d.message.clone()).collect())
120 }
121
122 fn spellings(src: &str) -> Vec<String> {
123 scan(src).0.into_iter().map(|(_, text)| text).collect()
124 }
125
126 #[test]
127 fn a_declaration_lexes_into_the_tokens_it_looks_like() {
128 let (tokens, diagnostics) = scan("int x = 1;");
129 assert!(diagnostics.is_empty());
130 assert_eq!(
131 tokens,
132 vec![
133 (PpTokenKind::Ident, "int".to_owned()),
134 (PpTokenKind::Ident, "x".to_owned()),
135 (PpTokenKind::Punct(Punct::Eq), "=".to_owned()),
136 (PpTokenKind::Number, "1".to_owned()),
137 (PpTokenKind::Punct(Punct::Semi), ";".to_owned()),
138 ]
139 );
140 }
141
142 #[test]
143 fn punctuators_take_the_longest_match() {
144 assert_eq!(spellings(">>="), vec![">>="]);
145 assert_eq!(spellings(">> ="), vec![">>", "="]);
146 assert_eq!(spellings("a->b"), vec!["a", "->", "b"]);
147 assert_eq!(spellings("x+++y"), vec!["x", "++", "+", "y"]);
148 assert_eq!(spellings("..."), vec!["..."]);
149 assert_eq!(spellings(".."), vec![".", "."]);
150 assert_eq!(spellings("[[gnu::packed]]"), vec!["[", "[", "gnu", "::", "packed", "]", "]"]);
151 }
152
153 #[test]
154 fn digraphs_mean_the_same_thing_as_what_they_stand_for() {
155 let (tokens, _) = scan("<% <: %: %:%: :> %>");
156 let kinds: Vec<_> = tokens.iter().map(|(k, _)| *k).collect();
157 assert_eq!(
158 kinds,
159 vec![
160 PpTokenKind::Punct(Punct::LBrace),
161 PpTokenKind::Punct(Punct::LBracket),
162 PpTokenKind::Punct(Punct::Hash),
163 PpTokenKind::Punct(Punct::HashHash),
164 PpTokenKind::Punct(Punct::RBracket),
165 PpTokenKind::Punct(Punct::RBrace),
166 ]
167 );
168 }
169
170 #[test]
171 fn a_digraph_says_it_was_written_as_one() {
172 let mut interner = Interner::new();
173 let (tokens, _) = tokenize(b"<: [", 0, Options::new(), &mut interner);
174 assert!(tokens[0].flags.has(TokenFlags::DIGRAPH));
175 assert!(!tokens[1].flags.has(TokenFlags::DIGRAPH));
176 }
177
178 #[test]
179 fn a_pp_number_is_looser_than_a_constant() {
180 assert_eq!(spellings("0x1p+3"), vec!["0x1p+3"]);
183 assert_eq!(spellings("1.2.3"), vec!["1.2.3"]);
184 assert_eq!(spellings(".5f"), vec![".5f"]);
185 assert_eq!(spellings("1e-9"), vec!["1e-9"]);
186 assert_eq!(spellings("0b1010"), vec!["0b1010"]);
187 assert_eq!(spellings("42wb"), vec!["42wb"]);
188 }
189
190 #[test]
191 fn c23_digit_separators_stay_inside_the_number() {
192 assert_eq!(spellings("1'000'000"), vec!["1'000'000"]);
193 assert_eq!(spellings("1 'a'"), vec!["1", "'a'"]);
196 }
197
198 #[test]
199 fn literal_prefixes_belong_to_the_literal() {
200 let (tokens, _) = scan(r#"L"wide" u8"utf8" u'c' U"big" L'w' u8'x'"#);
201 let kinds: Vec<_> = tokens.iter().map(|(k, _)| *k).collect();
202 assert_eq!(
203 kinds,
204 vec![
205 PpTokenKind::StringLit,
206 PpTokenKind::StringLit,
207 PpTokenKind::CharConst,
208 PpTokenKind::StringLit,
209 PpTokenKind::CharConst,
210 PpTokenKind::CharConst,
211 ]
212 );
213 assert_eq!(tokens[0].1, "L\"wide\"");
214 }
215
216 #[test]
217 fn an_escaped_quote_does_not_end_a_literal() {
218 assert_eq!(spellings(r#""a\"b" x"#), vec![r#""a\"b""#, "x"]);
219 assert_eq!(spellings(r"'\\' y"), vec![r"'\\'", "y"]);
220 }
221
222 #[test]
223 fn a_literal_does_not_run_past_the_end_of_its_line() {
224 let (tokens, diagnostics) = scan("char *s = \"oops;\nint x;");
227 assert_eq!(diagnostics.len(), 1);
228 assert!(diagnostics[0].contains("missing terminating quote"));
229 assert!(tokens.iter().any(|(k, text)| *k == PpTokenKind::Ident && text == "int"));
230 }
231
232 #[test]
233 fn comments_are_whitespace_and_leave_a_space_behind() {
234 assert_eq!(spellings("a/*b*/c"), vec!["a", "c"]);
235 assert_eq!(spellings("a//b\nc"), vec!["a", "c"]);
236 let mut interner = Interner::new();
237 let (tokens, _) = tokenize(b"a/*b*/c", 0, Options::new(), &mut interner);
238 assert!(tokens[1].flags.has(TokenFlags::LEADING_SPACE));
239 }
240
241 #[test]
242 fn an_unterminated_comment_is_reported_once() {
243 let (_, diagnostics) = scan("int x; /* and then nothing");
244 assert_eq!(diagnostics, vec!["unterminated comment".to_owned()]);
245 }
246
247 #[test]
248 fn a_token_after_a_comment_that_crossed_a_line_still_starts_a_line() {
249 let mut interner = Interner::new();
252 let (tokens, _) = tokenize(b"x /*\n*/ #define F 1", 0, Options::new(), &mut interner);
253 assert!(!tokens[0].flags.has(TokenFlags::START_OF_LINE) || tokens[0].span.lo == 0);
254 assert!(tokens[1].flags.has(TokenFlags::START_OF_LINE));
255 assert_eq!(tokens[1].punct(), Some(Punct::Hash));
256 }
257
258 #[test]
259 fn the_first_token_of_a_line_says_so() {
260 let mut interner = Interner::new();
261 let (tokens, _) = tokenize(b"a b\nc", 0, Options::new(), &mut interner);
262 assert!(tokens[0].flags.has(TokenFlags::START_OF_LINE));
263 assert!(!tokens[1].flags.has(TokenFlags::START_OF_LINE));
264 assert!(tokens[2].flags.has(TokenFlags::START_OF_LINE));
265 }
266
267 #[test]
268 fn a_splice_joins_one_identifier_and_the_span_still_covers_real_bytes() {
269 let mut interner = Interner::new();
270 let (tokens, diagnostics) = tokenize(b"in\\\nt", 0, Options::new(), &mut interner);
271 assert!(diagnostics.is_empty());
272 assert_eq!(interner.resolve(tokens[0].value.unwrap()), "int");
273 assert!(tokens[0].flags.has(TokenFlags::SPLICED));
274 assert_eq!(tokens[0].span.lo, 0);
277 assert_eq!(tokens[0].span.hi, 5);
278 }
279
280 #[test]
281 fn a_splice_inside_a_punctuator_still_makes_one_punctuator() {
282 let mut interner = Interner::new();
283 let (tokens, _) = tokenize(b">\\\n>=", 0, Options::new(), &mut interner);
284 assert_eq!(tokens[0].punct(), Some(Punct::ShrEq));
285 assert!(tokens[0].flags.has(TokenFlags::SPLICED));
286 }
287
288 #[test]
289 fn a_clean_token_is_not_marked_spliced() {
290 let mut interner = Interner::new();
291 let (tokens, _) = tokenize(b"int", 0, Options::new(), &mut interner);
292 assert!(!tokens[0].flags.has(TokenFlags::SPLICED));
293 }
294
295 #[test]
296 fn trigraphs_are_off_by_default() {
297 let (tokens, _) = scan("??=define");
298 assert_eq!(tokens[0].0, PpTokenKind::Punct(Punct::Question));
299 let mut interner = Interner::new();
300 let opts = Options { trigraphs: true };
301 let (on, _) = tokenize(b"??=define", 0, opts, &mut interner);
302 assert_eq!(on[0].punct(), Some(Punct::Hash));
303 assert_eq!(interner.resolve(on[1].value.unwrap()), "define");
304 }
305
306 #[test]
311 fn a_line_comment_ends_where_a_splice_says_it_does() {
312 assert_eq!(spellings("a //b\\\nc\nd"), vec!["a", "d"]);
316 assert_eq!(spellings("a //b\\\r\nc\nd"), vec!["a", "d"]);
317 assert_eq!(spellings("a //bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\\\nc\nd"), vec!["a", "d"]);
320 }
321
322 #[test]
323 fn a_trigraph_backslash_still_continues_a_comment_it_is_at_the_end_of() {
324 let mut interner = Interner::new();
328 let src = b"a //bbbbbbbbbbbbbbbb??/\nc\nd";
329 let (on, _) = tokenize(src, 0, Options { trigraphs: true }, &mut interner);
330 let text: Vec<_> = on
331 .iter()
332 .filter(|t| !t.is_eof())
333 .filter_map(|t| t.value.map(|s| interner.resolve(s).to_owned()))
334 .collect();
335 assert_eq!(text, vec!["a", "d"]);
336 assert_eq!(spellings("a //bbbbbbbbbbbbbbbb??/\nc\nd"), vec!["a", "c", "d"]);
339 }
340
341 #[test]
342 fn a_block_comment_is_still_terminated_when_the_stars_are_a_long_way_in() {
343 let body = "x".repeat(200);
346 assert_eq!(spellings(&format!("a /*{body}*/ b")), vec!["a", "b"]);
347 assert_eq!(spellings(&format!("a /*{body}\n{body}*/ b")), vec!["a", "b"]);
348 assert_eq!(spellings(&format!("a /*{body}*{body}*/ b")), vec!["a", "b"]);
350 let (_, diagnostics) = scan(&format!("a /*{body}"));
352 assert_eq!(diagnostics, vec!["unterminated comment".to_owned()]);
353 }
354
355 #[test]
356 fn a_spliced_comment_opener_is_not_missed_by_the_whitespace_scan() {
357 assert_eq!(spellings("a /\\\n* body *\\\n/ b"), vec!["a", "b"]);
361 }
362
363 #[test]
364 fn a_long_run_of_indentation_leaves_exactly_one_space_behind() {
365 let mut interner = Interner::new();
368 let src = format!("a{}b", " ".repeat(100));
369 let (tokens, _) = tokenize(src.as_bytes(), 0, Options::new(), &mut interner);
370 assert!(tokens[1].flags.has(TokenFlags::LEADING_SPACE));
371 assert!(!tokens[1].flags.has(TokenFlags::START_OF_LINE));
372 let src = format!("a{}\nb", "\t".repeat(100));
375 let (tokens, _) = tokenize(src.as_bytes(), 0, Options::new(), &mut interner);
376 assert!(tokens[1].flags.has(TokenFlags::START_OF_LINE));
377 }
378
379 #[test]
380 fn a_stray_byte_is_a_token_rather_than_a_hard_stop() {
381 let (tokens, diagnostics) = scan("a ` b");
384 assert!(diagnostics.is_empty());
385 assert_eq!(tokens[1].0, PpTokenKind::Other);
386 assert_eq!(tokens[1].1, "`");
387 }
388
389 #[test]
390 fn a_file_that_is_only_whitespace_lexes_to_end_of_file() {
391 let mut interner = Interner::new();
392 let (tokens, diagnostics) = tokenize(b" \n\t\n", 0, Options::new(), &mut interner);
393 assert!(diagnostics.is_empty());
394 assert_eq!(tokens.len(), 1);
395 assert!(tokens[0].is_eof());
396 }
397
398 #[test]
399 fn the_empty_file_lexes_to_end_of_file() {
400 let mut interner = Interner::new();
401 let (tokens, _) = tokenize(b"", 0, Options::new(), &mut interner);
402 assert_eq!(tokens.len(), 1);
403 assert!(tokens[0].is_eof());
404 }
405
406 #[test]
407 fn spans_are_offset_by_where_the_file_sits() {
408 let mut interner = Interner::new();
411 let (tokens, _) = tokenize(b"ab", 1000, Options::new(), &mut interner);
412 assert_eq!(tokens[0].span.lo, 1000);
413 assert_eq!(tokens[0].span.hi, 1002);
414 }
415
416 #[test]
417 fn a_header_name_is_only_scanned_when_a_directive_asks_for_one() {
418 let mut interner = Interner::new();
419 let mut lexer = Lexer::new(b"<stdio.h>", 0, Options::new());
420 let header = lexer.header_name(&mut interner).expect("a header name starts here");
421 assert_eq!(header.kind, PpTokenKind::HeaderName);
422 assert_eq!(interner.resolve(header.value.unwrap()), "<stdio.h>");
423
424 let (tokens, _) = scan("<stdio.h>");
427 assert_eq!(tokens[0].0, PpTokenKind::Punct(Punct::Lt));
428 }
429
430 #[test]
431 fn a_quoted_header_name_works_and_a_computed_one_declines() {
432 let mut interner = Interner::new();
433 let mut lexer = Lexer::new(b" \"local.h\"", 0, Options::new());
434 let header = lexer.header_name(&mut interner).expect("a header name starts here");
435 assert_eq!(interner.resolve(header.value.unwrap()), "\"local.h\"");
436
437 let mut lexer = Lexer::new(b"MACRO_NAME", 0, Options::new());
438 assert!(lexer.header_name(&mut interner).is_none());
439 }
440
441 #[test]
442 fn identifiers_are_interned_during_the_scan_and_repeat_for_free() {
443 let mut interner = Interner::new();
444 let (tokens, _) = tokenize(b"foo bar foo", 0, Options::new(), &mut interner);
445 assert_eq!(tokens[0].value, tokens[2].value);
446 assert_ne!(tokens[0].value, tokens[1].value);
447 assert_eq!(interner.len(), 2);
448 }
449
450 #[test]
451 fn a_universal_character_name_is_part_of_the_identifier() {
452 let (tokens, diagnostics) = scan(r"café = 1;");
456 assert!(diagnostics.is_empty());
457 assert_eq!(tokens[0], (PpTokenKind::Ident, r"café".to_owned()));
458 assert_eq!(tokens[1].0, PpTokenKind::Punct(Punct::Eq));
459 }
460
461 #[test]
462 fn a_backslash_with_a_trailing_space_splices_and_says_so() {
463 let (tokens, diagnostics) = scan("in\\ \nt x;");
467 assert_eq!(tokens[0], (PpTokenKind::Ident, "int".to_owned()));
468 assert_eq!(
469 diagnostics,
470 vec!["backslash and line ending separated by whitespace".to_owned()]
471 );
472 }
473
474 #[test]
475 fn utf8_in_an_identifier_survives_the_scan() {
476 let (tokens, diagnostics) = scan("café = 1;");
477 assert!(diagnostics.is_empty());
478 assert_eq!(tokens[0].1, "café");
479 }
480
481 #[test]
482 fn every_byte_of_the_file_ends_up_in_exactly_one_span_or_in_trivia() {
483 let src = "int main(void) { return 0; } /* c */ \"s\" 'c' 1.5e+3 // end\n";
485 let mut interner = Interner::new();
486 let (tokens, _) = tokenize(src.as_bytes(), 0, Options::new(), &mut interner);
487 let mut last = 0;
488 for t in &tokens {
489 assert!(t.span.lo >= last, "spans went backwards at {:?}", t.kind);
490 assert!(t.span.hi >= t.span.lo);
491 last = t.span.hi;
492 }
493 assert_eq!(last as usize, src.len());
494 }
495
496 #[test]
497 fn milestone_is_recorded() {
498 assert!(MILESTONE.starts_with('M'));
499 }
500}