1use llvmkit_support::{Span, Spanned};
13
14use super::ll_token::{
15 FpLit, HexFpKind, IntLit, Keyword, NumBase, Opcode, PrimitiveTy, QuotedNameKind, Sign, Token,
16};
17
18mod escape;
25mod keywords;
26
27pub const INT_TY_MIN_BITS: u64 = 1;
32
33pub const INT_TY_MAX_BITS: u64 = (1u64 << 24) - 1; #[derive(Clone, PartialEq, Eq, Hash, Debug, thiserror::Error)]
45pub enum LexError {
46 #[error("unterminated /* */ block comment")]
47 UnterminatedBlockComment { span: Span },
48
49 #[error("end of file in string constant")]
50 UnterminatedString { span: Span },
51
52 #[error("end of file in {kind:?} name")]
53 UnterminatedQuotedName { kind: QuotedNameKind, span: Span },
54
55 #[error("NUL character is not allowed in names")]
56 NulInName { span: Span },
57
58 #[error("invalid value number (does not fit in u32)")]
59 IdOverflow { span: Span },
60
61 #[error("constant bigger than 64 bits detected")]
62 IntegerOverflow64 { span: Span },
63
64 #[error("constant bigger than 128 bits detected")]
65 IntegerOverflow128 { span: Span },
66
67 #[error("bitwidth for integer type out of range (got {width}, must be 1..={max})")]
68 IntegerWidthOutOfRange { width: u64, max: u32, span: Span },
69
70 #[error("hexadecimal constant too large for {target:?} (16-bit)")]
71 HexFpTooLarge { target: HexFpKind, span: Span },
72
73 #[error("invalid token")]
74 UnknownToken { span: Span },
75
76 #[error("expected '*' after '/' to start block comment")]
77 StraySlash { span: Span },
78}
79
80impl LexError {
81 pub fn span(&self) -> Span {
84 match self {
85 LexError::UnterminatedBlockComment { span }
86 | LexError::UnterminatedString { span }
87 | LexError::UnterminatedQuotedName { span, .. }
88 | LexError::NulInName { span }
89 | LexError::IdOverflow { span }
90 | LexError::IntegerOverflow64 { span }
91 | LexError::IntegerOverflow128 { span }
92 | LexError::IntegerWidthOutOfRange { span, .. }
93 | LexError::HexFpTooLarge { span, .. }
94 | LexError::UnknownToken { span }
95 | LexError::StraySlash { span } => *span,
96 }
97 }
98}
99
100#[derive(Clone, Debug)]
111pub struct Lexer<'src> {
112 src: &'src [u8],
113 pos: usize,
114 tok_start: usize,
115 prev_tok_end: usize,
116 pub ignore_colon_in_idents: bool,
119}
120
121impl<'src> Lexer<'src> {
122 #[inline]
124 pub fn new(src: &'src [u8]) -> Self {
125 Self {
126 src,
127 pos: 0,
128 tok_start: 0,
129 prev_tok_end: 0,
130 ignore_colon_in_idents: false,
131 }
132 }
133
134 #[inline]
136 pub fn source(&self) -> &'src [u8] {
137 self.src
138 }
139
140 #[inline]
142 pub fn position(&self) -> u32 {
143 self.pos as u32
144 }
145
146 #[inline]
148 pub fn prev_token_end(&self) -> u32 {
149 self.prev_tok_end as u32
150 }
151
152 #[inline]
155 fn peek(&self) -> Option<u8> {
156 self.src.get(self.pos).copied()
157 }
158
159 #[inline]
160 fn peek_at(&self, n: usize) -> Option<u8> {
161 self.src.get(self.pos + n).copied()
162 }
163
164 #[inline]
165 fn bump(&mut self) -> Option<u8> {
166 let b = self.peek()?;
167 self.pos += 1;
168 Some(b)
169 }
170
171 #[inline]
172 fn span_from(&self, start: usize) -> Span {
173 Span::new(start as u32, self.pos as u32)
174 }
175
176 #[inline]
177 fn current_span(&self) -> Span {
178 self.span_from(self.tok_start)
179 }
180
181 #[inline]
182 fn spanned<T>(&self, value: T, span: Span) -> Spanned<T> {
183 Spanned::new(value, span)
184 }
185
186 pub fn next_token(&mut self) -> Result<Spanned<Token<'src>>, LexError> {
192 loop {
193 self.prev_tok_end = self.pos;
197 self.tok_start = self.pos;
198
199 let Some(c) = self.peek() else {
200 let span = Span::empty_at(self.pos as u32);
201 return Ok(self.spanned(Token::Eof, span));
202 };
203
204 match c {
205 b' ' | b'\t' | b'\n' | b'\r' | 0 => {
207 self.bump();
208 continue;
209 }
210 b';' => {
212 self.skip_line_comment();
213 continue;
214 }
215 b'/' => {
217 self.bump();
218 self.skip_block_comment()?;
219 continue;
220 }
221 b'=' => {
223 self.bump();
224 return Ok(self.spanned(Token::Equal, self.current_span()));
225 }
226 b',' => {
227 self.bump();
228 return Ok(self.spanned(Token::Comma, self.current_span()));
229 }
230 b'*' => {
231 self.bump();
232 return Ok(self.spanned(Token::Star, self.current_span()));
233 }
234 b'[' => {
235 self.bump();
236 return Ok(self.spanned(Token::LSquare, self.current_span()));
237 }
238 b']' => {
239 self.bump();
240 return Ok(self.spanned(Token::RSquare, self.current_span()));
241 }
242 b'{' => {
243 self.bump();
244 return Ok(self.spanned(Token::LBrace, self.current_span()));
245 }
246 b'}' => {
247 self.bump();
248 return Ok(self.spanned(Token::RBrace, self.current_span()));
249 }
250 b'<' => {
251 self.bump();
252 return Ok(self.spanned(Token::Less, self.current_span()));
253 }
254 b'>' => {
255 self.bump();
256 return Ok(self.spanned(Token::Greater, self.current_span()));
257 }
258 b'(' => {
259 self.bump();
260 return Ok(self.spanned(Token::LParen, self.current_span()));
261 }
262 b')' => {
263 self.bump();
264 return Ok(self.spanned(Token::RParen, self.current_span()));
265 }
266 b'|' => {
267 self.bump();
268 return Ok(self.spanned(Token::Bar, self.current_span()));
269 }
270 b':' => {
271 self.bump();
272 return Ok(self.spanned(Token::Colon, self.current_span()));
273 }
274
275 b'+' => return self.lex_positive(),
277 b'-' | b'0'..=b'9' => return self.lex_digit_or_negative(),
278 b'@' => return self.lex_var(QuotedNameKind::Global),
279 b'%' => return self.lex_var(QuotedNameKind::Local),
280 b'$' => return self.lex_dollar(),
281 b'!' => return self.lex_exclaim(),
282 b'^' => return self.lex_caret(),
283 b'#' => return self.lex_hash(),
284 b'"' => return self.lex_quote(),
285 b'.' => return self.lex_dot(),
286
287 c if c.is_ascii_alphabetic() || c == b'_' => return self.lex_identifier(),
288
289 _ => {
290 self.bump();
291 return Err(LexError::UnknownToken {
292 span: self.current_span(),
293 });
294 }
295 }
296 }
297 }
298
299 fn skip_line_comment(&mut self) {
304 while let Some(b) = self.peek() {
305 if b == b'\n' || b == b'\r' {
306 return;
307 }
308 self.bump();
309 }
310 }
311
312 fn skip_block_comment(&mut self) -> Result<(), LexError> {
315 match self.bump() {
317 Some(b'*') => {}
318 _ => {
319 return Err(LexError::StraySlash {
320 span: self.span_from(self.tok_start),
321 });
322 }
323 }
324 loop {
325 match self.bump() {
326 None => {
327 return Err(LexError::UnterminatedBlockComment {
328 span: self.span_from(self.tok_start),
329 });
330 }
331 Some(b'*') => match self.peek() {
332 Some(b'/') => {
333 self.bump();
334 return Ok(());
335 }
336 Some(_) => continue,
337 None => {
338 return Err(LexError::UnterminatedBlockComment {
339 span: self.span_from(self.tok_start),
340 });
341 }
342 },
343 Some(_) => {}
344 }
345 }
346 }
347
348 fn lex_var(&mut self, kind: QuotedNameKind) -> Result<Spanned<Token<'src>>, LexError> {
353 self.bump(); if self.peek() == Some(b'"') {
357 return self.lex_quoted_name(kind);
358 }
359
360 let name_start = self.pos;
362 if let Some(b) = self.peek()
363 && is_unquoted_name_start(b)
364 {
365 self.bump();
366 while let Some(b) = self.peek() {
367 if is_unquoted_name_cont(b) {
368 self.bump();
369 } else {
370 break;
371 }
372 }
373 let name = &self.src[name_start..self.pos];
374 let token = match kind {
375 QuotedNameKind::Global => Token::GlobalVar(std::borrow::Cow::Borrowed(name)),
376 QuotedNameKind::Local => Token::LocalVar(std::borrow::Cow::Borrowed(name)),
377 QuotedNameKind::Comdat => Token::ComdatVar(std::borrow::Cow::Borrowed(name)),
378 QuotedNameKind::String | QuotedNameKind::Metadata => {
379 unreachable!("lex_var handles only Global/Local/Comdat")
380 }
381 };
382 return Ok(self.spanned(token, self.current_span()));
383 }
384
385 if matches!(self.peek(), Some(b'0'..=b'9')) {
387 let id = self.lex_uint()?;
388 let span = self.current_span();
389 let token = match kind {
390 QuotedNameKind::Global => Token::GlobalId(id),
391 QuotedNameKind::Local => Token::LocalVarId(id),
392 _ => unreachable!("only @42 / %42 reach this branch"),
393 };
394 return Ok(self.spanned(token, span));
395 }
396
397 Err(LexError::UnknownToken {
398 span: self.current_span(),
399 })
400 }
401
402 fn lex_dollar(&mut self) -> Result<Spanned<Token<'src>>, LexError> {
405 if let Some(end) = is_label_tail(self.src, self.pos) {
407 let content = &self.src[self.tok_start..end - 1];
410 self.pos = end;
411 return Ok(self.spanned(
412 Token::LabelStr(std::borrow::Cow::Borrowed(content)),
413 self.current_span(),
414 ));
415 }
416
417 self.bump();
419
420 if self.peek() == Some(b'"') {
422 return self.lex_quoted_name(QuotedNameKind::Comdat);
423 }
424
425 let name_start = self.pos;
427 if let Some(b) = self.peek()
428 && is_unquoted_name_start(b)
429 {
430 self.bump();
431 while let Some(b) = self.peek() {
432 if is_unquoted_name_cont(b) {
433 self.bump();
434 } else {
435 break;
436 }
437 }
438 let name = &self.src[name_start..self.pos];
439 return Ok(self.spanned(
440 Token::ComdatVar(std::borrow::Cow::Borrowed(name)),
441 self.current_span(),
442 ));
443 }
444
445 Err(LexError::UnknownToken {
446 span: self.current_span(),
447 })
448 }
449
450 fn lex_exclaim(&mut self) -> Result<Spanned<Token<'src>>, LexError> {
453 self.bump(); let name_start = self.pos;
456 let Some(first) = self.peek() else {
457 return Ok(self.spanned(Token::Exclaim, self.current_span()));
458 };
459 if !is_metadata_name_start(first) {
460 return Ok(self.spanned(Token::Exclaim, self.current_span()));
461 }
462 self.bump();
463 while let Some(b) = self.peek() {
464 if is_metadata_name_cont(b) {
465 self.bump();
466 } else {
467 break;
468 }
469 }
470 let raw = &self.src[name_start..self.pos];
471 let decoded = escape::unescape(raw);
472 Ok(self.spanned(Token::MetadataVar(decoded), self.current_span()))
473 }
474
475 fn lex_caret(&mut self) -> Result<Spanned<Token<'src>>, LexError> {
477 self.bump(); if !matches!(self.peek(), Some(b'0'..=b'9')) {
479 return Err(LexError::UnknownToken {
480 span: self.current_span(),
481 });
482 }
483 let id = self.lex_uint()?;
484 Ok(self.spanned(Token::SummaryId(id), self.current_span()))
485 }
486
487 fn lex_hash(&mut self) -> Result<Spanned<Token<'src>>, LexError> {
490 self.bump(); if matches!(self.peek(), Some(b'0'..=b'9')) {
492 let id = self.lex_uint()?;
493 return Ok(self.spanned(Token::AttrGrpId(id), self.current_span()));
494 }
495 Ok(self.spanned(Token::Hash, self.current_span()))
496 }
497
498 fn lex_quote(&mut self) -> Result<Spanned<Token<'src>>, LexError> {
501 let raw = self.read_quoted_payload(QuotedNameKind::String)?;
502 let decoded = escape::unescape(raw);
503
504 if self.peek() == Some(b':') {
506 self.bump();
507 if decoded.contains(&0) {
508 return Err(LexError::NulInName {
509 span: self.current_span(),
510 });
511 }
512 return Ok(self.spanned(Token::LabelStr(decoded), self.current_span()));
513 }
514
515 Ok(self.spanned(Token::StringConstant(decoded), self.current_span()))
517 }
518
519 fn lex_quoted_name(&mut self, kind: QuotedNameKind) -> Result<Spanned<Token<'src>>, LexError> {
522 let raw = self.read_quoted_payload(kind)?;
523 let decoded = escape::unescape(raw);
524 if decoded.contains(&0) {
525 return Err(LexError::NulInName {
526 span: self.current_span(),
527 });
528 }
529 let token = match kind {
530 QuotedNameKind::Global => Token::GlobalVar(decoded),
531 QuotedNameKind::Local => Token::LocalVar(decoded),
532 QuotedNameKind::Comdat => Token::ComdatVar(decoded),
533 QuotedNameKind::Metadata | QuotedNameKind::String => {
534 unreachable!("metadata and string use direct paths")
535 }
536 };
537 Ok(self.spanned(token, self.current_span()))
538 }
539
540 fn read_quoted_payload(&mut self, kind: QuotedNameKind) -> Result<&'src [u8], LexError> {
544 debug_assert_eq!(self.peek(), Some(b'"'));
545 self.bump(); let start = self.pos;
547 loop {
548 match self.bump() {
549 None => {
550 let span = self.current_span();
551 return Err(match kind {
552 QuotedNameKind::String => LexError::UnterminatedString { span },
553 _ => LexError::UnterminatedQuotedName { kind, span },
554 });
555 }
556 Some(b'"') => {
557 return Ok(&self.src[start..self.pos - 1]);
559 }
560 _ => {}
561 }
562 }
563 }
564
565 fn lex_uint(&mut self) -> Result<u32, LexError> {
571 let digits_start = self.pos;
572 while matches!(self.peek(), Some(b'0'..=b'9')) {
573 self.bump();
574 }
575 let digits = ascii_str(&self.src[digits_start..self.pos]);
578 digits.parse::<u32>().map_err(|_| LexError::IdOverflow {
579 span: self.span_from(self.tok_start),
580 })
581 }
582
583 fn lex_dot(&mut self) -> Result<Spanned<Token<'src>>, LexError> {
586 if let Some(end) = is_label_tail(self.src, self.pos + 1) {
588 let content = &self.src[self.pos..end - 1];
589 self.pos = end;
590 return Ok(self.spanned(
591 Token::LabelStr(std::borrow::Cow::Borrowed(content)),
592 self.current_span(),
593 ));
594 }
595 if self.peek_at(0) == Some(b'.')
597 && self.peek_at(1) == Some(b'.')
598 && self.peek_at(2) == Some(b'.')
599 {
600 self.pos += 3;
601 return Ok(self.spanned(Token::DotDotDot, self.current_span()));
602 }
603 self.bump();
605 Err(LexError::UnknownToken {
606 span: self.current_span(),
607 })
608 }
609
610 fn lex_digit_or_negative(&mut self) -> Result<Spanned<Token<'src>>, LexError> {
613 let first = self.peek().expect("caller verified non-empty");
614
615 if first == b'-' && !matches!(self.peek_at(1), Some(b'0'..=b'9')) {
617 self.bump(); if let Some(end) = is_label_tail(self.src, self.pos) {
620 let content = &self.src[self.tok_start..end - 1];
621 self.pos = end;
622 return Ok(self.spanned(
623 Token::LabelStr(std::borrow::Cow::Borrowed(content)),
624 self.current_span(),
625 ));
626 }
627 return Err(LexError::UnknownToken {
628 span: self.current_span(),
629 });
630 }
631
632 let sign = if first == b'-' {
634 self.bump();
635 Sign::Neg
636 } else {
637 Sign::Pos
638 };
639 let digits_start = self.pos;
640 while matches!(self.peek(), Some(b'0'..=b'9')) {
641 self.bump();
642 }
643
644 if matches!(sign, Sign::Pos) && self.peek() == Some(b':') {
646 let content = &self.src[self.tok_start..self.pos];
649 self.bump(); return Ok(self.spanned(
651 Token::LabelStr(std::borrow::Cow::Borrowed(content)),
652 self.current_span(),
653 ));
654 }
655
656 if matches!(self.peek(), Some(c) if is_label_char(c) || c == b':')
658 && let Some(end) = is_label_tail(self.src, self.pos)
659 {
660 let content = &self.src[self.tok_start..end - 1];
661 self.pos = end;
662 return Ok(self.spanned(
663 Token::LabelStr(std::borrow::Cow::Borrowed(content)),
664 self.current_span(),
665 ));
666 }
667
668 if self.peek() != Some(b'.') {
670 if matches!(sign, Sign::Pos)
672 && self.src.get(self.tok_start) == Some(&b'0')
673 && self.src.get(self.tok_start + 1) == Some(&b'x')
674 {
675 return self.lex_hex_fp();
676 }
677
678 let digits = ascii_str(&self.src[digits_start..self.pos]);
679 return Ok(self.spanned(
680 Token::IntegerLit(IntLit {
681 sign,
682 base: NumBase::Dec,
683 digits,
684 }),
685 self.current_span(),
686 ));
687 }
688
689 self.consume_fp_tail()?;
690 let lex = ascii_str(&self.src[self.tok_start..self.pos]);
691 Ok(self.spanned(Token::FloatLit(FpLit::Decimal(lex)), self.current_span()))
692 }
693
694 fn lex_positive(&mut self) -> Result<Spanned<Token<'src>>, LexError> {
696 self.bump(); if !matches!(self.peek(), Some(b'0'..=b'9')) {
698 return Err(LexError::UnknownToken {
699 span: self.current_span(),
700 });
701 }
702 while matches!(self.peek(), Some(b'0'..=b'9')) {
704 self.bump();
705 }
706 if self.peek() != Some(b'.') {
708 return Err(LexError::UnknownToken {
709 span: self.current_span(),
710 });
711 }
712 self.consume_fp_tail()?;
713 let lex = ascii_str(&self.src[self.tok_start..self.pos]);
714 Ok(self.spanned(Token::FloatLit(FpLit::Decimal(lex)), self.current_span()))
715 }
716
717 fn consume_fp_tail(&mut self) -> Result<(), LexError> {
720 debug_assert_eq!(self.peek(), Some(b'.'));
721 self.bump(); while matches!(self.peek(), Some(b'0'..=b'9')) {
723 self.bump();
724 }
725 if matches!(self.peek(), Some(b'e' | b'E')) {
727 let after_e = self.peek_at(1);
729 let after_sign = self.peek_at(2);
730 let has_exp = matches!(after_e, Some(b'0'..=b'9'))
731 || (matches!(after_e, Some(b'+' | b'-'))
732 && matches!(after_sign, Some(b'0'..=b'9')));
733 if has_exp {
734 self.bump(); if matches!(self.peek(), Some(b'+' | b'-')) {
736 self.bump();
737 }
738 while matches!(self.peek(), Some(b'0'..=b'9')) {
739 self.bump();
740 }
741 }
742 }
743 Ok(())
744 }
745
746 fn lex_hex_fp(&mut self) -> Result<Spanned<Token<'src>>, LexError> {
749 self.pos = self.tok_start + 2;
752
753 let kind = match self.peek() {
754 Some(b @ (b'K' | b'L' | b'M' | b'H' | b'R')) => {
755 self.bump();
756 b
757 }
758 _ => b'J', };
760
761 let digits_start = self.pos;
762 if !matches!(self.peek(), Some(c) if c.is_ascii_hexdigit()) {
763 self.pos = self.tok_start + 1;
766 return Err(LexError::UnknownToken {
767 span: self.current_span(),
768 });
769 }
770 while matches!(self.peek(), Some(c) if c.is_ascii_hexdigit()) {
771 self.bump();
772 }
773 let digits = ascii_str(&self.src[digits_start..self.pos]);
774
775 let token = match kind {
776 b'J' => Token::FloatLit(FpLit::HexDouble(digits)),
777 b'K' => Token::FloatLit(FpLit::HexX87(digits)),
778 b'L' => Token::FloatLit(FpLit::HexQuad(digits)),
779 b'M' => Token::FloatLit(FpLit::HexPpc128(digits)),
780 b'H' => {
781 self.check_hex16_fits(digits, HexFpKind::Half)?;
782 Token::FloatLit(FpLit::HexHalf(digits))
783 }
784 b'R' => {
785 self.check_hex16_fits(digits, HexFpKind::BFloat)?;
786 Token::FloatLit(FpLit::HexBFloat(digits))
787 }
788 _ => unreachable!(),
789 };
790 Ok(self.spanned(token, self.current_span()))
791 }
792
793 fn check_hex16_fits(&self, digits: &str, target: HexFpKind) -> Result<(), LexError> {
796 u16::from_str_radix(digits, 16)
797 .map(|_| ())
798 .map_err(|_| LexError::HexFpTooLarge {
799 target,
800 span: self.current_span(),
801 })
802 }
803
804 fn lex_identifier(&mut self) -> Result<Spanned<Token<'src>>, LexError> {
809 let first = self.bump().expect("caller verified non-empty");
810
811 let mut keyword_end: Option<usize> = None;
815 while let Some(b) = self.peek() {
816 if !is_label_char(b) {
817 break;
818 }
819 if keyword_end.is_none() && !b.is_ascii_alphanumeric() && b != b'_' {
820 keyword_end = Some(self.pos);
821 }
822 self.bump();
823 }
824
825 if !self.ignore_colon_in_idents && self.peek() == Some(b':') {
827 let content = &self.src[self.tok_start..self.pos];
828 self.bump(); return Ok(self.spanned(
830 Token::LabelStr(std::borrow::Cow::Borrowed(content)),
831 self.current_span(),
832 ));
833 }
834
835 if first == b'i' {
838 let mut int_end = self.tok_start + 1;
839 while int_end < self.pos && self.src[int_end].is_ascii_digit() {
840 int_end += 1;
841 }
842 if int_end > self.tok_start + 1 {
843 self.pos = int_end;
844 let digits = ascii_str(&self.src[self.tok_start + 1..self.pos]);
845 let width = digits.parse::<u64>().unwrap_or(u64::MAX);
849 if !(INT_TY_MIN_BITS..=INT_TY_MAX_BITS).contains(&width) {
850 return Err(LexError::IntegerWidthOutOfRange {
851 width,
852 max: INT_TY_MAX_BITS as u32,
853 span: self.current_span(),
854 });
855 }
856 let nz = std::num::NonZeroU32::new(width as u32)
859 .expect("width validated within 1..=INT_TY_MAX_BITS");
860 return Ok(self.spanned(
861 Token::PrimitiveType(PrimitiveTy::Integer(nz)),
862 self.current_span(),
863 ));
864 }
865 }
866
867 let kw_end = keyword_end.unwrap_or(self.pos);
871 self.pos = kw_end;
872 let word = &self.src[self.tok_start..self.pos];
873 let span = self.current_span();
874
875 if let Some(tok) = self.classify_prefixed(word) {
877 return Ok(self.spanned(tok, span));
878 }
879
880 if let Some(tok) = keywords::classify_word(word) {
882 return Ok(self.spanned(tok, span));
883 }
884
885 if let Some(tok) = self.classify_hex_apsint(word)? {
887 return Ok(self.spanned(tok, span));
888 }
889
890 if word.len() > 2 && word.starts_with(b"cc") && word[2..].iter().all(|c| c.is_ascii_digit())
892 {
893 self.pos = self.tok_start + 2;
894 return Ok(self.spanned(
895 Token::Kw(Keyword::Cc),
896 Span::new(self.tok_start as u32, self.pos as u32),
897 ));
898 }
899
900 self.pos = self.tok_start + 1;
902 Err(LexError::UnknownToken {
903 span: self.current_span(),
904 })
905 }
906
907 fn classify_prefixed(&self, word: &'src [u8]) -> Option<Token<'src>> {
910 let s = ascii_str(word);
912
913 if let Some(suffix) = s.strip_prefix("dbg_") {
915 return match suffix {
916 "value" | "declare" | "assign" | "label" | "declare_value" => {
917 let suffix_str = ascii_str(&word[4..]);
918 Some(Token::DbgRecordType(suffix_str))
919 }
920 _ => None,
921 };
922 }
923
924 if let Some(rest) = s.strip_prefix("DW_") {
926 let token = match rest.split_once('_').map(|(t, _)| t) {
927 Some("TAG") => Token::DwarfTag(s),
928 Some("ATE") => Token::DwarfAttEncoding(s),
929 Some("VIRTUALITY") => Token::DwarfVirtuality(s),
930 Some("LANG") => Token::DwarfLang(s),
931 Some("LNAME") => Token::DwarfSourceLangName(s),
932 Some("CC") => Token::DwarfCC(s),
933 Some("OP") => Token::DwarfOp(s),
934 Some("MACINFO") => Token::DwarfMacinfo(s),
935 Some("APPLE") if rest.starts_with("APPLE_ENUM_KIND_") => Token::DwarfEnumKind(s),
936 _ => return None,
937 };
938 return Some(token);
939 }
940
941 if matches!(
942 s,
943 "DIFile"
944 | "DICompileUnit"
945 | "DISubprogram"
946 | "DILocation"
947 | "DILocalVariable"
948 | "DIBasicType"
949 | "DIDerivedType"
950 | "DICompositeType"
951 | "DISubrange"
952 | "DINamespace"
953 | "DIExpression"
954 | "DIGlobalVariable"
955 | "DIGlobalVariableExpression"
956 | "DISubroutineType"
957 | "DIEnumerator"
958 | "DIModule"
959 | "DITemplateTypeParameter"
960 | "DITemplateValueParameter"
961 ) {
962 return Some(Token::SpecializedMetadata(s));
963 }
964
965 if s.starts_with("DIFlag") {
966 return Some(Token::DiFlag(s));
967 }
968 if s.starts_with("DISPFlag") {
969 return Some(Token::DiSpFlag(s));
970 }
971 if s.starts_with("CSK_") {
972 return Some(Token::ChecksumKind(s));
973 }
974
975 match s {
976 "NoDebug" | "FullDebug" | "LineTablesOnly" | "DebugDirectivesOnly" => {
977 Some(Token::EmissionKind(s))
978 }
979 "GNU" | "Apple" | "None" | "Default" => Some(Token::NameTableKind(s)),
980 "Binary" | "Decimal" | "Rational" => Some(Token::FixedPointKind(s)),
981 _ => None,
982 }
983 }
984
985 fn classify_hex_apsint(&self, word: &'src [u8]) -> Result<Option<Token<'src>>, LexError> {
989 if word.len() < 4 {
990 return Ok(None);
991 }
992 let base = match word[0] {
993 b's' => NumBase::HexSigned,
994 b'u' => NumBase::HexUnsigned,
995 _ => return Ok(None),
996 };
997 if word[1] != b'0' || word[2] != b'x' {
998 return Ok(None);
999 }
1000 if !word[3..].iter().all(|c| c.is_ascii_hexdigit()) {
1001 return Ok(None);
1002 }
1003 let digits = ascii_str(&word[3..]);
1004 Ok(Some(Token::IntegerLit(IntLit {
1005 sign: Sign::Pos,
1006 base,
1007 digits,
1008 })))
1009 }
1010}
1011
1012impl<'src> From<&'src [u8]> for Lexer<'src> {
1017 #[inline]
1018 fn from(src: &'src [u8]) -> Self {
1019 Self::new(src)
1020 }
1021}
1022
1023impl<'src> From<&'src str> for Lexer<'src> {
1024 #[inline]
1025 fn from(src: &'src str) -> Self {
1026 Self::new(src.as_bytes())
1027 }
1028}
1029
1030impl<'src> Iterator for Lexer<'src> {
1031 type Item = Result<Spanned<Token<'src>>, LexError>;
1032
1033 fn next(&mut self) -> Option<Self::Item> {
1034 match self.next_token() {
1035 Ok(spanned) if matches!(spanned.value, Token::Eof) => None,
1036 other => Some(other),
1037 }
1038 }
1039}
1040
1041#[inline]
1045fn is_label_char(b: u8) -> bool {
1046 b.is_ascii_alphanumeric() || b == b'-' || b == b'$' || b == b'.' || b == b'_'
1047}
1048
1049#[inline]
1051fn is_unquoted_name_start(b: u8) -> bool {
1052 b.is_ascii_alphabetic() || b == b'-' || b == b'$' || b == b'.' || b == b'_'
1053}
1054
1055#[inline]
1057fn is_unquoted_name_cont(b: u8) -> bool {
1058 is_label_char(b)
1059}
1060
1061#[inline]
1063fn is_metadata_name_start(b: u8) -> bool {
1064 is_unquoted_name_start(b) || b == b'\\'
1065}
1066
1067#[inline]
1069fn is_metadata_name_cont(b: u8) -> bool {
1070 is_label_char(b) || b == b'\\'
1071}
1072
1073#[inline]
1076fn ascii_str(bytes: &[u8]) -> &str {
1077 std::str::from_utf8(bytes).expect("lexer regex guarantees ASCII")
1078}
1079
1080#[inline]
1084fn is_label_tail(src: &[u8], mut pos: usize) -> Option<usize> {
1085 while let Some(&b) = src.get(pos) {
1086 if b == b':' {
1087 return Some(pos + 1);
1088 }
1089 if !is_label_char(b) {
1090 return None;
1091 }
1092 pos += 1;
1093 }
1094 None
1095}
1096
1097#[cfg(test)]
1098#[path = "ll_lexer_tests.rs"]
1099mod tests;