Current section
Files
Jump to
Current section
Files
src/gin.erl
-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)).