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, kind, 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, PpTokenKind::HeaderName, 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(
288 &mut self,
289 interner: &mut Interner,
290 kind: PpTokenKind,
291 start: u32,
292 end: u32,
293 ) -> Symbol {
294 let bytes: &[u8] = if self.unclean {
295 &self.scratch
296 } else {
297 &self.cursor.bytes()[start as usize..end as usize]
298 };
299 if std::str::from_utf8(bytes).is_ok() {
300 return interner.intern_bytes(bytes);
301 }
302 if matches!(kind, PpTokenKind::StringLit | PpTokenKind::CharConst) {
303 return interner.intern_bytes(bytes);
304 }
305 let symbol = interner.intern_bytes(bytes);
306 let span = Span::new(self.file_start + start, self.file_start + end);
307 self.diagnostics.push(Diagnostic::error("source is not valid UTF-8 here", span));
310 symbol
311 }
312
313 fn skip_trivia(&mut self) {
315 while !self.cursor.at_end() {
316 if self.cursor.skip_blanks() {
319 self.leading_space = true;
320 continue;
321 }
322 match CLASS[self.cursor.first() as usize] {
323 Class::Space => {
324 self.cursor.bump();
325 self.leading_space = true;
326 }
327 Class::Newline => {
328 self.cursor.bump();
329 self.at_line_start = true;
330 self.leading_space = false;
331 }
332 Class::Slash => match self.cursor.nth(1) {
333 b'/' => self.line_comment(),
334 b'*' => self.block_comment(),
335 _ => return,
336 },
337 _ => return,
338 }
339 }
340 }
341
342 fn skip_horizontal(&mut self) {
344 while !self.cursor.at_end() {
345 if self.cursor.skip_blanks() {
346 self.leading_space = true;
347 continue;
348 }
349 let b = self.cursor.first();
350 if CLASS[b as usize] == Class::Space {
351 self.cursor.bump();
352 self.leading_space = true;
353 } else if b == b'/' && self.cursor.nth(1) == b'*' {
354 self.block_comment();
355 } else {
356 return;
357 }
358 }
359 }
360
361 fn line_comment(&mut self) {
362 if !self.options.line_comments && !self.reported_line_comment {
363 self.reported_line_comment = true;
367 let at = self.cursor.pos();
368 let span = Span::new(self.file_start + at, self.file_start + at + 2);
369 self.diagnostics
370 .push(Diagnostic::error("C++ style comments are not allowed in ISO C90", span));
371 }
372 while !self.cursor.at_end() && self.cursor.first() != b'\n' {
373 self.cursor.skip_plain(&[]);
377 if self.cursor.at_end() || self.cursor.first() == b'\n' {
378 break;
379 }
380 self.cursor.bump();
381 }
382 self.leading_space = true;
385 }
386
387 fn block_comment(&mut self) {
388 let start = self.cursor.pos();
389 self.cursor.bump();
390 self.cursor.bump();
391 let mut spans_lines = false;
392 loop {
393 self.cursor.skip_plain(b"*");
397 if self.cursor.at_end() {
398 let span = Span::new(self.file_start + start, self.file_start + self.cursor.pos());
399 self.diagnostics.push(Diagnostic::error("unterminated comment", span));
400 break;
401 }
402 let b = self.cursor.first();
403 if b == b'\n' {
404 spans_lines = true;
405 }
406 if b == b'*' && self.cursor.nth(1) == b'/' {
407 self.cursor.bump();
408 self.cursor.bump();
409 break;
410 }
411 self.cursor.bump();
412 }
413 self.leading_space = true;
414 if spans_lines {
415 self.at_line_start = true;
419 }
420 }
421
422 fn ident_or_prefixed_literal(&mut self, b: u8, start: u32) -> PpTokenKind {
423 let (n1, n2) = (self.cursor.nth(1), self.cursor.nth(2));
426 match b {
427 b'L' | b'u' | b'U' if n1 == b'"' => {
428 self.eat();
429 self.literal(b'"', start, PpTokenKind::StringLit)
430 }
431 b'L' | b'u' | b'U' if n1 == b'\'' => {
432 self.eat();
433 self.literal(b'\'', start, PpTokenKind::CharConst)
434 }
435 b'u' if n1 == b'8' && (n2 == b'"' || n2 == b'\'') => {
437 self.eat();
438 self.eat();
439 let kind = if n2 == b'"' { PpTokenKind::StringLit } else { PpTokenKind::CharConst };
440 self.literal(n2, start, kind)
441 }
442 _ => self.identifier(),
443 }
444 }
445
446 fn identifier(&mut self) -> PpTokenKind {
447 while !self.cursor.at_end() {
448 let b = self.cursor.first();
449 if is_ident_continue(b) {
450 self.eat();
451 } else if b == b'\\' && matches!(self.cursor.nth(1), b'u' | b'U') {
452 self.eat();
456 self.eat();
457 } else {
458 break;
459 }
460 }
461 PpTokenKind::Ident
462 }
463
464 fn pp_number(&mut self) -> PpTokenKind {
465 self.eat();
469 while !self.cursor.at_end() {
470 let b = self.cursor.first();
471 let n1 = self.cursor.nth(1);
472 if matches!(b, b'e' | b'E' | b'p' | b'P') && matches!(n1, b'+' | b'-') {
473 self.eat();
474 self.eat();
475 } else if is_ident_continue(b) || b == b'.' {
476 self.eat();
477 } else if b == b'\'' && is_ident_continue(n1) && self.options.digit_separators {
478 self.eat();
483 self.eat();
484 } else if b == b'\\' && matches!(n1, b'u' | b'U') {
485 self.eat();
486 self.eat();
487 } else {
488 break;
489 }
490 }
491 PpTokenKind::Number
492 }
493
494 fn literal(&mut self, quote: u8, start: u32, kind: PpTokenKind) -> PpTokenKind {
495 self.eat();
496 loop {
497 if self.cursor.at_end() || self.cursor.first() == b'\n' {
498 let span = Span::new(self.file_start + start, self.file_start + self.cursor.pos());
502 let what = if quote == b'"' { "string literal" } else { "character constant" };
503 self.diagnostics
504 .push(Diagnostic::error(format!("missing terminating quote in {what}"), span));
505 break;
506 }
507 let b = self.eat();
508 if b == quote {
509 break;
510 }
511 if b == b'\\' && !self.cursor.at_end() && self.cursor.first() != b'\n' {
512 self.eat();
515 }
516 }
517 kind
518 }
519
520 fn punctuator(&mut self, start: u32, flags: TokenFlags) -> Option<PpToken> {
523 let (punct, len, digraph) = self.punctuator_kind()?;
524 for _ in 0..len {
525 self.eat();
526 }
527 let end = self.cursor.pos();
528 let mut flags = flags;
529 if digraph {
530 flags = flags.with(TokenFlags::DIGRAPH);
531 }
532 if self.unclean {
533 flags = flags.with(TokenFlags::SPLICED);
534 }
535 let span = Span::new(self.file_start + start, self.file_start + end);
536 Some(PpToken { kind: PpTokenKind::Punct(punct), flags, value: None, span })
537 }
538
539 fn punctuator_kind(&self) -> Option<(Punct, usize, bool)> {
541 let one = self.cursor.first();
542 let two = self.cursor.nth(1);
543 let three = self.cursor.nth(2);
544 let four = self.cursor.nth(3);
545 let found = match one {
546 b'[' => (Punct::LBracket, 1, false),
547 b']' => (Punct::RBracket, 1, false),
548 b'(' => (Punct::LParen, 1, false),
549 b')' => (Punct::RParen, 1, false),
550 b'{' => (Punct::LBrace, 1, false),
551 b'}' => (Punct::RBrace, 1, false),
552 b'~' => (Punct::Tilde, 1, false),
553 b'?' => (Punct::Question, 1, false),
554 b';' => (Punct::Semi, 1, false),
555 b',' => (Punct::Comma, 1, false),
556 b'.' if two == b'.' && three == b'.' => (Punct::Ellipsis, 3, false),
557 b'.' => (Punct::Dot, 1, false),
558 b'-' => match two {
559 b'>' => (Punct::Arrow, 2, false),
560 b'-' => (Punct::MinusMinus, 2, false),
561 b'=' => (Punct::MinusEq, 2, false),
562 _ => (Punct::Minus, 1, false),
563 },
564 b'+' => match two {
565 b'+' => (Punct::PlusPlus, 2, false),
566 b'=' => (Punct::PlusEq, 2, false),
567 _ => (Punct::Plus, 1, false),
568 },
569 b'&' => match two {
570 b'&' => (Punct::AmpAmp, 2, false),
571 b'=' => (Punct::AmpEq, 2, false),
572 _ => (Punct::Amp, 1, false),
573 },
574 b'|' => match two {
575 b'|' => (Punct::PipePipe, 2, false),
576 b'=' => (Punct::PipeEq, 2, false),
577 _ => (Punct::Pipe, 1, false),
578 },
579 b'*' if two == b'=' => (Punct::StarEq, 2, false),
580 b'*' => (Punct::Star, 1, false),
581 b'/' if two == b'=' => (Punct::SlashEq, 2, false),
582 b'/' => (Punct::Slash, 1, false),
583 b'!' if two == b'=' => (Punct::Ne, 2, false),
584 b'!' => (Punct::Bang, 1, false),
585 b'^' if two == b'=' => (Punct::CaretEq, 2, false),
586 b'^' => (Punct::Caret, 1, false),
587 b'=' if two == b'=' => (Punct::EqEq, 2, false),
588 b'=' => (Punct::Eq, 1, false),
589 b':' => match two {
590 b'>' => (Punct::RBracket, 2, true),
591 b':' => (Punct::ColonColon, 2, false),
592 _ => (Punct::Colon, 1, false),
593 },
594 b'<' => match two {
595 b'<' if three == b'=' => (Punct::ShlEq, 3, false),
596 b'<' => (Punct::Shl, 2, false),
597 b'=' => (Punct::Le, 2, false),
598 b':' => (Punct::LBracket, 2, true),
599 b'%' => (Punct::LBrace, 2, true),
600 _ => (Punct::Lt, 1, false),
601 },
602 b'>' => match two {
603 b'>' if three == b'=' => (Punct::ShrEq, 3, false),
604 b'>' => (Punct::Shr, 2, false),
605 b'=' => (Punct::Ge, 2, false),
606 _ => (Punct::Gt, 1, false),
607 },
608 b'%' => match two {
609 b'=' => (Punct::PercentEq, 2, false),
610 b'>' => (Punct::RBrace, 2, true),
611 b':' if three == b'%' && four == b':' => (Punct::HashHash, 4, true),
612 b':' => (Punct::Hash, 2, true),
613 _ => (Punct::Percent, 1, false),
614 },
615 b'#' if two == b'#' => (Punct::HashHash, 2, false),
616 b'#' => (Punct::Hash, 1, false),
617 _ => return None,
618 };
619 Some(found)
620 }
621}
622
623pub fn tokenize(
628 src: &[u8],
629 file_start: BytePos,
630 opts: Options,
631 interner: &mut Interner,
632) -> (Vec<PpToken>, Vec<Diagnostic>) {
633 let mut lexer = Lexer::new(src, file_start, opts);
634 let mut out = Vec::new();
635 loop {
636 let token = lexer.next_token(interner);
637 let done = token.is_eof();
638 out.push(token);
639 if done {
640 break;
641 }
642 }
643 let diagnostics = lexer.take_diagnostics();
644 (out, diagnostics)
645}