prqlc_parser/parser/
mod.rs1use chumsky::{prelude::*, Stream};
2
3use self::perror::PError;
4use self::pr::{Annotation, Stmt, StmtKind};
5use crate::error::Error;
6use crate::lexer::lr;
7use crate::lexer::lr::TokenKind;
8use crate::span::Span;
9
10mod expr;
11mod interpolation;
12pub(crate) mod perror;
13pub mod pr;
14pub(crate) mod stmt;
15#[cfg(test)]
16mod test;
17mod types;
18
19pub fn parse_lr_to_pr(source_id: u16, lr: Vec<lr::Token>) -> (Option<Vec<pr::Stmt>>, Vec<Error>) {
23 let stream = prepare_stream(lr, source_id);
24 let (pr, parse_errors) = stmt::source().parse_recovery(stream);
25
26 let errors = parse_errors.into_iter().map(|e| e.into()).collect();
27 log::debug!("parse errors: {errors:?}");
28
29 (pr, errors)
30}
31
32pub(crate) fn prepare_stream<'a>(
35 tokens: Vec<lr::Token>,
36 source_id: u16,
37) -> Stream<'a, lr::TokenKind, Span, impl Iterator<Item = (lr::TokenKind, Span)> + Sized + 'a> {
38 let final_span = tokens.last().map(|t| t.span.end).unwrap_or(0);
39
40 let semantic_tokens = tokens.into_iter().filter(|token| {
43 !matches!(
44 token.kind,
45 lr::TokenKind::Comment(_) | lr::TokenKind::LineWrap(_)
46 )
47 });
48
49 let tokens = semantic_tokens
50 .into_iter()
51 .map(move |token| (token.kind, Span::new(source_id, token.span)));
52 let eoi = Span {
53 start: final_span,
54 end: final_span,
55 source_id,
56 };
57 Stream::from_iter(eoi, tokens)
58}
59
60fn ident_part() -> impl Parser<TokenKind, String, Error = PError> + Clone {
61 select! {
62 TokenKind::Ident(ident) => ident,
63 TokenKind::Keyword(ident) if &ident == "module" => ident,
64 }
65 .map_err(|e: PError| {
66 PError::expected_input_found(
67 e.span(),
68 [Some(TokenKind::Ident("".to_string()))],
69 e.found().cloned(),
70 )
71 })
72}
73
74fn keyword(kw: &'static str) -> impl Parser<TokenKind, (), Error = PError> + Clone {
75 just(TokenKind::Keyword(kw.to_string())).ignored()
76}
77
78pub(crate) fn new_line() -> impl Parser<TokenKind, (), Error = PError> + Clone {
83 just(TokenKind::NewLine)
84 .or(just(TokenKind::Start))
87 .ignored()
88 .labelled("new line")
89}
90
91fn ctrl(char: char) -> impl Parser<TokenKind, (), Error = PError> + Clone {
92 just(TokenKind::Control(char)).ignored()
93}
94
95fn into_stmt((annotations, kind): (Vec<Annotation>, StmtKind), span: Span) -> Stmt {
96 Stmt {
97 kind,
98 span: Some(span),
99 annotations,
100 doc_comment: None,
101 }
102}
103
104fn doc_comment() -> impl Parser<TokenKind, String, Error = PError> + Clone {
105 (new_line().repeated().at_least(1).ignore_then(select! {
111 TokenKind::DocComment(dc) => dc,
112 }))
113 .repeated()
114 .at_least(1)
115 .collect()
116 .map(|lines: Vec<String>| lines.join("\n"))
117 .labelled("doc comment")
118}
119
120fn with_doc_comment<'a, P, O>(parser: P) -> impl Parser<TokenKind, O, Error = PError> + Clone + 'a
121where
122 P: Parser<TokenKind, O, Error = PError> + Clone + 'a,
123 O: SupportsDocComment + 'a,
124{
125 doc_comment()
126 .or_not()
127 .then(parser)
128 .map(|(doc_comment, inner)| inner.with_doc_comment(doc_comment))
129}
130
131trait SupportsDocComment {
137 fn with_doc_comment(self, doc_comment: Option<String>) -> Self;
138}
139
140fn sequence<'a, P, O>(parser: P) -> impl Parser<TokenKind, Vec<O>, Error = PError> + Clone + 'a
143where
144 P: Parser<TokenKind, O, Error = PError> + Clone + 'a,
145 O: 'a,
146{
147 parser
148 .separated_by(ctrl(',').then_ignore(new_line().repeated()))
149 .allow_trailing()
150 .padded_by(new_line().repeated())
162}
163
164fn pipe() -> impl Parser<TokenKind, (), Error = PError> + Clone {
165 ctrl('|')
166 .ignored()
167 .or(new_line().repeated().at_least(1).ignored())
168}
169
170#[cfg(test)]
171mod tests {
172 use insta::assert_debug_snapshot;
173
174 use super::*;
175 use crate::test::parse_with_parser;
176
177 #[test]
178 fn test_doc_comment() {
179 assert_debug_snapshot!(parse_with_parser(r#"
180 #! doc comment
181 #! another line
182
183 "#, doc_comment()), @r#"
184 Ok(
185 " doc comment\n another line",
186 )
187 "#);
188 }
189
190 #[test]
191 fn test_doc_comment_or_not() {
192 assert_debug_snapshot!(parse_with_parser(r#"hello"#, doc_comment().or_not()).unwrap(), @"None");
193 assert_debug_snapshot!(parse_with_parser(r#"hello"#, doc_comment().or_not().then_ignore(new_line().repeated()).then(ident_part())).unwrap(), @r#"
194 (
195 None,
196 "hello",
197 )
198 "#);
199 }
200
201 #[cfg(test)]
202 impl SupportsDocComment for String {
203 fn with_doc_comment(self, _doc_comment: Option<String>) -> Self {
204 self
205 }
206 }
207
208 #[test]
209 fn test_no_doc_comment_in_with_doc_comment() {
210 assert_debug_snapshot!(parse_with_parser(r#"hello"#, with_doc_comment(new_line().ignore_then(ident_part()))).unwrap(), @r#""hello""#);
211 }
212}