forms
A simple library that simplifies working with the Erlang abstract format.
If you are curious about what
formsis capable of doing, I suggest you to check out meta.
Fetching a module's forms
The forms module features a read/1 function that can be used to fetch a module's abstract syntax tree (AST).
read/1 works with both Erlang source files (i.e., files with the .erl suffix) and Erlang binary files
(i.e., files with the .beam suffix).
The line below would read the forms from Erlang's internal lists module.
forms:read(lists).
Similarly, the following line would read the forms from a developer-provided hello_world source file.
forms:read("src/hello_world.erl").Note that in order to be able to fetch a beam file's AST the Erlang binary file must have been compiled using the
debug_infooption (e.g.,erlc -o ebin +debug_info src/hello_world.erl). Obviously, this is not a requirement when reading the forms from a source file.
High-order functions
Traversing an Erlang module's AST is now as simple as traversing a list.
The forms library ships with the map/{2,3}, reduce\{3,4}, mr/3, filter/2, any/2 and all/2 functions,
which are anologous to those available in the lists module.
Most of the above mentioned functions support two different traversal modes: 1) (valid) forms only and 2) full.
The latter (i.e., full) is the default traversal mode. If one wants to change to forms_only, one can do so by using the
optional options argument (e.g., forms:map(Fun, Forms, _Opts = [forms_only])..
Debug functions
Working with Erlang's abstract format is often tedious because Erlang developers are not used to see Erlang abstract code.
Unlike Lisp, Erlang is not homonoic, which means that Erlang's code does not have the same structure as its AST. Homoicinity makes metaprogramming easier than in a programming language without this property because code can be treated as data.
To fill this gap, forms features the eval/1, from_abstract/1 and to_abstract/1 debug functions that may come in handy
when working with Erlang's abstract code.
eval/1
Evaluate an Erlang expression (in string form) or abstract form.
forms:eval("1 + 1.").
%% => 2forms:eval({op,1,'+',{integer,1,1},{integer,1,1}}).
%% => 2to_abstract/1
Convert the provided Erlang attribute or expression (in string form) to its abstract format representation.
forms:to_abstract("hello(Name) -> io:format(\"Hello, ~s!~n\", [Name]).").
%% => {function,1,hello,1,
%% [{clause,1,
%% [{var,1,'Name'}],
%% [],
%% [{call,1,
%% {remote,1,{atom,1,io},{atom,1,format}},
%% [{string,1,"Hello, ~s!~n"},
%% {cons,1,{var,1,'Name'},{nil,1}}]}]}]}forms:to_abstract("-export([hello/1]).").
%% => {attribute,1,export,[{hello,1}]}from_abstract/1
Convert the provided form into its Erlang attribute or expression counterpart.
forms:from_abstract({attribute,1,export,[{hello,1}]}).
%% => "-export([hello/1])."forms:from_abstract({function,1,hello,1,
[{clause,1,
[{var,1,'Name'}],
[],
[{call,1,
{remote,1,{atom,1,io},{atom,1,format}},
[{string,1,"Hello, ~s!~n"},
{cons,1,{var,1,'Name'},{nil,1}}]}]}]}).
%% => "hello(Name) -> io:format(\"Hello, ~s!~n\", [Name])."Examples
Count how many times the anonymous variable (i.e., '_') is used in the lists module.
Forms = forms:read(lists),
forms:reduce(fun({var, _, '_'}, Count) -> Count + 1; (_, Count) -> Count end, 0, Forms).
%% => 57Easy. Isn't it? :-)