feat: expect boolean sugar

This commit is contained in:
rvcas 2023-07-15 20:50:02 -04:00
parent d009358266
commit 1b8e94fe32
No known key found for this signature in database
GPG Key ID: C09B64E263F7D68C
9 changed files with 198 additions and 31 deletions

View File

@ -2,6 +2,10 @@
## v1.0.13-alpha - unreleased ## v1.0.13-alpha - unreleased
### Added
- **aiken-lang**: `expect foo == bar` desugars into `expect True = foo == bar`
### Fixed ### Fixed
- **aiken-lang**: fail, todo, and trace had issues with sequences and expressions - **aiken-lang**: fail, todo, and trace had issues with sequences and expressions

View File

@ -643,10 +643,17 @@ impl<'comments> Formatter<'comments> {
self.pop_empty_lines(pattern.location().end); self.pop_empty_lines(pattern.location().end);
let keyword = match kind { let keyword = match kind {
AssignmentKind::Let => "let ", AssignmentKind::Let => "let",
AssignmentKind::Expect => "expect ", AssignmentKind::Expect => "expect",
}; };
match pattern {
UntypedPattern::Constructor {
name, module: None, ..
} if name == "True" && annotation.is_none() && kind.is_expect() => {
keyword.to_doc().append(self.case_clause_value(value))
}
_ => {
let pattern = self.pattern(pattern); let pattern = self.pattern(pattern);
let annotation = annotation let annotation = annotation
@ -655,10 +662,13 @@ impl<'comments> Formatter<'comments> {
keyword keyword
.to_doc() .to_doc()
.append(" ")
.append(pattern.append(annotation).group()) .append(pattern.append(annotation).group())
.append(" =") .append(" =")
.append(self.case_clause_value(value)) .append(self.case_clause_value(value))
} }
}
}
pub fn bytearray<'a>( pub fn bytearray<'a>(
&mut self, &mut self,

View File

@ -9,25 +9,7 @@ use crate::{
pub fn let_( pub fn let_(
r: Recursive<'_, Token, UntypedExpr, ParseError>, r: Recursive<'_, Token, UntypedExpr, ParseError>,
) -> impl Parser<Token, UntypedExpr, Error = ParseError> + '_ { ) -> impl Parser<Token, UntypedExpr, Error = ParseError> + '_ {
assignment(r, ast::AssignmentKind::Let) just(Token::Let)
}
pub fn expect(
r: Recursive<'_, Token, UntypedExpr, ParseError>,
) -> impl Parser<Token, UntypedExpr, Error = ParseError> + '_ {
assignment(r, ast::AssignmentKind::Expect)
}
fn assignment(
r: Recursive<'_, Token, UntypedExpr, ParseError>,
kind: ast::AssignmentKind,
) -> impl Parser<Token, UntypedExpr, Error = ParseError> + '_ {
let keyword = match kind {
ast::AssignmentKind::Let => Token::Let,
ast::AssignmentKind::Expect => Token::Expect,
};
just(keyword)
.ignore_then(pattern()) .ignore_then(pattern())
.then(just(Token::Colon).ignore_then(annotation()).or_not()) .then(just(Token::Colon).ignore_then(annotation()).or_not())
.then_ignore(just(Token::Equal)) .then_ignore(just(Token::Equal))
@ -37,12 +19,50 @@ fn assignment(
location: span, location: span,
value: Box::new(value), value: Box::new(value),
pattern, pattern,
kind, kind: ast::AssignmentKind::Let,
annotation, annotation,
}, },
) )
} }
pub fn expect(
r: Recursive<'_, Token, UntypedExpr, ParseError>,
) -> impl Parser<Token, UntypedExpr, Error = ParseError> + '_ {
just(Token::Expect)
.ignore_then(
pattern()
.then(just(Token::Colon).ignore_then(annotation()).or_not())
.then_ignore(just(Token::Equal))
.or_not(),
)
.then(r.clone())
.map_with_span(move |(opt_pattern, value), span| {
let (pattern, annotation) = opt_pattern.unwrap_or_else(|| {
(
ast::UntypedPattern::Constructor {
is_record: false,
location: span,
name: "True".to_string(),
arguments: vec![],
module: None,
constructor: (),
with_spread: false,
tipo: (),
},
None,
)
});
UntypedExpr::Assignment {
location: span,
value: Box::new(value),
pattern,
kind: ast::AssignmentKind::Expect,
annotation,
}
})
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::assert_expr; use crate::assert_expr;
@ -56,4 +76,9 @@ mod tests {
fn expect() { fn expect() {
assert_expr!("expect Some(x) = something.field"); assert_expr!("expect Some(x) = something.field");
} }
#[test]
fn expect_bool_sugar() {
assert_expr!("expect something.field == wow");
}
} }

