1#![allow(clippy::result_large_err)]
4
5use std::sync::Arc;
6
7use miette::NamedSource;
8
9use cljrs_types::error::{CljxError, CljxResult};
10use cljrs_types::span::Span;
11
12use crate::chars::{is_symbol_char, is_symbol_start};
13use crate::token::Token;
14
15pub struct Lexer {
18 source: Arc<String>,
19 file: Arc<String>,
20 pos: usize, line: u32, col: u32, }
24
25impl Lexer {
26 pub fn new(source: String, file: String) -> Self {
27 Self {
28 source: Arc::new(source),
29 file: Arc::new(file),
30 pos: 0,
31 line: 1,
32 col: 1,
33 }
34 }
35
36 pub fn source(&self) -> &Arc<String> {
39 &self.source
40 }
41
42 pub fn file(&self) -> &Arc<String> {
43 &self.file
44 }
45
46 fn peek(&self) -> Option<char> {
49 self.source[self.pos..].chars().next()
50 }
51
52 fn peek_next(&self) -> Option<char> {
53 let mut chars = self.source[self.pos..].chars();
54 chars.next(); chars.next()
56 }
57
58 fn advance(&mut self) -> Option<char> {
59 let ch = self.peek()?;
60 self.pos += ch.len_utf8();
61 if ch == '\n' {
62 self.line += 1;
63 self.col = 1;
64 } else {
65 self.col += ch.len_utf8() as u32;
66 }
67 Some(ch)
68 }
69
70 fn span_from(&self, start_pos: usize, start_line: u32, start_col: u32) -> Span {
71 Span::new(
72 Arc::clone(&self.file),
73 start_pos,
74 self.pos,
75 start_line,
76 start_col,
77 )
78 }
79
80 fn make_error(&self, msg: impl Into<String>, span: Span) -> CljxError {
81 CljxError::ReadError {
82 message: msg.into(),
83 span: Some(miette::SourceSpan::from(span)),
84 src: NamedSource::new((*self.file).clone(), (*self.source).clone()),
85 }
86 }
87
88 fn read_symbol_chars(&mut self) -> String {
91 let mut buf = String::new();
92 while let Some(ch) = self.peek() {
93 if is_symbol_char(ch) {
94 buf.push(ch);
95 self.advance();
96 } else {
97 break;
98 }
99 }
100 buf
101 }
102
103 fn skip_whitespace_and_comments(&mut self) {
106 loop {
107 match self.peek() {
108 Some('#') if self.pos == 0 => {
110 if self.peek_next() == Some('!') {
111 while let Some(ch) = self.advance() {
113 if ch == '\n' {
114 break;
115 }
116 }
117 } else {
118 break; }
120 }
121 Some(' ') | Some('\t') | Some('\r') | Some('\n') | Some(',') => {
122 self.advance();
123 }
124 Some(';') => {
125 while let Some(ch) = self.advance() {
126 if ch == '\n' || ch == '\r' {
127 break;
128 }
129 }
130 }
131 _ => break,
132 }
133 }
134 }
135
136 fn lex_unquote(
139 &mut self,
140 start_pos: usize,
141 start_line: u32,
142 start_col: u32,
143 ) -> CljxResult<(Token, Span)> {
144 self.advance(); if self.peek() == Some('@') {
146 self.advance();
147 Ok((
148 Token::UnquoteSplice,
149 self.span_from(start_pos, start_line, start_col),
150 ))
151 } else {
152 Ok((
153 Token::Unquote,
154 self.span_from(start_pos, start_line, start_col),
155 ))
156 }
157 }
158
159 fn lex_hash(
162 &mut self,
163 start_pos: usize,
164 start_line: u32,
165 start_col: u32,
166 ) -> CljxResult<(Token, Span)> {
167 self.advance(); match self.peek() {
169 Some('(') => {
170 self.advance();
171 Ok((
172 Token::HashFn,
173 self.span_from(start_pos, start_line, start_col),
174 ))
175 }
176 Some('{') => {
177 self.advance();
178 Ok((
179 Token::HashSet,
180 self.span_from(start_pos, start_line, start_col),
181 ))
182 }
183 Some('\'') => {
184 self.advance();
185 Ok((
186 Token::HashVar,
187 self.span_from(start_pos, start_line, start_col),
188 ))
189 }
190 Some('_') => {
191 self.advance();
192 Ok((
193 Token::HashDiscard,
194 self.span_from(start_pos, start_line, start_col),
195 ))
196 }
197 Some('"') => self.lex_regex(start_pos, start_line, start_col),
198 Some('?') => {
199 self.advance(); if self.peek() == Some('@') {
201 self.advance();
202 Ok((
203 Token::ReaderCondSplice,
204 self.span_from(start_pos, start_line, start_col),
205 ))
206 } else {
207 Ok((
208 Token::ReaderCond,
209 self.span_from(start_pos, start_line, start_col),
210 ))
211 }
212 }
213 Some('#') => self.lex_symbolic(start_pos, start_line, start_col),
214 Some(':') => self.lex_namespaced_map(start_pos, start_line, start_col),
215 Some(c) if is_symbol_start(c) => {
216 let name = self.read_symbol_chars();
217 Ok((
218 Token::TaggedLiteral(name),
219 self.span_from(start_pos, start_line, start_col),
220 ))
221 }
222 other => {
223 let span = self.span_from(start_pos, start_line, start_col);
224 Err(self.make_error(format!("unknown # dispatch character: {:?}", other), span))
225 }
226 }
227 }
228
229 fn lex_regex(
230 &mut self,
231 start_pos: usize,
232 start_line: u32,
233 start_col: u32,
234 ) -> CljxResult<(Token, Span)> {
235 self.advance(); let mut buf = String::new();
237 loop {
238 match self.advance() {
239 None => {
240 let span = self.span_from(start_pos, start_line, start_col);
241 return Err(self.make_error("unterminated regex literal", span));
242 }
243 Some('"') => break,
244 Some('\\') => {
245 buf.push('\\');
247 match self.advance() {
248 Some(c) => buf.push(c),
249 None => {
250 let span = self.span_from(start_pos, start_line, start_col);
251 return Err(self.make_error("unterminated regex literal", span));
252 }
253 }
254 }
255 Some(c) => buf.push(c),
256 }
257 }
258 Ok((
259 Token::Regex(buf),
260 self.span_from(start_pos, start_line, start_col),
261 ))
262 }
263
264 fn lex_symbolic(
265 &mut self,
266 start_pos: usize,
267 start_line: u32,
268 start_col: u32,
269 ) -> CljxResult<(Token, Span)> {
270 self.advance(); let name = self.read_symbol_chars();
272 match name.as_str() {
273 "Inf" | "-Inf" | "NaN" => Ok((
274 Token::Symbolic(name),
275 self.span_from(start_pos, start_line, start_col),
276 )),
277 _ => {
278 let span = self.span_from(start_pos, start_line, start_col);
279 Err(self.make_error(format!("unknown symbolic value: ##{name}"), span))
280 }
281 }
282 }
283
284 fn lex_string(
287 &mut self,
288 start_pos: usize,
289 start_line: u32,
290 start_col: u32,
291 ) -> CljxResult<(Token, Span)> {
292 self.advance(); let mut buf = String::new();
294 loop {
295 match self.advance() {
296 None => {
297 let span = self.span_from(start_pos, start_line, start_col);
298 return Err(self.make_error("unterminated string literal", span));
299 }
300 Some('"') => break,
301 Some('\\') => match self.advance() {
302 Some('n') => buf.push('\n'),
303 Some('t') => buf.push('\t'),
304 Some('r') => buf.push('\r'),
305 Some('b') => buf.push('\x08'),
306 Some('f') => buf.push('\x0C'),
307 Some('\\') => buf.push('\\'),
308 Some('"') => buf.push('"'),
309 Some('u') => {
310 let ch = self.read_unicode_escape(start_pos, start_line, start_col)?;
311 buf.push(ch);
312 }
313 Some(c) => {
314 let span = self.span_from(start_pos, start_line, start_col);
315 return Err(self.make_error(format!("unknown string escape: \\{c}"), span));
316 }
317 None => {
318 let span = self.span_from(start_pos, start_line, start_col);
319 return Err(self.make_error("unterminated string literal", span));
320 }
321 },
322 Some(c) => buf.push(c),
323 }
324 }
325 Ok((
326 Token::Str(buf),
327 self.span_from(start_pos, start_line, start_col),
328 ))
329 }
330
331 fn read_unicode_escape(
333 &mut self,
334 start_pos: usize,
335 start_line: u32,
336 start_col: u32,
337 ) -> CljxResult<char> {
338 let mut hex = String::with_capacity(4);
339 for _ in 0..4 {
340 match self.advance() {
341 Some(c) if c.is_ascii_hexdigit() => hex.push(c),
342 Some(c) => {
343 let span = self.span_from(start_pos, start_line, start_col);
344 return Err(self.make_error(
345 format!("invalid \\u escape: expected hex digit, got {c:?}"),
346 span,
347 ));
348 }
349 None => {
350 let span = self.span_from(start_pos, start_line, start_col);
351 return Err(self.make_error("unterminated \\u escape", span));
352 }
353 }
354 }
355 let code = u32::from_str_radix(&hex, 16).unwrap();
356 char::from_u32(code).ok_or_else(|| {
357 let span = self.span_from(start_pos, start_line, start_col);
358 self.make_error(format!("invalid unicode code point: \\u{hex}"), span)
359 })
360 }
361
362 fn lex_char_literal(
365 &mut self,
366 start_pos: usize,
367 start_line: u32,
368 start_col: u32,
369 ) -> CljxResult<(Token, Span)> {
370 self.advance(); let rest_start = self.pos;
374 let rest: String = self.source[rest_start..]
375 .chars()
376 .take_while(|&c| c.is_alphanumeric() || c == '-')
377 .collect();
378
379 let ch = match rest.as_str() {
380 "newline" => {
381 self.pos += "newline".len();
382 self.col += "newline".len() as u32;
383 '\n'
384 }
385 "space" => {
386 self.pos += "space".len();
387 self.col += "space".len() as u32;
388 ' '
389 }
390 "tab" => {
391 self.pos += "tab".len();
392 self.col += "tab".len() as u32;
393 '\t'
394 }
395 "backspace" => {
396 self.pos += "backspace".len();
397 self.col += "backspace".len() as u32;
398 '\x08'
399 }
400 "formfeed" => {
401 self.pos += "formfeed".len();
402 self.col += "formfeed".len() as u32;
403 '\x0C'
404 }
405 "return" => {
406 self.pos += "return".len();
407 self.col += "return".len() as u32;
408 '\r'
409 }
410 _ if rest.starts_with('u') && rest.len() >= 5 => {
411 let hex_part = &rest[1..5];
413 if hex_part.chars().all(|c| c.is_ascii_hexdigit()) {
414 let code = u32::from_str_radix(hex_part, 16).unwrap();
415 let c = char::from_u32(code).ok_or_else(|| {
416 let span = self.span_from(start_pos, start_line, start_col);
417 self.make_error(
418 format!("invalid unicode code point in char literal: \\u{hex_part}"),
419 span,
420 )
421 })?;
422 self.pos += 5;
424 self.col += 5;
425 c
426 } else {
427 let span = self.span_from(start_pos, start_line, start_col);
428 return Err(self.make_error(format!("unknown character name: {rest}"), span));
429 }
430 }
431 _ if rest.starts_with('o') && rest.len() > 1 => {
432 let digits = &rest[1..];
433 if digits.is_empty()
434 || digits.len() > 3
435 || !digits.chars().all(|c| matches!(c, '0'..='7'))
436 {
437 let span = self.span_from(start_pos, start_line, start_col);
438 return Err(self.make_error(format!("invalid octal character: {rest}"), span));
439 }
440 let code = u32::from_str_radix(digits, 8).unwrap();
441 if code > 0o377 {
442 let span = self.span_from(start_pos, start_line, start_col);
443 return Err(
444 self.make_error(format!("octal character out of range: {rest}"), span)
445 );
446 }
447 self.pos += rest.len();
448 self.col += rest.len() as u32;
449 char::from_u32(code).unwrap()
450 }
451 _ if rest.len() == 1 => {
452 let c = self.source[rest_start..].chars().next().unwrap();
454 self.pos += c.len_utf8();
455 self.col += c.len_utf8() as u32;
456 c
457 }
458 _ if rest.is_empty() => {
459 match self.source[rest_start..].chars().next() {
461 Some(c) => {
462 self.pos += c.len_utf8();
463 self.col += c.len_utf8() as u32;
464 c
465 }
466 None => {
467 let span = self.span_from(start_pos, start_line, start_col);
468 return Err(self.make_error("unexpected end of file after \\", span));
469 }
470 }
471 }
472 _ => {
473 let span = self.span_from(start_pos, start_line, start_col);
474 return Err(self.make_error(format!("unknown character name: {rest}"), span));
475 }
476 };
477
478 Ok((
479 Token::Char(ch),
480 self.span_from(start_pos, start_line, start_col),
481 ))
482 }
483
484 fn lex_namespaced_map(
488 &mut self,
489 start_pos: usize,
490 start_line: u32,
491 start_col: u32,
492 ) -> CljxResult<(Token, Span)> {
493 self.advance(); let auto = self.peek() == Some(':');
495 if auto {
496 self.advance(); }
498 let text = self.read_symbol_chars();
499 let ns = crate::namespaced_map::MapNs::parse(&text, auto).map_err(|msg| {
500 let span = self.span_from(start_pos, start_line, start_col);
501 self.make_error(msg, span)
502 })?;
503 self.skip_whitespace_only();
506 if self.peek() != Some('{') {
507 let span = self.span_from(start_pos, start_line, start_col);
508 return Err(self.make_error("namespaced map literal must be followed by a map", span));
509 }
510 Ok((
511 Token::NamespacedMap(ns),
512 self.span_from(start_pos, start_line, start_col),
513 ))
514 }
515
516 fn skip_whitespace_only(&mut self) {
518 while let Some(ch) = self.peek() {
519 if ch.is_whitespace() || ch == ',' {
520 self.advance();
521 } else {
522 break;
523 }
524 }
525 }
526
527 fn lex_keyword(
530 &mut self,
531 start_pos: usize,
532 start_line: u32,
533 start_col: u32,
534 ) -> CljxResult<(Token, Span)> {
535 self.advance(); if self.peek() == Some(':') {
537 self.advance(); let name = self.read_symbol_chars();
539 if name.is_empty() {
540 let span = self.span_from(start_pos, start_line, start_col);
541 return Err(self.make_error("empty auto-resolved keyword", span));
542 }
543 Ok((
544 Token::AutoKeyword(name),
545 self.span_from(start_pos, start_line, start_col),
546 ))
547 } else {
548 let name = self.read_symbol_chars();
549 if name.is_empty() {
550 let span = self.span_from(start_pos, start_line, start_col);
551 return Err(self.make_error("empty keyword", span));
552 }
553 Ok((
554 Token::Keyword(name),
555 self.span_from(start_pos, start_line, start_col),
556 ))
557 }
558 }
559
560 fn lex_symbol(
563 &mut self,
564 start_pos: usize,
565 start_line: u32,
566 start_col: u32,
567 ) -> CljxResult<(Token, Span)> {
568 let mut name = self.read_symbol_chars();
569
570 if self.peek() == Some('@') {
576 let version_candidate = self.peek_version_hash();
577 if let Some(hash) = version_candidate {
578 self.advance(); for _ in 0..hash.len() {
580 self.advance();
581 }
582 name.push('@');
583 name.push_str(&hash);
584 }
585 }
586
587 let tok = match name.as_str() {
588 "nil" => Token::Nil,
589 "true" => Token::Bool(true),
590 "false" => Token::Bool(false),
591 _ => Token::Symbol(name),
592 };
593 Ok((tok, self.span_from(start_pos, start_line, start_col)))
594 }
595
596 fn peek_version_hash(&self) -> Option<String> {
602 let at_byte = self.pos + 1; let rest = &self.source[at_byte..];
605 let hash: String = rest
606 .chars()
607 .take(40)
608 .take_while(|c| c.is_ascii_hexdigit())
609 .collect();
610 if hash.len() >= 7 {
611 let after = rest[hash.len()..].chars().next();
613 let is_delimited = after.is_none_or(|c| !c.is_ascii_hexdigit());
614 if is_delimited {
615 return Some(hash);
616 }
617 }
618 None
619 }
620
621 fn lex_number(
624 &mut self,
625 start_pos: usize,
626 start_line: u32,
627 start_col: u32,
628 ) -> CljxResult<(Token, Span)> {
629 let negative = match self.peek() {
631 Some('-') => {
632 self.advance();
633 true
634 }
635 Some('+') => {
636 self.advance();
637 false
638 }
639 _ => false,
640 };
641 let sign_str = if negative { "-" } else { "" };
642
643 let mut int_part = String::new();
645 while let Some(c) = self.peek() {
646 if c.is_ascii_digit() {
647 int_part.push(c);
648 self.advance();
649 } else {
650 break;
651 }
652 }
653
654 if int_part == "0" && matches!(self.peek(), Some('x') | Some('X')) {
656 self.advance(); let mut hex = String::new();
658 while let Some(c) = self.peek() {
659 if c.is_ascii_hexdigit() {
660 hex.push(c);
661 self.advance();
662 } else {
663 break;
664 }
665 }
666 if hex.is_empty() {
667 let span = self.span_from(start_pos, start_line, start_col);
668 return Err(self.make_error("expected hex digits after 0x", span));
669 }
670 let value = u128::from_str_radix(&hex, 16).unwrap_or(u128::MAX);
671 let span = self.span_from(start_pos, start_line, start_col);
672 return if negative {
673 if value <= (i64::MAX as u128) + 1 {
675 Ok((Token::Int(0i64.wrapping_sub(value as i64)), span))
676 } else {
677 Ok((Token::BigInt(format!("-{value}")), span))
679 }
680 } else if value <= i64::MAX as u128 {
681 Ok((Token::Int(value as i64), span))
682 } else {
683 Ok((Token::BigInt(value.to_string()), span))
684 };
685 }
686
687 if matches!(self.peek(), Some('r') | Some('R')) {
689 let radix: u32 = int_part.parse().unwrap_or(0);
690 self.advance(); let mut digits = String::new();
692 while let Some(c) = self.peek() {
693 if c.is_ascii_alphanumeric() {
694 digits.push(c);
695 self.advance();
696 } else {
697 break;
698 }
699 }
700 let mut value: u128 = 0;
701 for c in digits.chars() {
702 let d = c.to_digit(radix).ok_or_else(|| {
703 let span = self.span_from(start_pos, start_line, start_col);
704 self.make_error(format!("invalid digit {c:?} for radix {radix}"), span)
705 })?;
706 value = value.wrapping_mul(radix as u128).wrapping_add(d as u128);
707 }
708 if negative {
709 if value <= (i64::MAX as u128) + 1 {
711 let signed = -(value as i64);
712 return Ok((
713 Token::Int(signed),
714 self.span_from(start_pos, start_line, start_col),
715 ));
716 } else {
717 return Ok((
719 Token::BigInt(format!("-{value}")),
720 self.span_from(start_pos, start_line, start_col),
721 ));
722 }
723 } else if value <= i64::MAX as u128 {
724 return Ok((
725 Token::Int(value as i64),
726 self.span_from(start_pos, start_line, start_col),
727 ));
728 } else {
729 return Ok((
730 Token::BigInt(value.to_string()),
731 self.span_from(start_pos, start_line, start_col),
732 ));
733 }
734 }
735
736 if self.peek() == Some('N') {
738 self.advance();
739 return Ok((
740 Token::BigInt(format!("{sign_str}{int_part}")),
741 self.span_from(start_pos, start_line, start_col),
742 ));
743 }
744
745 if self.peek() == Some('M') {
747 self.advance();
748 return Ok((
749 Token::BigDecimal(format!("{sign_str}{int_part}")),
750 self.span_from(start_pos, start_line, start_col),
751 ));
752 }
753
754 if matches!(self.peek(), Some('.') | Some('e') | Some('E')) {
756 let mut raw = format!("{sign_str}{int_part}");
757 if self.peek() == Some('.') {
758 raw.push('.');
759 self.advance();
760 while let Some(c) = self.peek() {
761 if c.is_ascii_digit() {
762 raw.push(c);
763 self.advance();
764 } else {
765 break;
766 }
767 }
768 }
769 if matches!(self.peek(), Some('e') | Some('E')) {
770 raw.push('e');
771 self.advance();
772 if matches!(self.peek(), Some('+') | Some('-')) {
773 raw.push(self.peek().unwrap());
774 self.advance();
775 }
776 while let Some(c) = self.peek() {
777 if c.is_ascii_digit() {
778 raw.push(c);
779 self.advance();
780 } else {
781 break;
782 }
783 }
784 }
785 if self.peek() == Some('M') {
787 self.advance();
788 return Ok((
789 Token::BigDecimal(raw),
790 self.span_from(start_pos, start_line, start_col),
791 ));
792 }
793 let val: f64 = raw.parse().map_err(|_| {
794 let span = self.span_from(start_pos, start_line, start_col);
795 self.make_error(format!("invalid float: {raw}"), span)
796 })?;
797 return Ok((
798 Token::Float(val),
799 self.span_from(start_pos, start_line, start_col),
800 ));
801 }
802
803 if self.peek() == Some('/') && matches!(self.peek_next(), Some(c) if c.is_ascii_digit()) {
805 self.advance(); let mut denom = String::new();
807 while let Some(c) = self.peek() {
808 if c.is_ascii_digit() {
809 denom.push(c);
810 self.advance();
811 } else {
812 break;
813 }
814 }
815 return Ok((
816 Token::Ratio(format!("{sign_str}{int_part}/{denom}")),
817 self.span_from(start_pos, start_line, start_col),
818 ));
819 }
820
821 let full = format!("{sign_str}{int_part}");
823 if int_part.len() > 1 && int_part.starts_with('0') {
824 if !int_part.chars().all(|c| matches!(c, '0'..='7')) {
825 let span = self.span_from(start_pos, start_line, start_col);
826 return Err(self.make_error(format!("invalid octal integer: {full}"), span));
827 }
828 let magnitude = u128::from_str_radix(&int_part, 8).unwrap_or(u128::MAX);
829 let signed = if negative {
830 -(magnitude as i128)
831 } else {
832 magnitude as i128
833 };
834 return if let Ok(value) = i64::try_from(signed) {
835 Ok((
836 Token::Int(value),
837 self.span_from(start_pos, start_line, start_col),
838 ))
839 } else {
840 Ok((
841 Token::BigInt(signed.to_string()),
842 self.span_from(start_pos, start_line, start_col),
843 ))
844 };
845 }
846 match full.parse::<i64>() {
847 Ok(n) => Ok((
848 Token::Int(n),
849 self.span_from(start_pos, start_line, start_col),
850 )),
851 Err(_) => {
852 Ok((
854 Token::BigInt(full),
855 self.span_from(start_pos, start_line, start_col),
856 ))
857 }
858 }
859 }
860
861 pub fn next_token(&mut self) -> CljxResult<(Token, Span)> {
864 self.skip_whitespace_and_comments();
865
866 let start_pos = self.pos;
867 let start_line = self.line;
868 let start_col = self.col;
869
870 let ch = match self.peek() {
871 None => {
872 return Ok((Token::Eof, self.span_from(start_pos, start_line, start_col)));
873 }
874 Some(c) => c,
875 };
876
877 match ch {
878 '(' => {
879 self.advance();
880 Ok((
881 Token::LParen,
882 self.span_from(start_pos, start_line, start_col),
883 ))
884 }
885 ')' => {
886 self.advance();
887 Ok((
888 Token::RParen,
889 self.span_from(start_pos, start_line, start_col),
890 ))
891 }
892 '[' => {
893 self.advance();
894 Ok((
895 Token::LBracket,
896 self.span_from(start_pos, start_line, start_col),
897 ))
898 }
899 ']' => {
900 self.advance();
901 Ok((
902 Token::RBracket,
903 self.span_from(start_pos, start_line, start_col),
904 ))
905 }
906 '{' => {
907 self.advance();
908 Ok((
909 Token::LBrace,
910 self.span_from(start_pos, start_line, start_col),
911 ))
912 }
913 '}' => {
914 self.advance();
915 Ok((
916 Token::RBrace,
917 self.span_from(start_pos, start_line, start_col),
918 ))
919 }
920 '\'' => {
921 self.advance();
922 Ok((
923 Token::Quote,
924 self.span_from(start_pos, start_line, start_col),
925 ))
926 }
927 '`' => {
928 self.advance();
929 Ok((
930 Token::SyntaxQuote,
931 self.span_from(start_pos, start_line, start_col),
932 ))
933 }
934 '@' => {
935 self.advance();
936 Ok((
937 Token::Deref,
938 self.span_from(start_pos, start_line, start_col),
939 ))
940 }
941 '^' => {
942 self.advance();
943 Ok((
944 Token::Meta,
945 self.span_from(start_pos, start_line, start_col),
946 ))
947 }
948 '~' => self.lex_unquote(start_pos, start_line, start_col),
949 '#' => self.lex_hash(start_pos, start_line, start_col),
950 '"' => self.lex_string(start_pos, start_line, start_col),
951 '\\' => self.lex_char_literal(start_pos, start_line, start_col),
952 ':' => self.lex_keyword(start_pos, start_line, start_col),
953 c if c.is_ascii_digit() => self.lex_number(start_pos, start_line, start_col),
954 '+' | '-' if matches!(self.peek_next(), Some(d) if d.is_ascii_digit()) => {
955 self.lex_number(start_pos, start_line, start_col)
956 }
957 c if is_symbol_start(c) => self.lex_symbol(start_pos, start_line, start_col),
958 '+' | '-' => self.lex_symbol(start_pos, start_line, start_col),
960 c => {
961 self.advance();
962 let span = self.span_from(start_pos, start_line, start_col);
963 Err(self.make_error(format!("unexpected character: {c:?}"), span))
964 }
965 }
966 }
967}
968
969impl Iterator for Lexer {
970 type Item = CljxResult<(Token, Span)>;
971
972 fn next(&mut self) -> Option<Self::Item> {
973 match self.next_token() {
974 Ok((Token::Eof, _)) => None,
975 result => Some(result),
976 }
977 }
978}
979
980#[cfg(test)]
983mod tests {
984 use super::*;
985
986 fn lex_all(src: &str) -> Vec<Token> {
987 Lexer::new(src.to_string(), "<test>".to_string())
988 .map(|r: CljxResult<(Token, Span)>| r.expect("lex error").0)
989 .collect()
990 }
991
992 fn lex_one(src: &str) -> Token {
993 let mut l = Lexer::new(src.to_string(), "<test>".to_string());
994 l.next_token().expect("lex error").0
995 }
996
997 fn lex_err(src: &str) -> String {
998 let mut l = Lexer::new(src.to_string(), "<test>".to_string());
999 loop {
1000 match l.next_token() {
1001 Err(CljxError::ReadError { message, .. }) => return message,
1002 Err(e) => panic!("unexpected error type: {e}"),
1003 Ok((Token::Eof, _)) => panic!("expected an error but got Eof"),
1004 Ok(_) => {}
1005 }
1006 }
1007 }
1008
1009 #[test]
1012 fn test_nil() {
1013 assert_eq!(lex_one("nil"), Token::Nil);
1014 }
1015
1016 #[test]
1017 fn test_bool() {
1018 assert_eq!(lex_one("true"), Token::Bool(true));
1019 assert_eq!(lex_one("false"), Token::Bool(false));
1020 }
1021
1022 #[test]
1025 fn test_int_plain() {
1026 assert_eq!(lex_one("42"), Token::Int(42));
1027 assert_eq!(lex_one("-42"), Token::Int(-42));
1028 assert_eq!(lex_one("+42"), Token::Int(42));
1029 assert_eq!(lex_one("0"), Token::Int(0));
1030 }
1031
1032 #[test]
1033 fn test_bigint_suffix() {
1034 assert_eq!(lex_one("42N"), Token::BigInt("42".to_string()));
1035 assert_eq!(lex_one("-42N"), Token::BigInt("-42".to_string()));
1036 }
1037
1038 #[test]
1039 fn test_hex_literal() {
1040 assert_eq!(lex_one("0xff"), Token::Int(255));
1041 assert_eq!(lex_one("0xFF"), Token::Int(255));
1042 assert_eq!(lex_one("0x0"), Token::Int(0));
1043 assert_eq!(lex_one("0x7FFFFFFFFFFFFFFF"), Token::Int(i64::MAX));
1044 assert_eq!(lex_one("-0x8000000000000000"), Token::Int(i64::MIN));
1045 assert_eq!(lex_one("-0xff"), Token::Int(-255));
1046 match lex_one("0xFFFFFFFFFFFFFFFF") {
1048 Token::BigInt(_) => {}
1049 other => panic!("expected BigInt for 0xFFFF…, got {other:?}"),
1050 }
1051 }
1052
1053 #[test]
1054 fn test_radix() {
1055 assert_eq!(lex_one("2r1010"), Token::Int(10));
1056 assert_eq!(lex_one("8r77"), Token::Int(63));
1057 assert_eq!(lex_one("16rFF"), Token::Int(255));
1058 assert_eq!(lex_one("16rff"), Token::Int(255));
1059 assert_eq!(lex_one("36rZ"), Token::Int(35));
1060 }
1061
1062 #[test]
1063 fn test_radix_overflow() {
1064 let tok = lex_one("10r18446744073709551616");
1066 match tok {
1067 Token::BigInt(_) => {}
1068 other => panic!("expected BigInt, got {other:?}"),
1069 }
1070 }
1071
1072 #[test]
1075 #[allow(clippy::approx_constant)]
1076 fn test_floats() {
1077 assert_eq!(lex_one("3.14"), Token::Float(3.14));
1078 assert_eq!(lex_one("1e10"), Token::Float(1e10));
1079 assert_eq!(lex_one("1.5e-3"), Token::Float(1.5e-3));
1080 assert_eq!(lex_one("-0.5"), Token::Float(-0.5));
1081 }
1082
1083 #[test]
1084 fn test_bigdecimal() {
1085 assert_eq!(lex_one("3.14M"), Token::BigDecimal("3.14".to_string()));
1086 assert_eq!(lex_one("1e5M"), Token::BigDecimal("1e5".to_string()));
1087 }
1088
1089 #[test]
1092 fn test_ratio() {
1093 assert_eq!(lex_one("3/4"), Token::Ratio("3/4".to_string()));
1094 assert_eq!(lex_one("-1/2"), Token::Ratio("-1/2".to_string()));
1095 }
1096
1097 #[test]
1098 fn test_ratio_vs_symbol() {
1099 let toks = lex_all("3/foo");
1101 assert_eq!(toks[0], Token::Int(3));
1102 assert_eq!(toks[1], Token::Symbol("/foo".to_string()));
1103 }
1104
1105 #[test]
1108 fn test_char_simple() {
1109 assert_eq!(lex_one("\\a"), Token::Char('a'));
1110 }
1111
1112 #[test]
1113 fn test_char_named() {
1114 assert_eq!(lex_one("\\newline"), Token::Char('\n'));
1115 assert_eq!(lex_one("\\space"), Token::Char(' '));
1116 assert_eq!(lex_one("\\tab"), Token::Char('\t'));
1117 assert_eq!(lex_one("\\backspace"), Token::Char('\x08'));
1118 assert_eq!(lex_one("\\formfeed"), Token::Char('\x0C'));
1119 assert_eq!(lex_one("\\return"), Token::Char('\r'));
1120 }
1121
1122 #[test]
1123 fn test_char_unicode() {
1124 assert_eq!(lex_one("\\u0041"), Token::Char('A'));
1125 assert_eq!(lex_one("\\u00e9"), Token::Char('é'));
1126 }
1127
1128 #[test]
1129 fn test_char_octal() {
1130 assert_eq!(lex_one("\\o"), Token::Char('o'));
1131 assert_eq!(lex_one("\\o0"), Token::Char('\0'));
1132 assert_eq!(lex_one("\\o101"), Token::Char('A'));
1133 assert_eq!(lex_one("\\o377"), Token::Char('\u{ff}'));
1134 }
1135
1136 #[test]
1139 fn test_string_basic() {
1140 assert_eq!(lex_one("\"hello\""), Token::Str("hello".to_string()));
1141 }
1142
1143 #[test]
1144 fn test_string_escapes() {
1145 assert_eq!(
1146 lex_one(r#""\n\t\r\b\f\\\"" "#),
1147 Token::Str("\n\t\r\x08\x0C\\\"".to_string())
1148 );
1149 }
1150
1151 #[test]
1152 fn test_string_unicode_escape() {
1153 assert_eq!(lex_one("\"\\u0041\""), Token::Str("A".to_string()));
1154 }
1155
1156 #[test]
1159 fn test_symbols() {
1160 assert_eq!(lex_one("foo"), Token::Symbol("foo".to_string()));
1161 assert_eq!(lex_one("ns/name"), Token::Symbol("ns/name".to_string()));
1162 assert_eq!(lex_one("/"), Token::Symbol("/".to_string()));
1163 assert_eq!(lex_one(".."), Token::Symbol("..".to_string()));
1164 assert_eq!(lex_one(".method"), Token::Symbol(".method".to_string()));
1165 assert_eq!(lex_one("+"), Token::Symbol("+".to_string()));
1166 assert_eq!(lex_one("-"), Token::Symbol("-".to_string()));
1167 assert_eq!(lex_one("+foo"), Token::Symbol("+foo".to_string()));
1168 }
1169
1170 #[test]
1171 fn test_auto_gensym_symbol() {
1172 assert_eq!(lex_one("x#"), Token::Symbol("x#".to_string()));
1175 let toks = lex_all("`(let [x# 1] x#)");
1176 assert_eq!(
1177 toks,
1178 vec![
1179 Token::SyntaxQuote,
1180 Token::LParen,
1181 Token::Symbol("let".to_string()),
1182 Token::LBracket,
1183 Token::Symbol("x#".to_string()),
1184 Token::Int(1),
1185 Token::RBracket,
1186 Token::Symbol("x#".to_string()),
1187 Token::RParen,
1188 ]
1189 );
1190 }
1191
1192 #[test]
1195 fn test_keyword() {
1196 assert_eq!(lex_one(":foo"), Token::Keyword("foo".to_string()));
1197 assert_eq!(lex_one(":ns/name"), Token::Keyword("ns/name".to_string()));
1198 }
1199
1200 #[test]
1201 fn test_keyword_with_embedded_colon() {
1202 assert_eq!(
1205 lex_one(":xlink:href"),
1206 Token::Keyword("xlink:href".to_string())
1207 );
1208 let toks = lex_all("[:xlink:href]");
1209 assert_eq!(
1210 toks,
1211 vec![
1212 Token::LBracket,
1213 Token::Keyword("xlink:href".to_string()),
1214 Token::RBracket,
1215 ]
1216 );
1217 }
1218
1219 #[test]
1220 fn test_auto_keyword() {
1221 assert_eq!(lex_one("::foo"), Token::AutoKeyword("foo".to_string()));
1222 assert_eq!(
1223 lex_one("::ns/alias"),
1224 Token::AutoKeyword("ns/alias".to_string())
1225 );
1226 }
1227
1228 #[test]
1229 fn test_namespaced_map_prefix() {
1230 use crate::namespaced_map::MapNs;
1231 assert_eq!(
1234 lex_one("#:adt{:a 1}"),
1235 Token::NamespacedMap(MapNs::Literal("adt".to_string()))
1236 );
1237 assert_eq!(lex_one("#::{:a 1}"), Token::NamespacedMap(MapNs::CurrentNs));
1238 assert_eq!(
1239 lex_one("#::al{:a 1}"),
1240 Token::NamespacedMap(MapNs::Alias("al".to_string()))
1241 );
1242 }
1243
1244 #[test]
1245 fn test_namespaced_map_prefix_skips_whitespace_before_the_brace() {
1246 use crate::namespaced_map::MapNs;
1247 for src in ["#:adt {:a 1}", "#:adt, {:a 1}", "#:adt\n{:a 1}"] {
1249 assert_eq!(
1250 lex_one(src),
1251 Token::NamespacedMap(MapNs::Literal("adt".to_string())),
1252 "{src}"
1253 );
1254 }
1255 assert_eq!(
1256 lex_one("#:: {:a 1}"),
1257 Token::NamespacedMap(MapNs::CurrentNs)
1258 );
1259 assert_eq!(
1260 lex_one("#::al {:a 1}"),
1261 Token::NamespacedMap(MapNs::Alias("al".to_string()))
1262 );
1263 }
1264
1265 #[test]
1266 fn test_namespaced_map_prefix_does_not_skip_comments() {
1267 assert!(lex_err("#:adt ;; here\n{:a 1}").contains("must be followed by a map"));
1269 }
1270
1271 #[test]
1272 fn test_namespaced_map_prefix_errors() {
1273 assert!(lex_err("#:{:a 1}").contains("requires a namespace"));
1274 assert!(lex_err("#:adt [1]").contains("must be followed by a map"));
1275 }
1276
1277 #[test]
1278 fn test_namespaced_map_prefix_must_be_an_unqualified_symbol() {
1279 for src in [
1280 "#:foo/bar{:a 1}",
1281 "#:1{:a 1}",
1282 "#:nil{:a 1}",
1283 "#::foo/bar{:a 1}",
1284 ] {
1285 let err = lex_err(src);
1286 assert!(err.contains("unqualified symbol"), "{src}: {err}");
1287 }
1288 }
1289
1290 #[test]
1293 fn test_delimiters() {
1294 assert_eq!(
1295 lex_all("([{}])"),
1296 vec![
1297 Token::LParen,
1298 Token::LBracket,
1299 Token::LBrace,
1300 Token::RBrace,
1301 Token::RBracket,
1302 Token::RParen,
1303 ]
1304 );
1305 }
1306
1307 #[test]
1310 fn test_reader_macros() {
1311 assert_eq!(lex_one("'x"), Token::Quote);
1312 assert_eq!(lex_one("`x"), Token::SyntaxQuote);
1313 assert_eq!(lex_one("~x"), Token::Unquote);
1314 assert_eq!(lex_one("~@x"), Token::UnquoteSplice);
1315 assert_eq!(lex_one("@x"), Token::Deref);
1316 assert_eq!(lex_one("^x"), Token::Meta);
1317 }
1318
1319 #[test]
1322 fn test_hash_dispatch() {
1323 assert_eq!(lex_one("#("), Token::HashFn);
1324 assert_eq!(lex_one("#{"), Token::HashSet);
1325 assert_eq!(lex_one("#'"), Token::HashVar);
1326 assert_eq!(lex_one("#_"), Token::HashDiscard);
1327 assert_eq!(lex_one("#?"), Token::ReaderCond);
1328 assert_eq!(lex_one("#?@"), Token::ReaderCondSplice);
1329 }
1330
1331 #[test]
1332 fn test_regex() {
1333 assert_eq!(lex_one("#\"[a-z]+\""), Token::Regex("[a-z]+".to_string()));
1334 }
1335
1336 #[test]
1337 fn test_symbolic() {
1338 assert_eq!(lex_one("##Inf"), Token::Symbolic("Inf".to_string()));
1339 assert_eq!(lex_one("##-Inf"), Token::Symbolic("-Inf".to_string()));
1340 assert_eq!(lex_one("##NaN"), Token::Symbolic("NaN".to_string()));
1341 }
1342
1343 #[test]
1344 fn test_tagged_literal() {
1345 assert_eq!(lex_one("#mytag"), Token::TaggedLiteral("mytag".to_string()));
1346 }
1347
1348 #[test]
1351 fn test_multi_token() {
1352 let toks = lex_all("(+ 1 2)");
1353 assert_eq!(
1354 toks,
1355 vec![
1356 Token::LParen,
1357 Token::Symbol("+".to_string()),
1358 Token::Int(1),
1359 Token::Int(2),
1360 Token::RParen,
1361 ]
1362 );
1363 }
1364
1365 #[test]
1368 fn test_comma_skipped() {
1369 assert_eq!(lex_all("{,,,}"), vec![Token::LBrace, Token::RBrace]);
1370 }
1371
1372 #[test]
1373 fn test_comment_skipped() {
1374 assert_eq!(lex_all("; this is a comment\n42"), vec![Token::Int(42)]);
1375 }
1376
1377 #[test]
1378 fn test_shebang_skipped() {
1379 assert_eq!(lex_all("#!/usr/bin/env cljx\n42"), vec![Token::Int(42)]);
1380 }
1381
1382 #[test]
1385 fn test_span_col() {
1386 let mut l = Lexer::new(" foo".to_string(), "<test>".to_string());
1387 let (_tok, span) = l.next_token().unwrap();
1388 assert_eq!(span.start, 2);
1389 assert_eq!(span.col, 3);
1390 }
1391
1392 #[test]
1393 fn test_span_newline() {
1394 let mut l = Lexer::new("a\nb".to_string(), "<test>".to_string());
1395 l.next_token().unwrap(); let (_tok, span) = l.next_token().unwrap(); assert_eq!(span.line, 2);
1398 assert_eq!(span.col, 1);
1399 }
1400
1401 #[test]
1404 fn test_error_unterminated_string() {
1405 let msg = lex_err("\"unterminated");
1406 assert!(msg.contains("unterminated string"));
1407 }
1408
1409 #[test]
1410 fn test_error_bad_hash_dispatch() {
1411 let msg = lex_err("#1");
1413 assert!(msg.contains("unknown # dispatch"));
1414 }
1415
1416 #[test]
1417 fn test_error_bad_unicode_escape_in_string() {
1418 let msg = lex_err("\"\\uGHIJ\"");
1419 assert!(msg.contains("invalid") || msg.contains("hex"));
1420 }
1421
1422 #[test]
1423 fn test_error_unknown_char_name() {
1424 let msg = lex_err("\\bogus");
1425 assert!(msg.contains("unknown character name"));
1426 }
1427
1428 #[test]
1429 fn test_error_unknown_symbolic() {
1430 let msg = lex_err("##Bogus");
1431 assert!(msg.contains("unknown symbolic value"));
1432 }
1433
1434 #[test]
1435 fn test_error_bad_string_escape() {
1436 let msg = lex_err("\"\\q\"");
1437 assert!(msg.contains("unknown string escape"));
1438 }
1439}