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        while let Some(byte) = self.peek(0) {
445            if !is_identifier_continue(byte) || self.math_operator_width().is_some() {
446                break;
447            }
448            bytes.push(self.next_or_error(LexErrorKind::InvalidUtf8, start)?);
449        }
450        let _ = bytes;
451        output.extend([TokenTree::Ident(IdentToken::from_span(
452            self.span_from(start),
453        ))]);
454        Ok(())
455    }
456
457    /// Lexes number.
458    fn lex_number(&mut self, output: &mut TokenStream) -> Result<(), LexError> {
459        let start = self.offset;
460        let mut bytes = Vec::new();
461        let mut decimal = true;
462
463        if self.peek(0) == Some(b'0') {
464            bytes.push(self.next_or_error(LexErrorKind::InvalidNumber, start)?);
465            if let Some(radix) = self.number_radix() {
466                decimal = false;
467                bytes.push(self.next_or_error(LexErrorKind::InvalidNumber, start)?);
468                bytes.extend(self.take_while(|byte| radix.is_digit(byte)));
469            } else {
470                bytes.extend(self.take_while(|byte| byte.is_ascii_digit()));
471            }
472        } else {
473            bytes.extend(self.take_while(|byte| byte.is_ascii_digit()));
474        }
475
476        let next = self.peek(0);
477        let begins_float = decimal
478            && (next == Some(b'.') && self.byte_after_current() != Some(b'.')
479                || matches!(next, Some(b'p' | b'e' | b'E')));
480        let kind = if begins_float {
481            if self.peek(0) == Some(b'.') {
482                bytes.push(self.next_or_error(LexErrorKind::InvalidNumber, start)?);
483                bytes.extend(self.take_while(|byte| byte.is_ascii_digit()));
484            }
485            if self.peek(0) == Some(b'p') {
486                bytes.push(self.next_or_error(LexErrorKind::InvalidNumber, start)?);
487                if !self.peek(0).is_some_and(|byte| byte.is_ascii_digit()) {
488                    return Err(self.error(LexErrorKind::InvalidNumber, start));
489                }
490                bytes.extend(self.take_while(|byte| byte.is_ascii_digit()));
491            }
492            if matches!(self.peek(0), Some(b'e' | b'E')) {
493                bytes.push(self.next_or_error(LexErrorKind::InvalidNumber, start)?);
494                if matches!(self.peek(0), Some(b'+' | b'-')) {
495                    bytes.push(self.next_or_error(LexErrorKind::InvalidNumber, start)?);
496                }
497                if !self.peek(0).is_some_and(|byte| byte.is_ascii_digit()) {
498                    return Err(self.error(LexErrorKind::InvalidNumber, start));
499                }
500                bytes.extend(self.take_while(|byte| byte.is_ascii_digit()));
501            }
502            LiteralKind::Float
503        } else {
504            LiteralKind::Integer
505        };
506
507        let _ = bytes;
508        output.extend([TokenTree::Literal(LiteralToken::from_span(
509            kind,
510            self.span_from(start),
511        ))]);
512        Ok(())
513    }
514
515    /// Handles number radix.
516    fn number_radix(&mut self) -> Option<NumberRadix> {
517        let marker = self.peek(0)?;
518        let digit = self.peek(1);
519        match marker {
520            b'b' | b'B' if digit.is_some_and(|byte| NumberRadix::Binary.is_digit(byte)) => {
521                Some(NumberRadix::Binary)
522            }
523            b'o' | b'O' if digit.is_some_and(|byte| NumberRadix::Octal.is_digit(byte)) => {
524                Some(NumberRadix::Octal)
525            }
526            b'x' | b'X' if digit.is_some_and(|byte| NumberRadix::Hexadecimal.is_digit(byte)) => {
527                Some(NumberRadix::Hexadecimal)
528            }
529            _ => None,
530        }
531    }
532
533    /// Lexes string.
534    fn lex_string(&mut self, output: &mut TokenStream) -> Result<(), LexError> {
535        let start = self.offset;
536        let mut bytes = vec![self.next_or_error(LexErrorKind::UnterminatedString, start)?];
537
538        loop {
539            match self.peek(0) {
540                None => return Err(self.error(LexErrorKind::UnterminatedString, start)),
541                Some(b'"') => {
542                    bytes.push(self.next_or_error(LexErrorKind::UnterminatedString, start)?);
543                    let _ = bytes;
544                    output.extend([TokenTree::Literal(LiteralToken::from_span(
545                        LiteralKind::String,
546                        self.span_from(start),
547                    ))]);
548                    return Ok(());
549                }
550                Some(b'\\') => {
551                    let escape_start = self.offset;
552                    bytes.push(self.next_or_error(LexErrorKind::UnterminatedString, start)?);
553                    let Some(escaped) = self.peek(0) else {
554                        return Err(self.error(LexErrorKind::UnterminatedString, start));
555                    };
556                    bytes.push(self.next_or_error(LexErrorKind::UnterminatedString, start)?);
557                    match escaped {
558                        b'a' | b'b' | b'e' | b'E' | b'f' | b'r' | b't' | b'v' | b'n' | b'"'
559                        | b'\\' => {}
560                        b'0'..=b'7' => {
561                            for _ in 0..2 {
562                                if !matches!(self.peek(0), Some(b'0'..=b'7')) {
563                                    break;
564                                }
565                                bytes.push(
566                                    self.next_or_error(LexErrorKind::InvalidEscape, escape_start)?,
567                                );
568                            }
569                        }
570                        b'x' => self.take_hex_escape(&mut bytes, 2, escape_start)?,
571                        b'u' => self.take_hex_escape(&mut bytes, 4, escape_start)?,
572                        _ => return Err(self.error(LexErrorKind::InvalidEscape, escape_start)),
573                    }
574                }
575                Some(_) => bytes.push(self.next_or_error(LexErrorKind::UnterminatedString, start)?),
576            }
577        }
578    }
579
580    /// Takes hex escape.
581    fn take_hex_escape(
582        &mut self,
583        bytes: &mut Vec<u8>,
584        digits: usize,
585        start: Offset,
586    ) -> Result<(), LexError> {
587        for _ in 0..digits {
588            if !self.peek(0).is_some_and(|byte| byte.is_ascii_hexdigit()) {
589                return Err(self.error(LexErrorKind::InvalidEscape, start));
590            }
591            bytes.push(self.next_or_error(LexErrorKind::InvalidEscape, start)?);
592        }
593        Ok(())
594    }
595
596    /// Lexes raw string.
597    fn lex_raw_string(
598        &mut self,
599        opener_len: usize,
600        output: &mut TokenStream,
601    ) -> Result<(), LexError> {
602        let start = self.offset;
603        let mut bytes = self.take_exact(opener_len, start)?;
604
605        loop {
606            if self.peek(0).is_none() {
607                return Err(self.error(LexErrorKind::UnterminatedRawString, start));
608            }
609            if self.peek(0) == Some(b'/') {
610                match self.slash_run_len() {
611                    3 => {
612                        bytes.extend(self.take_exact(3, start)?);
613                        let _ = bytes;
614                        output.extend([TokenTree::Literal(LiteralToken::from_span(
615                            LiteralKind::RawString,
616                            self.span_from(start),
617                        ))]);
618                        return Ok(());
619                    }
620                    0 => return Err(self.error(LexErrorKind::UnterminatedRawString, start)),
621                    1 => {
622                        bytes.push(self.next_or_error(LexErrorKind::UnterminatedRawString, start)?);
623                    }
624                    _ => bytes.extend(self.take_exact(2, start)?),
625                }
626            } else {
627                bytes.push(self.next_or_error(LexErrorKind::UnterminatedRawString, start)?);
628            }
629        }
630    }
631
632    /// Lexes line comment.
633    fn lex_line_comment(
634        &mut self,
635        opener_len: usize,
636        output: &mut TokenStream,
637    ) -> Result<(), LexError> {
638        let start = self.offset;
639        let mut bytes = self.take_exact(opener_len, start)?;
640        loop {
641            match self.peek(0) {
642                None | Some(b'\n') => break,
643                Some(b'\r') if self.byte_after_current() == Some(b'\n') => break,
644                Some(_) => bytes.push(self.next_or_error(LexErrorKind::InvalidCharacter, start)?),
645            }
646        }
647        let _ = bytes;
648        output.push_line_comment(Trivia::from_span(
649            TriviaKind::LineComment,
650            self.span_from(start),
651        ));
652        Ok(())
653    }
654
655    /// Lexes block comment.
656    fn lex_block_comment(
657        &mut self,
658        opener_len: usize,
659        output: &mut TokenStream,
660    ) -> Result<(), LexError> {
661        let start = self.offset;
662        let mut bytes = self.take_exact(opener_len, start)?;
663        loop {
664            if self.peek(0).is_none() {
665                return Err(self.error(LexErrorKind::UnterminatedBlockComment, start));
666            }
667            if self.peek(0) == Some(b'*') && self.byte_after_current() == Some(b'-') {
668                bytes.extend(self.take_exact(2, start)?);
669                let _ = bytes;
670                output.push_block_comment(Trivia::from_span(
671                    TriviaKind::BlockComment,
672                    self.span_from(start),
673                ));
674                return Ok(());
675            }
676            bytes.push(self.next_or_error(LexErrorKind::UnterminatedBlockComment, start)?);
677        }
678    }
679
680    /// Handles match fixed.
681    fn match_fixed(&mut self) -> Option<FixedMatch> {
682        fixed_spellings()
683            .filter(|(spelling, _)| {
684                spelling
685                    .bytes()
686                    .enumerate()
687                    .all(|(distance, expected)| self.peek(distance) == Some(expected))
688            })
689            .max_by_key(|(spelling, _)| spelling.len())
690            .map(|(spelling, kind)| FixedMatch {
691                len: spelling.len(),
692                kind,
693            })
694    }
695
696    /// Handles math operator width.
697    fn math_operator_width(&mut self) -> Option<usize> {
698        let first = self.peek(0)?;
699        if !matches!(first, 0xc2 | 0xc3 | 0xe2) {
700            return None;
701        }
702        self.peek(1)
703            .filter(|second| is_math_operator_pair(first, *second))
704            .and_then(|_| utf8_char_width(first))
705    }
706
707    /// Lexes math operator.
708    fn lex_math_operator(
709        &mut self,
710        width: usize,
711        output: &mut TokenStream,
712    ) -> Result<(), LexError> {
713        let start = self.offset;
714        let mut bytes = self.take_exact(width, start)?;
715        if self.peek(0) == Some(b'=') {
716            bytes.push(self.next_or_error(LexErrorKind::InvalidUtf8, start)?);
717        }
718        self.emit_punctuation(bytes, start, output)
719    }
720
721    /// Lexes punctuation.
722    fn lex_punctuation(&mut self, len: usize, output: &mut TokenStream) -> Result<(), LexError> {
723        let start = self.offset;
724        let bytes = self.take_exact(len, start)?;
725        self.emit_punctuation(bytes, start, output)
726    }
727
728    /// Emits punctuation.
729    fn emit_punctuation(
730        &self,
731        bytes: Vec<u8>,
732        start: Offset,
733        output: &mut TokenStream,
734    ) -> Result<(), LexError> {
735        let _ = bytes;
736        output.extend([TokenTree::Punct(Punct::from_span(self.span_from(start)))]);
737        Ok(())
738    }
739
740    /// Handles byte after current.
741    fn byte_after_current(&mut self) -> Option<u8> {
742        self.peek(1)
743    }
744
745    /// Handles slash run len.
746    fn slash_run_len(&mut self) -> usize {
747        let mut len = 0;
748        while len < 4 && self.peek(len) == Some(b'/') {
749            len += 1;
750        }
751        len
752    }
753
754    /// Takes while.
755    fn take_while(&mut self, predicate: impl Fn(u8) -> bool) -> Vec<u8> {
756        let mut bytes = Vec::new();
757        while self.peek(0).is_some_and(&predicate) {
758            let Some(byte) = self.next() else {
759                break;
760            };
761            bytes.push(byte);
762        }
763        bytes
764    }
765
766    /// Takes exact.
767    fn take_exact(&mut self, len: usize, start: Offset) -> Result<Vec<u8>, LexError> {
768        let mut bytes = Vec::with_capacity(len);
769        for _ in 0..len {
770            let Some(byte) = self.next() else {
771                return Err(self.error(LexErrorKind::InvalidUtf8, start));
772            };
773            bytes.push(byte);
774        }
775        Ok(bytes)
776    }
777
778    /// Handles next.
779    fn next(&mut self) -> Option<u8> {
780        let byte = self.peek(0)?;
781        self.offset += 1;
782        Some(byte)
783    }
784
785    /// Handles peek.
786    fn peek(&self, distance: usize) -> Option<u8> {
787        self.offset
788            .checked_add(distance)
789            .and_then(|offset| self.source.as_bytes().get(offset))
790            .copied()
791    }
792
793    /// Handles next or error.
794    fn next_or_error(&mut self, kind: LexErrorKind, start: Offset) -> Result<u8, LexError> {
795        match self.next() {
796            Some(byte) => Ok(byte),
797            None => Err(self.error(kind, start)),
798        }
799    }
800
801    /// Handles error.
802    fn error(&self, kind: LexErrorKind, start: Offset) -> LexError {
803        LexError {
804            kind,
805            delimiter: None,
806            span: self.span_from(start),
807        }
808    }
809
810    /// The span running from `start` to wherever the cursor has reached.
811    fn span_from(&self, start: Offset) -> Span {
812        self.source_span.subspan(start, self.offset)
813    }
814}
815
816#[derive(Debug, Clone, Copy, PartialEq, Eq)]
817/// Internal alternatives for RequiredClause.
818enum RequiredClause {
819    /// The Then alternative.
820    Then,
821    /// The LoopBody alternative.
822    LoopBody,
823    /// The ExceptBody alternative.
824    ExceptBody,
825}
826
827/// Handles newline ends cell.
828pub(crate) fn newline_ends_cell(stream: &TokenStream) -> bool {
829    let mut required_clauses = Vec::new();
830    let mut last_text = None;
831    let mut quote_next = false;
832
833    for tree in stream.iter() {
834        let text = match tree {
835            TokenTree::Ident(token) => Some(token.text()),
836            TokenTree::Punct(token) => Some(token.text()),
837            TokenTree::Literal(_) | TokenTree::Group(_) => {
838                quote_next = false;
839                last_text = None;
840                None
841            }
842        };
843
844        let Some(text) = text else {
845            continue;
846        };
847        if quote_next {
848            quote_next = false;
849            last_text = None;
850            continue;
851        }
852        let text = canonical_word_spelling(text);
853        last_text = Some(text);
854        if is_quote_specifier(text) {
855            quote_next = true;
856            continue;
857        }
858        match text {
859            "if" => required_clauses.push(RequiredClause::Then),
860            "for" | "while" => required_clauses.push(RequiredClause::LoopBody),
861            "except" => required_clauses.push(RequiredClause::ExceptBody),
862            "then" => {
863                remove_last_clause(&mut required_clauses, RequiredClause::Then);
864            }
865            "list" => {
866                remove_last_clause(&mut required_clauses, RequiredClause::LoopBody);
867            }
868            "do" if !remove_last_clause(&mut required_clauses, RequiredClause::ExceptBody) => {
869                remove_last_clause(&mut required_clauses, RequiredClause::LoopBody);
870            }
871            _ => {}
872        }
873    }
874
875    if !required_clauses.is_empty() {
876        return false;
877    }
878
879    let Some(last) = last_text else {
880        return true;
881    };
882    if keyword_requires_value(last) {
883        return false;
884    }
885    !GENERATED_OPERATOR_SPELLINGS.contains(&last)
886        || GENERATED_POSTFIX_OPERATOR_SPELLINGS.contains(&last)
887}
888
889/// Removes last clause.
890fn remove_last_clause(clauses: &mut Vec<RequiredClause>, clause: RequiredClause) -> bool {
891    let Some(position) = clauses.iter().rposition(|candidate| *candidate == clause) else {
892        return false;
893    };
894    clauses.remove(position);
895    true
896}
897
898/// Handles keyword requires value.
899fn keyword_requires_value(keyword: &str) -> bool {
900    matches!(
901        keyword,
902        "if" | "then"
903            | "else"
904            | "for"
905            | "while"
906            | "in"
907            | "from"
908            | "to"
909            | "when"
910            | "list"
911            | "do"
912            | "new"
913            | "of"
914            | "try"
915            | "except"
916            | "catch"
917            | "throw"
918            | "trap"
919            | "shield"
920            | "TEST"
921            | "time"
922            | "timing"
923            | "breakpoint"
924            | "elapsedTime"
925            | "elapsedTiming"
926            | "profile"
927            | "symbol"
928            | "local"
929            | "global"
930            | "threadVariable"
931            | "threadLocal"
932    )
933}
934
935/// Whether the token just lexed quotes the one after it.
936fn last_token_quotes_next(stream: &TokenStream) -> bool {
937    matches!(stream.last_innermost(), Some(TokenTree::Ident(token))
938        if is_quote_specifier(canonical_word_spelling(token.text())))
939}
940
941/// Returns whether the value is quote specifier.
942pub(crate) fn is_quote_specifier(keyword: &str) -> bool {
943    matches!(
944        keyword,
945        "symbol" | "local" | "global" | "threadVariable" | "threadLocal"
946    )
947}
948
949/// Handles punctuation would merge.
950pub(crate) fn punctuation_would_merge(left: &str, right: &str) -> bool {
951    let mut combined = String::with_capacity(left.len() + right.len());
952    combined.push_str(left);
953    combined.push_str(right);
954    fixed_spellings()
955        .any(|(spelling, _)| spelling.len() > left.len() && combined.starts_with(spelling))
956        || (right.starts_with('=') && is_math_operator(left))
957}
958
959#[derive(Debug)]
960/// Internal state for OpenGroup.
961struct OpenGroup {
962    /// The kind.
963    kind: DelimiterKind,
964    /// The opening start.
965    opening_start: Offset,
966}
967
968#[derive(Debug, Clone, Copy, PartialEq, Eq)]
969/// Internal alternatives for FixedKind.
970enum FixedKind {
971    /// The Punctuation alternative.
972    Punctuation,
973    /// The LineComment alternative.
974    LineComment,
975    /// The BlockComment alternative.
976    BlockComment,
977    /// The RawString alternative.
978    RawString,
979    /// The Open alternative.
980    Open(DelimiterKind),
981    /// The Close alternative.
982    Close(DelimiterKind),
983}
984
985/// The FIXED SPELLINGS constant.
986const FIXED_SPELLINGS: &[(&str, FixedKind)] = &[
987    ("///", FixedKind::RawString),
988    ("--", FixedKind::LineComment),
989    ("-*", FixedKind::BlockComment),
990    ("(", FixedKind::Open(DelimiterKind::Parenthesis)),
991    (")", FixedKind::Close(DelimiterKind::Parenthesis)),
992    ("[", FixedKind::Open(DelimiterKind::Bracket)),
993    ("]", FixedKind::Close(DelimiterKind::Bracket)),
994    ("{", FixedKind::Open(DelimiterKind::Brace)),
995    ("}", FixedKind::Close(DelimiterKind::Brace)),
996    ("<|", FixedKind::Open(DelimiterKind::AngleBar)),
997    ("|>", FixedKind::Close(DelimiterKind::AngleBar)),
998];
999
1000/// Handles fixed spellings.
1001fn fixed_spellings() -> impl Iterator<Item = (&'static str, FixedKind)> {
1002    FIXED_SPELLINGS.iter().copied().chain(
1003        GENERATED_PUNCTUATION_SPELLINGS
1004            .iter()
1005            .map(|spelling| (*spelling, FixedKind::Punctuation)),
1006    )
1007}
1008
1009#[derive(Debug, Clone, PartialEq, Eq)]
1010/// Internal state for FixedMatch.
1011struct FixedMatch {
1012    /// The len.
1013    len: usize,
1014    /// The kind.
1015    kind: FixedKind,
1016}
1017
1018#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1019/// Internal alternatives for NumberRadix.
1020enum NumberRadix {
1021    /// The Binary alternative.
1022    Binary,
1023    /// The Octal alternative.
1024    Octal,
1025    /// The Hexadecimal alternative.
1026    Hexadecimal,
1027}
1028
1029impl NumberRadix {
1030    /// Returns whether the value is digit.
1031    fn is_digit(self, byte: u8) -> bool {
1032        match self {
1033            Self::Binary => matches!(byte, b'0' | b'1'),
1034            Self::Octal => matches!(byte, b'0'..=b'7'),
1035            Self::Hexadecimal => byte.is_ascii_hexdigit(),
1036        }
1037    }
1038}
1039
1040/// Returns whether the value is identifier start.
1041fn is_identifier_start(byte: u8) -> bool {
1042    byte.is_ascii_alphabetic() || byte >= 128
1043}
1044
1045/// Returns whether the value is identifier continue.
1046fn is_identifier_continue(byte: u8) -> bool {
1047    byte.is_ascii_alphanumeric() || matches!(byte, b'$' | b'\'') || byte >= 128
1048}
1049
1050/// Returns whether the value is math operator pair.
1051fn is_math_operator_pair(first: u8, second: u8) -> bool {
1052    matches!(
1053        (first, second),
1054        (0xc2, 0xa0..=0xbf)
1055            | (0xc3, 0x97 | 0xb7)
1056            | (0xe2, 0x86..=0x8f | 0x9f | 0xa4..=0xaf)
1057    )
1058}
1059
1060/// Returns whether the value is math operator.
1061fn is_math_operator(spelling: &str) -> bool {
1062    let bytes = spelling.as_bytes();
1063    bytes
1064        .first()
1065        .zip(bytes.get(1))
1066        .is_some_and(|(first, second)| {
1067            is_math_operator_pair(*first, *second) && utf8_char_width(*first) == Some(bytes.len())
1068        })
1069}
1070
1071/// Handles UTF-8 char width.
1072fn utf8_char_width(first: u8) -> Option<usize> {
1073    match first {
1074        0x00..=0x7f => Some(1),
1075        0xc2..=0xdf => Some(2),
1076        0xe0..=0xef => Some(3),
1077        0xf0..=0xf4 => Some(4),
1078        _ => None,
1079    }
1080}
1081
1082#[cfg(test)]
1083mod tests {
1084    use super::*;
1085
1086    fn tokens(source: &str) -> TokenStream {
1087        let stream = lex_str(source).unwrap();
1088        assert_eq!(stream.to_source(), source);
1089        stream
1090    }
1091
1092    fn span_at(start: usize, text: &str) -> Span {
1093        let source = Source::new(format!("{}{}", " ".repeat(start), text));
1094        Span::ranged(source, start..start + text.len())
1095    }
1096
1097    #[test]
1098    fn lexer_accepts_an_arbitrary_non_peekable_byte_iterator() {
1099        let bytes = vec![b'a', b'+', b'1'].into_iter();
1100        let stream = lex(bytes).unwrap();
1101        assert_eq!(stream.to_source(), "a+1");
1102    }
1103
1104    #[test]
1105    fn every_short_byte_sequence_returns_without_panicking() {
1106        fn check(bytes: &[u8]) {
1107            let outcome = std::panic::catch_unwind(|| lex(bytes.iter().copied()));
1108            assert!(outcome.is_ok(), "lexer panicked for bytes {bytes:?}");
1109        }
1110
1111        check(&[]);
1112        for first in u8::MIN..=u8::MAX {
1113            check(&[first]);
1114            for second in u8::MIN..=u8::MAX {
1115                check(&[first, second]);
1116            }
1117        }
1118    }
1119
1120    #[test]
1121    fn invalid_utf8_is_a_lex_error() {
1122        assert_eq!(
1123            lex([0xf0, 0x80, 0x80]).unwrap_err().kind(),
1124            LexErrorKind::InvalidUtf8
1125        );
1126    }
1127
1128    #[test]
1129    fn punctuation_is_returned_as_maximal_tokens() {
1130        let source = "<<|-1||>2";
1131        let stream = tokens(source);
1132        let punctuation = stream
1133            .iter()
1134            .filter_map(|tree| match tree {
1135                TokenTree::Punct(token) => Some(token.text()),
1136                _ => None,
1137            })
1138            .collect::<Vec<_>>();
1139        let integers = stream
1140            .iter()
1141            .filter_map(|tree| match tree {
1142                TokenTree::Literal(token) if token.kind == LiteralKind::Integer => {
1143                    Some(token.text())
1144                }
1145                _ => None,
1146            })
1147            .collect::<Vec<_>>();
1148        assert_eq!(punctuation, ["<<", "|-", "||", ">"]);
1149        assert_eq!(integers, ["1", "2"]);
1150    }
1151
1152    #[test]
1153    fn comment_openers_only_win_at_the_current_position() {
1154        let ordinary = tokens("|--1");
1155        assert!(
1156            ordinary.iter().all(|tree| {
1157                tree.leading_trivia().is_empty() && tree.trailing_trivia().is_empty()
1158            })
1159        );
1160
1161        let source = "-- comment\n-* block *-";
1162        let comments = tokens(source);
1163        assert!(comments.is_empty());
1164        assert!(matches!(
1165            comments.suffix_trivia(),
1166            [line, block]
1167                if line.kind() == TriviaKind::LineComment
1168                    && block.kind() == TriviaKind::BlockComment
1169        ));
1170    }
1171
1172    #[test]
1173    fn leading_operator_runs_split_at_maximal_known_spellings() {
1174        let source = "***1";
1175        let stream = tokens(source);
1176        let trees = stream.iter().collect::<Vec<_>>();
1177        assert!(matches!(
1178            trees.as_slice(),
1179            [
1180                first,
1181                second,
1182                TokenTree::Literal(integer),
1183            ] if first.spelling() == Some("**")
1184                && second.spelling() == Some("*")
1185                && integer.kind == LiteralKind::Integer
1186                && integer.text() == "1"
1187        ));
1188    }
1189
1190    #[test]
1191    fn identifiers_follow_the_m2_character_classes() {
1192        let source = "Core$foo'12 1not not1";
1193        let qualified = tokens(source);
1194        let trees = qualified.iter().collect::<Vec<_>>();
1195        assert!(matches!(
1196            trees.as_slice(),
1197            [
1198                TokenTree::Ident(qualified),
1199                TokenTree::Literal(one),
1200                TokenTree::Ident(not),
1201                TokenTree::Ident(not_one),
1202            ] if qualified.text() == "Core$foo'12"
1203                && one.text() == "1"
1204                && not.text() == "not"
1205                && not_one.text() == "not1"
1206        ));
1207
1208        let source = "_name";
1209        let underscored = tokens(source);
1210        let trees = underscored.iter().collect::<Vec<_>>();
1211        assert!(matches!(
1212            trees.as_slice(),
1213            [underscore, TokenTree::Ident(name)]
1214                if underscore.spelling() == Some("_") && name.text() == "name"
1215        ));
1216
1217        let source = "éλ🐻";
1218        let unicode = tokens(source);
1219        assert!(matches!(
1220            unicode.iter().next(),
1221            Some(TokenTree::Ident(identifier)) if identifier.text() == "éλ🐻"
1222        ));
1223
1224        let source = "α⊠β";
1225        let operated = tokens(source);
1226        let trees = operated.iter().collect::<Vec<_>>();
1227        assert!(matches!(
1228            trees.as_slice(),
1229            [
1230                TokenTree::Ident(alpha),
1231                operator,
1232                TokenTree::Ident(beta),
1233            ] if alpha.text() == "α"
1234                && operator.spelling() == Some("⊠")
1235                && beta.text() == "β"
1236        ));
1237    }
1238
1239    #[test]
1240    fn qualified_identifier_metadata_uses_subspans_without_splitting_the_atom() {
1241        let spelling = "Package$name$12";
1242        let stream = tokens(spelling);
1243        assert!(matches!(
1244            stream.iter().collect::<Vec<_>>().as_slice(),
1245            [TokenTree::Ident(_)]
1246        ));
1247
1248        let symbol = crate::Symbol::new(spelling, span_at(100, spelling));
1249        assert_eq!(
1250            symbol.package_qualifier.as_ref().and_then(Span::bounds),
1251            Some((100, 107))
1252        );
1253        assert_eq!(
1254            symbol.index_qualifier.as_ref().and_then(Span::bounds),
1255            Some((113, 115))
1256        );
1257        assert!(matches!(
1258            crate::Lex::to_token_stream(&symbol)
1259                .iter()
1260                .collect::<Vec<_>>()
1261                .as_slice(),
1262            [TokenTree::Ident(identifier)] if identifier.text() == spelling
1263        ));
1264
1265        let package = crate::Symbol::new("Package$name", span_at(20, "Package$name"));
1266        assert_eq!(
1267            package.package_qualifier.as_ref().and_then(Span::bounds),
1268            Some((20, 27))
1269        );
1270        assert_eq!(package.index_qualifier, None);
1271
1272        let indexed = crate::Symbol::new("name$12", span_at(30, "name$12"));
1273        assert_eq!(indexed.package_qualifier, None);
1274        assert_eq!(
1275            indexed.index_qualifier.as_ref().and_then(Span::bounds),
1276            Some((35, 37))
1277        );
1278
1279        for unqualified in ["name", "A$B$C", "A$$B", "A$", "A$1B", "Core$Core$not"] {
1280            let symbol = crate::Symbol::new(unqualified, Span::for_text(unqualified));
1281            assert_eq!(symbol.package_qualifier, None, "`{unqualified}`");
1282            assert_eq!(symbol.index_qualifier, None, "`{unqualified}`");
1283        }
1284    }
1285
1286    #[test]
1287    fn radix_literals_stop_before_the_first_invalid_digit() {
1288        let source = "0b010201";
1289        let binary = tokens(source);
1290        let trees = binary.iter().collect::<Vec<_>>();
1291        assert!(matches!(
1292            trees.as_slice(),
1293            [
1294                TokenTree::Literal(binary),
1295                TokenTree::Literal(decimal),
1296            ]
1297                if binary.text() == "0b010" && decimal.text() == "201"
1298        ));
1299
1300        let source = "0o70789";
1301        let octal = tokens(source);
1302        let trees = octal.iter().collect::<Vec<_>>();
1303        assert!(matches!(
1304            trees.as_slice(),
1305            [
1306                TokenTree::Literal(octal),
1307                TokenTree::Literal(decimal),
1308            ]
1309                if octal.text() == "0o707" && decimal.text() == "89"
1310        ));
1311
1312        let source = "0b1E2";
1313        let binary_then_identifier = tokens(source);
1314        let trees = binary_then_identifier.iter().collect::<Vec<_>>();
1315        assert!(matches!(
1316            trees.as_slice(),
1317            [
1318                TokenTree::Literal(binary),
1319                TokenTree::Ident(identifier),
1320            ]
1321                if binary.text() == "0b1" && identifier.text() == "E2"
1322        ));
1323    }
1324
1325    #[test]
1326    fn spans_preserve_physical_separation_between_punctuation_tokens() {
1327        let source = "++x +\n+";
1328        let stream = tokens(source);
1329        let trees = stream.iter().collect::<Vec<_>>();
1330        assert!(matches!(
1331            trees.as_slice(),
1332            [
1333                increment,
1334                TokenTree::Ident(identifier),
1335                first_plus,
1336                second_plus,
1337            ] if increment.spelling() == Some("++")
1338                && identifier.text() == "x"
1339                && first_plus.spelling() == Some("+")
1340                && second_plus.spelling() == Some("+")
1341        ));
1342        let first_plus = trees[2].expect_span();
1343        let second_plus = trees[3].expect_span();
1344        assert_eq!(
1345            &first_plus.source().text()[first_plus.end().unwrap()..second_plus.start().unwrap()],
1346            "\n"
1347        );
1348    }
1349
1350    #[test]
1351    fn strings_and_raw_strings_are_literal_token_trees() {
1352        for (source, kind) in [
1353            (r#""a\n\u0101""#, LiteralKind::String),
1354            ("///left////right///", LiteralKind::RawString),
1355            ("///left//right/single///", LiteralKind::RawString),
1356        ] {
1357            let stream = tokens(source);
1358            assert!(matches!(
1359                stream.iter().next(),
1360                Some(TokenTree::Literal(literal))
1361                    if literal.kind == kind && literal.text() == source
1362            ));
1363        }
1364    }
1365
1366    #[test]
1367    fn structural_delimiters_form_nested_groups_with_exact_spans() {
1368        let source = "(1, [2, <|3|>])";
1369        let stream = tokens(source);
1370        let mut trees = stream.into_iter();
1371        let Some(TokenTree::Group(parenthesized)) = trees.next() else {
1372            panic!("the source should be one parenthesized group");
1373        };
1374        assert!(trees.next().is_none());
1375        assert_eq!(parenthesized.delim_kind(), DelimiterKind::Parenthesis);
1376
1377        let span = parenthesized.expect_span();
1378        assert_eq!(span.start().unwrap(), 0);
1379        assert_eq!(span.end().unwrap(), 15);
1380        assert_eq!(parenthesized.delimiter().opening_span().end().unwrap(), 1);
1381        assert_eq!(
1382            parenthesized
1383                .delimiter()
1384                .closing_span()
1385                .unwrap()
1386                .start()
1387                .unwrap(),
1388            14
1389        );
1390
1391        let bracketed = parenthesized
1392            .stream()
1393            .iter()
1394            .find_map(|tree| match tree {
1395                TokenTree::Group(group) => Some(group),
1396                _ => None,
1397            })
1398            .unwrap();
1399        assert_eq!(bracketed.delim_kind(), DelimiterKind::Bracket);
1400        assert_eq!(bracketed.delimiter().opening_span().start().unwrap(), 4);
1401        assert_eq!(
1402            bracketed
1403                .delimiter()
1404                .closing_span()
1405                .unwrap()
1406                .start()
1407                .unwrap(),
1408            13
1409        );
1410
1411        let angle_bar = bracketed
1412            .stream()
1413            .iter()
1414            .find_map(|tree| match tree {
1415                TokenTree::Group(group) => Some(group),
1416                _ => None,
1417            })
1418            .unwrap();
1419        assert_eq!(angle_bar.delim_kind(), DelimiterKind::AngleBar);
1420        let opening = angle_bar.delimiter().opening_span();
1421        let closing = angle_bar.delimiter().closing_span().unwrap();
1422        assert_eq!(opening.start().unwrap(), 8);
1423        assert_eq!(opening.end().unwrap(), 10);
1424        assert_eq!(closing.start().unwrap(), 11);
1425        assert_eq!(closing.end().unwrap(), 13);
1426    }
1427
1428    #[test]
1429    fn graded_operator_wins_before_parenthesis_grouping() {
1430        let source = "(*)";
1431        let graded = tokens(source);
1432        let trees = graded.iter().collect::<Vec<_>>();
1433        assert!(matches!(
1434            trees.as_slice(),
1435            [graded] if graded.spelling() == Some("(*)")
1436        ));
1437
1438        let grouped = tokens("(*x)");
1439        assert!(matches!(grouped.iter().next(), Some(TokenTree::Group(_))));
1440    }
1441
1442    #[test]
1443    fn malformed_source_returns_lex_errors() {
1444        for (source, kind) in [
1445            ("\"\\q\"", LexErrorKind::InvalidEscape),
1446            ("(1", LexErrorKind::UnterminatedGroup),
1447            (")", LexErrorKind::UnexpectedClosingDelimiter),
1448            ("(]", LexErrorKind::UnexpectedClosingDelimiter),
1449            ("///", LexErrorKind::UnterminatedRawString),
1450            ("-* unterminated", LexErrorKind::UnterminatedBlockComment),
1451        ] {
1452            assert_eq!(lex_str(source).unwrap_err().kind(), kind);
1453        }
1454
1455        let nested = "(".repeat(MAX_GROUP_DEPTH + 1);
1456        assert_eq!(
1457            lex_str(&nested).unwrap_err().kind(),
1458            LexErrorKind::NestingLimitExceeded
1459        );
1460    }
1461
1462    #[test]
1463    fn a_crlf_line_break_counts_as_the_two_bytes_it_is() {
1464        let source = "x\r\ny";
1465        let stream = tokens(source);
1466        let range = stream
1467            .iter()
1468            .find_map(|tree| match tree {
1469                TokenTree::Ident(identifier) if identifier.text() == "y" => {
1470                    Some(identifier.expect_span())
1471                }
1472                _ => None,
1473            })
1474            .unwrap();
1475        // `x` `\r` `\n` `y`, so `y` occupies byte 3 alone.
1476        assert_eq!(range.bounds(), Some((3, 4)));
1477
1478        let error = lex_str("-* x\r\n").unwrap_err();
1479        assert_eq!(error.expect_span().end(), Some("-* x\r\n".len()));
1480    }
1481
1482    #[test]
1483    fn identifier_classes_match_m2_byte_ctype() {
1484        for byte in [b'a', b'Z', 0x80, 0xff] {
1485            assert!(is_identifier_start(byte));
1486            assert!(is_identifier_continue(byte));
1487        }
1488        for byte in *b"09$'" {
1489            assert!(!is_identifier_start(byte));
1490            assert!(is_identifier_continue(byte));
1491        }
1492        for byte in *b"_ +" {
1493            assert!(!is_identifier_start(byte));
1494            assert!(!is_identifier_continue(byte));
1495        }
1496    }
1497
1498    #[test]
1499    fn math_operator_prefixes_match_m2_ranges() {
1500        for pair in [
1501            (0xc2, 0xa0),
1502            (0xc2, 0xbf),
1503            (0xc3, 0x97),
1504            (0xc3, 0xb7),
1505            (0xe2, 0x86),
1506            (0xe2, 0x87),
1507            (0xe2, 0x88),
1508            (0xe2, 0x8f),
1509            (0xe2, 0x9f),
1510            (0xe2, 0xa4),
1511            (0xe2, 0xa7),
1512            (0xe2, 0xa8),
1513            (0xe2, 0xaf),
1514        ] {
1515            assert!(is_math_operator_pair(pair.0, pair.1));
1516        }
1517        for pair in [
1518            (0xc2, 0x9f),
1519            (0xc3, 0x96),
1520            (0xc3, 0x98),
1521            (0xe2, 0x85),
1522            (0xe2, 0x90),
1523            (0xe2, 0x9e),
1524            (0xe2, 0xa3),
1525            (0xe2, 0xb0),
1526        ] {
1527            assert!(!is_math_operator_pair(pair.0, pair.1));
1528        }
1529    }
1530
1531    #[test]
1532    fn utf8_width_rejects_continuations_and_invalid_leads() {
1533        assert_eq!(utf8_char_width(b'a'), Some(1));
1534        assert_eq!(utf8_char_width(0xc2), Some(2));
1535        assert_eq!(utf8_char_width(0xe2), Some(3));
1536        assert_eq!(utf8_char_width(0xf0), Some(4));
1537        assert_eq!(utf8_char_width(0x80), None);
1538        assert_eq!(utf8_char_width(0xc0), None);
1539        assert_eq!(utf8_char_width(0xf5), None);
1540    }
1541
1542    #[test]
1543    fn ordinary_strings_accept_exact_m2_escape_widths() {
1544        for source in [r#""\u0101""#, r#""\x4f""#, r#""\1\12\123""#, r#""\n\"\\""#] {
1545            let stream = tokens(source);
1546            assert!(matches!(
1547                stream.iter().next(),
1548                Some(TokenTree::Literal(literal))
1549                    if literal.kind == LiteralKind::String && literal.text() == source
1550            ));
1551        }
1552        for source in [r#""\u101""#, r#""\u010g""#, r#""\x4""#] {
1553            assert_eq!(
1554                lex_str(source).unwrap_err().kind(),
1555                LexErrorKind::InvalidEscape
1556            );
1557        }
1558    }
1559}