Rewrites Erlang source (.erl) so that vanilla OTP calls
(gen_server:call, erlang:spawn, Pid ! Msg, bare receive)
go through Lockstep's controller. Mirrors Lockstep.Rewriter but
on Erlang's abstract format instead of Elixir's macro AST.
Why
Most of the BEAM ecosystem's foundational libraries are pure Erlang:
:pg, gen_stage, gen_statem, :dets, sleeplocks, the Erlang
internals of :gen_server, :supervisor, etc. Without an Erlang
rewriter, libraries that depend on these (Phoenix.PubSub, GenStage,
Cachex's transaction layer, libcluster, ...) fall outside Lockstep.
This module is the bridge.
Mappings
vanilla Erlang rewritten
------- ---------
gen_server:call(S, M) 'Elixir.Lockstep.GenServer':call(S, M)
gen_server:cast(S, M) 'Elixir.Lockstep.GenServer':cast(S, M)
gen_server:start_link(M, A, O) 'Elixir.Lockstep.GenServer':start_link(M, A, O)
gen_server:reply(F, R) 'Elixir.Lockstep.GenServer':reply(F, R)
gen_server:stop(S, R, T) 'Elixir.Lockstep.GenServer':stop(S, R, T)
erlang:spawn(F) 'Elixir.Lockstep':spawn(F)
erlang:spawn_link(F) 'Elixir.Lockstep':spawn_link(F)
erlang:send(D, M) 'Elixir.Lockstep':send(D, M)
erlang:monitor(process, P) 'Elixir.Lockstep':monitor(P)
erlang:demonitor(R) 'Elixir.Lockstep':demonitor(R, [])
erlang:demonitor(R, O) 'Elixir.Lockstep':demonitor(R, O)
erlang:link(P) 'Elixir.Lockstep':link(P)
erlang:unlink(P) 'Elixir.Lockstep':unlink(P)
erlang:process_flag(F, V) 'Elixir.Lockstep':flag(F, V)
erlang:is_process_alive(P) 'Elixir.Lockstep':'alive?'(P)
erlang:send_after(T, D, M) 'Elixir.Lockstep':send_after(D, M, T) ** arg reorder! **
erlang:cancel_timer(R) 'Elixir.Lockstep':cancel_timer(R)
Pid ! Msg 'Elixir.Lockstep':send(Pid, Msg)
receive Cls end case 'Elixir.Lockstep':recv_first(matcher) of Cls end
receive Cls after T -> Body end timer + recv_first dispatch (see below)Also handles bare BIFs (spawn(F) without erlang: prefix), which
are the auto-imported BIFs from the Erlang module.
Limitations (v0.1)
gen_server:start_link({local, Name}, M, A, O)4-arg form not yet supported. Use the 3-arg form (no name) and pass the pid around explicitly.- Pre-bound variables in receive patterns lose their "pin" semantics (the matcher does structural match only). Most code doesn't rely on this; use guards explicitly if you do.
gen_statem,supervisor,:pgare not yet wrapper-rewritten individually. They could be added by extending the call mappings.
Summary
Functions
Rewrite a .erl file's forms and compile directly to a .beam
binary. Skips the .erl round-trip. Returns {:ok, module, binary}
or {:error, errors, warnings}.
Read .erl file, parse, rewrite, write back to output_path.
Returns {:ok, output_path} on success.
Walk a list of Erlang forms and rewrite. Returns the new forms.
Functions
@spec rewrite_and_compile( Path.t(), keyword() ) :: {:ok, atom(), binary()} | {:error, list(), list()}
Rewrite a .erl file's forms and compile directly to a .beam
binary. Skips the .erl round-trip. Returns {:ok, module, binary}
or {:error, errors, warnings}.
Read .erl file, parse, rewrite, write back to output_path.
Returns {:ok, output_path} on success.
Walk a list of Erlang forms and rewrite. Returns the new forms.