%% @doc Guard in %% @author Michael Uvarov (freeakk@gmail.com) %% Source: https://github.com/mad-cocktail/gin %% License: MIT -module(gin). -author('freeakk@gmail.com'). -export([parse_transform/2]). parse_transform(Forms, _Options) -> F1 = local_function(numeric_in, 2, in_transform('==')), F2 = local_function(in, 2, in_transform('=:=')), F3 = local_function(beetween, 3, fun beetween_transform/1), F = foldl_functions([F1, F2, F3, fun erl_syntax:revert/1]), X = [erl_syntax_lib:map(F, Tree) || Tree <- Forms], % io:format(user, "Before:\t~p\n\nAfter:\t~p\n", [Forms, X]), X. %% ================================================================== %% In %% ================================================================== %% It is curry (from Haskell) for `in_transform/2'. in_transform(Op) -> fun(Node) -> in_transform(Op, Node) end. %% @doc Replace `in(X, List)' with `(X =:= E1) andalso (X =:= E2)' %% when `List' is `[E1, E2]' and `Op' is `=:='. %% %% The caller checks, that the function name is valid. %% `in' can be any function, for example, `in2' is valid too. -spec in_transform(Op, Node) -> Node when Op :: '==' | '=:=', Node :: erl_syntax_lib:syntaxTree(). in_transform(Op, Node) -> Pos = erl_syntax:get_pos(Node), %% Call it fore all new nodes. New = fun(NewNode) -> erl_syntax:set_pos(NewNode, Pos) end, %% Extract arguments of the `in' function. [SubjectForm, ListForm] = erl_syntax:application_arguments(Node), Elems = case erl_syntax:type(ListForm) of string -> Str = erl_syntax:string_value(ListForm), [erl_syntax:char(C) || C <- Str]; list -> %% Extract the list of the valid values. erl_syntax:list_elements(ListForm) end, case Elems of [] -> %% Always `false'. New(erl_syntax:atom(false)); _ -> EqOp = New(erl_syntax:operator(Op)), OrOp = New(erl_syntax:operator('orelse')), %% `X' is `Subject =:= Xs'. [X|Xs] = [New(erl_syntax:infix_expr(E, EqOp, SubjectForm)) || E <- Elems], F = fun(Right, Left) -> New(erl_syntax:infix_expr(Left, OrOp, Right)) end, GuardAST = New(erl_syntax:parentheses(lists:foldl(F, X, Xs))), erl_syntax:revert(GuardAST) end. %% ================================================================== %% Beetween %% ================================================================== %% @doc Transforms `beetween(Subject, Start, To)'. %% Subject is a term, but usually it is a number. %% `From' and `To' can be wrapped with the `open(_)' call. %% It meand, that this value is not inluded in the interval. %% %% `beetween(X, F, T)' is replaced with `((X =< F) andalso (X >= T))'. %% `beetween(X, open(F), T)' is replaced with `((X < F) andalso (X >= T))'. beetween_transform(Node) -> Pos = erl_syntax:get_pos(Node), %% Call it fore all new nodes. New = fun(NewNode) -> erl_syntax:set_pos(NewNode, Pos) end, %% Extract arguments of the `in' function. [SubjectForm, FromForm, ToForm] = erl_syntax:application_arguments(Node), GtEqOp = New(erl_syntax:operator(greater(is_open(FromForm)))), LoEqOp = New(erl_syntax:operator(less(is_open(ToForm)))), AndOp = New(erl_syntax:operator('andalso')), Exp1 = New(erl_syntax:infix_expr(SubjectForm, GtEqOp, clean_open(FromForm))), Exp2 = New(erl_syntax:infix_expr(SubjectForm, LoEqOp, clean_open(ToForm))), Exp3 = New(erl_syntax:infix_expr(Exp1, AndOp, Exp2)), GuardAST = New(erl_syntax:parentheses(Exp3)), erl_syntax:revert(GuardAST). %% @doc Returns an operator name. -spec less(IsExcluded) -> Op when IsExcluded :: boolean(), Op :: atom(). less(true) -> '<'; less(false) -> '=<'. -spec greater(IsExcluded) -> Op when IsExcluded :: boolean(), Op :: atom(). greater(true) -> '>'; greater(false) -> '>='. %% @doc Return true, if `Node' is wrapped by `open(_)'. is_open(Node) -> is_local_function(open, 1, Node). %% @doc Convert the call of `open(Body)' to `Body'. clean_open(Node) -> case is_open(Node) of true -> hd(erl_syntax:application_arguments(Node)); false -> Node end. foldl_functions(Fs) -> fun(Node) -> Apply = fun(F, N) -> F(N) end, lists:foldl(Apply, Node, Fs) end. local_function(FunName, FunArity, TransFun) -> fun(Node) -> IsFun = is_local_function(FunName, FunArity, Node), if IsFun -> TransFun(Node); true -> Node end end. %% @doc Return `true', `Node' is a function call of the `FunName/FunArity' function. is_local_function(FunName, FunArity, Node) -> erl_syntax:type(Node) =:= application andalso always(Op = erl_syntax:application_operator(Node)) andalso erl_syntax:type(Op) =:= atom andalso erl_syntax:atom_value(Op) =:= FunName andalso application_arity(Node) =:= FunArity. always(_) -> true. %% @doc Return arity of the called function inside `Node'. application_arity(Node) -> length(erl_syntax:application_arguments(Node)).