Skip to main content

m2_syn/lex/
cell_stream.rs

1//! Grammar-aware partitioning of raw token trees into source cells.
2//!
3//! [`TokenStream`] retains physical atoms, trivia, and delimiter groups.
4//! [`CellStream`] is the next representation: an iterable sequence of
5//! independently parseable token streams. End-of-cell and end-of-file are
6//! structural boundaries, not token variants. The only inferred atom is
7//! adjacency, represented as zero-width [`Punct`] with an empty spelling.
8
9use std::error::Error;
10use std::fmt::{Display, Formatter, Result as FmtResult};
11use std::mem::take;
12use std::sync::Arc;
13use std::vec::IntoIter;
14
15#[cfg(feature = "tree-sitter")]
16use crate::CommentTrivia;
17use crate::lexer::{canonical_word_spelling, is_quote_specifier, newline_ends_cell};
18use crate::{
19    Lex, Punct, Source, Span, Spanned, TOKEN_PARSE_RULES, TokenParseRule, TokenStream, TokenTree,
20};
21
22#[derive(Debug, Clone, Default)]
23/// A sequence of independently parseable Macaulay2 token streams.
24///
25/// Cell boundaries are expressed by the collection itself. The boundary after
26/// its final element is also the end-of-file boundary, so neither fact needs a
27/// virtual token in the contained streams.
28pub struct CellStream {
29    cells: Vec<TokenStream>,
30    source: Option<Arc<Source>>,
31}
32
33impl PartialEq for CellStream {
34    fn eq(&self, other: &Self) -> bool {
35        self.cells == other.cells
36    }
37}
38
39impl Eq for CellStream {}
40
41impl CellStream {
42    /// Partitions a complete raw token stream and inserts inferred adjacency.
43    ///
44    /// # Errors
45    ///
46    /// Returns [`CellStreamError`] when the final tokens require more input
47    /// before they can form an independently parseable cell.
48    pub fn new(tokens: TokenStream) -> Result<Self, CellStreamError> {
49        let source = tokens.source();
50        let FramedCells { cells, incomplete } = frame_recovering(tokens);
51        match incomplete {
52            Some(tokens) => Err(CellStreamError {
53                span: end_span(&tokens),
54            }),
55            None => Ok(Self { cells, source }),
56        }
57    }
58
59    /// Creates a stream from token streams already known to be complete cells.
60    pub fn from_cells(cells: impl IntoIterator<Item = TokenStream>) -> Self {
61        Self::from_cells_with_source(cells, None)
62    }
63
64    pub(crate) fn from_cells_with_source(
65        cells: impl IntoIterator<Item = TokenStream>,
66        source: Option<Arc<Source>>,
67    ) -> Self {
68        Self {
69            cells: cells.into_iter().collect(),
70            source,
71        }
72    }
73
74    /// Borrows the independently parseable cells.
75    pub fn cells(&self) -> &[TokenStream] {
76        &self.cells
77    }
78
79    /// Iterates over the independently parseable cells.
80    pub fn iter(&self) -> impl DoubleEndedIterator<Item = &TokenStream> + ExactSizeIterator {
81        self.cells.iter()
82    }
83
84    /// Returns the number of cells.
85    pub fn len(&self) -> usize {
86        self.cells.len()
87    }
88
89    /// Reports whether the stream contains no cells.
90    pub fn is_empty(&self) -> bool {
91        self.cells.is_empty()
92    }
93
94    /// Flattens the cells back into one token stream.
95    pub fn into_token_stream(self) -> TokenStream {
96        flatten_cells(self.cells)
97    }
98
99    /// Flattens borrowed cells back into one token stream.
100    pub fn to_token_stream(&self) -> TokenStream {
101        flatten_cells(self.cells.iter().cloned())
102    }
103
104    pub(crate) fn complete_source(&self) -> Option<Arc<Source>> {
105        let source = self.source.as_ref()?;
106        let mut cursor = 0;
107        for cell in &self.cells {
108            if !covers_stream(cell, source, &mut cursor) {
109                return None;
110            }
111        }
112        source.text()[cursor..]
113            .bytes()
114            .all(|byte| byte.is_ascii_whitespace())
115            .then(|| Arc::clone(source))
116    }
117
118    pub(crate) fn physical_eof(&self) -> Span {
119        if let Some(source) = self.complete_source() {
120            return Span::ranged(
121                Arc::clone(&source),
122                source.text().len()..source.text().len(),
123            );
124        }
125        self.cells
126            .last()
127            .map_or_else(|| Span::for_text(""), end_span)
128    }
129
130    #[cfg(feature = "tree-sitter")]
131    pub(crate) fn comment_ownership(&self) -> (Vec<CommentTrivia>, CommentTrivia) {
132        let mut cells = Vec::new();
133        let mut stream = CommentTrivia::default();
134        for tokens in &self.cells {
135            let mut tokens = tokens.clone();
136            if tokens.has_significant_tokens() {
137                cells.push(CommentTrivia::take_from_cell(&mut tokens));
138            } else {
139                stream.append(CommentTrivia::take_inner(&mut tokens));
140            }
141        }
142        (cells, stream)
143    }
144
145    pub(crate) fn from_source_file(source_file: &crate::SourceFile) -> Self {
146        let mut cells = source_file
147            .cells
148            .iter()
149            .map(Lex::to_token_stream)
150            .collect::<Vec<_>>();
151        if !source_file.comments.is_empty() {
152            if cells.is_empty() {
153                cells.push(TokenStream::new());
154            }
155            if let Some(cell) = cells.last_mut() {
156                source_file.comments.__apply_to(cell);
157            }
158        }
159        Self {
160            cells,
161            source: None,
162        }
163    }
164}
165
166fn covers_stream(stream: &TokenStream, source: &Arc<Source>, cursor: &mut usize) -> bool {
167    for trivia in stream.prefix_trivia() {
168        if !covers_span(trivia.span(), source, cursor) {
169            return false;
170        }
171    }
172    for token in stream.iter() {
173        for trivia in token.leading_trivia().as_slice() {
174            if !covers_span(trivia.span(), source, cursor) {
175                return false;
176            }
177        }
178        let covered = match token {
179            TokenTree::Group(group) => {
180                covers_span(group.delimiter().opening_span(), source, cursor)
181                    && covers_stream(group.stream(), source, cursor)
182                    && group
183                        .delimiter()
184                        .closing_span()
185                        .is_none_or(|span| covers_span(span, source, cursor))
186            }
187            _ => covers_span(token.span(), source, cursor),
188        };
189        if !covered {
190            return false;
191        }
192        for trivia in token.trailing_trivia().as_slice() {
193            if !covers_span(trivia.span(), source, cursor) {
194                return false;
195            }
196        }
197    }
198    stream
199        .suffix_trivia()
200        .iter()
201        .all(|trivia| covers_span(trivia.span(), source, cursor))
202}
203
204fn covers_span(span: Span, source: &Arc<Source>, cursor: &mut usize) -> bool {
205    if !Arc::ptr_eq(span.source(), source) {
206        return false;
207    }
208    let (Some(start), Some(end)) = (span.start(), span.end()) else {
209        return false;
210    };
211    if start == end {
212        return start >= *cursor;
213    }
214    if start < *cursor
215        || !source.text()[*cursor..start]
216            .bytes()
217            .all(|byte| byte.is_ascii_whitespace())
218    {
219        return false;
220    }
221    *cursor = end;
222    true
223}
224
225impl Display for CellStream {
226    fn fmt(&self, formatter: &mut Formatter<'_>) -> FmtResult {
227        let mut previous: Option<Span> = None;
228        for (index, cell) in self.cells.iter().enumerate() {
229            if index != 0 {
230                let next = cell_first_span(cell);
231                let mut recovered = false;
232                if let Some((left, right)) = previous.as_ref().zip(next.as_ref())
233                    && left.shares_source(right)
234                    && let (Some(end), Some(start)) = (left.end(), right.start())
235                    && end <= start
236                {
237                    let gap = &left.source().text()[end..start];
238                    if !gap.is_empty() && gap.bytes().all(|byte| byte.is_ascii_whitespace()) {
239                        formatter.write_str(gap)?;
240                        recovered = true;
241                    }
242                }
243                if !recovered {
244                    formatter.write_str("\n")?;
245                }
246            }
247            cell.fmt(formatter)?;
248            previous = cell_last_span(cell);
249        }
250        Ok(())
251    }
252}
253
254fn cell_first_span(tokens: &TokenStream) -> Option<Span> {
255    tokens
256        .prefix_trivia()
257        .first()
258        .map(Spanned::span)
259        .or_else(|| {
260            tokens.iter().next().map(|token| {
261                token
262                    .leading_trivia()
263                    .as_slice()
264                    .first()
265                    .map_or_else(|| token.span(), Spanned::span)
266            })
267        })
268        .or_else(|| tokens.suffix_trivia().first().map(Spanned::span))
269}
270
271fn cell_last_span(tokens: &TokenStream) -> Option<Span> {
272    tokens
273        .suffix_trivia()
274        .last()
275        .map(Spanned::span)
276        .or_else(|| tokens.iter().next_back().map(token_end_span))
277        .or_else(|| tokens.prefix_trivia().last().map(Spanned::span))
278}
279
280impl IntoIterator for CellStream {
281    type Item = TokenStream;
282    type IntoIter = IntoIter<TokenStream>;
283
284    fn into_iter(self) -> Self::IntoIter {
285        self.cells.into_iter()
286    }
287}
288
289impl<'syntax> IntoIterator for &'syntax CellStream {
290    type Item = &'syntax TokenStream;
291    type IntoIter = std::slice::Iter<'syntax, TokenStream>;
292
293    fn into_iter(self) -> Self::IntoIter {
294        self.cells.iter()
295    }
296}
297
298impl FromIterator<TokenStream> for CellStream {
299    fn from_iter<T: IntoIterator<Item = TokenStream>>(iter: T) -> Self {
300        Self::from_cells(iter)
301    }
302}
303
304fn flatten_cells(cells: impl IntoIterator<Item = TokenStream>) -> TokenStream {
305    let mut output = TokenStream::new();
306    for cell in cells {
307        output.extend([cell]);
308    }
309    output
310}
311
312#[derive(Debug, Clone, PartialEq, Eq, Spanned)]
313/// A failure to partition a final token prefix into complete cells.
314pub struct CellStreamError {
315    span: Span,
316}
317
318impl Display for CellStreamError {
319    fn fmt(&self, formatter: &mut Formatter<'_>) -> FmtResult {
320        formatter.write_str("input ends inside an incomplete cell")
321    }
322}
323
324impl Error for CellStreamError {}
325
326/// Converts syntax or raw tokens into structural cells.
327///
328/// This boundary is intentionally distinct from [`Lex`]: most macros should
329/// preserve cell partitioning, while function-like expansion may explicitly
330/// choose a raw [`TokenStream`] when syntax is not valid before expansion.
331pub trait Tokenize {
332    /// Appends this value's independently parseable cells to `output`.
333    ///
334    /// # Errors
335    ///
336    /// Returns [`CellStreamError`] when a raw token prefix does not end at a
337    /// complete cell boundary.
338    fn tokenize(&self, output: &mut CellStream) -> Result<(), CellStreamError>;
339
340    /// Tokenizes this value into a fresh cell stream.
341    ///
342    /// # Errors
343    ///
344    /// Returns [`CellStreamError`] when a raw token prefix does not end at a
345    /// complete cell boundary.
346    fn to_cell_stream(&self) -> Result<CellStream, CellStreamError> {
347        let mut output = CellStream::default();
348        self.tokenize(&mut output)?;
349        Ok(output)
350    }
351}
352
353impl Tokenize for TokenStream {
354    fn tokenize(&self, output: &mut CellStream) -> Result<(), CellStreamError> {
355        let tokenized = CellStream::new(self.clone())?;
356        if output.cells.is_empty() {
357            output.source = tokenized.source;
358        } else {
359            output.source = None;
360        }
361        output.cells.extend(tokenized.cells);
362        Ok(())
363    }
364}
365
366impl Tokenize for crate::SourceFile {
367    fn tokenize(&self, output: &mut CellStream) -> Result<(), CellStreamError> {
368        output
369            .cells
370            .extend(CellStream::from_source_file(self).cells);
371        output.source = None;
372        Ok(())
373    }
374}
375
376impl Tokenize for crate::ParsedFile {
377    fn tokenize(&self, output: &mut CellStream) -> Result<(), CellStreamError> {
378        self.cst().tokenize(output)
379    }
380}
381
382impl<T: Tokenize + ?Sized> Tokenize for &T {
383    fn tokenize(&self, output: &mut CellStream) -> Result<(), CellStreamError> {
384        (*self).tokenize(output)
385    }
386}
387
388pub(crate) struct FramedCells {
389    pub(crate) cells: Vec<TokenStream>,
390    pub(crate) incomplete: Option<TokenStream>,
391}
392
393pub(crate) fn frame_recovering(tokens: TokenStream) -> FramedCells {
394    let mut tokens = tokens;
395    let prefix_trivia = tokens.take_prefix_trivia();
396    let suffix_trivia = tokens.take_suffix_trivia();
397    let mut cells = Vec::new();
398    let mut current = TokenStream::new();
399    current.extend(prefix_trivia.into_vec());
400    let mut pending_boundary = false;
401    let mut quote_next = false;
402
403    for token in tokens {
404        if pending_boundary {
405            cells.push(insert_adjacency(take(&mut current)));
406            pending_boundary = false;
407            quote_next = false;
408        } else if current
409            .last()
410            .is_some_and(|left| tokens_cross_line_break(left, &token))
411            && newline_ends_cell(&current)
412        {
413            cells.push(insert_adjacency(take(&mut current)));
414            quote_next = false;
415        }
416
417        let quoted = take(&mut quote_next);
418        let quotes_next = !quoted
419            && matches!(&token, TokenTree::Ident(identifier)
420                if is_quote_specifier(canonical_word_spelling(identifier.text())));
421        let semicolon = !quoted && token.spelling() == Some(";");
422
423        current.extend([token]);
424        if semicolon {
425            pending_boundary = true;
426        }
427        if quotes_next {
428            quote_next = true;
429        }
430    }
431
432    current.extend(suffix_trivia.into_vec());
433
434    if pending_boundary {
435        cells.push(insert_adjacency(current));
436        return FramedCells {
437            cells,
438            incomplete: None,
439        };
440    }
441
442    if !has_syntax(&current) {
443        if current.has_trivia() {
444            cells.push(current);
445        }
446        return FramedCells {
447            cells,
448            incomplete: None,
449        };
450    }
451
452    if !ends_in_incomplete_group(&current) && newline_ends_cell(&current) {
453        cells.push(insert_adjacency(current));
454        FramedCells {
455            cells,
456            incomplete: None,
457        }
458    } else {
459        FramedCells {
460            cells,
461            incomplete: Some(insert_adjacency(current)),
462        }
463    }
464}
465
466pub(crate) fn end_span(tokens: &TokenStream) -> Span {
467    let span = tokens
468        .iter()
469        .next_back()
470        .map(token_end_span)
471        .or_else(|| tokens.suffix_trivia().last().map(Spanned::span))
472        .or_else(|| tokens.prefix_trivia().last().map(Spanned::span));
473    let Some(span) = span else {
474        return Span::for_text("");
475    };
476    let Some(end) = span.end() else {
477        return Span::unlocated(Arc::clone(span.source()));
478    };
479    if !ends_with_cell_semicolon(tokens)
480        && let Some(newline) = span.source().text()[end..].find('\n')
481    {
482        return span.at(end + newline + 1);
483    }
484    span.at(end)
485}
486
487fn token_end_span(token: &TokenTree) -> Span {
488    if let Some(trivia) = token.trailing_trivia().as_slice().last() {
489        return trivia.span();
490    }
491    if let TokenTree::Group(group) = token
492        && !group.delimiter().is_closed()
493        && let Some(inner) = group.stream().iter().next_back()
494    {
495        return token_end_span(inner);
496    }
497    token.span()
498}
499
500pub(crate) fn ends_with_cell_semicolon(tokens: &TokenStream) -> bool {
501    let mut quote_next = false;
502    let mut final_semicolon = false;
503    for token in tokens.iter() {
504        let quoted = take(&mut quote_next);
505        final_semicolon = !quoted && token.spelling() == Some(";");
506        quote_next = !quoted
507            && matches!(token, TokenTree::Ident(identifier)
508                if is_quote_specifier(canonical_word_spelling(identifier.text())));
509    }
510    final_semicolon
511}
512
513fn has_syntax(stream: &TokenStream) -> bool {
514    !stream.is_empty()
515}
516
517fn ends_in_incomplete_group(stream: &TokenStream) -> bool {
518    matches!(stream.iter().next_back(), Some(TokenTree::Group(group)) if !group.delimiter().is_closed())
519}
520
521fn insert_adjacency(mut tokens: TokenStream) -> TokenStream {
522    let prefix = tokens.take_prefix_trivia();
523    let suffix = tokens.take_suffix_trivia();
524    let physical_eof = tokens.take_physical_eof();
525    let mut output = TokenStream::new();
526    output.extend(prefix.into_vec());
527    let mut left_can_end = false;
528    let mut quote_next = false;
529
530    for mut token in tokens {
531        if let TokenTree::Group(group) = &mut token {
532            let contents = take(group.stream_mut());
533            *group.stream_mut() = insert_adjacency(contents);
534        }
535
536        let quoted = take(&mut quote_next);
537        let crossed_newline = output
538            .last()
539            .is_some_and(|left| tokens_cross_line_break(left, &token));
540        if left_can_end && !quoted && !crossed_newline && can_begin_implicit_operand(&token) {
541            let span = token.expect_span().subspan(0, 0);
542            output.extend([TokenTree::Punct(Punct::from_span(span))]);
543        }
544
545        let quotes_next = !quoted
546            && matches!(&token, TokenTree::Ident(identifier)
547                if is_quote_specifier(canonical_word_spelling(identifier.text())));
548        left_can_end = quoted || can_end_operand(&token);
549        quote_next = quotes_next;
550        output.extend([token]);
551    }
552    for trivia in suffix.into_vec() {
553        output.push_leading_trivia(trivia);
554    }
555    if let Some(physical_eof) = physical_eof {
556        output.set_physical_eof(physical_eof);
557    }
558    output
559}
560
561fn tokens_cross_line_break(left: &TokenTree, right: &TokenTree) -> bool {
562    let left = left.expect_span();
563    let right = right.expect_span();
564    if !left.shares_source(&right) {
565        return false;
566    }
567    let (Some(end), Some(start)) = (left.end(), right.start()) else {
568        return false;
569    };
570    end <= start && left.source().text()[end..start].contains('\n')
571}
572
573fn token_parse_rule(token: &TokenTree) -> Option<TokenParseRule> {
574    let spelling = token.spelling().map(canonical_word_spelling)?;
575    TOKEN_PARSE_RULES
576        .binary_search_by_key(&spelling, |(declared, _)| *declared)
577        .ok()
578        .map(|index| TOKEN_PARSE_RULES[index].1)
579}
580
581fn can_begin_implicit_operand(token: &TokenTree) -> bool {
582    match token_parse_rule(token) {
583        Some(TokenParseRule::Prefix(_, _) | TokenParseRule::Unary(_, _)) => true,
584        Some(_) => false,
585        None => matches!(
586            token,
587            TokenTree::Ident(_) | TokenTree::Literal(_) | TokenTree::Group(_)
588        ),
589    }
590}
591
592fn can_end_operand(token: &TokenTree) -> bool {
593    match token_parse_rule(token) {
594        Some(TokenParseRule::Postfix(_)) => true,
595        Some(_) => false,
596        None => matches!(
597            token,
598            TokenTree::Ident(_) | TokenTree::Literal(_) | TokenTree::Group(_)
599        ),
600    }
601}
602
603#[cfg(test)]
604mod tests {
605    use super::CellStream;
606    use crate::{TokenStream, TokenTree, Tokenize, TriviaKind, lex_str};
607
608    #[test]
609    fn cells_are_structural_partitions() {
610        let cells = CellStream::new(lex_str("f \t-- note\n x").unwrap()).unwrap();
611        assert_eq!(cells.len(), 2);
612        assert_eq!(cells.to_string(), "f \t-- note\n x");
613    }
614
615    #[test]
616    fn same_line_application_gets_one_empty_punctuation() {
617        let cells = lex_str("f x").unwrap().to_cell_stream().unwrap();
618        assert_eq!(
619            cells
620                .iter()
621                .flat_map(TokenStream::iter)
622                .filter(|token| matches!(token, TokenTree::Punct(punct) if punct.text().is_empty()))
623                .count(),
624            1,
625        );
626    }
627
628    #[test]
629    fn line_comment_blocks_begin_the_cell_on_their_right() {
630        let cells = CellStream::new(lex_str("x\n-- first\n-- second\ny").unwrap()).unwrap();
631        assert_eq!(cells.len(), 2);
632        let comments = cells.cells()[1]
633            .iter()
634            .next()
635            .unwrap()
636            .leading_trivia()
637            .as_slice();
638        assert_eq!(comments.len(), 1);
639        assert_eq!(comments[0].kind(), TriviaKind::LineComment);
640        assert_eq!(comments[0].text(), "-- first\n-- second");
641    }
642
643    #[test]
644    fn a_blank_line_separates_outer_comment_blocks() {
645        let cells = CellStream::new(lex_str("-- first\n\n-- second\ny").unwrap()).unwrap();
646        let comments = cells.cells()[0]
647            .iter()
648            .next()
649            .unwrap()
650            .leading_trivia()
651            .as_slice();
652
653        assert_eq!(comments.len(), 2);
654        assert_eq!(comments[0].text(), "-- first");
655        assert_eq!(comments[1].text(), "-- second");
656    }
657
658    #[test]
659    fn line_comments_do_not_split_an_incomplete_cell() {
660        let cells = CellStream::new(lex_str("x + -- reason\ny").unwrap()).unwrap();
661        assert_eq!(cells.len(), 1);
662        assert!(cells.cells()[0].iter().any(|token| {
663            token
664                .trailing_trivia()
665                .as_slice()
666                .iter()
667                .any(|trivia| trivia.kind() == TriviaKind::LineComment)
668        }));
669    }
670}