Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add the pipe operator |> #41

Merged
merged 4 commits into from
Apr 24, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module Test exports (..);

bad_pipe = 5 |> (() -> 10);
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@

× wrong number of arguments
╭─[golden:1:1]
1 │ module Test exports (..);
2 │
3 │ bad_pipe = 5 |> (() -> 10);
· ────┬───
· ╰── this expects no arguments
╰────
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module Test exports (..);

huh = 5 |> 5;
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@

× expression isn't callable
╭─[golden:1:1]
1 │ module Test exports (..);
2 │
3 │ huh = 5 |> 5;
· ┬
· ╰── can't call this
╰────
help: expression has type: Int
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module Test exports (..);

identity_int = (n: Int): Int -> n;

huh = "nope" |> identity_int;
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@

× types don't unify
╭─[golden:2:1]
2 │
3 │ identity_int = (n: Int): Int -> n;
4 │
5 │ huh = "nope" |> identity_int;
· ───┬──
· ╰── here
╰────
help: expected Int
got String
8 changes: 8 additions & 0 deletions crates/ditto-checker/src/module/value_declarations/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -480,6 +480,14 @@ fn toposort_value_declarations(
Expression::Parens(parens) => {
get_connected_nodes_rec(&parens.value, nodes, accum);
}
Expression::BinOp {
box lhs,
operator: _,
box rhs,
} => {
get_connected_nodes_rec(lhs, nodes, accum);
get_connected_nodes_rec(rhs, nodes, accum);
}
// noop
Expression::Constructor(_qualified_proper_name) => {}
Expression::String(_) => {}
Expand Down
33 changes: 33 additions & 0 deletions crates/ditto-checker/src/typechecker/pre_ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,39 @@ fn convert_cst(
body: Box::new(body),
})
}
cst::Expression::BinOp {
box lhs,
// Desugar!
operator: cst::BinOp::RightPizza(_),
box rhs,
} => {
let lhs = convert_cst(env, state, lhs)?;
let rhs = convert_cst(env, state, rhs)?;
match rhs {
Expression::Call {
span,
function,
arguments: original_arguments,
} => {
// Push the lhs as the first argument to the rhs
let mut arguments = vec![Argument::Expression(lhs)];
arguments.extend(original_arguments);
Ok(Expression::Call {
span,
function,
arguments,
})
}
function => {
let arguments = vec![Argument::Expression(lhs)];
Ok(Expression::Call {
span,
function: Box::new(function),
arguments,
})
}
}
}
}
}

Expand Down
1 change: 1 addition & 0 deletions crates/ditto-checker/src/typechecker/tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,6 @@ mod function;
mod int;
pub(self) mod macros;
mod r#match;
mod right_pipe;
mod string;
mod unit;
17 changes: 17 additions & 0 deletions crates/ditto-checker/src/typechecker/tests/right_pipe.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
use super::macros::*;
use crate::TypeError::*;

#[test]
fn it_typechecks_as_expected() {
assert_type!("(f) -> 5 |> f", "((Int) -> $1) -> $1");
assert_type!("(f) -> 5 |> f()", "((Int) -> $1) -> $1");
assert_type!("(f, g) -> 5 |> f |> g", "((Int) -> $2, ($2) -> $3) -> $3");
assert_type!("5 |> ((n) -> n)", "Int");
assert_type!("5 |> ((n) -> n)()", "Int");
}

