Compile nested pattern matching to case trees

(Only tuple and variable patterns so far)
This commit is contained in:
Ulf Norell 2019-04-02 18:26:44 +02:00
parent ac25a8fc55
commit 41387ce0b1
2 changed files with 121 additions and 35 deletions

View File

@ -31,13 +31,22 @@
| {var, var_name()} | {var, var_name()}
| {binop, ftype(), binop(), fexpr(), fexpr()} | {binop, ftype(), binop(), fexpr(), fexpr()}
| {'if', fexpr(), fexpr(), fexpr()} | {'if', fexpr(), fexpr(), fexpr()}
| {switch, fexpr(), [falt()]}. | {'let', var_name(), fexpr(), fexpr()}
| {switch, fcase()}.
-type fcase() :: {split, ftype(), var_name(), [fsplit_case()], fdefault()}
| {nosplit, [var_name()], fexpr()}.
-type fsplit_case() :: {'case', fsplit_pat(), fcase()}.
-type fsplit_pat() :: {tuple, [var_name()]}.
-type fdefault() :: nodefault | {default, fcase()}.
%% Intermediate format before case trees (fcase() and fsplit()).
-type falt() :: {'case', [fpat()], fexpr()}.
-type fpat() :: {var, var_name()} -type fpat() :: {var, var_name()}
| {tuple, [fpat()]}. | {tuple, [fpat()]}.
-type falt() :: {'case', fpat(), fexpr()}.
-type ftype() :: aeb_fate_data:fate_type_type(). -type ftype() :: aeb_fate_data:fate_type_type().
@ -126,8 +135,12 @@ decls_to_fcode(Env, Decls) ->
%% First compute mapping from Sophia names to fun_names and add it to the %% First compute mapping from Sophia names to fun_names and add it to the
%% environment. %% environment.
Env1 = add_fun_env(Env, Decls), Env1 = add_fun_env(Env, Decls),
lists:foldl(fun(D, E) -> decl_to_fcode(E, D) end, lists:foldl(fun(D, E) ->
Env1, Decls). init_fresh_names(),
R = decl_to_fcode(E, D),
clear_fresh_names(),
R
end, Env1, Decls).
-spec decl_to_fcode(env(), aeso_syntax:decl()) -> env(). -spec decl_to_fcode(env(), aeso_syntax:decl()) -> env().
decl_to_fcode(Env, {type_decl, _, _, _}) -> Env; decl_to_fcode(Env, {type_decl, _, _, _}) -> Env;
@ -187,9 +200,10 @@ expr_to_fcode(Env, _Type, {'if', _, Cond, Then, Else}) ->
expr_to_fcode(Env, Else)}; expr_to_fcode(Env, Else)};
%% Switch %% Switch
expr_to_fcode(Env, _Type, {switch, _, Expr, Alts}) -> expr_to_fcode(Env, _, {switch, _, Expr = {typed, _, _, Type}, Alts}) ->
{switch, expr_to_fcode(Env, Expr), X = fresh_name(),
[ alt_to_fcode(Env, Alt) || Alt <- Alts ]}; {'let', X, expr_to_fcode(Env, Expr),
{switch, alts_to_fcode(Env, type_to_fcode(Env, Type), X, Alts)}};
%% Blocks %% Blocks
expr_to_fcode(Env, _Type, {block, _, Stmts}) -> expr_to_fcode(Env, _Type, {block, _, Stmts}) ->
@ -205,9 +219,68 @@ expr_to_fcode(_Env, Type, Expr) ->
binop_to_fcode(Op) when Op == '+'; Op == '-'; Op == '==' -> Op. binop_to_fcode(Op) when Op == '+'; Op == '-'; Op == '==' -> Op.
-spec alts_to_fcode(env(), ftype(), var_name(), [aeso_syntax:alt()]) -> fcase().
alts_to_fcode(Env, Type, X, Alts) ->
split_tree(Env, [{X, Type}], [alt_to_fcode(Env, Alt) || Alt <- Alts]).
%% Invariant: the number of variables matches the number of patterns in each falt.
-spec split_tree(env(), [{var_name(), ftype()}], [falt()]) -> fcase().
split_tree(_Env, [], [{'case', [], Expr}]) ->
{nosplit, Expr};
split_tree(Env, Vars, Alts) ->
case next_split(Alts) of
{nosplit, Xs, Expr} -> {nosplit, Xs, Expr};
{split, I, Splits} ->
{Vars1, [{X, T} | Vars2]} = lists:split(I, Vars),
Cases = [{'case', Pat, split_tree(Env, Vars1 ++ split_vars(Pat, T) ++ Vars2, As)}
|| {Pat, As} <- Splits],
{split, T, X, Cases, nodefault}
end.
-spec split_vars(fsplit_pat(), ftype()) -> [{var_name(), ftype()}].
split_vars({tuple, Xs}, {tuple, Ts}) ->
lists:zip(Xs, Ts).
%% TODO: catchalls
-spec next_split([falt()]) -> {nosplit, [var_name()], fexpr()} | {split, integer(), [{fsplit_pat(), [falt()]}]}.
next_split([]) ->
{nosplit, {abort, <<"Non-exhaustive pattern">>}};
next_split(Alts = [{'case', Pats, Body} | _]) ->
NotMatch = fun({var, _}) -> true; (_) -> false end,
case lists:splitwith(NotMatch, Pats) of
{Vars, []} -> {nosplit, [X || {var, X} <- Vars], Body};
{Vars, _} ->
I = length(Vars),
Splits = group_by_split_pat([ split_alt(I, Alt) || Alt <- Alts ]),
{split, I, Splits}
end.
-spec split_alt(integer(), falt()) -> {fsplit_pat() | default, falt()}.
split_alt(I, {'case', Pats, Body}) ->
{Pats1, [Pat | Pats2]} = lists:split(I, Pats),
{FPat, InnerPats} = split_pat(Pat),
{FPat, {'case', Pats1 ++ InnerPats ++ Pats2, Body}}.
-spec split_pat(fpat()) -> {fsplit_pat() | default, [fpat()]}.
split_pat({var, X}) -> {default, [{var, X}]};
split_pat({tuple, Pats}) ->
Var = fun({var, X}) -> X; (_) -> fresh_name() end,
Xs = [Var(P) || P <- Pats],
{{tuple, Xs}, Pats}.
-spec group_by_split_pat([{fsplit_pat() | default, falt()}]) -> [{fsplit_pat(), [falt()]}].
group_by_split_pat(Alts) ->
Tag = fun(default) -> default;
({tuple, _}) -> tuple end,
Grouped = maps:values(lists:foldr(
fun({Pat, _} = Alt, Map) ->
maps:update_with(Tag(Pat), fun(As) -> [Alt | As] end, [Alt], Map)
end, #{}, Alts)),
[ {Pat, [As || {_, As} <- G]} || G = [{Pat, _} | _] <- Grouped ].
-spec alt_to_fcode(env(), aeso_syntax:alt()) -> falt(). -spec alt_to_fcode(env(), aeso_syntax:alt()) -> falt().
alt_to_fcode(Env, {'case', _, Pat, Expr}) -> alt_to_fcode(Env, {'case', _, Pat, Expr}) ->
{'case', pat_to_fcode(Env, Pat), expr_to_fcode(Env, Expr)}. {'case', [pat_to_fcode(Env, Pat)], expr_to_fcode(Env, Expr)}.
-spec pat_to_fcode(env(), aeso_syntax:pattern()) -> fpat(). -spec pat_to_fcode(env(), aeso_syntax:pattern()) -> fpat().
pat_to_fcode(Env, {typed, _, Pat, Type}) -> pat_to_fcode(Env, {typed, _, Pat, Type}) ->
@ -222,9 +295,8 @@ pat_to_fcode(Env, _Type, {tuple, _, Pats}) ->
pat_to_fcode(_Env, Type, Pat) -> {todo, Pat, ':', Type}. pat_to_fcode(_Env, Type, Pat) -> {todo, Pat, ':', Type}.
-spec stmts_to_fcode(env(), [aeso_syntax:stmt()]) -> fexpr(). -spec stmts_to_fcode(env(), [aeso_syntax:stmt()]) -> fexpr().
stmts_to_fcode(Env, [{letval, _, Pat, _, Expr} | Stmts]) -> stmts_to_fcode(Env, [{letval, _, {typed, _, {id, _, X}, _}, _, Expr} | Stmts]) ->
{switch, expr_to_fcode(Env, Expr), {'let', X, expr_to_fcode(Env, Expr), stmts_to_fcode(Env, Stmts)};
[{'case', pat_to_fcode(Env, Pat), stmts_to_fcode(Env, Stmts)}]};
stmts_to_fcode(Env, [Expr]) -> stmts_to_fcode(Env, [Expr]) ->
expr_to_fcode(Env, Expr). expr_to_fcode(Env, Expr).
@ -302,6 +374,18 @@ lookup_fun(#{ fun_env := FunEnv }, Name) ->
FName -> FName FName -> FName
end. end.
init_fresh_names() ->
put('%fresh', 0).
clear_fresh_names() ->
erase('%fresh').
-spec fresh_name() -> var_name().
fresh_name() ->
N = get('%fresh'),
put('%fresh', N + 1),
lists:concat(["%", N]).
%% -- Attributes -- %% -- Attributes --
get_attributes(Ann) -> get_attributes(Ann) ->

View File

@ -94,15 +94,16 @@ push_env(Type, Env) ->
Env#env{ stack = [Type | Env#env.stack] }. Env#env{ stack = [Type | Env#env.stack] }.
bind_local(Name, Env = #env{ locals = Locals }) -> bind_local(Name, Env = #env{ locals = Locals }) ->
{length(Locals), Env#env{ locals = Locals ++ [Name] }}. I = length(Locals),
{I, Env#env{ locals = [{Name, I} | Locals] }}.
notail(Env) -> Env#env{ tailpos = false }. notail(Env) -> Env#env{ tailpos = false }.
lookup_var(Env = #env{ args = Args, locals = Locals }, X) -> lookup_var(Env = #env{ args = Args, locals = Locals }, X) ->
case {find_index(X, Locals), keyfind_index(X, 1, Args)} of case {lists:keyfind(X, 1, Locals), keyfind_index(X, 1, Args)} of
{false, false} -> error({unbound_variable, X, Env}); {false, false} -> error({unbound_variable, X, Env});
{false, Arg} -> {arg, Arg}; {false, Arg} -> {arg, Arg};
{Local, _} -> {var, Local} {{_, Local}, _} -> {var, Local}
end. end.
%% -- The compiler -- %% -- The compiler --
@ -127,27 +128,26 @@ to_scode(Env, {'if', Dec, Then, Else}) ->
[ to_scode(notail(Env), Dec), [ to_scode(notail(Env), Dec),
{ifte, to_scode(Env, Then), to_scode(Env, Else)} ]; {ifte, to_scode(Env, Then), to_scode(Env, Else)} ];
to_scode(Env, {switch, Expr, Alts}) -> to_scode(Env, {'let', X, Expr, Body}) ->
[ to_scode(notail(Env), Expr), {I, Env1} = bind_local(X, Env),
alts_to_scode(Env, Alts) ]; [ to_scode(Env, Expr),
aeb_fate_code:store({var, I}, {stack, 0}),
to_scode(Env1, Body) ];
to_scode(Env, {switch, Case}) ->
case_to_scode(Env, Case);
to_scode(_Env, Icode) -> ?TODO(Icode). to_scode(_Env, Icode) -> ?TODO(Icode).
alts_to_scode(Env, [{'case', {var, X}, Body}]) -> case_to_scode(Env, {nosplit, _Xs, Expr}) ->
{I, Env1} = bind_local(X, Env), %% TODO: need to worry about variable names?
[ aeb_fate_code:store({var, I}, {stack, 0}), to_scode(Env, Expr);
to_scode(Env1, Body) ]; case_to_scode(Env, {split, _Type, X, [{'case', {tuple, Xs}, Case}], nodefault}) ->
alts_to_scode(Env, Alts = [{'case', {tuple, Pats}, Body}]) -> {Code, Env1} = match_tuple(Env, Xs),
Xs = lists:flatmap(fun({var, X}) -> [X]; (_) -> [] end, Pats), [aeb_fate_code:push(lookup_var(Env, X)),
N = length(Pats), Code, case_to_scode(Env1, Case)];
case length(Xs) == N of case_to_scode(_, Split = {split, _, _, _, _}) ->
false -> ?TODO(Alts); ?TODO({'case', Split}).
true ->
{Code, Env1} = match_tuple(Env, Xs),
[Code, to_scode(Env1, Body)]
end;
alts_to_scode(_Env, Alts) ->
?TODO(Alts).
%% Tuple is in the accumulator. Arguments are the variable names. %% Tuple is in the accumulator. Arguments are the variable names.
match_tuple(Env, Xs) -> match_tuple(Env, Xs) ->
@ -466,6 +466,8 @@ r_push_consume({Ann1, {'PUSH', A}}, [{Ann2, {Op, R, ?a, B}} | Code]) when ?IsBin
{[{merge_ann(Ann1, Ann2), {Op, R, A, B}}], Code}; {[{merge_ann(Ann1, Ann2), {Op, R, A, B}}], Code};
r_push_consume({Ann1, {'PUSH', B}}, [{Ann2, {Op, R, A, ?a}} | Code]) when A /= ?a, ?IsBinOp(Op) -> r_push_consume({Ann1, {'PUSH', B}}, [{Ann2, {Op, R, A, ?a}} | Code]) when A /= ?a, ?IsBinOp(Op) ->
{[{merge_ann(Ann1, Ann2), {Op, R, A, B}}], Code}; {[{merge_ann(Ann1, Ann2), {Op, R, A, B}}], Code};
r_push_consume({Ann1, {'PUSH', A}}, [{Ann2, {'STORE', R, ?a}} | Code]) ->
{[{merge_ann(Ann1, Ann2), {'STORE', R, A}}], Code};
r_push_consume({Ann1, {'PUSH', A}}, [{Ann2, {'POP', B}} | Code]) -> r_push_consume({Ann1, {'PUSH', A}}, [{Ann2, {'POP', B}} | Code]) ->
case live_out(B, Ann2) of case live_out(B, Ann2) of
true -> {[{merge_ann(Ann1, Ann2), {'STORE', B, A}}], Code}; true -> {[{merge_ann(Ann1, Ann2), {'STORE', B, A}}], Code};