1#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
10pub struct Identifier(String);
11
12impl Identifier {
13 #[must_use]
14 pub const fn as_str(&self) -> &str {
15 self.0.as_str()
16 }
17}
18
19impl AsRef<str> for Identifier {
20 fn as_ref(&self) -> &str {
21 self.as_str()
22 }
23}
24
25impl std::fmt::Display for Identifier {
26 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
27 f.write_str(self.as_str())
28 }
29}
30
31impl From<String> for Identifier {
32 fn from(value: String) -> Self {
33 Self(value)
34 }
35}
36
37impl From<&str> for Identifier {
38 fn from(value: &str) -> Self {
39 Self(value.to_string())
40 }
41}
42
43impl From<Identifier> for String {
44 fn from(value: Identifier) -> Self {
45 value.0
46 }
47}
48
49impl std::ops::Deref for Identifier {
50 type Target = str;
51 fn deref(&self) -> &Self::Target {
52 self.as_str()
53 }
54}
55
56impl std::borrow::Borrow<str> for Identifier {
57 fn borrow(&self) -> &str {
58 self.as_str()
59 }
60}
61
62impl PartialEq<&str> for Identifier {
63 fn eq(&self, other: &&str) -> bool {
64 self.as_str() == *other
65 }
66}
67
68impl PartialEq<Identifier> for &str {
69 fn eq(&self, other: &Identifier) -> bool {
70 *self == other.as_str()
71 }
72}
73
74impl PartialEq<String> for Identifier {
75 fn eq(&self, other: &String) -> bool {
76 self.as_str() == other.as_str()
77 }
78}
79
80impl PartialEq<Identifier> for String {
81 fn eq(&self, other: &Identifier) -> bool {
82 self.as_str() == other.as_str()
83 }
84}
85
86#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
88pub struct Operator(String);
89
90impl Operator {
91 #[must_use]
92 pub const fn as_str(&self) -> &str {
93 self.0.as_str()
94 }
95}
96
97impl AsRef<str> for Operator {
98 fn as_ref(&self) -> &str {
99 self.as_str()
100 }
101}
102
103impl std::fmt::Display for Operator {
104 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
105 f.write_str(self.as_str())
106 }
107}
108
109impl From<String> for Operator {
110 fn from(value: String) -> Self {
111 Self(value)
112 }
113}
114
115impl From<&str> for Operator {
116 fn from(value: &str) -> Self {
117 Self(value.to_string())
118 }
119}
120
121impl From<Operator> for String {
122 fn from(value: Operator) -> Self {
123 value.0
124 }
125}
126
127impl std::ops::Deref for Operator {
128 type Target = str;
129 fn deref(&self) -> &Self::Target {
130 self.as_str()
131 }
132}
133
134impl std::borrow::Borrow<str> for Operator {
135 fn borrow(&self) -> &str {
136 self.as_str()
137 }
138}
139
140impl PartialEq<&str> for Operator {
141 fn eq(&self, other: &&str) -> bool {
142 self.as_str() == *other
143 }
144}
145
146impl PartialEq<Operator> for &str {
147 fn eq(&self, other: &Operator) -> bool {
148 *self == other.as_str()
149 }
150}
151
152impl PartialEq<String> for Operator {
153 fn eq(&self, other: &String) -> bool {
154 self.as_str() == other.as_str()
155 }
156}
157
158impl PartialEq<Operator> for String {
159 fn eq(&self, other: &Operator) -> bool {
160 self.as_str() == other.as_str()
161 }
162}
163
164#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
166pub struct ModuleName(String);
167
168impl ModuleName {
169 #[must_use]
170 pub const fn as_str(&self) -> &str {
171 self.0.as_str()
172 }
173}
174
175impl AsRef<str> for ModuleName {
176 fn as_ref(&self) -> &str {
177 self.as_str()
178 }
179}
180
181impl std::fmt::Display for ModuleName {
182 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
183 f.write_str(self.as_str())
184 }
185}
186
187impl From<String> for ModuleName {
188 fn from(value: String) -> Self {
189 Self(value)
190 }
191}
192
193impl From<&str> for ModuleName {
194 fn from(value: &str) -> Self {
195 Self(value.to_string())
196 }
197}
198
199impl From<Identifier> for ModuleName {
200 fn from(value: Identifier) -> Self {
201 Self(value.0)
202 }
203}
204
205impl From<ModuleName> for String {
206 fn from(value: ModuleName) -> Self {
207 value.0
208 }
209}
210
211impl std::ops::Deref for ModuleName {
212 type Target = str;
213 fn deref(&self) -> &Self::Target {
214 self.as_str()
215 }
216}
217
218impl std::borrow::Borrow<str> for ModuleName {
219 fn borrow(&self) -> &str {
220 self.as_str()
221 }
222}
223
224impl PartialEq<&str> for ModuleName {
225 fn eq(&self, other: &&str) -> bool {
226 self.as_str() == *other
227 }
228}
229
230impl PartialEq<ModuleName> for &str {
231 fn eq(&self, other: &ModuleName) -> bool {
232 *self == other.as_str()
233 }
234}
235
236impl PartialEq<String> for ModuleName {
237 fn eq(&self, other: &String) -> bool {
238 self.as_str() == other.as_str()
239 }
240}
241
242impl PartialEq<ModuleName> for String {
243 fn eq(&self, other: &ModuleName) -> bool {
244 self.as_str() == other.as_str()
245 }
246}
247
248#[derive(Debug, Clone, Copy, PartialEq, Eq)]
250pub struct Pos {
251 pub line: usize,
252 pub column: usize,
253}
254
255#[derive(Debug, Clone, PartialEq, Eq)]
256#[non_exhaustive]
257pub enum TokenKind {
258 LowerId {
260 qualifier: Option<ModuleName>,
261 name: Identifier,
262 },
263 UpperId {
265 qualifier: Option<ModuleName>,
266 name: Identifier,
267 },
268 Op(Operator),
270 IntLit(String),
271 DecimalLit(String),
272 StringLit(String),
273 CharLit(String),
274 LParen,
275 RParen,
276 LBracket,
277 RBracket,
278 LBrace,
279 RBrace,
280 Comma,
281 Semi,
282 Backtick,
283 VLBrace,
285 VRBrace,
287 VSemi,
289}
290
291#[derive(Debug, Clone, PartialEq, Eq)]
292pub struct Token {
293 pub(crate) kind: TokenKind,
294 pub(crate) pos: Pos,
295 pub(crate) start: usize,
298 pub(crate) end: usize,
300}
301
302impl Token {
303 #[must_use]
304 pub const fn kind(&self) -> &TokenKind {
305 &self.kind
306 }
307
308 #[must_use]
309 pub const fn pos(&self) -> Pos {
310 self.pos
311 }
312
313 #[must_use]
314 pub const fn start(&self) -> usize {
315 self.start
316 }
317
318 #[must_use]
319 pub const fn end(&self) -> usize {
320 self.end
321 }
322
323 #[must_use]
326 pub const fn is_virtual(&self) -> bool {
327 matches!(
328 self.kind,
329 TokenKind::VLBrace | TokenKind::VRBrace | TokenKind::VSemi
330 )
331 }
332}
333
334#[derive(Debug, Clone, PartialEq, Eq)]
339#[non_exhaustive]
340pub enum TriviaKind {
341 LineComment,
343 BlockComment,
345 CppDirective,
347 BlankLines(usize),
349}
350
351#[derive(Debug, Clone, PartialEq, Eq)]
352pub struct Trivia {
353 pub(crate) kind: TriviaKind,
354 pub(crate) text: String,
356 pub(crate) pos: Pos,
357 pub(crate) start: usize,
358 pub(crate) end: usize,
359}
360
361impl Trivia {
362 #[must_use]
363 pub const fn kind(&self) -> &TriviaKind {
364 &self.kind
365 }
366
367 #[must_use]
368 pub fn text(&self) -> &str {
369 &self.text
370 }
371
372 #[must_use]
373 pub const fn pos(&self) -> Pos {
374 self.pos
375 }
376
377 #[must_use]
378 pub const fn start(&self) -> usize {
379 self.start
380 }
381
382 #[must_use]
383 pub const fn end(&self) -> usize {
384 self.end
385 }
386}
387
388#[derive(Debug, Clone, PartialEq, Eq)]
391pub struct LexError {
392 pub kind: LexErrorKind,
393 pub pos: Pos,
394}
395
396impl std::fmt::Display for LexError {
397 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
398 self.kind.fmt(f)
399 }
400}
401
402impl LexError {
403 #[must_use]
405 pub fn byte_range_in(&self, source: &str) -> std::ops::Range<usize> {
406 let start = byte_of_pos(source, self.pos);
407 if start >= source.len() {
408 return start..start;
409 }
410
411 match &self.kind {
412 LexErrorKind::UnexpectedCharacter(c) => {
413 let end = (start + c.len_utf8()).min(source.len());
414 start..end
415 }
416 LexErrorKind::UnterminatedStringLiteral
417 | LexErrorKind::UnterminatedStringGap
418 | LexErrorKind::StraySingleQuote => {
419 let end = (start + 1).min(source.len());
420 start..end
421 }
422 LexErrorKind::UnterminatedBlockComment => start..source.len(),
423 LexErrorKind::InvalidEscapeSequence(_) => {
424 let first = char_len_at(source, start).unwrap_or(0);
425 let mut end = (start + first).min(source.len());
426 if let Some(second) = source.get(end..).and_then(|s| s.chars().next()) {
427 end = (end + second.len_utf8()).min(source.len());
428 }
429 start..end
430 }
431 LexErrorKind::CharacterLiteralWrongLength => start..end_char_lit_error(source, start),
432 LexErrorKind::HexLiteralMissingDigits => start..end_hex_missing_digits(source, start),
433 LexErrorKind::DecimalExponentMissingDigits => {
434 start..end_decimal_exponent_missing_digits(source, start)
435 }
436 }
437 }
438}
439impl std::error::Error for LexError {}
440
441#[inline]
442fn char_len_at(source: &str, byte: usize) -> Option<usize> {
443 source
444 .get(byte..)
445 .and_then(|s| s.chars().next())
446 .map(char::len_utf8)
447}
448
449fn end_char_lit_error(source: &str, start: usize) -> usize {
450 if start >= source.len() {
451 return start;
452 }
453 if char_len_at(source, start).is_none() {
454 return start;
455 }
456
457 let mut i = start + char_len_at(source, start).unwrap_or(0);
458 let mut escaped = false;
459 while i < source.len() {
460 let len = char_len_at(source, i).unwrap_or(0);
461 if len == 0 {
462 return start + 1;
463 }
464 let ch = source[i..i + len].chars().next().unwrap();
465 i += len;
466
467 if escaped {
468 escaped = false;
469 continue;
470 }
471 if ch == '\\' {
472 escaped = true;
473 continue;
474 }
475 if ch == '\'' {
476 return i;
477 }
478 }
479
480 start + 1
481}
482
483fn end_hex_missing_digits(source: &str, start: usize) -> usize {
484 if start >= source.len() {
485 return start;
486 }
487 let after_prefix = start.saturating_add(2);
488 let prefix = source.get(start..after_prefix).unwrap_or("");
489 if prefix != "0x" && prefix != "0X" {
490 return start;
491 }
492
493 let mut end = after_prefix;
494 while let Some(len) = char_len_at(source, end) {
495 if source.get(end..end + len) != Some("_") {
496 break;
497 }
498 end += len;
499 }
500 end
501}
502
503fn end_decimal_exponent_missing_digits(source: &str, start: usize) -> usize {
504 let mut byte = start;
505 while byte < source.len() {
506 let len = match char_len_at(source, byte) {
507 Some(len) => len,
508 None => return start,
509 };
510 if len == 0 {
511 return start;
512 }
513 let ch = source[byte..byte + len].chars().next().unwrap();
514 if matches!(ch, 'e' | 'E') {
515 let mut end = byte + len;
516 if let Some(sign_len) = char_len_at(source, end) {
517 let sign = source[end..end + sign_len].chars().next().unwrap();
518 if matches!(sign, '+' | '-') {
519 end += sign_len;
520 }
521 }
522 return end;
523 }
524
525 if ch.is_whitespace() {
526 return start;
527 }
528 byte += len;
529 }
530
531 start
532}
533
534#[inline]
536fn byte_of_pos(source: &str, pos: Pos) -> usize {
537 let mut line = 1usize;
538 let mut col = 1usize;
539 for (idx, ch) in source.char_indices() {
540 if line == pos.line && col == pos.column {
541 return idx;
542 }
543 match ch {
544 '\n' => {
545 line += 1;
546 col = 1;
547 }
548 '\t' => col = ((col - 1) / TAB_STOP + 1) * TAB_STOP + 1,
549 _ => col += 1,
550 }
551 }
552 source.len()
553}
554
555#[derive(Debug, Clone, PartialEq, Eq)]
556#[non_exhaustive]
557pub enum LexErrorKind {
558 UnexpectedCharacter(char),
559 UnterminatedBlockComment,
560 UnterminatedStringLiteral,
561 UnterminatedStringGap,
562 InvalidEscapeSequence(char),
563 StraySingleQuote,
564 CharacterLiteralWrongLength,
565 HexLiteralMissingDigits,
566 DecimalExponentMissingDigits,
567}
568
569impl std::fmt::Display for LexErrorKind {
570 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
571 match self {
572 Self::UnexpectedCharacter(c) => write!(f, "unexpected character '{c}'"),
573 Self::UnterminatedBlockComment => f.write_str("unterminated block comment"),
574 Self::UnterminatedStringLiteral => f.write_str("unterminated string literal"),
575 Self::UnterminatedStringGap => f.write_str("unterminated string gap"),
576 Self::InvalidEscapeSequence(c) => write!(f, "invalid escape sequence \\{c}"),
577 Self::StraySingleQuote => f.write_str("stray single quote"),
578 Self::CharacterLiteralWrongLength => {
579 f.write_str("character literal must contain exactly one character")
580 }
581 Self::HexLiteralMissingDigits => f.write_str("hex literal requires at least one digit"),
582 Self::DecimalExponentMissingDigits => {
583 f.write_str("decimal exponent requires at least one digit")
584 }
585 }
586 }
587}
588
589#[derive(Debug, Clone, PartialEq, Eq)]
590pub struct LexOutput {
591 pub tokens: Vec<Token>,
592 pub errors: Vec<LexError>,
593}
594
595impl LexOutput {
596 #[must_use]
597 pub fn into_parts(self) -> (Vec<Token>, Vec<LexError>) {
598 (self.tokens, self.errors)
599 }
600}
601
602#[derive(Debug, Clone, PartialEq, Eq)]
603pub struct LexWithTriviaOutput {
604 pub tokens: Vec<Token>,
605 pub trivia: Vec<Trivia>,
606 pub errors: Vec<LexError>,
607}
608
609impl LexWithTriviaOutput {
610 #[must_use]
611 pub fn into_parts(self) -> (Vec<Token>, Vec<Trivia>, Vec<LexError>) {
612 (self.tokens, self.trivia, self.errors)
613 }
614}
615
616#[derive(Debug, Clone, PartialEq, Eq)]
617#[non_exhaustive]
618pub enum RenderLosslessError {
619 OverlappingSpans {
620 start: usize,
621 },
622 UncoveredBytes {
623 start: usize,
624 end: usize,
625 text: String,
626 },
627 UncoveredTail {
628 start: usize,
629 text: String,
630 },
631}
632
633impl std::fmt::Display for RenderLosslessError {
634 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
635 match self {
636 Self::OverlappingSpans { start } => write!(f, "overlapping spans at byte {start}"),
637 Self::UncoveredBytes { start, end, text } => write!(
638 f,
639 "bytes {start}..{end} lost (not covered by any token/trivia): {text:?}"
640 ),
641 Self::UncoveredTail { start, text } => {
642 write!(f, "bytes {start}.. lost at EOF: {text:?}")
643 }
644 }
645 }
646}
647
648impl std::error::Error for RenderLosslessError {}
649
650const fn is_symbol_char(c: char) -> bool {
651 matches!(
652 c,
653 '!' | '#'
654 | '$'
655 | '%'
656 | '&'
657 | '*'
658 | '+'
659 | '.'
660 | '/'
661 | '<'
662 | '='
663 | '>'
664 | '?'
665 | '@'
666 | '\\'
667 | '^'
668 | '|'
669 | '-'
670 | '~'
671 | ':'
672 )
673}
674
675fn is_ident_start(c: char) -> bool {
676 c.is_alphabetic() || c == '_'
677}
678
679fn is_ident_char(c: char) -> bool {
680 c.is_alphanumeric() || c == '_' || c == '\''
681}
682
683pub(crate) const TAB_STOP: usize = 8;
684
685struct Lexer<'a> {
686 chars: Vec<char>,
687 src: &'a str,
688 i: usize,
689 byte: usize,
690 line: usize,
691 column: usize,
692 tokens: Vec<Token>,
693 trivia: Vec<Trivia>,
694 errors: Vec<LexError>,
695}
696
697#[must_use]
701pub fn lex(source: &str) -> LexOutput {
702 let LexWithTriviaOutput { tokens, errors, .. } = lex_with_trivia(source);
703 LexOutput { tokens, errors }
704}
705
706#[must_use]
708pub fn lex_with_trivia(source: &str) -> LexWithTriviaOutput {
709 let mut lexer = Lexer {
710 chars: source.chars().collect(),
711 src: source,
712 i: 0,
713 byte: 0,
714 line: 1,
715 column: 1,
716 tokens: Vec::new(),
717 trivia: Vec::new(),
718 errors: Vec::new(),
719 };
720 lexer.scan_tokens();
721 let mut trivia = lexer.trivia;
722 add_blank_line_trivia(source, &lexer.tokens, &mut trivia);
723 trivia.sort_by_key(|t| t.start);
724 LexWithTriviaOutput {
725 tokens: lexer.tokens,
726 trivia,
727 errors: lexer.errors,
728 }
729}
730
731#[must_use = "handle render errors instead of discarding"]
738pub fn render_lossless(
739 source: &str,
740 tokens: &[Token],
741 trivia: &[Trivia],
742) -> Result<String, RenderLosslessError> {
743 let mut items: Vec<(usize, usize)> = tokens
744 .iter()
745 .filter(|t| {
746 !matches!(
747 t.kind,
748 TokenKind::VLBrace | TokenKind::VRBrace | TokenKind::VSemi
749 )
750 })
751 .map(|t| (t.start, t.end))
752 .chain(
753 trivia
754 .iter()
755 .filter(|t| !matches!(t.kind, TriviaKind::BlankLines(_)))
756 .map(|t| (t.start, t.end)),
757 )
758 .collect();
759 items.sort_unstable();
760 let mut out = String::with_capacity(source.len());
761 let mut prev = 0usize;
762 for (start, end) in items {
763 if start < prev {
764 return Err(RenderLosslessError::OverlappingSpans { start });
765 }
766 let gap = &source[prev..start];
767 if !gap.chars().all(char::is_whitespace) {
768 return Err(RenderLosslessError::UncoveredBytes {
769 start: prev,
770 end: start,
771 text: gap.to_string(),
772 });
773 }
774 out.push_str(gap);
775 out.push_str(&source[start..end]);
776 prev = end;
777 }
778 let tail = &source[prev..];
779 if !tail.chars().all(char::is_whitespace) {
780 return Err(RenderLosslessError::UncoveredTail {
781 start: prev,
782 text: tail.to_string(),
783 });
784 }
785 out.push_str(tail);
786 Ok(out)
787}
788
789fn add_blank_line_trivia(source: &str, tokens: &[Token], trivia: &mut Vec<Trivia>) {
794 let mut spans: Vec<(usize, usize)> = tokens
795 .iter()
796 .map(|t| (t.start, t.end))
797 .chain(trivia.iter().map(|t| (t.start, t.end)))
798 .collect();
799 spans.sort_unstable();
800 let bytes = source.as_bytes();
801 let mut blanks = Vec::new();
802 let mut gap_start = 0usize;
803 let emit_gap = |from: usize, to: usize, out: &mut Vec<Trivia>| {
804 let newlines: Vec<usize> = (from..to).filter(|&i| bytes[i] == b'\n').collect();
809 let count = if from == 0 {
813 newlines.len()
814 } else {
815 newlines.len().saturating_sub(1)
816 };
817 if count == 0 {
818 return;
819 }
820 let region_start = if from == 0 { 0 } else { newlines[0] + 1 };
821 let region_end = newlines[newlines.len() - 1] + 1;
822 let line = source[..region_start].matches('\n').count() + 1;
823 out.push(Trivia {
824 kind: TriviaKind::BlankLines(count),
825 text: String::new(),
826 pos: Pos { line, column: 1 },
827 start: region_start,
828 end: region_end,
829 });
830 };
831 for &(s, e) in &spans {
832 if s > gap_start {
833 emit_gap(gap_start, s, &mut blanks);
834 }
835 gap_start = gap_start.max(e);
836 }
837 if source.len() > gap_start {
838 emit_gap(gap_start, source.len(), &mut blanks);
839 }
840 trivia.extend(blanks);
841}
842
843impl<'a> Lexer<'a> {
844 fn peek(&self) -> Option<char> {
845 self.chars.get(self.i).copied()
846 }
847
848 fn peek_at(&self, n: usize) -> Option<char> {
849 self.chars.get(self.i + n).copied()
850 }
851
852 fn bump(&mut self) -> Option<char> {
853 let c = self.chars.get(self.i).copied()?;
854 self.i += 1;
855 self.byte += c.len_utf8();
856 match c {
857 '\n' => {
858 self.line += 1;
859 self.column = 1;
860 }
861 '\t' => {
862 self.column = ((self.column - 1) / TAB_STOP + 1) * TAB_STOP + 1;
865 }
866 _ => self.column += 1,
867 }
868 Some(c)
869 }
870
871 const fn pos(&self) -> Pos {
872 Pos {
873 line: self.line,
874 column: self.column,
875 }
876 }
877
878 fn push(&mut self, tok: TokenKind, pos: Pos, start: usize) {
881 self.tokens.push(Token {
882 kind: tok,
883 pos,
884 start,
885 end: self.byte,
886 });
887 }
888
889 fn push_trivia(&mut self, kind: TriviaKind, pos: Pos, start: usize) {
890 self.trivia.push(Trivia {
891 kind,
892 text: self.src[start..self.byte].to_string(),
893 pos,
894 start,
895 end: self.byte,
896 });
897 }
898
899 fn error(&mut self, kind: LexErrorKind, pos: Pos) {
900 self.errors.push(LexError { kind, pos });
901 }
902
903 fn scan_tokens(&mut self) {
904 while let Some(c) = self.peek() {
905 let pos = self.pos();
906 let start = self.byte;
907 match c {
908 ' ' | '\t' | '\n' | '\r' => {
909 self.bump();
910 }
911 '(' => {
912 self.bump();
913 self.push(TokenKind::LParen, pos, start);
914 }
915 ')' => {
916 self.bump();
917 self.push(TokenKind::RParen, pos, start);
918 }
919 '[' => {
920 self.bump();
921 self.push(TokenKind::LBracket, pos, start);
922 }
923 ']' => {
924 self.bump();
925 self.push(TokenKind::RBracket, pos, start);
926 }
927 ',' => {
928 self.bump();
929 self.push(TokenKind::Comma, pos, start);
930 }
931 ';' => {
932 self.bump();
933 self.push(TokenKind::Semi, pos, start);
934 }
935 '`' => {
936 self.bump();
937 self.push(TokenKind::Backtick, pos, start);
938 }
939 '{' => {
940 if self.peek_at(1) == Some('-') {
941 self.block_comment(pos);
942 } else {
943 self.bump();
944 self.push(TokenKind::LBrace, pos, start);
945 }
946 }
947 '}' => {
948 self.bump();
949 self.push(TokenKind::RBrace, pos, start);
950 }
951 '#' if self.column == 1
955 && self.peek_at(1).is_some_and(|c| c.is_ascii_lowercase()) =>
956 {
957 while self.peek().is_some_and(|c| c != '\n') {
958 self.bump();
959 }
960 self.push_trivia(TriviaKind::CppDirective, pos, start);
961 }
962 '"' => self.string_lit(pos),
963 '\'' => self.char_lit(pos),
964 c if c.is_ascii_digit() => self.number(pos),
965 c if is_ident_start(c) => self.identifier(pos),
966 c if is_symbol_char(c) => self.operator(pos),
967 _ => {
968 self.bump();
969 self.error(LexErrorKind::UnexpectedCharacter(c), pos);
970 }
971 }
972 }
973 }
974
975 fn block_comment(&mut self, pos: Pos) {
978 let start = self.byte;
979 self.bump(); self.bump(); let mut depth = 1usize;
982 while depth > 0 {
983 match self.peek() {
984 None => {
985 self.error(LexErrorKind::UnterminatedBlockComment, pos);
986 self.push_trivia(TriviaKind::BlockComment, pos, start);
987 return;
988 }
989 Some('{') if self.peek_at(1) == Some('-') => {
990 self.bump();
991 self.bump();
992 depth += 1;
993 }
994 Some('-') if self.peek_at(1) == Some('}') => {
995 self.bump();
996 self.bump();
997 depth -= 1;
998 }
999 Some(_) => {
1000 self.bump();
1001 }
1002 }
1003 }
1004 self.push_trivia(TriviaKind::BlockComment, pos, start);
1005 }
1006
1007 fn string_lit(&mut self, pos: Pos) {
1008 let start = self.byte;
1009 self.bump(); let mut value = String::new();
1011 loop {
1012 match self.peek() {
1013 None | Some('\n') => {
1014 self.error(LexErrorKind::UnterminatedStringLiteral, pos);
1015 break;
1016 }
1017 Some('"') => {
1018 self.bump();
1019 break;
1020 }
1021 Some('\\') => {
1022 let escape_pos = self.pos();
1023 self.bump();
1024 match self.peek() {
1025 Some(w) if w.is_whitespace() => {
1027 while self.peek().is_some_and(|c| c.is_whitespace()) {
1028 self.bump();
1029 }
1030 if self.peek() == Some('\\') {
1031 self.bump();
1032 } else {
1033 self.error(LexErrorKind::UnterminatedStringGap, pos);
1034 break;
1035 }
1036 }
1037 Some(e) => {
1038 self.bump();
1039 match unescape(e) {
1040 Some(c) => value.push(c),
1041 None => {
1042 self.error(LexErrorKind::InvalidEscapeSequence(e), escape_pos);
1043 value.push(e);
1044 }
1045 }
1046 }
1047 None => {
1048 self.error(LexErrorKind::UnterminatedStringLiteral, pos);
1049 break;
1050 }
1051 }
1052 }
1053 Some(c) => {
1054 self.bump();
1055 value.push(c);
1056 }
1057 }
1058 }
1059 self.push(TokenKind::StringLit(value), pos, start);
1060 }
1061
1062 fn char_lit(&mut self, pos: Pos) {
1066 let start = self.byte;
1067 let mut j = self.i + 1;
1069 let mut escaped = false;
1070 let mut ok = false;
1071 let window_end = (self.i + 12).min(self.chars.len());
1072 while j < window_end {
1073 match self.chars[j] {
1074 '\\' if !escaped => escaped = true,
1075 '\'' if !escaped => {
1076 ok = j > self.i + 1;
1077 break;
1078 }
1079 '\n' => break,
1080 _ => escaped = false,
1081 }
1082 j += 1;
1083 }
1084 if !ok {
1085 self.bump();
1086 self.error(LexErrorKind::StraySingleQuote, pos);
1087 return;
1088 }
1089 self.bump(); let mut value = String::new();
1091 while self.peek() != Some('\'') {
1092 let c = self.bump().unwrap();
1093 if c == '\\' {
1094 let escape_pos = Pos {
1095 line: self.line,
1096 column: self.column.saturating_sub(1),
1097 };
1098 if let Some(e) = self.bump() {
1099 match unescape(e) {
1100 Some(c) => value.push(c),
1101 None => {
1102 self.error(LexErrorKind::InvalidEscapeSequence(e), escape_pos);
1103 value.push(e);
1104 }
1105 }
1106 }
1107 } else {
1108 value.push(c);
1109 }
1110 }
1111 self.bump(); if value.chars().count() != 1 {
1113 self.error(LexErrorKind::CharacterLiteralWrongLength, pos);
1114 }
1115 self.push(TokenKind::CharLit(value), pos, start);
1116 }
1117
1118 fn number(&mut self, pos: Pos) {
1119 let start = self.byte;
1120 let mut text = String::new();
1121 if self.peek() == Some('0') && matches!(self.peek_at(1), Some('x' | 'X')) {
1122 text.push(self.bump().unwrap());
1123 text.push(self.bump().unwrap());
1124 let mut has_hex_digit = false;
1125 while self
1126 .peek()
1127 .is_some_and(|c| c.is_ascii_hexdigit() || c == '_')
1128 {
1129 let c = self.bump().unwrap();
1130 has_hex_digit |= c.is_ascii_hexdigit();
1131 text.push(c);
1132 }
1133 if !has_hex_digit {
1134 self.error(LexErrorKind::HexLiteralMissingDigits, pos);
1135 }
1136 self.push(TokenKind::IntLit(text), pos, start);
1137 return;
1138 }
1139 while self.peek().is_some_and(|c| c.is_ascii_digit() || c == '_') {
1140 text.push(self.bump().unwrap());
1141 }
1142 let mut decimal = false;
1143 if self.peek() == Some('.') && self.peek_at(1).is_some_and(|c| c.is_ascii_digit()) {
1145 decimal = true;
1146 text.push(self.bump().unwrap());
1147 while self.peek().is_some_and(|c| c.is_ascii_digit() || c == '_') {
1148 text.push(self.bump().unwrap());
1149 }
1150 }
1151 if matches!(self.peek(), Some('e' | 'E')) {
1152 decimal = true;
1153 if self.peek_at(1).is_some_and(|c| c.is_ascii_digit())
1154 || (matches!(self.peek_at(1), Some('+' | '-'))
1155 && self.peek_at(2).is_some_and(|c| c.is_ascii_digit()))
1156 {
1157 text.push(self.bump().unwrap());
1158 if matches!(self.peek(), Some('+' | '-')) {
1159 text.push(self.bump().unwrap());
1160 }
1161 while self.peek().is_some_and(|c| c.is_ascii_digit()) {
1162 text.push(self.bump().unwrap());
1163 }
1164 } else {
1165 text.push(self.bump().unwrap());
1166 if matches!(self.peek(), Some('+' | '-')) {
1167 text.push(self.bump().unwrap());
1168 }
1169 self.error(LexErrorKind::DecimalExponentMissingDigits, pos);
1170 }
1171 }
1172 if decimal {
1173 self.push(TokenKind::DecimalLit(text), pos, start);
1174 } else {
1175 self.push(TokenKind::IntLit(text), pos, start);
1176 }
1177 }
1178
1179 fn identifier(&mut self, pos: Pos) {
1182 let start = self.byte;
1183 let mut segments: Vec<String> = Vec::new();
1184 loop {
1185 let mut seg = String::new();
1186 while self.peek().is_some_and(is_ident_char) {
1187 seg.push(self.bump().unwrap());
1188 }
1189 let seg_is_upper = seg.chars().next().is_some_and(|c| c.is_uppercase());
1190 segments.push(seg);
1191 if seg_is_upper
1194 && self.peek() == Some('.')
1195 && self.peek_at(1).is_some_and(is_ident_start)
1196 {
1197 self.bump(); continue;
1199 }
1200 break;
1201 }
1202 let name = segments.pop().unwrap();
1203 let qualifier = if segments.is_empty() {
1204 None
1205 } else {
1206 Some(segments.join(".").into())
1207 };
1208 let tok = if name.chars().next().is_some_and(|c| c.is_uppercase()) {
1209 TokenKind::UpperId {
1210 qualifier,
1211 name: name.into(),
1212 }
1213 } else {
1214 TokenKind::LowerId {
1215 qualifier,
1216 name: name.into(),
1217 }
1218 };
1219 self.push(tok, pos, start);
1220 }
1221
1222 fn operator(&mut self, pos: Pos) {
1223 let start = self.i;
1224 let byte_start = self.byte;
1225 while self.peek().is_some_and(is_symbol_char) {
1226 self.bump();
1229 }
1230 let text: String = self.chars[start..self.i].iter().collect();
1231 if text.len() >= 2 && text.chars().all(|c| c == '-') {
1234 while self.peek().is_some_and(|c| c != '\n') {
1235 self.bump();
1236 }
1237 self.push_trivia(TriviaKind::LineComment, pos, byte_start);
1238 return;
1239 }
1240 self.push(TokenKind::Op(text.into()), pos, byte_start);
1241 }
1242}
1243
1244const fn unescape(c: char) -> Option<char> {
1245 match c {
1246 'n' => Some('\n'),
1247 't' => Some('\t'),
1248 'r' => Some('\r'),
1249 '0' => Some('\0'),
1250 'a' => Some('\u{07}'),
1251 'b' => Some('\u{08}'),
1252 'f' => Some('\u{0c}'),
1253 'v' => Some('\u{0b}'),
1254 '"' => Some('"'),
1255 '\'' => Some('\''),
1256 '\\' => Some('\\'),
1257 '&' => Some('&'),
1258 '1'..='9' | 'o' | 'x' | 'A'..='Z' => Some(c),
1264 _ => None,
1265 }
1266}
1267
1268impl TokenKind {
1269 #[must_use]
1272 pub const fn keyword(&self) -> Option<&str> {
1273 match self {
1274 Self::LowerId {
1275 qualifier: None,
1276 name,
1277 } => Some(name.as_str()),
1278 _ => None,
1279 }
1280 }
1281
1282 #[must_use]
1283 pub fn is_keyword(&self, kw: &str) -> bool {
1284 self.keyword() == Some(kw)
1285 }
1286
1287 #[must_use]
1288 pub fn is_op(&self, op: &str) -> bool {
1289 matches!(self, Self::Op(o) if o.as_str() == op)
1290 }
1291}
1292
1293#[cfg(test)]
1294mod tests {
1295 use super::*;
1296
1297 fn toks(src: &str) -> Vec<TokenKind> {
1298 let (tokens, errors) = lex(src).into_parts();
1299 assert!(errors.is_empty(), "lex errors: {errors:?}");
1300 tokens.into_iter().map(|t| t.kind).collect()
1301 }
1302
1303 fn lex_error_messages(src: &str) -> Vec<String> {
1304 let (_, errors) = lex(src).into_parts();
1305 errors.into_iter().map(|e| e.to_string()).collect()
1306 }
1307
1308 fn lower(name: &str) -> TokenKind {
1309 TokenKind::LowerId {
1310 qualifier: None,
1311 name: name.into(),
1312 }
1313 }
1314
1315 fn upper(name: &str) -> TokenKind {
1316 TokenKind::UpperId {
1317 qualifier: None,
1318 name: name.into(),
1319 }
1320 }
1321
1322 #[test]
1323 fn identifier_as_ref_str() {
1324 let identifier = Identifier::from("value");
1325 let operator = Operator::from("+");
1326 let module = ModuleName::from("DA.Map");
1327
1328 assert_eq!(identifier.as_ref(), "value");
1329 assert_eq!(operator.as_ref(), "+");
1330 assert_eq!(module.as_ref(), "DA.Map");
1331 }
1332
1333 #[test]
1334 fn line_comment_with_keywords_produces_no_tokens() {
1335 assert_eq!(toks("-- electing to exercise the option"), vec![]);
1336 assert_eq!(toks("--- template Foo"), vec![]);
1337 }
1338
1339 #[test]
1340 fn arrow_like_operator_is_not_comment() {
1341 assert_eq!(
1342 toks("a --> b"),
1343 vec![lower("a"), TokenKind::Op("-->".into()), lower("b")]
1344 );
1345 }
1346
1347 #[test]
1348 fn nested_block_comment() {
1349 assert_eq!(toks("{- outer {- inner -} still -} x"), vec![lower("x")]);
1350 }
1351
1352 #[test]
1353 fn string_with_keyword_and_escapes() {
1354 assert_eq!(
1355 toks(r#""template \"Foo\" \n""#),
1356 vec![TokenKind::StringLit("template \"Foo\" \n".into())]
1357 );
1358 }
1359
1360 #[test]
1361 fn qualified_identifiers() {
1362 assert_eq!(
1363 toks("DA.Set.fromList Map.Map foo"),
1364 vec![
1365 TokenKind::LowerId {
1366 qualifier: Some("DA.Set".into()),
1367 name: "fromList".into()
1368 },
1369 TokenKind::UpperId {
1370 qualifier: Some("Map".into()),
1371 name: "Map".into()
1372 },
1373 lower("foo"),
1374 ]
1375 );
1376 }
1377
1378 #[test]
1379 fn numbers() {
1380 assert_eq!(
1381 toks("42 1.5 0x1F 2e3 1_000"),
1382 vec![
1383 TokenKind::IntLit("42".into()),
1384 TokenKind::DecimalLit("1.5".into()),
1385 TokenKind::IntLit("0x1F".into()),
1386 TokenKind::DecimalLit("2e3".into()),
1387 TokenKind::IntLit("1_000".into()),
1388 ]
1389 );
1390 }
1391
1392 #[test]
1393 fn malformed_hex_literal_reports_error() {
1394 assert_eq!(
1395 lex_error_messages("0x 0x_"),
1396 vec![
1397 "hex literal requires at least one digit",
1398 "hex literal requires at least one digit",
1399 ]
1400 );
1401 }
1402
1403 #[test]
1404 fn malformed_decimal_exponent_reports_error() {
1405 assert_eq!(
1406 lex_error_messages("1e 1e+ 1e-"),
1407 vec![
1408 "decimal exponent requires at least one digit",
1409 "decimal exponent requires at least one digit",
1410 "decimal exponent requires at least one digit",
1411 ]
1412 );
1413 }
1414
1415 #[test]
1416 fn enum_from_to_is_not_decimal() {
1417 assert_eq!(
1418 toks("[1..5]"),
1419 vec![
1420 TokenKind::LBracket,
1421 TokenKind::IntLit("1".into()),
1422 TokenKind::Op("..".into()),
1423 TokenKind::IntLit("5".into()),
1424 TokenKind::RBracket,
1425 ]
1426 );
1427 }
1428
1429 #[test]
1430 fn primes_stay_in_identifier_and_char_lit_works() {
1431 assert_eq!(
1432 toks(r"foo' 'a' '\n'"),
1433 vec![
1434 lower("foo'"),
1435 TokenKind::CharLit("a".into()),
1436 TokenKind::CharLit("\n".into())
1437 ]
1438 );
1439 }
1440
1441 #[test]
1442 fn invalid_escape_sequences_report_errors() {
1443 assert_eq!(
1444 lex_error_messages(r#""\q" '\q'"#),
1445 vec!["invalid escape sequence \\q", "invalid escape sequence \\q"]
1446 );
1447 }
1448
1449 #[test]
1450 fn multi_character_char_literal_reports_error() {
1451 let (tokens, errors) = lex("'ab'").into_parts();
1452 assert_eq!(
1453 tokens.iter().map(|t| t.kind.clone()).collect::<Vec<_>>(),
1454 vec![TokenKind::CharLit("ab".into())]
1455 );
1456 assert_eq!(
1457 errors.iter().map(|e| e.to_string()).collect::<Vec<_>>(),
1458 vec!["character literal must contain exactly one character".to_string()]
1459 );
1460 }
1461
1462 #[test]
1463 fn operators_and_punctuation() {
1464 assert_eq!(
1465 toks("x <- f (y, z) `div` 2"),
1466 vec![
1467 lower("x"),
1468 TokenKind::Op("<-".into()),
1469 lower("f"),
1470 TokenKind::LParen,
1471 lower("y"),
1472 TokenKind::Comma,
1473 lower("z"),
1474 TokenKind::RParen,
1475 TokenKind::Backtick,
1476 lower("div"),
1477 TokenKind::Backtick,
1478 TokenKind::IntLit("2".into()),
1479 ]
1480 );
1481 }
1482
1483 #[test]
1484 fn spans_are_one_based() {
1485 let (tokens, _) = lex("ab\n cd").into_parts();
1486 assert_eq!(tokens[0].pos, Pos { line: 1, column: 1 });
1487 assert_eq!(tokens[1].pos, Pos { line: 2, column: 3 });
1488 }
1489
1490 #[test]
1491 fn tab_advances_to_stop() {
1492 let (tokens, _) = lex("\tx").into_parts();
1493 assert_eq!(tokens[0].pos, Pos { line: 1, column: 9 });
1494 }
1495
1496 #[test]
1497 fn unterminated_string_is_error_not_hang() {
1498 let (_, errors) = lex("x = \"oops\ny").into_parts();
1499 assert_eq!(errors.len(), 1);
1500 }
1501
1502 #[test]
1503 fn unterminated_block_comment_is_error_not_hang() {
1504 let (_, errors) = lex("{- never closed").into_parts();
1505 assert_eq!(errors.len(), 1);
1506 }
1507
1508 fn trivia_of(src: &str) -> Vec<Trivia> {
1509 let (_, trivia, _) = lex_with_trivia(src).into_parts();
1510 trivia
1511 }
1512
1513 fn assert_round_trip(src: &str) {
1516 let (tokens, trivia, errors) = lex_with_trivia(src).into_parts();
1517 assert!(errors.is_empty(), "lex errors: {errors:?}");
1518 assert_eq!(
1519 render_lossless(src, &tokens, &trivia).as_deref(),
1520 Ok(src),
1521 "round trip failed for {src:?}"
1522 );
1523 }
1524
1525 #[test]
1526 fn line_comment_becomes_trivia_with_exact_text_and_span() {
1527 let src = "x = 1 -- electing to exercise\ny = 2\n";
1528 let trivia = trivia_of(src);
1529 assert_eq!(trivia.len(), 1);
1530 assert_eq!(trivia[0].kind, TriviaKind::LineComment);
1531 assert_eq!(trivia[0].text, "-- electing to exercise");
1532 assert_eq!(&src[trivia[0].start..trivia[0].end], trivia[0].text);
1533 assert_eq!(trivia[0].pos, Pos { line: 1, column: 7 });
1534 }
1535
1536 #[test]
1537 fn nested_block_comment_becomes_one_trivia() {
1538 let src = "{- outer {- inner -} still -} x";
1539 let trivia = trivia_of(src);
1540 assert_eq!(trivia.len(), 1);
1541 assert_eq!(trivia[0].kind, TriviaKind::BlockComment);
1542 assert_eq!(trivia[0].text, "{- outer {- inner -} still -}");
1543 }
1544
1545 #[test]
1546 fn unterminated_block_comment_still_yields_trivia_to_eof() {
1547 let (_, trivia, errors) = lex_with_trivia("x {- never closed").into_parts();
1548 assert_eq!(errors.len(), 1);
1549 assert_eq!(trivia.len(), 1);
1550 assert_eq!(trivia[0].text, "{- never closed");
1551 }
1552
1553 #[test]
1554 fn blank_lines_between_items_counted() {
1555 let src = "x = 1\n\n\ny = 2\n";
1556 let trivia = trivia_of(src);
1557 assert_eq!(trivia.len(), 1);
1558 assert_eq!(trivia[0].kind, TriviaKind::BlankLines(2));
1559 assert_eq!(trivia[0].pos, Pos { line: 2, column: 1 });
1560 }
1561
1562 #[test]
1563 fn blank_line_at_file_start_counted() {
1564 let trivia = trivia_of("\nx = 1\n");
1565 assert_eq!(trivia.len(), 1);
1566 assert_eq!(trivia[0].kind, TriviaKind::BlankLines(1));
1567 assert_eq!(trivia[0].pos, Pos { line: 1, column: 1 });
1568 }
1569
1570 #[test]
1571 fn blank_looking_lines_inside_block_comment_are_not_blank_trivia() {
1572 let src = "x = 1 {- a\n\nb -}\ny = 2\n";
1573 let trivia = trivia_of(src);
1574 assert_eq!(trivia.len(), 1, "{trivia:?}");
1575 assert_eq!(trivia[0].kind, TriviaKind::BlockComment);
1576 }
1577
1578 #[test]
1579 fn blank_line_between_comments_counted() {
1580 let src = "-- a\n\n-- b\nx = 1\n";
1581 let kinds: Vec<_> = trivia_of(src).into_iter().map(|t| t.kind).collect();
1582 assert_eq!(
1583 kinds,
1584 vec![
1585 TriviaKind::LineComment,
1586 TriviaKind::BlankLines(1),
1587 TriviaKind::LineComment,
1588 ]
1589 );
1590 }
1591
1592 #[test]
1593 fn cpp_directive_becomes_trivia() {
1594 let src = "#ifdef DAML_BIGNUMERIC\nx = 1\n#endif\n";
1595 let trivia = trivia_of(src);
1596 assert_eq!(trivia.len(), 2);
1597 assert!(trivia.iter().all(|t| t.kind == TriviaKind::CppDirective));
1598 assert_eq!(trivia[0].text, "#ifdef DAML_BIGNUMERIC");
1599 }
1600
1601 #[test]
1602 fn round_trip_is_byte_identical() {
1603 assert_round_trip("module M where\n\n-- doc\nf : Int -> Int\nf x = x + 1\n");
1604 assert_round_trip("x = \"tem\\\"plate \\n\" {- block {- nested -} -}\r\ny = 'a'\r\n");
1605 assert_round_trip("\tärger = [1..5] -- ütf\n");
1606 assert_round_trip("s = \"gap \\ \\ here\"\n");
1607 assert_round_trip("#ifdef X\nf = 0x1F\n#endif\n");
1608 assert_round_trip("\n\n \nf = 1 \n ");
1609 assert_round_trip("");
1610 }
1611
1612 #[test]
1613 fn render_lossless_detects_lost_bytes() {
1614 let src = "x = 1 -- comment\n";
1615 let (tokens, mut trivia, _) = lex_with_trivia(src).into_parts();
1616 trivia.clear(); assert!(render_lossless(src, &tokens, &trivia).is_err());
1618 }
1619
1620 #[test]
1621 fn unicode_identifier() {
1622 assert_eq!(
1623 toks("ärger = 1"),
1624 vec![
1625 lower("ärger"),
1626 TokenKind::Op("=".into()),
1627 TokenKind::IntLit("1".into())
1628 ]
1629 );
1630 let _ = upper("Ülf"); }
1632}