Add `passage_transform` module
diff --git a/src/passage_transform.erl b/src/passage_transform.erl
new file mode 100644
index 0000000..7cb4280
--- /dev/null
+++ b/src/passage_transform.erl
@@ -0,0 +1,203 @@
+%% @copyright 2017 Takeru Ohta <phjgt308@gmail.com>
+%%
+%% @doc  A `parse_transform' module for `passage'
+%%
+%% This module handles `passage_trace' attribute.
+%%
+%% If the attribute is appeared, the next function will be traced automatically.
+%%
+%% See following example:
+%%
+%% ```
+%% -module(example).
+%%
+%% -compile({parse_transform, passage_transform}). % Enables `passage_transform'
+%%
+%% -passage_trace([{tags, #{foo => bar}}, {eval_tags, #{size => "byte_size(Bin)"}}]).
+%% -spec foo(binary()) -> binary().
+%% foo(Bin) ->
+%%   <<"foo", Bin/binary>>.
+%% '''
+%%
+%% The above `foo' function will be transformed as follows:
+%%
+%% ```
+%% foo(Bin) ->
+%%   try
+%%     passage_pd:start_span(foo, [{tags, #{application => example, module => example}}]),
+%%     passage_pd:set_tags(#{process => self(), size => byte_size(Bin)}),
+%%     <<"foo", Bin/binary>>
+%%    after
+%%     passage_pd:finish_span()
+%%    end.
+%% '''
+%%
+%% === References ===
+%%
+%% <ul>
+%%   <li><a href="erlang.org/doc/apps/erts/absform.html">The Abstract Format</a></li>
+%% </ul>
+-module(passage_transform).
+
+%%------------------------------------------------------------------------------
+%% Exported API
+%%------------------------------------------------------------------------------
+-export([parse_transform/2]).
+
+%%------------------------------------------------------------------------------
+%% Types & Records
+%%------------------------------------------------------------------------------
+-type form() :: {attribute, line(), atom(), term()}
+              | {function, line(), atom(), non_neg_integer(), [clause()]}
+              | erl_parse:abstract_form().
+
+-type clause() :: {clause, line(), [term()], [term()], [expr()]}
+                | erl_parse:abstract_clause().
+
+-type expr() :: expr_call_remote()
+              | expr_var()
+              | erl_parse:abstract_expr().
+
+-type expr_call_remote() :: {call, line(), {remote, line(), expr(), expr()}, [expr()]}.
+-type expr_var() :: {var, line(), atom()}.
+
+-type line() :: non_neg_integer().
+
+-record(state,
+        {
+          application :: atom(),
+          module      :: module(),
+          function    :: atom(),
+
+          trace   = false :: boolean(), % if `true' the next function will be traced
+          tags      = #{} :: map(),
+          eval_tags = #{} :: map()
+        }).
+
+%%------------------------------------------------------------------------------
+%% Exported Functions
+%%------------------------------------------------------------------------------
+%% @doc Performs transformations for `passage'
+-spec parse_transform(AbstractForms, list()) -> AbstractForms when
+      AbstractForms :: [term()].
+parse_transform(AbstractForms, CompileOptions) ->
+    State = #state{
+             application = guess_application(AbstractForms, CompileOptions),
+             module      = get_module(AbstractForms)
+            },
+    walk_forms(AbstractForms, State).
+
+%%------------------------------------------------------------------------------
+%% Internal Functions
+%%------------------------------------------------------------------------------
+-spec walk_forms([form()], #state{}) -> [form()].
+walk_forms(Forms, State) ->
+    {_, ResultFroms} =
+        lists:foldl(
+          fun ({attribute, _, passage_trace, Options}, {State0, Acc}) ->
+                  Tags =
+                      maps:merge(
+                        proplists:get_value(tags, Options, #{}),
+                        #{
+                           application => State0#state.application,
+                           module => State0#state.module
+                         }),
+                  EvalTags =
+                      proplists:get_value(eval_tags, Options, #{}),
+                  State1 = State0#state{trace = true, tags = Tags, eval_tags = EvalTags},
+                  {State1, Acc};
+              ({function, _, Name, _, Clauses} = Form, {State0 = #state{trace = true}, Acc}) ->
+                  State1 = State0#state{function = Name},
+                  NewForm = setelement(5, Form, walk_clauses(Clauses, State1)),
+                  {State1#state{trace = false}, [NewForm | Acc]};
+              (Form, {State0, Acc}) ->
+                  {State0, [Form | Acc]}
+          end,
+          {State, []},
+          Forms),
+    lists:reverse(ResultFroms).
+
+-spec walk_clauses([clause()], #state{}) -> [clause()].
+walk_clauses(Clauses, State) ->
+    [case Clause of
+         {clause, Line, Args, Guards, Body} ->
+             OperationName = {atom, Line, State#state.function},
+             StartOptions =
+                 erl_parse:abstract(
+                   [{tags, State#state.tags}],
+                   [{line, Line}]),
+             StartSpan =
+                 make_call_remote(
+                   Line, passage_pd, 'start_span', [OperationName, StartOptions]),
+
+             EvalTags =
+                 {map, Line,
+                  [
+                   {map_field_assoc, Line,
+                    {atom, Line, 'process'}, make_call_remote(Line, erlang, self, [])} |
+                   [begin
+                        case parse_expr_string(Line, ValueExprStr) of
+                            {error, Reason} ->
+                                error({bad_passage_eval_tag,
+                                       [{module, State#state.module},
+                                        {function, State#state.function},
+                                        {key, Key}, {value_expr, ValueExprStr},
+                                        {error, Reason}]});
+                            {ok, Exprs} ->
+                                {map_field_assoc, Line, {atom, Line, Key}, {block, Line, Exprs}}
+                        end
+                    end || {Key, ValueExprStr} <- maps:to_list(State#state.eval_tags)]
+                  ]},
+             SetTags =
+                 make_call_remote(Line, passage_pd, 'set_tags', [EvalTags]),
+
+             FinishSpan =
+                 make_call_remote(Line, passage_pd, 'finish_span', []),
+             {clause, Line, Args, Guards,
+              [
+               {'try', Line, [StartSpan, SetTags | Body], [], [], [FinishSpan]}
+              ]};
+         _ ->
+             Clause
+     end || Clause <- Clauses].
+
+-spec parse_expr_string(line(), string()) -> {ok, list()} | {error, term()}.
+parse_expr_string(Line, ExprStr) ->
+    case erl_scan:string(ExprStr ++ ".") of
+        {error, Reason, _} -> {error, {cannot_tokenize, Reason}};
+        {ok, Tokens0, _}   ->
+            Tokens1 = lists:map(fun (T) -> setelement(2, T, Line) end, Tokens0),
+            case erl_parse:parse_exprs(Tokens1) of
+                {error, Reason} -> {error, {cannot_parse, Reason}};
+                {ok, Exprs}     -> {ok, Exprs}
+            end
+    end.
+
+-spec get_module([form()]) -> module().
+get_module([{attribute, _, module, Module} | _]) -> Module;  % The `module' attribute will always exist
+get_module([_                              | T]) -> get_module(T).
+
+-spec guess_application([form()], list()) -> atom() | undefined.
+guess_application(Forms, CompileOptions) ->
+    OutDir = proplists:get_value(outdir, CompileOptions),
+    SrcDir = case hd(Forms) of
+                 {attribute, _, file, {FilePath, _}} -> filename:dirname(FilePath);
+                 _                                   -> undefined
+             end,
+    find_app_file([Dir || Dir <- [OutDir, SrcDir], Dir =/= undefined]).
+
+-spec make_call_remote(line(), module(), atom(), [expr()]) -> expr_call_remote().
+make_call_remote(Line, Module, Function, ArgsExpr) ->
+    {call, Line, {remote, Line, {atom, Line, Module}, {atom, Line, Function}}, ArgsExpr}.
+
+-spec find_app_file([string()]) -> atom() | undefined.
+find_app_file([])           -> undefined;
+find_app_file([Dir | Dirs]) ->
+    case filelib:wildcard(Dir++"/*.{app,app.src}") of
+        [File] ->
+            case file:consult(File) of
+                {ok, [{application, AppName, _}|_]} -> AppName;
+                _                                   -> find_app_file(Dirs)
+            end;
+        _ -> find_app_file(Dirs)
+    end.