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;
11use std::rc::Rc;
12
13use crate::cell_stream::frame_recovering;
14use crate::{
15    Adj, CellStream, ExternalCstNode, IdentToken, LexError, LiteralToken, Migrate, MigrateError,
16    Punct, SourceFile, Span, Spanned, Terminal, Token, TokenGroup, TokenStream, TokenTree,
17    TrailingTrivia, lex_str,
18};
19
20/// A parser backend that produces one typed syntax target.
21///
22/// Backends may construct `T` directly or adapt their concrete syntax tree
23/// through [`ExternalCstNode`] and [`migrate`].
24pub trait Parser<T = SourceFile> {
25    /// The backend-specific parse failure.
26    type Error: Error;
27
28    /// Parses `cells` into one typed syntax target.
29    ///
30    /// # Errors
31    ///
32    /// Returns the backend's [`Self::Error`] when it cannot produce `T`.
33    fn parse_cells(&mut self, cells: CellStream) -> Result<T, Self::Error>;
34}
35
36/// Parses an existing cell stream with an explicitly selected backend.
37///
38/// # Errors
39///
40/// Returns the selected backend's error when parsing fails.
41pub fn parse_with<T, P>(parser: &mut P, cells: CellStream) -> Result<T, P::Error>
42where
43    P: Parser<T>,
44{
45    parser.parse_cells(cells)
46}
47
48/// A typed syntax value that can consume itself from an M2 token cursor.
49pub trait Parse: Sized {
50    /// Consumes this value from `input`.
51    ///
52    /// # Errors
53    ///
54    /// Returns [`TokenParseError`] when the next tokens do not encode `Self`.
55    fn parse(input: &mut ParseStream) -> Result<Self, TokenParseError>;
56}
57
58impl<T: Parse> Parse for Box<T> {
59    fn parse(input: &mut ParseStream) -> Result<Self, TokenParseError> {
60        T::parse(input).map(Self::new)
61    }
62}
63
64impl Parse for TokenTree {
65    fn parse(input: &mut ParseStream) -> Result<Self, TokenParseError> {
66        input
67            .next_raw_token()
68            .ok_or_else(|| TokenParseError::unexpected_end("a token tree", input.eof_span()))
69    }
70}
71
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).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    pub(crate) token: TokenTree,
208    pub(crate) crossed_newline: bool,
209    pub(crate) trailing_trivia: TrailingTrivia,
210}
211
212#[derive(Debug, Clone)]
213#[allow(clippy::large_enum_variant)]
214pub(crate) enum Lookahead {
215    Token(SignificantToken),
216    End(Span),
217}
218
219impl Spanned for Lookahead {
220    fn span(&self) -> Span {
221        match self {
222            Self::Token(token) => token.token.span(),
223            Self::End(span) => span.clone(),
224        }
225    }
226}
227
228/// An owned, cheaply forkable cursor over one immutable token stream.
229///
230/// Forks share the token storage through [`Rc`] and copy only their current
231/// position. Advancing a fork never changes the cursor from which it came.
232#[derive(Clone)]
233pub struct ParseStream {
234    tokens: Rc<TokenStream>,
235    position: usize,
236    /// The zero-width position just past the last token.
237    ///
238    /// End of input is the absence of a token, which the cursor already
239    /// reports. Only the position needs carrying, for diagnostics that point
240    /// at where input ran out.
241    eof_span: Span,
242}
243
244impl ParseStream {
245    /// Creates a cursor at the beginning of `tokens`.
246    pub fn new(tokens: TokenStream) -> Self {
247        let eof_span = tokens
248            .last()
249            .and_then(|token| {
250                let span = token.expect_span();
251                Some(span.at(span.end()?))
252            })
253            .unwrap_or_else(|| Span::for_text(""));
254        Self {
255            tokens: Rc::new(tokens),
256            position: 0,
257            eof_span,
258        }
259    }
260
261    /// Creates an independent cursor over the same immutable token storage.
262    pub fn fork(&self) -> Self {
263        self.clone()
264    }
265
266    /// Commits this cursor to a position reached by one of its forks.
267    ///
268    /// # Panics
269    ///
270    /// Panics if `fork` belongs to another token stream.
271    pub fn advance_to(&mut self, fork: &Self) {
272        assert!(
273            Rc::ptr_eq(&self.tokens, &fork.tokens),
274            "cannot advance to a cursor from another token stream"
275        );
276        self.position = fork.position;
277    }
278
279    /// Returns the next token without consuming it.
280    pub fn peek(&self) -> Option<&TokenTree> {
281        self.tokens().get(self.position)
282    }
283
284    pub(crate) fn lookahead(&self) -> Lookahead {
285        let Some(mut token) = self.tokens().get(self.position).cloned() else {
286            return Lookahead::End(self.eof_span.clone());
287        };
288        let crossed_newline = self.position.checked_sub(1).is_some_and(|left| {
289            self.tokens()
290                .get(left)
291                .is_some_and(|left| tokens_cross_line_break(left, &token))
292        });
293        let trailing_trivia = std::mem::take(token.trailing_trivia_mut());
294        Lookahead::Token(SignificantToken {
295            token,
296            crossed_newline,
297            trailing_trivia,
298        })
299    }
300
301    pub(crate) fn consume_lookahead(&mut self) -> Lookahead {
302        let lookahead = self.lookahead();
303        if matches!(lookahead, Lookahead::Token(_)) {
304            self.position += 1;
305        }
306        lookahead
307    }
308
309    pub(crate) fn next_significant(&mut self) -> Option<SignificantToken> {
310        match self.consume_lookahead() {
311            Lookahead::Token(token) => Some(token),
312            Lookahead::End(_) => None,
313        }
314    }
315
316    pub(crate) fn skip_trivia(&mut self) {
317        // Comments are owned by tokens, so every stream element is significant.
318    }
319
320    pub(crate) fn eof_span(&self) -> Span {
321        self.eof_span.clone()
322    }
323
324    /// Consumes and returns the next non-trivia token.
325    pub fn next_token(&mut self) -> Option<TokenTree> {
326        self.next_raw_token()
327    }
328
329    /// Consumes the next token, including trivia.
330    pub fn next_raw_token(&mut self) -> Option<TokenTree> {
331        self.next()
332    }
333
334    /// Parses one generated typed token from its matching raw atom.
335    ///
336    /// # Errors
337    ///
338    /// Returns [`TokenParseError`] when input ends or the next significant token does
339    /// not match `T`.
340    pub fn parse_token<T: Token>(&mut self) -> Result<T, TokenParseError> {
341        let Some(token) = self.next_significant() else {
342            return Err(TokenParseError::unexpected_end(
343                T::SPELLING,
344                self.eof_span(),
345            ));
346        };
347        let error = TokenParseError::unexpected_token(T::SPELLING, &token.token);
348        T::from_token_tree_with_trivia(token.token, token.trailing_trivia).ok_or(error)
349    }
350
351    /// Returns whether no significant token remains.
352    pub fn is_eof(&self) -> bool {
353        self.peek().is_none()
354    }
355
356    fn tokens(&self) -> &TokenStream {
357        &self.tokens
358    }
359
360    fn finish(&mut self) -> Result<(), TokenParseError> {
361        if let Some(token) = self.next() {
362            return Err(TokenParseError::TrailingToken {
363                found: token.description(),
364                span: token.expect_span(),
365            });
366        }
367        Ok(())
368    }
369}
370
371fn tokens_cross_line_break(left: &TokenTree, right: &TokenTree) -> bool {
372    let left = left.expect_span();
373    let right = right.expect_span();
374    if !left.shares_source(&right) {
375        return false;
376    }
377    let (Some(end), Some(start)) = (left.end(), right.start()) else {
378        return false;
379    };
380    end <= start && left.source().text()[end..start].contains('\n')
381}
382
383impl Iterator for ParseStream {
384    type Item = TokenTree;
385
386    fn next(&mut self) -> Option<Self::Item> {
387        let token = self.tokens().get(self.position)?.clone();
388        self.position += 1;
389        Some(token)
390    }
391
392    fn size_hint(&self) -> (usize, Option<usize>) {
393        let remaining = self.tokens().len().saturating_sub(self.position);
394        (remaining, Some(remaining))
395    }
396}
397
398impl ExactSizeIterator for ParseStream {}
399impl FusedIterator for ParseStream {}
400
401#[derive(Debug, Clone, PartialEq, Eq, Spanned)]
402/// A failure while consuming typed syntax from a [`ParseStream`].
403pub enum TokenParseError {
404    /// Input ended before the required syntax appeared.
405    UnexpectedEnd {
406        /// The expected syntax description or spelling.
407        expected: &'static str,
408        /// The end-of-input position.
409        span: Span,
410    },
411    /// The next token did not match the required syntax.
412    UnexpectedToken {
413        /// The expected syntax description or spelling.
414        expected: &'static str,
415        /// A readable description of the received token.
416        found: String,
417        /// The received token span.
418        span: Span,
419    },
420    /// A complete parse left a significant token unconsumed.
421    TrailingToken {
422        /// A readable description of the remaining token.
423        found: String,
424        /// The remaining token span.
425        span: Span,
426    },
427    /// A parser backend reported a higher-level syntax failure.
428    Syntax {
429        /// The backend's diagnostic message.
430        message: String,
431        /// The syntax associated with the failure.
432        span: Span,
433    },
434}
435
436impl TokenParseError {
437    pub(crate) fn unexpected_end(expected: &'static str, span: Span) -> Self {
438        Self::UnexpectedEnd { expected, span }
439    }
440
441    pub(crate) fn unexpected(expected: &'static str, found: impl Into<String>, span: Span) -> Self {
442        Self::UnexpectedToken {
443            expected,
444            found: found.into(),
445            span,
446        }
447    }
448
449    pub(crate) fn unexpected_token(expected: &'static str, token: &TokenTree) -> Self {
450        Self::unexpected(expected, token.description(), token.expect_span())
451    }
452}
453
454impl Display for TokenParseError {
455    fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
456        match self {
457            Self::UnexpectedEnd { expected, .. } => {
458                write!(formatter, "expected `{expected}`, found the end of input")
459            }
460            Self::UnexpectedToken {
461                expected, found, ..
462            } => write!(formatter, "expected `{expected}`, found `{found}`"),
463            Self::TrailingToken { found, .. } => {
464                write!(formatter, "expected the end of input, found `{found}`")
465            }
466            Self::Syntax { message, .. } => formatter.write_str(message),
467        }
468    }
469}
470
471impl Error for TokenParseError {}
472
473/// Migrates a typed syntax target from a parser-specific CST root.
474///
475/// # Errors
476///
477/// Returns [`MigrateError`] when the root cannot migrate `T`.
478pub fn migrate<T, N>(root: N) -> Result<T, MigrateError>
479where
480    N: ExternalCstNode,
481    T: Migrate<N>,
482{
483    T::migrate(root)
484}
485
486#[cfg(test)]
487mod tests {
488    use super::*;
489    use crate::{LiteralKind, quote_m2};
490
491    #[test]
492    fn parse_stream_iteration_excludes_its_eof_token() {
493        let mut input = ParseStream::new(quote_m2!(name + 1));
494        assert_eq!(input.len(), 3);
495        let emitted = input.by_ref().collect::<TokenStream>();
496
497        assert_eq!(emitted.to_string(), "name+1");
498        assert!(input.is_eof());
499        assert_eq!(input.next(), None);
500        assert_eq!(input.next(), None);
501    }
502
503    #[test]
504    fn exact_size_counts_every_remaining_token() {
505        let mut input = ParseStream::new(quote_m2!(name));
506
507        assert_eq!(input.len(), 1);
508        assert!(input.next().is_some());
509        assert_eq!(input.len(), 0);
510    }
511
512    #[test]
513    fn raw_token_categories_implement_parse() {
514        let identifier = parse2::<IdentToken>(quote_m2!(name)).unwrap();
515        let literal = parse2::<LiteralToken>(quote_m2!(1)).unwrap();
516        let punctuation = parse2::<Punct>(quote_m2!(+)).unwrap();
517        let group = parse2::<TokenGroup>(quote_m2!((name))).unwrap();
518
519        assert_eq!(identifier.text(), "name");
520        assert_eq!(literal.kind, LiteralKind::Integer);
521        assert_eq!(punctuation.text(), "+");
522        assert_eq!(crate::Lex::to_token_stream(&group).to_string(), "(name)");
523    }
524
525    #[test]
526    fn lookahead_reports_the_stored_eof_span_through_spanned() {
527        let tokens = lex_str("name").unwrap();
528        let mut input = ParseStream::new(tokens);
529        input.next_token();
530
531        let span = input.lookahead().expect_span();
532        assert_eq!(span.start().unwrap(), "name".len());
533        assert_eq!(span, input.eof_span().span());
534    }
535}