views:

89

answers:

2

I'm trying to compile a module from forms, with included header files. First, if I have the module in a source file, everything works fine.

user.hrl

-record(user, {name :: string()}).

zed.erl

-module(zed).
-export([f/1]).
-include("user.hrl").

f(User) ->
   User#user.name.

shell

1> compile:file(zed, [return]). 
{ok,zed,[]}
2> rr("user.hrl").
[user]
3> zed:f(#user{name = "Zed"}).
"Zed"

If I try to compile the same module from forms, I get an undefined record error. Playing with {i, Dir} and other options does not help.

shell

1> Forms = [{attribute,1,module,zed},
1>  {attribute,1,export,[{f,1}]},
1>  {attribute,1,include,"user.hrl"},
1>  {function,1,f,1,
1>      [{clause,1,
1>           [{var,1,'User'}], [],
1>           [{record_field,1,
1>                {var,1,'User'},
1>                user,
1>                {atom,1,name}}]}]}].
  ....
2> compile:forms(Forms, [return]).
{error,[{".",[{1,erl_lint,{undefined_record,user}}]}],[]}

What am I doing wrong?

+3  A: 

Include files, and macros, are handled by epp, the erlang preprocessor. The compile:forms/1 function assumes that all preprocessing has already been done so it will process the {attribute,1,include,...} as a, for it, unknown attribute. The same with macros.

There is today no way of running the preprocessor on list of forms. You will have to explicitly include the file and do macro processing. It might also seem a little strange to take some of your input from forms and some from files.

rvirding
Thanks. I was mislead by the documentation on that. So basically I can either 1) get the headers myself, parse them into forms, and replace the attributes with them, or 2) pretty print my forms to a source file, and then compile.
Zed
That's about it. Or fix epp. :-)
rvirding
A: 

We did this for a project only for records, but it requires a couple of things:

  1. You must have a module loaded in run time which have the .hrl-files you need included (the beam for this module must also be available on the path).
  2. The module has to be compiled with debug info (+debug_info to the compiler or with [debug_info] as option argument to c/2).
  3. You have to insert the .hrl-record definitions into your forms yourself.

Here's how to do it:

First create a module which includes the .hrl-file:

-module(my_hrl).

-include("my_hrl.hrl").

-export([records/0]).

records() ->
    {_Module, _Beam, FilePath} = code:get_object_code(?MODULE),
    {ok, {_, [{abstract_code, {_, AC}}]}} =
        beam_lib:chunks(FilePath, [abstract_code]),
    [R || {attribute, _, record, _} = R <- AC].

This will give you a module whose include/0 function will give you a list of the abstract code for all the records in that module (coming from the .hrl-files that where inluded).

This way of doing it could of course be used for other attributes than the record attribute as well (or even functions in the .hrl-files).

Once you have the list of record attributes you just append them to your forms:

Forms = [{attribute,1,module,zed},
         {attribute,1,export,[{f,1}]}]

        ++ my_hrl:records() ++

        [{function,1,f,1,
          [{clause,1,
            [{var,1,'User'}], [],
            [{record_field,1,
              {var,1,'User'},
              user,
              {atom,1,name}}]}]}].
Adam Lindberg
Thanks for sharing. That's an interesting "workaround" of the problem :)
Zed
Yes it is! It's nice because it always gives the latest version of the records, even when the .hrl-file has change (because my_hrl.erl get's recompiled).
Adam Lindberg