Merge pull request #619 from aiken-lang/first-class-binary-operators
First class binary operators
This commit is contained in:
commit
42519d3965
11
CHANGELOG.md
11
CHANGELOG.md
|
@ -1,5 +1,16 @@
|
|||
# Changelog
|
||||
|
||||
## v1.0.11-alpha - unreleased
|
||||
|
||||
### Added
|
||||
|
||||
- **aiken-lang**: Binary operator are now treated like first-class citizen in
|
||||
expressions. In particular, they can be used as function arguments directly:
|
||||
|
||||
```
|
||||
compare_with(a, >=, b) == compare_with(a, fn(l, r) { l >= r }, b)
|
||||
```
|
||||
|
||||
## v1.0.10-alpha - 2023-06-13
|
||||
|
||||
### Added
|
||||
|
|
|
@ -647,6 +647,24 @@ impl Annotation {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn boolean(location: Span) -> Self {
|
||||
Annotation::Constructor {
|
||||
name: "Bool".to_string(),
|
||||
module: None,
|
||||
arguments: vec![],
|
||||
location,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn int(location: Span) -> Self {
|
||||
Annotation::Constructor {
|
||||
name: "Int".to_string(),
|
||||
module: None,
|
||||
arguments: vec![],
|
||||
location,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_logically_equal(&self, other: &Annotation) -> bool {
|
||||
match self {
|
||||
Annotation::Constructor {
|
||||
|
|
|
@ -399,6 +399,14 @@ impl TypedExpr {
|
|||
}
|
||||
}
|
||||
|
||||
// Represent how a function was written so that we can format it back.
|
||||
#[derive(Debug, Clone, PartialEq, Copy)]
|
||||
pub enum FnStyle {
|
||||
Plain,
|
||||
Capture,
|
||||
BinOp(BinOp),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum UntypedExpr {
|
||||
Int {
|
||||
|
@ -424,7 +432,7 @@ pub enum UntypedExpr {
|
|||
|
||||
Fn {
|
||||
location: Span,
|
||||
is_capture: bool,
|
||||
fn_style: FnStyle,
|
||||
arguments: Vec<Arg<()>>,
|
||||
body: Box<Self>,
|
||||
return_annotation: Option<Annotation>,
|
||||
|
|
|
@ -8,7 +8,7 @@ use crate::{
|
|||
Use, Validator, CAPTURE_VARIABLE,
|
||||
},
|
||||
docvec,
|
||||
expr::{UntypedExpr, DEFAULT_ERROR_STR, DEFAULT_TODO_STR},
|
||||
expr::{FnStyle, UntypedExpr, DEFAULT_ERROR_STR, DEFAULT_TODO_STR},
|
||||
parser::{
|
||||
extra::{Comment, ModuleExtra},
|
||||
token::Base,
|
||||
|
@ -768,12 +768,18 @@ impl<'comments> Formatter<'comments> {
|
|||
UntypedExpr::UnOp { value, op, .. } => self.un_op(value, op),
|
||||
|
||||
UntypedExpr::Fn {
|
||||
is_capture: true,
|
||||
fn_style: FnStyle::Capture,
|
||||
body,
|
||||
..
|
||||
} => self.fn_capture(body),
|
||||
|
||||
UntypedExpr::Fn {
|
||||
fn_style: FnStyle::BinOp(op),
|
||||
..
|
||||
} => op.to_doc(),
|
||||
|
||||
UntypedExpr::Fn {
|
||||
fn_style: FnStyle::Plain,
|
||||
return_annotation,
|
||||
arguments: args,
|
||||
body,
|
||||
|
@ -1061,7 +1067,9 @@ impl<'comments> Formatter<'comments> {
|
|||
let right = self.expr(right);
|
||||
|
||||
self.operator_side(left, precedence, left_precedence)
|
||||
.append(" ")
|
||||
.append(name)
|
||||
.append(" ")
|
||||
.append(self.operator_side(right, precedence, right_precedence - 1))
|
||||
}
|
||||
|
||||
|
@ -1093,7 +1101,7 @@ impl<'comments> Formatter<'comments> {
|
|||
let comments = self.pop_comments(expr.location().start);
|
||||
let doc = match expr {
|
||||
UntypedExpr::Fn {
|
||||
is_capture: true,
|
||||
fn_style: FnStyle::Capture,
|
||||
body,
|
||||
..
|
||||
} => self.pipe_capture_right_hand_side(body),
|
||||
|
@ -1717,19 +1725,19 @@ impl<'a> Documentable<'a> for &'a UnqualifiedImport {
|
|||
impl<'a> Documentable<'a> for &'a BinOp {
|
||||
fn to_doc(self) -> Document<'a> {
|
||||
match self {
|
||||
BinOp::And => " && ",
|
||||
BinOp::Or => " || ",
|
||||
BinOp::LtInt => " < ",
|
||||
BinOp::LtEqInt => " <= ",
|
||||
BinOp::Eq => " == ",
|
||||
BinOp::NotEq => " != ",
|
||||
BinOp::GtEqInt => " >= ",
|
||||
BinOp::GtInt => " > ",
|
||||
BinOp::AddInt => " + ",
|
||||
BinOp::SubInt => " - ",
|
||||
BinOp::MultInt => " * ",
|
||||
BinOp::DivInt => " / ",
|
||||
BinOp::ModInt => " % ",
|
||||
BinOp::And => "&&",
|
||||
BinOp::Or => "||",
|
||||
BinOp::LtInt => "<",
|
||||
BinOp::LtEqInt => "<=",
|
||||
BinOp::Eq => "==",
|
||||
BinOp::NotEq => "!=",
|
||||
BinOp::GtEqInt => ">=",
|
||||
BinOp::GtInt => ">",
|
||||
BinOp::AddInt => "+",
|
||||
BinOp::SubInt => "-",
|
||||
BinOp::MultInt => "*",
|
||||
BinOp::DivInt => "/",
|
||||
BinOp::ModInt => "%",
|
||||
}
|
||||
.to_doc()
|
||||
}
|
||||
|
|
|
@ -935,11 +935,91 @@ pub fn expr_parser(
|
|||
arguments,
|
||||
body: Box::new(body),
|
||||
location: span,
|
||||
is_capture: false,
|
||||
fn_style: expr::FnStyle::Plain,
|
||||
return_annotation,
|
||||
},
|
||||
);
|
||||
|
||||
let anon_binop_parser = select! {
|
||||
Token::EqualEqual => BinOp::Eq,
|
||||
Token::NotEqual => BinOp::NotEq,
|
||||
Token::Less => BinOp::LtInt,
|
||||
Token::LessEqual => BinOp::LtEqInt,
|
||||
Token::Greater => BinOp::GtInt,
|
||||
Token::GreaterEqual => BinOp::GtEqInt,
|
||||
Token::VbarVbar => BinOp::Or,
|
||||
Token::AmperAmper => BinOp::And,
|
||||
Token::Plus => BinOp::AddInt,
|
||||
Token::Minus => BinOp::SubInt,
|
||||
Token::Slash => BinOp::DivInt,
|
||||
Token::Star => BinOp::MultInt,
|
||||
Token::Percent => BinOp::ModInt,
|
||||
}
|
||||
.map_with_span(|name, location| {
|
||||
use BinOp::*;
|
||||
|
||||
let arg_annotation = match name {
|
||||
Or | And => Some(ast::Annotation::boolean(location)),
|
||||
Eq | NotEq => None,
|
||||
LtInt | LtEqInt | GtInt | GtEqInt | AddInt | SubInt | MultInt | DivInt | ModInt => {
|
||||
Some(ast::Annotation::int(location))
|
||||
}
|
||||
};
|
||||
|
||||
let return_annotation = match name {
|
||||
Or | And | Eq | NotEq | LtInt | LtEqInt | GtInt | GtEqInt => {
|
||||
Some(ast::Annotation::boolean(location))
|
||||
}
|
||||
AddInt | SubInt | MultInt | DivInt | ModInt => Some(ast::Annotation::int(location)),
|
||||
};
|
||||
|
||||
let arguments = vec![
|
||||
ast::Arg {
|
||||
arg_name: ast::ArgName::Named {
|
||||
name: "left".to_string(),
|
||||
label: "left".to_string(),
|
||||
location,
|
||||
is_validator_param: false,
|
||||
},
|
||||
annotation: arg_annotation.clone(),
|
||||
location,
|
||||
tipo: (),
|
||||
},
|
||||
ast::Arg {
|
||||
arg_name: ast::ArgName::Named {
|
||||
name: "right".to_string(),
|
||||
label: "right".to_string(),
|
||||
location,
|
||||
is_validator_param: false,
|
||||
},
|
||||
annotation: arg_annotation,
|
||||
location,
|
||||
tipo: (),
|
||||
},
|
||||
];
|
||||
|
||||
let body = expr::UntypedExpr::BinOp {
|
||||
location,
|
||||
name,
|
||||
left: Box::new(expr::UntypedExpr::Var {
|
||||
location,
|
||||
name: "left".to_string(),
|
||||
}),
|
||||
right: Box::new(expr::UntypedExpr::Var {
|
||||
location,
|
||||
name: "right".to_string(),
|
||||
}),
|
||||
};
|
||||
|
||||
expr::UntypedExpr::Fn {
|
||||
arguments,
|
||||
body: Box::new(body),
|
||||
return_annotation,
|
||||
fn_style: expr::FnStyle::BinOp(name),
|
||||
location,
|
||||
}
|
||||
});
|
||||
|
||||
let when_clause_parser = pattern_parser()
|
||||
.then(
|
||||
just(Token::Vbar)
|
||||
|
@ -1083,6 +1163,7 @@ pub fn expr_parser(
|
|||
bytearray,
|
||||
list_parser,
|
||||
anon_fn_parser,
|
||||
anon_binop_parser,
|
||||
block_parser,
|
||||
when_parser,
|
||||
let_parser,
|
||||
|
@ -1205,7 +1286,7 @@ pub fn expr_parser(
|
|||
} else {
|
||||
expr::UntypedExpr::Fn {
|
||||
location: call.location(),
|
||||
is_capture: true,
|
||||
fn_style: expr::FnStyle::Capture,
|
||||
arguments: holes,
|
||||
body: Box::new(call),
|
||||
return_annotation: None,
|
||||
|
@ -1239,7 +1320,20 @@ pub fn expr_parser(
|
|||
// Negate
|
||||
let op = choice((
|
||||
just(Token::Bang).to(UnOp::Not),
|
||||
just(Token::Minus).to(UnOp::Negate),
|
||||
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(UnOp::Negate),
|
||||
));
|
||||
|
||||
let unary = op
|
||||
|
|
|
@ -864,3 +864,26 @@ fn hex_and_numeric_underscore() {
|
|||
|
||||
assert_fmt(src, src);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn first_class_binop() {
|
||||
let src = indoc! { r#"
|
||||
fn foo() {
|
||||
compare_with(a, >, b)
|
||||
compare_with(a, >=, b)
|
||||
compare_with(a, <, b)
|
||||
compare_with(a, <=, b)
|
||||
compare_with(a, ==, b)
|
||||
compare_with(a, !=, b)
|
||||
combine_with(a, &&, b)
|
||||
combine_with(a, ||, b)
|
||||
compute_with(a, +, b)
|
||||
compute_with(a, -, b)
|
||||
compute_with(a, /, b)
|
||||
compute_with(a, *, b)
|
||||
compute_with(a, %, b)
|
||||
}
|
||||
"#};
|
||||
|
||||
assert_fmt(src, src);
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -12,7 +12,7 @@ use crate::{
|
|||
UntypedRecordUpdateArg,
|
||||
},
|
||||
builtins::{bool, byte_array, function, int, list, string, tuple},
|
||||
expr::{TypedExpr, UntypedExpr},
|
||||
expr::{FnStyle, TypedExpr, UntypedExpr},
|
||||
format,
|
||||
tipo::fields::FieldMap,
|
||||
};
|
||||
|
@ -220,12 +220,19 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
|
|||
|
||||
UntypedExpr::Fn {
|
||||
location,
|
||||
is_capture,
|
||||
fn_style,
|
||||
arguments: args,
|
||||
body,
|
||||
return_annotation,
|
||||
..
|
||||
} => self.infer_fn(args, &[], *body, is_capture, return_annotation, location),
|
||||
} => self.infer_fn(
|
||||
args,
|
||||
&[],
|
||||
*body,
|
||||
fn_style == FnStyle::Capture,
|
||||
return_annotation,
|
||||
location,
|
||||
),
|
||||
|
||||
UntypedExpr::If {
|
||||
location,
|
||||
|
@ -1011,17 +1018,19 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
|
|||
body,
|
||||
return_annotation,
|
||||
location,
|
||||
is_capture: false,
|
||||
fn_style,
|
||||
..
|
||||
},
|
||||
) if expected_arguments.len() == arguments.len() => self.infer_fn(
|
||||
arguments,
|
||||
expected_arguments,
|
||||
*body,
|
||||
false,
|
||||
return_annotation,
|
||||
location,
|
||||
),
|
||||
) if fn_style != FnStyle::Capture && expected_arguments.len() == arguments.len() => {
|
||||
self.infer_fn(
|
||||
arguments,
|
||||
expected_arguments,
|
||||
*body,
|
||||
false,
|
||||
return_annotation,
|
||||
location,
|
||||
)
|
||||
}
|
||||
|
||||
// Otherwise just perform normal type inference.
|
||||
(_, value) => self.infer(value),
|
||||
|
|
Loading…
Reference in New Issue