Files
n8n-openai-adapter/lib/elixir/src/elixir_code_server.erl
T
2014-12-23 22:26:04 +01:00

206 lines
7.0 KiB
Erlang

-module(elixir_code_server).
-export([call/1, cast/1]).
-export([start_link/0, init/1, handle_call/3, handle_cast/2,
handle_info/2, terminate/2, code_change/3]).
-behaviour(gen_server).
-define(timeout, 30000).
-record(elixir_code_server, {
argv=[],
loaded=[],
at_exit=[],
paths={[],[]},
mod_pool={[],0},
mod_ets=dict:new(),
compilation_status=[],
compiler_options=[{docs,true},{debug_info,true},{warnings_as_errors,false}],
erl_compiler_options=nil
}).
call(Args) ->
gen_server:call(?MODULE, Args, ?timeout).
cast(Args) ->
gen_server:cast(?MODULE, Args).
%% Callbacks
start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, ok, []).
init(ok) ->
%% We attempt to load those modules here so throughout
%% the codebase we can avoid code:is_loaded/1 checks.
_ = code:ensure_loaded('Elixir.Macro.Env'),
%% The table where we store module definitions
_ = ets:new(elixir_modules, [set, protected, named_table, {read_concurrency, true}]),
{ok, #elixir_code_server{}}.
handle_call({defmodule, Pid, Tuple}, _From, Config) ->
{Ref, New} = defmodule(Pid, Tuple, Config),
{reply, Ref, New};
handle_call({undefmodule, Ref}, _From, Config) ->
{reply, ok, undefmodule(Ref, Config)};
handle_call({acquire, Path}, From, Config) ->
Current = Config#elixir_code_server.loaded,
case orddict:find(Path, Current) of
{ok, true} ->
{reply, loaded, Config};
{ok, {Ref, List}} when is_list(List), is_reference(Ref) ->
Queued = orddict:store(Path, {Ref, [From|List]}, Current),
{reply, {queued, Ref}, Config#elixir_code_server{loaded=Queued}};
error ->
Queued = orddict:store(Path, {make_ref(), []}, Current),
{reply, proceed, Config#elixir_code_server{loaded=Queued}}
end;
handle_call(loaded, _From, Config) ->
{reply, [F || {F, true} <- Config#elixir_code_server.loaded], Config};
handle_call(at_exit, _From, Config) ->
{reply, Config#elixir_code_server.at_exit, Config};
handle_call(flush_at_exit, _From, Config) ->
{reply, Config#elixir_code_server.at_exit, Config#elixir_code_server{at_exit=[]}};
handle_call(argv, _From, Config) ->
{reply, Config#elixir_code_server.argv, Config};
handle_call(compiler_options, _From, Config) ->
{reply, Config#elixir_code_server.compiler_options, Config};
handle_call({compilation_status, CompilerPid}, _From, Config) ->
CompilationStatusList = Config#elixir_code_server.compilation_status,
CompilationStatusListNew = orddict:erase(CompilerPid, CompilationStatusList),
CompilationStatus = orddict:fetch(CompilerPid, CompilationStatusList),
{reply, CompilationStatus,
Config#elixir_code_server{compilation_status=CompilationStatusListNew}};
handle_call(retrieve_module_name, _From, Config) ->
case Config#elixir_code_server.mod_pool of
{[H|T], Counter} ->
{reply, module_tuple(H), Config#elixir_code_server{mod_pool={T,Counter}}};
{[], Counter} ->
{reply, module_tuple(Counter), Config#elixir_code_server{mod_pool={[],Counter+1}}}
end;
handle_call(erl_compiler_options, _From, Config) ->
case Config#elixir_code_server.erl_compiler_options of
nil ->
Opts = erl_compiler_options(),
{reply, Opts, Config#elixir_code_server{erl_compiler_options=Opts}};
Opts ->
{reply, Opts, Config}
end;
handle_call(paths, _From, Config) ->
{reply, Config#elixir_code_server.paths, Config};
handle_call(Request, _From, Config) ->
{stop, {badcall, Request}, Config}.
handle_cast({at_exit, AtExit}, Config) ->
{noreply, Config#elixir_code_server{at_exit=[AtExit|Config#elixir_code_server.at_exit]}};
handle_cast({argv, Argv}, Config) ->
{noreply, Config#elixir_code_server{argv=Argv}};
handle_cast({compiler_options, Options}, Config) ->
Final = orddict:merge(fun(_,_,V) -> V end, Config#elixir_code_server.compiler_options, Options),
{noreply, Config#elixir_code_server{compiler_options=Final}};
handle_cast({register_warning, CompilerPid}, Config) ->
CompilationStatusCurrent = Config#elixir_code_server.compilation_status,
CompilationStatusNew = orddict:store(CompilerPid, error, CompilationStatusCurrent),
case orddict:find(warnings_as_errors, Config#elixir_code_server.compiler_options) of
{ok, true} -> {noreply, Config#elixir_code_server{compilation_status=CompilationStatusNew}};
_ -> {noreply, Config}
end;
handle_cast({reset_warnings, CompilerPid}, Config) ->
CompilationStatusCurrent = Config#elixir_code_server.compilation_status,
CompilationStatusNew = orddict:store(CompilerPid, ok, CompilationStatusCurrent),
{noreply, Config#elixir_code_server{compilation_status=CompilationStatusNew}};
handle_cast({loaded, Path}, Config) ->
Current = Config#elixir_code_server.loaded,
case orddict:find(Path, Current) of
{ok, true} ->
{noreply, Config};
{ok, {Ref, List}} when is_list(List), is_reference(Ref) ->
_ = [Pid ! {elixir_code_server, Ref, loaded} || {Pid, _Tag} <- lists:reverse(List)],
Done = orddict:store(Path, true, Current),
{noreply, Config#elixir_code_server{loaded=Done}};
error ->
Done = orddict:store(Path, true, Current),
{noreply, Config#elixir_code_server{loaded=Done}}
end;
handle_cast({unload_files, Files}, Config) ->
Current = Config#elixir_code_server.loaded,
Unloaded = lists:foldl(fun(File, Acc) -> orddict:erase(File, Acc) end, Current, Files),
{noreply, Config#elixir_code_server{loaded=Unloaded}};
handle_cast({return_module_name, H}, #elixir_code_server{mod_pool={T,Counter}} = Config) ->
{noreply, Config#elixir_code_server{mod_pool={[H|T],Counter}}};
handle_cast({paths, PA, PZ}, #elixir_code_server{} = Config) ->
{noreply, Config#elixir_code_server{paths={PA,PZ}}};
handle_cast(Request, Config) ->
{stop, {badcast, Request}, Config}.
handle_info({'DOWN', Ref, process, _Pid, _Reason}, Config) ->
{noreply, undefmodule(Ref, Config)};
handle_info(_Msg, Config) ->
{noreply, Config}.
terminate(_Reason, _Config) ->
ok.
code_change(_Old, Config, _Extra) ->
{ok, Config}.
module_tuple(I) ->
{list_to_atom("elixir_compiler_" ++ integer_to_list(I)), I}.
defmodule(Pid, Tuple, #elixir_code_server{mod_ets=ModEts} = Config) ->
ets:insert(elixir_modules, Tuple),
Ref = erlang:monitor(process, Pid),
Mod = erlang:element(1, Tuple),
{Ref, Config#elixir_code_server{mod_ets=dict:store(Ref, Mod, ModEts)}}.
undefmodule(Ref, #elixir_code_server{mod_ets=ModEts} = Config) ->
case dict:find(Ref, ModEts) of
{ok, Mod} ->
ets:delete(elixir_modules, Mod),
Config#elixir_code_server{mod_ets=dict:erase(Ref, ModEts)};
error ->
Config
end.
erl_compiler_options() ->
Key = "ERL_COMPILER_OPTIONS",
case os:getenv(Key) of
false -> [];
Str when is_list(Str) ->
case erl_scan:string(Str) of
{ok,Tokens,_} ->
case erl_parse:parse_term(Tokens ++ [{dot, 1}]) of
{ok,List} when is_list(List) -> List;
{ok,Term} -> [Term];
{error,_Reason} ->
io:format("Ignoring bad term in ~ts\n", [Key]),
[]
end;
{error, {_,_,_Reason}, _} ->
io:format("Ignoring bad term in ~ts\n", [Key]),
[]
end
end.