1#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
14pub struct ByteOffset(usize);
15
16impl ByteOffset {
17 #[must_use]
18 pub const fn new(value: usize) -> Self {
19 Self(value)
20 }
21
22 #[must_use]
23 pub const fn get(self) -> usize {
24 self.0
25 }
26
27 #[must_use]
28 pub const fn saturating_sub(self, rhs: usize) -> Self {
29 Self(self.0.saturating_sub(rhs))
30 }
31}
32
33impl From<ByteOffset> for usize {
34 fn from(value: ByteOffset) -> Self {
35 value.get()
36 }
37}
38
39#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
41pub struct ByteSpan {
42 pub start: ByteOffset,
44 pub end: ByteOffset,
46}
47
48impl ByteSpan {
49 #[must_use]
50 pub const fn new(start: ByteOffset, end: ByteOffset) -> Self {
51 Self { start, end }
52 }
53
54 #[must_use]
55 pub const fn from_usize(start: usize, end: usize) -> Self {
56 Self {
57 start: ByteOffset::new(start),
58 end: ByteOffset::new(end),
59 }
60 }
61
62 #[must_use]
63 pub const fn start_usize(self) -> usize {
64 self.start.get()
65 }
66
67 #[must_use]
68 pub const fn end_usize(self) -> usize {
69 self.end.get()
70 }
71}
72
73#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
79pub struct Identifier(String);
80
81impl Identifier {
82 #[must_use]
83 pub const fn as_str(&self) -> &str {
84 self.0.as_str()
85 }
86}
87
88impl AsRef<str> for Identifier {
89 fn as_ref(&self) -> &str {
90 self.as_str()
91 }
92}
93
94impl std::fmt::Display for Identifier {
95 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
96 f.write_str(self.as_str())
97 }
98}
99
100impl From<String> for Identifier {
101 fn from(value: String) -> Self {
102 Self(value)
103 }
104}
105
106impl From<&str> for Identifier {
107 fn from(value: &str) -> Self {
108 Self(value.to_string())
109 }
110}
111
112impl From<Identifier> for String {
113 fn from(value: Identifier) -> Self {
114 value.0
115 }
116}
117
118impl std::ops::Deref for Identifier {
119 type Target = str;
120 fn deref(&self) -> &Self::Target {
121 self.as_str()
122 }
123}
124
125impl std::borrow::Borrow<str> for Identifier {
126 fn borrow(&self) -> &str {
127 self.as_str()
128 }
129}
130
131impl PartialEq<&str> for Identifier {
132 fn eq(&self, other: &&str) -> bool {
133 self.as_str() == *other
134 }
135}
136
137impl PartialEq<Identifier> for &str {
138 fn eq(&self, other: &Identifier) -> bool {
139 *self == other.as_str()
140 }
141}
142
143impl PartialEq<String> for Identifier {
144 fn eq(&self, other: &String) -> bool {
145 self.as_str() == other.as_str()
146 }
147}
148
149impl PartialEq<Identifier> for String {
150 fn eq(&self, other: &Identifier) -> bool {
151 self.as_str() == other.as_str()
152 }
153}
154
155#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
160pub struct Operator(String);
161
162impl Operator {
163 #[must_use]
164 pub const fn as_str(&self) -> &str {
165 self.0.as_str()
166 }
167}
168
169impl AsRef<str> for Operator {
170 fn as_ref(&self) -> &str {
171 self.as_str()
172 }
173}
174
175impl std::fmt::Display for Operator {
176 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
177 f.write_str(self.as_str())
178 }
179}
180
181impl From<String> for Operator {
182 fn from(value: String) -> Self {
183 Self(value)
184 }
185}
186
187impl From<&str> for Operator {
188 fn from(value: &str) -> Self {
189 Self(value.to_string())
190 }
191}
192
193impl From<Operator> for String {
194 fn from(value: Operator) -> Self {
195 value.0
196 }
197}
198
199impl std::ops::Deref for Operator {
200 type Target = str;
201 fn deref(&self) -> &Self::Target {
202 self.as_str()
203 }
204}
205
206impl std::borrow::Borrow<str> for Operator {
207 fn borrow(&self) -> &str {
208 self.as_str()
209 }
210}
211
212impl PartialEq<&str> for Operator {
213 fn eq(&self, other: &&str) -> bool {
214 self.as_str() == *other
215 }
216}
217
218impl PartialEq<Operator> for &str {
219 fn eq(&self, other: &Operator) -> bool {
220 *self == other.as_str()
221 }
222}
223
224impl PartialEq<String> for Operator {
225 fn eq(&self, other: &String) -> bool {
226 self.as_str() == other.as_str()
227 }
228}
229
230impl PartialEq<Operator> for String {
231 fn eq(&self, other: &Operator) -> bool {
232 self.as_str() == other.as_str()
233 }
234}
235
236#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
243pub struct ModuleName(String);
244
245impl ModuleName {
246 #[must_use]
247 pub const fn as_str(&self) -> &str {
248 self.0.as_str()
249 }
250}
251
252impl AsRef<str> for ModuleName {
253 fn as_ref(&self) -> &str {
254 self.as_str()
255 }
256}
257
258impl std::fmt::Display for ModuleName {
259 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
260 f.write_str(self.as_str())
261 }
262}
263
264impl From<String> for ModuleName {
265 fn from(value: String) -> Self {
266 Self(value)
267 }
268}
269
270impl From<&str> for ModuleName {
271 fn from(value: &str) -> Self {
272 Self(value.to_string())
273 }
274}
275
276impl From<Identifier> for ModuleName {
277 fn from(value: Identifier) -> Self {
278 Self(value.0)
279 }
280}
281
282impl From<ModuleName> for String {
283 fn from(value: ModuleName) -> Self {
284 value.0
285 }
286}
287
288impl std::ops::Deref for ModuleName {
289 type Target = str;
290 fn deref(&self) -> &Self::Target {
291 self.as_str()
292 }
293}
294
295impl std::borrow::Borrow<str> for ModuleName {
296 fn borrow(&self) -> &str {
297 self.as_str()
298 }
299}
300
301impl PartialEq<&str> for ModuleName {
302 fn eq(&self, other: &&str) -> bool {
303 self.as_str() == *other
304 }
305}
306
307impl PartialEq<ModuleName> for &str {
308 fn eq(&self, other: &ModuleName) -> bool {
309 *self == other.as_str()
310 }
311}
312
313impl PartialEq<String> for ModuleName {
314 fn eq(&self, other: &String) -> bool {
315 self.as_str() == other.as_str()
316 }
317}
318
319impl PartialEq<ModuleName> for String {
320 fn eq(&self, other: &ModuleName) -> bool {
321 self.as_str() == other.as_str()
322 }
323}
324
325#[derive(Debug, Clone, Copy, PartialEq, Eq)]
327pub struct Pos {
328 pub line: usize,
330 pub column: usize,
333}
334
335#[derive(Debug, Clone, PartialEq, Eq)]
343#[non_exhaustive]
344pub enum TokenKind {
345 LowerId {
347 qualifier: Option<ModuleName>,
348 name: Identifier,
349 },
350 UpperId {
352 qualifier: Option<ModuleName>,
353 name: Identifier,
354 },
355 Op(Operator),
357 IntLit(String),
359 DecimalLit(String),
361 StringLit(String),
364 CharLit(String),
367 LParen,
369 RParen,
371 LBracket,
373 RBracket,
375 LBrace,
377 RBrace,
379 Comma,
381 Semi,
383 Backtick,
385 VLBrace,
387 VRBrace,
389 VSemi,
391}
392
393impl std::fmt::Display for TokenKind {
394 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
395 match self {
396 Self::LowerId { qualifier, name } | Self::UpperId { qualifier, name } => {
397 if let Some(qualifier) = qualifier {
398 write!(f, "{qualifier}.{name}")
399 } else {
400 name.fmt(f)
401 }
402 }
403 Self::Op(operator) => operator.fmt(f),
404 Self::IntLit(value) | Self::DecimalLit(value) => value.fmt(f),
405 Self::StringLit(value) => write!(f, "{value:?}"),
406 Self::CharLit(value) => write!(f, "'{}'", value.escape_debug()),
407 Self::LParen => f.write_str("("),
408 Self::RParen => f.write_str(")"),
409 Self::LBracket => f.write_str("["),
410 Self::RBracket => f.write_str("]"),
411 Self::LBrace | Self::VLBrace => f.write_str("{"),
412 Self::RBrace | Self::VRBrace => f.write_str("}"),
413 Self::Comma => f.write_str(","),
414 Self::Semi | Self::VSemi => f.write_str(";"),
415 Self::Backtick => f.write_str("`"),
416 }
417 }
418}
419
420#[derive(Debug, Clone, PartialEq, Eq)]
421pub struct Token {
422 pub(crate) kind: TokenKind,
423 pub(crate) pos: Pos,
424 pub(crate) start: usize,
427 pub(crate) end: usize,
429}
430
431impl Token {
432 #[must_use]
433 pub const fn kind(&self) -> &TokenKind {
434 &self.kind
435 }
436
437 #[must_use]
438 pub const fn pos(&self) -> Pos {
439 self.pos
440 }
441
442 #[must_use]
443 pub const fn start(&self) -> ByteOffset {
444 ByteOffset::new(self.start)
445 }
446
447 #[must_use]
448 pub const fn end(&self) -> ByteOffset {
449 ByteOffset::new(self.end)
450 }
451
452 #[must_use]
453 pub const fn span(&self) -> ByteSpan {
454 ByteSpan::from_usize(self.start, self.end)
455 }
456
457 #[must_use]
460 pub const fn is_virtual(&self) -> bool {
461 matches!(
462 self.kind,
463 TokenKind::VLBrace | TokenKind::VRBrace | TokenKind::VSemi
464 )
465 }
466}
467
468#[derive(Debug, Clone, PartialEq, Eq)]
473#[non_exhaustive]
474pub enum TriviaKind {
475 LineComment,
477 BlockComment,
479 CppDirective,
481 BlankLines(usize),
483}
484
485#[derive(Debug, Clone, PartialEq, Eq)]
486pub struct Trivia {
487 pub(crate) kind: TriviaKind,
488 pub(crate) text: String,
490 pub(crate) pos: Pos,
491 pub(crate) start: usize,
492 pub(crate) end: usize,
493}
494
495impl Trivia {
496 #[must_use]
497 pub const fn kind(&self) -> &TriviaKind {
498 &self.kind
499 }
500
501 #[must_use]
502 pub fn text(&self) -> &str {
503 &self.text
504 }
505
506 #[must_use]
507 pub const fn pos(&self) -> Pos {
508 self.pos
509 }
510
511 #[must_use]
512 pub const fn start(&self) -> ByteOffset {
513 ByteOffset::new(self.start)
514 }
515
516 #[must_use]
517 pub const fn end(&self) -> ByteOffset {
518 ByteOffset::new(self.end)
519 }
520
521 #[must_use]
522 pub const fn span(&self) -> ByteSpan {
523 ByteSpan::from_usize(self.start, self.end)
524 }
525}
526
527#[derive(Debug, Clone, PartialEq, Eq)]
530pub struct LexError {
531 pub kind: LexErrorKind,
532 pub pos: Pos,
533}
534
535impl std::fmt::Display for LexError {
536 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
537 self.kind.fmt(f)
538 }
539}
540
541impl LexError {
542 #[must_use]
544 pub fn byte_range_in(&self, source: &str) -> std::ops::Range<usize> {
545 let start = byte_of_pos(source, self.pos);
546 if start >= source.len() {
547 return start..start;
548 }
549
550 match &self.kind {
551 LexErrorKind::UnexpectedCharacter(c) => {
552 let end = (start + c.len_utf8()).min(source.len());
553 start..end
554 }
555 LexErrorKind::UnterminatedStringLiteral
556 | LexErrorKind::UnterminatedStringGap
557 | LexErrorKind::StraySingleQuote => {
558 let end = (start + 1).min(source.len());
559 start..end
560 }
561 LexErrorKind::UnterminatedBlockComment => start..source.len(),
562 LexErrorKind::InvalidEscapeSequence(_) => {
563 let first = char_len_at(source, start).unwrap_or(0);
564 let mut end = (start + first).min(source.len());
565 if let Some(second) = source.get(end..).and_then(|s| s.chars().next()) {
566 end = (end + second.len_utf8()).min(source.len());
567 }
568 start..end
569 }
570 LexErrorKind::CharacterLiteralWrongLength => start..end_char_lit_error(source, start),
571 LexErrorKind::HexLiteralMissingDigits => start..end_hex_missing_digits(source, start),
572 LexErrorKind::DecimalExponentMissingDigits => {
573 start..end_decimal_exponent_missing_digits(source, start)
574 }
575 }
576 }
577}
578impl std::error::Error for LexError {}
579
580#[inline]
581fn char_len_at(source: &str, byte: usize) -> Option<usize> {
582 source
583 .get(byte..)
584 .and_then(|s| s.chars().next())
585 .map(char::len_utf8)
586}
587
588fn end_char_lit_error(source: &str, start: usize) -> usize {
589 if start >= source.len() {
590 return start;
591 }
592 if char_len_at(source, start).is_none() {
593 return start;
594 }
595
596 let mut i = start + char_len_at(source, start).unwrap_or(0);
597 let mut escaped = false;
598 while i < source.len() {
599 let len = char_len_at(source, i).unwrap_or(0);
600 if len == 0 {
601 return start + 1;
602 }
603 let ch = source[i..i + len]
604 .chars()
605 .next()
606 .expect("positive char_len_at guarantees one UTF-8 scalar");
607 i += len;
608
609 if escaped {
610 escaped = false;
611 continue;
612 }
613 if ch == '\\' {
614 escaped = true;
615 continue;
616 }
617 if ch == '\'' {
618 return i;
619 }
620 }
621
622 start + 1
623}
624
625fn end_hex_missing_digits(source: &str, start: usize) -> usize {
626 if start >= source.len() {
627 return start;
628 }
629 let after_prefix = start.saturating_add(2);
630 let prefix = source.get(start..after_prefix).unwrap_or("");
631 if prefix != "0x" && prefix != "0X" {
632 return start;
633 }
634
635 let mut end = after_prefix;
636 while let Some(len) = char_len_at(source, end) {
637 if source.get(end..end + len) != Some("_") {
638 break;
639 }
640 end += len;
641 }
642 end
643}
644
645fn end_decimal_exponent_missing_digits(source: &str, start: usize) -> usize {
646 let mut byte = start;
647 while byte < source.len() {
648 let Some(len) = char_len_at(source, byte) else {
649 return start;
650 };
651 if len == 0 {
652 return start;
653 }
654 let ch = source[byte..byte + len]
655 .chars()
656 .next()
657 .expect("positive char_len_at guarantees one UTF-8 scalar");
658 if matches!(ch, 'e' | 'E') {
659 let mut end = byte + len;
660 if let Some(sign_len) = char_len_at(source, end) {
661 let sign = source[end..end + sign_len]
662 .chars()
663 .next()
664 .expect("positive char_len_at guarantees one UTF-8 scalar");
665 if matches!(sign, '+' | '-') {
666 end += sign_len;
667 }
668 }
669 return end;
670 }
671
672 if ch.is_whitespace() {
673 return start;
674 }
675 byte += len;
676 }
677
678 start
679}
680
681#[inline]
683fn byte_of_pos(source: &str, pos: Pos) -> usize {
684 let mut line = 1usize;
685 let mut col = 1usize;
686 for (idx, ch) in source.char_indices() {
687 if line == pos.line && col == pos.column {
688 return idx;
689 }
690 match ch {
691 '\n' => {
692 line += 1;
693 col = 1;
694 }
695 '\t' => col = ((col - 1) / TAB_STOP + 1) * TAB_STOP + 1,
696 _ => col += 1,
697 }
698 }
699 source.len()
700}
701
702#[derive(Debug, Clone, PartialEq, Eq)]
703#[non_exhaustive]
704pub enum LexErrorKind {
705 UnexpectedCharacter(char),
707 UnterminatedBlockComment,
709 UnterminatedStringLiteral,
711 UnterminatedStringGap,
713 InvalidEscapeSequence(char),
715 StraySingleQuote,
717 CharacterLiteralWrongLength,
719 HexLiteralMissingDigits,
721 DecimalExponentMissingDigits,
723}
724
725impl std::fmt::Display for LexErrorKind {
726 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
727 match self {
728 Self::UnexpectedCharacter(c) => write!(f, "unexpected character '{c}'"),
729 Self::UnterminatedBlockComment => f.write_str("unterminated block comment"),
730 Self::UnterminatedStringLiteral => f.write_str("unterminated string literal"),
731 Self::UnterminatedStringGap => f.write_str("unterminated string gap"),
732 Self::InvalidEscapeSequence(c) => write!(f, "invalid escape sequence \\{c}"),
733 Self::StraySingleQuote => f.write_str("stray single quote"),
734 Self::CharacterLiteralWrongLength => {
735 f.write_str("character literal must contain exactly one character")
736 }
737 Self::HexLiteralMissingDigits => f.write_str("hex literal requires at least one digit"),
738 Self::DecimalExponentMissingDigits => {
739 f.write_str("decimal exponent requires at least one digit")
740 }
741 }
742 }
743}
744
745#[derive(Debug, Clone, PartialEq, Eq)]
746pub struct LexOutput {
747 pub tokens: Vec<Token>,
750 pub errors: Vec<LexError>,
752}
753
754impl LexOutput {
755 #[must_use]
756 pub fn into_parts(self) -> (Vec<Token>, Vec<LexError>) {
757 (self.tokens, self.errors)
758 }
759}
760
761#[derive(Debug, Clone, PartialEq, Eq)]
762pub struct LexWithTriviaOutput {
763 pub tokens: Vec<Token>,
765 pub trivia: Vec<Trivia>,
767 pub errors: Vec<LexError>,
769}
770
771impl LexWithTriviaOutput {
772 #[must_use]
773 pub fn into_parts(self) -> (Vec<Token>, Vec<Trivia>, Vec<LexError>) {
774 (self.tokens, self.trivia, self.errors)
775 }
776}
777
778#[derive(Debug, Clone, PartialEq, Eq)]
779#[non_exhaustive]
780pub enum RenderLosslessError {
781 OverlappingSpans {
784 start: usize,
786 },
787 UncoveredBytes {
789 start: usize,
791 end: usize,
793 text: String,
795 },
796 UncoveredTail {
798 start: usize,
800 text: String,
802 },
803}
804
805impl std::fmt::Display for RenderLosslessError {
806 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
807 match self {
808 Self::OverlappingSpans { start } => write!(f, "overlapping spans at byte {start}"),
809 Self::UncoveredBytes { start, end, text } => write!(
810 f,
811 "bytes {start}..{end} lost (not covered by any token/trivia): {text:?}"
812 ),
813 Self::UncoveredTail { start, text } => {
814 write!(f, "bytes {start}.. lost at EOF: {text:?}")
815 }
816 }
817 }
818}
819
820impl std::error::Error for RenderLosslessError {}
821
822const fn is_symbol_char(c: char) -> bool {
823 matches!(
824 c,
825 '!' | '#'
826 | '$'
827 | '%'
828 | '&'
829 | '*'
830 | '+'
831 | '.'
832 | '/'
833 | '<'
834 | '='
835 | '>'
836 | '?'
837 | '@'
838 | '\\'
839 | '^'
840 | '|'
841 | '-'
842 | '~'
843 | ':'
844 )
845}
846
847fn is_ident_start(c: char) -> bool {
848 c.is_alphabetic() || c == '_'
849}
850
851fn is_ident_char(c: char) -> bool {
852 c.is_alphanumeric() || c == '_' || c == '\''
853}
854
855pub(crate) const TAB_STOP: usize = 8;
856
857struct Lexer<'a> {
858 chars: Vec<char>,
859 src: &'a str,
860 i: usize,
861 byte: usize,
862 line: usize,
863 column: usize,
864 capture_trivia: bool,
865 tokens: Vec<Token>,
866 trivia: Vec<Trivia>,
867 errors: Vec<LexError>,
868}
869
870impl<'a> Lexer<'a> {
871 fn new(source: &'a str, capture_trivia: bool) -> Self {
872 Self {
873 chars: source.chars().collect(),
874 src: source,
875 i: 0,
876 byte: 0,
877 line: 1,
878 column: 1,
879 capture_trivia,
880 tokens: Vec::new(),
881 trivia: Vec::new(),
882 errors: Vec::new(),
883 }
884 }
885}
886
887#[must_use]
892pub fn lex(source: &str) -> LexOutput {
893 let mut lexer = Lexer::new(source, false);
894 lexer.scan_tokens();
895 LexOutput {
896 tokens: lexer.tokens,
897 errors: lexer.errors,
898 }
899}
900
901#[must_use]
903pub fn lex_with_trivia(source: &str) -> LexWithTriviaOutput {
904 let mut lexer = Lexer::new(source, true);
905 lexer.scan_tokens();
906 let mut trivia = lexer.trivia;
907 add_blank_line_trivia(source, &lexer.tokens, &mut trivia);
908 trivia.sort_by_key(|t| t.start);
909 LexWithTriviaOutput {
910 tokens: lexer.tokens,
911 trivia,
912 errors: lexer.errors,
913 }
914}
915
916#[must_use = "handle render errors instead of discarding"]
940pub fn render_lossless(
941 source: &str,
942 tokens: &[Token],
943 trivia: &[Trivia],
944) -> Result<String, RenderLosslessError> {
945 let mut items: Vec<(usize, usize)> = tokens
946 .iter()
947 .filter(|t| {
948 !matches!(
949 t.kind,
950 TokenKind::VLBrace | TokenKind::VRBrace | TokenKind::VSemi
951 )
952 })
953 .map(|t| (t.start, t.end))
954 .chain(
955 trivia
956 .iter()
957 .filter(|t| !matches!(t.kind, TriviaKind::BlankLines(_)))
958 .map(|t| (t.start, t.end)),
959 )
960 .collect();
961 items.sort_unstable();
962 let mut out = String::with_capacity(source.len());
963 let mut prev = 0usize;
964 for (start, end) in items {
965 if start < prev {
966 return Err(RenderLosslessError::OverlappingSpans { start });
967 }
968 let gap = &source[prev..start];
969 if !gap.chars().all(char::is_whitespace) {
970 return Err(RenderLosslessError::UncoveredBytes {
971 start: prev,
972 end: start,
973 text: gap.to_string(),
974 });
975 }
976 out.push_str(gap);
977 out.push_str(&source[start..end]);
978 prev = end;
979 }
980 let tail = &source[prev..];
981 if !tail.chars().all(char::is_whitespace) {
982 return Err(RenderLosslessError::UncoveredTail {
983 start: prev,
984 text: tail.to_string(),
985 });
986 }
987 out.push_str(tail);
988 Ok(out)
989}
990
991fn add_blank_line_trivia(source: &str, tokens: &[Token], trivia: &mut Vec<Trivia>) {
996 let mut spans: Vec<(usize, usize)> = tokens
997 .iter()
998 .map(|t| (t.start, t.end))
999 .chain(trivia.iter().map(|t| (t.start, t.end)))
1000 .collect();
1001 spans.sort_unstable();
1002 let bytes = source.as_bytes();
1003 let mut blanks = Vec::new();
1004 let mut gap_start = 0usize;
1005 let emit_gap = |from: usize, to: usize, out: &mut Vec<Trivia>| {
1006 let newlines: Vec<usize> = (from..to).filter(|&i| bytes[i] == b'\n').collect();
1011 let count = if from == 0 {
1015 newlines.len()
1016 } else {
1017 newlines.len().saturating_sub(1)
1018 };
1019 if count == 0 {
1020 return;
1021 }
1022 let region_start = if from == 0 { 0 } else { newlines[0] + 1 };
1023 let region_end = newlines[newlines.len() - 1] + 1;
1024 let line = source[..region_start].matches('\n').count() + 1;
1025 out.push(Trivia {
1026 kind: TriviaKind::BlankLines(count),
1027 text: String::new(),
1028 pos: Pos { line, column: 1 },
1029 start: region_start,
1030 end: region_end,
1031 });
1032 };
1033 for &(s, e) in &spans {
1034 if s > gap_start {
1035 emit_gap(gap_start, s, &mut blanks);
1036 }
1037 gap_start = gap_start.max(e);
1038 }
1039 if source.len() > gap_start {
1040 emit_gap(gap_start, source.len(), &mut blanks);
1041 }
1042 trivia.extend(blanks);
1043}
1044
1045impl<'a> Lexer<'a> {
1046 fn peek(&self) -> Option<char> {
1047 self.chars.get(self.i).copied()
1048 }
1049
1050 fn peek_at(&self, n: usize) -> Option<char> {
1051 self.chars.get(self.i + n).copied()
1052 }
1053
1054 fn bump(&mut self) -> Option<char> {
1055 let c = self.chars.get(self.i).copied()?;
1056 self.i += 1;
1057 self.byte += c.len_utf8();
1058 match c {
1059 '\n' => {
1060 self.line += 1;
1061 self.column = 1;
1062 }
1063 '\t' => {
1064 self.column = ((self.column - 1) / TAB_STOP + 1) * TAB_STOP + 1;
1067 }
1068 _ => self.column += 1,
1069 }
1070 Some(c)
1071 }
1072
1073 fn bump_after_peek(&mut self) -> char {
1075 self.bump()
1076 .expect("lexer cursor guarded by peek/peek_at before bump")
1077 }
1078
1079 const fn pos(&self) -> Pos {
1080 Pos {
1081 line: self.line,
1082 column: self.column,
1083 }
1084 }
1085
1086 fn push(&mut self, tok: TokenKind, pos: Pos, start: usize) {
1089 self.tokens.push(Token {
1090 kind: tok,
1091 pos,
1092 start,
1093 end: self.byte,
1094 });
1095 }
1096
1097 fn push_trivia(&mut self, kind: TriviaKind, pos: Pos, start: usize) {
1098 if !self.capture_trivia {
1099 return;
1100 }
1101 self.trivia.push(Trivia {
1102 kind,
1103 text: self.src[start..self.byte].to_string(),
1104 pos,
1105 start,
1106 end: self.byte,
1107 });
1108 }
1109
1110 fn error(&mut self, kind: LexErrorKind, pos: Pos) {
1111 self.errors.push(LexError { kind, pos });
1112 }
1113
1114 fn scan_tokens(&mut self) {
1115 while let Some(c) = self.peek() {
1116 let pos = self.pos();
1117 let start = self.byte;
1118 match c {
1119 ' ' | '\t' | '\n' | '\r' => {
1120 self.bump();
1121 }
1122 '(' => {
1123 self.bump();
1124 self.push(TokenKind::LParen, pos, start);
1125 }
1126 ')' => {
1127 self.bump();
1128 self.push(TokenKind::RParen, pos, start);
1129 }
1130 '[' => {
1131 self.bump();
1132 self.push(TokenKind::LBracket, pos, start);
1133 }
1134 ']' => {
1135 self.bump();
1136 self.push(TokenKind::RBracket, pos, start);
1137 }
1138 ',' => {
1139 self.bump();
1140 self.push(TokenKind::Comma, pos, start);
1141 }
1142 ';' => {
1143 self.bump();
1144 self.push(TokenKind::Semi, pos, start);
1145 }
1146 '`' => {
1147 self.bump();
1148 self.push(TokenKind::Backtick, pos, start);
1149 }
1150 '{' => {
1151 if self.peek_at(1) == Some('-') {
1152 self.block_comment(pos);
1153 } else {
1154 self.bump();
1155 self.push(TokenKind::LBrace, pos, start);
1156 }
1157 }
1158 '}' => {
1159 self.bump();
1160 self.push(TokenKind::RBrace, pos, start);
1161 }
1162 '#' if self.column == 1
1166 && self.peek_at(1).is_some_and(|c| c.is_ascii_lowercase()) =>
1167 {
1168 while self.peek().is_some_and(|c| c != '\n') {
1169 self.bump();
1170 }
1171 self.push_trivia(TriviaKind::CppDirective, pos, start);
1172 }
1173 '"' => self.string_lit(pos),
1174 '\'' => self.char_lit(pos),
1175 c if c.is_ascii_digit() => self.number(pos),
1176 c if is_ident_start(c) => self.identifier(pos),
1177 c if is_symbol_char(c) => self.operator(pos),
1178 _ => {
1179 self.bump();
1180 self.error(LexErrorKind::UnexpectedCharacter(c), pos);
1181 }
1182 }
1183 }
1184 }
1185
1186 fn block_comment(&mut self, pos: Pos) {
1189 let start = self.byte;
1190 self.bump(); self.bump(); let mut depth = 1usize;
1193 while depth > 0 {
1194 match self.peek() {
1195 None => {
1196 self.error(LexErrorKind::UnterminatedBlockComment, pos);
1197 self.push_trivia(TriviaKind::BlockComment, pos, start);
1198 return;
1199 }
1200 Some('{') if self.peek_at(1) == Some('-') => {
1201 self.bump();
1202 self.bump();
1203 depth += 1;
1204 }
1205 Some('-') if self.peek_at(1) == Some('}') => {
1206 self.bump();
1207 self.bump();
1208 depth -= 1;
1209 }
1210 Some(_) => {
1211 self.bump();
1212 }
1213 }
1214 }
1215 self.push_trivia(TriviaKind::BlockComment, pos, start);
1216 }
1217
1218 fn string_lit(&mut self, pos: Pos) {
1219 let start = self.byte;
1220 self.bump(); let mut value = String::new();
1222 loop {
1223 match self.peek() {
1224 None | Some('\n') => {
1225 self.error(LexErrorKind::UnterminatedStringLiteral, pos);
1226 break;
1227 }
1228 Some('"') => {
1229 self.bump();
1230 break;
1231 }
1232 Some('\\') => {
1233 let escape_pos = self.pos();
1234 self.bump();
1235 match self.peek() {
1236 Some(w) if w.is_whitespace() => {
1238 while self.peek().is_some_and(|c| c.is_whitespace()) {
1239 self.bump();
1240 }
1241 if self.peek() == Some('\\') {
1242 self.bump();
1243 } else {
1244 self.error(LexErrorKind::UnterminatedStringGap, pos);
1245 break;
1246 }
1247 }
1248 Some(e) => {
1249 self.bump();
1250 match unescape(e) {
1251 Some(c) => value.push(c),
1252 None => {
1253 self.error(LexErrorKind::InvalidEscapeSequence(e), escape_pos);
1254 value.push(e);
1255 }
1256 }
1257 }
1258 None => {
1259 self.error(LexErrorKind::UnterminatedStringLiteral, pos);
1260 break;
1261 }
1262 }
1263 }
1264 Some(c) => {
1265 self.bump();
1266 value.push(c);
1267 }
1268 }
1269 }
1270 self.push(TokenKind::StringLit(value), pos, start);
1271 }
1272
1273 fn char_lit(&mut self, pos: Pos) {
1277 let start = self.byte;
1278 let mut j = self.i + 1;
1280 let mut escaped = false;
1281 let mut ok = false;
1282 let window_end = (self.i + 12).min(self.chars.len());
1283 while j < window_end {
1284 match self.chars[j] {
1285 '\\' if !escaped => escaped = true,
1286 '\'' if !escaped => {
1287 ok = j > self.i + 1;
1288 break;
1289 }
1290 '\n' => break,
1291 _ => escaped = false,
1292 }
1293 j += 1;
1294 }
1295 if !ok {
1296 self.bump();
1297 self.error(LexErrorKind::StraySingleQuote, pos);
1298 return;
1299 }
1300 self.bump(); let mut value = String::new();
1302 while self.peek() != Some('\'') {
1303 let c = self.bump_after_peek();
1304 if c == '\\' {
1305 let escape_pos = Pos {
1306 line: self.line,
1307 column: self.column.saturating_sub(1),
1308 };
1309 if let Some(e) = self.bump() {
1310 match unescape(e) {
1311 Some(c) => value.push(c),
1312 None => {
1313 self.error(LexErrorKind::InvalidEscapeSequence(e), escape_pos);
1314 value.push(e);
1315 }
1316 }
1317 }
1318 } else {
1319 value.push(c);
1320 }
1321 }
1322 self.bump(); if value.chars().count() != 1 {
1324 self.error(LexErrorKind::CharacterLiteralWrongLength, pos);
1325 }
1326 self.push(TokenKind::CharLit(value), pos, start);
1327 }
1328
1329 fn number(&mut self, pos: Pos) {
1330 let start = self.byte;
1331 let mut text = String::new();
1332 if self.peek() == Some('0') && matches!(self.peek_at(1), Some('x' | 'X')) {
1333 text.push(self.bump_after_peek());
1334 text.push(self.bump_after_peek());
1335 let mut has_hex_digit = false;
1336 while self
1337 .peek()
1338 .is_some_and(|c| c.is_ascii_hexdigit() || c == '_')
1339 {
1340 let c = self.bump_after_peek();
1341 has_hex_digit |= c.is_ascii_hexdigit();
1342 text.push(c);
1343 }
1344 if !has_hex_digit {
1345 self.error(LexErrorKind::HexLiteralMissingDigits, pos);
1346 }
1347 self.push(TokenKind::IntLit(text), pos, start);
1348 return;
1349 }
1350 while self.peek().is_some_and(|c| c.is_ascii_digit() || c == '_') {
1351 text.push(self.bump_after_peek());
1352 }
1353 let mut decimal = false;
1354 if self.peek() == Some('.') && self.peek_at(1).is_some_and(|c| c.is_ascii_digit()) {
1356 decimal = true;
1357 text.push(self.bump_after_peek());
1358 while self.peek().is_some_and(|c| c.is_ascii_digit() || c == '_') {
1359 text.push(self.bump_after_peek());
1360 }
1361 }
1362 if matches!(self.peek(), Some('e' | 'E')) {
1363 decimal = true;
1364 if self.peek_at(1).is_some_and(|c| c.is_ascii_digit())
1365 || (matches!(self.peek_at(1), Some('+' | '-'))
1366 && self.peek_at(2).is_some_and(|c| c.is_ascii_digit()))
1367 {
1368 text.push(self.bump_after_peek());
1369 if matches!(self.peek(), Some('+' | '-')) {
1370 text.push(self.bump_after_peek());
1371 }
1372 while self.peek().is_some_and(|c| c.is_ascii_digit()) {
1373 text.push(self.bump_after_peek());
1374 }
1375 } else {
1376 text.push(self.bump_after_peek());
1377 if matches!(self.peek(), Some('+' | '-')) {
1378 text.push(self.bump_after_peek());
1379 }
1380 self.error(LexErrorKind::DecimalExponentMissingDigits, pos);
1381 }
1382 }
1383 if decimal {
1384 self.push(TokenKind::DecimalLit(text), pos, start);
1385 } else {
1386 self.push(TokenKind::IntLit(text), pos, start);
1387 }
1388 }
1389
1390 fn identifier(&mut self, pos: Pos) {
1393 let start = self.byte;
1394 let mut segments: Vec<String> = Vec::new();
1395 loop {
1396 let mut seg = String::new();
1397 while self.peek().is_some_and(is_ident_char) {
1398 seg.push(self.bump_after_peek());
1399 }
1400 let seg_is_upper = seg.chars().next().is_some_and(|c| c.is_uppercase());
1401 segments.push(seg);
1402 if seg_is_upper
1405 && self.peek() == Some('.')
1406 && self.peek_at(1).is_some_and(is_ident_start)
1407 {
1408 self.bump(); continue;
1410 }
1411 break;
1412 }
1413 let name = segments
1414 .pop()
1415 .expect("identifier loop always records at least one segment");
1416 let qualifier = if segments.is_empty() {
1417 None
1418 } else {
1419 Some(segments.join(".").into())
1420 };
1421 let tok = if name.chars().next().is_some_and(|c| c.is_uppercase()) {
1422 TokenKind::UpperId {
1423 qualifier,
1424 name: name.into(),
1425 }
1426 } else {
1427 TokenKind::LowerId {
1428 qualifier,
1429 name: name.into(),
1430 }
1431 };
1432 self.push(tok, pos, start);
1433 }
1434
1435 fn operator(&mut self, pos: Pos) {
1436 let start = self.i;
1437 let byte_start = self.byte;
1438 while self.peek().is_some_and(is_symbol_char) {
1439 self.bump();
1442 }
1443 let text: String = self.chars[start..self.i].iter().collect();
1444 if text.len() >= 2 && text.chars().all(|c| c == '-') {
1447 while self.peek().is_some_and(|c| c != '\n') {
1448 self.bump();
1449 }
1450 self.push_trivia(TriviaKind::LineComment, pos, byte_start);
1451 return;
1452 }
1453 self.push(TokenKind::Op(text.into()), pos, byte_start);
1454 }
1455}
1456
1457const fn unescape(c: char) -> Option<char> {
1458 match c {
1459 'n' => Some('\n'),
1460 't' => Some('\t'),
1461 'r' => Some('\r'),
1462 '0' => Some('\0'),
1463 'a' => Some('\u{07}'),
1464 'b' => Some('\u{08}'),
1465 'f' => Some('\u{0c}'),
1466 'v' => Some('\u{0b}'),
1467 '"' => Some('"'),
1468 '\'' => Some('\''),
1469 '\\' => Some('\\'),
1470 '&' => Some('&'),
1471 '1'..='9' | 'o' | 'x' | 'A'..='Z' => Some(c),
1477 _ => None,
1478 }
1479}
1480
1481impl TokenKind {
1482 #[must_use]
1485 pub const fn keyword(&self) -> Option<&str> {
1486 match self {
1487 Self::LowerId {
1488 qualifier: None,
1489 name,
1490 } => Some(name.as_str()),
1491 _ => None,
1492 }
1493 }
1494
1495 #[must_use]
1496 pub fn is_keyword(&self, kw: &str) -> bool {
1497 self.keyword() == Some(kw)
1498 }
1499
1500 #[must_use]
1501 pub fn is_op(&self, op: &str) -> bool {
1502 matches!(self, Self::Op(o) if o.as_str() == op)
1503 }
1504}
1505
1506#[cfg(test)]
1508mod tests {
1509 use super::*;
1510
1511 fn toks(src: &str) -> Vec<TokenKind> {
1512 let (tokens, errors) = lex(src).into_parts();
1513 assert!(errors.is_empty(), "lex errors: {errors:?}");
1514 tokens.into_iter().map(|t| t.kind).collect()
1515 }
1516
1517 fn lex_error_messages(src: &str) -> Vec<String> {
1518 let (_, errors) = lex(src).into_parts();
1519 errors.into_iter().map(|e| e.to_string()).collect()
1520 }
1521
1522 fn lower(name: &str) -> TokenKind {
1523 TokenKind::LowerId {
1524 qualifier: None,
1525 name: name.into(),
1526 }
1527 }
1528
1529 fn upper(name: &str) -> TokenKind {
1530 TokenKind::UpperId {
1531 qualifier: None,
1532 name: name.into(),
1533 }
1534 }
1535
1536 #[test]
1537 fn identifier_as_ref_str() {
1538 let identifier = Identifier::from("value");
1539 let operator = Operator::from("+");
1540 let module = ModuleName::from("DA.Map");
1541
1542 assert_eq!(identifier.as_ref(), "value");
1543 assert_eq!(operator.as_ref(), "+");
1544 assert_eq!(module.as_ref(), "DA.Map");
1545 }
1546
1547 #[test]
1548 fn token_kind_display_is_normalized_not_lossless_source() {
1549 assert_eq!(TokenKind::LBrace.to_string(), "{");
1552 assert_eq!(TokenKind::VLBrace.to_string(), "{");
1553 assert_eq!(TokenKind::Semi.to_string(), ";");
1554 assert_eq!(TokenKind::VSemi.to_string(), ";");
1555
1556 assert_eq!(
1557 TokenKind::UpperId {
1558 qualifier: Some("DA.Map".into()),
1559 name: "Map".into(),
1560 }
1561 .to_string(),
1562 "DA.Map.Map"
1563 );
1564 assert_eq!(TokenKind::Op("->".into()).to_string(), "->");
1565 assert_eq!(
1566 TokenKind::StringLit("line\n".into()).to_string(),
1567 "\"line\\n\""
1568 );
1569 assert_eq!(TokenKind::CharLit("'".into()).to_string(), "'\\''");
1570 }
1571
1572 #[test]
1573 fn line_comment_with_keywords_produces_no_tokens() {
1574 assert_eq!(toks("-- electing to exercise the option"), vec![]);
1575 assert_eq!(toks("--- template Foo"), vec![]);
1576 }
1577
1578 #[test]
1579 fn arrow_like_operator_is_not_comment() {
1580 assert_eq!(
1581 toks("a --> b"),
1582 vec![lower("a"), TokenKind::Op("-->".into()), lower("b")]
1583 );
1584 }
1585
1586 #[test]
1587 fn nested_block_comment() {
1588 assert_eq!(toks("{- outer {- inner -} still -} x"), vec![lower("x")]);
1589 }
1590
1591 #[test]
1592 fn string_with_keyword_and_escapes() {
1593 assert_eq!(
1594 toks(r#""template \"Foo\" \n""#),
1595 vec![TokenKind::StringLit("template \"Foo\" \n".into())]
1596 );
1597 }
1598
1599 #[test]
1600 fn qualified_identifiers() {
1601 assert_eq!(
1602 toks("DA.Set.fromList Map.Map foo"),
1603 vec![
1604 TokenKind::LowerId {
1605 qualifier: Some("DA.Set".into()),
1606 name: "fromList".into()
1607 },
1608 TokenKind::UpperId {
1609 qualifier: Some("Map".into()),
1610 name: "Map".into()
1611 },
1612 lower("foo"),
1613 ]
1614 );
1615 }
1616
1617 #[test]
1618 fn numbers() {
1619 assert_eq!(
1620 toks("42 1.5 0x1F 2e3 1_000"),
1621 vec![
1622 TokenKind::IntLit("42".into()),
1623 TokenKind::DecimalLit("1.5".into()),
1624 TokenKind::IntLit("0x1F".into()),
1625 TokenKind::DecimalLit("2e3".into()),
1626 TokenKind::IntLit("1_000".into()),
1627 ]
1628 );
1629 }
1630
1631 #[test]
1632 fn malformed_hex_literal_reports_error() {
1633 assert_eq!(
1634 lex_error_messages("0x 0x_"),
1635 vec![
1636 "hex literal requires at least one digit",
1637 "hex literal requires at least one digit",
1638 ]
1639 );
1640 }
1641
1642 #[test]
1643 fn malformed_decimal_exponent_reports_error() {
1644 assert_eq!(
1645 lex_error_messages("1e 1e+ 1e-"),
1646 vec![
1647 "decimal exponent requires at least one digit",
1648 "decimal exponent requires at least one digit",
1649 "decimal exponent requires at least one digit",
1650 ]
1651 );
1652 }
1653
1654 #[test]
1655 fn enum_from_to_is_not_decimal() {
1656 assert_eq!(
1657 toks("[1..5]"),
1658 vec![
1659 TokenKind::LBracket,
1660 TokenKind::IntLit("1".into()),
1661 TokenKind::Op("..".into()),
1662 TokenKind::IntLit("5".into()),
1663 TokenKind::RBracket,
1664 ]
1665 );
1666 }
1667
1668 #[test]
1669 fn primes_stay_in_identifier_and_char_lit_works() {
1670 assert_eq!(
1671 toks(r"foo' 'a' '\n'"),
1672 vec![
1673 lower("foo'"),
1674 TokenKind::CharLit("a".into()),
1675 TokenKind::CharLit("\n".into())
1676 ]
1677 );
1678 }
1679
1680 #[test]
1681 fn invalid_escape_sequences_report_errors() {
1682 assert_eq!(
1683 lex_error_messages(r#""\q" '\q'"#),
1684 vec!["invalid escape sequence \\q", "invalid escape sequence \\q"]
1685 );
1686 }
1687
1688 #[test]
1689 fn multi_character_char_literal_reports_error() {
1690 let (tokens, errors) = lex("'ab'").into_parts();
1691 assert_eq!(
1692 tokens.iter().map(|t| t.kind.clone()).collect::<Vec<_>>(),
1693 vec![TokenKind::CharLit("ab".into())]
1694 );
1695 assert_eq!(
1696 errors.iter().map(|e| e.to_string()).collect::<Vec<_>>(),
1697 vec!["character literal must contain exactly one character".to_string()]
1698 );
1699 }
1700
1701 #[test]
1702 fn operators_and_punctuation() {
1703 assert_eq!(
1704 toks("x <- f (y, z) `div` 2"),
1705 vec![
1706 lower("x"),
1707 TokenKind::Op("<-".into()),
1708 lower("f"),
1709 TokenKind::LParen,
1710 lower("y"),
1711 TokenKind::Comma,
1712 lower("z"),
1713 TokenKind::RParen,
1714 TokenKind::Backtick,
1715 lower("div"),
1716 TokenKind::Backtick,
1717 TokenKind::IntLit("2".into()),
1718 ]
1719 );
1720 }
1721
1722 #[test]
1723 fn spans_are_one_based() {
1724 let (tokens, _) = lex("ab\n cd").into_parts();
1725 assert_eq!(tokens[0].pos, Pos { line: 1, column: 1 });
1726 assert_eq!(tokens[1].pos, Pos { line: 2, column: 3 });
1727 }
1728
1729 #[test]
1730 fn tab_advances_to_stop() {
1731 let (tokens, _) = lex("\tx").into_parts();
1732 assert_eq!(tokens[0].pos, Pos { line: 1, column: 9 });
1733 }
1734
1735 #[test]
1736 fn unterminated_string_is_error_not_hang() {
1737 let (_, errors) = lex("x = \"oops\ny").into_parts();
1738 assert_eq!(errors.len(), 1);
1739 }
1740
1741 #[test]
1742 fn unterminated_block_comment_is_error_not_hang() {
1743 let (_, errors) = lex("{- never closed").into_parts();
1744 assert_eq!(errors.len(), 1);
1745 }
1746
1747 fn trivia_of(src: &str) -> Vec<Trivia> {
1748 let (_, trivia, _) = lex_with_trivia(src).into_parts();
1749 trivia
1750 }
1751
1752 fn assert_round_trip(src: &str) {
1755 let (tokens, trivia, errors) = lex_with_trivia(src).into_parts();
1756 assert!(errors.is_empty(), "lex errors: {errors:?}");
1757 assert_eq!(
1758 render_lossless(src, &tokens, &trivia).as_deref(),
1759 Ok(src),
1760 "round trip failed for {src:?}"
1761 );
1762 }
1763
1764 #[test]
1765 fn line_comment_becomes_trivia_with_exact_text_and_span() {
1766 let src = "x = 1 -- electing to exercise\ny = 2\n";
1767 let trivia = trivia_of(src);
1768 assert_eq!(trivia.len(), 1);
1769 assert_eq!(trivia[0].kind, TriviaKind::LineComment);
1770 assert_eq!(trivia[0].text, "-- electing to exercise");
1771 assert_eq!(&src[trivia[0].start..trivia[0].end], trivia[0].text);
1772 assert_eq!(trivia[0].pos, Pos { line: 1, column: 7 });
1773 }
1774
1775 #[test]
1776 fn nested_block_comment_becomes_one_trivia() {
1777 let src = "{- outer {- inner -} still -} x";
1778 let trivia = trivia_of(src);
1779 assert_eq!(trivia.len(), 1);
1780 assert_eq!(trivia[0].kind, TriviaKind::BlockComment);
1781 assert_eq!(trivia[0].text, "{- outer {- inner -} still -}");
1782 }
1783
1784 #[test]
1785 fn unterminated_block_comment_still_yields_trivia_to_eof() {
1786 let (_, trivia, errors) = lex_with_trivia("x {- never closed").into_parts();
1787 assert_eq!(errors.len(), 1);
1788 assert_eq!(trivia.len(), 1);
1789 assert_eq!(trivia[0].text, "{- never closed");
1790 }
1791
1792 #[test]
1793 fn blank_lines_between_items_counted() {
1794 let src = "x = 1\n\n\ny = 2\n";
1795 let trivia = trivia_of(src);
1796 assert_eq!(trivia.len(), 1);
1797 assert_eq!(trivia[0].kind, TriviaKind::BlankLines(2));
1798 assert_eq!(trivia[0].pos, Pos { line: 2, column: 1 });
1799 }
1800
1801 #[test]
1802 fn blank_line_at_file_start_counted() {
1803 let trivia = trivia_of("\nx = 1\n");
1804 assert_eq!(trivia.len(), 1);
1805 assert_eq!(trivia[0].kind, TriviaKind::BlankLines(1));
1806 assert_eq!(trivia[0].pos, Pos { line: 1, column: 1 });
1807 }
1808
1809 #[test]
1810 fn blank_looking_lines_inside_block_comment_are_not_blank_trivia() {
1811 let src = "x = 1 {- a\n\nb -}\ny = 2\n";
1812 let trivia = trivia_of(src);
1813 assert_eq!(trivia.len(), 1, "{trivia:?}");
1814 assert_eq!(trivia[0].kind, TriviaKind::BlockComment);
1815 }
1816
1817 #[test]
1818 fn blank_line_between_comments_counted() {
1819 let src = "-- a\n\n-- b\nx = 1\n";
1820 let kinds: Vec<_> = trivia_of(src).into_iter().map(|t| t.kind).collect();
1821 assert_eq!(
1822 kinds,
1823 vec![
1824 TriviaKind::LineComment,
1825 TriviaKind::BlankLines(1),
1826 TriviaKind::LineComment,
1827 ]
1828 );
1829 }
1830
1831 #[test]
1832 fn cpp_directive_becomes_trivia() {
1833 let src = "#ifdef DAML_BIGNUMERIC\nx = 1\n#endif\n";
1834 let trivia = trivia_of(src);
1835 assert_eq!(trivia.len(), 2);
1836 assert!(trivia.iter().all(|t| t.kind == TriviaKind::CppDirective));
1837 assert_eq!(trivia[0].text, "#ifdef DAML_BIGNUMERIC");
1838 }
1839
1840 #[test]
1841 fn round_trip_is_byte_identical() {
1842 assert_round_trip("module M where\n\n-- doc\nf : Int -> Int\nf x = x + 1\n");
1843 assert_round_trip("x = \"tem\\\"plate \\n\" {- block {- nested -} -}\r\ny = 'a'\r\n");
1844 assert_round_trip("\tärger = [1..5] -- ütf\n");
1845 assert_round_trip("s = \"gap \\ \\ here\"\n");
1846 assert_round_trip("#ifdef X\nf = 0x1F\n#endif\n");
1847 assert_round_trip("\n\n \nf = 1 \n ");
1848 assert_round_trip("");
1849 }
1850
1851 #[test]
1852 fn render_lossless_detects_lost_bytes() {
1853 let src = "x = 1 -- comment\n";
1854 let (tokens, mut trivia, _) = lex_with_trivia(src).into_parts();
1855 trivia.clear(); assert!(render_lossless(src, &tokens, &trivia).is_err());
1857 }
1858
1859 #[test]
1860 fn unicode_identifier() {
1861 assert_eq!(
1862 toks("ärger = 1"),
1863 vec![
1864 lower("ärger"),
1865 TokenKind::Op("=".into()),
1866 TokenKind::IntLit("1".into())
1867 ]
1868 );
1869 let _ = upper("Ülf"); }
1871
1872 fn assert_lex_tokens_match(src: &str) {
1875 let (parse_tokens, parse_errors) = lex(src).into_parts();
1876 let (trivia_tokens, _, trivia_errors) = lex_with_trivia(src).into_parts();
1877 assert_eq!(parse_errors, trivia_errors, "lex errors differ for {src:?}");
1878 assert_eq!(
1879 parse_tokens.len(),
1880 trivia_tokens.len(),
1881 "token count for {src:?}"
1882 );
1883 for (a, b) in parse_tokens.iter().zip(trivia_tokens.iter()) {
1884 assert_eq!(a.kind, b.kind, "token kind for {src:?}");
1885 assert_eq!(a.pos, b.pos, "token pos for {src:?}");
1886 assert_eq!(a.start, b.start, "token start for {src:?}");
1887 assert_eq!(a.end, b.end, "token end for {src:?}");
1888 }
1889 }
1890
1891 #[test]
1892 fn lex_and_lex_with_trivia_emit_identical_tokens() {
1893 assert_lex_tokens_match("x = 1 -- electing to exercise\ny = 2\n");
1894 assert_lex_tokens_match("module M where\n\n-- doc\nf : Int -> Int\nf x = x + 1\n");
1895 assert_lex_tokens_match("{- outer {- inner -} still -} x");
1896 assert_lex_tokens_match("#ifdef DAML_BIGNUMERIC\nx = 1\n#endif\n");
1897 assert_lex_tokens_match("\n\n \nf = 1 \n ");
1898 }
1899
1900 #[test]
1901 fn lex_with_trivia_preserves_lossless_comment_and_blank_line_render() {
1902 let sources = [
1903 "x = 1 -- electing to exercise\ny = 2\n",
1904 "x = 1\n\n\ny = 2\n",
1905 "-- a\n\n-- b\nx = 1\n",
1906 ];
1907 for src in sources {
1908 let (tokens, trivia, errors) = lex_with_trivia(src).into_parts();
1909 assert!(errors.is_empty(), "lex errors for {src:?}: {errors:?}");
1910 assert_eq!(
1911 render_lossless(src, &tokens, &trivia).as_deref(),
1912 Ok(src),
1913 "lossless render failed for {src:?}"
1914 );
1915 }
1916 }
1917}