Skip to content

Commit b72c5b2

Browse files
feat: Add DocComments to PR (#4701)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent 080a8bb commit b72c5b2

File tree

38 files changed

+772
-176
lines changed

38 files changed

+772
-176
lines changed

prqlc/prqlc-parser/src/error.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,12 @@ pub enum MessageKind {
4646
pub enum Reason {
4747
Simple(String),
4848
Expected {
49+
/// Where we were
50+
// (could rename to `where` / `location` / `within`?)
4951
who: Option<String>,
52+
/// What we expected
5053
expected: String,
54+
/// What we found
5155
found: String,
5256
},
5357
Unexpected {

prqlc/prqlc-parser/src/lexer/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,9 @@ fn line_wrap() -> impl Parser<char, TokenKind, Error = Cheap<char>> {
184184

185185
fn comment() -> impl Parser<char, TokenKind, Error = Cheap<char>> {
186186
just('#').ignore_then(choice((
187+
// One option would be to check that doc comments have new lines in the
188+
// lexer (we currently do in the parser); which would give better error
189+
// messages?
187190
just('!').ignore_then(
188191
newline()
189192
.not()

prqlc/prqlc-parser/src/parser/common.rs

Lines changed: 89 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,10 @@ use super::pr::{Annotation, Stmt, StmtKind};
55
use crate::lexer::lr::TokenKind;
66
use crate::span::Span;
77

8-
pub fn ident_part() -> impl Parser<TokenKind, String, Error = PError> + Clone {
9-
return select! {
8+
use super::SupportsDocComment;
9+
10+
pub(crate) fn ident_part() -> impl Parser<TokenKind, String, Error = PError> + Clone {
11+
select! {
1012
TokenKind::Ident(ident) => ident,
1113
TokenKind::Keyword(ident) if &ident == "module" => ident,
1214
}
@@ -16,18 +18,27 @@ pub fn ident_part() -> impl Parser<TokenKind, String, Error = PError> + Clone {
1618
[Some(TokenKind::Ident("".to_string()))],
1719
e.found().cloned(),
1820
)
19-
});
21+
})
2022
}
2123

22-
pub fn keyword(kw: &'static str) -> impl Parser<TokenKind, (), Error = PError> + Clone {
24+
pub(crate) fn keyword(kw: &'static str) -> impl Parser<TokenKind, (), Error = PError> + Clone {
2325
just(TokenKind::Keyword(kw.to_string())).ignored()
2426
}
2527

28+
/// Our approach to new lines is each item consumes new lines _before_ itself,
29+
/// but not newlines after itself. This allows us to enforce new lines between
30+
/// some items. The only place we handle new lines after an item is in the root
31+
/// parser.
2632
pub fn new_line() -> impl Parser<TokenKind, (), Error = PError> + Clone {
27-
just(TokenKind::NewLine).ignored()
33+
just(TokenKind::NewLine)
34+
// Start is considered a new line, so we can enforce things start on a new
35+
// line while allowing them to be at the beginning of a file
36+
.or(just(TokenKind::Start))
37+
.ignored()
38+
.labelled("new line")
2839
}
2940

30-
pub fn ctrl(char: char) -> impl Parser<TokenKind, (), Error = PError> + Clone {
41+
pub(crate) fn ctrl(char: char) -> impl Parser<TokenKind, (), Error = PError> + Clone {
3142
just(TokenKind::Control(char)).ignored()
3243
}
3344

@@ -36,5 +47,77 @@ pub fn into_stmt((annotations, kind): (Vec<Annotation>, StmtKind), span: Span) -
3647
kind,
3748
span: Some(span),
3849
annotations,
50+
doc_comment: None,
51+
}
52+
}
53+
54+
pub(crate) fn doc_comment() -> impl Parser<TokenKind, String, Error = PError> + Clone {
55+
// doc comments must start on a new line, so we enforce a new line (which
56+
// can also be a file start) before the doc comment
57+
//
58+
// TODO: we currently lose any empty newlines between doc comments;
59+
// eventually we want to retain them
60+
(new_line().repeated().at_least(1).ignore_then(select! {
61+
TokenKind::DocComment(dc) => dc,
62+
}))
63+
.repeated()
64+
.at_least(1)
65+
.collect()
66+
.map(|lines: Vec<String>| lines.join("\n"))
67+
.labelled("doc comment")
68+
}
69+
70+
pub(crate) fn with_doc_comment<'a, P, O>(
71+
parser: P,
72+
) -> impl Parser<TokenKind, O, Error = PError> + Clone + 'a
73+
where
74+
P: Parser<TokenKind, O, Error = PError> + Clone + 'a,
75+
O: SupportsDocComment + 'a,
76+
{
77+
doc_comment()
78+
.or_not()
79+
.then(parser)
80+
.map(|(doc_comment, inner)| inner.with_doc_comment(doc_comment))
81+
}
82+
83+
#[cfg(test)]
84+
mod tests {
85+
use insta::assert_debug_snapshot;
86+
87+
use super::*;
88+
use crate::test::parse_with_parser;
89+
90+
#[test]
91+
fn test_doc_comment() {
92+
assert_debug_snapshot!(parse_with_parser(r#"
93+
#! doc comment
94+
#! another line
95+
96+
"#, doc_comment()), @r###"
97+
Ok(
98+
" doc comment\n another line",
99+
)
100+
"###);
101+
}
102+
103+
#[test]
104+
fn test_doc_comment_or_not() {
105+
assert_debug_snapshot!(parse_with_parser(r#"hello"#, doc_comment().or_not()).unwrap(), @"None");
106+
assert_debug_snapshot!(parse_with_parser(r#"hello"#, doc_comment().or_not().then_ignore(new_line().repeated()).then(ident_part())).unwrap(), @r###"
107+
(
108+
None,
109+
"hello",
110+
)
111+
"###);
112+
}
113+
114+
#[test]
115+
fn test_no_doc_comment_in_with_doc_comment() {
116+
impl SupportsDocComment for String {
117+
fn with_doc_comment(self, _doc_comment: Option<String>) -> Self {
118+
self
119+
}
120+
}
121+
assert_debug_snapshot!(parse_with_parser(r#"hello"#, with_doc_comment(new_line().ignore_then(ident_part()))).unwrap(), @r###""hello""###);
39122
}
40123
}

0 commit comments

Comments
 (0)