Implement parser for when clause guard

With pretty parse errors on failures. The type-checker was already
  implemented for those, so it now only requires some work in the code
  generation.

  Fixes #297.
This commit is contained in:
KtorZ 2023-01-21 17:36:38 +01:00
parent 91bd0d1d77
commit 5d7585cc05
No known key found for this signature in database
GPG Key ID: 33173CB6F77F4277
6 changed files with 456 additions and 65 deletions

View File

@ -807,6 +807,11 @@ pub type TypedClauseGuard = ClauseGuard<Arc<Type>, String>;
#[derive(Debug, Clone, PartialEq)]
pub enum ClauseGuard<Type, RecordTag> {
Not {
location: Span,
value: Box<Self>,
},
Equals {
location: Span,
left: Box<Self>,
@ -861,12 +866,6 @@ pub enum ClauseGuard<Type, RecordTag> {
name: String,
},
// TupleIndex {
// location: Span,
// index: u64,
// tipo: Type,
// tuple: Box<Self>,
// },
Constant(Constant<Type, RecordTag>),
}
@ -874,10 +873,10 @@ impl<A, B> ClauseGuard<A, B> {
pub fn location(&self) -> Span {
match self {
ClauseGuard::Constant(constant) => constant.location(),
ClauseGuard::Or { location, .. }
ClauseGuard::Not { location, .. }
| ClauseGuard::Or { location, .. }
| ClauseGuard::And { location, .. }
| ClauseGuard::Var { location, .. }
// | ClauseGuard::TupleIndex { location, .. }
| ClauseGuard::Equals { location, .. }
| ClauseGuard::NotEquals { location, .. }
| ClauseGuard::GtInt { location, .. }
@ -890,17 +889,15 @@ impl<A, B> ClauseGuard<A, B> {
pub fn precedence(&self) -> u8 {
// Ensure that this matches the other precedence function for guards
match self {
ClauseGuard::Or { .. } => 1,
ClauseGuard::And { .. } => 2,
ClauseGuard::Equals { .. } | ClauseGuard::NotEquals { .. } => 3,
ClauseGuard::Not { .. } => 1,
ClauseGuard::Or { .. } => 2,
ClauseGuard::And { .. } => 3,
ClauseGuard::Equals { .. } | ClauseGuard::NotEquals { .. } => 4,
ClauseGuard::GtInt { .. }
| ClauseGuard::GtEqInt { .. }
| ClauseGuard::LtInt { .. }
| ClauseGuard::LtEqInt { .. } => 4,
ClauseGuard::Constant(_) | ClauseGuard::Var { .. } => 5,
| ClauseGuard::LtEqInt { .. } => 5,
ClauseGuard::Constant(_) | ClauseGuard::Var { .. } => 6,
}
}
}
@ -909,10 +906,9 @@ impl TypedClauseGuard {
pub fn tipo(&self) -> Arc<Type> {
match self {
ClauseGuard::Var { tipo, .. } => tipo.clone(),
// ClauseGuard::TupleIndex { type_, .. } => type_.clone(),
ClauseGuard::Constant(constant) => constant.tipo(),
ClauseGuard::Or { .. }
ClauseGuard::Not { .. }
| ClauseGuard::Or { .. }
| ClauseGuard::And { .. }
| ClauseGuard::Equals { .. }
| ClauseGuard::NotEquals { .. }

View File

@ -1539,6 +1539,9 @@ impl<'comments> Formatter<'comments> {
fn clause_guard<'a>(&mut self, clause_guard: &'a UntypedClauseGuard) -> Document<'a> {
match clause_guard {
ClauseGuard::Not { value, .. } => {
docvec!["!", self.clause_guard(value)]
}
ClauseGuard::And { left, right, .. } => {
self.clause_guard_bin_op(" && ", clause_guard.precedence(), left, right)
}

View File

@ -989,9 +989,6 @@ pub fn expr_parser(
},
);
// TODO: do guards later
// let when_clause_guard_parser = just(Token::If);
let when_clause_parser = pattern_parser()
.separated_by(just(Token::Comma))
.at_least(1)
@ -1005,20 +1002,29 @@ pub fn expr_parser(
.repeated()
.or_not(),
)
// TODO: do guards later
// .then(when_clause_guard_parser)
.then(choice((
just(Token::If)
.ignore_then(when_clause_guard_parser())
.or_not()
.then_ignore(just(Token::RArrow)),
just(Token::If)
.ignore_then(take_until(just(Token::RArrow)))
.validate(|_value, span, emit| {
emit(ParseError::invalid_when_clause_guard(span));
None
}),
)))
// TODO: add hint "Did you mean to wrap a multi line clause in curly braces?"
.then_ignore(just(Token::RArrow))
.then(r.clone())
.map_with_span(|((patterns, alternative_patterns_opt), then), span| {
ast::UntypedClause {
.map_with_span(
|(((patterns, alternative_patterns_opt), guard), then), span| ast::UntypedClause {
location: span,
pattern: patterns,
alternative_patterns: alternative_patterns_opt.unwrap_or_default(),
guard: None,
guard,
then,
}
});
},
);
let when_parser = just(Token::When)
// TODO: If subject is empty we should return ParseErrorType::ExpectedExpr,
@ -1373,6 +1379,120 @@ pub fn expr_parser(
})
}
pub fn when_clause_guard_parser() -> impl Parser<Token, ast::ClauseGuard<(), ()>, Error = ParseError>
{
recursive(|r| {
let var_parser = select! {
Token::Name { name } => name,
Token::UpName { name } => name,
}
.map_with_span(|name, span| ast::ClauseGuard::Var {
name,
tipo: (),
location: span,
});
let constant_parser = constant_value_parser().map(ast::ClauseGuard::Constant);
let block_parser = r
.clone()
.delimited_by(just(Token::LeftParen), just(Token::RightParen));
let leaf_parser = choice((var_parser, constant_parser, block_parser)).boxed();
let unary_op = just(Token::Bang);
let unary = unary_op
.map_with_span(|op, span| (op, span))
.repeated()
.then(leaf_parser)
.foldr(|(_, span), value| ast::ClauseGuard::Not {
location: span.union(value.location()),
value: Box::new(value),
})
.boxed();
let comparison_op = choice((
just(Token::EqualEqual).to(BinOp::Eq),
just(Token::NotEqual).to(BinOp::NotEq),
just(Token::Less).to(BinOp::LtInt),
just(Token::Greater).to(BinOp::GtInt),
just(Token::LessEqual).to(BinOp::LtEqInt),
just(Token::GreaterEqual).to(BinOp::GtEqInt),
));
let comparison = unary
.clone()
.then(comparison_op.then(unary).repeated())
.foldl(|left, (op, right)| {
let location = left.location().union(right.location());
let left = Box::new(left);
let right = Box::new(right);
match op {
BinOp::Eq => ast::ClauseGuard::Equals {
location,
left,
right,
},
BinOp::NotEq => ast::ClauseGuard::NotEquals {
location,
left,
right,
},
BinOp::LtInt => ast::ClauseGuard::LtInt {
location,
left,
right,
},
BinOp::GtInt => ast::ClauseGuard::GtInt {
location,
left,
right,
},
BinOp::LtEqInt => ast::ClauseGuard::LtEqInt {
location,
left,
right,
},
BinOp::GtEqInt => ast::ClauseGuard::GtEqInt {
location,
left,
right,
},
_ => unreachable!(),
}
})
.boxed();
let logical_op = choice((
just(Token::AmperAmper).to(BinOp::And),
just(Token::VbarVbar).to(BinOp::Or),
));
comparison
.clone()
.then(logical_op.then(comparison).repeated())
.foldl(|left, (op, right)| {
let location = left.location().union(right.location());
let left = Box::new(left);
let right = Box::new(right);
match op {
BinOp::And => ast::ClauseGuard::And {
location,
left,
right,
},
BinOp::Or => ast::ClauseGuard::Or {
location,
left,
right,
},
_ => unreachable!(),
}
})
})
}
pub fn type_parser() -> impl Parser<Token, ast::Annotation, Error = ParseError> {
recursive(|r| {
choice((

View File

@ -36,6 +36,16 @@ impl ParseError {
}
}
pub fn invalid_when_clause_guard(span: Span) -> Self {
Self {
kind: ErrorKind::InvalidWhenClause,
span,
while_parsing: None,
expected: HashSet::new(),
label: Some("invalid clause guard"),
}
}
pub fn malformed_base16_string_literal(span: Span) -> Self {
Self {
kind: ErrorKind::MalformedBase16StringLiteral,
@ -101,7 +111,7 @@ pub enum ErrorKind {
#[help]
hint: Option<String>,
},
#[error("I tripped over a malformed base16-encoded string literal")]
#[error("I tripped over a malformed base16-encoded string literal.")]
#[diagnostic(help("{}", formatdoc! {
r#"You can declare literal bytearrays from base16-encoded (a.k.a. hexadecimal) string literals.
@ -113,6 +123,28 @@ pub enum ErrorKind {
"#
, "pub const".bright_blue(), "=".yellow(), "\"f4c9f9c4252d86702c2f4c2e49e6648c7cffe3c8f2b6b7d779788f50\"".bright_purple()}))]
MalformedBase16StringLiteral,
#[error("I failed to understand a when clause guard.")]
#[diagnostic(url("https://aiken-lang.org/language-tour/control-flow#checking-equality-and-ordering-in-patterns"))]
#[diagnostic(help("{}", formatdoc! {
r#"Clause guards are not as capable as standard expressions. While you can combine multiple clauses using '{operator_or}' and '{operator_and}', you can't do any arithmetic in there. They are mainly meant to compare pattern variables to some known constants using simple binary operators.
For example, the following clauses are well-formed:
{good} (x, _) if x == 10 -> ...
{good} (_, y) if y > 0 && y < 10 -> ...
{good} (x, y) if x && (y > 0 || y < 10) -> ...
However, those aren't:
{bad} (x, _) if x % 3 == 0 -> ...
{bad} (x, y) if x + y > 42 -> ...
"#
, operator_or = "||".yellow()
, operator_and = "&&".yellow()
, good = "✔️".green()
, bad = "✖️".red()
}))]
InvalidWhenClause,
}
#[derive(Debug, PartialEq, Eq, Hash, Diagnostic, thiserror::Error)]

View File

@ -2112,3 +2112,267 @@ fn subtraction_vs_negate() {
})],
);
}
#[test]
fn clause_guards() {
let code = indoc! {r#"
fn foo() {
when a is {
_ if 42 -> Void
_ if bar -> Void
_ if True -> Void
_ if a || b && c -> Void
_ if a || (b && c) -> Void
_ if a <= 42 || b > 14 || "str" -> Void
_ if a == 14 && !b -> Void
_ if !!True -> Void
}
}
"#};
assert_definitions(
code,
vec![ast::Definition::Fn(Function {
arguments: vec![],
body: expr::UntypedExpr::When {
location: Span::new((), 13..250),
subjects: vec![expr::UntypedExpr::Var {
location: Span::new((), 18..19),
name: "a".to_string(),
}],
clauses: vec![
ast::Clause {
location: Span::new((), 29..44),
pattern: vec![ast::Pattern::Discard {
name: "_".to_string(),
location: Span::new((), 29..30),
}],
alternative_patterns: vec![],
guard: Some(ast::ClauseGuard::Constant(ast::Constant::Int {
location: Span::new((), 34..36),
value: "42".to_string(),
})),
then: expr::UntypedExpr::Var {
location: Span::new((), 40..44),
name: "Void".to_string(),
},
},
ast::Clause {
location: Span::new((), 49..65),
pattern: vec![ast::Pattern::Discard {
name: "_".to_string(),
location: Span::new((), 49..50),
}],
alternative_patterns: vec![],
guard: Some(ast::ClauseGuard::Var {
location: Span::new((), 54..57),
name: "bar".to_string(),
tipo: (),
}),
then: expr::UntypedExpr::Var {
location: Span::new((), 61..65),
name: "Void".to_string(),
},
},
ast::Clause {
location: Span::new((), 70..87),
pattern: vec![ast::Pattern::Discard {
name: "_".to_string(),
location: Span::new((), 70..71),
}],
alternative_patterns: vec![],
guard: Some(ast::ClauseGuard::Var {
location: Span::new((), 75..79),
tipo: (),
name: "True".to_string(),
}),
then: expr::UntypedExpr::Var {
location: Span::new((), 83..87),
name: "Void".to_string(),
},
},
ast::Clause {
location: Span::new((), 92..116),
pattern: vec![ast::Pattern::Discard {
name: "_".to_string(),
location: Span::new((), 92..93),
}],
alternative_patterns: vec![],
guard: Some(ast::ClauseGuard::And {
location: Span::new((), 97..108),
left: Box::new(ast::ClauseGuard::Or {
location: Span::new((), 97..103),
left: Box::new(ast::ClauseGuard::Var {
location: Span::new((), 97..98),
name: "a".to_string(),
tipo: (),
}),
right: Box::new(ast::ClauseGuard::Var {
location: Span::new((), 102..103),
name: "b".to_string(),
tipo: (),
}),
}),
right: Box::new(ast::ClauseGuard::Var {
location: Span::new((), 107..108),
name: "c".to_string(),
tipo: (),
}),
}),
then: expr::UntypedExpr::Var {
location: Span::new((), 112..116),
name: "Void".to_string(),
},
},
ast::Clause {
location: Span::new((), 121..147),
pattern: vec![ast::Pattern::Discard {
name: "_".to_string(),
location: Span::new((), 121..122),
}],
alternative_patterns: vec![],
guard: Some(ast::ClauseGuard::Or {
location: Span::new((), 126..138),
left: Box::new(ast::ClauseGuard::Var {
location: Span::new((), 126..127),
name: "a".to_string(),
tipo: (),
}),
right: Box::new(ast::ClauseGuard::And {
location: Span::new((), 132..138),
left: Box::new(ast::ClauseGuard::Var {
location: Span::new((), 132..133),
name: "b".to_string(),
tipo: (),
}),
right: Box::new(ast::ClauseGuard::Var {
location: Span::new((), 137..138),
name: "c".to_string(),
tipo: (),
}),
}),
}),
then: expr::UntypedExpr::Var {
location: Span::new((), 143..147),
name: "Void".to_string(),
},
},
ast::Clause {
location: Span::new((), 152..191),
pattern: vec![ast::Pattern::Discard {
name: "_".to_string(),
location: Span::new((), 152..153),
}],
alternative_patterns: vec![],
guard: Some(ast::ClauseGuard::Or {
location: Span::new((), 157..183),
left: Box::new(ast::ClauseGuard::Or {
location: Span::new((), 157..174),
left: Box::new(ast::ClauseGuard::LtEqInt {
location: Span::new((), 157..164),
left: Box::new(ast::ClauseGuard::Var {
location: Span::new((), 157..158),
name: "a".to_string(),
tipo: (),
}),
right: Box::new(ast::ClauseGuard::Constant(
ast::Constant::Int {
location: Span::new((), 162..164),
value: "42".to_string(),
},
)),
}),
right: Box::new(ast::ClauseGuard::GtInt {
location: Span::new((), 168..174),
left: Box::new(ast::ClauseGuard::Var {
location: Span::new((), 168..169),
name: "b".to_string(),
tipo: (),
}),
right: Box::new(ast::ClauseGuard::Constant(
ast::Constant::Int {
location: Span::new((), 172..174),
value: "14".to_string(),
},
)),
}),
}),
right: Box::new(ast::ClauseGuard::Constant(ast::Constant::String {
location: Span::new((), 178..183),
value: "str".to_string(),
})),
}),
then: expr::UntypedExpr::Var {
location: Span::new((), 187..191),
name: "Void".to_string(),
},
},
ast::Clause {
location: Span::new((), 196..222),
pattern: vec![ast::Pattern::Discard {
name: "_".to_string(),
location: Span::new((), 196..197),
}],
alternative_patterns: vec![],
guard: Some(ast::ClauseGuard::And {
location: Span::new((), 201..214),
left: Box::new(ast::ClauseGuard::Equals {
location: Span::new((), 201..208),
left: Box::new(ast::ClauseGuard::Var {
location: Span::new((), 201..202),
name: "a".to_string(),
tipo: (),
}),
right: Box::new(ast::ClauseGuard::Constant(ast::Constant::Int {
location: Span::new((), 206..208),
value: "14".to_string(),
})),
}),
right: Box::new(ast::ClauseGuard::Not {
location: Span::new((), 212..214),
value: Box::new(ast::ClauseGuard::Var {
location: Span::new((), 213..214),
name: "b".to_string(),
tipo: (),
}),
}),
}),
then: expr::UntypedExpr::Var {
location: Span::new((), 218..222),
name: "Void".to_string(),
},
},
ast::Clause {
location: Span::new((), 227..246),
pattern: vec![ast::Pattern::Discard {
name: "_".to_string(),
location: Span::new((), 227..228),
}],
alternative_patterns: vec![],
guard: Some(ast::ClauseGuard::Not {
location: Span::new((), 232..238),
value: Box::new(ast::ClauseGuard::Not {
location: Span::new((), 233..238),
value: Box::new(ast::ClauseGuard::Var {
location: Span::new((), 234..238),
tipo: (),
name: "True".to_string(),
}),
}),
}),
then: expr::UntypedExpr::Var {
location: Span::new((), 242..246),
name: "Void".to_string(),
},
},
],
},
doc: None,
location: Span::new((), 0..8),
name: "foo".to_string(),
public: false,
return_annotation: None,
return_type: (),
end_position: 251,
})],
);
}

View File

@ -993,41 +993,17 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
})
}
// ClauseGuard::TupleIndex {
// location,
// tuple,
// index,
// ..
// } => {
// let tuple = self.infer_clause_guard(*tuple)?;
// match tuple.type_().as_ref() {
// Type::Tuple { elems } => {
// let type_ = elems
// .get(index as usize)
// .ok_or(Error::OutOfBoundsTupleIndex {
// location,
// index,
// size: elems.len(),
// })?
// .clone();
// Ok(ClauseGuard::TupleIndex {
// location,
// index,
// type_,
// tuple: Box::new(tuple),
// })
// }
ClauseGuard::Not {
location, value, ..
} => {
let value = self.infer_clause_guard(*value)?;
self.unify(bool(), value.tipo(), value.location())?;
Ok(ClauseGuard::Not {
location,
value: Box::new(value),
})
}
// typ if typ.is_unbound() => Err(Error::NotATupleUnbound {
// location: tuple.location(),
// }),
// _ => Err(Error::NotATuple {
// location: tuple.location(),
// given: tuple.type_(),
// }),
// }
// }
ClauseGuard::And {
location,
left,