Current section
Files
Jump to
Current section
Files
src/otpcl_repl.erl
% @doc OTPCL shell/REPL.
-module(otpcl_repl).
-if(?OTP_RELEASE >= 21).
-define(CATCH(Type, Reason), Type:Reason:Stacktrace).
-define(HANDLE(Type, Reason),
show_error(Type, Reason, Stacktrace),
read(State, ps1(State), "")).
-else.
-define(CATCH(Type, Reason), Type:Reason).
-define(HANDLE(Type, Reason),
show_error(Type, Reason, erlang:get_stacktrace()),
read(State, ps1(State), "")).
-endif.
-export([start/0, start/1, simple_shell/1, shell_init_state/1]).
% @doc Starts the OTPCL shell preloaded with commands useful for general
% shell-like usage (`ls', `cd', etc.).
start() ->
start(shell_init_state(otpcl_env:default_state())).
% @doc Starts the OTPCL shell with a user-provided custom state. Useful for
% implementing customized shells (e.g. restricted command shells for embedding
% in other OTP applications).
start(State) ->
spawn(?MODULE, simple_shell, [State]).
% @doc A simple read-parse-eval loop.
simple_shell(State) ->
process_flag(trap_exit, true),
show_banner(State),
read(State, ps1(State), "").
read(State, Prompt, Acc) ->
parse(State, Acc ++ io:get_line(Prompt)).
parse(State, Input) ->
try otpcl_parse:parse(Input) of
{ok, Tree, []} ->
eval(State, Tree);
{error, {expected, _}, _, _, _} ->
read(State, ps2(State), Input);
{error, {unexpected, Char}, _, _, _} ->
show_error(error, {unexpected, [Char]}),
read(State, ps1(State), "");
{error, Reason, _, _, _} ->
show_error(error, Reason),
read(State, ps1(State), "")
catch
?CATCH(Anything, Else) ->
?HANDLE(Anything, Else)
end.
eval(State, Tree) ->
try otpcl_eval:interpret(Tree, State) of
{RetVal, NewState} ->
show_retval(RetVal),
read(NewState, ps1(NewState), "")
catch
?CATCH(error, {badmatch,{error,{Rsn,Val},_}}) ->
?HANDLE(Rsn, Val);
?CATCH(Anything, Else) ->
?HANDLE(Anything, Else)
end.
%% Prompts.
ps1(State) ->
case otpcl_meta:get(['PS1'], State) of
{Prompt, State} ->
Prompt;
_ ->
"otpcl> "
end.
ps2(State) ->
case otpcl_meta:get(['PS2'], State) of
{Prompt, State} ->
Prompt;
_ ->
" ...> "
end.
%% Show stuff
show_banner(_) ->
io:format("OTPCL Shell (WIP!)~n~n").
show_retval(RetVal) ->
io:format("~p~n", [RetVal]).
show_error(Step, Reason) ->
io:format("~p: ~p~n", [Step, Reason]).
show_error(Step, Reason, Stacktrace) ->
io:format("~p: ~p~nStacktrace:~n", [Step, Reason]),
show_trace(Stacktrace).
show_trace([{Mod,Fun,Arity,Locs}|Rem]) ->
io:format(" ~p:~p/~p~n", [Mod,Fun,Arity]),
show_trace_locs(Locs),
show_trace(Rem);
show_trace([]) ->
ok.
show_trace_locs([{Type,Val}|Rem]) ->
io:format(" ~p: ~p~n", [Type,Val]),
show_trace_locs(Rem);
show_trace_locs([]) ->
ok.
% @doc Given an input state, returns an output state with pre-imported shell
% commands and sensible default prompts.
shell_init_state(State0) ->
{_, State1} = otpcl_meta:import([otpcl_shell], State0),
{_, State2} = otpcl_meta:set(['PS1', "otpcl> "], State1),
{_, State3} = otpcl_meta:set(['PS2', " ...> "], State2),
State3.