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