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