Skip to main content

m2_syn/
parse.rs

1//! Parsing boundaries over the shared raw 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 `ToTokens`. 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::io::Cursor;
11use std::iter::FusedIterator;
12use std::rc::Rc;
13
14use crate::cst::Token;
15use crate::{
16    CellStream, ExternalCstNode, Group, IdentToken, LexError, Literal, Punct, Reconstruct,
17    ReconstructError, SourceFile, SourceId, Span, Spanned, TextRange, TokenStream, TokenTree,
18    Trivia, TriviaKind, lex_str,
19};
20
21/// A parser backend that produces one typed syntax target.
22///
23/// Backends may construct `T` directly or adapt their concrete syntax tree
24/// through [`CstNode`] and [`reconstruct`].
25pub trait Parser<T = SourceFile> {
26    type Error: Error;
27
28    fn parse_cells(&mut self, tokens: CellStream) -> Result<T, Self::Error>;
29}
30
31/// Parses an existing cell stream with an explicitly selected backend.
32pub fn parse_with<T, P>(parser: &mut P, tokens: CellStream) -> Result<T, P::Error>
33where
34    P: Parser<T>,
35{
36    parser.parse_cells(tokens)
37}
38
39/// A typed syntax value that can consume itself from an M2 token cursor.
40pub trait Parse: Sized {
41    fn parse(input: &mut ParseStream) -> Result<Self, TokenParseError>;
42}
43
44impl<T: Parse> Parse for Box<T> {
45    fn parse(input: &mut ParseStream) -> Result<Self, TokenParseError> {
46        T::parse(input).map(Self::new)
47    }
48}
49
50impl Parse for TokenTree {
51    fn parse(input: &mut ParseStream) -> Result<Self, TokenParseError> {
52        input
53            .next_raw_token()
54            .ok_or(TokenParseError::UnexpectedEnd {
55                expected: "a token tree",
56                span: input.eof_span(),
57            })
58    }
59}
60
61macro_rules! parse_raw_variant {
62    ($ty:ty, $variant:ident, $expected:literal) => {
63        impl Parse for $ty {
64            fn parse(input: &mut ParseStream) -> Result<Self, TokenParseError> {
65                let token = TokenTree::parse(input)?;
66                let span = token.span();
67                let found = token_description(&token);
68                match token {
69                    TokenTree::$variant(value) => Ok(value),
70                    _ => Err(TokenParseError::UnexpectedToken {
71                        expected: $expected,
72                        found,
73                        span,
74                    }),
75                }
76            }
77        }
78    };
79}
80
81parse_raw_variant!(Group, Group, "a delimited group");
82parse_raw_variant!(Literal, Literal, "a literal");
83parse_raw_variant!(Punct, Punct, "punctuation");
84parse_raw_variant!(IdentToken, Ident, "an identifier");
85parse_raw_variant!(Trivia, Trivia, "trivia");
86
87impl Parse for TokenStream {
88    fn parse(input: &mut ParseStream) -> Result<Self, TokenParseError> {
89        Ok(input.by_ref().collect())
90    }
91}
92
93/// Parses one complete token stream as `T`.
94pub fn parse1<T: Parse>(tokens: TokenStream) -> Result<T, TokenParseError> {
95    let mut input = ParseStream::new(tokens);
96    let value = T::parse(&mut input)?;
97    input.finish()?;
98    Ok(value)
99}
100
101/// Lexes one source cell and parses it as a typed syntax fragment.
102///
103/// This applies the grammar expressed by `T`'s [`Parse`] implementation. It
104/// does not validate the fragment against the complete Macaulay2 grammar; use
105/// [`crate::parse_native`] or [`crate::parse_file`] when parsing a source file.
106/// A delimited fragment containing semicolons is still one cell because cell
107/// boundaries are recognized only at the top level.
108pub fn parse_fragment_str<T: Parse>(
109    source: &str,
110    source_id: SourceId,
111) -> Result<T, FragmentParseError> {
112    let cells = lex_str(source, source_id)?.into_cells();
113    let tokens = match cells.len() {
114        0 => TokenStream::new(),
115        1 => cells.into_iter().next().unwrap().into_stream(),
116        count => return Err(FragmentParseError::MultipleCells { count }),
117    };
118    parse1(tokens).map_err(Into::into)
119}
120
121/// An error produced while lexing or parsing one typed syntax fragment.
122#[derive(Debug, Clone, PartialEq, Eq)]
123pub enum FragmentParseError {
124    /// The source could not be converted into token trees.
125    Lex(LexError),
126    /// The token trees did not match the requested fragment type.
127    Parse(TokenParseError),
128    /// The source contained top-level cell boundaries and therefore was not a
129    /// single fragment.
130    MultipleCells {
131        /// The number of cells produced by the lexer.
132        count: usize,
133    },
134}
135
136impl Display for FragmentParseError {
137    fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
138        match self {
139            Self::Lex(error) => error.fmt(formatter),
140            Self::Parse(error) => error.fmt(formatter),
141            Self::MultipleCells { count } => {
142                write!(formatter, "expected one source cell, found {count}")
143            }
144        }
145    }
146}
147
148impl Error for FragmentParseError {
149    fn source(&self) -> Option<&(dyn Error + 'static)> {
150        match self {
151            Self::Lex(error) => Some(error),
152            Self::Parse(error) => Some(error),
153            Self::MultipleCells { .. } => None,
154        }
155    }
156}
157
158impl From<LexError> for FragmentParseError {
159    fn from(error: LexError) -> Self {
160        Self::Lex(error)
161    }
162}
163
164impl From<TokenParseError> for FragmentParseError {
165    fn from(error: TokenParseError) -> Self {
166        Self::Parse(error)
167    }
168}
169
170/// One non-trivia token together with facts about the trivia immediately
171/// before it. Parser backends share this view instead of maintaining their own
172/// token vectors, cursor positions, and newline scanners.
173#[derive(Debug, Clone)]
174pub(crate) struct SignificantToken {
175    pub(crate) token: TokenTree,
176    pub(crate) crossed_newline: bool,
177    pub(crate) leading_span: Span,
178}
179
180#[derive(Debug, Clone)]
181pub(crate) enum Lookahead {
182    Token(SignificantToken),
183    End(Span),
184}
185
186impl Spanned for Lookahead {
187    fn span(&self) -> Span {
188        match self {
189            Self::Token(token) => token.token.span(),
190            Self::End(span) => *span,
191        }
192    }
193}
194
195/// An owned, cheaply forkable cursor over one immutable token stream.
196///
197/// Forks share the token storage through [`Rc`] and copy only their current
198/// position. Advancing a fork never changes the cursor from which it came.
199#[derive(Clone)]
200pub struct ParseStream {
201    cursor: Cursor<Rc<TokenStream>>,
202}
203
204impl ParseStream {
205    /// Creates a cursor at the beginning of `tokens`.
206    pub fn new(mut tokens: TokenStream) -> Self {
207        let eof_span = tokens
208            .last()
209            .and_then(|token| {
210                let source = token.span().source().ok()?;
211                let end = token.span().end_point().ok()?;
212                Some(Span::new(source, TextRange::from_point(end)))
213            })
214            .unwrap_or_else(Span::detached);
215        tokens.push_eof(eof_span);
216        Self {
217            cursor: Cursor::new(Rc::new(tokens)),
218        }
219    }
220
221    /// Creates an independent cursor over the same immutable token storage.
222    pub fn fork(&self) -> Self {
223        self.clone()
224    }
225
226    /// Commits this cursor to a position reached by one of its forks.
227    ///
228    /// Panics if `fork` belongs to another token stream.
229    pub fn advance_to(&mut self, fork: &Self) {
230        assert!(
231            Rc::ptr_eq(self.cursor.get_ref(), fork.cursor.get_ref()),
232            "cannot advance to a cursor from another token stream"
233        );
234        self.cursor.set_position(fork.cursor.position());
235    }
236
237    /// Returns the next non-trivia token without consuming it.
238    pub fn peek(&self) -> Option<&TokenTree> {
239        match self.tokens().get(self.significant_position()) {
240            Some(TokenTree::Eof(_)) | None => None,
241            token => token,
242        }
243    }
244
245    pub(crate) fn lookahead(&self) -> Lookahead {
246        let mut position = self.position();
247        let mut crossed_newline = false;
248        let mut leading_span = Span::detached();
249        while let Some(TokenTree::Trivia(trivia)) = self.tokens().get(position) {
250            crossed_newline |= trivia.contains_line_break();
251            leading_span = leading_span.join(trivia.span());
252            position += 1;
253        }
254        let Some(token) = self.tokens().get(position).cloned() else {
255            unreachable!("ParseStream always stores a final EOF token")
256        };
257        if let TokenTree::Eof(span) = token {
258            return Lookahead::End(span);
259        }
260        if leading_span == Span::detached() {
261            leading_span = token
262                .span()
263                .source()
264                .ok()
265                .zip(token.span().start_point().ok())
266                .map(|(source, point)| Span::new(source, TextRange::from_point(point)))
267                .unwrap_or_else(Span::detached);
268        }
269        Lookahead::Token(SignificantToken {
270            token,
271            crossed_newline,
272            leading_span,
273        })
274    }
275
276    pub(crate) fn consume_lookahead(&mut self) -> Lookahead {
277        let lookahead = self.lookahead();
278        if matches!(lookahead, Lookahead::Token(_)) {
279            self.set_position(self.significant_position() + 1);
280        }
281        lookahead
282    }
283
284    pub(crate) fn skip_trivia(&mut self) {
285        self.set_position(self.significant_position());
286    }
287
288    pub(crate) fn eof_span(&self) -> Span {
289        match self.tokens().last() {
290            Some(TokenTree::Eof(span)) => *span,
291            _ => unreachable!("ParseStream always stores a final EOF token"),
292        }
293    }
294
295    /// Consumes and returns the next non-trivia token.
296    pub fn next_token(&mut self) -> Option<TokenTree> {
297        self.set_position(self.significant_position());
298        self.next_raw_token()
299    }
300
301    /// Consumes the next token, including trivia.
302    pub fn next_raw_token(&mut self) -> Option<TokenTree> {
303        self.next()
304    }
305
306    /// Parses one generated typed token from its matching raw atom.
307    pub fn parse_token<T: Token>(&mut self) -> Result<T, TokenParseError> {
308        let token = self.next_raw_token();
309        let Some(token) = token else {
310            return Err(TokenParseError::UnexpectedEnd {
311                expected: T::SPELLING,
312                span: self.eof_span(),
313            });
314        };
315        let span = token.span();
316        let found = token_description(&token);
317        T::from_token_tree(token).ok_or(TokenParseError::UnexpectedToken {
318            expected: T::SPELLING,
319            found,
320            span,
321        })
322    }
323
324    /// Returns whether only the stored end-of-input marker remains.
325    pub fn is_eof(&self) -> bool {
326        self.peek().is_none()
327    }
328
329    fn significant_position(&self) -> usize {
330        let mut position = self.position();
331        while matches!(self.tokens().get(position), Some(TokenTree::Trivia(_))) {
332            position += 1;
333        }
334        position
335    }
336
337    fn tokens(&self) -> &TokenStream {
338        self.cursor.get_ref()
339    }
340
341    fn position(&self) -> usize {
342        self.cursor.position() as usize
343    }
344
345    fn set_position(&mut self, position: usize) {
346        self.cursor.set_position(position as u64);
347    }
348
349    fn finish(&mut self) -> Result<(), TokenParseError> {
350        let Some(token) = self.next_token() else {
351            return Ok(());
352        };
353        Err(TokenParseError::TrailingToken {
354            found: token_description(&token),
355            span: token.span(),
356        })
357    }
358}
359
360impl Iterator for ParseStream {
361    type Item = TokenTree;
362
363    fn next(&mut self) -> Option<Self::Item> {
364        let token = self.tokens().get(ParseStream::position(self))?.clone();
365        match token {
366            TokenTree::Eof(_) => None,
367            token => {
368                self.cursor.set_position(self.cursor.position() + 1);
369                Some(token)
370            }
371        }
372    }
373
374    fn size_hint(&self) -> (usize, Option<usize>) {
375        let remaining = self
376            .tokens()
377            .len()
378            .saturating_sub(ParseStream::position(self) + 1);
379        (remaining, Some(remaining))
380    }
381}
382
383impl ExactSizeIterator for ParseStream {}
384impl FusedIterator for ParseStream {}
385
386#[derive(Debug, Clone, PartialEq, Eq, Spanned)]
387pub enum TokenParseError {
388    UnexpectedEnd {
389        expected: &'static str,
390        span: Span,
391    },
392    UnexpectedToken {
393        expected: &'static str,
394        found: String,
395        span: Span,
396    },
397    TrailingToken {
398        found: String,
399        span: Span,
400    },
401    Syntax {
402        message: String,
403        span: Span,
404    },
405}
406
407impl Display for TokenParseError {
408    fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
409        match self {
410            Self::UnexpectedEnd { expected, .. } => {
411                write!(formatter, "expected `{expected}`, found the end of input")
412            }
413            Self::UnexpectedToken {
414                expected, found, ..
415            } => write!(formatter, "expected `{expected}`, found `{found}`"),
416            Self::TrailingToken { found, .. } => {
417                write!(formatter, "expected the end of input, found `{found}`")
418            }
419            Self::Syntax { message, .. } => formatter.write_str(message),
420        }
421    }
422}
423
424impl Error for TokenParseError {}
425
426fn token_description(token: &TokenTree) -> String {
427    match token {
428        TokenTree::Group(group) => group.delim_kind().to_string(),
429        TokenTree::Trivia(trivia) => match trivia.kind() {
430            TriviaKind::Whitespace => "whitespace".into(),
431            TriviaKind::CarriageReturn => "carriage return".into(),
432            TriviaKind::LineBreak => "line break".into(),
433            TriviaKind::LineComment => "line comment".into(),
434            TriviaKind::BlockComment => "block comment".into(),
435        },
436        token => token.spelling().unwrap_or("").to_owned(),
437    }
438}
439
440/// Reconstructs a typed syntax target from a parser-specific CST root.
441pub fn reconstruct<T, N>(root: N) -> Result<T, ReconstructError>
442where
443    N: ExternalCstNode,
444    T: Reconstruct<N>,
445{
446    T::reconstruct(root)
447}
448
449#[cfg(test)]
450mod tests {
451    use super::*;
452    use crate::{LiteralKind, quote_m2};
453
454    #[test]
455    fn parse_stream_iteration_excludes_its_eof_token() {
456        let mut input = ParseStream::new(quote_m2!(name + 1));
457        let emitted = input.by_ref().collect::<TokenStream>();
458
459        assert_eq!(emitted.to_string(), "name+1");
460        assert!(input.is_eof());
461        assert_eq!(input.next(), None);
462        assert_eq!(input.next(), None);
463    }
464
465    #[test]
466    fn raw_token_categories_implement_parse() {
467        let identifier = parse1::<IdentToken>(quote_m2!(name)).unwrap();
468        let literal = parse1::<Literal>(quote_m2!(1)).unwrap();
469        let punctuation = parse1::<Punct>(quote_m2!(+)).unwrap();
470        let group = parse1::<Group>(quote_m2!((name))).unwrap();
471
472        assert_eq!(identifier.text(), "name");
473        assert_eq!(literal.kind, LiteralKind::Integer);
474        assert_eq!(punctuation.text(), "+");
475        assert_eq!(group.to_string(), "(name)");
476    }
477
478    #[test]
479    fn lookahead_reports_the_stored_eof_span_through_spanned() {
480        let source = SourceId(91);
481        let tokens = lex_str("name", source)
482            .unwrap()
483            .into_cells()
484            .pop()
485            .unwrap()
486            .into_stream();
487        let mut input = ParseStream::new(tokens);
488        input.next_token();
489
490        let span = input.lookahead().span();
491        assert_eq!(span.source(), Ok(source));
492        assert_eq!(span.start_point().unwrap().byte, "name".len());
493        assert_eq!(span, input.eof_span());
494    }
495}