View File

@ -0,0 +1,35 @@
---
source: crates/aiken-lang/src/parser/expr/assignment.rs
description: "Code:\n\nexpect something.field == wow"
---
Assignment {
location: 0..29,
value: BinOp {
location: 7..29,
name: Eq,
left: FieldAccess {
location: 7..22,
label: "field",
container: Var {
location: 7..16,
name: "something",
},
},
right: Var {
location: 26..29,
name: "wow",
},
},
pattern: Constructor {
is_record: false,
location: 0..29,
name: "True",
arguments: [],
module: None,
constructor: (),
with_spread: false,
tipo: (),
},
kind: Expect,
annotation: None,
}

View File

@ -83,3 +83,18 @@ pub(crate) fn args(
.unwrap_or_else(|| (vec![], false, false)) .unwrap_or_else(|| (vec![], false, false))
}) })
} }
#[cfg(test)]
mod tests {
use crate::assert_pattern;
#[test]
fn constructor_basic() {
assert_pattern!("True");
}
#[test]
fn constructor_module_select() {
assert_pattern!("module.Foo");
}
}

View File

@ -0,0 +1,14 @@
---
source: crates/aiken-lang/src/parser/pattern/constructor.rs
description: "Code:\n\nTrue"
---
Constructor {
is_record: false,
location: 0..4,
name: "True",
arguments: [],
module: None,
constructor: (),
with_spread: false,
tipo: (),
}

View File

@ -0,0 +1,16 @@
---
source: crates/aiken-lang/src/parser/pattern/constructor.rs
description: "Code:\n\nmodule.Foo"
---
Constructor {
is_record: false,
location: 0..10,
name: "Foo",
arguments: [],
module: Some(
"module",
),
constructor: (),
with_spread: false,
tipo: (),
}

View File

@ -61,6 +61,27 @@ macro_rules! assert_annotation {
}; };
} }
#[macro_export]
macro_rules! assert_pattern {
($code:expr) => {
use chumsky::Parser;
let $crate::parser::lexer::LexInfo { tokens, .. } = $crate::parser::lexer::run(indoc::indoc! { $code }).unwrap();
let stream = chumsky::Stream::from_iter($crate::ast::Span::create(tokens.len(), 1), tokens.into_iter());
let result = $crate::parser::pattern().parse(stream).unwrap();
insta::with_settings!({
description => concat!("Code:\n\n", indoc::indoc! { $code }),
prepend_module_to_snapshot => false,
omit_expression => true
}, {
insta::assert_debug_snapshot!(result);
});
};
}
#[macro_export] #[macro_export]
macro_rules! assert_module { macro_rules! assert_module {
($code:expr) => { ($code:expr) => {

View File

@ -154,6 +154,33 @@ fn multi_validator_warning() {
)) ))
} }
#[test]
fn expect_sugar_correct_type() {
let source_code = r#"
fn foo() {
expect 1 == 1
2
}
"#;
assert!(matches!(check(parse(source_code)), Ok(_)))
}
#[test]
fn expect_sugar_incorrect_type() {
let source_code = r#"
fn foo() {
expect 1
2
}
"#;
assert!(matches!(
dbg!(check(parse(source_code))),
Err((_, Error::CouldNotUnify { .. }))
))
}
#[test] #[test]
fn anonymous_function_scoping() { fn anonymous_function_scoping() {
let source_code = r#" let source_code = r#"