I’m building a module from a csv. If I set it up to define methods as I parse through the file, it works fine:
defmodule ExModbus.SunSpec.Fronius do
@external_resource Path.join([__DIR__, "sunspec.csv"])
for line <- File.stream!(Path.join([__DIR__, "sunspec.csv"]), [], :line) |> Stream.drop(1) do
[start, _end, size, _access, _func_codes, name, desc, type, _units, _scale_factor, _range] = String.split(line, ",")
start = String.to_integer(start)
size = String.to_integer(size)
name = String.to_atom(String.downcase(name))
def unquote(name)(pid, slave_id \\ 1) do
case ExModbus.Client.read_data(pid, slave_id, unquote(start - 1 + 40000), unquote(size)) do
%{data: {:read_holding_registers, data}, transaction_id: transaction_id, unit_id: unit_id} ->
data = data |> ExModbus.Types.convert_type(unquote(type))
{:ok, %{data: data, transaction_id: transaction_id, slave_id: unit_id}}
other ->
IO.puts inspect other
end
end
end
end
However, my CSV also includes a description, and I want to use it for documentation @doc unquote(desc)
This doesn’t work though, because I’m not in a quote block. So I tried this:
defmodule ExModbus.SunSpec.Fronius do
@external_resource Path.join([__DIR__, "sunspec.csv"])
ast = for line <- File.stream!(Path.join([__DIR__, "sunspec.csv"]), [], :line) |> Stream.drop(1) do
[start, _end, size, _access, _func_codes, name, desc, type, _units, _scale_factor, _range] = String.split(line, ",")
start = String.to_integer(start)
size = String.to_integer(size)
name = Macro.underscore(name)
quote do
def unquote(name)(pid, slave_id \\ 1) do
case ExModbus.Client.read_data(pid, slave_id, unquote(start - 1 + 40000), unquote(size)) do
%{data: {:read_holding_registers, data}, transaction_id: transaction_id, unit_id: unit_id} ->
data = data |> ExModbus.Types.convert_type(unquote(type))
{:ok, %{data: data, transaction_id: transaction_id, slave_id: unit_id}}
other ->
IO.puts inspect other
end
end
end
end
IO.puts inspect Macro.to_string(ast)
Code.eval_quoted ast
end
My AST looks reasonable, but it dies at the last line with:
** (ArgumentError) cannot invoke def/2 outside module
(elixir) lib/kernel.ex:4436: Kernel.assert_module_scope/3
(elixir) lib/kernel.ex:3433: Kernel.define/4
(elixir) expanding macro: Kernel.def/2
nofile:1: (file)
It looks like I’m running this in a module, but apparently not.
The docs say Code.eval_quoted is bad form anyway, so I tried replacing it with this:
quote do
unquote(ast)
end
This compiles, but none of my methods are defined. It seems wrong that my AST is a list, and not a tree, but the same is true for the Translator module from Metaprogramming Elixir, and it’s still working. I suppose this isn’t the top of the tree anyway. But I’m out of guesses for the moment. Any suggestions?