Current section
Files
Jump to
Current section
Files
src/do.erl
%% This file is a copy of erl_id_trans.erl from the R14B02 Erlang/OTP
%% distribution, with modifications to make it implement Haskell-style
%% 'do' syntax in Erlang.
%% All modifications are (C) 2011-2013 VMware, Inc; Eduard Sergeev.
%%
%% ``The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
%% compliance with the License. You should have received a copy of the
%% Erlang Public License along with this software. If not, it can be
%% retrieved via the world wide web at http://www.erlang.org/.
%%
%% Software distributed under the License is distributed on an "AS IS"
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%% the License for the specific language governing rights and limitations
%% under the License.
%%
%% The Initial Developer of the Original Code is Ericsson Utvecklings AB.
%% Portions created by Ericsson are Copyright 1999, Ericsson Utvecklings
%% AB. All Rights Reserved.''
%%
-module(do).
-export([
parse_transform/2,
format_error/1
]).
parse_transform(Ast, _Options) ->
forms(Ast).
forms([F0|Fs0]) ->
F1 = try transform_subnodes(F0, [])
catch throw:{Error, Line} ->
{error, {Line, ?MODULE, Error}}
end,
[F1|forms(Fs0)];
forms([]) -> [].
%% do syntax detection:
transform_node(
{call, Line, {atom, _Line1, do},
[{lc, _Line2, {AtomOrVar, _Line3, _MonadModule} = Monad, Qs}]},
MonadStack) when AtomOrVar =:= atom orelse AtomOrVar =:= var ->
%% 'do' calls of a particular form:
%% do([ MonadMod || Qualifiers ])
{call, Line,
{'fun', Line,
{clauses,
[{clause, Line, [], [], do_syntax(Qs, [Monad | MonadStack])}]}}, []};
transform_node(
{call, Line, {atom, Line1, ReturnOrFail}, As0},
[Monad|_Monads] = MonadStack) when ReturnOrFail =:= return orelse
ReturnOrFail =:= fail ->
%% 'return' calls of a particular form:
%% return(Arguments), and
%% 'fail' calls of a particular form:
%% fail(Arguments)
%% Transformed to:
%% "Monad:return(Args)" or "Monad:fail(Args)" in monadic context
{call, Line, {remote, Line1, Monad, {atom, Line1, ReturnOrFail}},
transform_node(As0, MonadStack)};
transform_node(Node, MonadStack) ->
transform_subnodes(Node, MonadStack).
transform_subnodes(Node, MonadStack) when is_tuple(Node) ->
List = tuple_to_list(Node),
Node2 = transform_subnodes(List, MonadStack),
list_to_tuple(Node2);
transform_subnodes(Nodes, MonadStack) when is_list(Nodes) ->
state_map(fun transform_node/2, MonadStack, Nodes);
transform_subnodes(Node, _MonadStack) ->
Node.
%% 'do' syntax transformation:
do_syntax([], [{_AtomOrVar, MLine, _MonadModule} | _MonadStack]) ->
transform_error("A 'do' construct cannot be empty", MLine);
do_syntax([{GenerateOrMatch, Line, _Pattern, _Expr}], _MonadStack)
when GenerateOrMatch =:= generate orelse GenerateOrMatch =:= match ->
transform_error("The last statement in a 'do' construct must be an expression", Line);
do_syntax([{generate, Line, {var, _Line, _Var} = Pattern, Expr} | Exprs],
[Monad | _Monads] = MonadStack) ->
%% "Pattern <- Expr, Tail" where Pattern is a simple variable
%% is transformed to
%% "Monad:'>>='(Expr, fun (Pattern) -> Tail')"
%% without a fail to match clause
[{call, Line, {remote, Line, Monad, {atom, Line, '>>='}},
[transform_node(Expr, MonadStack),
{'fun', Line,
{clauses,
[{clause, Line, [Pattern], [], do_syntax(Exprs, MonadStack)}]}}]}];
do_syntax([{generate, Line, Pattern, Expr} | Exprs],
[Monad | _Monads] = MonadStack) ->
%% "Pattern <- Expr, Tail" where Pattern is not a simple variable
%% is transformed to
%% "Monad:'>>='(Expr, fun (Pattern) -> Tail')"
%% with a fail clause if the function does not match
[{call, Line, {remote, Line, Monad, {atom, Line, '>>='}},
[transform_node(Expr, MonadStack),
{'fun', Line,
{clauses,
[{clause, Line, [Pattern], [], do_syntax(Exprs, MonadStack)},
{clause, Line, [{var, Line, '_'}], [],
[{call, Line, {remote, Line, Monad, {atom, Line, 'fail'}},
[{atom, Line, 'monad_badmatch'}]}]}]}}]}];
do_syntax([Expr], MonadStack) ->
[transform_node(Expr, MonadStack)]; %% Don't do '>>' chaining on the last elem
do_syntax([{match, _Line, _Pattern, _Expr} = Expr | Exprs],
MonadStack) ->
%% Handles 'let binding' in do expression a-la Haskell
[transform_node(Expr, MonadStack) | do_syntax(Exprs, MonadStack)];
do_syntax([Expr | Exprs], [Monad | _Monads] = MonadStack) ->
%% "Expr, Tail" is transformed to "Monad:'>>='(Expr, fun (_) -> Tail')"
%% Line is always the 2nd element of Expr
Line = element(2, Expr),
[{call, Line, {remote, Line, Monad, {atom, Line, '>>='}},
[transform_node(Expr, MonadStack),
{'fun', Line,
{clauses,
[{clause, Line,
[{var, Line, '_'}], [], do_syntax(Exprs, MonadStack)}]}}]}].
%% = Error formating =========================================================
%% Use this function to report any parse_transform error. The
%% resulting error message will be displayed as an ordinary
%% compilation error in a standard format.
transform_error(Message, Line) ->
throw({Message, Line}).
%% This function is called by the Erlang compiler to obtain an error
%% message which will be shown to the user.
format_error(Message) ->
case io_lib:deep_char_list(Message) of
true -> Message;
_ -> io_lib:write(Message)
end.
%% =============================================================================
%% Internal functions
%% =============================================================================
state_map(_Fun, _State, []) -> [];
state_map(Fun, State, [H|T]) ->
[Fun(H, State)|state_map(Fun, State, T)].