1use rucc_base::{Interner, Symbol};
12use rucc_diag::{BytePos, Diagnostic, Span};
13
14use crate::class::{CLASS, Class, is_ident_continue};
15use crate::cursor::Cursor;
16use crate::token::{PpToken, PpTokenKind, Punct, TokenFlags};
17
18#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
20#[non_exhaustive]
21pub struct Options {
22 pub trigraphs: bool,
28}
29
30impl Options {
31 #[must_use]
33 pub fn new() -> Options {
34 Options { trigraphs: false }
35 }
36}
37
38#[derive(Debug)]
40pub struct Lexer<'a> {
41 cursor: Cursor<'a>,
42 file_start: BytePos,
45 at_line_start: bool,
46 leading_space: bool,
47 token_start: u32,
50 scratch: Vec<u8>,
53 unclean: bool,
55 diagnostics: Vec<Diagnostic>,
56}
57
58impl<'a> Lexer<'a> {
59 #[must_use]
61 pub fn new(src: &'a [u8], file_start: BytePos, opts: Options) -> Lexer<'a> {
62 Lexer {
63 cursor: Cursor::new(src, opts.trigraphs),
64 file_start,
65 at_line_start: true,
66 leading_space: false,
67 token_start: 0,
68 scratch: Vec::new(),
69 unclean: false,
70 diagnostics: Vec::new(),
71 }
72 }
73
74 #[must_use]
76 pub fn diagnostics(&self) -> &[Diagnostic] {
77 &self.diagnostics
78 }
79
80 pub fn take_diagnostics(&mut self) -> Vec<Diagnostic> {
82 std::mem::take(&mut self.diagnostics)
83 }
84
85 pub fn next_token(&mut self, interner: &mut Interner) -> PpToken {
87 let token = self.scan(interner);
88 self.report_loose_splices();
89 token
90 }
91
92 fn report_loose_splices(&mut self) {
99 for at in self.cursor.take_loose_splices() {
100 let span = Span::new(self.file_start + at, self.file_start + at + 1);
101 self.diagnostics.push(Diagnostic::warning(
102 "backslash and line ending separated by whitespace",
103 span,
104 ));
105 }
106 }
107
108 fn scan(&mut self, interner: &mut Interner) -> PpToken {
109 self.skip_trivia();
110
111 let start = self.cursor.pos();
112 let flags = self.take_flags();
113
114 if self.cursor.at_end() {
115 return PpToken {
116 kind: PpTokenKind::Eof,
117 flags,
118 value: None,
119 span: Span::empty_at(self.file_start + start),
120 };
121 }
122
123 self.token_start = start;
124 self.unclean = false;
125
126 let b = self.cursor.first();
127 let kind = match CLASS[b as usize] {
128 Class::IdentStart => self.ident_or_prefixed_literal(b, start),
129 Class::Digit => self.pp_number(),
130 Class::Dot if CLASS[self.cursor.nth(1) as usize] == Class::Digit => self.pp_number(),
131 Class::Quote => self.literal(b'"', start, PpTokenKind::StringLit),
132 Class::Apostrophe => self.literal(b'\'', start, PpTokenKind::CharConst),
133 Class::Backslash => {
134 if matches!(self.cursor.nth(1), b'u' | b'U') {
137 self.identifier()
138 } else {
139 self.eat();
140 PpTokenKind::Other
141 }
142 }
143 Class::Dot | Class::Slash | Class::Punct => match self.punctuator(start, flags) {
144 Some(token) => return token,
145 None => {
146 self.eat();
147 PpTokenKind::Other
148 }
149 },
150 Class::Space | Class::Newline | Class::Other => {
151 self.eat();
152 PpTokenKind::Other
153 }
154 };
155
156 let end = self.cursor.pos();
157 let value = Some(self.intern_spelling(interner, start, end));
158 let mut flags = flags;
159 if self.unclean {
160 flags = flags.with(TokenFlags::SPLICED);
161 }
162 let span = Span::new(self.file_start + start, self.file_start + end);
163 PpToken { kind, flags, value, span }
164 }
165
166 pub fn header_name(&mut self, interner: &mut Interner) -> Option<PpToken> {
173 let token = self.scan_header_name(interner);
174 self.report_loose_splices();
175 token
176 }
177
178 fn scan_header_name(&mut self, interner: &mut Interner) -> Option<PpToken> {
179 self.skip_horizontal();
180 let start = self.cursor.pos();
181 let close = match self.cursor.first() {
182 b'<' => b'>',
183 b'"' => b'"',
184 _ => return None,
185 };
186 let flags = self.take_flags();
187 self.token_start = start;
188 self.unclean = false;
189 self.eat();
190 loop {
191 if self.cursor.at_end() || self.cursor.first() == b'\n' {
192 let span = Span::new(self.file_start + start, self.file_start + self.cursor.pos());
193 self.diagnostics
194 .push(Diagnostic::error("missing terminating character in header name", span));
195 break;
196 }
197 if self.eat() == close {
198 break;
199 }
200 }
201 let end = self.cursor.pos();
202 let value = Some(self.intern_spelling(interner, start, end));
203 let mut flags = flags;
204 if self.unclean {
205 flags = flags.with(TokenFlags::SPLICED);
206 }
207 let span = Span::new(self.file_start + start, self.file_start + end);
208 Some(PpToken { kind: PpTokenKind::HeaderName, flags, value, span })
209 }
210
211 fn take_flags(&mut self) -> TokenFlags {
213 let mut flags = TokenFlags::EMPTY;
214 if self.at_line_start {
215 flags = flags.with(TokenFlags::START_OF_LINE);
216 }
217 if self.leading_space {
218 flags = flags.with(TokenFlags::LEADING_SPACE);
219 }
220 self.at_line_start = false;
221 self.leading_space = false;
222 flags
223 }
224
225 fn eat(&mut self) -> u8 {
227 let before = self.cursor.pos();
228 let (b, clean) = self.cursor.bump().expect("eat called at end of file");
231 if self.unclean {
232 self.scratch.push(b);
233 } else if !clean {
234 let from = self.token_start as usize;
237 let bytes = self.cursor.bytes();
238 self.scratch.clear();
239 self.scratch.extend_from_slice(&bytes[from..before as usize]);
240 self.scratch.push(b);
241 self.unclean = true;
242 }
243 b
244 }
245
246 fn intern_spelling(&mut self, interner: &mut Interner, start: u32, end: u32) -> Symbol {
248 let lossy = {
249 let bytes: &[u8] = if self.unclean {
250 &self.scratch
251 } else {
252 &self.cursor.bytes()[start as usize..end as usize]
253 };
254 match std::str::from_utf8(bytes) {
255 Ok(text) => return interner.intern(text),
256 Err(_) => String::from_utf8_lossy(bytes).into_owned(),
260 }
261 };
262 let span = Span::new(self.file_start + start, self.file_start + end);
263 self.diagnostics.push(Diagnostic::error("source is not valid UTF-8 here", span));
264 interner.intern(&lossy)
265 }
266
267 fn skip_trivia(&mut self) {
269 while !self.cursor.at_end() {
270 if self.cursor.skip_blanks() {
273 self.leading_space = true;
274 continue;
275 }
276 match CLASS[self.cursor.first() as usize] {
277 Class::Space => {
278 self.cursor.bump();
279 self.leading_space = true;
280 }
281 Class::Newline => {
282 self.cursor.bump();
283 self.at_line_start = true;
284 self.leading_space = false;
285 }
286 Class::Slash => match self.cursor.nth(1) {
287 b'/' => self.line_comment(),
288 b'*' => self.block_comment(),
289 _ => return,
290 },
291 _ => return,
292 }
293 }
294 }
295
296 fn skip_horizontal(&mut self) {
298 while !self.cursor.at_end() {
299 if self.cursor.skip_blanks() {
300 self.leading_space = true;
301 continue;
302 }
303 let b = self.cursor.first();
304 if CLASS[b as usize] == Class::Space {
305 self.cursor.bump();
306 self.leading_space = true;
307 } else if b == b'/' && self.cursor.nth(1) == b'*' {
308 self.block_comment();
309 } else {
310 return;
311 }
312 }
313 }
314
315 fn line_comment(&mut self) {
316 while !self.cursor.at_end() && self.cursor.first() != b'\n' {
317 self.cursor.skip_plain(&[]);
321 if self.cursor.at_end() || self.cursor.first() == b'\n' {
322 break;
323 }
324 self.cursor.bump();
325 }
326 self.leading_space = true;
329 }
330
331 fn block_comment(&mut self) {
332 let start = self.cursor.pos();
333 self.cursor.bump();
334 self.cursor.bump();
335 let mut spans_lines = false;
336 loop {
337 self.cursor.skip_plain(b"*");
341 if self.cursor.at_end() {
342 let span = Span::new(self.file_start + start, self.file_start + self.cursor.pos());
343 self.diagnostics.push(Diagnostic::error("unterminated comment", span));
344 break;
345 }
346 let b = self.cursor.first();
347 if b == b'\n' {
348 spans_lines = true;
349 }
350 if b == b'*' && self.cursor.nth(1) == b'/' {
351 self.cursor.bump();
352 self.cursor.bump();
353 break;
354 }
355 self.cursor.bump();
356 }
357 self.leading_space = true;
358 if spans_lines {
359 self.at_line_start = true;
363 }
364 }
365
366 fn ident_or_prefixed_literal(&mut self, b: u8, start: u32) -> PpTokenKind {
367 let (n1, n2) = (self.cursor.nth(1), self.cursor.nth(2));
370 match b {
371 b'L' | b'u' | b'U' if n1 == b'"' => {
372 self.eat();
373 self.literal(b'"', start, PpTokenKind::StringLit)
374 }
375 b'L' | b'u' | b'U' if n1 == b'\'' => {
376 self.eat();
377 self.literal(b'\'', start, PpTokenKind::CharConst)
378 }
379 b'u' if n1 == b'8' && (n2 == b'"' || n2 == b'\'') => {
381 self.eat();
382 self.eat();
383 let kind = if n2 == b'"' { PpTokenKind::StringLit } else { PpTokenKind::CharConst };
384 self.literal(n2, start, kind)
385 }
386 _ => self.identifier(),
387 }
388 }
389
390 fn identifier(&mut self) -> PpTokenKind {
391 while !self.cursor.at_end() {
392 let b = self.cursor.first();
393 if is_ident_continue(b) {
394 self.eat();
395 } else if b == b'\\' && matches!(self.cursor.nth(1), b'u' | b'U') {
396 self.eat();
400 self.eat();
401 } else {
402 break;
403 }
404 }
405 PpTokenKind::Ident
406 }
407
408 fn pp_number(&mut self) -> PpTokenKind {
409 self.eat();
413 while !self.cursor.at_end() {
414 let b = self.cursor.first();
415 let n1 = self.cursor.nth(1);
416 if matches!(b, b'e' | b'E' | b'p' | b'P') && matches!(n1, b'+' | b'-') {
417 self.eat();
418 self.eat();
419 } else if is_ident_continue(b) || b == b'.' {
420 self.eat();
421 } else if b == b'\'' && is_ident_continue(n1) {
422 self.eat();
426 self.eat();
427 } else if b == b'\\' && matches!(n1, b'u' | b'U') {
428 self.eat();
429 self.eat();
430 } else {
431 break;
432 }
433 }
434 PpTokenKind::Number
435 }
436
437 fn literal(&mut self, quote: u8, start: u32, kind: PpTokenKind) -> PpTokenKind {
438 self.eat();
439 loop {
440 if self.cursor.at_end() || self.cursor.first() == b'\n' {
441 let span = Span::new(self.file_start + start, self.file_start + self.cursor.pos());
445 let what = if quote == b'"' { "string literal" } else { "character constant" };
446 self.diagnostics
447 .push(Diagnostic::error(format!("missing terminating quote in {what}"), span));
448 break;
449 }
450 let b = self.eat();
451 if b == quote {
452 break;
453 }
454 if b == b'\\' && !self.cursor.at_end() && self.cursor.first() != b'\n' {
455 self.eat();
458 }
459 }
460 kind
461 }
462
463 fn punctuator(&mut self, start: u32, flags: TokenFlags) -> Option<PpToken> {
466 let (punct, len, digraph) = self.punctuator_kind()?;
467 for _ in 0..len {
468 self.eat();
469 }
470 let end = self.cursor.pos();
471 let mut flags = flags;
472 if digraph {
473 flags = flags.with(TokenFlags::DIGRAPH);
474 }
475 if self.unclean {
476 flags = flags.with(TokenFlags::SPLICED);
477 }
478 let span = Span::new(self.file_start + start, self.file_start + end);
479 Some(PpToken { kind: PpTokenKind::Punct(punct), flags, value: None, span })
480 }
481
482 fn punctuator_kind(&self) -> Option<(Punct, usize, bool)> {
484 let one = self.cursor.first();
485 let two = self.cursor.nth(1);
486 let three = self.cursor.nth(2);
487 let four = self.cursor.nth(3);
488 let found = match one {
489 b'[' => (Punct::LBracket, 1, false),
490 b']' => (Punct::RBracket, 1, false),
491 b'(' => (Punct::LParen, 1, false),
492 b')' => (Punct::RParen, 1, false),
493 b'{' => (Punct::LBrace, 1, false),
494 b'}' => (Punct::RBrace, 1, false),
495 b'~' => (Punct::Tilde, 1, false),
496 b'?' => (Punct::Question, 1, false),
497 b';' => (Punct::Semi, 1, false),
498 b',' => (Punct::Comma, 1, false),
499 b'.' if two == b'.' && three == b'.' => (Punct::Ellipsis, 3, false),
500 b'.' => (Punct::Dot, 1, false),
501 b'-' => match two {
502 b'>' => (Punct::Arrow, 2, false),
503 b'-' => (Punct::MinusMinus, 2, false),
504 b'=' => (Punct::MinusEq, 2, false),
505 _ => (Punct::Minus, 1, false),
506 },
507 b'+' => match two {
508 b'+' => (Punct::PlusPlus, 2, false),
509 b'=' => (Punct::PlusEq, 2, false),
510 _ => (Punct::Plus, 1, false),
511 },
512 b'&' => match two {
513 b'&' => (Punct::AmpAmp, 2, false),
514 b'=' => (Punct::AmpEq, 2, false),
515 _ => (Punct::Amp, 1, false),
516 },
517 b'|' => match two {
518 b'|' => (Punct::PipePipe, 2, false),
519 b'=' => (Punct::PipeEq, 2, false),
520 _ => (Punct::Pipe, 1, false),
521 },
522 b'*' if two == b'=' => (Punct::StarEq, 2, false),
523 b'*' => (Punct::Star, 1, false),
524 b'/' if two == b'=' => (Punct::SlashEq, 2, false),
525 b'/' => (Punct::Slash, 1, false),
526 b'!' if two == b'=' => (Punct::Ne, 2, false),
527 b'!' => (Punct::Bang, 1, false),
528 b'^' if two == b'=' => (Punct::CaretEq, 2, false),
529 b'^' => (Punct::Caret, 1, false),
530 b'=' if two == b'=' => (Punct::EqEq, 2, false),
531 b'=' => (Punct::Eq, 1, false),
532 b':' => match two {
533 b'>' => (Punct::RBracket, 2, true),
534 b':' => (Punct::ColonColon, 2, false),
535 _ => (Punct::Colon, 1, false),
536 },
537 b'<' => match two {
538 b'<' if three == b'=' => (Punct::ShlEq, 3, false),
539 b'<' => (Punct::Shl, 2, false),
540 b'=' => (Punct::Le, 2, false),
541 b':' => (Punct::LBracket, 2, true),
542 b'%' => (Punct::LBrace, 2, true),
543 _ => (Punct::Lt, 1, false),
544 },
545 b'>' => match two {
546 b'>' if three == b'=' => (Punct::ShrEq, 3, false),
547 b'>' => (Punct::Shr, 2, false),
548 b'=' => (Punct::Ge, 2, false),
549 _ => (Punct::Gt, 1, false),
550 },
551 b'%' => match two {
552 b'=' => (Punct::PercentEq, 2, false),
553 b'>' => (Punct::RBrace, 2, true),
554 b':' if three == b'%' && four == b':' => (Punct::HashHash, 4, true),
555 b':' => (Punct::Hash, 2, true),
556 _ => (Punct::Percent, 1, false),
557 },
558 b'#' if two == b'#' => (Punct::HashHash, 2, false),
559 b'#' => (Punct::Hash, 1, false),
560 _ => return None,
561 };
562 Some(found)
563 }
564}
565
566pub fn tokenize(
571 src: &[u8],
572 file_start: BytePos,
573 opts: Options,
574 interner: &mut Interner,
575) -> (Vec<PpToken>, Vec<Diagnostic>) {
576 let mut lexer = Lexer::new(src, file_start, opts);
577 let mut out = Vec::new();
578 loop {
579 let token = lexer.next_token(interner);
580 let done = token.is_eof();
581 out.push(token);
582 if done {
583 break;
584 }
585 }
586 let diagnostics = lexer.take_diagnostics();
587 (out, diagnostics)
588}