Skip to main content

lutra_compiler/parser/
mod.rs

1mod def;
2mod expr;
3mod helpers;
4mod interpolation;
5mod lexer;
6mod test;
7mod types;
8
9pub use self::lexer::{Token, TokenKind, lex_source_recovery};
10
11use chumsky::input::Input as _;
12use chumsky::prelude::*;
13
14use crate::Span;
15use crate::diagnostic::Diagnostic;
16use crate::pr;
17
18pub fn is_submodule(source: &str) -> Result<bool, Vec<Diagnostic>> {
19    let mut tokens = lexer::lex_source(source)?;
20    Ok(tokens
21        .next()
22        .is_some_and(|t| matches!(t.kind, TokenKind::Keyword("submodule"))))
23}
24
25pub fn parse_source(
26    source: &str,
27    source_id: u16,
28) -> (Option<pr::Source>, Vec<Diagnostic>, Vec<Token>) {
29    let (tokens, mut errors) = lexer::lex_source_recovery(source, source_id);
30
31    let mut trivia = Vec::new();
32
33    let ast = if let Some(tokens) = tokens {
34        trivia = tokens.trivia;
35        let tokens = prepare_tokens(tokens.semantic, source_id);
36
37        let (ast, errs) = def::source().parse(tokens.as_input()).into_output_errors();
38
39        let mut parsed = ast;
40        if let Some(parsed) = &mut parsed {
41            parsed.span.start = 0;
42            parsed.span.len = source.len() as u16;
43        }
44
45        errors.extend(errs.into_iter().map(Diagnostic::from));
46
47        parsed
48    } else {
49        None
50    };
51
52    (ast, errors, trivia)
53}
54
55pub fn parse_expr(source: &str, source_id: u16) -> (Option<pr::Expr>, Vec<Diagnostic>) {
56    let (tokens, mut errors) = lexer::lex_source_recovery(source, source_id);
57
58    let ast = if let Some(tokens) = tokens {
59        let tokens = prepare_tokens(tokens.semantic, source_id);
60
61        let (ast, errs) = expr::expr(types::type_expr())
62            .parse(tokens.as_input())
63            .into_output_errors();
64        errors.extend(errs.into_iter().map(Diagnostic::from));
65        ast
66    } else {
67        None
68    };
69
70    (ast, errors)
71}
72
73pub fn parse_path(source: &str) -> Option<pr::Path> {
74    let tokens = lexer::lex_source(source).ok()?;
75    let tokens = prepare_tokens(tokens.collect(), 0);
76
77    let (path, _) = expr::path().parse(tokens.as_input()).into_output_errors();
78    path
79}
80
81/// Convert the output of the lexer into token pairs and end-of-input span.
82fn prepare_tokens(tokens: Vec<lexer::Token>, source_id: u16) -> ParserTokens {
83    let pairs: Vec<(TokenKind, Span)> = tokens
84        .into_iter()
85        .map(|t| (t.kind, t.span.with_source_id(source_id)))
86        .collect();
87
88    let eoi = Span {
89        start: pairs.last().map(|(_, s)| s.end()).unwrap_or(0),
90        len: 0,
91        source_id,
92    };
93
94    ParserTokens { pairs, eoi }
95}
96
97struct ParserTokens {
98    pairs: Vec<(TokenKind, Span)>,
99    eoi: Span,
100}
101
102impl ParserTokens {
103    fn as_input(&self) -> PInput<'_> {
104        self.pairs.as_slice().split_token_span(self.eoi)
105    }
106}
107
108/// Parser error type. `'src` is the lifetime of the input being parsed; it
109/// bounds token references stored inside the Rich error.
110pub(crate) type PError<'src> = Rich<'src, lexer::TokenKind, Span>;
111
112/// Type of parser Extra we use (full `Rich` errors, for error reporting).
113pub(crate) type PExtra<'src> = chumsky::extra::Err<PError<'src>>;
114
115/// Type of parser input we use.
116///
117/// Uses a slice-based input so that backtracking is O(1) (cursor is just an
118/// index) instead of cloning the entire remaining iterator on every checkpoint.
119type PInput<'src> =
120    chumsky::input::MappedInput<'src, lexer::TokenKind, Span, &'src [(lexer::TokenKind, Span)]>;