wip: rewrite case tree compiler to handle catch-alls

still with debug printing, and can't compile it yet
This commit is contained in:
Ulf Norell 2019-04-05 17:43:28 +02:00
parent ab13222d29
commit e2c48e1069

View File

@ -29,19 +29,20 @@
-type fexpr() :: {integer, integer()} -type fexpr() :: {integer, integer()}
| {bool, false | true} | {bool, false | true}
| {var, var_name()} | {var, var_name()}
| {tuple, [fexpr()]}
| {binop, ftype(), binop(), fexpr(), fexpr()} | {binop, ftype(), binop(), fexpr(), fexpr()}
| {'if', fexpr(), fexpr(), fexpr()} | {'if', fexpr(), fexpr(), fexpr()}
| {'let', var_name(), fexpr(), fexpr()} | {'let', var_name(), fexpr(), fexpr()}
| {switch, fcase()}. | {switch, fsplit()}.
-type fcase() :: {split, ftype(), var_name(), [fsplit_case()], fdefault()} -type fsplit() :: {split, ftype(), var_name(), [fcase()]}
| {nosplit, [var_name()], fexpr()}. | {nosplit, fexpr()}.
-type fcase() :: {'case', fsplit_pat(), fsplit()}.
-type fsplit_case() :: {'case', fsplit_pat(), fcase()}.
-type fsplit_pat() :: {bool, false | true} -type fsplit_pat() :: {bool, false | true}
| {tuple, [var_name()]}. | {tuple, [var_name()]}
| {var, var_name()}.
-type fdefault() :: nodefault | {default, fcase()}.
%% Intermediate format before case trees (fcase() and fsplit()). %% Intermediate format before case trees (fcase() and fsplit()).
-type falt() :: {'case', [fpat()], fexpr()}. -type falt() :: {'case', [fpat()], fexpr()}.
@ -202,10 +203,17 @@ 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, _, {switch, _, Expr = {typed, _, _, Type}, Alts}) -> expr_to_fcode(Env, _, {switch, _, Expr = {typed, _, E, Type}, Alts}) ->
X = fresh_name(), Switch = fun(X) ->
{'let', X, expr_to_fcode(Env, Expr), {switch, alts_to_fcode(Env, type_to_fcode(Env, Type), X, Alts)}
{switch, alts_to_fcode(Env, type_to_fcode(Env, Type), X, Alts)}}; end,
case E of
{id, _, X} -> Switch(X);
_ ->
X = fresh_name(),
{'let', X, expr_to_fcode(Env, Expr),
Switch(X)}
end;
%% Blocks %% Blocks
expr_to_fcode(Env, _Type, {block, _, Stmts}) -> expr_to_fcode(Env, _Type, {block, _, Stmts}) ->
@ -221,68 +229,140 @@ 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(). -spec alts_to_fcode(env(), ftype(), var_name(), [aeso_syntax:alt()]) -> fsplit().
alts_to_fcode(Env, Type, X, Alts) -> alts_to_fcode(Env, Type, X, Alts) ->
split_tree(Env, [{X, Type}], [alt_to_fcode(Env, Alt) || Alt <- Alts]). FAlts = [alt_to_fcode(Env, Alt) || Alt <- Alts],
split_tree(Env, [{X, Type}], FAlts).
%% 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()]) -> fcase(). -spec split_tree(env(), [{var_name(), ftype()}], [falt()]) -> fsplit().
split_tree(_Env, [], [{'case', [], Expr}]) -> split_tree(_Env, _Vars, []) ->
{nosplit, Expr}; error(non_exhaustive_patterns); %% TODO: nice error
split_tree(Env, Vars, Alts) -> split_tree(Env, Vars, Alts = [{'case', Pats, Body} | _]) ->
case next_split(Alts) of io:format("split_tree\n Vars = ~p\n Alts = ~120p\n", [Vars, Alts]),
{nosplit, Xs, Expr} -> {nosplit, Xs, Expr}; case next_split(Pats) of
{split, I, Splits} -> false ->
{Vars1, [{X, T} | Vars2]} = lists:split(I, Vars), Xs = [ X || {X, _} <- Vars ],
Cases = [{'case', Pat, split_tree(Env, Vars1 ++ split_vars(Pat, T) ++ Vars2, As)} Ys = [ Y || {var, Y} <- Pats ],
|| {Pat, As} <- Splits], Ren = [ {Y, X} || {Y, X} <- lists:zip(Ys, Xs), X /= Y, Y /= "_" ],
{split, T, X, Cases, nodefault} %% TODO: Unreachable clauses error
io:format("Renaming: ~p in\n ~p\n", [Ren, Body]),
{nosplit, rename(Ren, Body)};
I when is_integer(I) ->
io:format(" split_at ~p\n", [I]),
{Vars0, [{X, Type} | Vars1]} = lists:split(I - 1, Vars),
SAlts = merge_alts(I, X, [ split_alt(I, A) || A <- Alts ]),
io:format(" SAlts = ~p\n", [SAlts]),
Cases = [ {'case', SPat, split_tree(Env, Vars0 ++ split_vars(SPat, Type) ++ Vars1, FAlts)}
|| {SPat, FAlts} <- SAlts ],
{split, Type, X, Cases}
end. end.
-spec merge_alts(integer(), var_name(), [{fsplit_pat(), falt()}]) -> [{fsplit_pat(), [falt()]}].
merge_alts(I, X, Alts) ->
lists:foldr(fun(A, As) -> merge_alt(I, X, A, As) end,
[], Alts).
-spec merge_alt(integer(), var_name(), {fsplit_pat(), falt()}, Alts) -> Alts
when Alts :: [{fsplit_pat(), [falt()]}].
merge_alt(_, _, {P, A}, []) -> [{P, [A]}];
merge_alt(I, X, {P, A}, [{Q, As} | Rest]) ->
Match = fun({var, _}, {var, _}) -> match;
({tuple, _}, {tuple, _}) -> match;
({bool, B}, {bool, B}) -> match;
({var, _}, _) -> expand;
(_, {var, _}) -> insert;
(_, _) -> mismatch
end,
case Match(P, Q) of
match -> [{Q, [A | As]} | Rest];
mismatch -> [{Q, As} | merge_alt(I, X, {P, A}, Rest)];
expand -> [{Q, [expand(I, X, Q, A) | As]} | Rest];
insert -> [{P, [A]}, {Q, As} | Rest]
end.
expand(I, X, Q, {'case', Ps, E}) ->
{Ps0, [{var, Y} | Ps1]} = lists:split(I - 1, Ps),
Qs = case Q of
{tuple, Xs} -> [{var, "_"} || _ <- Xs];
{bool, _} -> []
end,
{'case', Ps0 ++ Qs ++ Ps1, rename([{Y, X}], E)}.
-spec split_alt(integer(), falt()) -> {fsplit_pat(), falt()}.
split_alt(I, {'case', Pats, Body}) ->
{Pats0, [Pat | Pats1]} = lists:split(I - 1, Pats),
{SPat, InnerPats} = split_pat(Pat),
{SPat, {'case', Pats0 ++ InnerPats ++ Pats1, Body}}.
-spec split_pat(fpat()) -> {fsplit_pat(), [fpat()]}.
split_pat(P = {var, X}) -> {{var, fresh_if_blank(X)}, [P]};
split_pat({bool, B}) -> {{bool, B}, []};
split_pat({tuple, Pats}) ->
Var = fun({var, X}) -> fresh_if_blank(X); (_) -> fresh_name() end,
Xs = [Var(P) || P <- Pats],
{{tuple, Xs}, Pats}.
-spec split_vars(fsplit_pat(), ftype()) -> [{var_name(), ftype()}]. -spec split_vars(fsplit_pat(), ftype()) -> [{var_name(), ftype()}].
split_vars({bool, _}, boolean) -> []; split_vars({bool, _}, boolean) -> [];
split_vars({tuple, Xs}, {tuple, Ts}) -> split_vars({tuple, Xs}, {tuple, Ts}) ->
lists:zip(Xs, Ts). lists:zip(Xs, Ts);
split_vars({var, X}, T) -> [{X, T}].
%% TODO: catchalls -spec rename([{var_name(), var_name()}], fexpr()) -> fexpr().
-spec next_split([falt()]) -> {nosplit, [var_name()], fexpr()} | {split, integer(), [{fsplit_pat(), [falt()]}]}. rename(Ren, Expr) ->
next_split([]) -> case Expr of
{nosplit, {abort, <<"Non-exhaustive pattern">>}}; {integer, _} -> Expr;
next_split(Alts = [{'case', Pats, Body} | _]) -> {bool, _} -> Expr;
NotMatch = fun({var, _}) -> true; (_) -> false end, {var, X} -> {var, rename_var(Ren, X)};
case lists:splitwith(NotMatch, Pats) of {tuple, Es} -> {tuple, [rename(Ren, E) || E <- Es]};
{Vars, []} -> {nosplit, [X || {var, X} <- Vars], Body}; {binop, T, Op, E1, E2} -> {binop, T, Op, rename(Ren, E1), rename(Ren, E2)};
{Vars, _} -> {'if', A, B, C} -> {'if', rename(Ren, A), rename(Ren, B), rename(Ren, C)};
I = length(Vars), {'let', X, E, Body} ->
Splits = group_by_split_pat([ split_alt(I, Alt) || Alt <- Alts ]), {Z, Ren1} = rename_binding(Ren, X),
{split, I, Splits} {'let', Z, rename(Ren, E), rename(Ren1, Body)};
{switch, Split} -> {switch, rename_split(Ren, Split)}
end. end.
-spec split_alt(integer(), falt()) -> {fsplit_pat() | default, falt()}. rename_var(Ren, X) -> proplists:get_value(X, Ren, X).
split_alt(I, {'case', Pats, Body}) -> rename_binding(Ren, X) ->
{Pats1, [Pat | Pats2]} = lists:split(I, Pats), Ren1 = lists:keydelete(X, 1, Ren),
{FPat, InnerPats} = split_pat(Pat), case lists:keymember(X, 2, Ren) of
{FPat, {'case', Pats1 ++ InnerPats ++ Pats2, Body}}. false -> {X, Ren1};
true ->
Z = fresh_name(),
{Z, [{X, Z} | Ren1]}
end.
-spec split_pat(fpat()) -> {fsplit_pat() | default, [fpat()]}. rename_bindings(Ren, []) -> {[], Ren};
split_pat({var, X}) -> {default, [{var, X}]}; rename_bindings(Ren, [X | Xs]) ->
split_pat({bool, B}) -> {{bool, B}, []}; {Z, Ren1} = rename_binding(Ren, X),
split_pat({tuple, Pats}) -> {Zs, Ren2} = rename_bindings(Ren1, Xs),
Var = fun({var, X}) -> X; (_) -> fresh_name() end, {[Z | Zs], Ren2}.
Xs = [Var(P) || P <- Pats],
{{tuple, Xs}, Pats}.
-spec group_by_split_pat([{fsplit_pat() | default, falt()}]) -> [{fsplit_pat(), [falt()]}]. rename_pat(Ren, P = {bool, _}) -> {P, Ren};
group_by_split_pat(Alts) -> rename_pat(Ren, {var, X}) ->
Tag = fun(default) -> default; {Z, Ren1} = rename_binding(Ren, X),
({tuple, _}) -> tuple; {{var, Z}, Ren1};
({bool, B}) -> B rename_pat(Ren, {tuple, Xs}) ->
end, {Zs, Ren1} = rename_bindings(Ren, Xs),
Grouped = maps:values(lists:foldr( {{tuple, Zs}, Ren1}.
fun({Pat, _} = Alt, Map) ->
maps:update_with(Tag(Pat), fun(As) -> [Alt | As] end, [Alt], Map) rename_split(Ren, {split, Type, X, Cases}) ->
end, #{}, Alts)), {split, Type, rename_var(Ren, X), [rename_case(Ren, C) || C <- Cases]}.
[ {Pat, [As || {_, As} <- G]} || G = [{Pat, _} | _] <- Grouped ].
rename_case(Ren, {'case', Pat, Split}) ->
{Pat1, Ren1} = rename_pat(Ren, Pat),
{'case', Pat1, rename_split(Ren1, Split)}.
-spec next_split([fpat()]) -> integer() | false.
next_split(Pats) ->
IsVar = fun({var, _}) -> true; (_) -> false end,
case [ I || {I, P} <- indexed(Pats), not IsVar(P) ] of
[] -> false;
[I | _] -> I
end.
-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}) ->
@ -395,8 +475,17 @@ fresh_name() ->
put('%fresh', N + 1), put('%fresh', N + 1),
lists:concat(["%", N]). lists:concat(["%", N]).
-spec fresh_if_blank(var_name()) -> var_name().
fresh_if_blank("_") -> fresh_name();
fresh_if_blank(X) -> X.
%% -- Attributes -- %% -- Attributes --
get_attributes(Ann) -> get_attributes(Ann) ->
[stateful || proplists:get_value(stateful, Ann, false)]. [stateful || proplists:get_value(stateful, Ann, false)].
%% -- Basic utilities --
indexed(Xs) ->
lists:zip(lists:seq(1, length(Xs)), Xs).