#[test]
fn it_errors_as_expected() {
assert_type_error!("5 |> 5", NotAFunction { .. });
assert_type_error!("5 |> (() -> 5)", ArgumentLengthMismatch { .. });
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
module Test exports (..);

identity = (a) -> a;

with_parens = 5 |> identity;
without_parens = 5 |> identity();

inline_function = 5 |> ((n) -> n);

tagged_identity = (a, tag) -> a;

-- these two should look the same in the generated code!
want = tagged_identity(tagged_identity(tagged_identity(5, "1"), "2"), "3");
got_ =
5
|> tagged_identity("1")
|> tagged_identity("2")
|> tagged_identity("3");
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
function tagged_identity(a, tag) {
return a;
}
const got_ = tagged_identity(
tagged_identity(tagged_identity(5, "1"), "2"),
"3",
);
const want = tagged_identity(
tagged_identity(tagged_identity(5, "1"), "2"),
"3",
);
const inline_function = (n => n)(5);
function identity(a) {
return a;
}
const without_parens = identity(5);
const with_parens = identity(5);
export {
got_,
identity,
inline_function,
tagged_identity,
want,
with_parens,
without_parens,
};
20 changes: 18 additions & 2 deletions crates/ditto-cst/src/expression.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use crate::{
BracketsList, CloseBrace, Colon, DoKeyword, ElseKeyword, FalseKeyword, IfKeyword, LeftArrow,
MatchKeyword, Name, OpenBrace, Parens, ParensList, ParensList1, Pipe, QualifiedName,
QualifiedProperName, ReturnKeyword, RightArrow, Semicolon, StringToken, ThenKeyword,
TrueKeyword, Type, UnitKeyword, WithKeyword,
QualifiedProperName, ReturnKeyword, RightArrow, RightPizzaOperator, Semicolon, StringToken,
ThenKeyword, TrueKeyword, Type, UnitKeyword, WithKeyword,
};

/// A value expression.
Expand Down Expand Up @@ -125,6 +125,22 @@ pub enum Expression {
Float(StringToken),
/// `[this, is, an, array]`
Array(BracketsList<Box<Self>>),
/// Binary operator expression.s
BinOp {
/// The left-hand side of the operator.
lhs: Box<Self>,
/// The binary operator.
operator: BinOp,
/// The right-hand side of the operator.
rhs: Box<Self>,
},
}

/// A binary operator.
#[derive(Debug, Clone)]
pub enum BinOp {
/// `|>`
RightPizza(RightPizzaOperator),
}

/// A chain of Effect statements.
Expand Down
1 change: 1 addition & 0 deletions crates/ditto-cst/src/get_span.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ impl Expression {
Self::True(true_keyword) => true_keyword.0.get_span(),
Self::False(false_keyword) => false_keyword.0.get_span(),
Self::Unit(unit_keyword) => unit_keyword.0.get_span(),
Self::BinOp { lhs, rhs, .. } => lhs.get_span().merge(&rhs.get_span()),
}
}
}
Expand Down
74 changes: 69 additions & 5 deletions crates/ditto-cst/src/parser/expression.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
use super::{parse_rule, Result, Rule};
use crate::{
BracketsList, CloseBrace, Colon, DoKeyword, Effect, ElseKeyword, Expression, FalseKeyword,
IfKeyword, LeftArrow, MatchArm, MatchKeyword, Name, OpenBrace, Parens, ParensList, ParensList1,
Pattern, Pipe, QualifiedName, QualifiedProperName, ReturnKeyword, RightArrow, Semicolon,
StringToken, ThenKeyword, TrueKeyword, Type, TypeAnnotation, UnitKeyword, WithKeyword,
BinOp, BracketsList, CloseBrace, Colon, DoKeyword, Effect, ElseKeyword, Expression,
FalseKeyword, IfKeyword, LeftArrow, MatchArm, MatchKeyword, Name, OpenBrace, Parens,
ParensList, ParensList1, Pattern, Pipe, QualifiedName, QualifiedProperName, ReturnKeyword,
RightArrow, RightPizzaOperator, Semicolon, StringToken, ThenKeyword, TrueKeyword, Type,
TypeAnnotation, UnitKeyword, WithKeyword,
};
use pest::iterators::Pair;

