1use std::error::Error;
10use std::fmt::{Display, Formatter, Result as FmtResult};
11use std::mem::take;
12use std::sync::Arc;
13
14use crate::{
15 Delimiter, DelimiterKind, DoubleSpan, GENERATED_OPERATOR_SPELLINGS,
16 GENERATED_POSTFIX_OPERATOR_SPELLINGS, GENERATED_PUNCTUATION_SPELLINGS,
17 GENERATED_WORD_TOKEN_SPELLINGS, IdentToken, LiteralKind, LiteralToken, Offset, Punct,
18 Recovered, Source, Span, Spanned, TokenGroup, TokenStream, TokenTree, Trivia, TriviaKind,
19};
20
21const MAX_GROUP_DEPTH: usize = 256;
23
24pub(crate) fn canonical_word_spelling(spelling: &str) -> &str {
27 let Some(word) = spelling.strip_prefix("Core$") else {
28 return spelling;
29 };
30 if GENERATED_WORD_TOKEN_SPELLINGS.contains(&word) {
31 word
32 } else {
33 spelling
34 }
35}
36
37#[doc(hidden)]
38pub fn __qualified_identifier_spans(spelling: &str, span: &Span) -> (Option<Span>, Option<Span>) {
39 let components = spelling.match_indices('$').collect::<Vec<_>>();
40 let subspan = |start: usize, end: usize| span.subspan(start, end);
41 match components.as_slice() {
42 [(separator, _)] => {
43 let left = &spelling[..*separator];
44 let right_start = separator + 1;
45 let right = &spelling[right_start..];
46 if is_bare_symbol_name(left) && is_bare_symbol_name(right) {
47 (Some(subspan(0, *separator)), None)
48 } else if is_bare_symbol_name(left) && is_decimal_index(right) {
49 (None, Some(subspan(right_start, spelling.len())))
50 } else {
51 (None, None)
52 }
53 }
54 [(package_separator, _), (index_separator, _)] => {
55 let package = &spelling[..*package_separator];
56 let name_start = package_separator + 1;
57 let name = &spelling[name_start..*index_separator];
58 let index_start = index_separator + 1;
59 let index = &spelling[index_start..];
60 if is_bare_symbol_name(package) && is_bare_symbol_name(name) && is_decimal_index(index)
61 {
62 (
63 Some(subspan(0, *package_separator)),
64 Some(subspan(index_start, spelling.len())),
65 )
66 } else {
67 (None, None)
68 }
69 }
70 _ => (None, None),
71 }
72}
73
74fn is_bare_symbol_name(spelling: &str) -> bool {
76 let Some(first) = spelling.as_bytes().first().copied() else {
77 return false;
78 };
79 is_identifier_start(first)
80 && spelling
81 .as_bytes()
82 .iter()
83 .copied()
84 .all(|byte| byte != b'$' && is_identifier_continue(byte))
85}
86
87fn is_decimal_index(spelling: &str) -> bool {
89 !spelling.is_empty() && spelling.bytes().all(|byte| byte.is_ascii_digit())
90}
91
92#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
93pub enum LexErrorKind {
95 InvalidCharacter,
97 InvalidEscape,
99 InvalidNumber,
101 InvalidUtf8,
103 NestingLimitExceeded,
105 UnexpectedClosingDelimiter,
107 UnterminatedGroup,
109 UnterminatedString,
111 UnterminatedRawString,
113 UnterminatedBlockComment,
115}
116
117#[derive(Debug, Clone, PartialEq, Eq, Hash, Spanned)]
118pub struct LexError {
120 kind: LexErrorKind,
122 delimiter: Option<DelimiterKind>,
124 span: Span,
126}
127
128impl LexError {
129 pub fn kind(&self) -> LexErrorKind {
131 self.kind
132 }
133
134 pub fn unclosed_expression(&self) -> Option<&'static str> {
136 match self.kind {
137 LexErrorKind::UnterminatedGroup => {
138 self.delimiter.as_ref().map(DelimiterKind::expression_name)
139 }
140 LexErrorKind::UnterminatedString => Some("string literal"),
141 LexErrorKind::UnterminatedRawString => Some("raw string literal"),
142 _ => None,
143 }
144 }
145
146 pub fn expected(&self) -> Option<&'static str> {
148 match self.kind {
149 LexErrorKind::UnterminatedGroup => self.delimiter.map(DelimiterKind::closing),
150 LexErrorKind::UnterminatedString => Some("\""),
151 LexErrorKind::UnterminatedRawString => Some("///"),
152 LexErrorKind::UnterminatedBlockComment => Some("*-"),
153 _ => None,
154 }
155 }
156
157 pub fn found(&self) -> Option<&'static str> {
159 matches!(
160 self.kind,
161 LexErrorKind::UnterminatedGroup
162 | LexErrorKind::UnterminatedString
163 | LexErrorKind::UnterminatedRawString
164 | LexErrorKind::UnterminatedBlockComment
165 )
166 .then_some("end of input")
167 }
168}
169
170impl Display for LexError {
171 fn fmt(&self, formatter: &mut Formatter<'_>) -> FmtResult {
172 let message = match self.kind {
173 LexErrorKind::InvalidCharacter => "invalid character",
174 LexErrorKind::InvalidEscape => "invalid string escape",
175 LexErrorKind::InvalidNumber => "invalid number literal",
176 LexErrorKind::InvalidUtf8 => "source is not valid UTF-8",
177 LexErrorKind::NestingLimitExceeded => "delimiter nesting limit exceeded",
178 LexErrorKind::UnexpectedClosingDelimiter => "unexpected closing delimiter",
179 LexErrorKind::UnterminatedGroup => {
180 if let Some(delimiter) = self.delimiter {
181 return write!(
182 formatter,
183 "unclosed {}: expected `{}`, found end of input",
184 delimiter.expression_name(),
185 delimiter.closing(),
186 );
187 }
188 "unterminated delimited group"
189 }
190 LexErrorKind::UnterminatedString => {
191 "unclosed string literal: expected `\"`, found end of input"
192 }
193 LexErrorKind::UnterminatedRawString => {
194 "unclosed raw string literal: expected `///`, found end of input"
195 }
196 LexErrorKind::UnterminatedBlockComment => "unterminated block comment",
197 };
198 formatter.write_str(message)
199 }
200}
201
202impl Error for LexError {}
203
204pub fn lex<I>(bytes: I) -> Result<TokenStream, LexError>
215where
216 I: IntoIterator<Item = u8>,
217{
218 lexer_from_bytes(bytes)?.lex()
219}
220
221pub fn lex_recovering<I>(bytes: I) -> Result<Recovered<TokenStream, LexError>, LexError>
233where
234 I: IntoIterator<Item = u8>,
235{
236 lexer_from_bytes(bytes)?.lex_recovering()
237}
238
239pub fn lex_str(source: &str) -> Result<TokenStream, LexError> {
245 NativeLexer::new(Arc::<str>::from(source)).lex()
246}
247
248pub fn lex_str_recovering(source: &str) -> Result<Recovered<TokenStream, LexError>, LexError> {
256 NativeLexer::new(Arc::<str>::from(source)).lex_recovering()
257}
258
259#[cfg(feature = "tree-sitter")]
262pub(crate) fn lex_source_recovering(
263 source: &Arc<Source>,
264) -> Result<Recovered<TokenStream, LexError>, LexError> {
265 NativeLexer::over(Arc::clone(source)).lex_recovering()
266}
267
268#[doc(hidden)]
281pub fn __quote_fragment(source: &str) -> TokenStream {
282 NativeLexer::over(Source::synthetic(source))
283 .lex()
284 .unwrap_or_else(|error| panic!("invalid static M2 quote fragment: {error}"))
285}
286
287fn lexer_from_bytes<I>(bytes: I) -> Result<NativeLexer, LexError>
289where
290 I: IntoIterator<Item = u8>,
291{
292 let bytes = bytes.into_iter().collect::<Vec<_>>();
293 let len = bytes.len();
294 let source = String::from_utf8(bytes).map_err(|_| LexError {
295 kind: LexErrorKind::InvalidUtf8,
296 delimiter: None,
297 span: Span::ranged(Source::new(""), 0..len),
298 })?;
299 Ok(NativeLexer::new(Arc::<str>::from(source)))
300}
301
302struct NativeLexer {
304 source: Arc<str>,
306 source_span: Span,
308 offset: Offset,
310}
311
312impl NativeLexer {
313 fn over(source: Arc<Source>) -> Self {
316 Self {
317 source: Arc::<str>::from(source.text()),
318 source_span: Span::ranged(Arc::clone(&source), 0..source.text().len()),
319 offset: 0,
320 }
321 }
322
323 fn new(source: Arc<str>) -> Self {
325 let source_span = Span::ranged(Source::new(Arc::clone(&source)), 0..source.len());
326 Self {
327 source,
328 source_span,
329 offset: 0,
330 }
331 }
332
333 fn lex(self) -> Result<TokenStream, LexError> {
335 let recovered = self.lex_recovering()?;
336 match recovered.diagnostics.into_iter().next() {
337 Some(error) => Err(error),
338 None => Ok(recovered.value),
339 }
340 }
341
342 fn lex_recovering(mut self) -> Result<Recovered<TokenStream, LexError>, LexError> {
344 let mut output = TokenStream::new();
345 let mut groups = Vec::<OpenGroup>::new();
346 let mut diagnostics = Vec::new();
347 let mut quote_next = false;
352 loop {
353 let Some(first) = self.peek(0) else {
354 output.set_source(Arc::clone(self.source_span.source()));
355 while let Some(group) = groups.pop() {
356 diagnostics.push(LexError {
357 kind: LexErrorKind::UnterminatedGroup,
358 delimiter: Some(group.kind),
359 span: self.span_from(group.opening_start),
360 });
361 }
362 return Ok(Recovered::new(output, diagnostics));
363 };
364
365 if first == b'\n' || (first == b'\r' && self.byte_after_current() == Some(b'\n')) {
366 self.lex_line_break(&mut output)?;
367 } else if first == b'\r' {
368 self.lex_carriage_return(&mut output)?;
369 } else if matches!(first, b' ' | b'\t') {
370 self.lex_whitespace(&mut output)?;
371 } else if let Some(width) = self.math_operator_width() {
372 self.lex_math_operator(width, &mut output)?;
373 quote_next = false;
374 } else if is_identifier_start(first) {
375 let quoted = take(&mut quote_next);
376 self.lex_identifier(&mut output)?;
377 quote_next = !quoted && last_token_quotes_next(&output);
378 } else if first.is_ascii_digit()
379 || (first == b'.'
380 && self
381 .byte_after_current()
382 .is_some_and(|byte| byte.is_ascii_digit()))
383 {
384 self.lex_number(&mut output)?;
385 quote_next = false;
386 } else if first == b'"' {
387 match self.lex_string(&mut output) {
388 Ok(()) => {}
389 Err(error) if error.kind == LexErrorKind::UnterminatedString => {
390 output.extend([TokenTree::Literal(LiteralToken::from_span(
391 LiteralKind::String,
392 error.span.clone(),
393 ))]);
394 diagnostics.push(error);
395 }
396 Err(error) => return Err(error),
397 }
398 quote_next = false;
399 } else if let Some(fixed) = self.match_fixed() {
400 let quoted = take(&mut quote_next);
401 match fixed.kind {
402 FixedKind::Open(_) | FixedKind::Close(_) if quoted => {
405 self.lex_punctuation(fixed.len, &mut output)?;
406 }
407 FixedKind::Punctuation => {
408 self.lex_punctuation(fixed.len, &mut output)?;
409 }
410 FixedKind::LineComment => {
413 self.lex_line_comment(fixed.len, &mut output)?;
414 quote_next = quoted;
415 }
416 FixedKind::BlockComment => {
417 self.lex_block_comment(fixed.len, &mut output)?;
418 quote_next = quoted;
419 }
420 FixedKind::RawString => match self.lex_raw_string(fixed.len, &mut output) {
421 Ok(()) => {}
422 Err(error) if error.kind == LexErrorKind::UnterminatedRawString => {
423 output.extend([TokenTree::Literal(LiteralToken::from_span(
424 LiteralKind::RawString,
425 error.span.clone(),
426 ))]);
427 diagnostics.push(error);
428 }
429 Err(error) => return Err(error),
430 },
431 FixedKind::Open(kind) => {
432 if groups.len() == MAX_GROUP_DEPTH {
433 return Err(self.error(LexErrorKind::NestingLimitExceeded, self.offset));
434 }
435 let opening_start = self.offset;
436 self.take_exact(fixed.len, opening_start)?;
437 let opening = self.span_from(opening_start);
438 groups.push(OpenGroup {
439 kind,
440 opening_start,
441 });
442 output.extend([TokenGroup::new(
443 Delimiter::new(kind, DoubleSpan::unclosed(opening)),
444 TokenStream::new(),
445 )]);
446 }
447 FixedKind::Close(kind) => {
448 let closing_start = self.offset;
449 self.take_exact(fixed.len, closing_start)?;
450 let closing = self.span_from(closing_start);
451 let Some(group) = groups.pop() else {
452 return Err(
453 self.error(LexErrorKind::UnexpectedClosingDelimiter, closing_start)
454 );
455 };
456 if group.kind != kind {
457 return Err(
458 self.error(LexErrorKind::UnexpectedClosingDelimiter, closing_start)
459 );
460 }
461 assert!(
462 output.complete_last_group(kind, closing),
463 "the open-group stack and token stream must agree"
464 );
465 }
466 }
467 } else {
468 let start = self.offset;
469 self.next();
470 return Err(self.error(LexErrorKind::InvalidCharacter, start));
471 }
472 }
473 }
474
475 fn lex_line_break(&mut self, _output: &mut TokenStream) -> Result<(), LexError> {
477 let start = self.offset;
478 let first = self.next_or_error(LexErrorKind::InvalidCharacter, start)?;
479 let mut bytes = vec![first];
480 if first == b'\r' && self.peek(0) == Some(b'\n') {
481 bytes.push(self.next_or_error(LexErrorKind::InvalidCharacter, start)?);
482 }
483 let _ = bytes;
484 Ok(())
485 }
486
487 fn lex_carriage_return(&mut self, _output: &mut TokenStream) -> Result<(), LexError> {
489 let start = self.offset;
490 let byte = self.next_or_error(LexErrorKind::InvalidCharacter, start)?;
491 let _ = byte;
492 Ok(())
493 }
494
495 fn lex_whitespace(&mut self, _output: &mut TokenStream) -> Result<(), LexError> {
497 let bytes = self.take_while(|byte| matches!(byte, b' ' | b'\t'));
498 let _ = bytes;
499 Ok(())
500 }
501
502 fn lex_identifier(&mut self, output: &mut TokenStream) -> Result<(), LexError> {
504 let start = self.offset;
505 let mut bytes = vec![self.next_or_error(LexErrorKind::InvalidUtf8, start)?];
506 while let Some(byte) = self.peek(0) {
507 if !is_identifier_continue(byte) || self.math_operator_width().is_some() {
508 break;
509 }
510 bytes.push(self.next_or_error(LexErrorKind::InvalidUtf8, start)?);
511 }
512 let _ = bytes;
513 output.extend([TokenTree::Ident(IdentToken::from_span(
514 self.span_from(start),
515 ))]);
516 Ok(())
517 }
518
519 fn lex_number(&mut self, output: &mut TokenStream) -> Result<(), LexError> {
521 let start = self.offset;
522 let mut bytes = Vec::new();
523 let mut decimal = true;
524
525 if self.peek(0) == Some(b'0') {
526 bytes.push(self.next_or_error(LexErrorKind::InvalidNumber, start)?);
527 if let Some(radix) = self.number_radix() {
528 decimal = false;
529 bytes.push(self.next_or_error(LexErrorKind::InvalidNumber, start)?);
530 bytes.extend(self.take_while(|byte| radix.is_digit(byte)));
531 } else {
532 bytes.extend(self.take_while(|byte| byte.is_ascii_digit()));
533 }
534 } else {
535 bytes.extend(self.take_while(|byte| byte.is_ascii_digit()));
536 }
537
538 let next = self.peek(0);
539 let begins_float = decimal
540 && (next == Some(b'.') && self.byte_after_current() != Some(b'.')
541 || matches!(next, Some(b'p' | b'e' | b'E')));
542 let kind = if begins_float {
543 if self.peek(0) == Some(b'.') {
544 bytes.push(self.next_or_error(LexErrorKind::InvalidNumber, start)?);
545 bytes.extend(self.take_while(|byte| byte.is_ascii_digit()));
546 }
547 if self.peek(0) == Some(b'p') {
548 bytes.push(self.next_or_error(LexErrorKind::InvalidNumber, start)?);
549 if !self.peek(0).is_some_and(|byte| byte.is_ascii_digit()) {
550 return Err(self.error(LexErrorKind::InvalidNumber, start));
551 }
552 bytes.extend(self.take_while(|byte| byte.is_ascii_digit()));
553 }
554 if matches!(self.peek(0), Some(b'e' | b'E')) {
555 bytes.push(self.next_or_error(LexErrorKind::InvalidNumber, start)?);
556 if matches!(self.peek(0), Some(b'+' | b'-')) {
557 bytes.push(self.next_or_error(LexErrorKind::InvalidNumber, start)?);
558 }
559 if !self.peek(0).is_some_and(|byte| byte.is_ascii_digit()) {
560 return Err(self.error(LexErrorKind::InvalidNumber, start));
561 }
562 bytes.extend(self.take_while(|byte| byte.is_ascii_digit()));
563 }
564 LiteralKind::Float
565 } else {
566 LiteralKind::Integer
567 };
568
569 let _ = bytes;
570 output.extend([TokenTree::Literal(LiteralToken::from_span(
571 kind,
572 self.span_from(start),
573 ))]);
574 Ok(())
575 }
576
577 fn number_radix(&mut self) -> Option<NumberRadix> {
579 let marker = self.peek(0)?;
580 let digit = self.peek(1);
581 match marker {
582 b'b' | b'B' if digit.is_some_and(|byte| NumberRadix::Binary.is_digit(byte)) => {
583 Some(NumberRadix::Binary)
584 }
585 b'o' | b'O' if digit.is_some_and(|byte| NumberRadix::Octal.is_digit(byte)) => {
586 Some(NumberRadix::Octal)
587 }
588 b'x' | b'X' if digit.is_some_and(|byte| NumberRadix::Hexadecimal.is_digit(byte)) => {
589 Some(NumberRadix::Hexadecimal)
590 }
591 _ => None,
592 }
593 }
594
595 fn lex_string(&mut self, output: &mut TokenStream) -> Result<(), LexError> {
597 let start = self.offset;
598 let mut bytes = vec![self.next_or_error(LexErrorKind::UnterminatedString, start)?];
599
600 loop {
601 match self.peek(0) {
602 None => return Err(self.error(LexErrorKind::UnterminatedString, start)),
603 Some(b'"') => {
604 bytes.push(self.next_or_error(LexErrorKind::UnterminatedString, start)?);
605 let _ = bytes;
606 output.extend([TokenTree::Literal(LiteralToken::from_span(
607 LiteralKind::String,
608 self.span_from(start),
609 ))]);
610 return Ok(());
611 }
612 Some(b'\\') => {
613 let escape_start = self.offset;
614 bytes.push(self.next_or_error(LexErrorKind::UnterminatedString, start)?);
615 let Some(escaped) = self.peek(0) else {
616 return Err(self.error(LexErrorKind::UnterminatedString, start));
617 };
618 bytes.push(self.next_or_error(LexErrorKind::UnterminatedString, start)?);
619 match escaped {
620 b'a' | b'b' | b'e' | b'E' | b'f' | b'r' | b't' | b'v' | b'n' | b'"'
621 | b'\\' => {}
622 b'0'..=b'7' => {
623 for _ in 0..2 {
624 if !matches!(self.peek(0), Some(b'0'..=b'7')) {
625 break;
626 }
627 bytes.push(
628 self.next_or_error(LexErrorKind::InvalidEscape, escape_start)?,
629 );
630 }
631 }
632 b'x' => self.take_hex_escape(&mut bytes, 2, escape_start)?,
633 b'u' => self.take_hex_escape(&mut bytes, 4, escape_start)?,
634 _ => return Err(self.error(LexErrorKind::InvalidEscape, escape_start)),
635 }
636 }
637 Some(_) => bytes.push(self.next_or_error(LexErrorKind::UnterminatedString, start)?),
638 }
639 }
640 }
641
642 fn take_hex_escape(
644 &mut self,
645 bytes: &mut Vec<u8>,
646 digits: usize,
647 start: Offset,
648 ) -> Result<(), LexError> {
649 for _ in 0..digits {
650 if !self.peek(0).is_some_and(|byte| byte.is_ascii_hexdigit()) {
651 return Err(self.error(LexErrorKind::InvalidEscape, start));
652 }
653 bytes.push(self.next_or_error(LexErrorKind::InvalidEscape, start)?);
654 }
655 Ok(())
656 }
657
658 fn lex_raw_string(
660 &mut self,
661 opener_len: usize,
662 output: &mut TokenStream,
663 ) -> Result<(), LexError> {
664 let start = self.offset;
665 let mut bytes = self.take_exact(opener_len, start)?;
666
667 loop {
668 if self.peek(0).is_none() {
669 return Err(self.error(LexErrorKind::UnterminatedRawString, start));
670 }
671 if self.peek(0) == Some(b'/') {
672 match self.slash_run_len() {
673 3 => {
674 bytes.extend(self.take_exact(3, start)?);
675 let _ = bytes;
676 output.extend([TokenTree::Literal(LiteralToken::from_span(
677 LiteralKind::RawString,
678 self.span_from(start),
679 ))]);
680 return Ok(());
681 }
682 0 => return Err(self.error(LexErrorKind::UnterminatedRawString, start)),
683 1 => {
684 bytes.push(self.next_or_error(LexErrorKind::UnterminatedRawString, start)?);
685 }
686 _ => bytes.extend(self.take_exact(2, start)?),
687 }
688 } else {
689 bytes.push(self.next_or_error(LexErrorKind::UnterminatedRawString, start)?);
690 }
691 }
692 }
693
694 fn lex_line_comment(
696 &mut self,
697 opener_len: usize,
698 output: &mut TokenStream,
699 ) -> Result<(), LexError> {
700 let start = self.offset;
701 let mut bytes = self.take_exact(opener_len, start)?;
702 loop {
703 match self.peek(0) {
704 None | Some(b'\n') => break,
705 Some(b'\r') if self.byte_after_current() == Some(b'\n') => break,
706 Some(_) => bytes.push(self.next_or_error(LexErrorKind::InvalidCharacter, start)?),
707 }
708 }
709 let _ = bytes;
710 output.push_line_comment(Trivia::from_span(
711 TriviaKind::LineComment,
712 self.span_from(start),
713 ));
714 Ok(())
715 }
716
717 fn lex_block_comment(
719 &mut self,
720 opener_len: usize,
721 output: &mut TokenStream,
722 ) -> Result<(), LexError> {
723 let start = self.offset;
724 let mut bytes = self.take_exact(opener_len, start)?;
725 loop {
726 if self.peek(0).is_none() {
727 return Err(self.error(LexErrorKind::UnterminatedBlockComment, start));
728 }
729 if self.peek(0) == Some(b'*') && self.byte_after_current() == Some(b'-') {
730 bytes.extend(self.take_exact(2, start)?);
731 let _ = bytes;
732 output.push_block_comment(Trivia::from_span(
733 TriviaKind::BlockComment,
734 self.span_from(start),
735 ));
736 return Ok(());
737 }
738 bytes.push(self.next_or_error(LexErrorKind::UnterminatedBlockComment, start)?);
739 }
740 }
741
742 fn match_fixed(&mut self) -> Option<FixedMatch> {
744 fixed_spellings()
745 .filter(|(spelling, _)| {
746 spelling
747 .bytes()
748 .enumerate()
749 .all(|(distance, expected)| self.peek(distance) == Some(expected))
750 })
751 .max_by_key(|(spelling, _)| spelling.len())
752 .map(|(spelling, kind)| FixedMatch {
753 len: spelling.len(),
754 kind,
755 })
756 }
757
758 fn math_operator_width(&mut self) -> Option<usize> {
760 let first = self.peek(0)?;
761 if !matches!(first, 0xc2 | 0xc3 | 0xe2) {
762 return None;
763 }
764 self.peek(1)
765 .filter(|second| is_math_operator_pair(first, *second))
766 .and_then(|_| utf8_char_width(first))
767 }
768
769 fn lex_math_operator(
771 &mut self,
772 width: usize,
773 output: &mut TokenStream,
774 ) -> Result<(), LexError> {
775 let start = self.offset;
776 let mut bytes = self.take_exact(width, start)?;
777 if self.peek(0) == Some(b'=') {
778 bytes.push(self.next_or_error(LexErrorKind::InvalidUtf8, start)?);
779 }
780 self.emit_punctuation(bytes, start, output)
781 }
782
783 fn lex_punctuation(&mut self, len: usize, output: &mut TokenStream) -> Result<(), LexError> {
785 let start = self.offset;
786 let bytes = self.take_exact(len, start)?;
787 self.emit_punctuation(bytes, start, output)
788 }
789
790 fn emit_punctuation(
792 &self,
793 bytes: Vec<u8>,
794 start: Offset,
795 output: &mut TokenStream,
796 ) -> Result<(), LexError> {
797 let _ = bytes;
798 output.extend([TokenTree::Punct(Punct::from_span(self.span_from(start)))]);
799 Ok(())
800 }
801
802 fn byte_after_current(&mut self) -> Option<u8> {
804 self.peek(1)
805 }
806
807 fn slash_run_len(&mut self) -> usize {
809 let mut len = 0;
810 while len < 4 && self.peek(len) == Some(b'/') {
811 len += 1;
812 }
813 len
814 }
815
816 fn take_while(&mut self, predicate: impl Fn(u8) -> bool) -> Vec<u8> {
818 let mut bytes = Vec::new();
819 while self.peek(0).is_some_and(&predicate) {
820 let Some(byte) = self.next() else {
821 break;
822 };
823 bytes.push(byte);
824 }
825 bytes
826 }
827
828 fn take_exact(&mut self, len: usize, start: Offset) -> Result<Vec<u8>, LexError> {
830 let mut bytes = Vec::with_capacity(len);
831 for _ in 0..len {
832 let Some(byte) = self.next() else {
833 return Err(self.error(LexErrorKind::InvalidUtf8, start));
834 };
835 bytes.push(byte);
836 }
837 Ok(bytes)
838 }
839
840 fn next(&mut self) -> Option<u8> {
842 let byte = self.peek(0)?;
843 self.offset += 1;
844 Some(byte)
845 }
846
847 fn peek(&self, distance: usize) -> Option<u8> {
849 self.offset
850 .checked_add(distance)
851 .and_then(|offset| self.source.as_bytes().get(offset))
852 .copied()
853 }
854
855 fn next_or_error(&mut self, kind: LexErrorKind, start: Offset) -> Result<u8, LexError> {
857 match self.next() {
858 Some(byte) => Ok(byte),
859 None => Err(self.error(kind, start)),
860 }
861 }
862
863 fn error(&self, kind: LexErrorKind, start: Offset) -> LexError {
865 LexError {
866 kind,
867 delimiter: None,
868 span: self.span_from(start),
869 }
870 }
871
872 fn span_from(&self, start: Offset) -> Span {
874 self.source_span.subspan(start, self.offset)
875 }
876}
877
878#[derive(Debug, Clone, Copy, PartialEq, Eq)]
879enum RequiredClause {
881 Then,
883 LoopBody,
885 ExceptBody,
887}
888
889pub(crate) fn newline_ends_cell(stream: &TokenStream) -> bool {
891 let mut required_clauses = Vec::new();
892 let mut last_text = None;
893 let mut quote_next = false;
894
895 for tree in stream.iter() {
896 let text = match tree {
897 TokenTree::Ident(token) => Some(token.text()),
898 TokenTree::Punct(token) => Some(token.text()),
899 TokenTree::Literal(_) | TokenTree::Group(_) => {
900 quote_next = false;
901 last_text = None;
902 None
903 }
904 };
905
906 let Some(text) = text else {
907 continue;
908 };
909 if quote_next {
910 quote_next = false;
911 last_text = None;
912 continue;
913 }
914 let text = canonical_word_spelling(text);
915 last_text = Some(text);
916 if is_quote_specifier(text) {
917 quote_next = true;
918 continue;
919 }
920 match text {
921 "if" => required_clauses.push(RequiredClause::Then),
922 "for" | "while" => required_clauses.push(RequiredClause::LoopBody),
923 "except" => required_clauses.push(RequiredClause::ExceptBody),
924 "then" => {
925 remove_last_clause(&mut required_clauses, RequiredClause::Then);
926 }
927 "list" => {
928 remove_last_clause(&mut required_clauses, RequiredClause::LoopBody);
929 }
930 "do" if !remove_last_clause(&mut required_clauses, RequiredClause::ExceptBody) => {
931 remove_last_clause(&mut required_clauses, RequiredClause::LoopBody);
932 }
933 _ => {}
934 }
935 }
936
937 if !required_clauses.is_empty() {
938 return false;
939 }
940
941 let Some(last) = last_text else {
942 return true;
943 };
944 if keyword_requires_value(last) {
945 return false;
946 }
947 !GENERATED_OPERATOR_SPELLINGS.contains(&last)
948 || GENERATED_POSTFIX_OPERATOR_SPELLINGS.contains(&last)
949}
950
951fn remove_last_clause(clauses: &mut Vec<RequiredClause>, clause: RequiredClause) -> bool {
953 let Some(position) = clauses.iter().rposition(|candidate| *candidate == clause) else {
954 return false;
955 };
956 clauses.remove(position);
957 true
958}
959
960fn keyword_requires_value(keyword: &str) -> bool {
962 matches!(
963 keyword,
964 "if" | "then"
965 | "else"
966 | "for"
967 | "while"
968 | "in"
969 | "from"
970 | "to"
971 | "when"
972 | "list"
973 | "do"
974 | "new"
975 | "of"
976 | "try"
977 | "except"
978 | "catch"
979 | "throw"
980 | "trap"
981 | "shield"
982 | "TEST"
983 | "time"
984 | "timing"
985 | "breakpoint"
986 | "elapsedTime"
987 | "elapsedTiming"
988 | "profile"
989 | "symbol"
990 | "local"
991 | "global"
992 | "threadVariable"
993 | "threadLocal"
994 )
995}
996
997fn last_token_quotes_next(stream: &TokenStream) -> bool {
999 matches!(stream.last_innermost(), Some(TokenTree::Ident(token))
1000 if is_quote_specifier(canonical_word_spelling(token.text())))
1001}
1002
1003pub(crate) fn is_quote_specifier(keyword: &str) -> bool {
1005 matches!(
1006 keyword,
1007 "symbol" | "local" | "global" | "threadVariable" | "threadLocal"
1008 )
1009}
1010
1011pub(crate) fn punctuation_would_merge(left: &str, right: &str) -> bool {
1013 let mut combined = String::with_capacity(left.len() + right.len());
1014 combined.push_str(left);
1015 combined.push_str(right);
1016 fixed_spellings()
1017 .any(|(spelling, _)| spelling.len() > left.len() && combined.starts_with(spelling))
1018 || (right.starts_with('=') && is_math_operator(left))
1019}
1020
1021#[derive(Debug)]
1022struct OpenGroup {
1024 kind: DelimiterKind,
1026 opening_start: Offset,
1028}
1029
1030#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1031enum FixedKind {
1033 Punctuation,
1035 LineComment,
1037 BlockComment,
1039 RawString,
1041 Open(DelimiterKind),
1043 Close(DelimiterKind),
1045}
1046
1047const FIXED_SPELLINGS: &[(&str, FixedKind)] = &[
1049 ("///", FixedKind::RawString),
1050 ("--", FixedKind::LineComment),
1051 ("-*", FixedKind::BlockComment),
1052 ("(", FixedKind::Open(DelimiterKind::Parenthesis)),
1053 (")", FixedKind::Close(DelimiterKind::Parenthesis)),
1054 ("[", FixedKind::Open(DelimiterKind::Bracket)),
1055 ("]", FixedKind::Close(DelimiterKind::Bracket)),
1056 ("{", FixedKind::Open(DelimiterKind::Brace)),
1057 ("}", FixedKind::Close(DelimiterKind::Brace)),
1058 ("<|", FixedKind::Open(DelimiterKind::AngleBar)),
1059 ("|>", FixedKind::Close(DelimiterKind::AngleBar)),
1060];
1061
1062fn fixed_spellings() -> impl Iterator<Item = (&'static str, FixedKind)> {
1064 FIXED_SPELLINGS.iter().copied().chain(
1065 GENERATED_PUNCTUATION_SPELLINGS
1066 .iter()
1067 .map(|spelling| (*spelling, FixedKind::Punctuation)),
1068 )
1069}
1070
1071#[derive(Debug, Clone, PartialEq, Eq)]
1072struct FixedMatch {
1074 len: usize,
1076 kind: FixedKind,
1078}
1079
1080#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1081enum NumberRadix {
1083 Binary,
1085 Octal,
1087 Hexadecimal,
1089}
1090
1091impl NumberRadix {
1092 fn is_digit(self, byte: u8) -> bool {
1094 match self {
1095 Self::Binary => matches!(byte, b'0' | b'1'),
1096 Self::Octal => matches!(byte, b'0'..=b'7'),
1097 Self::Hexadecimal => byte.is_ascii_hexdigit(),
1098 }
1099 }
1100}
1101
1102fn is_identifier_start(byte: u8) -> bool {
1104 byte.is_ascii_alphabetic() || byte >= 128
1105}
1106
1107fn is_identifier_continue(byte: u8) -> bool {
1109 byte.is_ascii_alphanumeric() || matches!(byte, b'$' | b'\'') || byte >= 128
1110}
1111
1112fn is_math_operator_pair(first: u8, second: u8) -> bool {
1114 matches!(
1115 (first, second),
1116 (0xc2, 0xa0..=0xbf)
1117 | (0xc3, 0x97 | 0xb7)
1118 | (0xe2, 0x86..=0x8f | 0x9f | 0xa4..=0xaf)
1119 )
1120}
1121
1122fn is_math_operator(spelling: &str) -> bool {
1124 let bytes = spelling.as_bytes();
1125 bytes
1126 .first()
1127 .zip(bytes.get(1))
1128 .is_some_and(|(first, second)| {
1129 is_math_operator_pair(*first, *second) && utf8_char_width(*first) == Some(bytes.len())
1130 })
1131}
1132
1133fn utf8_char_width(first: u8) -> Option<usize> {
1135 match first {
1136 0x00..=0x7f => Some(1),
1137 0xc2..=0xdf => Some(2),
1138 0xe0..=0xef => Some(3),
1139 0xf0..=0xf4 => Some(4),
1140 _ => None,
1141 }
1142}
1143
1144#[cfg(test)]
1145mod tests {
1146 use super::*;
1147
1148 fn tokens(source: &str) -> TokenStream {
1149 let stream = lex_str(source).unwrap();
1150 assert_eq!(stream.to_source(), source);
1151 stream
1152 }
1153
1154 fn span_at(start: usize, text: &str) -> Span {
1155 let source = Source::new(format!("{}{}", " ".repeat(start), text));
1156 Span::ranged(source, start..start + text.len())
1157 }
1158
1159 #[test]
1160 fn lexer_accepts_an_arbitrary_non_peekable_byte_iterator() {
1161 let bytes = vec![b'a', b'+', b'1'].into_iter();
1162 let stream = lex(bytes).unwrap();
1163 assert_eq!(stream.to_source(), "a+1");
1164 }
1165
1166 #[test]
1167 fn every_short_byte_sequence_returns_without_panicking() {
1168 fn check(bytes: &[u8]) {
1169 let outcome = std::panic::catch_unwind(|| lex(bytes.iter().copied()));
1170 assert!(outcome.is_ok(), "lexer panicked for bytes {bytes:?}");
1171 }
1172
1173 check(&[]);
1174 for first in u8::MIN..=u8::MAX {
1175 check(&[first]);
1176 for second in u8::MIN..=u8::MAX {
1177 check(&[first, second]);
1178 }
1179 }
1180 }
1181
1182 #[test]
1183 fn invalid_utf8_is_a_lex_error() {
1184 assert_eq!(
1185 lex([0xf0, 0x80, 0x80]).unwrap_err().kind(),
1186 LexErrorKind::InvalidUtf8
1187 );
1188 }
1189
1190 #[test]
1191 fn punctuation_is_returned_as_maximal_tokens() {
1192 let source = "<<|-1||>2";
1193 let stream = tokens(source);
1194 let punctuation = stream
1195 .iter()
1196 .filter_map(|tree| match tree {
1197 TokenTree::Punct(token) => Some(token.text()),
1198 _ => None,
1199 })
1200 .collect::<Vec<_>>();
1201 let integers = stream
1202 .iter()
1203 .filter_map(|tree| match tree {
1204 TokenTree::Literal(token) if token.kind == LiteralKind::Integer => {
1205 Some(token.text())
1206 }
1207 _ => None,
1208 })
1209 .collect::<Vec<_>>();
1210 assert_eq!(punctuation, ["<<", "|-", "||", ">"]);
1211 assert_eq!(integers, ["1", "2"]);
1212 }
1213
1214 #[test]
1215 fn comment_openers_only_win_at_the_current_position() {
1216 let ordinary = tokens("|--1");
1217 assert!(
1218 ordinary.iter().all(|tree| {
1219 tree.leading_trivia().is_empty() && tree.trailing_trivia().is_empty()
1220 })
1221 );
1222
1223 let source = "-- comment\n-* block *-";
1224 let comments = tokens(source);
1225 assert!(comments.is_empty());
1226 assert!(matches!(
1227 comments.suffix_trivia(),
1228 [line, block]
1229 if line.kind() == TriviaKind::LineComment
1230 && block.kind() == TriviaKind::BlockComment
1231 ));
1232 }
1233
1234 #[test]
1235 fn leading_operator_runs_split_at_maximal_known_spellings() {
1236 let source = "***1";
1237 let stream = tokens(source);
1238 let trees = stream.iter().collect::<Vec<_>>();
1239 assert!(matches!(
1240 trees.as_slice(),
1241 [
1242 first,
1243 second,
1244 TokenTree::Literal(integer),
1245 ] if first.spelling() == Some("**")
1246 && second.spelling() == Some("*")
1247 && integer.kind == LiteralKind::Integer
1248 && integer.text() == "1"
1249 ));
1250 }
1251
1252 #[test]
1253 fn identifiers_follow_the_m2_character_classes() {
1254 let source = "Core$foo'12 1not not1";
1255 let qualified = tokens(source);
1256 let trees = qualified.iter().collect::<Vec<_>>();
1257 assert!(matches!(
1258 trees.as_slice(),
1259 [
1260 TokenTree::Ident(qualified),
1261 TokenTree::Literal(one),
1262 TokenTree::Ident(not),
1263 TokenTree::Ident(not_one),
1264 ] if qualified.text() == "Core$foo'12"
1265 && one.text() == "1"
1266 && not.text() == "not"
1267 && not_one.text() == "not1"
1268 ));
1269
1270 let source = "_name";
1271 let underscored = tokens(source);
1272 let trees = underscored.iter().collect::<Vec<_>>();
1273 assert!(matches!(
1274 trees.as_slice(),
1275 [underscore, TokenTree::Ident(name)]
1276 if underscore.spelling() == Some("_") && name.text() == "name"
1277 ));
1278
1279 let source = "éλ🐻";
1280 let unicode = tokens(source);
1281 assert!(matches!(
1282 unicode.iter().next(),
1283 Some(TokenTree::Ident(identifier)) if identifier.text() == "éλ🐻"
1284 ));
1285
1286 let source = "α⊠β";
1287 let operated = tokens(source);
1288 let trees = operated.iter().collect::<Vec<_>>();
1289 assert!(matches!(
1290 trees.as_slice(),
1291 [
1292 TokenTree::Ident(alpha),
1293 operator,
1294 TokenTree::Ident(beta),
1295 ] if alpha.text() == "α"
1296 && operator.spelling() == Some("⊠")
1297 && beta.text() == "β"
1298 ));
1299 }
1300
1301 #[test]
1302 fn qualified_identifier_metadata_uses_subspans_without_splitting_the_atom() {
1303 let spelling = "Package$name$12";
1304 let stream = tokens(spelling);
1305 assert!(matches!(
1306 stream.iter().collect::<Vec<_>>().as_slice(),
1307 [TokenTree::Ident(_)]
1308 ));
1309
1310 let symbol = crate::Symbol::new(spelling, span_at(100, spelling));
1311 assert_eq!(
1312 symbol.package_qualifier.as_ref().and_then(Span::bounds),
1313 Some((100, 107))
1314 );
1315 assert_eq!(
1316 symbol.index_qualifier.as_ref().and_then(Span::bounds),
1317 Some((113, 115))
1318 );
1319 assert!(matches!(
1320 crate::Lex::to_token_stream(&symbol)
1321 .iter()
1322 .collect::<Vec<_>>()
1323 .as_slice(),
1324 [TokenTree::Ident(identifier)] if identifier.text() == spelling
1325 ));
1326
1327 let package = crate::Symbol::new("Package$name", span_at(20, "Package$name"));
1328 assert_eq!(
1329 package.package_qualifier.as_ref().and_then(Span::bounds),
1330 Some((20, 27))
1331 );
1332 assert_eq!(package.index_qualifier, None);
1333
1334 let indexed = crate::Symbol::new("name$12", span_at(30, "name$12"));
1335 assert_eq!(indexed.package_qualifier, None);
1336 assert_eq!(
1337 indexed.index_qualifier.as_ref().and_then(Span::bounds),
1338 Some((35, 37))
1339 );
1340
1341 for unqualified in ["name", "A$B$C", "A$$B", "A$", "A$1B", "Core$Core$not"] {
1342 let symbol = crate::Symbol::new(unqualified, Span::for_text(unqualified));
1343 assert_eq!(symbol.package_qualifier, None, "`{unqualified}`");
1344 assert_eq!(symbol.index_qualifier, None, "`{unqualified}`");
1345 }
1346 }
1347
1348 #[test]
1349 fn radix_literals_stop_before_the_first_invalid_digit() {
1350 let source = "0b010201";
1351 let binary = tokens(source);
1352 let trees = binary.iter().collect::<Vec<_>>();
1353 assert!(matches!(
1354 trees.as_slice(),
1355 [
1356 TokenTree::Literal(binary),
1357 TokenTree::Literal(decimal),
1358 ]
1359 if binary.text() == "0b010" && decimal.text() == "201"
1360 ));
1361
1362 let source = "0o70789";
1363 let octal = tokens(source);
1364 let trees = octal.iter().collect::<Vec<_>>();
1365 assert!(matches!(
1366 trees.as_slice(),
1367 [
1368 TokenTree::Literal(octal),
1369 TokenTree::Literal(decimal),
1370 ]
1371 if octal.text() == "0o707" && decimal.text() == "89"
1372 ));
1373
1374 let source = "0b1E2";
1375 let binary_then_identifier = tokens(source);
1376 let trees = binary_then_identifier.iter().collect::<Vec<_>>();
1377 assert!(matches!(
1378 trees.as_slice(),
1379 [
1380 TokenTree::Literal(binary),
1381 TokenTree::Ident(identifier),
1382 ]
1383 if binary.text() == "0b1" && identifier.text() == "E2"
1384 ));
1385 }
1386
1387 #[test]
1388 fn spans_preserve_physical_separation_between_punctuation_tokens() {
1389 let source = "++x +\n+";
1390 let stream = tokens(source);
1391 let trees = stream.iter().collect::<Vec<_>>();
1392 assert!(matches!(
1393 trees.as_slice(),
1394 [
1395 increment,
1396 TokenTree::Ident(identifier),
1397 first_plus,
1398 second_plus,
1399 ] if increment.spelling() == Some("++")
1400 && identifier.text() == "x"
1401 && first_plus.spelling() == Some("+")
1402 && second_plus.spelling() == Some("+")
1403 ));
1404 let first_plus = trees[2].expect_span();
1405 let second_plus = trees[3].expect_span();
1406 assert_eq!(
1407 &first_plus.source().text()[first_plus.end().unwrap()..second_plus.start().unwrap()],
1408 "\n"
1409 );
1410 }
1411
1412 #[test]
1413 fn strings_and_raw_strings_are_literal_token_trees() {
1414 for (source, kind) in [
1415 (r#""a\n\u0101""#, LiteralKind::String),
1416 ("///left////right///", LiteralKind::RawString),
1417 ("///left//right/single///", LiteralKind::RawString),
1418 ] {
1419 let stream = tokens(source);
1420 assert!(matches!(
1421 stream.iter().next(),
1422 Some(TokenTree::Literal(literal))
1423 if literal.kind == kind && literal.text() == source
1424 ));
1425 }
1426 }
1427
1428 #[test]
1429 fn structural_delimiters_form_nested_groups_with_exact_spans() {
1430 let source = "(1, [2, <|3|>])";
1431 let stream = tokens(source);
1432 let mut trees = stream.into_iter();
1433 let Some(TokenTree::Group(parenthesized)) = trees.next() else {
1434 panic!("the source should be one parenthesized group");
1435 };
1436 assert!(trees.next().is_none());
1437 assert_eq!(parenthesized.delim_kind(), DelimiterKind::Parenthesis);
1438
1439 let span = parenthesized.expect_span();
1440 assert_eq!(span.start().unwrap(), 0);
1441 assert_eq!(span.end().unwrap(), 15);
1442 assert_eq!(parenthesized.delimiter().opening_span().end().unwrap(), 1);
1443 assert_eq!(
1444 parenthesized
1445 .delimiter()
1446 .closing_span()
1447 .unwrap()
1448 .start()
1449 .unwrap(),
1450 14
1451 );
1452
1453 let bracketed = parenthesized
1454 .stream()
1455 .iter()
1456 .find_map(|tree| match tree {
1457 TokenTree::Group(group) => Some(group),
1458 _ => None,
1459 })
1460 .unwrap();
1461 assert_eq!(bracketed.delim_kind(), DelimiterKind::Bracket);
1462 assert_eq!(bracketed.delimiter().opening_span().start().unwrap(), 4);
1463 assert_eq!(
1464 bracketed
1465 .delimiter()
1466 .closing_span()
1467 .unwrap()
1468 .start()
1469 .unwrap(),
1470 13
1471 );
1472
1473 let angle_bar = bracketed
1474 .stream()
1475 .iter()
1476 .find_map(|tree| match tree {
1477 TokenTree::Group(group) => Some(group),
1478 _ => None,
1479 })
1480 .unwrap();
1481 assert_eq!(angle_bar.delim_kind(), DelimiterKind::AngleBar);
1482 let opening = angle_bar.delimiter().opening_span();
1483 let closing = angle_bar.delimiter().closing_span().unwrap();
1484 assert_eq!(opening.start().unwrap(), 8);
1485 assert_eq!(opening.end().unwrap(), 10);
1486 assert_eq!(closing.start().unwrap(), 11);
1487 assert_eq!(closing.end().unwrap(), 13);
1488 }
1489
1490 #[test]
1491 fn graded_operator_wins_before_parenthesis_grouping() {
1492 let source = "(*)";
1493 let graded = tokens(source);
1494 let trees = graded.iter().collect::<Vec<_>>();
1495 assert!(matches!(
1496 trees.as_slice(),
1497 [graded] if graded.spelling() == Some("(*)")
1498 ));
1499
1500 let grouped = tokens("(*x)");
1501 assert!(matches!(grouped.iter().next(), Some(TokenTree::Group(_))));
1502 }
1503
1504 #[test]
1505 fn malformed_source_returns_lex_errors() {
1506 for (source, kind) in [
1507 ("\"\\q\"", LexErrorKind::InvalidEscape),
1508 ("(1", LexErrorKind::UnterminatedGroup),
1509 (")", LexErrorKind::UnexpectedClosingDelimiter),
1510 ("(]", LexErrorKind::UnexpectedClosingDelimiter),
1511 ("///", LexErrorKind::UnterminatedRawString),
1512 ("-* unterminated", LexErrorKind::UnterminatedBlockComment),
1513 ] {
1514 assert_eq!(lex_str(source).unwrap_err().kind(), kind);
1515 }
1516
1517 let nested = "(".repeat(MAX_GROUP_DEPTH + 1);
1518 assert_eq!(
1519 lex_str(&nested).unwrap_err().kind(),
1520 LexErrorKind::NestingLimitExceeded
1521 );
1522 }
1523
1524 #[test]
1525 fn recovering_lexer_retains_unclosed_string_literals() {
1526 for (source, kind, expected) in [
1527 ("\"", LexErrorKind::UnterminatedString, "\""),
1528 ("\"unfinished", LexErrorKind::UnterminatedString, "\""),
1529 ("///", LexErrorKind::UnterminatedRawString, "///"),
1530 ("///unfinished", LexErrorKind::UnterminatedRawString, "///"),
1531 ] {
1532 let recovered = lex_str_recovering(source).unwrap();
1533
1534 assert_eq!(recovered.value.to_string(), source);
1535 assert_eq!(recovered.diagnostics.len(), 1);
1536 assert_eq!(recovered.diagnostics[0].kind(), kind);
1537 assert_eq!(recovered.diagnostics[0].expected(), Some(expected));
1538 assert_eq!(recovered.diagnostics[0].found(), Some("end of input"));
1539 }
1540 }
1541
1542 #[test]
1543 fn a_crlf_line_break_counts_as_the_two_bytes_it_is() {
1544 let source = "x\r\ny";
1545 let stream = tokens(source);
1546 let range = stream
1547 .iter()
1548 .find_map(|tree| match tree {
1549 TokenTree::Ident(identifier) if identifier.text() == "y" => {
1550 Some(identifier.expect_span())
1551 }
1552 _ => None,
1553 })
1554 .unwrap();
1555 assert_eq!(range.bounds(), Some((3, 4)));
1557
1558 let error = lex_str("-* x\r\n").unwrap_err();
1559 assert_eq!(error.expect_span().end(), Some("-* x\r\n".len()));
1560 }
1561
1562 #[test]
1563 fn identifier_classes_match_m2_byte_ctype() {
1564 for byte in [b'a', b'Z', 0x80, 0xff] {
1565 assert!(is_identifier_start(byte));
1566 assert!(is_identifier_continue(byte));
1567 }
1568 for byte in *b"09$'" {
1569 assert!(!is_identifier_start(byte));
1570 assert!(is_identifier_continue(byte));
1571 }
1572 for byte in *b"_ +" {
1573 assert!(!is_identifier_start(byte));
1574 assert!(!is_identifier_continue(byte));
1575 }
1576 }
1577
1578 #[test]
1579 fn math_operator_prefixes_match_m2_ranges() {
1580 for pair in [
1581 (0xc2, 0xa0),
1582 (0xc2, 0xbf),
1583 (0xc3, 0x97),
1584 (0xc3, 0xb7),
1585 (0xe2, 0x86),
1586 (0xe2, 0x87),
1587 (0xe2, 0x88),
1588 (0xe2, 0x8f),
1589 (0xe2, 0x9f),
1590 (0xe2, 0xa4),
1591 (0xe2, 0xa7),
1592 (0xe2, 0xa8),
1593 (0xe2, 0xaf),
1594 ] {
1595 assert!(is_math_operator_pair(pair.0, pair.1));
1596 }
1597 for pair in [
1598 (0xc2, 0x9f),
1599 (0xc3, 0x96),
1600 (0xc3, 0x98),
1601 (0xe2, 0x85),
1602 (0xe2, 0x90),
1603 (0xe2, 0x9e),
1604 (0xe2, 0xa3),
1605 (0xe2, 0xb0),
1606 ] {
1607 assert!(!is_math_operator_pair(pair.0, pair.1));
1608 }
1609 }
1610
1611 #[test]
1612 fn utf8_width_rejects_continuations_and_invalid_leads() {
1613 assert_eq!(utf8_char_width(b'a'), Some(1));
1614 assert_eq!(utf8_char_width(0xc2), Some(2));
1615 assert_eq!(utf8_char_width(0xe2), Some(3));
1616 assert_eq!(utf8_char_width(0xf0), Some(4));
1617 assert_eq!(utf8_char_width(0x80), None);
1618 assert_eq!(utf8_char_width(0xc0), None);
1619 assert_eq!(utf8_char_width(0xf5), None);
1620 }
1621
1622 #[test]
1623 fn ordinary_strings_accept_exact_m2_escape_widths() {
1624 for source in [r#""\u0101""#, r#""\x4f""#, r#""\1\12\123""#, r#""\n\"\\""#] {
1625 let stream = tokens(source);
1626 assert!(matches!(
1627 stream.iter().next(),
1628 Some(TokenTree::Literal(literal))
1629 if literal.kind == LiteralKind::String && literal.text() == source
1630 ));
1631 }
1632 for source in [r#""\u101""#, r#""\u010g""#, r#""\x4""#] {
1633 assert_eq!(
1634 lex_str(source).unwrap_err().kind(),
1635 LexErrorKind::InvalidEscape
1636 );
1637 }
1638 }
1639}