1#![doc(html_root_url = "https://docs.rs/rucc-lex/0.2.19")]
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
113 use super::*;
114
115 fn scan(src: &str) -> (Vec<(PpTokenKind, String)>, Vec<String>) {
118 let mut interner = Interner::new();
119 let (tokens, diagnostics) = tokenize(src.as_bytes(), 0, Options::new(), &mut interner);
120 let out = tokens
121 .iter()
122 .filter(|t| !t.is_eof())
123 .map(|t| {
124 let text = match t.value {
125 Some(sym) => interner.resolve(sym).to_owned(),
126 None => t.punct().map_or_else(String::new, |p| p.as_str().to_owned()),
127 };
128 (t.kind, text)
129 })
130 .collect();
131 (out, diagnostics.iter().map(|d| d.message.clone()).collect())
132 }
133
134 fn spellings(src: &str) -> Vec<String> {
135 scan(src).0.into_iter().map(|(_, text)| text).collect()
136 }
137
138 #[test]
139 fn a_declaration_lexes_into_the_tokens_it_looks_like() {
140 let (tokens, diagnostics) = scan("int x = 1;");
141 assert!(diagnostics.is_empty());
142 assert_eq!(
143 tokens,
144 vec![
145 (PpTokenKind::Ident, "int".to_owned()),
146 (PpTokenKind::Ident, "x".to_owned()),
147 (PpTokenKind::Punct(Punct::Eq), "=".to_owned()),
148 (PpTokenKind::Number, "1".to_owned()),
149 (PpTokenKind::Punct(Punct::Semi), ";".to_owned()),
150 ]
151 );
152 }
153
154 #[test]
155 fn punctuators_take_the_longest_match() {
156 assert_eq!(spellings(">>="), vec![">>="]);
157 assert_eq!(spellings(">> ="), vec![">>", "="]);
158 assert_eq!(spellings("a->b"), vec!["a", "->", "b"]);
159 assert_eq!(spellings("x+++y"), vec!["x", "++", "+", "y"]);
160 assert_eq!(spellings("..."), vec!["..."]);
161 assert_eq!(spellings(".."), vec![".", "."]);
162 assert_eq!(spellings("[[gnu::packed]]"), vec!["[", "[", "gnu", "::", "packed", "]", "]"]);
163 }
164
165 #[test]
166 fn digraphs_mean_the_same_thing_as_what_they_stand_for() {
167 let (tokens, _) = scan("<% <: %: %:%: :> %>");
168 let kinds: Vec<_> = tokens.iter().map(|(k, _)| *k).collect();
169 assert_eq!(
170 kinds,
171 vec![
172 PpTokenKind::Punct(Punct::LBrace),
173 PpTokenKind::Punct(Punct::LBracket),
174 PpTokenKind::Punct(Punct::Hash),
175 PpTokenKind::Punct(Punct::HashHash),
176 PpTokenKind::Punct(Punct::RBracket),
177 PpTokenKind::Punct(Punct::RBrace),
178 ]
179 );
180 }
181
182 #[test]
183 fn a_digraph_says_it_was_written_as_one() {
184 let mut interner = Interner::new();
185 let (tokens, _) = tokenize(b"<: [", 0, Options::new(), &mut interner);
186 assert!(tokens[0].flags.has(TokenFlags::DIGRAPH));
187 assert!(!tokens[1].flags.has(TokenFlags::DIGRAPH));
188 }
189
190 #[test]
191 fn a_pp_number_is_looser_than_a_constant() {
192 assert_eq!(spellings("0x1p+3"), vec!["0x1p+3"]);
195 assert_eq!(spellings("1.2.3"), vec!["1.2.3"]);
196 assert_eq!(spellings(".5f"), vec![".5f"]);
197 assert_eq!(spellings("1e-9"), vec!["1e-9"]);
198 assert_eq!(spellings("0b1010"), vec!["0b1010"]);
199 assert_eq!(spellings("42wb"), vec!["42wb"]);
200 }
201
202 #[test]
203 fn c23_digit_separators_stay_inside_the_number() {
204 assert_eq!(spellings("1'000'000"), vec!["1'000'000"]);
205 assert_eq!(spellings("1 'a'"), vec!["1", "'a'"]);
208 }
209
210 #[test]
211 fn literal_prefixes_belong_to_the_literal() {
212 let (tokens, _) = scan(r#"L"wide" u8"utf8" u'c' U"big" L'w' u8'x'"#);
213 let kinds: Vec<_> = tokens.iter().map(|(k, _)| *k).collect();
214 assert_eq!(
215 kinds,
216 vec![
217 PpTokenKind::StringLit,
218 PpTokenKind::StringLit,
219 PpTokenKind::CharConst,
220 PpTokenKind::StringLit,
221 PpTokenKind::CharConst,
222 PpTokenKind::CharConst,
223 ]
224 );
225 assert_eq!(tokens[0].1, "L\"wide\"");
226 }
227
228 #[test]
229 fn an_escaped_quote_does_not_end_a_literal() {
230 assert_eq!(spellings(r#""a\"b" x"#), vec![r#""a\"b""#, "x"]);
231 assert_eq!(spellings(r"'\\' y"), vec![r"'\\'", "y"]);
232 }
233
234 #[test]
235 fn a_literal_does_not_run_past_the_end_of_its_line() {
236 let (tokens, diagnostics) = scan("char *s = \"oops;\nint x;");
239 assert_eq!(diagnostics.len(), 1);
240 assert!(diagnostics[0].contains("missing terminating quote"));
241 assert!(tokens.iter().any(|(k, text)| *k == PpTokenKind::Ident && text == "int"));
242 }
243
244 #[test]
245 fn comments_are_whitespace_and_leave_a_space_behind() {
246 assert_eq!(spellings("a/*b*/c"), vec!["a", "c"]);
247 assert_eq!(spellings("a//b\nc"), vec!["a", "c"]);
248 let mut interner = Interner::new();
249 let (tokens, _) = tokenize(b"a/*b*/c", 0, Options::new(), &mut interner);
250 assert!(tokens[1].flags.has(TokenFlags::LEADING_SPACE));
251 }
252
253 #[test]
254 fn an_unterminated_comment_is_reported_once() {
255 let (_, diagnostics) = scan("int x; /* and then nothing");
256 assert_eq!(diagnostics, vec!["unterminated comment".to_owned()]);
257 }
258
259 #[test]
260 fn a_token_after_a_comment_that_crossed_a_line_still_starts_a_line() {
261 let mut interner = Interner::new();
264 let (tokens, _) = tokenize(b"x /*\n*/ #define F 1", 0, Options::new(), &mut interner);
265 assert!(!tokens[0].flags.has(TokenFlags::START_OF_LINE) || tokens[0].span.lo == 0);
266 assert!(tokens[1].flags.has(TokenFlags::START_OF_LINE));
267 assert_eq!(tokens[1].punct(), Some(Punct::Hash));
268 }
269
270 #[test]
271 fn the_first_token_of_a_line_says_so() {
272 let mut interner = Interner::new();
273 let (tokens, _) = tokenize(b"a b\nc", 0, Options::new(), &mut interner);
274 assert!(tokens[0].flags.has(TokenFlags::START_OF_LINE));
275 assert!(!tokens[1].flags.has(TokenFlags::START_OF_LINE));
276 assert!(tokens[2].flags.has(TokenFlags::START_OF_LINE));
277 }
278
279 #[test]
280 fn a_splice_joins_one_identifier_and_the_span_still_covers_real_bytes() {
281 let mut interner = Interner::new();
282 let (tokens, diagnostics) = tokenize(b"in\\\nt", 0, Options::new(), &mut interner);
283 assert!(diagnostics.is_empty());
284 assert_eq!(interner.resolve(tokens[0].value.unwrap()), "int");
285 assert!(tokens[0].flags.has(TokenFlags::SPLICED));
286 assert_eq!(tokens[0].span.lo, 0);
289 assert_eq!(tokens[0].span.hi, 5);
290 }
291
292 #[test]
293 fn a_splice_inside_a_punctuator_still_makes_one_punctuator() {
294 let mut interner = Interner::new();
295 let (tokens, _) = tokenize(b">\\\n>=", 0, Options::new(), &mut interner);
296 assert_eq!(tokens[0].punct(), Some(Punct::ShrEq));
297 assert!(tokens[0].flags.has(TokenFlags::SPLICED));
298 }
299
300 #[test]
301 fn a_clean_token_is_not_marked_spliced() {
302 let mut interner = Interner::new();
303 let (tokens, _) = tokenize(b"int", 0, Options::new(), &mut interner);
304 assert!(!tokens[0].flags.has(TokenFlags::SPLICED));
305 }
306
307 #[test]
308 fn trigraphs_are_off_by_default() {
309 let (tokens, _) = scan("??=define");
310 assert_eq!(tokens[0].0, PpTokenKind::Punct(Punct::Question));
311 let mut interner = Interner::new();
312 let opts = Options { trigraphs: true };
313 let (on, _) = tokenize(b"??=define", 0, opts, &mut interner);
314 assert_eq!(on[0].punct(), Some(Punct::Hash));
315 assert_eq!(interner.resolve(on[1].value.unwrap()), "define");
316 }
317
318 #[test]
323 fn a_line_comment_ends_where_a_splice_says_it_does() {
324 assert_eq!(spellings("a //b\\\nc\nd"), vec!["a", "d"]);
328 assert_eq!(spellings("a //b\\\r\nc\nd"), vec!["a", "d"]);
329 assert_eq!(spellings("a //bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\\\nc\nd"), vec!["a", "d"]);
332 }
333
334 #[test]
335 fn a_trigraph_backslash_still_continues_a_comment_it_is_at_the_end_of() {
336 let mut interner = Interner::new();
340 let src = b"a //bbbbbbbbbbbbbbbb??/\nc\nd";
341 let (on, _) = tokenize(src, 0, Options { trigraphs: true }, &mut interner);
342 let text: Vec<_> = on
343 .iter()
344 .filter(|t| !t.is_eof())
345 .filter_map(|t| t.value.map(|s| interner.resolve(s).to_owned()))
346 .collect();
347 assert_eq!(text, vec!["a", "d"]);
348 assert_eq!(spellings("a //bbbbbbbbbbbbbbbb??/\nc\nd"), vec!["a", "c", "d"]);
351 }
352
353 #[test]
354 fn a_block_comment_is_still_terminated_when_the_stars_are_a_long_way_in() {
355 let body = "x".repeat(200);
358 assert_eq!(spellings(&format!("a /*{body}*/ b")), vec!["a", "b"]);
359 assert_eq!(spellings(&format!("a /*{body}\n{body}*/ b")), vec!["a", "b"]);
360 assert_eq!(spellings(&format!("a /*{body}*{body}*/ b")), vec!["a", "b"]);
362 let (_, diagnostics) = scan(&format!("a /*{body}"));
364 assert_eq!(diagnostics, vec!["unterminated comment".to_owned()]);
365 }
366
367 #[test]
368 fn a_spliced_comment_opener_is_not_missed_by_the_whitespace_scan() {
369 assert_eq!(spellings("a /\\\n* body *\\\n/ b"), vec!["a", "b"]);
373 }
374
375 #[test]
376 fn a_long_run_of_indentation_leaves_exactly_one_space_behind() {
377 let mut interner = Interner::new();
380 let src = format!("a{}b", " ".repeat(100));
381 let (tokens, _) = tokenize(src.as_bytes(), 0, Options::new(), &mut interner);
382 assert!(tokens[1].flags.has(TokenFlags::LEADING_SPACE));
383 assert!(!tokens[1].flags.has(TokenFlags::START_OF_LINE));
384 let src = format!("a{}\nb", "\t".repeat(100));
387 let (tokens, _) = tokenize(src.as_bytes(), 0, Options::new(), &mut interner);
388 assert!(tokens[1].flags.has(TokenFlags::START_OF_LINE));
389 }
390
391 #[test]
392 fn a_stray_byte_is_a_token_rather_than_a_hard_stop() {
393 let (tokens, diagnostics) = scan("a ` b");
396 assert!(diagnostics.is_empty());
397 assert_eq!(tokens[1].0, PpTokenKind::Other);
398 assert_eq!(tokens[1].1, "`");
399 }
400
401 #[test]
402 fn a_file_that_is_only_whitespace_lexes_to_end_of_file() {
403 let mut interner = Interner::new();
404 let (tokens, diagnostics) = tokenize(b" \n\t\n", 0, Options::new(), &mut interner);
405 assert!(diagnostics.is_empty());
406 assert_eq!(tokens.len(), 1);
407 assert!(tokens[0].is_eof());
408 }
409
410 #[test]
411 fn the_empty_file_lexes_to_end_of_file() {
412 let mut interner = Interner::new();
413 let (tokens, _) = tokenize(b"", 0, Options::new(), &mut interner);
414 assert_eq!(tokens.len(), 1);
415 assert!(tokens[0].is_eof());
416 }
417
418 #[test]
419 fn spans_are_offset_by_where_the_file_sits() {
420 let mut interner = Interner::new();
423 let (tokens, _) = tokenize(b"ab", 1000, Options::new(), &mut interner);
424 assert_eq!(tokens[0].span.lo, 1000);
425 assert_eq!(tokens[0].span.hi, 1002);
426 }
427
428 #[test]
429 fn a_header_name_is_only_scanned_when_a_directive_asks_for_one() {
430 let mut interner = Interner::new();
431 let mut lexer = Lexer::new(b"<stdio.h>", 0, Options::new());
432 let header = lexer.header_name(&mut interner).expect("a header name starts here");
433 assert_eq!(header.kind, PpTokenKind::HeaderName);
434 assert_eq!(interner.resolve(header.value.unwrap()), "<stdio.h>");
435
436 let (tokens, _) = scan("<stdio.h>");
439 assert_eq!(tokens[0].0, PpTokenKind::Punct(Punct::Lt));
440 }
441
442 #[test]
443 fn a_quoted_header_name_works_and_a_computed_one_declines() {
444 let mut interner = Interner::new();
445 let mut lexer = Lexer::new(b" \"local.h\"", 0, Options::new());
446 let header = lexer.header_name(&mut interner).expect("a header name starts here");
447 assert_eq!(interner.resolve(header.value.unwrap()), "\"local.h\"");
448
449 let mut lexer = Lexer::new(b"MACRO_NAME", 0, Options::new());
450 assert!(lexer.header_name(&mut interner).is_none());
451 }
452
453 #[test]
454 fn identifiers_are_interned_during_the_scan_and_repeat_for_free() {
455 let mut interner = Interner::new();
456 let before = interner.len();
457 let (tokens, _) = tokenize(b"foo bar foo", 0, Options::new(), &mut interner);
458 assert_eq!(tokens[0].value, tokens[2].value);
459 assert_ne!(tokens[0].value, tokens[1].value);
460 assert_eq!(interner.len() - before, 2);
461 }
462
463 #[test]
464 fn a_universal_character_name_is_part_of_the_identifier() {
465 let (tokens, diagnostics) = scan(r"café = 1;");
469 assert!(diagnostics.is_empty());
470 assert_eq!(tokens[0], (PpTokenKind::Ident, r"café".to_owned()));
471 assert_eq!(tokens[1].0, PpTokenKind::Punct(Punct::Eq));
472 }
473
474 #[test]
475 fn a_backslash_with_a_trailing_space_splices_and_says_so() {
476 let (tokens, diagnostics) = scan("in\\ \nt x;");
480 assert_eq!(tokens[0], (PpTokenKind::Ident, "int".to_owned()));
481 assert_eq!(
482 diagnostics,
483 vec!["backslash and line ending separated by whitespace".to_owned()]
484 );
485 }
486
487 #[test]
488 fn utf8_in_an_identifier_survives_the_scan() {
489 let (tokens, diagnostics) = scan("café = 1;");
490 assert!(diagnostics.is_empty());
491 assert_eq!(tokens[0].1, "café");
492 }
493
494 #[test]
495 fn every_byte_of_the_file_ends_up_in_exactly_one_span_or_in_trivia() {
496 let src = "int main(void) { return 0; } /* c */ \"s\" 'c' 1.5e+3 // end\n";
498 let mut interner = Interner::new();
499 let (tokens, _) = tokenize(src.as_bytes(), 0, Options::new(), &mut interner);
500 let mut last = 0;
501 for t in &tokens {
502 assert!(t.span.lo >= last, "spans went backwards at {:?}", t.kind);
503 assert!(t.span.hi >= t.span.lo);
504 last = t.span.hi;
505 }
506 assert_eq!(last as usize, src.len());
507 }
508
509 #[test]
510 fn milestone_is_recorded() {
511 assert!(MILESTONE.starts_with('M'));
512 }
513}