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]
465 .chars()
466 .next()
467 .expect("positive char_len_at guarantees one UTF-8 scalar");
468 i += len;
469
470 if escaped {
471 escaped = false;
472 continue;
473 }
474 if ch == '\\' {
475 escaped = true;
476 continue;
477 }
478 if ch == '\'' {
479 return i;
480 }
481 }
482
483 start + 1
484}
485
486fn end_hex_missing_digits(source: &str, start: usize) -> usize {
487 if start >= source.len() {
488 return start;
489 }
490 let after_prefix = start.saturating_add(2);
491 let prefix = source.get(start..after_prefix).unwrap_or("");
492 if prefix != "0x" && prefix != "0X" {
493 return start;
494 }
495
496 let mut end = after_prefix;
497 while let Some(len) = char_len_at(source, end) {
498 if source.get(end..end + len) != Some("_") {
499 break;
500 }
501 end += len;
502 }
503 end
504}
505
506fn end_decimal_exponent_missing_digits(source: &str, start: usize) -> usize {
507 let mut byte = start;
508 while byte < source.len() {
509 let len = match char_len_at(source, byte) {
510 Some(len) => len,
511 None => return start,
512 };
513 if len == 0 {
514 return start;
515 }
516 let ch = source[byte..byte + len]
517 .chars()
518 .next()
519 .expect("positive char_len_at guarantees one UTF-8 scalar");
520 if matches!(ch, 'e' | 'E') {
521 let mut end = byte + len;
522 if let Some(sign_len) = char_len_at(source, end) {
523 let sign = source[end..end + sign_len]
524 .chars()
525 .next()
526 .expect("positive char_len_at guarantees one UTF-8 scalar");
527 if matches!(sign, '+' | '-') {
528 end += sign_len;
529 }
530 }
531 return end;
532 }
533
534 if ch.is_whitespace() {
535 return start;
536 }
537 byte += len;
538 }
539
540 start
541}
542
543#[inline]
545fn byte_of_pos(source: &str, pos: Pos) -> usize {
546 let mut line = 1usize;
547 let mut col = 1usize;
548 for (idx, ch) in source.char_indices() {
549 if line == pos.line && col == pos.column {
550 return idx;
551 }
552 match ch {
553 '\n' => {
554 line += 1;
555 col = 1;
556 }
557 '\t' => col = ((col - 1) / TAB_STOP + 1) * TAB_STOP + 1,
558 _ => col += 1,
559 }
560 }
561 source.len()
562}
563
564#[derive(Debug, Clone, PartialEq, Eq)]
565#[non_exhaustive]
566pub enum LexErrorKind {
567 UnexpectedCharacter(char),
568 UnterminatedBlockComment,
569 UnterminatedStringLiteral,
570 UnterminatedStringGap,
571 InvalidEscapeSequence(char),
572 StraySingleQuote,
573 CharacterLiteralWrongLength,
574 HexLiteralMissingDigits,
575 DecimalExponentMissingDigits,
576}
577
578impl std::fmt::Display for LexErrorKind {
579 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
580 match self {
581 Self::UnexpectedCharacter(c) => write!(f, "unexpected character '{c}'"),
582 Self::UnterminatedBlockComment => f.write_str("unterminated block comment"),
583 Self::UnterminatedStringLiteral => f.write_str("unterminated string literal"),
584 Self::UnterminatedStringGap => f.write_str("unterminated string gap"),
585 Self::InvalidEscapeSequence(c) => write!(f, "invalid escape sequence \\{c}"),
586 Self::StraySingleQuote => f.write_str("stray single quote"),
587 Self::CharacterLiteralWrongLength => {
588 f.write_str("character literal must contain exactly one character")
589 }
590 Self::HexLiteralMissingDigits => f.write_str("hex literal requires at least one digit"),
591 Self::DecimalExponentMissingDigits => {
592 f.write_str("decimal exponent requires at least one digit")
593 }
594 }
595 }
596}
597
598#[derive(Debug, Clone, PartialEq, Eq)]
599pub struct LexOutput {
600 pub tokens: Vec<Token>,
601 pub errors: Vec<LexError>,
602}
603
604impl LexOutput {
605 #[must_use]
606 pub fn into_parts(self) -> (Vec<Token>, Vec<LexError>) {
607 (self.tokens, self.errors)
608 }
609}
610
611#[derive(Debug, Clone, PartialEq, Eq)]
612pub struct LexWithTriviaOutput {
613 pub tokens: Vec<Token>,
614 pub trivia: Vec<Trivia>,
615 pub errors: Vec<LexError>,
616}
617
618impl LexWithTriviaOutput {
619 #[must_use]
620 pub fn into_parts(self) -> (Vec<Token>, Vec<Trivia>, Vec<LexError>) {
621 (self.tokens, self.trivia, self.errors)
622 }
623}
624
625#[derive(Debug, Clone, PartialEq, Eq)]
626#[non_exhaustive]
627pub enum RenderLosslessError {
628 OverlappingSpans {
629 start: usize,
630 },
631 UncoveredBytes {
632 start: usize,
633 end: usize,
634 text: String,
635 },
636 UncoveredTail {
637 start: usize,
638 text: String,
639 },
640}
641
642impl std::fmt::Display for RenderLosslessError {
643 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
644 match self {
645 Self::OverlappingSpans { start } => write!(f, "overlapping spans at byte {start}"),
646 Self::UncoveredBytes { start, end, text } => write!(
647 f,
648 "bytes {start}..{end} lost (not covered by any token/trivia): {text:?}"
649 ),
650 Self::UncoveredTail { start, text } => {
651 write!(f, "bytes {start}.. lost at EOF: {text:?}")
652 }
653 }
654 }
655}
656
657impl std::error::Error for RenderLosslessError {}
658
659const fn is_symbol_char(c: char) -> bool {
660 matches!(
661 c,
662 '!' | '#'
663 | '$'
664 | '%'
665 | '&'
666 | '*'
667 | '+'
668 | '.'
669 | '/'
670 | '<'
671 | '='
672 | '>'
673 | '?'
674 | '@'
675 | '\\'
676 | '^'
677 | '|'
678 | '-'
679 | '~'
680 | ':'
681 )
682}
683
684fn is_ident_start(c: char) -> bool {
685 c.is_alphabetic() || c == '_'
686}
687
688fn is_ident_char(c: char) -> bool {
689 c.is_alphanumeric() || c == '_' || c == '\''
690}
691
692pub(crate) const TAB_STOP: usize = 8;
693
694struct Lexer<'a> {
695 chars: Vec<char>,
696 src: &'a str,
697 i: usize,
698 byte: usize,
699 line: usize,
700 column: usize,
701 capture_trivia: bool,
702 tokens: Vec<Token>,
703 trivia: Vec<Trivia>,
704 errors: Vec<LexError>,
705}
706
707impl<'a> Lexer<'a> {
708 fn new(source: &'a str, capture_trivia: bool) -> Self {
709 Self {
710 chars: source.chars().collect(),
711 src: source,
712 i: 0,
713 byte: 0,
714 line: 1,
715 column: 1,
716 capture_trivia,
717 tokens: Vec::new(),
718 trivia: Vec::new(),
719 errors: Vec::new(),
720 }
721 }
722}
723
724#[must_use]
729pub fn lex(source: &str) -> LexOutput {
730 let mut lexer = Lexer::new(source, false);
731 lexer.scan_tokens();
732 LexOutput {
733 tokens: lexer.tokens,
734 errors: lexer.errors,
735 }
736}
737
738#[must_use]
740pub fn lex_with_trivia(source: &str) -> LexWithTriviaOutput {
741 let mut lexer = Lexer::new(source, true);
742 lexer.scan_tokens();
743 let mut trivia = lexer.trivia;
744 add_blank_line_trivia(source, &lexer.tokens, &mut trivia);
745 trivia.sort_by_key(|t| t.start);
746 LexWithTriviaOutput {
747 tokens: lexer.tokens,
748 trivia,
749 errors: lexer.errors,
750 }
751}
752
753#[must_use = "handle render errors instead of discarding"]
760pub fn render_lossless(
761 source: &str,
762 tokens: &[Token],
763 trivia: &[Trivia],
764) -> Result<String, RenderLosslessError> {
765 let mut items: Vec<(usize, usize)> = tokens
766 .iter()
767 .filter(|t| {
768 !matches!(
769 t.kind,
770 TokenKind::VLBrace | TokenKind::VRBrace | TokenKind::VSemi
771 )
772 })
773 .map(|t| (t.start, t.end))
774 .chain(
775 trivia
776 .iter()
777 .filter(|t| !matches!(t.kind, TriviaKind::BlankLines(_)))
778 .map(|t| (t.start, t.end)),
779 )
780 .collect();
781 items.sort_unstable();
782 let mut out = String::with_capacity(source.len());
783 let mut prev = 0usize;
784 for (start, end) in items {
785 if start < prev {
786 return Err(RenderLosslessError::OverlappingSpans { start });
787 }
788 let gap = &source[prev..start];
789 if !gap.chars().all(char::is_whitespace) {
790 return Err(RenderLosslessError::UncoveredBytes {
791 start: prev,
792 end: start,
793 text: gap.to_string(),
794 });
795 }
796 out.push_str(gap);
797 out.push_str(&source[start..end]);
798 prev = end;
799 }
800 let tail = &source[prev..];
801 if !tail.chars().all(char::is_whitespace) {
802 return Err(RenderLosslessError::UncoveredTail {
803 start: prev,
804 text: tail.to_string(),
805 });
806 }
807 out.push_str(tail);
808 Ok(out)
809}
810
811fn add_blank_line_trivia(source: &str, tokens: &[Token], trivia: &mut Vec<Trivia>) {
816 let mut spans: Vec<(usize, usize)> = tokens
817 .iter()
818 .map(|t| (t.start, t.end))
819 .chain(trivia.iter().map(|t| (t.start, t.end)))
820 .collect();
821 spans.sort_unstable();
822 let bytes = source.as_bytes();
823 let mut blanks = Vec::new();
824 let mut gap_start = 0usize;
825 let emit_gap = |from: usize, to: usize, out: &mut Vec<Trivia>| {
826 let newlines: Vec<usize> = (from..to).filter(|&i| bytes[i] == b'\n').collect();
831 let count = if from == 0 {
835 newlines.len()
836 } else {
837 newlines.len().saturating_sub(1)
838 };
839 if count == 0 {
840 return;
841 }
842 let region_start = if from == 0 { 0 } else { newlines[0] + 1 };
843 let region_end = newlines[newlines.len() - 1] + 1;
844 let line = source[..region_start].matches('\n').count() + 1;
845 out.push(Trivia {
846 kind: TriviaKind::BlankLines(count),
847 text: String::new(),
848 pos: Pos { line, column: 1 },
849 start: region_start,
850 end: region_end,
851 });
852 };
853 for &(s, e) in &spans {
854 if s > gap_start {
855 emit_gap(gap_start, s, &mut blanks);
856 }
857 gap_start = gap_start.max(e);
858 }
859 if source.len() > gap_start {
860 emit_gap(gap_start, source.len(), &mut blanks);
861 }
862 trivia.extend(blanks);
863}
864
865impl<'a> Lexer<'a> {
866 fn peek(&self) -> Option<char> {
867 self.chars.get(self.i).copied()
868 }
869
870 fn peek_at(&self, n: usize) -> Option<char> {
871 self.chars.get(self.i + n).copied()
872 }
873
874 fn bump(&mut self) -> Option<char> {
875 let c = self.chars.get(self.i).copied()?;
876 self.i += 1;
877 self.byte += c.len_utf8();
878 match c {
879 '\n' => {
880 self.line += 1;
881 self.column = 1;
882 }
883 '\t' => {
884 self.column = ((self.column - 1) / TAB_STOP + 1) * TAB_STOP + 1;
887 }
888 _ => self.column += 1,
889 }
890 Some(c)
891 }
892
893 fn bump_after_peek(&mut self) -> char {
895 self.bump()
896 .expect("lexer cursor guarded by peek/peek_at before bump")
897 }
898
899 const fn pos(&self) -> Pos {
900 Pos {
901 line: self.line,
902 column: self.column,
903 }
904 }
905
906 fn push(&mut self, tok: TokenKind, pos: Pos, start: usize) {
909 self.tokens.push(Token {
910 kind: tok,
911 pos,
912 start,
913 end: self.byte,
914 });
915 }
916
917 fn push_trivia(&mut self, kind: TriviaKind, pos: Pos, start: usize) {
918 if !self.capture_trivia {
919 return;
920 }
921 self.trivia.push(Trivia {
922 kind,
923 text: self.src[start..self.byte].to_string(),
924 pos,
925 start,
926 end: self.byte,
927 });
928 }
929
930 fn error(&mut self, kind: LexErrorKind, pos: Pos) {
931 self.errors.push(LexError { kind, pos });
932 }
933
934 fn scan_tokens(&mut self) {
935 while let Some(c) = self.peek() {
936 let pos = self.pos();
937 let start = self.byte;
938 match c {
939 ' ' | '\t' | '\n' | '\r' => {
940 self.bump();
941 }
942 '(' => {
943 self.bump();
944 self.push(TokenKind::LParen, pos, start);
945 }
946 ')' => {
947 self.bump();
948 self.push(TokenKind::RParen, pos, start);
949 }
950 '[' => {
951 self.bump();
952 self.push(TokenKind::LBracket, pos, start);
953 }
954 ']' => {
955 self.bump();
956 self.push(TokenKind::RBracket, pos, start);
957 }
958 ',' => {
959 self.bump();
960 self.push(TokenKind::Comma, pos, start);
961 }
962 ';' => {
963 self.bump();
964 self.push(TokenKind::Semi, pos, start);
965 }
966 '`' => {
967 self.bump();
968 self.push(TokenKind::Backtick, pos, start);
969 }
970 '{' => {
971 if self.peek_at(1) == Some('-') {
972 self.block_comment(pos);
973 } else {
974 self.bump();
975 self.push(TokenKind::LBrace, pos, start);
976 }
977 }
978 '}' => {
979 self.bump();
980 self.push(TokenKind::RBrace, pos, start);
981 }
982 '#' if self.column == 1
986 && self.peek_at(1).is_some_and(|c| c.is_ascii_lowercase()) =>
987 {
988 while self.peek().is_some_and(|c| c != '\n') {
989 self.bump();
990 }
991 self.push_trivia(TriviaKind::CppDirective, pos, start);
992 }
993 '"' => self.string_lit(pos),
994 '\'' => self.char_lit(pos),
995 c if c.is_ascii_digit() => self.number(pos),
996 c if is_ident_start(c) => self.identifier(pos),
997 c if is_symbol_char(c) => self.operator(pos),
998 _ => {
999 self.bump();
1000 self.error(LexErrorKind::UnexpectedCharacter(c), pos);
1001 }
1002 }
1003 }
1004 }
1005
1006 fn block_comment(&mut self, pos: Pos) {
1009 let start = self.byte;
1010 self.bump(); self.bump(); let mut depth = 1usize;
1013 while depth > 0 {
1014 match self.peek() {
1015 None => {
1016 self.error(LexErrorKind::UnterminatedBlockComment, pos);
1017 self.push_trivia(TriviaKind::BlockComment, pos, start);
1018 return;
1019 }
1020 Some('{') if self.peek_at(1) == Some('-') => {
1021 self.bump();
1022 self.bump();
1023 depth += 1;
1024 }
1025 Some('-') if self.peek_at(1) == Some('}') => {
1026 self.bump();
1027 self.bump();
1028 depth -= 1;
1029 }
1030 Some(_) => {
1031 self.bump();
1032 }
1033 }
1034 }
1035 self.push_trivia(TriviaKind::BlockComment, pos, start);
1036 }
1037
1038 fn string_lit(&mut self, pos: Pos) {
1039 let start = self.byte;
1040 self.bump(); let mut value = String::new();
1042 loop {
1043 match self.peek() {
1044 None | Some('\n') => {
1045 self.error(LexErrorKind::UnterminatedStringLiteral, pos);
1046 break;
1047 }
1048 Some('"') => {
1049 self.bump();
1050 break;
1051 }
1052 Some('\\') => {
1053 let escape_pos = self.pos();
1054 self.bump();
1055 match self.peek() {
1056 Some(w) if w.is_whitespace() => {
1058 while self.peek().is_some_and(|c| c.is_whitespace()) {
1059 self.bump();
1060 }
1061 if self.peek() == Some('\\') {
1062 self.bump();
1063 } else {
1064 self.error(LexErrorKind::UnterminatedStringGap, pos);
1065 break;
1066 }
1067 }
1068 Some(e) => {
1069 self.bump();
1070 match unescape(e) {
1071 Some(c) => value.push(c),
1072 None => {
1073 self.error(LexErrorKind::InvalidEscapeSequence(e), escape_pos);
1074 value.push(e);
1075 }
1076 }
1077 }
1078 None => {
1079 self.error(LexErrorKind::UnterminatedStringLiteral, pos);
1080 break;
1081 }
1082 }
1083 }
1084 Some(c) => {
1085 self.bump();
1086 value.push(c);
1087 }
1088 }
1089 }
1090 self.push(TokenKind::StringLit(value), pos, start);
1091 }
1092
1093 fn char_lit(&mut self, pos: Pos) {
1097 let start = self.byte;
1098 let mut j = self.i + 1;
1100 let mut escaped = false;
1101 let mut ok = false;
1102 let window_end = (self.i + 12).min(self.chars.len());
1103 while j < window_end {
1104 match self.chars[j] {
1105 '\\' if !escaped => escaped = true,
1106 '\'' if !escaped => {
1107 ok = j > self.i + 1;
1108 break;
1109 }
1110 '\n' => break,
1111 _ => escaped = false,
1112 }
1113 j += 1;
1114 }
1115 if !ok {
1116 self.bump();
1117 self.error(LexErrorKind::StraySingleQuote, pos);
1118 return;
1119 }
1120 self.bump(); let mut value = String::new();
1122 while self.peek() != Some('\'') {
1123 let c = self.bump_after_peek();
1124 if c == '\\' {
1125 let escape_pos = Pos {
1126 line: self.line,
1127 column: self.column.saturating_sub(1),
1128 };
1129 if let Some(e) = self.bump() {
1130 match unescape(e) {
1131 Some(c) => value.push(c),
1132 None => {
1133 self.error(LexErrorKind::InvalidEscapeSequence(e), escape_pos);
1134 value.push(e);
1135 }
1136 }
1137 }
1138 } else {
1139 value.push(c);
1140 }
1141 }
1142 self.bump(); if value.chars().count() != 1 {
1144 self.error(LexErrorKind::CharacterLiteralWrongLength, pos);
1145 }
1146 self.push(TokenKind::CharLit(value), pos, start);
1147 }
1148
1149 fn number(&mut self, pos: Pos) {
1150 let start = self.byte;
1151 let mut text = String::new();
1152 if self.peek() == Some('0') && matches!(self.peek_at(1), Some('x' | 'X')) {
1153 text.push(self.bump_after_peek());
1154 text.push(self.bump_after_peek());
1155 let mut has_hex_digit = false;
1156 while self
1157 .peek()
1158 .is_some_and(|c| c.is_ascii_hexdigit() || c == '_')
1159 {
1160 let c = self.bump_after_peek();
1161 has_hex_digit |= c.is_ascii_hexdigit();
1162 text.push(c);
1163 }
1164 if !has_hex_digit {
1165 self.error(LexErrorKind::HexLiteralMissingDigits, pos);
1166 }
1167 self.push(TokenKind::IntLit(text), pos, start);
1168 return;
1169 }
1170 while self.peek().is_some_and(|c| c.is_ascii_digit() || c == '_') {
1171 text.push(self.bump_after_peek());
1172 }
1173 let mut decimal = false;
1174 if self.peek() == Some('.') && self.peek_at(1).is_some_and(|c| c.is_ascii_digit()) {
1176 decimal = true;
1177 text.push(self.bump_after_peek());
1178 while self.peek().is_some_and(|c| c.is_ascii_digit() || c == '_') {
1179 text.push(self.bump_after_peek());
1180 }
1181 }
1182 if matches!(self.peek(), Some('e' | 'E')) {
1183 decimal = true;
1184 if self.peek_at(1).is_some_and(|c| c.is_ascii_digit())
1185 || (matches!(self.peek_at(1), Some('+' | '-'))
1186 && self.peek_at(2).is_some_and(|c| c.is_ascii_digit()))
1187 {
1188 text.push(self.bump_after_peek());
1189 if matches!(self.peek(), Some('+' | '-')) {
1190 text.push(self.bump_after_peek());
1191 }
1192 while self.peek().is_some_and(|c| c.is_ascii_digit()) {
1193 text.push(self.bump_after_peek());
1194 }
1195 } else {
1196 text.push(self.bump_after_peek());
1197 if matches!(self.peek(), Some('+' | '-')) {
1198 text.push(self.bump_after_peek());
1199 }
1200 self.error(LexErrorKind::DecimalExponentMissingDigits, pos);
1201 }
1202 }
1203 if decimal {
1204 self.push(TokenKind::DecimalLit(text), pos, start);
1205 } else {
1206 self.push(TokenKind::IntLit(text), pos, start);
1207 }
1208 }
1209
1210 fn identifier(&mut self, pos: Pos) {
1213 let start = self.byte;
1214 let mut segments: Vec<String> = Vec::new();
1215 loop {
1216 let mut seg = String::new();
1217 while self.peek().is_some_and(is_ident_char) {
1218 seg.push(self.bump_after_peek());
1219 }
1220 let seg_is_upper = seg.chars().next().is_some_and(|c| c.is_uppercase());
1221 segments.push(seg);
1222 if seg_is_upper
1225 && self.peek() == Some('.')
1226 && self.peek_at(1).is_some_and(is_ident_start)
1227 {
1228 self.bump(); continue;
1230 }
1231 break;
1232 }
1233 let name = segments
1234 .pop()
1235 .expect("identifier loop always records at least one segment");
1236 let qualifier = if segments.is_empty() {
1237 None
1238 } else {
1239 Some(segments.join(".").into())
1240 };
1241 let tok = if name.chars().next().is_some_and(|c| c.is_uppercase()) {
1242 TokenKind::UpperId {
1243 qualifier,
1244 name: name.into(),
1245 }
1246 } else {
1247 TokenKind::LowerId {
1248 qualifier,
1249 name: name.into(),
1250 }
1251 };
1252 self.push(tok, pos, start);
1253 }
1254
1255 fn operator(&mut self, pos: Pos) {
1256 let start = self.i;
1257 let byte_start = self.byte;
1258 while self.peek().is_some_and(is_symbol_char) {
1259 self.bump();
1262 }
1263 let text: String = self.chars[start..self.i].iter().collect();
1264 if text.len() >= 2 && text.chars().all(|c| c == '-') {
1267 while self.peek().is_some_and(|c| c != '\n') {
1268 self.bump();
1269 }
1270 self.push_trivia(TriviaKind::LineComment, pos, byte_start);
1271 return;
1272 }
1273 self.push(TokenKind::Op(text.into()), pos, byte_start);
1274 }
1275}
1276
1277const fn unescape(c: char) -> Option<char> {
1278 match c {
1279 'n' => Some('\n'),
1280 't' => Some('\t'),
1281 'r' => Some('\r'),
1282 '0' => Some('\0'),
1283 'a' => Some('\u{07}'),
1284 'b' => Some('\u{08}'),
1285 'f' => Some('\u{0c}'),
1286 'v' => Some('\u{0b}'),
1287 '"' => Some('"'),
1288 '\'' => Some('\''),
1289 '\\' => Some('\\'),
1290 '&' => Some('&'),
1291 '1'..='9' | 'o' | 'x' | 'A'..='Z' => Some(c),
1297 _ => None,
1298 }
1299}
1300
1301impl TokenKind {
1302 #[must_use]
1305 pub const fn keyword(&self) -> Option<&str> {
1306 match self {
1307 Self::LowerId {
1308 qualifier: None,
1309 name,
1310 } => Some(name.as_str()),
1311 _ => None,
1312 }
1313 }
1314
1315 #[must_use]
1316 pub fn is_keyword(&self, kw: &str) -> bool {
1317 self.keyword() == Some(kw)
1318 }
1319
1320 #[must_use]
1321 pub fn is_op(&self, op: &str) -> bool {
1322 matches!(self, Self::Op(o) if o.as_str() == op)
1323 }
1324}
1325
1326#[cfg(test)]
1327mod tests {
1328 use super::*;
1329
1330 fn toks(src: &str) -> Vec<TokenKind> {
1331 let (tokens, errors) = lex(src).into_parts();
1332 assert!(errors.is_empty(), "lex errors: {errors:?}");
1333 tokens.into_iter().map(|t| t.kind).collect()
1334 }
1335
1336 fn lex_error_messages(src: &str) -> Vec<String> {
1337 let (_, errors) = lex(src).into_parts();
1338 errors.into_iter().map(|e| e.to_string()).collect()
1339 }
1340
1341 fn lower(name: &str) -> TokenKind {
1342 TokenKind::LowerId {
1343 qualifier: None,
1344 name: name.into(),
1345 }
1346 }
1347
1348 fn upper(name: &str) -> TokenKind {
1349 TokenKind::UpperId {
1350 qualifier: None,
1351 name: name.into(),
1352 }
1353 }
1354
1355 #[test]
1356 fn identifier_as_ref_str() {
1357 let identifier = Identifier::from("value");
1358 let operator = Operator::from("+");
1359 let module = ModuleName::from("DA.Map");
1360
1361 assert_eq!(identifier.as_ref(), "value");
1362 assert_eq!(operator.as_ref(), "+");
1363 assert_eq!(module.as_ref(), "DA.Map");
1364 }
1365
1366 #[test]
1367 fn line_comment_with_keywords_produces_no_tokens() {
1368 assert_eq!(toks("-- electing to exercise the option"), vec![]);
1369 assert_eq!(toks("--- template Foo"), vec![]);
1370 }
1371
1372 #[test]
1373 fn arrow_like_operator_is_not_comment() {
1374 assert_eq!(
1375 toks("a --> b"),
1376 vec![lower("a"), TokenKind::Op("-->".into()), lower("b")]
1377 );
1378 }
1379
1380 #[test]
1381 fn nested_block_comment() {
1382 assert_eq!(toks("{- outer {- inner -} still -} x"), vec![lower("x")]);
1383 }
1384
1385 #[test]
1386 fn string_with_keyword_and_escapes() {
1387 assert_eq!(
1388 toks(r#""template \"Foo\" \n""#),
1389 vec![TokenKind::StringLit("template \"Foo\" \n".into())]
1390 );
1391 }
1392
1393 #[test]
1394 fn qualified_identifiers() {
1395 assert_eq!(
1396 toks("DA.Set.fromList Map.Map foo"),
1397 vec![
1398 TokenKind::LowerId {
1399 qualifier: Some("DA.Set".into()),
1400 name: "fromList".into()
1401 },
1402 TokenKind::UpperId {
1403 qualifier: Some("Map".into()),
1404 name: "Map".into()
1405 },
1406 lower("foo"),
1407 ]
1408 );
1409 }
1410
1411 #[test]
1412 fn numbers() {
1413 assert_eq!(
1414 toks("42 1.5 0x1F 2e3 1_000"),
1415 vec![
1416 TokenKind::IntLit("42".into()),
1417 TokenKind::DecimalLit("1.5".into()),
1418 TokenKind::IntLit("0x1F".into()),
1419 TokenKind::DecimalLit("2e3".into()),
1420 TokenKind::IntLit("1_000".into()),
1421 ]
1422 );
1423 }
1424
1425 #[test]
1426 fn malformed_hex_literal_reports_error() {
1427 assert_eq!(
1428 lex_error_messages("0x 0x_"),
1429 vec![
1430 "hex literal requires at least one digit",
1431 "hex literal requires at least one digit",
1432 ]
1433 );
1434 }
1435
1436 #[test]
1437 fn malformed_decimal_exponent_reports_error() {
1438 assert_eq!(
1439 lex_error_messages("1e 1e+ 1e-"),
1440 vec![
1441 "decimal exponent requires at least one digit",
1442 "decimal exponent requires at least one digit",
1443 "decimal exponent requires at least one digit",
1444 ]
1445 );
1446 }
1447
1448 #[test]
1449 fn enum_from_to_is_not_decimal() {
1450 assert_eq!(
1451 toks("[1..5]"),
1452 vec![
1453 TokenKind::LBracket,
1454 TokenKind::IntLit("1".into()),
1455 TokenKind::Op("..".into()),
1456 TokenKind::IntLit("5".into()),
1457 TokenKind::RBracket,
1458 ]
1459 );
1460 }
1461
1462 #[test]
1463 fn primes_stay_in_identifier_and_char_lit_works() {
1464 assert_eq!(
1465 toks(r"foo' 'a' '\n'"),
1466 vec![
1467 lower("foo'"),
1468 TokenKind::CharLit("a".into()),
1469 TokenKind::CharLit("\n".into())
1470 ]
1471 );
1472 }
1473
1474 #[test]
1475 fn invalid_escape_sequences_report_errors() {
1476 assert_eq!(
1477 lex_error_messages(r#""\q" '\q'"#),
1478 vec!["invalid escape sequence \\q", "invalid escape sequence \\q"]
1479 );
1480 }
1481
1482 #[test]
1483 fn multi_character_char_literal_reports_error() {
1484 let (tokens, errors) = lex("'ab'").into_parts();
1485 assert_eq!(
1486 tokens.iter().map(|t| t.kind.clone()).collect::<Vec<_>>(),
1487 vec![TokenKind::CharLit("ab".into())]
1488 );
1489 assert_eq!(
1490 errors.iter().map(|e| e.to_string()).collect::<Vec<_>>(),
1491 vec!["character literal must contain exactly one character".to_string()]
1492 );
1493 }
1494
1495 #[test]
1496 fn operators_and_punctuation() {
1497 assert_eq!(
1498 toks("x <- f (y, z) `div` 2"),
1499 vec![
1500 lower("x"),
1501 TokenKind::Op("<-".into()),
1502 lower("f"),
1503 TokenKind::LParen,
1504 lower("y"),
1505 TokenKind::Comma,
1506 lower("z"),
1507 TokenKind::RParen,
1508 TokenKind::Backtick,
1509 lower("div"),
1510 TokenKind::Backtick,
1511 TokenKind::IntLit("2".into()),
1512 ]
1513 );
1514 }
1515
1516 #[test]
1517 fn spans_are_one_based() {
1518 let (tokens, _) = lex("ab\n cd").into_parts();
1519 assert_eq!(tokens[0].pos, Pos { line: 1, column: 1 });
1520 assert_eq!(tokens[1].pos, Pos { line: 2, column: 3 });
1521 }
1522
1523 #[test]
1524 fn tab_advances_to_stop() {
1525 let (tokens, _) = lex("\tx").into_parts();
1526 assert_eq!(tokens[0].pos, Pos { line: 1, column: 9 });
1527 }
1528
1529 #[test]
1530 fn unterminated_string_is_error_not_hang() {
1531 let (_, errors) = lex("x = \"oops\ny").into_parts();
1532 assert_eq!(errors.len(), 1);
1533 }
1534
1535 #[test]
1536 fn unterminated_block_comment_is_error_not_hang() {
1537 let (_, errors) = lex("{- never closed").into_parts();
1538 assert_eq!(errors.len(), 1);
1539 }
1540
1541 fn trivia_of(src: &str) -> Vec<Trivia> {
1542 let (_, trivia, _) = lex_with_trivia(src).into_parts();
1543 trivia
1544 }
1545
1546 fn assert_round_trip(src: &str) {
1549 let (tokens, trivia, errors) = lex_with_trivia(src).into_parts();
1550 assert!(errors.is_empty(), "lex errors: {errors:?}");
1551 assert_eq!(
1552 render_lossless(src, &tokens, &trivia).as_deref(),
1553 Ok(src),
1554 "round trip failed for {src:?}"
1555 );
1556 }
1557
1558 #[test]
1559 fn line_comment_becomes_trivia_with_exact_text_and_span() {
1560 let src = "x = 1 -- electing to exercise\ny = 2\n";
1561 let trivia = trivia_of(src);
1562 assert_eq!(trivia.len(), 1);
1563 assert_eq!(trivia[0].kind, TriviaKind::LineComment);
1564 assert_eq!(trivia[0].text, "-- electing to exercise");
1565 assert_eq!(&src[trivia[0].start..trivia[0].end], trivia[0].text);
1566 assert_eq!(trivia[0].pos, Pos { line: 1, column: 7 });
1567 }
1568
1569 #[test]
1570 fn nested_block_comment_becomes_one_trivia() {
1571 let src = "{- outer {- inner -} still -} x";
1572 let trivia = trivia_of(src);
1573 assert_eq!(trivia.len(), 1);
1574 assert_eq!(trivia[0].kind, TriviaKind::BlockComment);
1575 assert_eq!(trivia[0].text, "{- outer {- inner -} still -}");
1576 }
1577
1578 #[test]
1579 fn unterminated_block_comment_still_yields_trivia_to_eof() {
1580 let (_, trivia, errors) = lex_with_trivia("x {- never closed").into_parts();
1581 assert_eq!(errors.len(), 1);
1582 assert_eq!(trivia.len(), 1);
1583 assert_eq!(trivia[0].text, "{- never closed");
1584 }
1585
1586 #[test]
1587 fn blank_lines_between_items_counted() {
1588 let src = "x = 1\n\n\ny = 2\n";
1589 let trivia = trivia_of(src);
1590 assert_eq!(trivia.len(), 1);
1591 assert_eq!(trivia[0].kind, TriviaKind::BlankLines(2));
1592 assert_eq!(trivia[0].pos, Pos { line: 2, column: 1 });
1593 }
1594
1595 #[test]
1596 fn blank_line_at_file_start_counted() {
1597 let trivia = trivia_of("\nx = 1\n");
1598 assert_eq!(trivia.len(), 1);
1599 assert_eq!(trivia[0].kind, TriviaKind::BlankLines(1));
1600 assert_eq!(trivia[0].pos, Pos { line: 1, column: 1 });
1601 }
1602
1603 #[test]
1604 fn blank_looking_lines_inside_block_comment_are_not_blank_trivia() {
1605 let src = "x = 1 {- a\n\nb -}\ny = 2\n";
1606 let trivia = trivia_of(src);
1607 assert_eq!(trivia.len(), 1, "{trivia:?}");
1608 assert_eq!(trivia[0].kind, TriviaKind::BlockComment);
1609 }
1610
1611 #[test]
1612 fn blank_line_between_comments_counted() {
1613 let src = "-- a\n\n-- b\nx = 1\n";
1614 let kinds: Vec<_> = trivia_of(src).into_iter().map(|t| t.kind).collect();
1615 assert_eq!(
1616 kinds,
1617 vec![
1618 TriviaKind::LineComment,
1619 TriviaKind::BlankLines(1),
1620 TriviaKind::LineComment,
1621 ]
1622 );
1623 }
1624
1625 #[test]
1626 fn cpp_directive_becomes_trivia() {
1627 let src = "#ifdef DAML_BIGNUMERIC\nx = 1\n#endif\n";
1628 let trivia = trivia_of(src);
1629 assert_eq!(trivia.len(), 2);
1630 assert!(trivia.iter().all(|t| t.kind == TriviaKind::CppDirective));
1631 assert_eq!(trivia[0].text, "#ifdef DAML_BIGNUMERIC");
1632 }
1633
1634 #[test]
1635 fn round_trip_is_byte_identical() {
1636 assert_round_trip("module M where\n\n-- doc\nf : Int -> Int\nf x = x + 1\n");
1637 assert_round_trip("x = \"tem\\\"plate \\n\" {- block {- nested -} -}\r\ny = 'a'\r\n");
1638 assert_round_trip("\tärger = [1..5] -- ütf\n");
1639 assert_round_trip("s = \"gap \\ \\ here\"\n");
1640 assert_round_trip("#ifdef X\nf = 0x1F\n#endif\n");
1641 assert_round_trip("\n\n \nf = 1 \n ");
1642 assert_round_trip("");
1643 }
1644
1645 #[test]
1646 fn render_lossless_detects_lost_bytes() {
1647 let src = "x = 1 -- comment\n";
1648 let (tokens, mut trivia, _) = lex_with_trivia(src).into_parts();
1649 trivia.clear(); assert!(render_lossless(src, &tokens, &trivia).is_err());
1651 }
1652
1653 #[test]
1654 fn unicode_identifier() {
1655 assert_eq!(
1656 toks("ärger = 1"),
1657 vec![
1658 lower("ärger"),
1659 TokenKind::Op("=".into()),
1660 TokenKind::IntLit("1".into())
1661 ]
1662 );
1663 let _ = upper("Ülf"); }
1665
1666 fn assert_lex_tokens_match(src: &str) {
1669 let (parse_tokens, parse_errors) = lex(src).into_parts();
1670 let (trivia_tokens, _, trivia_errors) = lex_with_trivia(src).into_parts();
1671 assert_eq!(parse_errors, trivia_errors, "lex errors differ for {src:?}");
1672 assert_eq!(
1673 parse_tokens.len(),
1674 trivia_tokens.len(),
1675 "token count for {src:?}"
1676 );
1677 for (a, b) in parse_tokens.iter().zip(trivia_tokens.iter()) {
1678 assert_eq!(a.kind, b.kind, "token kind for {src:?}");
1679 assert_eq!(a.pos, b.pos, "token pos for {src:?}");
1680 assert_eq!(a.start, b.start, "token start for {src:?}");
1681 assert_eq!(a.end, b.end, "token end for {src:?}");
1682 }
1683 }
1684
1685 #[test]
1686 fn lex_and_lex_with_trivia_emit_identical_tokens() {
1687 assert_lex_tokens_match("x = 1 -- electing to exercise\ny = 2\n");
1688 assert_lex_tokens_match("module M where\n\n-- doc\nf : Int -> Int\nf x = x + 1\n");
1689 assert_lex_tokens_match("{- outer {- inner -} still -} x");
1690 assert_lex_tokens_match("#ifdef DAML_BIGNUMERIC\nx = 1\n#endif\n");
1691 assert_lex_tokens_match("\n\n \nf = 1 \n ");
1692 }
1693
1694 #[test]
1695 fn lex_with_trivia_preserves_lossless_comment_and_blank_line_render() {
1696 let sources = [
1697 "x = 1 -- electing to exercise\ny = 2\n",
1698 "x = 1\n\n\ny = 2\n",
1699 "-- a\n\n-- b\nx = 1\n",
1700 ];
1701 for src in sources {
1702 let (tokens, trivia, errors) = lex_with_trivia(src).into_parts();
1703 assert!(errors.is_empty(), "lex errors for {src:?}: {errors:?}");
1704 assert_eq!(
1705 render_lossless(src, &tokens, &trivia).as_deref(),
1706 Ok(src),
1707 "lossless render failed for {src:?}"
1708 );
1709 }
1710 }
1711}