367 lines
12 KiB
Rust
367 lines
12 KiB
Rust
use chumsky::prelude::*;
|
|
use vec1::Vec1;
|
|
|
|
mod anonymous_binop;
|
|
pub mod anonymous_function;
|
|
pub mod assignment;
|
|
mod block;
|
|
mod bytearray;
|
|
mod if_else;
|
|
mod int;
|
|
mod list;
|
|
mod record;
|
|
mod record_update;
|
|
mod sequence;
|
|
pub mod string;
|
|
mod tuple;
|
|
mod var;
|
|
pub mod when;
|
|
|
|
use anonymous_binop::parser as anonymous_binop;
|
|
pub use anonymous_function::parser as anonymous_function;
|
|
pub use block::parser as block;
|
|
pub use bytearray::parser as bytearray;
|
|
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 crate::{
|
|
ast::{self, Span},
|
|
expr::{FnStyle, UntypedExpr},
|
|
};
|
|
|
|
use super::{error::ParseError, token::Token};
|
|
|
|
pub fn parser(
|
|
seq_r: Recursive<'_, Token, UntypedExpr, ParseError>,
|
|
) -> impl Parser<Token, UntypedExpr, Error = ParseError> + '_ {
|
|
recursive(|r| {
|
|
let field_access_constructor = select! {Token::Name { name } => name}
|
|
.map_with_span(|module, span| (module, span))
|
|
.then_ignore(just(Token::Dot))
|
|
.then(select! {Token::UpName { name } => name})
|
|
.map_with_span(|((module, m_span), name), span| UntypedExpr::FieldAccess {
|
|
location: span,
|
|
label: name,
|
|
container: Box::new(UntypedExpr::Var {
|
|
location: m_span,
|
|
name: module,
|
|
}),
|
|
});
|
|
|
|
let expr_unit_parser = choice((
|
|
string(),
|
|
int(),
|
|
record_update(r.clone()),
|
|
record(r.clone()),
|
|
field_access_constructor,
|
|
var(),
|
|
tuple(r.clone()),
|
|
bytearray(),
|
|
list(r.clone()),
|
|
anonymous_function(seq_r.clone()),
|
|
anonymous_binop(),
|
|
block(seq_r.clone()),
|
|
when(r.clone()),
|
|
assignment::let_(r.clone()),
|
|
assignment::expect(r.clone()),
|
|
if_else(seq_r, r.clone()),
|
|
));
|
|
|
|
// Parsing a function call into the appropriate structure
|
|
#[derive(Debug)]
|
|
enum ParserArg {
|
|
Arg(Box<ast::CallArg<UntypedExpr>>),
|
|
Hole {
|
|
location: Span,
|
|
label: Option<String>,
|
|
},
|
|
}
|
|
|
|
enum Chain {
|
|
Call(Vec<ParserArg>, Span),
|
|
FieldAccess(String, Span),
|
|
TupleIndex(usize, Span),
|
|
}
|
|
|
|
let field_access_parser = just(Token::Dot)
|
|
.ignore_then(select! {
|
|
Token::Name { name } => name,
|
|
})
|
|
.map_with_span(Chain::FieldAccess);
|
|
|
|
let tuple_index_parser = just(Token::Dot)
|
|
.ignore_then(select! {
|
|
Token::Ordinal { index } => index,
|
|
})
|
|
.validate(|index, span, emit| {
|
|
if index < 1 {
|
|
emit(ParseError::invalid_tuple_index(
|
|
span,
|
|
index.to_string(),
|
|
None,
|
|
));
|
|
Chain::TupleIndex(0, span)
|
|
} else {
|
|
Chain::TupleIndex(index as usize - 1, span)
|
|
}
|
|
});
|
|
|
|
let call_parser = choice((
|
|
select! { Token::Name { name } => name }
|
|
.then_ignore(just(Token::Colon))
|
|
.or_not()
|
|
.then(r)
|
|
.map_with_span(|(label, value), span| {
|
|
ParserArg::Arg(Box::new(ast::CallArg {
|
|
label,
|
|
location: span,
|
|
value,
|
|
}))
|
|
}),
|
|
select! { Token::Name { name } => name }
|
|
.then_ignore(just(Token::Colon))
|
|
.or_not()
|
|
.then_ignore(select! {Token::DiscardName {name} => name })
|
|
.map_with_span(|label, span| ParserArg::Hole {
|
|
location: span,
|
|
label,
|
|
}),
|
|
))
|
|
.separated_by(just(Token::Comma))
|
|
.allow_trailing()
|
|
.delimited_by(just(Token::LeftParen), just(Token::RightParen))
|
|
.map_with_span(Chain::Call);
|
|
|
|
let chain = choice((tuple_index_parser, field_access_parser, call_parser));
|
|
|
|
let chained = expr_unit_parser
|
|
.then(chain.repeated())
|
|
.foldl(|expr, chain| match chain {
|
|
Chain::Call(args, span) => {
|
|
let mut holes = Vec::new();
|
|
|
|
let args = args
|
|
.into_iter()
|
|
.enumerate()
|
|
.map(|(index, a)| match a {
|
|
ParserArg::Arg(arg) => *arg,
|
|
ParserArg::Hole { location, label } => {
|
|
let name = format!("{}__{index}", ast::CAPTURE_VARIABLE);
|
|
|
|
holes.push(ast::Arg {
|
|
location: Span::empty(),
|
|
annotation: None,
|
|
arg_name: ast::ArgName::Named {
|
|
label: name.clone(),
|
|
name,
|
|
location: Span::empty(),
|
|
is_validator_param: false,
|
|
},
|
|
tipo: (),
|
|
});
|
|
|
|
ast::CallArg {
|
|
label,
|
|
location,
|
|
value: UntypedExpr::Var {
|
|
location,
|
|
name: format!("{}__{index}", ast::CAPTURE_VARIABLE),
|
|
},
|
|
}
|
|
}
|
|
})
|
|
.collect();
|
|
|
|
let call = UntypedExpr::Call {
|
|
location: expr.location().union(span),
|
|
fun: Box::new(expr),
|
|
arguments: args,
|
|
};
|
|
|
|
if holes.is_empty() {
|
|
call
|
|
} else {
|
|
UntypedExpr::Fn {
|
|
location: call.location(),
|
|
fn_style: FnStyle::Capture,
|
|
arguments: holes,
|
|
body: Box::new(call),
|
|
return_annotation: None,
|
|
}
|
|
}
|
|
}
|
|
|
|
Chain::FieldAccess(label, span) => UntypedExpr::FieldAccess {
|
|
location: expr.location().union(span),
|
|
label,
|
|
container: Box::new(expr),
|
|
},
|
|
|
|
Chain::TupleIndex(index, span) => UntypedExpr::TupleIndex {
|
|
location: expr.location().union(span),
|
|
index,
|
|
tuple: Box::new(expr),
|
|
},
|
|
});
|
|
|
|
let debug = chained.then(just(Token::Question).or_not()).map_with_span(
|
|
|(value, token), location| match token {
|
|
Some(_) => UntypedExpr::TraceIfFalse {
|
|
value: Box::new(value),
|
|
location,
|
|
},
|
|
None => value,
|
|
},
|
|
);
|
|
|
|
// Negate
|
|
let op = choice((
|
|
just(Token::Bang).to(ast::UnOp::Not),
|
|
just(Token::Minus)
|
|
// 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(debug)
|
|
.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,
|
|
}
|
|
})
|
|
})
|
|
}
|