Skip to content

Commit

Permalink
almost there
Browse files Browse the repository at this point in the history
  • Loading branch information
max-sixty committed Jul 13, 2024
1 parent da5221c commit f5ed3f4
Show file tree
Hide file tree
Showing 7 changed files with 350 additions and 125 deletions.
51 changes: 36 additions & 15 deletions prqlc/prqlc-parser/src/parser/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,10 @@ pub fn keyword(kw: &'static str) -> impl Parser<TokenKind, (), Error = PError> +
}

pub fn new_line() -> impl Parser<TokenKind, (), Error = PError> + Clone {
just(TokenKind::NewLine).ignored()
just(TokenKind::NewLine)
.or(just(TokenKind::Start))
.ignored()
.labelled("new line")
}

pub fn ctrl(char: char) -> impl Parser<TokenKind, (), Error = PError> + Clone {
Expand All @@ -44,23 +47,20 @@ pub fn into_stmt((annotations, kind): (Vec<Annotation>, StmtKind), span: Span) -
}

pub fn doc_comment() -> impl Parser<TokenKind, String, Error = PError> + Clone {
// doc comments must start on a new line, so we enforce a new line before
// the doc comment (but how to handle the start of a file?)
// doc comments must start on a new line, so we enforce a new line (which
// can also be a file start) before the doc comment
//
// TODO: we currently lose any empty newlines between doc comments;
// eventually we want to retain them
new_line()
.repeated()
.at_least(1)
.ignore_then(select! {
TokenKind::DocComment(dc) => dc,
})
.repeated()
.at_least(1)
.collect()
.debug("doc_comment")
.map(|lines: Vec<String>| lines.join("\n"))
.labelled("doc comment")
(new_line().repeated().at_least(1).ignore_then(select! {
TokenKind::DocComment(dc) => dc,
}))
.repeated()
.at_least(1)
.collect()
.debug("doc_comment")
.map(|lines: Vec<String>| lines.join("\n"))
.labelled("doc comment")
}

pub fn with_doc_comment<'a, P, O>(
Expand Down Expand Up @@ -95,4 +95,25 @@ mod tests {
)
"###);
}

