238 lines
6.2 KiB
Rust
238 lines
6.2 KiB
Rust
use chumsky::prelude::*;
|
|
use vec1::Vec1;
|
|
|
|
mod and_or_chain;
|
|
mod anonymous_binop;
|
|
pub mod anonymous_function;
|
|
pub mod assignment;
|
|
mod block;
|
|
pub(crate) mod bytearray;
|
|
mod chained;
|
|
mod fail_todo_trace;
|
|
mod if_else;
|
|
mod int;
|
|
mod list;
|
|
mod record;
|
|
mod record_update;
|
|
mod sequence;
|
|
pub mod string;
|
|
mod tuple;
|
|
mod var;
|
|
pub mod when;
|
|
|
|
pub use and_or_chain::parser as and_or_chain;
|
|
pub use anonymous_function::parser as anonymous_function;
|
|
pub use block::parser as block;
|
|
pub use bytearray::parser as bytearray;
|
|
pub use chained::parser as chained;
|
|
pub use fail_todo_trace::parser as fail_todo_trace;
|
|
pub use if_else::parser as if_else;
|
|
pub use int::parser as int;
|
|
pub use list::parser as list;
|
|
pub use record::parser as record;
|
|
pub use record_update::parser as record_update;
|
|
pub use sequence::parser as sequence;
|
|
pub use string::parser as string;
|
|
pub use tuple::parser as tuple;
|
|
pub use var::parser as var;
|
|
pub use when::parser as when;
|
|
|
|
use super::{error::ParseError, token::Token};
|
|
use crate::{ast, expr::UntypedExpr};
|
|
|
|
pub fn parser(
|
|
sequence: Recursive<'_, Token, UntypedExpr, ParseError>,
|
|
) -> impl Parser<Token, UntypedExpr, Error = ParseError> + '_ {
|
|
recursive(|expression| {
|
|
choice((
|
|
fail_todo_trace(expression.clone(), sequence.clone()),
|
|
pure_expression(sequence, expression),
|
|
))
|
|
})
|
|
}
|
|
|
|
pub fn pure_expression<'a>(
|
|
sequence: Recursive<'a, Token, UntypedExpr, ParseError>,
|
|
expression: Recursive<'a, Token, UntypedExpr, ParseError>,
|
|
) -> impl Parser<Token, UntypedExpr, Error = ParseError> + 'a {
|
|
// Negate
|
|
let op = choice((
|
|
just(Token::Bang).to(ast::UnOp::Not),
|
|
choice((just(Token::Minus), just(Token::NewLineMinus)))
|
|
// NOTE: Prevent conflict with usage for '-' as a standalone binary op.
|
|
// This will make '-' parse when used as standalone binop in a function call.
|
|
// For example:
|
|
//
|
|
// foo(a, -, b)
|
|
//
|
|
// but it'll fail in a let-binding:
|
|
//
|
|
// let foo = -
|
|
//
|
|
// which seems acceptable.
|
|
.then_ignore(just(Token::Comma).not().rewind())
|
|
.to(ast::UnOp::Negate),
|
|
));
|
|
|
|
let unary = op
|
|
.map_with_span(|op, span| (op, span))
|
|
.repeated()
|
|
.then(chained(sequence, expression))
|
|
.foldr(|(un_op, span), value| UntypedExpr::UnOp {
|
|
op: un_op,
|
|
location: span.union(value.location()),
|
|
value: Box::new(value),
|
|
})
|
|
.boxed();
|
|
|
|
// Product
|
|
let op = choice((
|
|
just(Token::Star).to(ast::BinOp::MultInt),
|
|
just(Token::Slash).to(ast::BinOp::DivInt),
|
|
just(Token::Percent).to(ast::BinOp::ModInt),
|
|
));
|
|
|
|
let product = unary
|
|
.clone()
|
|
.then(op.then(unary).repeated())
|
|
.foldl(|a, (op, b)| UntypedExpr::BinOp {
|
|
location: a.location().union(b.location()),
|
|
name: op,
|
|
left: Box::new(a),
|
|
right: Box::new(b),
|
|
})
|
|
.boxed();
|
|
|
|
// Sum
|
|
let op = choice((
|
|
just(Token::Plus).to(ast::BinOp::AddInt),
|
|
just(Token::Minus).to(ast::BinOp::SubInt),
|
|
));
|
|
|
|
let sum = product
|
|
.clone()
|
|
.then(op.then(product).repeated())
|
|
.foldl(|a, (op, b)| UntypedExpr::BinOp {
|
|
location: a.location().union(b.location()),
|
|
name: op,
|
|
left: Box::new(a),
|
|
right: Box::new(b),
|
|
})
|
|
.boxed();
|
|
|
|
// Comparison
|
|
let op = choice((
|
|
just(Token::EqualEqual).to(ast::BinOp::Eq),
|
|
just(Token::NotEqual).to(ast::BinOp::NotEq),
|
|
just(Token::Less).to(ast::BinOp::LtInt),
|
|
just(Token::Greater).to(ast::BinOp::GtInt),
|
|
just(Token::LessEqual).to(ast::BinOp::LtEqInt),
|
|
just(Token::GreaterEqual).to(ast::BinOp::GtEqInt),
|
|
));
|
|
|
|
let comparison = sum
|
|
.clone()
|
|
.then(op.then(sum).repeated())
|
|
.foldl(|a, (op, b)| UntypedExpr::BinOp {
|
|
location: a.location().union(b.location()),
|
|
name: op,
|
|
left: Box::new(a),
|
|
right: Box::new(b),
|
|
})
|
|
.boxed();
|
|
|
|
// Conjunction
|
|
let op = just(Token::AmperAmper).to(ast::BinOp::And);
|
|
let conjunction = comparison
|
|
.clone()
|
|
.then(op.then(comparison).repeated())
|
|
.foldl(|a, (op, b)| UntypedExpr::BinOp {
|
|
location: a.location().union(b.location()),
|
|
name: op,
|
|
left: Box::new(a),
|
|
right: Box::new(b),
|
|
})
|
|
.boxed();
|
|
|
|
// Disjunction
|
|
let op = just(Token::VbarVbar).to(ast::BinOp::Or);
|
|
let disjunction = conjunction
|
|
.clone()
|
|
.then(op.then(conjunction).repeated())
|
|
.foldl(|a, (op, b)| UntypedExpr::BinOp {
|
|
location: a.location().union(b.location()),
|
|
name: op,
|
|
left: Box::new(a),
|
|
right: Box::new(b),
|
|
})
|
|
.boxed();
|
|
|
|
// Pipeline
|
|
disjunction
|
|
.clone()
|
|
.then(
|
|
choice((just(Token::Pipe), just(Token::NewLinePipe)))
|
|
.then(disjunction)
|
|
.repeated(),
|
|
)
|
|
.foldl(|l, (pipe, r)| {
|
|
if let UntypedExpr::PipeLine {
|
|
mut expressions,
|
|
one_liner,
|
|
} = l
|
|
{
|
|
expressions.push(r);
|
|
return UntypedExpr::PipeLine {
|
|
expressions,
|
|
one_liner,
|
|
};
|
|
}
|
|
|
|
let mut expressions = Vec1::new(l);
|
|
expressions.push(r);
|
|
UntypedExpr::PipeLine {
|
|
expressions,
|
|
one_liner: pipe != Token::NewLinePipe,
|
|
}
|
|
})
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use crate::assert_expr;
|
|
|
|
#[test]
|
|
fn plus_binop() {
|
|
assert_expr!("a + 1");
|
|
}
|
|
|
|
#[test]
|
|
fn pipeline() {
|
|
assert_expr!(
|
|
r#"
|
|
a + 2
|
|
|> add_one
|
|
|> add_one
|
|
"#
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn field_access() {
|
|
assert_expr!("user.name");
|
|
}
|
|
|
|
#[test]
|
|
fn function_invoke() {
|
|
assert_expr!(
|
|
r#"
|
|
let x = add_one(3)
|
|
|
|
let map_add_x = list.map(_, fn (y) { x + y })
|
|
|
|
map_add_x([ 1, 2, 3 ])
|
|
"#
|
|
);
|
|
}
|
|
}
|