Skip to main content

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