Skip to main content

m2_syn/lex/
lexer.rs

1//! Native source-to-token conversion.
2//!
3//! This module owns byte classification, maximal munch, delimiter balancing,
4//! and top-level cell splitting. Its output uses only the shared raw atoms from
5//! `token_stream`; it never constructs a second lexer-specific token model.
6//! Spans retain source identity and byte ranges, while lexical atoms retain the
7//! spellings needed for parser-independent token processing.
8
9use 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
21/// The MAX GROUP DEPTH constant.
22const MAX_GROUP_DEPTH: usize = 256;
23
24/// Collapses an explicitly `Core$`-qualified identifier back to its bare
25/// spelling when that spelling is one of the hardcoded word-token aliases.
26pub(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
74/// Returns whether the value is bare symbol name.
75fn 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
87/// Returns whether the value is decimal index.
88fn 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)]
93/// The class of a tokenization failure.
94pub enum LexErrorKind {
95    /// A byte cannot begin any M2 token.
96    InvalidCharacter,
97    /// A string contains an invalid escape sequence.
98    InvalidEscape,
99    /// A numeric spelling is malformed.
100    InvalidNumber,
101    /// The byte input is not valid UTF-8.
102    InvalidUtf8,
103    /// Delimiter nesting exceeded the defensive parser limit.
104    NestingLimitExceeded,
105    /// A closing delimiter has no matching opener.
106    UnexpectedClosingDelimiter,
107    /// Input ended before a delimited group closed.
108    UnterminatedGroup,
109    /// Input ended inside a string literal.
110    UnterminatedString,
111    /// Input ended inside a raw string literal.
112    UnterminatedRawString,
113    /// Input ended inside a block comment.
114    UnterminatedBlockComment,
115}
116
117#[derive(Debug, Clone, PartialEq, Eq, Hash, Spanned)]
118/// A tokenization failure and the source span that caused it.
119pub struct LexError {
120    /// The kind.
121    kind: LexErrorKind,
122    /// The delimiter.
123    delimiter: Option<DelimiterKind>,
124    /// The span.
125    span: Span,
126}
127
128impl LexError {
129    /// Returns the error class.
130    pub fn kind(&self) -> LexErrorKind {
131        self.kind
132    }
133
134    /// Returns the unclosed expression family for a missing closer.
135    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    /// Returns the token spelling required to complete this error.
147    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    /// Returns a readable description of what terminated this error.
158    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
204/// Lexes bytes into one source-spanned stream of token trees.
205///
206/// Malformed source is always reported as [`LexError`]; byte contents do not
207/// cause this function to panic. A user-provided iterator may still panic from
208/// its own [`Iterator::next`] implementation.
209///
210/// # Errors
211///
212/// Returns [`LexError`] for malformed UTF-8, literals, comments, delimiters,
213/// or characters that Macaulay2 cannot tokenize.
214pub fn lex<I>(bytes: I) -> Result<TokenStream, LexError>
215where
216    I: IntoIterator<Item = u8>,
217{
218    lexer_from_bytes(bytes)?.lex()
219}
220
221/// Lexes bytes while retaining groups whose closing delimiter is missing.
222///
223/// Every missing closer becomes an unbounded closing boundary.
224/// The corresponding [`LexErrorKind::UnterminatedGroup`] diagnostic spans from
225/// that group's opening delimiter through end of input. Other lexical failures
226/// remain fatal.
227///
228/// # Errors
229///
230/// Returns [`LexError`] for malformed UTF-8, literals, comments, unmatched
231/// closing delimiters, invalid characters, or excessive delimiter nesting.
232pub 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
239/// Lexes M2 source text into its source-spanned token tree.
240///
241/// # Errors
242///
243/// Returns [`LexError`] when the source contains malformed lexical syntax.
244pub fn lex_str(source: &str) -> Result<TokenStream, LexError> {
245    NativeLexer::new(Arc::<str>::from(source)).lex()
246}
247
248/// Lexes source text while retaining unclosed groups and string literals.
249///
250/// # Errors
251///
252/// Returns [`LexError`] for malformed lexical syntax other than missing group
253/// or string-literal closers, which are returned in
254/// [`Recovered::diagnostics`].
255pub fn lex_str_recovering(source: &str) -> Result<Recovered<TokenStream, LexError>, LexError> {
256    NativeLexer::new(Arc::<str>::from(source)).lex_recovering()
257}
258
259/// Lexes an existing snapshot while retaining unclosed groups and string
260/// literals, so every span of the result points into `source`.
261#[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/// Lexes one statically spelled fragment emitted by [`crate::quote_m2!`].
269///
270/// Procedural-macro input arrives as Rust token trees, so the macro first joins
271/// each uninterrupted static run into M2 source text. Sending that text through
272/// the real lexer keeps quoted literals, identifier rules, and maximal
273/// punctuation exactly aligned with ordinary source parsing.
274///
275/// # Panics
276///
277/// Panics when a static fragment accepted by Rust's tokenization is not valid
278/// M2 lexical syntax. Interpolate a pre-built [`TokenStream`] when its source
279/// spelling cannot be represented directly in Rust macro input.
280#[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
287/// Handles lexer from bytes.
288fn 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
302/// Internal state for NativeLexer.
303struct NativeLexer {
304    /// The source.
305    source: Arc<str>,
306    /// The source span.
307    source_span: Span,
308    /// The offset.
309    offset: Offset,
310}
311
312impl NativeLexer {
313    /// Creates a lexer over an existing snapshot, so every span it produces
314    /// points into `source`.
315    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    /// Creates a new value.
324    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    /// Handles lex.
334    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    /// Lexes recovering.
343    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        // A quote specifier takes the following token literally, so that token
348        // is not interpreted as structure. This is the one grammar fact the
349        // lexer needs: without it `symbol (` opens a group and `symbol ;` ends
350        // a cell, neither of which M2 does.
351        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                    // A quoted bracket is an ordinary token, not a group
403                    // boundary; the parser reads it as the quoted symbol.
404                    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                    // A comment does not consume the pending quote: M2 reads
411                    // `symbol -- note⏎x` as the quoted `x`.
412                    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    /// Lexes line break.
476    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    /// Lexes carriage return.
488    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    /// Lexes whitespace.
496    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    /// Lexes identifier.
503    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    /// Lexes number.
520    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    /// Handles number radix.
578    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    /// Lexes string.
596    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    /// Takes hex escape.
643    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    /// Lexes raw string.
659    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    /// Lexes line comment.
695    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    /// Lexes block comment.
718    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    /// Handles match fixed.
743    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    /// Handles math operator width.
759    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    /// Lexes math operator.
770    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    /// Lexes punctuation.
784    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    /// Emits punctuation.
791    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    /// Handles byte after current.
803    fn byte_after_current(&mut self) -> Option<u8> {
804        self.peek(1)
805    }
806
807    /// Handles slash run len.
808    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    /// Takes while.
817    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    /// Takes exact.
829    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    /// Handles next.
841    fn next(&mut self) -> Option<u8> {
842        let byte = self.peek(0)?;
843        self.offset += 1;
844        Some(byte)
845    }
846
847    /// Handles peek.
848    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    /// Handles next or error.
856    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    /// Handles error.
864    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    /// The span running from `start` to wherever the cursor has reached.
873    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)]
879/// Internal alternatives for RequiredClause.
880enum RequiredClause {
881    /// The Then alternative.
882    Then,
883    /// The LoopBody alternative.
884    LoopBody,
885    /// The ExceptBody alternative.
886    ExceptBody,
887}
888
889/// Handles newline ends cell.
890pub(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
951/// Removes last clause.
952fn 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
960/// Handles keyword requires value.
961fn 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
997/// Whether the token just lexed quotes the one after it.
998fn 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
1003/// Returns whether the value is quote specifier.
1004pub(crate) fn is_quote_specifier(keyword: &str) -> bool {
1005    matches!(
1006        keyword,
1007        "symbol" | "local" | "global" | "threadVariable" | "threadLocal"
1008    )
1009}
1010
1011/// Handles punctuation would merge.
1012pub(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)]
1022/// Internal state for OpenGroup.
1023struct OpenGroup {
1024    /// The kind.
1025    kind: DelimiterKind,
1026    /// The opening start.
1027    opening_start: Offset,
1028}
1029
1030#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1031/// Internal alternatives for FixedKind.
1032enum FixedKind {
1033    /// The Punctuation alternative.
1034    Punctuation,
1035    /// The LineComment alternative.
1036    LineComment,
1037    /// The BlockComment alternative.
1038    BlockComment,
1039    /// The RawString alternative.
1040    RawString,
1041    /// The Open alternative.
1042    Open(DelimiterKind),
1043    /// The Close alternative.
1044    Close(DelimiterKind),
1045}
1046
1047/// The FIXED SPELLINGS constant.
1048const 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
1062/// Handles fixed spellings.
1063fn 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)]
1072/// Internal state for FixedMatch.
1073struct FixedMatch {
1074    /// The len.
1075    len: usize,
1076    /// The kind.
1077    kind: FixedKind,
1078}
1079
1080#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1081/// Internal alternatives for NumberRadix.
1082enum NumberRadix {
1083    /// The Binary alternative.
1084    Binary,
1085    /// The Octal alternative.
1086    Octal,
1087    /// The Hexadecimal alternative.
1088    Hexadecimal,
1089}
1090
1091impl NumberRadix {
1092    /// Returns whether the value is digit.
1093    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
1102/// Returns whether the value is identifier start.
1103fn is_identifier_start(byte: u8) -> bool {
1104    byte.is_ascii_alphabetic() || byte >= 128
1105}
1106
1107/// Returns whether the value is identifier continue.
1108fn is_identifier_continue(byte: u8) -> bool {
1109    byte.is_ascii_alphanumeric() || matches!(byte, b'$' | b'\'') || byte >= 128
1110}
1111
1112/// Returns whether the value is math operator pair.
1113fn 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
1122/// Returns whether the value is math operator.
1123fn 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
1133/// Handles UTF-8 char width.
1134fn 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        // `x` `\r` `\n` `y`, so `y` occupies byte 3 alone.
1556        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}