Skip to main content

m2_syn/parser/
parse.rs

1//! Parsing boundaries over tokenized cells and their shared token representation.
2//!
3//! [`Parser`] consumes a whole [`CellStream`] and is the backend interface.
4//! [`Parse`] consumes one value from a [`ParseStream`] and is the generated
5//! dual of [`crate::Lex`]. Both native parsing and generated token parsing use the
6//! same cursor facts instead of maintaining parallel token arrays.
7
8use std::error::Error;
9use std::fmt::{Display, Formatter};
10use std::iter::FusedIterator;
11
12use crate::cell_stream::frame_recovering;
13use crate::{
14    Adj, CellStream, ExternalCstNode, IdentToken, LexError, LiteralToken, Migrate, MigrateError,
15    Punct, SourceFile, Span, Spanned, Terminal, Token, TokenGroup, TokenStream, TokenTree,
16    TrailingTrivia, lex_str,
17};
18
19/// A parser backend that produces one typed syntax target.
20///
21/// Backends may construct `T` directly or adapt their concrete syntax tree
22/// through [`ExternalCstNode`] and [`migrate`].
23pub trait Parser<T = SourceFile> {
24    /// The backend-specific parse failure.
25    type Error: Error;
26
27    /// Parses `cells` into one typed syntax target.
28    ///
29    /// # Errors
30    ///
31    /// Returns the backend's [`Self::Error`] when it cannot produce `T`.
32    fn parse_cells(&mut self, cells: CellStream) -> Result<T, Self::Error>;
33}
34
35/// Parses an existing cell stream with an explicitly selected backend.
36///
37/// # Errors
38///
39/// Returns the selected backend's error when parsing fails.
40pub fn parse_with<T, P>(parser: &mut P, cells: CellStream) -> Result<T, P::Error>
41where
42    P: Parser<T>,
43{
44    parser.parse_cells(cells)
45}
46
47/// A typed syntax value that can consume itself from an M2 token cursor.
48pub trait Parse: Sized {
49    /// Consumes this value from `input`.
50    ///
51    /// # Errors
52    ///
53    /// Returns [`TokenParseError`] when the next tokens do not encode `Self`.
54    fn parse(input: &mut ParseStream) -> Result<Self, TokenParseError>;
55}
56
57impl<T: Parse> Parse for Box<T> {
58    fn parse(input: &mut ParseStream) -> Result<Self, TokenParseError> {
59        T::parse(input).map(Self::new)
60    }
61}
62
63impl Parse for TokenTree {
64    fn parse(input: &mut ParseStream) -> Result<Self, TokenParseError> {
65        input
66            .next_raw_token()
67            .ok_or_else(|| TokenParseError::unexpected_end("a token tree", input.eof_span()))
68    }
69}
70
71/// Implements raw-token parsing for one [`TokenTree`] variant.
72macro_rules! parse_raw_variant {
73    ($ty:ty, $variant:ident, $expected:literal) => {
74        impl Parse for $ty {
75            fn parse(input: &mut ParseStream) -> Result<Self, TokenParseError> {
76                let token = TokenTree::parse(input)?;
77                match token {
78                    TokenTree::$variant(value) => Ok(value),
79                    token => Err(TokenParseError::unexpected_token($expected, &token)),
80                }
81            }
82        }
83    };
84}
85
86parse_raw_variant!(TokenGroup, Group, "a delimited group");
87parse_raw_variant!(LiteralToken, Literal, "a literal");
88parse_raw_variant!(Punct, Punct, "punctuation");
89parse_raw_variant!(IdentToken, Ident, "an identifier");
90
91impl Parse for Adj {
92    fn parse(input: &mut ParseStream) -> Result<Self, TokenParseError> {
93        let Some(token) = input.next_significant() else {
94            return Err(TokenParseError::unexpected_end(
95                "adjacency",
96                input.eof_span(),
97            ));
98        };
99        match token.token {
100            TokenTree::Punct(punctuation) if punctuation.text().is_empty() => {
101                let mut adjacency = Self::new("", punctuation.expect_span());
102                *adjacency.trailing_trivia_mut() = token.trailing_trivia;
103                Ok(adjacency)
104            }
105            token => Err(TokenParseError::unexpected_token("adjacency", &token)),
106        }
107    }
108}
109
110impl Parse for TokenStream {
111    fn parse(input: &mut ParseStream) -> Result<Self, TokenParseError> {
112        Ok(input.by_ref().collect())
113    }
114}
115
116/// Parses one complete token stream as `T`.
117///
118/// # Errors
119///
120/// Returns [`TokenParseError`] when `T` cannot be parsed or leaves trailing
121/// tokens.
122pub fn parse2<T: Parse>(tokens: TokenStream) -> Result<T, TokenParseError> {
123    let mut input = ParseStream::new(tokens);
124    let value = T::parse(&mut input)?;
125    input.finish()?;
126    Ok(value)
127}
128
129/// Lexes one source cell and parses it as a typed syntax fragment.
130///
131/// This applies the grammar expressed by `T`'s [`Parse`] implementation. It
132/// does not validate the fragment against the complete Macaulay2 grammar; use
133/// [`crate::parse_native`], or `parse_file` with the `tree-sitter` feature,
134/// when parsing a source file.
135/// A delimited fragment containing semicolons is still one cell because cell
136/// boundaries are recognized only at the top level.
137///
138/// # Errors
139///
140/// Returns [`FragmentParseError`] when lexing fails, the source contains
141/// multiple top-level cells, or the cell does not parse as `T`.
142pub fn parse_fragment_str<T: Parse>(source: &str) -> Result<T, FragmentParseError> {
143    let framed = frame_recovering(lex_str(source)?);
144    let mut cells = framed.cells;
145    cells.extend(framed.incomplete);
146    if cells.len() > 1 {
147        return Err(FragmentParseError::MultipleCells { count: cells.len() });
148    }
149    let tokens = cells.pop().unwrap_or_default();
150    parse2(tokens.into_parser_token_stream()).map_err(Into::into)
151}
152
153/// An error produced while lexing or parsing one typed syntax fragment.
154#[derive(Debug, Clone, PartialEq, Eq)]
155pub enum FragmentParseError {
156    /// The source could not be converted into token trees.
157    Lex(LexError),
158    /// The token trees did not match the requested fragment type.
159    Parse(TokenParseError),
160    /// The source contained top-level cell boundaries and therefore was not a
161    /// single fragment.
162    MultipleCells {
163        /// The number of cells produced by the lexer.
164        count: usize,
165    },
166}
167
168impl Display for FragmentParseError {
169    fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
170        match self {
171            Self::Lex(error) => error.fmt(formatter),
172            Self::Parse(error) => error.fmt(formatter),
173            Self::MultipleCells { count } => {
174                write!(formatter, "expected one source cell, found {count}")
175            }
176        }
177    }
178}
179
180impl Error for FragmentParseError {
181    fn source(&self) -> Option<&(dyn Error + 'static)> {
182        match self {
183            Self::Lex(error) => Some(error),
184            Self::Parse(error) => Some(error),
185            Self::MultipleCells { .. } => None,
186        }
187    }
188}
189
190impl From<LexError> for FragmentParseError {
191    fn from(error: LexError) -> Self {
192        Self::Lex(error)
193    }
194}
195
196impl From<TokenParseError> for FragmentParseError {
197    fn from(error: TokenParseError) -> Self {
198        Self::Parse(error)
199    }
200}
201
202/// One non-trivia token together with facts about the trivia immediately
203/// before it. Parser backends share this view instead of maintaining their own
204/// token vectors, cursor positions, and newline scanners.
205#[derive(Debug, Clone)]
206pub(crate) struct SignificantToken {
207    /// The token.
208    pub(crate) token: TokenTree,
209    /// The crossed newline.
210    pub(crate) crossed_newline: bool,
211    /// The trailing trivia.
212    pub(crate) trailing_trivia: TrailingTrivia,
213}
214
215#[derive(Debug, Clone)]
216#[allow(clippy::large_enum_variant)]
217/// Internal alternatives for Lookahead.
218pub(crate) enum Lookahead {
219    /// The Token alternative.
220    Token(SignificantToken),
221    /// The End alternative.
222    End(Span),
223}
224
225impl Spanned for Lookahead {
226    fn span(&self) -> Span {
227        match self {
228            Self::Token(token) => token.token.span(),
229            Self::End(span) => span.clone(),
230        }
231    }
232}
233
234/// An owned, cheaply forkable cursor over one immutable token stream.
235///
236/// Forks share the [`TokenStream`] backing allocation and copy only their
237/// current position. Advancing a fork never changes the cursor from which it
238/// came.
239#[derive(Clone)]
240pub struct ParseStream {
241    /// The tokens.
242    tokens: TokenStream,
243    /// The position.
244    position: usize,
245    /// The zero-width position just past the last token.
246    ///
247    /// End of input is the absence of a token, which the cursor already
248    /// reports. Only the position needs carrying, for diagnostics that point
249    /// at where input ran out.
250    eof_span: Span,
251}
252
253impl ParseStream {
254    /// Creates a cursor at the beginning of `tokens`.
255    pub fn new(tokens: TokenStream) -> Self {
256        let eof_span = tokens
257            .last()
258            .and_then(|token| {
259                let span = token.expect_span();
260                Some(span.at(span.end()?))
261            })
262            .unwrap_or_else(|| Span::for_text(""));
263        Self {
264            tokens,
265            position: 0,
266            eof_span,
267        }
268    }
269
270    /// Creates an independent cursor over the same immutable token storage.
271    pub fn fork(&self) -> Self {
272        self.clone()
273    }
274
275    /// Commits this cursor to a position reached by one of its forks.
276    ///
277    /// # Panics
278    ///
279    /// Panics if `fork` belongs to another token stream.
280    pub fn advance_to(&mut self, fork: &Self) {
281        assert!(
282            self.tokens.shares_storage(&fork.tokens),
283            "cannot advance to a cursor from another token stream"
284        );
285        self.position = fork.position;
286    }
287
288    /// Returns the next token without consuming it.
289    pub fn peek(&self) -> Option<&TokenTree> {
290        self.tokens().get(self.position)
291    }
292
293    /// Handles lookahead.
294    pub(crate) fn lookahead(&self) -> Lookahead {
295        let Some(mut token) = self.tokens().get(self.position).cloned() else {
296            return Lookahead::End(self.eof_span.clone());
297        };
298        let crossed_newline = self.position.checked_sub(1).is_some_and(|left| {
299            self.tokens()
300                .get(left)
301                .is_some_and(|left| tokens_cross_line_break(left, &token))
302        });
303        let trailing_trivia = std::mem::take(token.trailing_trivia_mut());
304        Lookahead::Token(SignificantToken {
305            token,
306            crossed_newline,
307            trailing_trivia,
308        })
309    }
310
311    /// Consumes lookahead.
312    pub(crate) fn consume_lookahead(&mut self) -> Lookahead {
313        let lookahead = self.lookahead();
314        if matches!(lookahead, Lookahead::Token(_)) {
315            self.position += 1;
316        }
317        lookahead
318    }
319
320    /// Handles next significant.
321    pub(crate) fn next_significant(&mut self) -> Option<SignificantToken> {
322        match self.consume_lookahead() {
323            Lookahead::Token(token) => Some(token),
324            Lookahead::End(_) => None,
325        }
326    }
327
328    /// Skips trivia.
329    pub(crate) fn skip_trivia(&mut self) {
330        // Comments are owned by tokens, so every stream element is significant.
331    }
332
333    /// Handles end-of-file span.
334    pub(crate) fn eof_span(&self) -> Span {
335        self.eof_span.clone()
336    }
337
338    /// Consumes and returns the next non-trivia token.
339    pub fn next_token(&mut self) -> Option<TokenTree> {
340        self.next_raw_token()
341    }
342
343    /// Consumes the next token, including trivia.
344    pub fn next_raw_token(&mut self) -> Option<TokenTree> {
345        self.next()
346    }
347
348    /// Parses one generated typed token from its matching raw atom.
349    ///
350    /// # Errors
351    ///
352    /// Returns [`TokenParseError`] when input ends or the next significant token does
353    /// not match `T`.
354    pub fn parse_token<T: Token>(&mut self) -> Result<T, TokenParseError> {
355        let Some(token) = self.next_significant() else {
356            return Err(TokenParseError::unexpected_end(
357                T::SPELLING,
358                self.eof_span(),
359            ));
360        };
361        let error = TokenParseError::unexpected_token(T::SPELLING, &token.token);
362        T::from_token_tree_with_trivia(token.token, token.trailing_trivia).ok_or(error)
363    }
364
365    /// Returns whether no significant token remains.
366    pub fn is_eof(&self) -> bool {
367        self.peek().is_none()
368    }
369
370    /// Handles tokens.
371    fn tokens(&self) -> &TokenStream {
372        &self.tokens
373    }
374
375    /// Handles finish.
376    fn finish(&mut self) -> Result<(), TokenParseError> {
377        if let Some(token) = self.next() {
378            return Err(TokenParseError::TrailingToken {
379                found: token.description(),
380                span: token.expect_span(),
381            });
382        }
383        Ok(())
384    }
385}
386
387/// Handles tokens cross line break.
388fn tokens_cross_line_break(left: &TokenTree, right: &TokenTree) -> bool {
389    let left = left.expect_span();
390    let right = right.expect_span();
391    if !left.shares_source(&right) {
392        return false;
393    }
394    let (Some(end), Some(start)) = (left.end(), right.start()) else {
395        return false;
396    };
397    end <= start && left.source().text()[end..start].contains('\n')
398}
399
400impl Iterator for ParseStream {
401    type Item = TokenTree;
402
403    fn next(&mut self) -> Option<Self::Item> {
404        let token = self.tokens().get(self.position)?.clone();
405        self.position += 1;
406        Some(token)
407    }
408
409    fn size_hint(&self) -> (usize, Option<usize>) {
410        let remaining = self.tokens().len().saturating_sub(self.position);
411        (remaining, Some(remaining))
412    }
413}
414
415impl ExactSizeIterator for ParseStream {}
416impl FusedIterator for ParseStream {}
417
418#[derive(Debug, Clone, PartialEq, Eq, Spanned)]
419/// A failure while consuming typed syntax from a [`ParseStream`].
420pub enum TokenParseError {
421    /// Input ended before the required syntax appeared.
422    UnexpectedEnd {
423        /// The expected syntax description or spelling.
424        expected: &'static str,
425        /// The end-of-input position.
426        span: Span,
427    },
428    /// The next token did not match the required syntax.
429    UnexpectedToken {
430        /// The expected syntax description or spelling.
431        expected: &'static str,
432        /// A readable description of the received token.
433        found: String,
434        /// The received token span.
435        span: Span,
436    },
437    /// A complete parse left a significant token unconsumed.
438    TrailingToken {
439        /// A readable description of the remaining token.
440        found: String,
441        /// The remaining token span.
442        span: Span,
443    },
444    /// A parser backend reported a higher-level syntax failure.
445    Syntax {
446        /// The backend's diagnostic message.
447        message: String,
448        /// The syntax associated with the failure.
449        span: Span,
450    },
451}
452
453impl TokenParseError {
454    /// Handles unexpected end.
455    pub(crate) fn unexpected_end(expected: &'static str, span: Span) -> Self {
456        Self::UnexpectedEnd { expected, span }
457    }
458
459    /// Handles unexpected.
460    pub(crate) fn unexpected(expected: &'static str, found: impl Into<String>, span: Span) -> Self {
461        Self::UnexpectedToken {
462            expected,
463            found: found.into(),
464            span,
465        }
466    }
467
468    /// Handles unexpected token.
469    pub(crate) fn unexpected_token(expected: &'static str, token: &TokenTree) -> Self {
470        Self::unexpected(expected, token.description(), token.expect_span())
471    }
472}
473
474impl Display for TokenParseError {
475    fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
476        match self {
477            Self::UnexpectedEnd { expected, .. } => {
478                write!(formatter, "expected `{expected}`, found the end of input")
479            }
480            Self::UnexpectedToken {
481                expected, found, ..
482            } => write!(formatter, "expected `{expected}`, found `{found}`"),
483            Self::TrailingToken { found, .. } => {
484                write!(formatter, "expected the end of input, found `{found}`")
485            }
486            Self::Syntax { message, .. } => formatter.write_str(message),
487        }
488    }
489}
490
491impl Error for TokenParseError {}
492
493/// Migrates a typed syntax target from a parser-specific CST root.
494///
495/// # Errors
496///
497/// Returns [`MigrateError`] when the root cannot migrate `T`.
498pub fn migrate<T, N>(root: N) -> Result<T, MigrateError>
499where
500    N: ExternalCstNode,
501    T: Migrate<N>,
502{
503    T::migrate(root)
504}
505
506#[cfg(test)]
507mod tests {
508    use super::*;
509    use crate::{LiteralKind, quote_m2};
510
511    #[test]
512    fn parse_stream_iteration_excludes_its_eof_token() {
513        let mut input = ParseStream::new(quote_m2!(name + 1));
514        assert_eq!(input.len(), 3);
515        let emitted = input.by_ref().collect::<TokenStream>();
516
517        assert_eq!(emitted.to_string(), "name+1");
518        assert!(input.is_eof());
519        assert_eq!(input.next(), None);
520        assert_eq!(input.next(), None);
521    }
522
523    #[test]
524    fn exact_size_counts_every_remaining_token() {
525        let mut input = ParseStream::new(quote_m2!(name));
526
527        assert_eq!(input.len(), 1);
528        assert!(input.next().is_some());
529        assert_eq!(input.len(), 0);
530    }
531
532    #[test]
533    fn raw_token_categories_implement_parse() {
534        let identifier = parse2::<IdentToken>(quote_m2!(name)).unwrap();
535        let literal = parse2::<LiteralToken>(quote_m2!(1)).unwrap();
536        let punctuation = parse2::<Punct>(quote_m2!(+)).unwrap();
537        let group = parse2::<TokenGroup>(quote_m2!((name))).unwrap();
538
539        assert_eq!(identifier.text(), "name");
540        assert_eq!(literal.kind, LiteralKind::Integer);
541        assert_eq!(punctuation.text(), "+");
542        assert_eq!(crate::Lex::to_token_stream(&group).to_string(), "(name)");
543    }
544
545    #[test]
546    fn lookahead_reports_the_stored_eof_span_through_spanned() {
547        let tokens = lex_str("name").unwrap();
548        let mut input = ParseStream::new(tokens);
549        input.next_token();
550
551        let span = input.lookahead().expect_span();
552        assert_eq!(span.start().unwrap(), "name".len());
553        assert_eq!(span, input.eof_span().span());
554    }
555}