How to import constants from Erlang header file?

Hi all,

How can I import the constants from a shared and mutable “.hrl” file at runtime?

Erlang “.hrl” file:

-define(X, valx).
-define(Y, valy).
....
path
|> File.read!()
|> Enum.filter(&String.starts_with(&1, "-define("))
|> Enum.map(&parse_and_extract_values/1)
|> Map.new()

Roughly like this.

But why is that file “mutable”? In my opinion the proper way where to have a single erlang module that imports those defines and exposes them as functions…

2 Likes

Heh, that’s more of a hack than a proper solution. @fun2src you can define an erlang module in src/ and have functions inside that return the constants. This will not impose a meaningful performance penalty since the constants will still be part of the constants memory space.

1 Like

Well, he explicitely asked for reading a mutable file at runtime, therefore I started with the hack, suggesting to use exactly the way you describe as well…

1 Like

Ah, I missed the runtime bit. That does pose a bit of an issue! I think I might still opt for reading and evaling the erlang file at runtime even since then it isn’t up to me to sort out what to do with the -define string. Regardless I think we’re in agreement that the runtime part of this is odd, @fun2src can you elaborate on that part?

The file is generated and read by Erlang applications (in production) to which I do not have access. I can only access the “.hrl” file in the shared directory. The file mutates four times a day.

So it would be something like this:

init([]) ->
  {ok, erlang:send_after(1, self(), check_hrl_file)}.

handle_info(check_hrl_file, CurrentTimer) ->
  erlang:cancel_timer(CurrentTimer),
  load_list_of_constants(?HDR_FILE),
  {noreply, erlang:send_after(2.16e+7, self(), check_hrl_file)}.

and the suggestion (@NobbZ):

def load_list_of_constants(path) do
  path
  |> File.read!()
  |> Enum.filter(&String.starts_with(&1, "-define("))
  |> Enum.map(&parse_and_extract_values/1)
  |> Map.new()
end

I would like to know what is the difference (in terms of efficiency) between

the proper way where to have a single erlang module that imports those defines and exposes them as functions…

and

path
|> File.read!()
|> Enum.filter(&String.starts_with(&1, “-define(”))
|> Enum.map(&parse_and_extract_values/1)
|> Map.new()

Could I implement the proper way using hot code swapping?

mv file.hrl path/elixir-app/src
compile(erlang_module)
Module:code_change

Thank you so much! @NobbZ @benwilson512

It would be better to use :epp_dodger.parse_file/1:

with {:ok, forms} <- :epp_dodger.parse_file(file) do
  for {:tree, :attribute, _, {_, {:atom, _, :define}, [{_, _, name}, {_, _, value}]}} <- forms,
    do: {name, value}
end
8 Likes

Beautiful @hauleth. Thank you! :smile:

1 Like

Great discussion, I’m struggling with using the humongous wx.hrl in my Elixir project and have been using this .ex module so far: https://gist.github.com/dominicletz/5da3637275d81a421bc6aa72ae5c31c7

So I do like the idea of just parsing “wx.hrl” at runtime but it has some expressions on the right hand of the define that make my curious about your implementation of parse_and_extract_values

Here some examples from wx.hrl:

-define(wxGA_VERTICAL, ?wxVERTICAL).
-define(wxBITMAP_TYPE_TIF, (?wxBITMAP_TYPE_BMP_RESOURCE+9)).
-define(wxGAUGE_EMULATE_INDETERMINATE_MODE, wxe_util:get_const(wxGAUGE_EMULATE_INDETERMINATE_MODE)).
-define(wxDialog_ButtonSizerFlags, (?wxOK bor ?wxCANCEL bor ?wxYES bor ?wxNO bor ?wxHELP bor ?wxNO_DEFAULT)).

Anyone has a lib that can handle all of these?