defmodule Formular.Client.Compiler do
@moduledoc """
Compiler for making the string code into an Elixir module.
Here we use Agent to prevent concurrent compilation.
"""
alias Formular.Client.Config
alias Formular.Client.PubSub
use Agent
require Logger
def start_link(opts),
do: Agent.start_link(fn -> nil end, Keyword.put_new(opts, :name, __MODULE__))
def handle_new_code_revision({pid, ref}, name, code, config) do
Agent.update(__MODULE__, fn _ ->
with {mod, name, context} = formula_cfg <- Config.formula_config(config, name) do
case config do
%{compiler: {m, f, a}} ->
apply(m, f, [{code, name, mod, context} | a])
|> report_result({pid, ref}, formula_cfg, code)
%{compiler: f} when is_function(f, 1) ->
apply(f, [{code, name, mod, context}])
|> report_result({pid, ref}, formula_cfg, code)
end
end
nil
end)
end
def compile({code, name, mod, context}) do
Logger.info(["Recompiling formula: #{name} to module #{mod}. Code: #{inspect(code)}"])
temporarily_disable_compilation_warning(fn ->
do_compile(code, name, mod, context)
end)
end
defp do_compile(code, _name, mod, context) do
Formular.compile_to_module!(code, mod, context)
:ok
rescue
e in CompileError ->
{:error, e}
end
defp temporarily_disable_compilation_warning(f) do
prev_mc_config = Code.get_compiler_option(:ignore_module_conflict)
Code.put_compiler_option(:ignore_module_conflict, true)
ret = f.()
Code.put_compiler_option(:ignore_module_conflict, prev_mc_config)
ret
end
defp report_result(:ok, {pid, ref}, {mod, name, context}, code) do
broadcast_success(name, code, mod, context)
send(pid, {ref, :ok})
end
defp report_result({:error, e}, {pid, ref}, {mod, name, context}, code) do
broadcast_failure(e, name, code, mod, context)
send(pid, {ref, {:error, e}})
end
defp broadcast_success(name, code, mod, context) do
PubSub.dispatch({:compiled, name, code, mod: mod, context: context})
end
defp broadcast_failure(err, name, code, mod, context) do
PubSub.dispatch({:compile_failed, name, err, code, mod: mod, context: context})
end
end