Allow assigning patterns to variables (#336)
* Change syntax tree and parser * Add assign pattern to type inference * Use check_expr instead of hard-coded type * Add fcode generation for assign pattern * Implement rename_spat for assign pattern * Add tests * Update CHANGELOG.md * Update docs and changelog * Add letpat to aeso_syntax_utils:fold * Use Plus instead of Scoped
This commit is contained in:
parent
a7b7aafced
commit
e8a171dc45
@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
- `Set` stdlib
|
- `Set` stdlib
|
||||||
- `Option.force_msg`
|
- `Option.force_msg`
|
||||||
- Loading namespaces into the current scope (e.g. `using Pair`)
|
- Loading namespaces into the current scope (e.g. `using Pair`)
|
||||||
|
- Assign patterns to variables (e.g. `let x::(t = y::_) = [1, 2, 3, 4]` where `t == [2, 3, 4]`)
|
||||||
### Changed
|
### Changed
|
||||||
### Removed
|
### Removed
|
||||||
|
|
||||||
|
@ -471,6 +471,17 @@ function
|
|||||||
get_left(Both(x, _)) = Some(x)
|
get_left(Both(x, _)) = Some(x)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Sophia also supports the assignment of patterns to variables:
|
||||||
|
```sophia
|
||||||
|
function f(x) = switch(x)
|
||||||
|
h1::(t = h2::_) => (h1 + h2)::t // same as `h1::h2::k => (h1 + h2)::h2::k`
|
||||||
|
_ => x
|
||||||
|
|
||||||
|
function g(p : int * option(int)) : int =
|
||||||
|
let (a, (o = Some(b))) = p // o is equal to Pair.snd(p)
|
||||||
|
b
|
||||||
|
```
|
||||||
|
|
||||||
*NOTE: Data types cannot currently be recursive.*
|
*NOTE: Data types cannot currently be recursive.*
|
||||||
|
|
||||||
## Lists
|
## Lists
|
||||||
|
@ -1703,6 +1703,9 @@ infer_expr(Env, {lam, Attrs, Args, Body}) ->
|
|||||||
infer_case(Env, Attrs, {tuple, Attrs, ArgPatterns}, {tuple_t, Attrs, ArgTypes}, Body, ResultType),
|
infer_case(Env, Attrs, {tuple, Attrs, ArgPatterns}, {tuple_t, Attrs, ArgTypes}, Body, ResultType),
|
||||||
NewArgs = [{arg, As, NewPat, NewT} || {typed, As, NewPat, NewT} <- NewArgPatterns],
|
NewArgs = [{arg, As, NewPat, NewT} || {typed, As, NewPat, NewT} <- NewArgPatterns],
|
||||||
{typed, Attrs, {lam, Attrs, NewArgs, NewBody}, {fun_t, Attrs, [], ArgTypes, ResultType}};
|
{typed, Attrs, {lam, Attrs, NewArgs, NewBody}, {fun_t, Attrs, [], ArgTypes, ResultType}};
|
||||||
|
infer_expr(Env, {letpat, Attrs, Id, Pattern}) ->
|
||||||
|
NewPattern = {typed, _, _, PatType} = infer_expr(Env, Pattern),
|
||||||
|
{typed, Attrs, {letpat, Attrs, check_expr(Env, Id, PatType), NewPattern}, PatType};
|
||||||
infer_expr(Env, Let = {letval, Attrs, _, _}) ->
|
infer_expr(Env, Let = {letval, Attrs, _, _}) ->
|
||||||
type_error({missing_body_for_let, Attrs}),
|
type_error({missing_body_for_let, Attrs}),
|
||||||
infer_expr(Env, {block, Attrs, [Let, abort_expr(Attrs, "missing body")]});
|
infer_expr(Env, {block, Attrs, [Let, abort_expr(Attrs, "missing body")]});
|
||||||
@ -1918,6 +1921,8 @@ free_vars({record, _, Fields}) ->
|
|||||||
free_vars([E || {field, _, _, E} <- Fields]);
|
free_vars([E || {field, _, _, E} <- Fields]);
|
||||||
free_vars({typed, _, A, _}) ->
|
free_vars({typed, _, A, _}) ->
|
||||||
free_vars(A);
|
free_vars(A);
|
||||||
|
free_vars({letpat, _, Id, Pat}) ->
|
||||||
|
free_vars(Id) ++ free_vars(Pat);
|
||||||
free_vars(L) when is_list(L) ->
|
free_vars(L) when is_list(L) ->
|
||||||
[V || Elem <- L,
|
[V || Elem <- L,
|
||||||
V <- free_vars(Elem)].
|
V <- free_vars(Elem)].
|
||||||
|
@ -96,7 +96,8 @@
|
|||||||
| nil
|
| nil
|
||||||
| {'::', var_name(), var_name()}
|
| {'::', var_name(), var_name()}
|
||||||
| {con, arities(), tag(), [var_name()]}
|
| {con, arities(), tag(), [var_name()]}
|
||||||
| {tuple, [var_name()]}.
|
| {tuple, [var_name()]}
|
||||||
|
| {assign, var_name(), var_name()}.
|
||||||
|
|
||||||
-type ftype() :: integer
|
-type ftype() :: integer
|
||||||
| boolean
|
| boolean
|
||||||
@ -875,7 +876,8 @@ alts_to_fcode(Env, Type, X, Alts) ->
|
|||||||
| {string, binary()}
|
| {string, binary()}
|
||||||
| nil | {'::', fpat(), fpat()}
|
| nil | {'::', fpat(), fpat()}
|
||||||
| {tuple, [fpat()]}
|
| {tuple, [fpat()]}
|
||||||
| {con, arities(), tag(), [fpat()]}.
|
| {con, arities(), tag(), [fpat()]}
|
||||||
|
| {assign, fpat(), fpat()}.
|
||||||
|
|
||||||
%% %% Invariant: the number of variables matches the number of patterns in each falt.
|
%% %% Invariant: the number of variables matches the number of patterns in each falt.
|
||||||
-spec split_tree(env(), [{var_name(), ftype()}], [falt()]) -> fsplit().
|
-spec split_tree(env(), [{var_name(), ftype()}], [falt()]) -> fsplit().
|
||||||
@ -975,6 +977,8 @@ split_pat({'::', P, Q}) -> {{'::', fresh_name(), fresh_name()}, [P, Q]};
|
|||||||
split_pat({con, As, I, Pats}) ->
|
split_pat({con, As, I, Pats}) ->
|
||||||
Xs = [fresh_name() || _ <- Pats],
|
Xs = [fresh_name() || _ <- Pats],
|
||||||
{{con, As, I, Xs}, Pats};
|
{{con, As, I, Xs}, Pats};
|
||||||
|
split_pat({assign, X = {var, _}, P}) ->
|
||||||
|
{{assign, fresh_name(), fresh_name()}, [X, P]};
|
||||||
split_pat({tuple, Pats}) ->
|
split_pat({tuple, Pats}) ->
|
||||||
Xs = [fresh_name() || _ <- Pats],
|
Xs = [fresh_name() || _ <- Pats],
|
||||||
{{tuple, Xs}, Pats}.
|
{{tuple, Xs}, Pats}.
|
||||||
@ -985,6 +989,7 @@ split_vars({int, _}, integer) -> [];
|
|||||||
split_vars({string, _}, string) -> [];
|
split_vars({string, _}, string) -> [];
|
||||||
split_vars(nil, {list, _}) -> [];
|
split_vars(nil, {list, _}) -> [];
|
||||||
split_vars({'::', X, Xs}, {list, T}) -> [{X, T}, {Xs, {list, T}}];
|
split_vars({'::', X, Xs}, {list, T}) -> [{X, T}, {Xs, {list, T}}];
|
||||||
|
split_vars({assign, X, P}, T) -> [{X, T}, {P, T}];
|
||||||
split_vars({con, _, I, Xs}, {variant, Cons}) ->
|
split_vars({con, _, I, Xs}, {variant, Cons}) ->
|
||||||
lists:zip(Xs, lists:nth(I + 1, Cons));
|
lists:zip(Xs, lists:nth(I + 1, Cons));
|
||||||
split_vars({tuple, Xs}, {tuple, Ts}) ->
|
split_vars({tuple, Xs}, {tuple, Ts}) ->
|
||||||
@ -1040,6 +1045,8 @@ pat_to_fcode(Env, {record_t, Fields}, {record, _, FieldPats}) ->
|
|||||||
end end,
|
end end,
|
||||||
make_tuple([pat_to_fcode(Env, FieldPat(Field))
|
make_tuple([pat_to_fcode(Env, FieldPat(Field))
|
||||||
|| Field <- Fields]);
|
|| Field <- Fields]);
|
||||||
|
pat_to_fcode(Env, _Type, {letpat, _, Id = {typed, _, {id, _, _}, _}, Pattern}) ->
|
||||||
|
{assign, pat_to_fcode(Env, Id), pat_to_fcode(Env, Pattern)};
|
||||||
|
|
||||||
pat_to_fcode(_Env, Type, Pat) ->
|
pat_to_fcode(_Env, Type, Pat) ->
|
||||||
error({todo, Pat, ':', Type}).
|
error({todo, Pat, ':', Type}).
|
||||||
@ -1530,6 +1537,7 @@ match_pat(L, {lit, L}) -> [];
|
|||||||
match_pat(nil, nil) -> [];
|
match_pat(nil, nil) -> [];
|
||||||
match_pat({'::', X, Y}, {op, '::', [A, B]}) -> [{X, A}, {Y, B}];
|
match_pat({'::', X, Y}, {op, '::', [A, B]}) -> [{X, A}, {Y, B}];
|
||||||
match_pat({var, X}, E) -> [{X, E}];
|
match_pat({var, X}, E) -> [{X, E}];
|
||||||
|
match_pat({assign, X, P}, E) -> [{X, E}, {P, E}];
|
||||||
match_pat(_, _) -> false.
|
match_pat(_, _) -> false.
|
||||||
|
|
||||||
constructor_form(Env, Expr) ->
|
constructor_form(Env, Expr) ->
|
||||||
@ -1765,6 +1773,7 @@ pat_vars(nil) -> [];
|
|||||||
pat_vars({'::', P, Q}) -> pat_vars(P) ++ pat_vars(Q);
|
pat_vars({'::', P, Q}) -> pat_vars(P) ++ pat_vars(Q);
|
||||||
pat_vars({tuple, Ps}) -> pat_vars(Ps);
|
pat_vars({tuple, Ps}) -> pat_vars(Ps);
|
||||||
pat_vars({con, _, _, Ps}) -> pat_vars(Ps);
|
pat_vars({con, _, _, Ps}) -> pat_vars(Ps);
|
||||||
|
pat_vars({assign, X, P}) -> pat_vars(X) ++ pat_vars(P);
|
||||||
pat_vars(Ps) when is_list(Ps) -> [X || P <- Ps, X <- pat_vars(P)].
|
pat_vars(Ps) when is_list(Ps) -> [X || P <- Ps, X <- pat_vars(P)].
|
||||||
|
|
||||||
-spec fsplit_pat_vars(fsplit_pat()) -> [var_name()].
|
-spec fsplit_pat_vars(fsplit_pat()) -> [var_name()].
|
||||||
@ -1985,7 +1994,11 @@ rename_spat(Ren, {con, Ar, C, Xs}) ->
|
|||||||
{{con, Ar, C, Zs}, Ren1};
|
{{con, Ar, C, Zs}, Ren1};
|
||||||
rename_spat(Ren, {tuple, Xs}) ->
|
rename_spat(Ren, {tuple, Xs}) ->
|
||||||
{Zs, Ren1} = rename_bindings(Ren, Xs),
|
{Zs, Ren1} = rename_bindings(Ren, Xs),
|
||||||
{{tuple, Zs}, Ren1}.
|
{{tuple, Zs}, Ren1};
|
||||||
|
rename_spat(Ren, {assign, X, P}) ->
|
||||||
|
{X1, Ren1} = rename_binding(Ren, X),
|
||||||
|
{P1, Ren2} = rename_binding(Ren1, P),
|
||||||
|
{{assign, X1, P1}, Ren2}.
|
||||||
|
|
||||||
rename_split(Ren, {split, Type, X, Cases}) ->
|
rename_split(Ren, {split, Type, X, Cases}) ->
|
||||||
{split, Type, rename_var(Ren, X), [rename_case(Ren, C) || C <- Cases]};
|
{split, Type, rename_var(Ren, X), [rename_case(Ren, C) || C <- Cases]};
|
||||||
|
@ -224,6 +224,9 @@ arg() -> choice(
|
|||||||
?RULE(id(), {arg, get_ann(_1), _1, type_wildcard(get_ann(_1))}),
|
?RULE(id(), {arg, get_ann(_1), _1, type_wildcard(get_ann(_1))}),
|
||||||
?RULE(id(), tok(':'), type(), {arg, get_ann(_1), _1, _3})).
|
?RULE(id(), tok(':'), type(), {arg, get_ann(_1), _1, _3})).
|
||||||
|
|
||||||
|
letpat() ->
|
||||||
|
?RULE(keyword('('), id(), tok('='), pattern(), tok(')'), {letpat, get_ann(_1), _2, _4}).
|
||||||
|
|
||||||
%% -- Types ------------------------------------------------------------------
|
%% -- Types ------------------------------------------------------------------
|
||||||
|
|
||||||
type_vars() -> paren_list(tvar()).
|
type_vars() -> paren_list(tvar()).
|
||||||
@ -328,6 +331,7 @@ exprAtom() ->
|
|||||||
, ?RULE(keyword('['), Expr, token('|'), comma_sep(comprehension_exp()), tok(']'), list_comp_e(_1, _2, _4))
|
, ?RULE(keyword('['), Expr, token('|'), comma_sep(comprehension_exp()), tok(']'), list_comp_e(_1, _2, _4))
|
||||||
, ?RULE(tok('['), Expr, binop('..'), Expr, tok(']'), _3(_2, _4))
|
, ?RULE(tok('['), Expr, binop('..'), Expr, tok(']'), _3(_2, _4))
|
||||||
, ?RULE(keyword('('), comma_sep(Expr), tok(')'), tuple_e(_1, _2))
|
, ?RULE(keyword('('), comma_sep(Expr), tok(')'), tuple_e(_1, _2))
|
||||||
|
, letpat()
|
||||||
])
|
])
|
||||||
end).
|
end).
|
||||||
|
|
||||||
@ -588,6 +592,8 @@ tuple_e(Ann, Exprs) -> {tuple, Ann, Exprs}.
|
|||||||
list_comp_e(Ann, Expr, Binds) -> {list_comp, Ann, Expr, Binds}.
|
list_comp_e(Ann, Expr, Binds) -> {list_comp, Ann, Expr, Binds}.
|
||||||
|
|
||||||
-spec parse_pattern(aeso_syntax:expr()) -> aeso_parse_lib:parser(aeso_syntax:pat()).
|
-spec parse_pattern(aeso_syntax:expr()) -> aeso_parse_lib:parser(aeso_syntax:pat()).
|
||||||
|
parse_pattern({letpat, Ann, Id, Pat}) ->
|
||||||
|
{letpat, Ann, Id, parse_pattern(Pat)};
|
||||||
parse_pattern({app, Ann, Con = {'::', _}, Es}) ->
|
parse_pattern({app, Ann, Con = {'::', _}, Es}) ->
|
||||||
{app, Ann, Con, lists:map(fun parse_pattern/1, Es)};
|
{app, Ann, Con, lists:map(fun parse_pattern/1, Es)};
|
||||||
parse_pattern({app, Ann, {'-', _}, [{int, _, N}]}) ->
|
parse_pattern({app, Ann, {'-', _}, [{int, _, N}]}) ->
|
||||||
|
@ -299,6 +299,8 @@ tuple_type(Factors) ->
|
|||||||
]).
|
]).
|
||||||
|
|
||||||
-spec expr_p(integer(), aeso_syntax:arg_expr()) -> doc().
|
-spec expr_p(integer(), aeso_syntax:arg_expr()) -> doc().
|
||||||
|
expr_p(P, {letpat, _, Id, Pat}) ->
|
||||||
|
paren(P > 100, follow(hsep(expr(Id), text("=")), expr(Pat)));
|
||||||
expr_p(P, {named_arg, _, Name, E}) ->
|
expr_p(P, {named_arg, _, Name, E}) ->
|
||||||
paren(P > 100, follow(hsep(expr(Name), text("=")), expr(E)));
|
paren(P > 100, follow(hsep(expr(Name), text("=")), expr(E)));
|
||||||
expr_p(P, {lam, _, Args, E}) ->
|
expr_p(P, {lam, _, Args, E}) ->
|
||||||
|
@ -58,6 +58,7 @@
|
|||||||
|
|
||||||
-type letval() :: {letval, ann(), pat(), expr()}.
|
-type letval() :: {letval, ann(), pat(), expr()}.
|
||||||
-type letfun() :: {letfun, ann(), id(), [pat()], type(), expr()}.
|
-type letfun() :: {letfun, ann(), id(), [pat()], type(), expr()}.
|
||||||
|
-type letpat() :: {letpat, ann(), id(), pat()}.
|
||||||
-type fundecl() :: {fun_decl, ann(), id(), type()}.
|
-type fundecl() :: {fun_decl, ann(), id(), type()}.
|
||||||
|
|
||||||
-type letbind()
|
-type letbind()
|
||||||
@ -122,7 +123,8 @@
|
|||||||
| {block, ann(), [stmt()]}
|
| {block, ann(), [stmt()]}
|
||||||
| {op(), ann()}
|
| {op(), ann()}
|
||||||
| id() | qid() | con() | qcon()
|
| id() | qid() | con() | qcon()
|
||||||
| constant().
|
| constant()
|
||||||
|
| letpat().
|
||||||
|
|
||||||
-type record_or_map() :: record | map | record_or_map_error.
|
-type record_or_map() :: record | map | record_or_map_error.
|
||||||
|
|
||||||
@ -156,6 +158,7 @@
|
|||||||
| {list, ann(), [pat()]}
|
| {list, ann(), [pat()]}
|
||||||
| {typed, ann(), pat(), type()}
|
| {typed, ann(), pat(), type()}
|
||||||
| {record, ann(), [field(pat())]}
|
| {record, ann(), [field(pat())]}
|
||||||
|
| letpat()
|
||||||
| constant()
|
| constant()
|
||||||
| con()
|
| con()
|
||||||
| id().
|
| id().
|
||||||
|
@ -88,6 +88,7 @@ fold(Alg = #alg{zero = Zero, plus = Plus, scoped = Scoped}, Fun, K, X) ->
|
|||||||
{map_get, _, A, B} -> Expr([A, B]);
|
{map_get, _, A, B} -> Expr([A, B]);
|
||||||
{map_get, _, A, B, C} -> Expr([A, B, C]);
|
{map_get, _, A, B, C} -> Expr([A, B, C]);
|
||||||
{block, _, Ss} -> Expr(Ss);
|
{block, _, Ss} -> Expr(Ss);
|
||||||
|
{letpat, _, X, P} -> Plus(BindExpr(X), Expr(P));
|
||||||
%% field()
|
%% field()
|
||||||
{field, _, LV, E} -> Expr([LV, E]);
|
{field, _, LV, E} -> Expr([LV, E]);
|
||||||
{field, _, LV, _, E} -> Expr([LV, E]);
|
{field, _, LV, _, E} -> Expr([LV, E]);
|
||||||
|
@ -201,6 +201,7 @@ compilable_contracts() ->
|
|||||||
"create",
|
"create",
|
||||||
"child_contract_init_bug",
|
"child_contract_init_bug",
|
||||||
"using_namespace",
|
"using_namespace",
|
||||||
|
"assign_patterns",
|
||||||
"test" % Custom general-purpose test file. Keep it last on the list.
|
"test" % Custom general-purpose test file. Keep it last on the list.
|
||||||
].
|
].
|
||||||
|
|
||||||
@ -236,6 +237,7 @@ failing_contracts() ->
|
|||||||
, ?PARSE_ERROR(vsemi, [<<?Pos(3, 3) "Unexpected indentation. Did you forget a '}'?">>])
|
, ?PARSE_ERROR(vsemi, [<<?Pos(3, 3) "Unexpected indentation. Did you forget a '}'?">>])
|
||||||
, ?PARSE_ERROR(vclose, [<<?Pos(4, 3) "Unexpected indentation. Did you forget a ']'?">>])
|
, ?PARSE_ERROR(vclose, [<<?Pos(4, 3) "Unexpected indentation. Did you forget a ']'?">>])
|
||||||
, ?PARSE_ERROR(indent_fail, [<<?Pos(3, 2) "Unexpected token 'entrypoint'.">>])
|
, ?PARSE_ERROR(indent_fail, [<<?Pos(3, 2) "Unexpected token 'entrypoint'.">>])
|
||||||
|
, ?PARSE_ERROR(assign_pattern_to_pattern, [<<?Pos(3, 22) "Unexpected token '='.">>])
|
||||||
|
|
||||||
%% Type errors
|
%% Type errors
|
||||||
, ?TYPE_ERROR(name_clash,
|
, ?TYPE_ERROR(name_clash,
|
||||||
|
4
test/contracts/assign_pattern_to_pattern.aes
Normal file
4
test/contracts/assign_pattern_to_pattern.aes
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
contract AssignPatternToPattern =
|
||||||
|
entrypoint f() =
|
||||||
|
let x::(t::z = y) = [1, 2, 3]
|
||||||
|
(x + t)::y
|
16
test/contracts/assign_patterns.aes
Normal file
16
test/contracts/assign_patterns.aes
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
include "List.aes"
|
||||||
|
|
||||||
|
contract AssignPatterns =
|
||||||
|
|
||||||
|
entrypoint test() = foo([1, 0, 2], (2, Some(3)), Some([4, 5]))
|
||||||
|
|
||||||
|
entrypoint foo(xs : list(int), p : int * option(int), some : option(list(int))) =
|
||||||
|
let x::(t = y::_) = xs
|
||||||
|
let z::_ = t
|
||||||
|
|
||||||
|
let (a, (o = Some(b))) = p
|
||||||
|
|
||||||
|
let Some((f = g::_)) = some
|
||||||
|
g + List.get(1, f)
|
||||||
|
|
||||||
|
x + y + z + a + b
|
Loading…
x
Reference in New Issue
Block a user