View Source merlin (merlin v3.0.1)

Parse transform helper library

The main function is transform/3, which let's you easily traverse an ast(), optionally modify it, and carry a state.

There are a few helper  functions that provides easy access to commonly needed information. For example the current set of bindings and the MFA for every function call.

Finally, when you're done transforming return/1 the result in the format expected by erl_lint (or else you crash the compiler).

Summary

Types

Represents the action to take after calling the transformer() callback.

Represents a syntax tree node. May be a tree or a leaf.
Copied from erl_syntax
A bit dirty to know about the internal structure like this, but erl_syntax:syntaxTree() also includes the vanilla AST and dialyser doesn't always approve of that.
Represents a compile time error. Must match erl_lint:error_info().
Represents the return value from a parse transform. Defined here because there's no parse_transform behaviour.
Represents the direction of the depth-first traversal through the ast().
Represents the callback used by transform/3.

Represents the return value from the transformer() callback.

Represents a compile time warning. Must match erl_lint:error_info().

Functions

Returns the result from transform/3, or just the final forms, to an erl_lint compatible format.

Returns the given node or nodes in erl_parse format.

Transforms the given Forms using the given Transformer with the given State.

Types

-type action() :: continue | delete | return | exceptions.

Represents the action to take after calling the transformer() callback.

continue
means to continue traversing the ast().
delete
means to delete the current node from the ast().
return
means to stop traversing the ast() and return the current node.
exceptions
means to stop traversing the ast() and return the given exceptions.
-type ast() :: erl_syntax_ast() | erl_parse() | erl_syntax:syntaxTree() | {eof, erl_anno:anno()}.
Represents a syntax tree node. May be a tree or a leaf.
Copied from erl_syntax
-type erl_syntax_ast() ::
    {tree, Type :: atom(), erl_syntax:syntaxTreeAttributes(), Data :: term()} |
    {wrapper, Type :: atom(), erl_syntax:syntaxTreeAttributes(), Tree :: erl_parse()}.
A bit dirty to know about the internal structure like this, but erl_syntax:syntaxTree() also includes the vanilla AST and dialyser doesn't always approve of that.
-type error_marker() :: {error, marker_with_file()}.
Represents a compile time error. Must match erl_lint:error_info().
Link to this type

exceptions_grouped_by_file/0

View Source
-type exceptions_grouped_by_file() :: [{File :: string(), [erl_lint:error_info()]}].
-type marker_with_file() :: {File :: file:filename(), erl_lint:error_info()}.
-type node_or_nodes() :: ast() | [ast()].
Link to this type

parse_transform_return/0

View Source
-type parse_transform_return() :: parse_transform_return([ast()]).
Link to this type

parse_transform_return/1

View Source
-type parse_transform_return(Forms) ::
    Forms |
    {warning, Forms, exceptions_grouped_by_file()} |
    {error, exceptions_grouped_by_file(), exceptions_grouped_by_file()}.
Represents the return value from a parse transform. Defined here because there's no parse_transform behaviour.
-type phase() :: enter | leaf | exit.
Represents the direction of the depth-first traversal through the ast().
-type transformer() :: transformer(State :: term()).
-type transformer(State) :: fun((phase(), ast(), State) -> transformer_return(State)).
Represents the callback used by transform/3.
-type transformer_return(State) ::
    node_or_nodes() |
    {node_or_nodes(), State} |
    continue |
    {continue, node_or_nodes()} |
    {continue, node_or_nodes(), State} |
    return |
    {return, node_or_nodes()} |
    {return, node_or_nodes(), State} |
    delete |
    {delete, State} |
    {error, Reason :: term()} |
    {error, Reason :: term(), State} |
    {warning, Reason :: term()} |
    {warning, Reason :: term(), node_or_nodes()} |
    {warning, Reason :: term(), node_or_nodes(), State} |
    {exceptions, [{error | warning, Reason :: term()}]} |
    {exceptions, [{error | warning, Reason :: term()}], node_or_nodes()} |
    {exceptions, [{error | warning, Reason :: term()}], node_or_nodes(), State}.

Represents the return value from the transformer() callback.

The first element is the action(), the second the new or modified node or nodes, and the third the new state.

Returning an atom is a shorthand for reusing the current node and state. Returning a two-tuple is a shorthand for reusing the current state.

You may also return {error, Reason} | {warning, Reason} to generate a compile time warning. These will be propagated to erl_lint. To generate multiple warnings, return {exceptions, ListOfErrorsAndWarnings}.
-type warning_marker() :: {warning, marker_with_file()}.
Represents a compile time warning. Must match erl_lint:error_info().

Functions

-spec return(parse_transform_return(Forms) | {parse_transform_return(Forms), State}) ->
          parse_transform_return(Forms)
          when State :: term(), Forms :: [ast()].

Returns the result from transform/3, or just the final forms, to an erl_lint compatible format.

This reverts the forms, while respecting any errors and/or warnings.

Returns the given node or nodes in erl_parse format.

Copied from parse_trans:revert_form/1 and slightly modified. The original also handles a bug in R16B03, but that is ancient history now.
Link to this function

transform(Forms, Transformer, State)

View Source
-spec transform(Forms, transformer(State), State) -> {parse_transform_return(Forms), State}
             when Forms :: [ast()].

Transforms the given Forms using the given Transformer with the given State.

This is done thorugh a depth-first traversal of the given Forms.

  • First entering each tree node (top-down)
  • Then, when you encounter a leaf node, you both "enter" and "exit" the node
  • Finally exiting each tree node (bottom-up)

For example, if you just want to change a call to a specific function, you would just use the top-down enter phase.

  parse_transform(Forms, _Options) ->
     transform(
         Forms,
         fun (enter, Form, _State) ->
                 case erl_syntax:type(Form) of
                     %% Application is the name of a function call
                     application ->
                         Operator erl_syntax:application_operator(Form),
                         case erl_syntax:type(Operator) =:= atom andalso
                              erl_syntax:atom_value(Operator) =:= foo of
                            true ->
                                 %% Change `foo(...)' to `my_module:foo(...)'
                                 %% using `merl' or `merlin_quote_transform'
                                 {return, ?Q("my_module:foo(123)")};
                           false ->
                                continue
                        end;
                     _ ->
                         continue
                  end;
             (_, _, _) ->
                 continue
          end,
         state
    ).

Sometimes it is easier, or necessary, to use a bottom-up approach. You can use exit for that. Sometimes you need both, and here is where merlin really shines, as you can just pattern match of enter and exit in the same function.

The latter shows up when you want to do some analysis on the way down, and then use that information on the way up.

For a real world example see merlin_quote_transform. That one is also pretty handy for writing transformers.

See also: transformer/3.