use crate::{
ast,
ast::OnTestFailure,
expr::UntypedExpr,
parser::{
annotation,
chain::{Chain, call::parser as call, field_access, tuple_index::parser as tuple_index},
error::ParseError,
expr::{self, bytearray, int as uint, list, pair, string, tuple, var},
pattern,
token::Token,
},
};
use chumsky::prelude::*;
pub fn parser(keyword: Token) -> impl Parser<Token, ast::UntypedDefinition, Error = ParseError> {
just(keyword.clone())
.ignore_then(select! {Token::Name {name} => name})
.then(
via()
.separated_by(just(Token::Comma))
.allow_trailing()
.delimited_by(just(Token::LeftParen), just(Token::RightParen)),
)
.then(
just(Token::Fail)
.ignore_then(just(Token::Once).ignored().or_not().map(|once| {
once.map(|_| OnTestFailure::SucceedImmediately)
.unwrap_or(OnTestFailure::SucceedEventually)
}))
.or_not(),
)
.map_with_span(|name, span| (name, span))
.then(
expr::sequence()
.or_not()
.delimited_by(just(Token::LeftBrace), just(Token::RightBrace)),
)
.map_with_span(
move |((((name, arguments), fail), span_end), body), span| match keyword {
Token::Test => ast::UntypedDefinition::Test(ast::Function {
arguments,
body: body.unwrap_or_else(|| UntypedExpr::todo(None, span)),
doc: None,
location: span_end,
end_position: span.end - 1,
name,
public: false,
return_annotation: None,
return_type: (),
on_test_failure: fail.unwrap_or(OnTestFailure::FailImmediately),
}),
Token::Benchmark => ast::UntypedDefinition::Benchmark(ast::Function {
arguments,
body: body.unwrap_or_else(|| UntypedExpr::todo(None, span)),
doc: None,
location: span_end,
end_position: span.end - 1,
name,
public: false,
return_annotation: None,
return_type: (),
on_test_failure: fail.unwrap_or(OnTestFailure::FailImmediately),
}),
_ => unreachable!("Only Test and Benchmark tokens are supported"),
},
)
}
pub fn via() -> impl Parser<Token, ast::UntypedArgVia, Error = ParseError> {
choice((
select! {Token::DiscardName {name} => name}.map_with_span(|name, span| {
ast::ArgBy::ByName(ast::ArgName::Discarded {
label: name.clone(),
name,
location: span,
})
}),
select! {Token::Name {name} => name}.map_with_span(|name, location| {
ast::ArgBy::ByName(ast::ArgName::Named {
label: name.clone(),
name,
location,
})
}),
pattern().map(ast::ArgBy::ByPattern),
))
.then(just(Token::Colon).ignore_then(annotation()).or_not())
.map_with_span(|(arg_name, annotation), location| (arg_name, annotation, location))
.then_ignore(just(Token::Via))
.then(fuzzer())
.map(|((by, annotation, location), via)| ast::ArgVia {
arg: ast::UntypedArg {
by,
annotation,
location,
doc: None,
is_validator_param: false,
},
via,
})
}
pub fn fuzzer<'a>() -> impl Parser<Token, UntypedExpr, Error = ParseError> + 'a {
recursive(|expression| {
let chain = choice((
tuple_index(),
field_access::parser(),
call(expression.clone()),
));
let int = || {
choice((just(Token::Minus), just(Token::NewLineMinus)))
.to(ast::UnOp::Negate)
.map_with_span(|op, span| (op, span))
.or_not()
.then(uint())
.map(|(op, value)| match op {
None => value,
Some((op, location)) => UntypedExpr::UnOp {
op,
location,
value: Box::new(value),
},
})
};
choice((
int(),
string(),
bytearray(),
pair(expression.clone()),
tuple(expression.clone()),
list(expression.clone()),
var(),
))
.then(chain.repeated())
.foldl(|expr, chain| match chain {
Chain::Call(args, span) => expr.call(args, span),
Chain::FieldAccess(label, span) => expr.field_access(label, span),
Chain::TupleIndex(index, span) => expr.tuple_index(index, span),
})
})
}
#[cfg(test)]
mod tests {
use crate::assert_definition;
#[test]
fn test_pair_in_via() {
assert_definition!(
r#"
test foo(x via some_generator(Pair(14, 42))) {
x == 56
}
"#
);
}
#[test]
fn test_parse_via_negative_int() {
assert_definition!(
r#"
test foo(n via int_between(-10, 10)) {
x * x <= 100
}
"#
);
}
#[test]
fn test_parse_via_negative_int_newline() {
assert_definition!(
r#"
test foo(
n via int_between(
-10,
10,
)
) {
x * x <= 100
}
"#
);
}
}