1use rucc_base::{Interner, Symbol};
12use rucc_diag::{BytePos, Diagnostic, Span};
13use rucc_session::Std;
14
15use crate::class::{CLASS, Class, is_ident_continue};
16use crate::cursor::Cursor;
17use crate::token::{PpToken, PpTokenKind, Punct, TokenFlags};
18
19#[derive(Debug, Clone, Copy, PartialEq, Eq)]
21#[non_exhaustive]
22pub struct Options {
23 pub trigraphs: bool,
29 pub line_comments: bool,
33 pub digit_separators: bool,
37}
38
39impl Options {
40 #[must_use]
42 pub fn new() -> Options {
43 Options { trigraphs: false, line_comments: true, digit_separators: true }
44 }
45
46 #[must_use]
48 pub fn for_dialect(std: Std, gnu: bool) -> Options {
49 Options {
50 trigraphs: false,
51 line_comments: std >= Std::C99 || gnu,
52 digit_separators: std >= Std::C23,
53 }
54 }
55}
56
57impl Default for Options {
58 fn default() -> Options {
59 Options::new()
60 }
61}
62
63#[derive(Debug)]
65pub struct Lexer<'a> {
66 cursor: Cursor<'a>,
67 file_start: BytePos,
70 at_line_start: bool,
71 leading_space: bool,
72 token_start: u32,
75 scratch: Vec<u8>,
78 unclean: bool,
80 options: Options,
82 reported_line_comment: bool,
85 diagnostics: Vec<Diagnostic>,
86}
87
88impl<'a> Lexer<'a> {
89 #[must_use]
91 pub fn new(src: &'a [u8], file_start: BytePos, opts: Options) -> Lexer<'a> {
92 Lexer {
93 cursor: Cursor::new(src, opts.trigraphs),
94 file_start,
95 at_line_start: true,
96 leading_space: false,
97 token_start: 0,
98 scratch: Vec::new(),
99 unclean: false,
100 options: opts,
101 reported_line_comment: false,
102 diagnostics: Vec::new(),
103 }
104 }
105
106 #[must_use]
108 pub fn diagnostics(&self) -> &[Diagnostic] {
109 &self.diagnostics
110 }
111
112 pub fn take_diagnostics(&mut self) -> Vec<Diagnostic> {
114 std::mem::take(&mut self.diagnostics)
115 }
116
117 pub fn next_token(&mut self, interner: &mut Interner) -> PpToken {
119 let token = self.scan(interner);
120 self.report_loose_splices();
121 token
122 }
123
124 fn report_loose_splices(&mut self) {
131 for at in self.cursor.take_loose_splices() {
132 let span = Span::new(self.file_start + at, self.file_start + at + 1);
133 self.diagnostics.push(Diagnostic::warning(
134 "backslash and line ending separated by whitespace",
135 span,
136 ));
137 }
138 }
139
140 fn scan(&mut self, interner: &mut Interner) -> PpToken {
141 self.skip_trivia();
142
143 let start = self.cursor.pos();
144 let flags = self.take_flags();
145
146 if self.cursor.at_end() {
147 return PpToken {
148 kind: PpTokenKind::Eof,
149 flags,
150 value: None,
151 span: Span::empty_at(self.file_start + start),
152 };
153 }
154
155 self.token_start = start;
156 self.unclean = false;
157
158 let b = self.cursor.first();
159 let kind = match CLASS[b as usize] {
160 Class::IdentStart => self.ident_or_prefixed_literal(b, start),
161 Class::Digit => self.pp_number(),
162 Class::Dot if CLASS[self.cursor.nth(1) as usize] == Class::Digit => self.pp_number(),
163 Class::Quote => self.literal(b'"', start, PpTokenKind::StringLit),
164 Class::Apostrophe => self.literal(b'\'', start, PpTokenKind::CharConst),
165 Class::Backslash => {
166 if matches!(self.cursor.nth(1), b'u' | b'U') {
169 self.identifier()
170 } else {
171 self.eat();
172 PpTokenKind::Other
173 }
174 }
175 Class::Dot | Class::Slash | Class::Punct => match self.punctuator(start, flags) {
176 Some(token) => return token,
177 None => {
178 self.eat();
179 PpTokenKind::Other
180 }
181 },
182 Class::Space | Class::Newline | Class::Other => {
183 self.eat();
184 PpTokenKind::Other
185 }
186 };
187
188 let end = self.cursor.pos();
189 let value = Some(self.intern_spelling(interner, start, end));
190 let mut flags = flags;
191 if self.unclean {
192 flags = flags.with(TokenFlags::SPLICED);
193 }
194 let span = Span::new(self.file_start + start, self.file_start + end);
195 PpToken { kind, flags, value, span }
196 }
197
198 pub fn header_name(&mut self, interner: &mut Interner) -> Option<PpToken> {
205 let token = self.scan_header_name(interner);
206 self.report_loose_splices();
207 token
208 }
209
210 fn scan_header_name(&mut self, interner: &mut Interner) -> Option<PpToken> {
211 self.skip_horizontal();
212 let start = self.cursor.pos();
213 let close = match self.cursor.first() {
214 b'<' => b'>',
215 b'"' => b'"',
216 _ => return None,
217 };
218 let flags = self.take_flags();
219 self.token_start = start;
220 self.unclean = false;
221 self.eat();
222 loop {
223 if self.cursor.at_end() || self.cursor.first() == b'\n' {
224 let span = Span::new(self.file_start + start, self.file_start + self.cursor.pos());
225 self.diagnostics
226 .push(Diagnostic::error("missing terminating character in header name", span));
227 break;
228 }
229 if self.eat() == close {
230 break;
231 }
232 }
233 let end = self.cursor.pos();
234 let value = Some(self.intern_spelling(interner, start, end));
235 let mut flags = flags;
236 if self.unclean {
237 flags = flags.with(TokenFlags::SPLICED);
238 }
239 let span = Span::new(self.file_start + start, self.file_start + end);
240 Some(PpToken { kind: PpTokenKind::HeaderName, flags, value, span })
241 }
242
243 fn take_flags(&mut self) -> TokenFlags {
245 let mut flags = TokenFlags::EMPTY;
246 if self.at_line_start {
247 flags = flags.with(TokenFlags::START_OF_LINE);
248 }
249 if self.leading_space {
250 flags = flags.with(TokenFlags::LEADING_SPACE);
251 }
252 self.at_line_start = false;
253 self.leading_space = false;
254 flags
255 }
256
257 fn eat(&mut self) -> u8 {
259 let before = self.cursor.pos();
260 let (b, clean) = self.cursor.bump().expect("eat called at end of file");
263 if self.unclean {
264 self.scratch.push(b);
265 } else if !clean {
266 let from = self.token_start as usize;
269 let bytes = self.cursor.bytes();
270 self.scratch.clear();
271 self.scratch.extend_from_slice(&bytes[from..before as usize]);
272 self.scratch.push(b);
273 self.unclean = true;
274 }
275 b
276 }
277
278 fn intern_spelling(&mut self, interner: &mut Interner, start: u32, end: u32) -> Symbol {
280 let lossy = {
281 let bytes: &[u8] = if self.unclean {
282 &self.scratch
283 } else {
284 &self.cursor.bytes()[start as usize..end as usize]
285 };
286 match std::str::from_utf8(bytes) {
287 Ok(text) => return interner.intern(text),
288 Err(_) => String::from_utf8_lossy(bytes).into_owned(),
292 }
293 };
294 let span = Span::new(self.file_start + start, self.file_start + end);
295 self.diagnostics.push(Diagnostic::error("source is not valid UTF-8 here", span));
296 interner.intern(&lossy)
297 }
298
299 fn skip_trivia(&mut self) {
301 while !self.cursor.at_end() {
302 if self.cursor.skip_blanks() {
305 self.leading_space = true;
306 continue;
307 }
308 match CLASS[self.cursor.first() as usize] {
309 Class::Space => {
310 self.cursor.bump();
311 self.leading_space = true;
312 }
313 Class::Newline => {
314 self.cursor.bump();
315 self.at_line_start = true;
316 self.leading_space = false;
317 }
318 Class::Slash => match self.cursor.nth(1) {
319 b'/' => self.line_comment(),
320 b'*' => self.block_comment(),
321 _ => return,
322 },
323 _ => return,
324 }
325 }
326 }
327
328 fn skip_horizontal(&mut self) {
330 while !self.cursor.at_end() {
331 if self.cursor.skip_blanks() {
332 self.leading_space = true;
333 continue;
334 }
335 let b = self.cursor.first();
336 if CLASS[b as usize] == Class::Space {
337 self.cursor.bump();
338 self.leading_space = true;
339 } else if b == b'/' && self.cursor.nth(1) == b'*' {
340 self.block_comment();
341 } else {
342 return;
343 }
344 }
345 }
346
347 fn line_comment(&mut self) {
348 if !self.options.line_comments && !self.reported_line_comment {
349 self.reported_line_comment = true;
353 let at = self.cursor.pos();
354 let span = Span::new(self.file_start + at, self.file_start + at + 2);
355 self.diagnostics
356 .push(Diagnostic::error("C++ style comments are not allowed in ISO C90", span));
357 }
358 while !self.cursor.at_end() && self.cursor.first() != b'\n' {
359 self.cursor.skip_plain(&[]);
363 if self.cursor.at_end() || self.cursor.first() == b'\n' {
364 break;
365 }
366 self.cursor.bump();
367 }
368 self.leading_space = true;
371 }
372
373 fn block_comment(&mut self) {
374 let start = self.cursor.pos();
375 self.cursor.bump();
376 self.cursor.bump();
377 let mut spans_lines = false;
378 loop {
379 self.cursor.skip_plain(b"*");
383 if self.cursor.at_end() {
384 let span = Span::new(self.file_start + start, self.file_start + self.cursor.pos());
385 self.diagnostics.push(Diagnostic::error("unterminated comment", span));
386 break;
387 }
388 let b = self.cursor.first();
389 if b == b'\n' {
390 spans_lines = true;
391 }
392 if b == b'*' && self.cursor.nth(1) == b'/' {
393 self.cursor.bump();
394 self.cursor.bump();
395 break;
396 }
397 self.cursor.bump();
398 }
399 self.leading_space = true;
400 if spans_lines {
401 self.at_line_start = true;
405 }
406 }
407
408 fn ident_or_prefixed_literal(&mut self, b: u8, start: u32) -> PpTokenKind {
409 let (n1, n2) = (self.cursor.nth(1), self.cursor.nth(2));
412 match b {
413 b'L' | b'u' | b'U' if n1 == b'"' => {
414 self.eat();
415 self.literal(b'"', start, PpTokenKind::StringLit)
416 }
417 b'L' | b'u' | b'U' if n1 == b'\'' => {
418 self.eat();
419 self.literal(b'\'', start, PpTokenKind::CharConst)
420 }
421 b'u' if n1 == b'8' && (n2 == b'"' || n2 == b'\'') => {
423 self.eat();
424 self.eat();
425 let kind = if n2 == b'"' { PpTokenKind::StringLit } else { PpTokenKind::CharConst };
426 self.literal(n2, start, kind)
427 }
428 _ => self.identifier(),
429 }
430 }
431
432 fn identifier(&mut self) -> PpTokenKind {
433 while !self.cursor.at_end() {
434 let b = self.cursor.first();
435 if is_ident_continue(b) {
436 self.eat();
437 } else if b == b'\\' && matches!(self.cursor.nth(1), b'u' | b'U') {
438 self.eat();
442 self.eat();
443 } else {
444 break;
445 }
446 }
447 PpTokenKind::Ident
448 }
449
450 fn pp_number(&mut self) -> PpTokenKind {
451 self.eat();
455 while !self.cursor.at_end() {
456 let b = self.cursor.first();
457 let n1 = self.cursor.nth(1);
458 if matches!(b, b'e' | b'E' | b'p' | b'P') && matches!(n1, b'+' | b'-') {
459 self.eat();
460 self.eat();
461 } else if is_ident_continue(b) || b == b'.' {
462 self.eat();
463 } else if b == b'\'' && is_ident_continue(n1) && self.options.digit_separators {
464 self.eat();
469 self.eat();
470 } else if b == b'\\' && matches!(n1, b'u' | b'U') {
471 self.eat();
472 self.eat();
473 } else {
474 break;
475 }
476 }
477 PpTokenKind::Number
478 }
479
480 fn literal(&mut self, quote: u8, start: u32, kind: PpTokenKind) -> PpTokenKind {
481 self.eat();
482 loop {
483 if self.cursor.at_end() || self.cursor.first() == b'\n' {
484 let span = Span::new(self.file_start + start, self.file_start + self.cursor.pos());
488 let what = if quote == b'"' { "string literal" } else { "character constant" };
489 self.diagnostics
490 .push(Diagnostic::error(format!("missing terminating quote in {what}"), span));
491 break;
492 }
493 let b = self.eat();
494 if b == quote {
495 break;
496 }
497 if b == b'\\' && !self.cursor.at_end() && self.cursor.first() != b'\n' {
498 self.eat();
501 }
502 }
503 kind
504 }
505
506 fn punctuator(&mut self, start: u32, flags: TokenFlags) -> Option<PpToken> {
509 let (punct, len, digraph) = self.punctuator_kind()?;
510 for _ in 0..len {
511 self.eat();
512 }
513 let end = self.cursor.pos();
514 let mut flags = flags;
515 if digraph {
516 flags = flags.with(TokenFlags::DIGRAPH);
517 }
518 if self.unclean {
519 flags = flags.with(TokenFlags::SPLICED);
520 }
521 let span = Span::new(self.file_start + start, self.file_start + end);
522 Some(PpToken { kind: PpTokenKind::Punct(punct), flags, value: None, span })
523 }
524
525 fn punctuator_kind(&self) -> Option<(Punct, usize, bool)> {
527 let one = self.cursor.first();
528 let two = self.cursor.nth(1);
529 let three = self.cursor.nth(2);
530 let four = self.cursor.nth(3);
531 let found = match one {
532 b'[' => (Punct::LBracket, 1, false),
533 b']' => (Punct::RBracket, 1, false),
534 b'(' => (Punct::LParen, 1, false),
535 b')' => (Punct::RParen, 1, false),
536 b'{' => (Punct::LBrace, 1, false),
537 b'}' => (Punct::RBrace, 1, false),
538 b'~' => (Punct::Tilde, 1, false),
539 b'?' => (Punct::Question, 1, false),
540 b';' => (Punct::Semi, 1, false),
541 b',' => (Punct::Comma, 1, false),
542 b'.' if two == b'.' && three == b'.' => (Punct::Ellipsis, 3, false),
543 b'.' => (Punct::Dot, 1, false),
544 b'-' => match two {
545 b'>' => (Punct::Arrow, 2, false),
546 b'-' => (Punct::MinusMinus, 2, false),
547 b'=' => (Punct::MinusEq, 2, false),
548 _ => (Punct::Minus, 1, false),
549 },
550 b'+' => match two {
551 b'+' => (Punct::PlusPlus, 2, false),
552 b'=' => (Punct::PlusEq, 2, false),
553 _ => (Punct::Plus, 1, false),
554 },
555 b'&' => match two {
556 b'&' => (Punct::AmpAmp, 2, false),
557 b'=' => (Punct::AmpEq, 2, false),
558 _ => (Punct::Amp, 1, false),
559 },
560 b'|' => match two {
561 b'|' => (Punct::PipePipe, 2, false),
562 b'=' => (Punct::PipeEq, 2, false),
563 _ => (Punct::Pipe, 1, false),
564 },
565 b'*' if two == b'=' => (Punct::StarEq, 2, false),
566 b'*' => (Punct::Star, 1, false),
567 b'/' if two == b'=' => (Punct::SlashEq, 2, false),
568 b'/' => (Punct::Slash, 1, false),
569 b'!' if two == b'=' => (Punct::Ne, 2, false),
570 b'!' => (Punct::Bang, 1, false),
571 b'^' if two == b'=' => (Punct::CaretEq, 2, false),
572 b'^' => (Punct::Caret, 1, false),
573 b'=' if two == b'=' => (Punct::EqEq, 2, false),
574 b'=' => (Punct::Eq, 1, false),
575 b':' => match two {
576 b'>' => (Punct::RBracket, 2, true),
577 b':' => (Punct::ColonColon, 2, false),
578 _ => (Punct::Colon, 1, false),
579 },
580 b'<' => match two {
581 b'<' if three == b'=' => (Punct::ShlEq, 3, false),
582 b'<' => (Punct::Shl, 2, false),
583 b'=' => (Punct::Le, 2, false),
584 b':' => (Punct::LBracket, 2, true),
585 b'%' => (Punct::LBrace, 2, true),
586 _ => (Punct::Lt, 1, false),
587 },
588 b'>' => match two {
589 b'>' if three == b'=' => (Punct::ShrEq, 3, false),
590 b'>' => (Punct::Shr, 2, false),
591 b'=' => (Punct::Ge, 2, false),
592 _ => (Punct::Gt, 1, false),
593 },
594 b'%' => match two {
595 b'=' => (Punct::PercentEq, 2, false),
596 b'>' => (Punct::RBrace, 2, true),
597 b':' if three == b'%' && four == b':' => (Punct::HashHash, 4, true),
598 b':' => (Punct::Hash, 2, true),
599 _ => (Punct::Percent, 1, false),
600 },
601 b'#' if two == b'#' => (Punct::HashHash, 2, false),
602 b'#' => (Punct::Hash, 1, false),
603 _ => return None,
604 };
605 Some(found)
606 }
607}
608
609pub fn tokenize(
614 src: &[u8],
615 file_start: BytePos,
616 opts: Options,
617 interner: &mut Interner,
618) -> (Vec<PpToken>, Vec<Diagnostic>) {
619 let mut lexer = Lexer::new(src, file_start, opts);
620 let mut out = Vec::new();
621 loop {
622 let token = lexer.next_token(interner);
623 let done = token.is_eof();
624 out.push(token);
625 if done {
626 break;
627 }
628 }
629 let diagnostics = lexer.take_diagnostics();
630 (out, diagnostics)
631}