Current section

Files

Jump to
lfe src lfe_ms.erl
Raw

src/lfe_ms.erl

%% Copyright (c) 2008-2013 Robert Virding
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%% http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%% File : lfe_ms.erl
%% Author : Robert Virding
%% Purpose : Lisp Flavoured Erlang match specification expander.
%% Expand match specification into vanilla compatible data
%% structure. We assume that all macros in the match spec have
%% already been expanded. These functions are intended to be used
%% within macros so they return code which when evaluated return the
%% match-spec.
%%
%% Note that the vanilla match spec expander starts numbering dollar
%% variables from 1. We do the same to be compatible.
-module(lfe_ms).
-export([expand/1,format_error/1]).
-import(lists, [foldr/3,mapfoldl/3]).
%% ets:test_ms/2.
%% format_error(Error) -> ErrorString.
format_error(match_spec_head) -> "Illegal number of head arguments".
-define(Q(E), [quote,E]). %We do a lot of quoting!
-record(ms, {dc=1, %Dollar variable count from 1
bs=[], %Variable/$var bindings
where=guard %Where in spec head/guard/body
}).
%% expand(MSBody) -> Expansion.
%% Expand the match spec body.
expand(Cls) ->
case catch clauses(Cls, #ms{}) of
{error,E} -> error(E); %Signals errors
{'EXIT',E} -> error(E); %Signals errors
{Exp,_} -> Exp %Hurrah it worked
end.
%% clauses(MSClauses, State) -> {Patterns,State}.
clauses([Cl0|Cls0], St0) ->
{Cl1,St1} = clause(Cl0, St0),
{Cls1,St2} = clauses(Cls0, St1),
{[cons,Cl1,Cls1],St2};
clauses([], St) -> {[],St}.
%% clause(ClauseBody, State) -> {{Head,Guard,Body},State}.
clause([H0,['when'|G0]|B0], St0) ->
St1 = St0#ms{dc=1,where=guard,bs=[]}, %Reset clause local data
{H1,St2} = head(H0, St1),
{G1,St3} = guard(G0, St2),
{B1,St4} = body(B0, St3),
{[tuple,H1,G1,B1],St4};
clause([H0|B0], St0) ->
St1 = St0#ms{dc=1,where=guard,bs=[]}, %Reset clause local data
{H1,St2} = head(H0, St1),
{B1,St3} = body(B0, St2),
{[tuple,H1,[],B1],St3}.
%% head(Patterns, State) -> {Pattern,State}.
%% Expand a head which can only consist of one argument. Only allow
%% aliasing at the top-level and only to a variable.
head(Pats, St0) ->
St1 = St0#ms{where=head}, %We are now in the head
case Pats of %Test for top-level aliasing
[['=',S,Pat]] when is_atom(S) ->
St2 = new_binding(S, '$_', St1),
pattern(Pat, St2);
[['=',Pat,S]] when is_atom(S) ->
St2 = new_binding(S, '$_', St1),
pattern(Pat, St2);
[Pat] -> pattern(Pat, St1);
_ -> throw({error,match_spec_head}) %Wrong size
end.
pattern('_', St) -> {?Q('_'),St};
pattern(Symb, St0) when is_atom(Symb) -> %Variable
{Dv,St1} = pat_binding(Symb, St0),
{?Q(Dv),St1};
pattern([quote,_]=E, St) -> {E,St};
pattern([cons,H0,T0], St0) ->
{H1,St1} = pattern(H0, St0),
{T1,St2} = pattern(T0, St1),
{[cons,H1,T1],St2};
pattern([list|Ps0], St0) ->
{Ps1,St1} = pat_list(Ps0, St0),
{[list|Ps1],St1};
pattern([tuple|Ps0], St0) ->
{Ps1,St1} = pat_list(Ps0, St0),
{[tuple|Ps1],St1};
%% Support old no constructor style list forms.
pattern([H0|T0], St0) ->
{H1,St1} = pattern(H0, St0),
{T1,St2} = pattern(T0, St1),
{[H1,T1],St2};
pattern(E, St) -> {E,St}. %Atomic
pat_list(Ps, St) -> mapfoldl(fun pattern/2, St, Ps).
%% pat_binding(Var, Status) -> {DVar,Status}.
%% Get dollar var for variable, creating a new one if neccessary.
pat_binding(Var, St0) ->
case find_binding(Var, St0) of
{ok,Dv} -> {Dv,St0};
error ->
{Dv,St1} = new_dollar(St0),
{Dv,new_binding(Var, Dv, St1)}
end.
%% guard(Tests, State) -> {Tests,State}.
%% body(Tests, State) -> {Tests,State}.
%% The expression translation in the same except for which
%% expressions/tests are allowed. We use the same functions but carry
%% a 'where' field in the State to separate them.
guard(Ts, St0) ->
St1 = St0#ms{where=guard},
exprs(Ts, St1).
body(Es, St0) ->
St1 = St0#ms{where=body},
exprs(Es, St1).
%% exprs(Es, State) -> {Conses,State}.
%% expr(E, State) -> {E,State}.
exprs([E0|Es0], St0) ->
{E1,St1} = expr(E0, St0),
{Es1,St2} = exprs(Es0, St1),
{[cons,E1,Es1],St2};
exprs([], St) -> {[],St}.
expr(S, St) when is_atom(S) -> %Variable
case find_binding(S, St) of
{ok,Dv} -> {?Q(Dv),St}; %Head variable
error -> {S,St} %Free variable, need binding
end;
expr([quote,A]=E, St) when is_atom(A) -> %Atom
case atom_to_list(A) of
[$$|_] -> {[tuple,?Q(const),E],St}; %Catch dollar variables
_ -> {E,St}
end;
expr([quote,T], St) when is_tuple(T) -> %Must tuple tuples
{[tuple,T],St};
expr([quote,_]=E, St) -> {E,St}; %No need for {const,E}?
expr([cons,H0,T0], St0) ->
{H1,St1} = expr(H0, St0),
{T1,St2} = expr(T0, St1),
{[cons,H1,T1],St2};
expr([list|Es0], St0) ->
{Es1,St1} = expr_list(Es0, St0),
{[list|Es1],St1};
expr([tuple|Es0], St0) -> %Must tuple tuples
{Es1,St1} = expr_list(Es0, St0),
{[tuple,[tuple|Es1]],St1}; %Yes this is what it is
expr([binary|Segs0], St0) ->
{Segs1,St1} = expr_bitsegs(Segs0, St0),
{[binary|Segs1],St1};
%% Special match spec calls.
expr([bindings], St) -> {?Q('$*'),St}; %Special calls
expr([object], St) -> {?Q('$_'),St};
%% General function calls.
expr([call,?Q(erlang),?Q(Op)|Es0], St0) when is_atom(Op) ->
Ar = length(Es0),
case is_ms_erlang_func(Op, Ar) of
true ->
{Es1,St1} = expr_list(Es0, St0),
{[tuple,?Q(Op)|Es1],St1};
false -> throw({error,{illegal_ms_func,{erlang,Op,Ar}}})
end;
expr([Op|Es0], St0) when is_atom(Op) ->
Ar = length(Es0),
case is_ms_func(Op, Ar, St0#ms.where) of %Need to know where we are!
true ->
{Es1,St1} = expr_list(Es0, St0),
{[tuple,?Q(Op)|Es1],St1};
false -> throw({error,{illegal_ms_func,{Op,Ar}}})
end;
expr([_|_], _) -> throw({error,illegal_ms_call});
expr([], St) -> {[],St};
expr(T, St) when is_tuple(T) -> %Must tuple tuples
{[tuple,T],St};
expr(E, St) -> {E,St}. %Atomic
expr_list(Es, St) -> mapfoldl(fun expr/2, St, Es).
expr_bitsegs(Ss, St) -> mapfoldl(fun expr_bitseg/2, St, Ss).
expr_bitseg([Val0|Specs0]=F, St0) ->
case is_integer_list(F) of
true -> {F,St0};
false ->
{Specs1,St1} = expr_bitspecs(Specs0, St0),
case is_integer_list(Val0) of
true -> {[Val0|Specs1],St1};
false ->
{Val1,St2} = expr(Val0, St1),
{[Val1|Specs1],St2}
end
end;
expr_bitseg(Val, St) ->
expr(Val, St).
expr_bitspecs(Specs, St) ->
mapfoldl(fun ([size,Sz0], S0) ->
{Sz1,S1} = expr(Sz0, S0),
{[size,Sz1],S1};
(Sp, S) -> {Sp,S}
end, St, Specs).
is_integer_list([I|Is]) when is_integer(I) ->
is_integer_list(Is);
is_integer_list([]) -> true;
is_integer_list(_) -> false.
is_ms_erlang_func(N, A) ->
is_ms_op(N, A) orelse is_ms_bif(N, A).
%% is_ms_func(Name, Arity, Where) -> bool().
%% Test if Name/Arity is legal function in Where (guard/body).
is_ms_func(N, A, guard) ->
is_ms_op(N, A) orelse is_ms_bif(N, A) orelse is_ms_guard(N, A);
is_ms_func(N, A, body) ->
is_ms_op(N, A) orelse is_ms_bif(N, A) orelse is_ms_action(N, A).
%% is_ms_guard(Name, Arity) -> bool().
%% is_ms_action(Name, Arity) -> bool().
is_ms_guard(get_tcw, 0) -> true;
is_ms_guard(_, _) -> false.
is_ms_action(caller, 0) -> true;
is_ms_action(disable_trace, 1) -> true;
is_ms_action(disable_trace, 2) -> true;
is_ms_action(display, 1) -> true;
is_ms_action(enable_trace, 1) -> true;
is_ms_action(enable_trace, 2) -> true;
is_ms_action(exception_trace, 0) -> true;
is_ms_action(get_seq_token, 0) -> true;
is_ms_action(process_dump,0) -> true;
is_ms_action(message, 1) -> true;
is_ms_action(return_trace, 0) -> true;
is_ms_action(set_seq_token, 2) -> true;
is_ms_action(set_tcw, 1) -> true;
is_ms_action(silent, 1) -> true;
is_ms_action(trace, 2) -> true;
is_ms_action(trace, 3) -> true;
is_ms_action(_, _) -> false.
%% is_ms_op(Name, Arity) -> bool().
%% Valid match-spec operators.
is_ms_op(Op, Ar) ->
erl_internal:arith_op(Op, Ar)
orelse erl_internal:bool_op(Op, Ar)
orelse erl_internal:comp_op(Op, Ar).
%% is_ms_bif(Name, Arity) -> bool().
%% Valid match-spec bifs, both guard and body. All the standard ones
%% MINUS a few!
is_ms_bif(setelement, 3) -> true; %Not true, dangerous!!!!!
is_ms_bif(bit_size, 1) -> false;
is_ms_bif(byte_size, 1) -> false;
is_ms_bif(tuple_size, 1) -> false;
is_ms_bif(binary_part, _) -> false;
is_ms_bif(N, Ar) ->
erl_internal:guard_bif(N, Ar).
%% new_binding(Name, Value, State) -> State.
%% find_binding(Name, State) -> {ok,Value} | error.
%% fetch_binding(Name, State) -> Value.
new_binding(Var, Val, #ms{bs=Bs}=St) ->
St#ms{bs=orddict:store(Var, Val, Bs)}.
find_binding(Var, #ms{bs=Bs}) ->
orddict:find(Var, Bs).
%% fetch_binding(Var, #ms{bs=Bs}) ->
%% orddict:fetch(Var, Bs).
new_dollar(St) ->
C = St#ms.dc,
{list_to_atom("$" ++ integer_to_list(C)),St#ms{dc=C+1}}.