From 74aff5401b003cd530fd64dba4d3eddf1c9e38b1 Mon Sep 17 00:00:00 2001 From: Gaith Hallak Date: Tue, 12 Apr 2022 12:40:32 +0300 Subject: [PATCH] Introduce pipe operator |> (#371) * Add pipe operator * Add tests * Update docs and CHANGELOG --- CHANGELOG.md | 4 ++++ docs/sophia_syntax.md | 3 +++ src/aeso_ast_infer_types.erl | 7 ++++++- src/aeso_ast_to_fcode.erl | 7 +++++-- src/aeso_parser.erl | 7 ++++--- test/aeso_compiler_tests.erl | 1 + test/contracts/pipe_operator.aes | 18 ++++++++++++++++++ 7 files changed, 41 insertions(+), 6 deletions(-) create mode 100644 test/contracts/pipe_operator.aes diff --git a/CHANGELOG.md b/CHANGELOG.md index af0a454..13a2b52 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Added - Compiler warnings for the follwing: shadowing, negative spends, division by zero, unused functions, unused includes, unused stateful annotations, unused variables, unused parameters, unused user-defined type, dead return value. +- The pipe operator |> + ``` + [1, 2, 3] |> List.first |> Option.is_some // Option.is_some(List.first([1, 2, 3])) + ``` ### Changed - Error messages have been restructured (less newlines) to provide more unified errors. Also `pp_oneline/1` has been added. ### Removed diff --git a/docs/sophia_syntax.md b/docs/sophia_syntax.md index db438b5..b9c6aa0 100644 --- a/docs/sophia_syntax.md +++ b/docs/sophia_syntax.md @@ -234,6 +234,7 @@ Path ::= Id // Record field BinOp ::= '||' | '&&' | '<' | '>' | '=<' | '>=' | '==' | '!=' | '::' | '++' | '+' | '-' | '*' | '/' | 'mod' | '^' + | '|>' UnOp ::= '-' | '!' ``` @@ -245,6 +246,7 @@ UnOp ::= '-' | '!' | `!` `&&` `\|\|` | logical operators | `==` `!=` `<` `>` `=<` `>=` | comparison operators | `::` `++` | list operators +| `\|>` | functional operators ## Operator precendences @@ -261,3 +263,4 @@ In order of highest to lowest precedence. | `<` `>` `=<` `>=` `==` `!=` | none | `&&` | right | `\|\|` | right +| `\|>` | left diff --git a/src/aeso_ast_infer_types.erl b/src/aeso_ast_infer_types.erl index 0e87869..4ef4403 100644 --- a/src/aeso_ast_infer_types.erl +++ b/src/aeso_ast_infer_types.erl @@ -1941,7 +1941,12 @@ infer_infix({'::', As}) -> infer_infix({'++', As}) -> ElemType = fresh_uvar(As), ListType = {app_t, As, {id, As, "list"}, [ElemType]}, - {fun_t, As, [], [ListType, ListType], ListType}. + {fun_t, As, [], [ListType, ListType], ListType}; +infer_infix({'|>', As}) -> + ArgType = fresh_uvar(As), + ResType = fresh_uvar(As), + FunType = {fun_t, As, [], [ArgType], ResType}, + {fun_t, As, [], [ArgType, FunType], ResType}. infer_prefix({'!',As}) -> Bool = {id, As, "bool"}, diff --git a/src/aeso_ast_to_fcode.erl b/src/aeso_ast_to_fcode.erl index f0ca66a..5194340 100644 --- a/src/aeso_ast_to_fcode.erl +++ b/src/aeso_ast_to_fcode.erl @@ -703,8 +703,11 @@ expr_to_fcode(Env, _Type, {block, _, Stmts}) -> expr_to_fcode(Env, _Type, Expr = {app, _, {Op, _}, [_, _]}) when Op == '&&'; Op == '||' -> Tree = expr_to_decision_tree(Env, Expr), decision_tree_to_fcode(Tree); -expr_to_fcode(Env, _Type, {app, _Ann, {Op, _}, [A, B]}) when is_atom(Op) -> - {op, Op, [expr_to_fcode(Env, A), expr_to_fcode(Env, B)]}; +expr_to_fcode(Env, Type, {app, Ann, {Op, _}, [A, B]}) when is_atom(Op) -> + case Op of + '|>' -> expr_to_fcode(Env, Type, {app, Ann, B, [A]}); + _ -> {op, Op, [expr_to_fcode(Env, A), expr_to_fcode(Env, B)]} + end; expr_to_fcode(Env, _Type, {app, _Ann, {Op, _}, [A]}) when is_atom(Op) -> case Op of '-' -> {op, '-', [{lit, {int, 0}}, expr_to_fcode(Env, A)]}; diff --git a/src/aeso_parser.erl b/src/aeso_parser.erl index 79d7a8b..7e5fa62 100644 --- a/src/aeso_parser.erl +++ b/src/aeso_parser.erl @@ -308,17 +308,18 @@ expr() -> expr100(). expr100() -> Expr100 = ?LAZY_P(expr100()), - Expr200 = ?LAZY_P(expr200()), + Expr150 = ?LAZY_P(expr150()), choice( [ ?RULE(lam_args(), keyword('=>'), body(), {lam, _2, _1, _3}) %% TODO: better location - , {'if', keyword('if'), parens(Expr100), Expr200, right(tok(else), Expr100)} - , ?RULE(Expr200, optional(right(tok(':'), type())), + , {'if', keyword('if'), parens(Expr100), Expr150, right(tok(else), Expr100)} + , ?RULE(Expr150, optional(right(tok(':'), type())), case _2 of none -> _1; {ok, Type} -> {typed, get_ann(_1), _1, Type} end) ]). +expr150() -> infixl(expr200(), binop('|>')). expr200() -> infixr(expr300(), binop('||')). expr300() -> infixr(expr400(), binop('&&')). expr400() -> infix(expr500(), binop(['<', '>', '=<', '>=', '==', '!='])). diff --git a/test/aeso_compiler_tests.erl b/test/aeso_compiler_tests.erl index fe3a130..fa3cb6a 100644 --- a/test/aeso_compiler_tests.erl +++ b/test/aeso_compiler_tests.erl @@ -218,6 +218,7 @@ compilable_contracts() -> "using_namespace", "assign_patterns", "patterns_guards", + "pipe_operator", "test" % Custom general-purpose test file. Keep it last on the list. ]. diff --git a/test/contracts/pipe_operator.aes b/test/contracts/pipe_operator.aes new file mode 100644 index 0000000..fc7248f --- /dev/null +++ b/test/contracts/pipe_operator.aes @@ -0,0 +1,18 @@ +contract Main = + function is_negative(x : int) = + if (x < 0) + true + else + false + + function inc_by_one(x : int) = x + 1 + function inc_by_two(x : int) = x + 2 + + type state = bool + + entrypoint init(x : int) = x + |> inc_by_one + |> inc_by_one + |> inc_by_two + |> ((x) => x * 5) + |> is_negative