Expand Down Expand Up @@ -151,6 +152,24 @@ impl Expression {
close_brace,
}
}
Rule::expression_right_pipe => {
let mut inner = pair.into_inner();
let lhs = Box::new(Expression::from_pair(inner.next().unwrap()));
let operator =
BinOp::RightPizza(RightPizzaOperator::from_pair(inner.next().unwrap()));
let rhs = Box::new(Expression::from_pair(inner.next().unwrap()));
let mut expression = Self::BinOp { lhs, operator, rhs };
while let Some(pair) = inner.next() {
let operator = BinOp::RightPizza(RightPizzaOperator::from_pair(pair));
let rhs = Box::new(Expression::from_pair(inner.next().unwrap()));
expression = Self::BinOp {
lhs: Box::new(expression),
operator,
rhs,
}
}
expression
}
other => unreachable!("{:#?} {:#?}", other, pair.into_inner()),
}
}
Expand Down Expand Up @@ -263,7 +282,7 @@ impl TypeAnnotation {
#[cfg(test)]
mod tests {
use super::test_macros::*;
use crate::{Brackets, CommaSep1, Expression, Parens, StringToken};
use crate::{BinOp, Brackets, CommaSep1, Expression, Parens, StringToken};

#[test]
fn it_parses_constructors() {
Expand Down Expand Up @@ -627,6 +646,51 @@ mod tests {
}
);
}

#[test]
fn it_parses_pipes() {
assert_parses!(
"x |> y",
Expression::BinOp {
operator: BinOp::RightPizza(_),
..
}
);

assert_parses!(
"x() |> y()",
Expression::BinOp {
operator: BinOp::RightPizza(_),
..
}
);
// Left associative
assert_parses!(
"x |> y |> z",
Expression::BinOp {
operator: BinOp::RightPizza(_),
lhs: box Expression::BinOp {
operator: BinOp::RightPizza(_),
..
},
..
}
);
assert_parses!(
"x |> (y |> z)",
Expression::BinOp {
operator: BinOp::RightPizza(_),
rhs: box Expression::Parens(Parens {
value: box Expression::BinOp {
operator: BinOp::RightPizza(_),
..
},
..
}),
..
}
);
}
}

#[cfg(test)]
Expand Down
28 changes: 22 additions & 6 deletions crates/ditto-cst/src/parser/grammar.pest
Original file line number Diff line number Diff line change
Expand Up @@ -129,20 +129,29 @@ return_type_annotation = { colon ~ type1 } // used for function expressions only
// Expressions

expression = _
{ expression_call
| expression_function
{ expression_function
| expression_match
| expression_effect
| expression1
| expression_if
| expression_2
}

expression_2 = _
{ expression_right_pipe
| expression_1
}

expression1 = _
expression_1 = _
{ expression_call
| expression_0
}

expression_0 = _
{ expression_parens
| expression_constructor
| expression_true
| expression_false
| expression_unit
| expression_if
// It's important that keyword expressions come before variable
| expression_variable
| expression_array
Expand All @@ -151,11 +160,14 @@ expression1 = _
| expression_integer
}

// Left-associative (so needs some left recursion hacking)
expression_right_pipe = { expression_1 ~ right_pizza ~ expression_1 ~ (right_pizza ~ expression_1)* }

expression_parens = { open_paren ~ expression ~ close_paren }

// No left recursion yet :(
// https://github.com/pest-parser/pest/pull/533
expression_call = { expression1 ~ expression_call_arguments+ }
expression_call = { expression_0 ~ expression_call_arguments+ }

expression_call_arguments = { open_paren ~ (expression ~ (comma ~ expression)* ~ comma?)? ~ close_paren }

Expand Down Expand Up @@ -278,6 +290,8 @@ dot = ${ (WHITESPACE | LINE_COMMENT)* ~ DOT ~ HORIZONTAL_WHITESPACE? ~ LINE_COMM

pipe = ${ (WHITESPACE | LINE_COMMENT)* ~ PIPE ~ HORIZONTAL_WHITESPACE? ~ LINE_COMMENT? }

right_pizza = ${ (WHITESPACE | LINE_COMMENT)* ~ RIGHT_PIZZA ~ HORIZONTAL_WHITESPACE? ~ LINE_COMMENT? }

double_dot = ${ (WHITESPACE | LINE_COMMENT)* ~ DOUBLE_DOT ~ HORIZONTAL_WHITESPACE? ~ LINE_COMMENT? }

comma = ${ (WHITESPACE | LINE_COMMENT)* ~ COMMA ~ HORIZONTAL_WHITESPACE? ~ LINE_COMMENT? }
Expand Down Expand Up @@ -357,6 +371,8 @@ DOT = { "." }

PIPE = { "|" }

RIGHT_PIZZA = { "|>" }

DOUBLE_DOT = { ".." }

COMMA = { "," }
Expand Down
Loading