Skip to main content

m2_syn/lex/
cell_stream.rs

1//! Structural cell boundaries over raw Macaulay2 token trees.
2//!
3//! [`TokenStream`] retains lexical atoms, comments, source spans, and nested
4//! delimiter groups. [`CellStream`] is the next representation in the parsing
5//! pipeline: a sequence of token streams that can each be parsed independently.
6//! End-of-cell and end-of-file are properties of that sequence rather than
7//! virtual [`TokenTree`] variants.
8//!
9//! Framing recognizes top-level semicolons and physical newlines at which the
10//! current operator, clause, and delimiter state can end. It also inserts the
11//! one non-lexical atom required by the native parser: zero-width
12//! [`CellTokenTree::Adj`]. Framing does not otherwise parse or validate the
13//! Macaulay2 grammar.
14//!
15//! [`CellStream`] behaves as a borrowed slice of [`CellTokenStream`] values. It
16//! can be indexed and iterated through the standard slice APIs, and consuming
17//! it yields owned cells. Clones share their backing allocation until mutation.
18//!
19//! # Examples
20//!
21//! ```
22//! use m2_syn::{Tokenize, lex_str};
23//!
24//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
25//! let cells = lex_str("x = 1;\ny = 2")?.to_cell_stream()?;
26//!
27//! assert_eq!(cells.len(), 2);
28//! assert_eq!(cells[0].to_string(), "x = 1;");
29//! assert_eq!(cells.iter().map(ToString::to_string).collect::<Vec<_>>(), [
30//!     "x = 1;",
31//!     "y = 2",
32//! ]);
33//! # Ok(())
34//! # }
35//! ```
36
37use std::error::Error;
38use std::fmt::{Display, Formatter, Result as FmtResult};
39use std::mem::take;
40use std::ops::Deref;
41use std::slice::Iter;
42use std::sync::Arc;
43use std::vec::IntoIter;
44
45use crate::lexer::{canonical_word_spelling, is_quote_specifier, newline_ends_cell};
46use crate::token_stream::TokenStreamParts;
47use crate::{
48    Delimiter, DelimiterKind, IdentToken, LeadingTrivia, Lex, LiteralToken, Punct, Source, Span,
49    Spanned, TOKEN_PARSE_RULES, Terminal, TokenGroup, TokenParseRule, TokenStream, TokenTree,
50    TrailingTrivia, Trivia,
51};
52
53#[derive(Debug, Clone, Default)]
54/// Internal state for CellTokenStreamData.
55struct CellTokenStreamData {
56    /// The trees.
57    trees: Vec<CellTokenTree>,
58    /// The prefix.
59    prefix: TrailingTrivia,
60    /// The suffix.
61    suffix: LeadingTrivia,
62    /// The source.
63    source: Option<Arc<Source>>,
64}
65
66impl PartialEq for CellTokenStreamData {
67    fn eq(&self, other: &Self) -> bool {
68        self.trees == other.trees
69            && self.prefix == other.prefix
70            && self.suffix == other.suffix
71            && match (&self.source, &other.source) {
72                (Some(left), Some(right)) => Arc::ptr_eq(left, right),
73                (None, None) => true,
74                _ => false,
75            }
76    }
77}
78
79impl Eq for CellTokenStreamData {}
80
81#[derive(Debug, Clone, PartialEq, Eq)]
82/// One token tree in a framed Macaulay2 cell.
83///
84/// Lexical atoms retain their raw token types. Delimiter groups recursively
85/// contain [`CellTokenStream`], while [`Self::Adj`] represents the inferred
86/// application boundary that does not exist in [`TokenTree`].
87pub enum CellTokenTree {
88    /// A recursively framed delimiter group.
89    Group(CellTokenGroup),
90    /// A lexical literal.
91    Literal(LiteralToken),
92    /// Lexical punctuation.
93    Punct(Punct),
94    /// A lexical identifier or keyword.
95    Ident(IdentToken),
96    /// Inferred same-line adjacency at its zero-width source position.
97    Adj(Span),
98}
99
100impl CellTokenTree {
101    /// Returns the lexical spelling of a flat token or the empty adjacency spelling.
102    pub fn spelling(&self) -> Option<&str> {
103        match self {
104            Self::Group(_) => None,
105            Self::Literal(token) => Some(token.text()),
106            Self::Punct(token) => Some(token.text()),
107            Self::Ident(token) => Some(token.text()),
108            Self::Adj(_) => Some(""),
109        }
110    }
111
112    /// Returns comments attached before this cell token.
113    pub fn leading_trivia(&self) -> &[Trivia] {
114        match self {
115            Self::Group(token) => token.leading_trivia().as_slice(),
116            Self::Literal(token) => token.leading_trivia().as_slice(),
117            Self::Punct(token) => token.leading_trivia().as_slice(),
118            Self::Ident(token) => token.leading_trivia().as_slice(),
119            Self::Adj(_) => &[],
120        }
121    }
122
123    /// Returns comments attached after this cell token.
124    pub fn trailing_trivia(&self) -> &[Trivia] {
125        match self {
126            Self::Group(token) => token.trailing_trivia().as_slice(),
127            Self::Literal(token) => token.trailing_trivia().as_slice(),
128            Self::Punct(token) => token.trailing_trivia().as_slice(),
129            Self::Ident(token) => token.trailing_trivia().as_slice(),
130            Self::Adj(_) => &[],
131        }
132    }
133
134    /// Creates the value from augmented.
135    fn from_augmented(tree: TokenTree) -> Self {
136        match tree {
137            TokenTree::Group(group) => Self::Group(CellTokenGroup::from_augmented(group)),
138            TokenTree::Literal(token) => Self::Literal(token),
139            TokenTree::Punct(token) if token.text().is_empty() => Self::Adj(token.expect_span()),
140            TokenTree::Punct(token) => Self::Punct(token),
141            TokenTree::Ident(token) => Self::Ident(token),
142        }
143    }
144
145    /// Converts the value into lexical.
146    fn into_lexical(self) -> Option<TokenTree> {
147        match self {
148            Self::Group(group) => Some(TokenTree::Group(group.into_lexical())),
149            Self::Literal(token) => Some(TokenTree::Literal(token)),
150            Self::Punct(token) => Some(TokenTree::Punct(token)),
151            Self::Ident(token) => Some(TokenTree::Ident(token)),
152            Self::Adj(_) => None,
153        }
154    }
155
156    /// Converts the value into parser token.
157    fn into_parser_token(self) -> TokenTree {
158        match self {
159            Self::Group(group) => TokenTree::Group(group.into_parser_group()),
160            Self::Literal(token) => TokenTree::Literal(token),
161            Self::Punct(token) => TokenTree::Punct(token),
162            Self::Ident(token) => TokenTree::Ident(token),
163            Self::Adj(span) => TokenTree::Punct(Punct::from_span(span)),
164        }
165    }
166}
167
168impl Spanned for CellTokenTree {
169    fn try_span(&self) -> Option<Span> {
170        match self {
171            Self::Group(token) => token.try_span(),
172            Self::Literal(token) => token.try_span(),
173            Self::Punct(token) => token.try_span(),
174            Self::Ident(token) => token.try_span(),
175            Self::Adj(span) => Some(span.clone()),
176        }
177    }
178}
179
180#[derive(Debug, Clone, PartialEq, Eq)]
181/// A delimiter group whose contents have passed through cell framing.
182pub struct CellTokenGroup {
183    /// The delimiter.
184    delimiter: Delimiter,
185    /// The stream.
186    stream: CellTokenStream,
187    /// The leading trivia.
188    leading_trivia: LeadingTrivia,
189    /// The trailing trivia.
190    trailing_trivia: TrailingTrivia,
191}
192
193impl CellTokenGroup {
194    /// Returns the group's delimiter data.
195    pub fn delimiter(&self) -> &Delimiter {
196        &self.delimiter
197    }
198
199    /// Returns the group's delimiter family.
200    pub fn delim_kind(&self) -> DelimiterKind {
201        self.delimiter.kind
202    }
203
204    /// Returns the recursively framed contents.
205    pub fn stream(&self) -> &CellTokenStream {
206        &self.stream
207    }
208
209    /// Consumes the group and returns its recursively framed contents.
210    pub fn into_stream(self) -> CellTokenStream {
211        self.stream
212    }
213
214    /// Creates the value from augmented.
215    fn from_augmented(group: TokenGroup) -> Self {
216        let (delimiter, stream, leading_trivia, trailing_trivia) = group.into_parts();
217        Self {
218            delimiter,
219            stream: CellTokenStream::from_augmented(stream),
220            leading_trivia,
221            trailing_trivia,
222        }
223    }
224
225    /// Converts the value into lexical.
226    fn into_lexical(self) -> TokenGroup {
227        TokenGroup::from_parts(
228            self.delimiter,
229            self.stream.into_token_stream(),
230            self.leading_trivia,
231            self.trailing_trivia,
232        )
233    }
234
235    /// Converts the value into parser group.
236    fn into_parser_group(self) -> TokenGroup {
237        TokenGroup::from_parts(
238            self.delimiter,
239            self.stream.into_parser_token_stream(),
240            self.leading_trivia,
241            self.trailing_trivia,
242        )
243    }
244}
245
246impl Spanned for CellTokenGroup {
247    fn try_span(&self) -> Option<Span> {
248        self.delimiter.try_span()
249    }
250}
251
252impl Terminal for CellTokenGroup {
253    fn leading_trivia(&self) -> &LeadingTrivia {
254        &self.leading_trivia
255    }
256
257    fn leading_trivia_mut(&mut self) -> &mut LeadingTrivia {
258        &mut self.leading_trivia
259    }
260
261    fn trailing_trivia(&self) -> &TrailingTrivia {
262        &self.trailing_trivia
263    }
264
265    fn trailing_trivia_mut(&mut self) -> &mut TrailingTrivia {
266        &mut self.trailing_trivia
267    }
268}
269
270#[derive(Debug, Clone, Default, PartialEq, Eq)]
271/// The framed tokens of one independently parseable Macaulay2 cell.
272///
273/// Unlike a lexical [`TokenStream`], this representation has passed through
274/// cell framing. It may contain inferred zero-width adjacency punctuation at
275/// every delimiter depth, and its surrounding [`CellStream`] records the
276/// structural boundary after it.
277///
278/// The stream dereferences to `[CellTokenTree]` for parser access. Converting
279/// it back into a [`TokenStream`] removes inferred adjacency recursively,
280/// because adjacency is not a lexical token.
281pub struct CellTokenStream(Arc<CellTokenStreamData>);
282
283impl CellTokenStream {
284    /// Creates a constant-time fork over the same framed token storage.
285    ///
286    /// Mutating either fork internally detaches only the modified path.
287    pub fn fork(&self) -> Self {
288        self.clone()
289    }
290
291    /// Consumes this framed cell and returns its lexical token representation.
292    ///
293    /// Inferred adjacency is removed recursively. Comments, delimiter groups,
294    /// source spans, and physical spellings are preserved.
295    pub fn into_token_stream(self) -> TokenStream {
296        let CellTokenStreamData {
297            trees,
298            prefix,
299            suffix,
300            source,
301        } = self.into_data();
302        TokenStream::from_parts(TokenStreamParts {
303            trees: trees
304                .into_iter()
305                .filter_map(CellTokenTree::into_lexical)
306                .collect(),
307            prefix,
308            suffix,
309            source,
310        })
311    }
312
313    /// Returns this framed cell's lexical token representation.
314    ///
315    /// This is the borrowing counterpart of [`Self::into_token_stream`].
316    pub fn to_token_stream(&self) -> TokenStream {
317        self.clone().into_token_stream()
318    }
319
320    /// Borrows the shared backing data.
321    fn data(&self) -> &CellTokenStreamData {
322        &self.0
323    }
324
325    /// Converts the value into data.
326    fn into_data(self) -> CellTokenStreamData {
327        Arc::try_unwrap(self.0).unwrap_or_else(|data| (*data).clone())
328    }
329
330    /// Creates the value from augmented.
331    pub(crate) fn from_augmented(tokens: TokenStream) -> Self {
332        let TokenStreamParts {
333            trees,
334            prefix,
335            suffix,
336            source,
337        } = tokens.into_parts();
338        Self(Arc::new(CellTokenStreamData {
339            trees: trees
340                .into_iter()
341                .map(CellTokenTree::from_augmented)
342                .collect(),
343            prefix,
344            suffix,
345            source,
346        }))
347    }
348
349    /// Returns the associated source snapshot.
350    pub(crate) fn source(&self) -> Option<Arc<Source>> {
351        self.data().source.clone().or_else(|| {
352            self.iter()
353                .find_map(Spanned::try_span)
354                .map(|span| Arc::clone(span.source()))
355        })
356    }
357
358    #[cfg(test)]
359    pub(crate) fn shares_storage(&self, other: &Self) -> bool {
360        Arc::ptr_eq(&self.0, &other.0)
361    }
362
363    /// Converts the value into parser token stream.
364    ///
365    /// Comments are settled onto the tokens they follow, so a parser finds
366    /// them in trailing trivia and in the stream prefix only.
367    pub(crate) fn into_parser_token_stream(self) -> TokenStream {
368        let CellTokenStreamData {
369            trees,
370            prefix,
371            suffix,
372            source,
373        } = self.into_data();
374        let mut tokens = TokenStream::from_parts(TokenStreamParts {
375            trees: trees
376                .into_iter()
377                .map(CellTokenTree::into_parser_token)
378                .collect(),
379            prefix,
380            suffix,
381            source,
382        });
383        tokens.settle_trivia();
384        tokens
385    }
386
387    /// Creates the value from framed cells.
388    pub(crate) fn from_framed_cells(cells: impl IntoIterator<Item = CellTokenStream>) -> Self {
389        let mut tokens = TokenStream::new();
390        for cell in cells {
391            tokens.extend([cell.into_parser_token_stream()]);
392        }
393        Self::from_augmented(tokens)
394    }
395}
396
397impl Deref for CellTokenStream {
398    type Target = [CellTokenTree];
399
400    fn deref(&self) -> &Self::Target {
401        &self.data().trees
402    }
403}
404
405impl AsRef<[CellTokenTree]> for CellTokenStream {
406    fn as_ref(&self) -> &[CellTokenTree] {
407        self
408    }
409}
410
411impl Spanned for CellTokenStream {
412    fn try_span(&self) -> Option<Span> {
413        Span::join_all(
414            self.data()
415                .prefix
416                .try_span()
417                .into_iter()
418                .chain(self.iter().filter_map(Spanned::try_span))
419                .chain(self.data().suffix.try_span()),
420        )
421    }
422}
423
424impl Display for CellTokenStream {
425    fn fmt(&self, formatter: &mut Formatter<'_>) -> FmtResult {
426        self.to_token_stream().fmt(formatter)
427    }
428}
429
430impl IntoIterator for CellTokenStream {
431    type Item = CellTokenTree;
432    type IntoIter = IntoIter<CellTokenTree>;
433
434    fn into_iter(self) -> Self::IntoIter {
435        self.into_data().trees.into_iter()
436    }
437}
438
439impl<'syntax> IntoIterator for &'syntax CellTokenStream {
440    type Item = &'syntax CellTokenTree;
441    type IntoIter = Iter<'syntax, CellTokenTree>;
442
443    fn into_iter(self) -> Self::IntoIter {
444        self.iter()
445    }
446}
447
448#[derive(Debug, Clone, Default)]
449struct CellStreamData {
450    cells: Vec<CellTokenStream>,
451    source: Option<Arc<Source>>,
452    /// The span of the layout that precedes the first cell.
453    bof: Option<Span>,
454    /// The span of the layout that follows the final cell.
455    eof: Option<Span>,
456}
457
458#[derive(Debug, Clone, Default)]
459/// A sequence of independently parseable Macaulay2 token streams.
460///
461/// Cell boundaries are expressed by the collection itself. The boundary after
462/// its final element is also the end-of-file boundary, so neither fact needs a
463/// virtual token in the contained streams. Clones and [`Self::fork`] share
464/// immutable backing storage, with mutations detaching through copy-on-write.
465///
466/// The stream dereferences to `[CellTokenStream]`, so slice operations such as
467/// indexing, [`slice::iter`], [`slice::len`], and [`slice::is_empty`] are
468/// available directly. Iterating `&CellStream` borrows cells; iterating an
469/// owned value consumes it and yields owned cells.
470pub struct CellStream(Arc<CellStreamData>);
471
472impl PartialEq for CellStream {
473    fn eq(&self, other: &Self) -> bool {
474        self.as_ref() == other.as_ref()
475    }
476}
477
478impl Eq for CellStream {}
479
480impl Deref for CellStream {
481    type Target = [CellTokenStream];
482
483    fn deref(&self) -> &Self::Target {
484        &self.data().cells
485    }
486}
487
488impl AsRef<[CellTokenStream]> for CellStream {
489    fn as_ref(&self) -> &[CellTokenStream] {
490        self
491    }
492}
493
494impl CellStream {
495    /// Partitions a complete raw token stream into independently parseable cells.
496    ///
497    /// Top-level cell boundaries are retained structurally, and inferred
498    /// adjacency punctuation is inserted recursively. This operation checks
499    /// whether the final cell requires more input, but it does not validate the
500    /// complete Macaulay2 grammar.
501    ///
502    /// # Errors
503    ///
504    /// Returns [`CellStreamError`] when the final tokens require more input
505    /// before they can form an independently parseable cell, such as after an
506    /// operator without its right operand or inside an unclosed delimiter.
507    /// Use [`frame_recovering`] to retain completed cells alongside that tail.
508    pub fn new(tokens: TokenStream) -> Result<Self, CellStreamError> {
509        let source = tokens.source();
510        let bof = leading_layout_span(source.as_ref());
511        let eof = trailing_layout_span(source.as_ref());
512        let FramedCells { cells, incomplete } = frame_recovering(tokens);
513        match incomplete {
514            Some(tokens) => Err(CellStreamError {
515                span: end_span(&tokens),
516            }),
517            None => Ok(Self(Arc::new(CellStreamData {
518                cells,
519                source,
520                bof,
521                eof,
522            }))),
523        }
524    }
525
526    /// Creates a stream from token streams already known to be complete cells.
527    ///
528    /// This constructor does not inspect, repartition, or insert adjacency into
529    /// its input. Use [`Self::new`] or [`Tokenize::to_cell_stream`] when starting
530    /// from an unframed raw stream.
531    pub fn from_cells(cells: impl IntoIterator<Item = CellTokenStream>) -> Self {
532        Self::from_cells_with_source(cells, None)
533    }
534
535    /// Creates the value from cells with source.
536    pub(crate) fn from_cells_with_source(
537        cells: impl IntoIterator<Item = CellTokenStream>,
538        source: Option<Arc<Source>>,
539    ) -> Self {
540        Self(Arc::new(CellStreamData {
541            cells: cells.into_iter().collect(),
542            bof: leading_layout_span(source.as_ref()),
543            eof: trailing_layout_span(source.as_ref()),
544            source,
545        }))
546    }
547
548    /// Creates a constant-time fork over the same cell storage.
549    ///
550    /// The fork initially shares both the cell collection and every contained
551    /// [`TokenStream`]. Mutating either value preserves the other and copies
552    /// only the backing structures along the modified path. This is equivalent
553    /// to [`Clone::clone`], with a name that emphasizes speculative use.
554    pub fn fork(&self) -> Self {
555        self.clone()
556    }
557
558    /// Borrows the shared backing data.
559    fn data(&self) -> &CellStreamData {
560        &self.0
561    }
562
563    /// Borrows the shared backing data mutably.
564    fn data_mut(&mut self) -> &mut CellStreamData {
565        Arc::make_mut(&mut self.0)
566    }
567
568    /// Converts the value into data.
569    fn into_data(self) -> CellStreamData {
570        Arc::try_unwrap(self.0).unwrap_or_else(|data| (*data).clone())
571    }
572
573    /// Consumes the cells and flattens them into one raw token stream.
574    ///
575    /// Cell boundaries are not emitted as tokens and therefore cannot be
576    /// recovered structurally from the result alone. Original whitespace
577    /// between cells remains renderable when their spans share one source.
578    pub fn into_token_stream(self) -> TokenStream {
579        let source = self.complete_source();
580        let mut tokens = flatten_cells(self.into_data().cells);
581        if let Some(source) = source {
582            tokens.set_source(source);
583        }
584        tokens
585    }
586
587    /// Flattens borrowed cells into one raw token stream.
588    ///
589    /// This has the same boundary semantics as [`Self::into_token_stream`] but
590    /// leaves `self` available for reuse.
591    pub fn to_token_stream(&self) -> TokenStream {
592        let mut tokens = flatten_cells(self.iter().cloned());
593        if let Some(source) = self.complete_source() {
594            tokens.set_source(source);
595        }
596        tokens
597    }
598
599    /// Handles complete source.
600    pub(crate) fn complete_source(&self) -> Option<Arc<Source>> {
601        let source = self.data().source.as_ref()?;
602        let mut cursor = 0;
603        for cell in self {
604            if !covers_stream(cell, source, &mut cursor) {
605                return None;
606            }
607        }
608        source.text()[cursor..]
609            .bytes()
610            .all(|byte| byte.is_ascii_whitespace())
611            .then(|| Arc::clone(source))
612    }
613
614    /// Handles physical beginning-of-file.
615    ///
616    /// The span covers the layout before the first significant byte, so the
617    /// beginning-of-file marker built from it retains input that no cell owns.
618    /// For a stream whose cells do not cover one shared source snapshot it is
619    /// the empty span where the first cell starts.
620    pub(crate) fn physical_bof(&self) -> Span {
621        if let Some(source) = self.complete_source() {
622            let leading = source.leading_whitespace();
623            return Span::ranged(Arc::clone(&source), leading);
624        }
625        self.first()
626            .and_then(cell_first_span)
627            .and_then(|span| span.start().map(|start| span.at(start)))
628            .unwrap_or_else(|| Span::for_text(""))
629    }
630
631    /// Handles physical end-of-file.
632    ///
633    /// The span covers the layout between the last significant byte and the
634    /// physical end, so the end-of-file marker built from it retains input that
635    /// no cell owns. It stays empty for a stream whose cells do not cover one
636    /// shared source snapshot.
637    pub(crate) fn physical_eof(&self) -> Span {
638        if let Some(source) = self.complete_source() {
639            let trailing = source.trailing_whitespace();
640            return Span::ranged(Arc::clone(&source), trailing);
641        }
642        self.last().map_or_else(|| Span::for_text(""), end_span)
643    }
644
645    /// Creates the value from source file.
646    pub(crate) fn from_source_file(source_file: &crate::SourceFile) -> Self {
647        let mut cells = source_file
648            .cells
649            .iter()
650            .map(Lex::to_token_stream)
651            .collect::<Vec<_>>();
652        for (index, comments) in [
653            (0, &source_file.leading_trivia),
654            (cells.len(), source_file.eof.leading_trivia()),
655        ]
656        .into_iter()
657        .rev()
658        {
659            if !comments.is_empty() {
660                let mut comment_cell = TokenStream::new();
661                comment_cell.extend(comments.as_slice().iter().cloned());
662                cells.insert(index, comment_cell);
663            }
664        }
665        Self(Arc::new(CellStreamData {
666            cells: cells
667                .into_iter()
668                .map(CellTokenStream::from_augmented)
669                .collect(),
670            source: None,
671            bof: source_file.bof.try_span(),
672            eof: source_file.eof.try_span(),
673        }))
674    }
675}
676
677/// Returns whether the value covers stream.
678fn covers_stream(stream: &CellTokenStream, source: &Arc<Source>, cursor: &mut usize) -> bool {
679    for trivia in stream.data().prefix.as_slice() {
680        if !covers_span(trivia.span(), source, cursor) {
681            return false;
682        }
683    }
684    for token in stream.iter() {
685        for trivia in token.leading_trivia() {
686            if !covers_span(trivia.span(), source, cursor) {
687                return false;
688            }
689        }
690        let covered = match token {
691            CellTokenTree::Group(group) => {
692                covers_span(group.delimiter().opening_span(), source, cursor)
693                    && covers_stream(group.stream(), source, cursor)
694                    && group
695                        .delimiter()
696                        .closing_span()
697                        .is_none_or(|span| covers_span(span, source, cursor))
698            }
699            _ => covers_span(token.span(), source, cursor),
700        };
701        if !covered {
702            return false;
703        }
704        for trivia in token.trailing_trivia() {
705            if !covers_span(trivia.span(), source, cursor) {
706                return false;
707            }
708        }
709    }
710    stream
711        .data()
712        .suffix
713        .as_slice()
714        .iter()
715        .all(|trivia| covers_span(trivia.span(), source, cursor))
716}
717
718/// Returns whether the value covers span.
719fn covers_span(span: Span, source: &Arc<Source>, cursor: &mut usize) -> bool {
720    if !Arc::ptr_eq(span.source(), source) {
721        return false;
722    }
723    let (Some(start), Some(end)) = (span.start(), span.end()) else {
724        return false;
725    };
726    if start == end {
727        return start >= *cursor;
728    }
729    if start < *cursor
730        || !source.text()[*cursor..start]
731            .bytes()
732            .all(|byte| byte.is_ascii_whitespace())
733    {
734        return false;
735    }
736    *cursor = end;
737    true
738}
739
740impl Display for CellStream {
741    fn fmt(&self, formatter: &mut Formatter<'_>) -> FmtResult {
742        write_leading_layout(
743            formatter,
744            self.data().bof.as_ref(),
745            self.first().and_then(cell_first_span).as_ref(),
746        )?;
747        let mut previous: Option<Span> = None;
748        for (index, cell) in self.iter().enumerate() {
749            if index != 0 {
750                let next = cell_first_span(cell);
751                let mut recovered = false;
752                if let Some((left, right)) = previous.as_ref().zip(next.as_ref())
753                    && left.shares_source(right)
754                    && let (Some(end), Some(start)) = (left.end(), right.start())
755                    && end <= start
756                {
757                    let gap = &left.source().text()[end..start];
758                    // Synthetic text is assembled without layout, so cells
759                    // touching there were never written on one line.
760                    let written = !gap.is_empty() || !left.source().file().is_synthetic();
761                    if written && gap.bytes().all(|byte| byte.is_ascii_whitespace()) {
762                        formatter.write_str(gap)?;
763                        recovered = true;
764                    }
765                }
766                if !recovered {
767                    formatter.write_str("\n")?;
768                }
769            }
770            cell.fmt(formatter)?;
771            previous = cell_last_span(cell);
772        }
773        write_trailing_layout(formatter, self.data().eof.as_ref(), previous.as_ref())
774    }
775}
776
777/// Spans the layout opening a source snapshot the cells were framed from.
778fn leading_layout_span(source: Option<&Arc<Source>>) -> Option<Span> {
779    source.map(|source| Span::ranged(Arc::clone(source), source.leading_whitespace()))
780}
781
782/// Writes the layout the beginning-of-file span retains before the first cell.
783///
784/// The prefix is emitted only when it is the whitespace physically preceding
785/// the first cell, so a marker left before an edited or reassembled stream
786/// contributes nothing rather than stale text. Without cells, the
787/// end-of-file marker owns all layout.
788fn write_leading_layout(
789    formatter: &mut Formatter<'_>,
790    bof: Option<&Span>,
791    first_cell: Option<&Span>,
792) -> FmtResult {
793    let (Some(bof), Some(first)) = (bof, first_cell) else {
794        return Ok(());
795    };
796    if !first.shares_source(bof) {
797        return Ok(());
798    }
799    let Some((start, stop)) = bof.start().zip(first.start()) else {
800        return Ok(());
801    };
802    match bof.source().text().get(start..stop) {
803        Some(prefix) if prefix.bytes().all(|byte| byte.is_ascii_whitespace()) => {
804            formatter.write_str(prefix)
805        }
806        _ => Ok(()),
807    }
808}
809
810/// Spans the layout closing a source snapshot the cells were framed from.
811fn trailing_layout_span(source: Option<&Arc<Source>>) -> Option<Span> {
812    source.map(|source| Span::ranged(Arc::clone(source), source.trailing_whitespace()))
813}
814
815/// Writes the layout the end-of-file span retains after the final cell.
816///
817/// The suffix is emitted only when it is the whitespace physically following
818/// the last cell, so a marker left behind by an edited or reassembled stream
819/// contributes nothing rather than stale text.
820fn write_trailing_layout(
821    formatter: &mut Formatter<'_>,
822    eof: Option<&Span>,
823    last_cell: Option<&Span>,
824) -> FmtResult {
825    let Some(eof) = eof else {
826        return Ok(());
827    };
828    let resume = match last_cell {
829        Some(last) if last.shares_source(eof) => last.end(),
830        Some(_) => return Ok(()),
831        None => eof.start(),
832    };
833    let Some((resume, stop)) = resume.zip(eof.end()) else {
834        return Ok(());
835    };
836    match eof.source().text().get(resume..stop) {
837        Some(suffix) if suffix.bytes().all(|byte| byte.is_ascii_whitespace()) => {
838            formatter.write_str(suffix)
839        }
840        _ => Ok(()),
841    }
842}
843
844/// Handles cell first span.
845fn cell_first_span(tokens: &CellTokenStream) -> Option<Span> {
846    tokens
847        .data()
848        .prefix
849        .as_slice()
850        .first()
851        .map(Spanned::span)
852        .or_else(|| {
853            tokens.iter().next().map(|token| {
854                token
855                    .leading_trivia()
856                    .first()
857                    .map_or_else(|| token.span(), Spanned::span)
858            })
859        })
860        .or_else(|| tokens.data().suffix.as_slice().first().map(Spanned::span))
861}
862
863/// Handles cell last span.
864fn cell_last_span(tokens: &CellTokenStream) -> Option<Span> {
865    tokens
866        .data()
867        .suffix
868        .as_slice()
869        .last()
870        .map(Spanned::span)
871        .or_else(|| tokens.iter().next_back().map(token_end_span))
872        .or_else(|| tokens.data().prefix.as_slice().last().map(Spanned::span))
873}
874
875impl IntoIterator for CellStream {
876    type Item = CellTokenStream;
877    type IntoIter = IntoIter<CellTokenStream>;
878
879    fn into_iter(self) -> Self::IntoIter {
880        self.into_data().cells.into_iter()
881    }
882}
883
884impl<'syntax> IntoIterator for &'syntax CellStream {
885    type Item = &'syntax CellTokenStream;
886    type IntoIter = Iter<'syntax, CellTokenStream>;
887
888    fn into_iter(self) -> Self::IntoIter {
889        self.iter()
890    }
891}
892
893impl FromIterator<CellTokenStream> for CellStream {
894    fn from_iter<T: IntoIterator<Item = CellTokenStream>>(iter: T) -> Self {
895        Self::from_cells(iter)
896    }
897}
898
899/// Handles flatten cells.
900fn flatten_cells(cells: impl IntoIterator<Item = CellTokenStream>) -> TokenStream {
901    let mut output = TokenStream::new();
902    for cell in cells {
903        output.extend([cell.into_token_stream()]);
904    }
905    output
906}
907
908#[derive(Debug, Clone, PartialEq, Eq, Spanned)]
909/// An incomplete final cell encountered while framing a raw token stream.
910///
911/// The error's [`Spanned`] implementation points at the position where more
912/// input is required. It describes incompleteness only; malformed syntax that
913/// already has a structural boundary is diagnosed by a parser backend.
914pub struct CellStreamError {
915    /// The span.
916    span: Span,
917}
918
919impl Display for CellStreamError {
920    fn fmt(&self, formatter: &mut Formatter<'_>) -> FmtResult {
921        formatter.write_str("input ends inside an incomplete cell")
922    }
923}
924
925impl Error for CellStreamError {}
926
927/// Converts syntax or raw tokens into structural cells.
928///
929/// This boundary is intentionally distinct from [`Lex`]: most macros should
930/// preserve cell partitioning, while function-like expansion may explicitly
931/// choose a raw [`TokenStream`] when syntax is not valid before expansion.
932/// Implementations append to the supplied output rather than replacing its
933/// existing cells.
934///
935/// # Examples
936///
937/// ```
938/// use m2_syn::{CellStream, Tokenize, lex_str};
939///
940/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
941/// let first = lex_str("x = 1;")?;
942/// let second = lex_str("y = 2")?;
943/// let mut cells = CellStream::default();
944///
945/// first.tokenize(&mut cells)?;
946/// second.tokenize(&mut cells)?;
947///
948/// assert_eq!(cells.len(), 2);
949/// # Ok(())
950/// # }
951/// ```
952pub trait Tokenize {
953    /// Appends this value's independently parseable cells to `output`.
954    ///
955    /// # Errors
956    ///
957    /// Returns [`CellStreamError`] when a raw token prefix does not end at a
958    /// complete cell boundary.
959    fn tokenize(&self, output: &mut CellStream) -> Result<(), CellStreamError>;
960
961    /// Tokenizes this value into a fresh cell stream.
962    ///
963    /// This is the allocating convenience form of [`Self::tokenize`].
964    ///
965    /// # Errors
966    ///
967    /// Returns [`CellStreamError`] when a raw token prefix does not end at a
968    /// complete cell boundary.
969    fn to_cell_stream(&self) -> Result<CellStream, CellStreamError> {
970        let mut output = CellStream::default();
971        self.tokenize(&mut output)?;
972        Ok(output)
973    }
974}
975
976impl Tokenize for TokenStream {
977    fn tokenize(&self, output: &mut CellStream) -> Result<(), CellStreamError> {
978        let tokenized = CellStream::new(self.clone())?.into_data();
979        let output = output.data_mut();
980        if output.cells.is_empty() {
981            output.source = tokenized.source;
982            output.bof = tokenized.bof;
983            output.eof = tokenized.eof;
984        } else {
985            output.source = None;
986            output.eof = None;
987        }
988        output.cells.extend(tokenized.cells);
989        Ok(())
990    }
991}
992
993impl crate::SourceFile {
994    /// Places comments that follow every cell: on the end-of-file marker, or
995    /// on the file itself when it has no cells for them to follow.
996    pub(crate) fn set_closing_comments(&mut self, comments: Vec<Trivia>) {
997        if self.cells.is_empty() {
998            self.leading_trivia = LeadingTrivia::new(comments);
999        } else {
1000            self.eof.set_leading_trivia(comments);
1001        }
1002    }
1003}
1004
1005impl Tokenize for crate::SourceFile {
1006    fn tokenize(&self, output: &mut CellStream) -> Result<(), CellStreamError> {
1007        let tokenized = CellStream::from_source_file(self).into_data();
1008        let output = output.data_mut();
1009        if output.cells.is_empty() {
1010            output.bof = tokenized.bof;
1011        }
1012        output.eof = output.cells.is_empty().then_some(tokenized.eof).flatten();
1013        output.cells.extend(tokenized.cells);
1014        output.source = None;
1015        Ok(())
1016    }
1017}
1018
1019impl Tokenize for crate::ParsedFile {
1020    fn tokenize(&self, output: &mut CellStream) -> Result<(), CellStreamError> {
1021        self.cst().tokenize(output)
1022    }
1023}
1024
1025impl<T: Tokenize + ?Sized> Tokenize for &T {
1026    fn tokenize(&self, output: &mut CellStream) -> Result<(), CellStreamError> {
1027        (*self).tokenize(output)
1028    }
1029}
1030
1031/// The recovering result of partitioning one raw stream into source cells.
1032///
1033/// Only the final source prefix can be incomplete: every confirmed boundary
1034/// before it is retained in [`Self::cells`]. This lets realtime and recovering
1035/// parsers reuse completed work while continuing to accumulate the tail.
1036/// Both parts retain their original source spans and trivia, accessible through
1037/// [`Spanned`] and [`CellTokenStream::to_token_stream`]. A cell's span covers
1038/// its tokens; attached comments retain their own spans and can be collected
1039/// with [`crate::CommentTrivia::of`].
1040#[derive(Debug, Clone, PartialEq, Eq)]
1041pub struct FramedCells {
1042    /// Complete cells in their original source order.
1043    pub cells: Vec<CellTokenStream>,
1044    /// The final token prefix that still requires more input, when present.
1045    pub incomplete: Option<CellTokenStream>,
1046}
1047
1048/// Partitions raw tokens at confirmed top-level cell boundaries.
1049///
1050/// An unquoted top-level semicolon always confirms a boundary. A physical
1051/// newline confirms one only when the current
1052/// operator/clause state can end there. At end of input, a nonempty tail is
1053/// complete under the same rule unless its final delimiter group is unclosed;
1054/// otherwise the tail is returned through [`FramedCells::incomplete`]. Empty
1055/// or trivia-only input never becomes an incomplete cell.
1056///
1057/// The pass does not parse expressions or reject malformed grammar. It retains
1058/// comments and physical source spans, observes quote specifiers so their next
1059/// token cannot act as syntax, and inserts the zero-width adjacency punctuation
1060/// required by the native parser into both complete and incomplete results.
1061/// It retains no state between calls; callers decide which results to reuse.
1062/// Lexical recovery is separate: use [`crate::lex_str_recovering`] to obtain
1063/// tokens for source with unclosed delimiters or string literals, and retain
1064/// its diagnostics independently.
1065///
1066/// # Examples
1067///
1068/// ```
1069/// use m2_syn::{Spanned, frame_recovering, lex_str_recovering};
1070///
1071/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
1072/// let tokens = lex_str_recovering("a = 1\nf = x -> (")?;
1073/// let framed = frame_recovering(tokens.value);
1074/// assert_eq!(framed.cells.len(), 1);
1075/// assert_eq!(framed.cells[0].span().text(), Some("a = 1"));
1076/// let tail = framed.incomplete.unwrap();
1077/// assert_eq!(tail.span().text(), Some("f = x -> ("));
1078/// # Ok(())
1079/// # }
1080/// ```
1081pub fn frame_recovering(tokens: TokenStream) -> FramedCells {
1082    let mut tokens = tokens;
1083    // Stream-level trivia must follow the first/last framed cell instead of
1084    // being lost when the significant tokens are distributed between cells.
1085    let prefix_trivia = tokens.take_prefix_trivia();
1086    let suffix_trivia = tokens.take_suffix_trivia();
1087    let mut cells = Vec::new();
1088    let mut current = TokenStream::new();
1089    current.extend(prefix_trivia.into_vec());
1090    let mut pending_boundary = false;
1091    let mut quote_next = false;
1092
1093    for token in tokens {
1094        // Delay a semicolon boundary until the next token so any stream-level
1095        // suffix trivia discovered at EOF remains owned by that final cell.
1096        if pending_boundary {
1097            cells.push(insert_adjacency(take(&mut current)));
1098            pending_boundary = false;
1099            quote_next = false;
1100        } else if current
1101            .last()
1102            .is_some_and(|left| tokens_cross_line_break(left, &token))
1103            && newline_ends_cell(&current)
1104        {
1105            cells.push(insert_adjacency(take(&mut current)));
1106            quote_next = false;
1107        }
1108
1109        let quoted = take(&mut quote_next);
1110        let quotes_next = !quoted
1111            && matches!(&token, TokenTree::Ident(identifier)
1112                if is_quote_specifier(canonical_word_spelling(identifier.text())));
1113        let semicolon = !quoted && token.spelling() == Some(";");
1114
1115        current.extend([token]);
1116        if semicolon {
1117            pending_boundary = true;
1118        }
1119        if quotes_next {
1120            quote_next = true;
1121        }
1122    }
1123
1124    current.extend(suffix_trivia.into_vec());
1125
1126    // A final semicolon confirms the current cell independently of its other
1127    // operator or delimiter state.
1128    if pending_boundary {
1129        cells.push(insert_adjacency(current));
1130        return FramedCells {
1131            cells,
1132            incomplete: None,
1133        };
1134    }
1135
1136    // Comments without syntax are complete retained input, not a request for
1137    // another line of source.
1138    if !has_syntax(&current) {
1139        if current.has_trivia() {
1140            cells.push(CellTokenStream::from_augmented(current));
1141        }
1142        return FramedCells {
1143            cells,
1144            incomplete: None,
1145        };
1146    }
1147
1148    // EOF behaves like a possible line boundary, except that an unclosed group
1149    // always keeps its containing cell recoverable.
1150    if !ends_in_incomplete_group(&current) && newline_ends_cell(&current) {
1151        cells.push(insert_adjacency(current));
1152        FramedCells {
1153            cells,
1154            incomplete: None,
1155        }
1156    } else {
1157        FramedCells {
1158            cells,
1159            incomplete: Some(insert_adjacency(current)),
1160        }
1161    }
1162}
1163
1164/// Handles end span.
1165pub(crate) fn end_span(tokens: &CellTokenStream) -> Span {
1166    let ends_with_semicolon = ends_with_cell_semicolon(tokens);
1167    let span = tokens
1168        .iter()
1169        .next_back()
1170        .map(token_end_span)
1171        .or_else(|| tokens.data().suffix.as_slice().last().map(Spanned::span))
1172        .or_else(|| tokens.data().prefix.as_slice().last().map(Spanned::span));
1173    let Some(span) = span else {
1174        return Span::for_text("");
1175    };
1176    let Some(end) = span.end() else {
1177        return Span::unlocated(Arc::clone(span.source()));
1178    };
1179    if !ends_with_semicolon && let Some(newline) = span.source().text()[end..].find('\n') {
1180        return span.at(end + newline + 1);
1181    }
1182    span.at(end)
1183}
1184
1185/// Handles token end span.
1186fn token_end_span(token: &CellTokenTree) -> Span {
1187    if let Some(trivia) = token.trailing_trivia().last() {
1188        return trivia.span();
1189    }
1190    if let CellTokenTree::Group(group) = token
1191        && !group.delimiter().is_closed()
1192    {
1193        let contents = group.stream();
1194        if let Some(inner) = contents.iter().next_back() {
1195            return token_end_span(inner);
1196        }
1197        if let Some(trivia) = contents
1198            .data()
1199            .suffix
1200            .as_slice()
1201            .last()
1202            .or_else(|| contents.data().prefix.as_slice().last())
1203        {
1204            return trivia.span();
1205        }
1206    }
1207    token.span()
1208}
1209
1210/// Returns whether the value ends with cell semicolon.
1211pub(crate) fn ends_with_cell_semicolon(tokens: &CellTokenStream) -> bool {
1212    let mut quote_next = false;
1213    let mut final_semicolon = false;
1214    for token in tokens.iter() {
1215        let quoted = take(&mut quote_next);
1216        final_semicolon = !quoted && token.spelling() == Some(";");
1217        quote_next = !quoted
1218            && matches!(token, CellTokenTree::Ident(identifier)
1219                if is_quote_specifier(canonical_word_spelling(identifier.text())));
1220    }
1221    final_semicolon
1222}
1223
1224/// Returns whether the value has syntax.
1225fn has_syntax(stream: &TokenStream) -> bool {
1226    !stream.is_empty()
1227}
1228
1229/// Returns whether the value ends in incomplete group.
1230fn ends_in_incomplete_group(stream: &TokenStream) -> bool {
1231    matches!(stream.iter().next_back(), Some(TokenTree::Group(group)) if !group.delimiter().is_closed())
1232}
1233
1234/// Inserts adjacency.
1235fn insert_adjacency(mut tokens: TokenStream) -> CellTokenStream {
1236    let prefix = tokens.take_prefix_trivia();
1237    let suffix = tokens.take_suffix_trivia();
1238    let mut output = TokenStream::new();
1239    output.extend(prefix.into_vec());
1240    let mut left_can_end = false;
1241    let mut quote_next = false;
1242
1243    for mut token in tokens {
1244        if let TokenTree::Group(group) = &mut token {
1245            let contents = take(group.stream_mut());
1246            *group.stream_mut() = insert_adjacency(contents).into_parser_token_stream();
1247        }
1248
1249        let quoted = take(&mut quote_next);
1250        let crossed_newline = output
1251            .last()
1252            .is_some_and(|left| tokens_cross_line_break(left, &token));
1253        if left_can_end && !quoted && !crossed_newline && can_begin_implicit_operand(&token) {
1254            let span = token.expect_span().subspan(0, 0);
1255            output.extend([TokenTree::Punct(Punct::from_span(span))]);
1256        }
1257
1258        let quotes_next = !quoted
1259            && matches!(&token, TokenTree::Ident(identifier)
1260                if is_quote_specifier(canonical_word_spelling(identifier.text())));
1261        left_can_end = quoted || can_end_operand(&token);
1262        quote_next = quotes_next;
1263        output.extend([token]);
1264    }
1265    for trivia in suffix.into_vec() {
1266        output.push_leading_trivia(trivia);
1267    }
1268    CellTokenStream::from_augmented(output)
1269}
1270
1271/// Handles tokens cross line break.
1272fn tokens_cross_line_break(left: &TokenTree, right: &TokenTree) -> bool {
1273    let left = left.expect_span();
1274    let right = right.expect_span();
1275    if !left.shares_source(&right) {
1276        return false;
1277    }
1278    let (Some(end), Some(start)) = (left.end(), right.start()) else {
1279        return false;
1280    };
1281    end <= start && left.source().text()[end..start].contains('\n')
1282}
1283
1284/// Handles token parse rule.
1285fn token_parse_rule(token: &TokenTree) -> Option<TokenParseRule> {
1286    let spelling = token.spelling().map(canonical_word_spelling)?;
1287    TOKEN_PARSE_RULES
1288        .binary_search_by_key(&spelling, |(declared, _)| *declared)
1289        .ok()
1290        .map(|index| TOKEN_PARSE_RULES[index].1)
1291}
1292
1293/// Returns whether the value can begin implicit operand.
1294fn can_begin_implicit_operand(token: &TokenTree) -> bool {
1295    match token_parse_rule(token) {
1296        Some(TokenParseRule::Prefix(_, _) | TokenParseRule::Unary(_, _)) => true,
1297        Some(_) => false,
1298        None => matches!(
1299            token,
1300            TokenTree::Ident(_) | TokenTree::Literal(_) | TokenTree::Group(_)
1301        ),
1302    }
1303}
1304
1305/// Returns whether the value can end operand.
1306fn can_end_operand(token: &TokenTree) -> bool {
1307    match token_parse_rule(token) {
1308        Some(TokenParseRule::Postfix(_)) => true,
1309        Some(_) => false,
1310        None => matches!(
1311            token,
1312            TokenTree::Ident(_) | TokenTree::Literal(_) | TokenTree::Group(_)
1313        ),
1314    }
1315}
1316
1317#[cfg(test)]
1318mod tests {
1319    use super::{Arc, CellStream, frame_recovering};
1320    use crate::{
1321        CellTokenTree, CommentTrivia, Spanned, TokenTree, Tokenize, TriviaKind, lex_str,
1322        lex_str_recovering,
1323    };
1324
1325    #[test]
1326    fn recovering_framing_retains_completed_prefix_and_incomplete_tail() {
1327        let source = "a = 1\nf = x -> (";
1328        let recovered = lex_str_recovering(source).unwrap();
1329        let framed = frame_recovering(recovered.value);
1330
1331        assert_eq!(framed.cells.len(), 1);
1332        let completed = framed.cells[0].span();
1333        assert_eq!(completed.text(), Some("a = 1"));
1334        assert_eq!(completed.range(), Some(0..5));
1335        let tail = framed.incomplete.unwrap();
1336        assert_eq!(tail.span().text(), Some("f = x -> ("));
1337        assert_eq!(tail.span().range(), Some(6..source.len()));
1338        assert!(completed.shares_source(&tail.span()));
1339    }
1340
1341    #[test]
1342    fn recovering_framing_preserves_comments_in_both_parts() {
1343        let source = "-- complete\na = 1 -- retained\n-- incomplete\nf = x -> ( -- inside\n";
1344        let framed = frame_recovering(lex_str_recovering(source).unwrap().value);
1345        assert_eq!(framed.cells.len(), 1);
1346        let completed = &framed.cells[0];
1347        let tail = framed.incomplete.unwrap();
1348
1349        let comments = |cell: &super::CellTokenStream| {
1350            let comments = CommentTrivia::of(&cell.to_token_stream());
1351            comments
1352                .outer()
1353                .iter()
1354                .chain(comments.inner())
1355                .map(|trivia| {
1356                    assert_eq!(trivia.span().text(), Some(trivia.text()));
1357                    assert!(trivia.span().shares_source(&cell.span()));
1358                    trivia.text().to_owned()
1359                })
1360                .collect::<Vec<_>>()
1361        };
1362        assert_eq!(comments(completed), ["-- complete", "-- retained"]);
1363        assert_eq!(comments(&tail), ["-- incomplete", "-- inside"]);
1364        assert_eq!(completed.span().text(), Some("a = 1"));
1365        assert_eq!(tail.span().text(), Some("f = x -> ("));
1366    }
1367
1368    #[test]
1369    fn recovering_framing_handles_complete_empty_and_trivia_only_input() {
1370        for (source, expected) in [
1371            ("a = 1; b = 2", vec!["a = 1;", "b = 2"]),
1372            ("", vec![]),
1373            (" \n ", vec![]),
1374            ("-- comment", vec!["-- comment"]),
1375        ] {
1376            let framed = frame_recovering(lex_str(source).unwrap());
1377            assert!(framed.incomplete.is_none(), "{source:?}");
1378            assert_eq!(
1379                framed
1380                    .cells
1381                    .iter()
1382                    .map(ToString::to_string)
1383                    .collect::<Vec<_>>(),
1384                expected,
1385                "{source:?}",
1386            );
1387        }
1388    }
1389
1390    fn count_cell_adjacency(tokens: &[CellTokenTree]) -> usize {
1391        tokens
1392            .iter()
1393            .map(|token| match token {
1394                CellTokenTree::Group(group) => count_cell_adjacency(group.stream()),
1395                CellTokenTree::Adj(_) => 1,
1396                _ => 0,
1397            })
1398            .sum()
1399    }
1400
1401    fn count_raw_adjacency(tokens: &[TokenTree]) -> usize {
1402        tokens
1403            .iter()
1404            .map(|token| match token {
1405                TokenTree::Group(group) => count_raw_adjacency(group.stream()),
1406                TokenTree::Punct(punctuation) if punctuation.text().is_empty() => 1,
1407                _ => 0,
1408            })
1409            .sum()
1410    }
1411
1412    #[test]
1413    fn cells_are_structural_partitions() {
1414        let cells = CellStream::new(lex_str("f \t-- note\n x").unwrap()).unwrap();
1415        assert_eq!(cells.len(), 2);
1416        assert_eq!(cells.to_string(), "f \t-- note\n x");
1417    }
1418
1419    #[test]
1420    fn same_line_application_gets_one_empty_punctuation() {
1421        let cells = lex_str("f x").unwrap().to_cell_stream().unwrap();
1422        assert_eq!(
1423            cells
1424                .iter()
1425                .flat_map(|cell| cell.iter())
1426                .filter(|token| matches!(token, CellTokenTree::Adj(_)))
1427                .count(),
1428            1,
1429        );
1430    }
1431
1432    #[test]
1433    fn flattening_a_framed_cell_restores_lexical_tokens() {
1434        let raw = lex_str("f (g x)").unwrap();
1435        assert_eq!(count_raw_adjacency(&raw), 0);
1436
1437        let cells = raw.to_cell_stream().unwrap();
1438        assert_eq!(count_cell_adjacency(&cells[0]), 2);
1439
1440        let raw = cells.into_token_stream();
1441        assert_eq!(count_raw_adjacency(&raw), 0);
1442        assert_eq!(raw.to_string(), "f (g x)");
1443    }
1444
1445    #[test]
1446    fn line_comment_blocks_begin_the_cell_on_their_right() {
1447        let cells = CellStream::new(lex_str("x\n-- first\n-- second\ny").unwrap()).unwrap();
1448        assert_eq!(cells.len(), 2);
1449        let comments = cells[1].iter().next().unwrap().leading_trivia();
1450        assert_eq!(comments.len(), 1);
1451        assert_eq!(comments[0].kind(), TriviaKind::LineComment);
1452        assert_eq!(comments[0].text(), "-- first\n-- second");
1453    }
1454
1455    #[test]
1456    fn a_blank_line_separates_outer_comment_blocks() {
1457        let cells = CellStream::new(lex_str("-- first\n\n-- second\ny").unwrap()).unwrap();
1458        let comments = cells[0].iter().next().unwrap().leading_trivia();
1459
1460        assert_eq!(comments.len(), 2);
1461        assert_eq!(comments[0].text(), "-- first");
1462        assert_eq!(comments[1].text(), "-- second");
1463    }
1464
1465    #[test]
1466    fn line_comments_do_not_split_an_incomplete_cell() {
1467        let cells = CellStream::new(lex_str("x + -- reason\ny").unwrap()).unwrap();
1468        assert_eq!(cells.len(), 1);
1469        assert!(cells[0].iter().any(|token| {
1470            token
1471                .trailing_trivia()
1472                .iter()
1473                .any(|trivia| trivia.kind() == TriviaKind::LineComment)
1474        }));
1475    }
1476
1477    #[test]
1478    fn fork_copies_only_the_modified_cell_path() {
1479        let original = CellStream::new(lex_str("left; right").unwrap()).unwrap();
1480        let mut fork = original.fork();
1481
1482        assert!(Arc::ptr_eq(&original.0, &fork.0));
1483
1484        fork.data_mut().cells.pop();
1485
1486        assert!(!Arc::ptr_eq(&original.0, &fork.0));
1487        assert_eq!(original.to_string(), "left; right");
1488        assert_eq!(fork.to_string(), "left;");
1489        assert!(original[0].shares_storage(&fork[0]));
1490    }
1491}