-module(gin). -moduledoc """ Guard in Source: https://github.com/mad-cocktail/gin License: MIT Author: Michael Uvarov (freeakk@gmail.com) """. -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 begin Op = erl_syntax:application_operator(Node), erl_syntax:type(Op) =:= atom andalso erl_syntax:atom_value(Op) =:= FunName end andalso application_arity(Node) =:= FunArity. -doc "Return arity of the called function inside `Node`.". application_arity(Node) -> length(erl_syntax:application_arguments(Node)).