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"]
928pub fn render_lossless(
929 source: &str,
930 tokens: &[Token],
931 trivia: &[Trivia],
932) -> Result<String, RenderLosslessError> {
933 let mut items: Vec<(usize, usize)> = tokens
934 .iter()
935 .filter(|t| {
936 !matches!(
937 t.kind,
938 TokenKind::VLBrace | TokenKind::VRBrace | TokenKind::VSemi
939 )
940 })
941 .map(|t| (t.start, t.end))
942 .chain(
943 trivia
944 .iter()
945 .filter(|t| !matches!(t.kind, TriviaKind::BlankLines(_)))
946 .map(|t| (t.start, t.end)),
947 )
948 .collect();
949 items.sort_unstable();
950 let mut out = String::with_capacity(source.len());
951 let mut prev = 0usize;
952 for (start, end) in items {
953 if start < prev {
954 return Err(RenderLosslessError::OverlappingSpans { start });
955 }
956 let gap = &source[prev..start];
957 if !gap.chars().all(char::is_whitespace) {
958 return Err(RenderLosslessError::UncoveredBytes {
959 start: prev,
960 end: start,
961 text: gap.to_string(),
962 });
963 }
964 out.push_str(gap);
965 out.push_str(&source[start..end]);
966 prev = end;
967 }
968 let tail = &source[prev..];
969 if !tail.chars().all(char::is_whitespace) {
970 return Err(RenderLosslessError::UncoveredTail {
971 start: prev,
972 text: tail.to_string(),
973 });
974 }
975 out.push_str(tail);
976 Ok(out)
977}
978
979fn add_blank_line_trivia(source: &str, tokens: &[Token], trivia: &mut Vec<Trivia>) {
984 let mut spans: Vec<(usize, usize)> = tokens
985 .iter()
986 .map(|t| (t.start, t.end))
987 .chain(trivia.iter().map(|t| (t.start, t.end)))
988 .collect();
989 spans.sort_unstable();
990 let bytes = source.as_bytes();
991 let mut blanks = Vec::new();
992 let mut gap_start = 0usize;
993 let emit_gap = |from: usize, to: usize, out: &mut Vec<Trivia>| {
994 let newlines: Vec<usize> = (from..to).filter(|&i| bytes[i] == b'\n').collect();
999 let count = if from == 0 {
1003 newlines.len()
1004 } else {
1005 newlines.len().saturating_sub(1)
1006 };
1007 if count == 0 {
1008 return;
1009 }
1010 let region_start = if from == 0 { 0 } else { newlines[0] + 1 };
1011 let region_end = newlines[newlines.len() - 1] + 1;
1012 let line = source[..region_start].matches('\n').count() + 1;
1013 out.push(Trivia {
1014 kind: TriviaKind::BlankLines(count),
1015 text: String::new(),
1016 pos: Pos { line, column: 1 },
1017 start: region_start,
1018 end: region_end,
1019 });
1020 };
1021 for &(s, e) in &spans {
1022 if s > gap_start {
1023 emit_gap(gap_start, s, &mut blanks);
1024 }
1025 gap_start = gap_start.max(e);
1026 }
1027 if source.len() > gap_start {
1028 emit_gap(gap_start, source.len(), &mut blanks);
1029 }
1030 trivia.extend(blanks);
1031}
1032
1033impl<'a> Lexer<'a> {
1034 fn peek(&self) -> Option<char> {
1035 self.chars.get(self.i).copied()
1036 }
1037
1038 fn peek_at(&self, n: usize) -> Option<char> {
1039 self.chars.get(self.i + n).copied()
1040 }
1041
1042 fn bump(&mut self) -> Option<char> {
1043 let c = self.chars.get(self.i).copied()?;
1044 self.i += 1;
1045 self.byte += c.len_utf8();
1046 match c {
1047 '\n' => {
1048 self.line += 1;
1049 self.column = 1;
1050 }
1051 '\t' => {
1052 self.column = ((self.column - 1) / TAB_STOP + 1) * TAB_STOP + 1;
1055 }
1056 _ => self.column += 1,
1057 }
1058 Some(c)
1059 }
1060
1061 fn bump_after_peek(&mut self) -> char {
1063 self.bump()
1064 .expect("lexer cursor guarded by peek/peek_at before bump")
1065 }
1066
1067 const fn pos(&self) -> Pos {
1068 Pos {
1069 line: self.line,
1070 column: self.column,
1071 }
1072 }
1073
1074 fn push(&mut self, tok: TokenKind, pos: Pos, start: usize) {
1077 self.tokens.push(Token {
1078 kind: tok,
1079 pos,
1080 start,
1081 end: self.byte,
1082 });
1083 }
1084
1085 fn push_trivia(&mut self, kind: TriviaKind, pos: Pos, start: usize) {
1086 if !self.capture_trivia {
1087 return;
1088 }
1089 self.trivia.push(Trivia {
1090 kind,
1091 text: self.src[start..self.byte].to_string(),
1092 pos,
1093 start,
1094 end: self.byte,
1095 });
1096 }
1097
1098 fn error(&mut self, kind: LexErrorKind, pos: Pos) {
1099 self.errors.push(LexError { kind, pos });
1100 }
1101
1102 fn scan_tokens(&mut self) {
1103 while let Some(c) = self.peek() {
1104 let pos = self.pos();
1105 let start = self.byte;
1106 match c {
1107 ' ' | '\t' | '\n' | '\r' => {
1108 self.bump();
1109 }
1110 '(' => {
1111 self.bump();
1112 self.push(TokenKind::LParen, pos, start);
1113 }
1114 ')' => {
1115 self.bump();
1116 self.push(TokenKind::RParen, pos, start);
1117 }
1118 '[' => {
1119 self.bump();
1120 self.push(TokenKind::LBracket, pos, start);
1121 }
1122 ']' => {
1123 self.bump();
1124 self.push(TokenKind::RBracket, pos, start);
1125 }
1126 ',' => {
1127 self.bump();
1128 self.push(TokenKind::Comma, pos, start);
1129 }
1130 ';' => {
1131 self.bump();
1132 self.push(TokenKind::Semi, pos, start);
1133 }
1134 '`' => {
1135 self.bump();
1136 self.push(TokenKind::Backtick, pos, start);
1137 }
1138 '{' => {
1139 if self.peek_at(1) == Some('-') {
1140 self.block_comment(pos);
1141 } else {
1142 self.bump();
1143 self.push(TokenKind::LBrace, pos, start);
1144 }
1145 }
1146 '}' => {
1147 self.bump();
1148 self.push(TokenKind::RBrace, pos, start);
1149 }
1150 '#' if self.column == 1
1154 && self.peek_at(1).is_some_and(|c| c.is_ascii_lowercase()) =>
1155 {
1156 while self.peek().is_some_and(|c| c != '\n') {
1157 self.bump();
1158 }
1159 self.push_trivia(TriviaKind::CppDirective, pos, start);
1160 }
1161 '"' => self.string_lit(pos),
1162 '\'' => self.char_lit(pos),
1163 c if c.is_ascii_digit() => self.number(pos),
1164 c if is_ident_start(c) => self.identifier(pos),
1165 c if is_symbol_char(c) => self.operator(pos),
1166 _ => {
1167 self.bump();
1168 self.error(LexErrorKind::UnexpectedCharacter(c), pos);
1169 }
1170 }
1171 }
1172 }
1173
1174 fn block_comment(&mut self, pos: Pos) {
1177 let start = self.byte;
1178 self.bump(); self.bump(); let mut depth = 1usize;
1181 while depth > 0 {
1182 match self.peek() {
1183 None => {
1184 self.error(LexErrorKind::UnterminatedBlockComment, pos);
1185 self.push_trivia(TriviaKind::BlockComment, pos, start);
1186 return;
1187 }
1188 Some('{') if self.peek_at(1) == Some('-') => {
1189 self.bump();
1190 self.bump();
1191 depth += 1;
1192 }
1193 Some('-') if self.peek_at(1) == Some('}') => {
1194 self.bump();
1195 self.bump();
1196 depth -= 1;
1197 }
1198 Some(_) => {
1199 self.bump();
1200 }
1201 }
1202 }
1203 self.push_trivia(TriviaKind::BlockComment, pos, start);
1204 }
1205
1206 fn string_lit(&mut self, pos: Pos) {
1207 let start = self.byte;
1208 self.bump(); let mut value = String::new();
1210 loop {
1211 match self.peek() {
1212 None | Some('\n') => {
1213 self.error(LexErrorKind::UnterminatedStringLiteral, pos);
1214 break;
1215 }
1216 Some('"') => {
1217 self.bump();
1218 break;
1219 }
1220 Some('\\') => {
1221 let escape_pos = self.pos();
1222 self.bump();
1223 match self.peek() {
1224 Some(w) if w.is_whitespace() => {
1226 while self.peek().is_some_and(|c| c.is_whitespace()) {
1227 self.bump();
1228 }
1229 if self.peek() == Some('\\') {
1230 self.bump();
1231 } else {
1232 self.error(LexErrorKind::UnterminatedStringGap, pos);
1233 break;
1234 }
1235 }
1236 Some(e) => {
1237 self.bump();
1238 match unescape(e) {
1239 Some(c) => value.push(c),
1240 None => {
1241 self.error(LexErrorKind::InvalidEscapeSequence(e), escape_pos);
1242 value.push(e);
1243 }
1244 }
1245 }
1246 None => {
1247 self.error(LexErrorKind::UnterminatedStringLiteral, pos);
1248 break;
1249 }
1250 }
1251 }
1252 Some(c) => {
1253 self.bump();
1254 value.push(c);
1255 }
1256 }
1257 }
1258 self.push(TokenKind::StringLit(value), pos, start);
1259 }
1260
1261 fn char_lit(&mut self, pos: Pos) {
1265 let start = self.byte;
1266 let mut j = self.i + 1;
1268 let mut escaped = false;
1269 let mut ok = false;
1270 let window_end = (self.i + 12).min(self.chars.len());
1271 while j < window_end {
1272 match self.chars[j] {
1273 '\\' if !escaped => escaped = true,
1274 '\'' if !escaped => {
1275 ok = j > self.i + 1;
1276 break;
1277 }
1278 '\n' => break,
1279 _ => escaped = false,
1280 }
1281 j += 1;
1282 }
1283 if !ok {
1284 self.bump();
1285 self.error(LexErrorKind::StraySingleQuote, pos);
1286 return;
1287 }
1288 self.bump(); let mut value = String::new();
1290 while self.peek() != Some('\'') {
1291 let c = self.bump_after_peek();
1292 if c == '\\' {
1293 let escape_pos = Pos {
1294 line: self.line,
1295 column: self.column.saturating_sub(1),
1296 };
1297 if let Some(e) = self.bump() {
1298 match unescape(e) {
1299 Some(c) => value.push(c),
1300 None => {
1301 self.error(LexErrorKind::InvalidEscapeSequence(e), escape_pos);
1302 value.push(e);
1303 }
1304 }
1305 }
1306 } else {
1307 value.push(c);
1308 }
1309 }
1310 self.bump(); if value.chars().count() != 1 {
1312 self.error(LexErrorKind::CharacterLiteralWrongLength, pos);
1313 }
1314 self.push(TokenKind::CharLit(value), pos, start);
1315 }
1316
1317 fn number(&mut self, pos: Pos) {
1318 let start = self.byte;
1319 let mut text = String::new();
1320 if self.peek() == Some('0') && matches!(self.peek_at(1), Some('x' | 'X')) {
1321 text.push(self.bump_after_peek());
1322 text.push(self.bump_after_peek());
1323 let mut has_hex_digit = false;
1324 while self
1325 .peek()
1326 .is_some_and(|c| c.is_ascii_hexdigit() || c == '_')
1327 {
1328 let c = self.bump_after_peek();
1329 has_hex_digit |= c.is_ascii_hexdigit();
1330 text.push(c);
1331 }
1332 if !has_hex_digit {
1333 self.error(LexErrorKind::HexLiteralMissingDigits, pos);
1334 }
1335 self.push(TokenKind::IntLit(text), pos, start);
1336 return;
1337 }
1338 while self.peek().is_some_and(|c| c.is_ascii_digit() || c == '_') {
1339 text.push(self.bump_after_peek());
1340 }
1341 let mut decimal = false;
1342 if self.peek() == Some('.') && self.peek_at(1).is_some_and(|c| c.is_ascii_digit()) {
1344 decimal = true;
1345 text.push(self.bump_after_peek());
1346 while self.peek().is_some_and(|c| c.is_ascii_digit() || c == '_') {
1347 text.push(self.bump_after_peek());
1348 }
1349 }
1350 if matches!(self.peek(), Some('e' | 'E')) {
1351 decimal = true;
1352 if self.peek_at(1).is_some_and(|c| c.is_ascii_digit())
1353 || (matches!(self.peek_at(1), Some('+' | '-'))
1354 && self.peek_at(2).is_some_and(|c| c.is_ascii_digit()))
1355 {
1356 text.push(self.bump_after_peek());
1357 if matches!(self.peek(), Some('+' | '-')) {
1358 text.push(self.bump_after_peek());
1359 }
1360 while self.peek().is_some_and(|c| c.is_ascii_digit()) {
1361 text.push(self.bump_after_peek());
1362 }
1363 } else {
1364 text.push(self.bump_after_peek());
1365 if matches!(self.peek(), Some('+' | '-')) {
1366 text.push(self.bump_after_peek());
1367 }
1368 self.error(LexErrorKind::DecimalExponentMissingDigits, pos);
1369 }
1370 }
1371 if decimal {
1372 self.push(TokenKind::DecimalLit(text), pos, start);
1373 } else {
1374 self.push(TokenKind::IntLit(text), pos, start);
1375 }
1376 }
1377
1378 fn identifier(&mut self, pos: Pos) {
1381 let start = self.byte;
1382 let mut segments: Vec<String> = Vec::new();
1383 loop {
1384 let mut seg = String::new();
1385 while self.peek().is_some_and(is_ident_char) {
1386 seg.push(self.bump_after_peek());
1387 }
1388 let seg_is_upper = seg.chars().next().is_some_and(|c| c.is_uppercase());
1389 segments.push(seg);
1390 if seg_is_upper
1393 && self.peek() == Some('.')
1394 && self.peek_at(1).is_some_and(is_ident_start)
1395 {
1396 self.bump(); continue;
1398 }
1399 break;
1400 }
1401 let name = segments
1402 .pop()
1403 .expect("identifier loop always records at least one segment");
1404 let qualifier = if segments.is_empty() {
1405 None
1406 } else {
1407 Some(segments.join(".").into())
1408 };
1409 let tok = if name.chars().next().is_some_and(|c| c.is_uppercase()) {
1410 TokenKind::UpperId {
1411 qualifier,
1412 name: name.into(),
1413 }
1414 } else {
1415 TokenKind::LowerId {
1416 qualifier,
1417 name: name.into(),
1418 }
1419 };
1420 self.push(tok, pos, start);
1421 }
1422
1423 fn operator(&mut self, pos: Pos) {
1424 let start = self.i;
1425 let byte_start = self.byte;
1426 while self.peek().is_some_and(is_symbol_char) {
1427 self.bump();
1430 }
1431 let text: String = self.chars[start..self.i].iter().collect();
1432 if text.len() >= 2 && text.chars().all(|c| c == '-') {
1435 while self.peek().is_some_and(|c| c != '\n') {
1436 self.bump();
1437 }
1438 self.push_trivia(TriviaKind::LineComment, pos, byte_start);
1439 return;
1440 }
1441 self.push(TokenKind::Op(text.into()), pos, byte_start);
1442 }
1443}
1444
1445const fn unescape(c: char) -> Option<char> {
1446 match c {
1447 'n' => Some('\n'),
1448 't' => Some('\t'),
1449 'r' => Some('\r'),
1450 '0' => Some('\0'),
1451 'a' => Some('\u{07}'),
1452 'b' => Some('\u{08}'),
1453 'f' => Some('\u{0c}'),
1454 'v' => Some('\u{0b}'),
1455 '"' => Some('"'),
1456 '\'' => Some('\''),
1457 '\\' => Some('\\'),
1458 '&' => Some('&'),
1459 '1'..='9' | 'o' | 'x' | 'A'..='Z' => Some(c),
1465 _ => None,
1466 }
1467}
1468
1469impl TokenKind {
1470 #[must_use]
1473 pub const fn keyword(&self) -> Option<&str> {
1474 match self {
1475 Self::LowerId {
1476 qualifier: None,
1477 name,
1478 } => Some(name.as_str()),
1479 _ => None,
1480 }
1481 }
1482
1483 #[must_use]
1484 pub fn is_keyword(&self, kw: &str) -> bool {
1485 self.keyword() == Some(kw)
1486 }
1487
1488 #[must_use]
1489 pub fn is_op(&self, op: &str) -> bool {
1490 matches!(self, Self::Op(o) if o.as_str() == op)
1491 }
1492}
1493
1494#[cfg(test)]
1496mod tests {
1497 use super::*;
1498
1499 fn toks(src: &str) -> Vec<TokenKind> {
1500 let (tokens, errors) = lex(src).into_parts();
1501 assert!(errors.is_empty(), "lex errors: {errors:?}");
1502 tokens.into_iter().map(|t| t.kind).collect()
1503 }
1504
1505 fn lex_error_messages(src: &str) -> Vec<String> {
1506 let (_, errors) = lex(src).into_parts();
1507 errors.into_iter().map(|e| e.to_string()).collect()
1508 }
1509
1510 fn lower(name: &str) -> TokenKind {
1511 TokenKind::LowerId {
1512 qualifier: None,
1513 name: name.into(),
1514 }
1515 }
1516
1517 fn upper(name: &str) -> TokenKind {
1518 TokenKind::UpperId {
1519 qualifier: None,
1520 name: name.into(),
1521 }
1522 }
1523
1524 #[test]
1525 fn identifier_as_ref_str() {
1526 let identifier = Identifier::from("value");
1527 let operator = Operator::from("+");
1528 let module = ModuleName::from("DA.Map");
1529
1530 assert_eq!(identifier.as_ref(), "value");
1531 assert_eq!(operator.as_ref(), "+");
1532 assert_eq!(module.as_ref(), "DA.Map");
1533 }
1534
1535 #[test]
1536 fn token_kind_display_is_normalized_not_lossless_source() {
1537 assert_eq!(TokenKind::LBrace.to_string(), "{");
1540 assert_eq!(TokenKind::VLBrace.to_string(), "{");
1541 assert_eq!(TokenKind::Semi.to_string(), ";");
1542 assert_eq!(TokenKind::VSemi.to_string(), ";");
1543
1544 assert_eq!(
1545 TokenKind::UpperId {
1546 qualifier: Some("DA.Map".into()),
1547 name: "Map".into(),
1548 }
1549 .to_string(),
1550 "DA.Map.Map"
1551 );
1552 assert_eq!(TokenKind::Op("->".into()).to_string(), "->");
1553 assert_eq!(
1554 TokenKind::StringLit("line\n".into()).to_string(),
1555 "\"line\\n\""
1556 );
1557 assert_eq!(TokenKind::CharLit("'".into()).to_string(), "'\\''");
1558 }
1559
1560 #[test]
1561 fn line_comment_with_keywords_produces_no_tokens() {
1562 assert_eq!(toks("-- electing to exercise the option"), vec![]);
1563 assert_eq!(toks("--- template Foo"), vec![]);
1564 }
1565
1566 #[test]
1567 fn arrow_like_operator_is_not_comment() {
1568 assert_eq!(
1569 toks("a --> b"),
1570 vec![lower("a"), TokenKind::Op("-->".into()), lower("b")]
1571 );
1572 }
1573
1574 #[test]
1575 fn nested_block_comment() {
1576 assert_eq!(toks("{- outer {- inner -} still -} x"), vec![lower("x")]);
1577 }
1578
1579 #[test]
1580 fn string_with_keyword_and_escapes() {
1581 assert_eq!(
1582 toks(r#""template \"Foo\" \n""#),
1583 vec![TokenKind::StringLit("template \"Foo\" \n".into())]
1584 );
1585 }
1586
1587 #[test]
1588 fn qualified_identifiers() {
1589 assert_eq!(
1590 toks("DA.Set.fromList Map.Map foo"),
1591 vec![
1592 TokenKind::LowerId {
1593 qualifier: Some("DA.Set".into()),
1594 name: "fromList".into()
1595 },
1596 TokenKind::UpperId {
1597 qualifier: Some("Map".into()),
1598 name: "Map".into()
1599 },
1600 lower("foo"),
1601 ]
1602 );
1603 }
1604
1605 #[test]
1606 fn numbers() {
1607 assert_eq!(
1608 toks("42 1.5 0x1F 2e3 1_000"),
1609 vec![
1610 TokenKind::IntLit("42".into()),
1611 TokenKind::DecimalLit("1.5".into()),
1612 TokenKind::IntLit("0x1F".into()),
1613 TokenKind::DecimalLit("2e3".into()),
1614 TokenKind::IntLit("1_000".into()),
1615 ]
1616 );
1617 }
1618
1619 #[test]
1620 fn malformed_hex_literal_reports_error() {
1621 assert_eq!(
1622 lex_error_messages("0x 0x_"),
1623 vec![
1624 "hex literal requires at least one digit",
1625 "hex literal requires at least one digit",
1626 ]
1627 );
1628 }
1629
1630 #[test]
1631 fn malformed_decimal_exponent_reports_error() {
1632 assert_eq!(
1633 lex_error_messages("1e 1e+ 1e-"),
1634 vec![
1635 "decimal exponent requires at least one digit",
1636 "decimal exponent requires at least one digit",
1637 "decimal exponent requires at least one digit",
1638 ]
1639 );
1640 }
1641
1642 #[test]
1643 fn enum_from_to_is_not_decimal() {
1644 assert_eq!(
1645 toks("[1..5]"),
1646 vec![
1647 TokenKind::LBracket,
1648 TokenKind::IntLit("1".into()),
1649 TokenKind::Op("..".into()),
1650 TokenKind::IntLit("5".into()),
1651 TokenKind::RBracket,
1652 ]
1653 );
1654 }
1655
1656 #[test]
1657 fn primes_stay_in_identifier_and_char_lit_works() {
1658 assert_eq!(
1659 toks(r"foo' 'a' '\n'"),
1660 vec![
1661 lower("foo'"),
1662 TokenKind::CharLit("a".into()),
1663 TokenKind::CharLit("\n".into())
1664 ]
1665 );
1666 }
1667
1668 #[test]
1669 fn invalid_escape_sequences_report_errors() {
1670 assert_eq!(
1671 lex_error_messages(r#""\q" '\q'"#),
1672 vec!["invalid escape sequence \\q", "invalid escape sequence \\q"]
1673 );
1674 }
1675
1676 #[test]
1677 fn multi_character_char_literal_reports_error() {
1678 let (tokens, errors) = lex("'ab'").into_parts();
1679 assert_eq!(
1680 tokens.iter().map(|t| t.kind.clone()).collect::<Vec<_>>(),
1681 vec![TokenKind::CharLit("ab".into())]
1682 );
1683 assert_eq!(
1684 errors.iter().map(|e| e.to_string()).collect::<Vec<_>>(),
1685 vec!["character literal must contain exactly one character".to_string()]
1686 );
1687 }
1688
1689 #[test]
1690 fn operators_and_punctuation() {
1691 assert_eq!(
1692 toks("x <- f (y, z) `div` 2"),
1693 vec![
1694 lower("x"),
1695 TokenKind::Op("<-".into()),
1696 lower("f"),
1697 TokenKind::LParen,
1698 lower("y"),
1699 TokenKind::Comma,
1700 lower("z"),
1701 TokenKind::RParen,
1702 TokenKind::Backtick,
1703 lower("div"),
1704 TokenKind::Backtick,
1705 TokenKind::IntLit("2".into()),
1706 ]
1707 );
1708 }
1709
1710 #[test]
1711 fn spans_are_one_based() {
1712 let (tokens, _) = lex("ab\n cd").into_parts();
1713 assert_eq!(tokens[0].pos, Pos { line: 1, column: 1 });
1714 assert_eq!(tokens[1].pos, Pos { line: 2, column: 3 });
1715 }
1716
1717 #[test]
1718 fn tab_advances_to_stop() {
1719 let (tokens, _) = lex("\tx").into_parts();
1720 assert_eq!(tokens[0].pos, Pos { line: 1, column: 9 });
1721 }
1722
1723 #[test]
1724 fn unterminated_string_is_error_not_hang() {
1725 let (_, errors) = lex("x = \"oops\ny").into_parts();
1726 assert_eq!(errors.len(), 1);
1727 }
1728
1729 #[test]
1730 fn unterminated_block_comment_is_error_not_hang() {
1731 let (_, errors) = lex("{- never closed").into_parts();
1732 assert_eq!(errors.len(), 1);
1733 }
1734
1735 fn trivia_of(src: &str) -> Vec<Trivia> {
1736 let (_, trivia, _) = lex_with_trivia(src).into_parts();
1737 trivia
1738 }
1739
1740 fn assert_round_trip(src: &str) {
1743 let (tokens, trivia, errors) = lex_with_trivia(src).into_parts();
1744 assert!(errors.is_empty(), "lex errors: {errors:?}");
1745 assert_eq!(
1746 render_lossless(src, &tokens, &trivia).as_deref(),
1747 Ok(src),
1748 "round trip failed for {src:?}"
1749 );
1750 }
1751
1752 #[test]
1753 fn line_comment_becomes_trivia_with_exact_text_and_span() {
1754 let src = "x = 1 -- electing to exercise\ny = 2\n";
1755 let trivia = trivia_of(src);
1756 assert_eq!(trivia.len(), 1);
1757 assert_eq!(trivia[0].kind, TriviaKind::LineComment);
1758 assert_eq!(trivia[0].text, "-- electing to exercise");
1759 assert_eq!(&src[trivia[0].start..trivia[0].end], trivia[0].text);
1760 assert_eq!(trivia[0].pos, Pos { line: 1, column: 7 });
1761 }
1762
1763 #[test]
1764 fn nested_block_comment_becomes_one_trivia() {
1765 let src = "{- outer {- inner -} still -} x";
1766 let trivia = trivia_of(src);
1767 assert_eq!(trivia.len(), 1);
1768 assert_eq!(trivia[0].kind, TriviaKind::BlockComment);
1769 assert_eq!(trivia[0].text, "{- outer {- inner -} still -}");
1770 }
1771
1772 #[test]
1773 fn unterminated_block_comment_still_yields_trivia_to_eof() {
1774 let (_, trivia, errors) = lex_with_trivia("x {- never closed").into_parts();
1775 assert_eq!(errors.len(), 1);
1776 assert_eq!(trivia.len(), 1);
1777 assert_eq!(trivia[0].text, "{- never closed");
1778 }
1779
1780 #[test]
1781 fn blank_lines_between_items_counted() {
1782 let src = "x = 1\n\n\ny = 2\n";
1783 let trivia = trivia_of(src);
1784 assert_eq!(trivia.len(), 1);
1785 assert_eq!(trivia[0].kind, TriviaKind::BlankLines(2));
1786 assert_eq!(trivia[0].pos, Pos { line: 2, column: 1 });
1787 }
1788
1789 #[test]
1790 fn blank_line_at_file_start_counted() {
1791 let trivia = trivia_of("\nx = 1\n");
1792 assert_eq!(trivia.len(), 1);
1793 assert_eq!(trivia[0].kind, TriviaKind::BlankLines(1));
1794 assert_eq!(trivia[0].pos, Pos { line: 1, column: 1 });
1795 }
1796
1797 #[test]
1798 fn blank_looking_lines_inside_block_comment_are_not_blank_trivia() {
1799 let src = "x = 1 {- a\n\nb -}\ny = 2\n";
1800 let trivia = trivia_of(src);
1801 assert_eq!(trivia.len(), 1, "{trivia:?}");
1802 assert_eq!(trivia[0].kind, TriviaKind::BlockComment);
1803 }
1804
1805 #[test]
1806 fn blank_line_between_comments_counted() {
1807 let src = "-- a\n\n-- b\nx = 1\n";
1808 let kinds: Vec<_> = trivia_of(src).into_iter().map(|t| t.kind).collect();
1809 assert_eq!(
1810 kinds,
1811 vec![
1812 TriviaKind::LineComment,
1813 TriviaKind::BlankLines(1),
1814 TriviaKind::LineComment,
1815 ]
1816 );
1817 }
1818
1819 #[test]
1820 fn cpp_directive_becomes_trivia() {
1821 let src = "#ifdef DAML_BIGNUMERIC\nx = 1\n#endif\n";
1822 let trivia = trivia_of(src);
1823 assert_eq!(trivia.len(), 2);
1824 assert!(trivia.iter().all(|t| t.kind == TriviaKind::CppDirective));
1825 assert_eq!(trivia[0].text, "#ifdef DAML_BIGNUMERIC");
1826 }
1827
1828 #[test]
1829 fn round_trip_is_byte_identical() {
1830 assert_round_trip("module M where\n\n-- doc\nf : Int -> Int\nf x = x + 1\n");
1831 assert_round_trip("x = \"tem\\\"plate \\n\" {- block {- nested -} -}\r\ny = 'a'\r\n");
1832 assert_round_trip("\tärger = [1..5] -- ütf\n");
1833 assert_round_trip("s = \"gap \\ \\ here\"\n");
1834 assert_round_trip("#ifdef X\nf = 0x1F\n#endif\n");
1835 assert_round_trip("\n\n \nf = 1 \n ");
1836 assert_round_trip("");
1837 }
1838
1839 #[test]
1840 fn render_lossless_detects_lost_bytes() {
1841 let src = "x = 1 -- comment\n";
1842 let (tokens, mut trivia, _) = lex_with_trivia(src).into_parts();
1843 trivia.clear(); assert!(render_lossless(src, &tokens, &trivia).is_err());
1845 }
1846
1847 #[test]
1848 fn unicode_identifier() {
1849 assert_eq!(
1850 toks("ärger = 1"),
1851 vec![
1852 lower("ärger"),
1853 TokenKind::Op("=".into()),
1854 TokenKind::IntLit("1".into())
1855 ]
1856 );
1857 let _ = upper("Ülf"); }
1859
1860 fn assert_lex_tokens_match(src: &str) {
1863 let (parse_tokens, parse_errors) = lex(src).into_parts();
1864 let (trivia_tokens, _, trivia_errors) = lex_with_trivia(src).into_parts();
1865 assert_eq!(parse_errors, trivia_errors, "lex errors differ for {src:?}");
1866 assert_eq!(
1867 parse_tokens.len(),
1868 trivia_tokens.len(),
1869 "token count for {src:?}"
1870 );
1871 for (a, b) in parse_tokens.iter().zip(trivia_tokens.iter()) {
1872 assert_eq!(a.kind, b.kind, "token kind for {src:?}");
1873 assert_eq!(a.pos, b.pos, "token pos for {src:?}");
1874 assert_eq!(a.start, b.start, "token start for {src:?}");
1875 assert_eq!(a.end, b.end, "token end for {src:?}");
1876 }
1877 }
1878
1879 #[test]
1880 fn lex_and_lex_with_trivia_emit_identical_tokens() {
1881 assert_lex_tokens_match("x = 1 -- electing to exercise\ny = 2\n");
1882 assert_lex_tokens_match("module M where\n\n-- doc\nf : Int -> Int\nf x = x + 1\n");
1883 assert_lex_tokens_match("{- outer {- inner -} still -} x");
1884 assert_lex_tokens_match("#ifdef DAML_BIGNUMERIC\nx = 1\n#endif\n");
1885 assert_lex_tokens_match("\n\n \nf = 1 \n ");
1886 }
1887
1888 #[test]
1889 fn lex_with_trivia_preserves_lossless_comment_and_blank_line_render() {
1890 let sources = [
1891 "x = 1 -- electing to exercise\ny = 2\n",
1892 "x = 1\n\n\ny = 2\n",
1893 "-- a\n\n-- b\nx = 1\n",
1894 ];
1895 for src in sources {
1896 let (tokens, trivia, errors) = lex_with_trivia(src).into_parts();
1897 assert!(errors.is_empty(), "lex errors for {src:?}: {errors:?}");
1898 assert_eq!(
1899 render_lossless(src, &tokens, &trivia).as_deref(),
1900 Ok(src),
1901 "lossless render failed for {src:?}"
1902 );
1903 }
1904 }
1905}