#[test]
fn test_doc_comment_or_not() {
assert_debug_snapshot!(parse_with_parser(r#"hello"#, doc_comment().or_not()).unwrap(), @"None");
assert_debug_snapshot!(parse_with_parser(r#"hello"#, doc_comment().or_not().then_ignore(new_line().repeated()).then(ident_part())).unwrap(), @r###"
(
None,
"hello",
)
"###);
}

#[test]
fn test_no_doc_comment_in_with_doc_comment() {
impl SupportsDocComment for String {
fn with_doc_comment(self, _doc_comment: Option<String>) -> Self {
self
}
}
assert_debug_snapshot!(parse_with_parser(r#"hello"#, with_doc_comment(new_line().ignore_then(ident_part()))).unwrap(), @r###""hello""###);
}
}
179 changes: 132 additions & 47 deletions prqlc/prqlc-parser/src/parser/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,12 +76,16 @@ pub fn expr<'a>() -> impl Parser<TokenKind, Expr, Error = PError> + Clone + 'a {
fn tuple(
nested_expr: impl Parser<TokenKind, Expr, Error = PError> + Clone,
) -> impl Parser<TokenKind, ExprKind, Error = PError> + Clone {
ident_part()
.then_ignore(ctrl('='))
.or_not()
.then(nested_expr)
.map(|(alias, expr)| Expr { alias, ..expr })
.padded_by(new_line().repeated())
new_line()
.repeated()
.ignore_then(
ident_part()
.then_ignore(ctrl('='))
.or_not()
.then(nested_expr)
.map(|(alias, expr)| Expr { alias, ..expr }),
)
// .padded_by(new_line().repeated())
.separated_by(ctrl(','))
.allow_trailing()
.then_ignore(new_line().repeated())
Expand All @@ -103,22 +107,27 @@ fn tuple(
fn array(
expr: impl Parser<TokenKind, Expr, Error = PError> + Clone,
) -> impl Parser<TokenKind, ExprKind, Error = PError> + Clone {
expr.padded_by(new_line().repeated())
.separated_by(ctrl(','))
.allow_trailing()
.then_ignore(new_line().repeated())
.delimited_by(ctrl('['), ctrl(']'))
.recover_with(nested_delimiters(
TokenKind::Control('['),
TokenKind::Control(']'),
[
(TokenKind::Control('{'), TokenKind::Control('}')),
(TokenKind::Control('('), TokenKind::Control(')')),
(TokenKind::Control('['), TokenKind::Control(']')),
],
|_| vec![],
))
.map(ExprKind::Array)
new_line()
.repeated()
.ignore_then(
expr
// .padded_by(new_line().repeated())
.separated_by(ctrl(','))
.allow_trailing()
.then_ignore(new_line().repeated())
.delimited_by(ctrl('['), ctrl(']'))
.recover_with(nested_delimiters(
TokenKind::Control('['),
TokenKind::Control(']'),
[
(TokenKind::Control('{'), TokenKind::Control('}')),
(TokenKind::Control('('), TokenKind::Control(')')),
(TokenKind::Control('['), TokenKind::Control(']')),
],
|_| vec![],
))
.map(ExprKind::Array),
)
.labelled("array")
}

Expand Down Expand Up @@ -162,12 +171,16 @@ fn case(
) -> impl Parser<TokenKind, ExprKind, Error = PError> + Clone {
keyword("case")
.ignore_then(
func_call(expr.clone())
.map(Box::new)
.then_ignore(just(TokenKind::ArrowFat))
.then(func_call(expr).map(Box::new))
.map(|(condition, value)| SwitchCase { condition, value })
.padded_by(new_line().repeated())
new_line()
.repeated()
.ignore_then(
func_call(expr.clone())
.map(Box::new)
.then_ignore(just(TokenKind::ArrowFat))
.then(func_call(expr).map(Box::new))
.map(|(condition, value)| SwitchCase { condition, value }),
)
// .padded_by(new_line().repeated())
.separated_by(ctrl(','))
.allow_trailing()
.then_ignore(new_line().repeated())
Expand Down Expand Up @@ -276,29 +289,29 @@ where
new_line().repeated().at_least(1).ignored(),
));

new_line()
.repeated()
.ignore_then(
with_doc_comment(
new_line().repeated().ignore_then(
ident_part()
.then_ignore(ctrl('='))
.or_not()
.then(expr)
.map(|(alias, expr)| Expr { alias, ..expr })
.separated_by(pipe)
.at_least(1)
.map_with_span(|exprs, span| {
// If there's only one expr, then we don't need to wrap it
// in a pipeline — just return the lone expr. Otherwise,
// wrap them in a pipeline.
exprs.into_iter().exactly_one().unwrap_or_else(|exprs| {
ExprKind::Pipeline(Pipeline {
exprs: exprs.collect(),
})
.into_expr(span)
})
}),
)
.labelled("pipeline")
.map(|(alias, expr)| Expr { alias, ..expr }),
),
)
.separated_by(pipe)
.at_least(1)
.map_with_span(|exprs, span| {
// If there's only one expr, then we don't need to wrap it
// in a pipeline — just return the lone expr. Otherwise,
// wrap them in a pipeline.
exprs.into_iter().exactly_one().unwrap_or_else(|exprs| {
ExprKind::Pipeline(Pipeline {
exprs: exprs.collect(),
})
.into_expr(span)
})
})
.labelled("pipeline")
}

fn binary_op_parser<'a, Term, Op>(
Expand Down Expand Up @@ -545,3 +558,75 @@ fn operator_or() -> impl Parser<TokenKind, BinOp, Error = PError> + Clone {
fn operator_coalesce() -> impl Parser<TokenKind, BinOp, Error = PError> + Clone {
just(TokenKind::Coalesce).to(BinOp::Coalesce)
}

#[cfg(test)]
mod tests {

use insta::assert_yaml_snapshot;

use crate::test::{parse_with_parser, trim_start};

use super::*;

#[test]
fn test_expr() {
assert_yaml_snapshot!(
parse_with_parser(r#"5+5"#, trim_start().ignore_then(expr())).unwrap(),
@r###"
---
Binary:
left:
Literal:
Integer: 5
span: "0:0-1"
op: Add
right:
Literal:
Integer: 5
span: "0:2-3"
span: "0:0-3"
"###);

assert_yaml_snapshot!(
parse_with_parser(r#"derive x = 5"#, trim_start().ignore_then(expr())).unwrap(),
@r###"
---
Ident: derive
span: "0:0-6"
"###);
}

#[test]
fn test_pipeline() {
assert_yaml_snapshot!(
parse_with_parser(r#"
from artists
derive x = 5
"#, trim_start().then(pipeline(expr_call()))).unwrap(),
@r###"
---
- ~
- Pipeline:
exprs:
- FuncCall:
name:
Ident: from
span: "0:13-17"
args:
- Ident: artists
span: "0:18-25"
span: "0:13-25"
- FuncCall:
name:
Ident: derive
span: "0:38-44"
args:
- Literal:
Integer: 5
span: "0:49-50"
alias: x
span: "0:38-50"
span: "0:13-50"
"###);
}
}
1 change: 1 addition & 0 deletions prqlc/prqlc-parser/src/parser/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use chumsky::{prelude::*, Stream};

pub use self::common::new_line;
use crate::error::Error;
use crate::lexer::lr;
use crate::span::Span;
Expand Down
Loading

0 comments on commit f5ed3f4

Please sign in to comment.