%% -*- mode: erlang; indent-tabs-mode: nil -*- %% Copyright (c) 2008-2024 Robert Virding %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. %% You may obtain a copy of the License at %% %% http://www.apache.org/licenses/LICENSE-2.0 %% %% Unless required by applicable law or agreed to in writing, software %% distributed under the License is distributed on an "AS IS" BASIS, %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %% See the License for the specific language governing permissions and %% limitations under the License. %% File : lfe_shell.erl %% Author : Robert Virding %% Purpose : A simple Lisp Flavoured Erlang shell. %% We keep three environments: the current environment; the saved %% environment which contains the environment from before a slurp; and %% the base environment which contains the predefined shell variables, %% functions and macros. The base environment is used when we need to %% revert to an "empty" environment. The save environment is used to %% store the current environment when we 'slurp' a file which we can %% revert back to when we do an 'unslurp'. -module(lfe_shell). -export([start/0,start/1,server/0,server/1, run_script/2,run_script/3, run_strings/1,run_strings/2,run_string/1,run_string/2, new_state/2,new_state/3,upd_state/3]). %% Useful for the LFE rebar3 plugin -export([banner/0,banner/1,banner/2]). %% The shell commands which generally callable. -export([c/1,c/2,cd/1,ec/1,ec/2,ep/1,ep/2,epp/1,epp/2,flush/0,help/0, h/1,h/2,h/3,i/0,i/1,i/3,l/1,ls/1,clear/0,m/0,m/1,memory/0,memory/1, nregs/0,pid/3,p/1,p/2,pp/1,pp/2,pwd/0,q/0,regs/0,uptime/0,exit/0]). -include("lfe.hrl"). -include("lfe_docs.hrl"). %% Coloured strings for the LFE banner, red, green, yellow and blue. -define(RED(Str), "\e[31m" ++ Str ++ "\e[0m"). -define(GRN(Str), "\e[1;32m" ++ Str ++ "\e[0m"). -define(YLW(Str), "\e[1;33m" ++ Str ++ "\e[0m"). -define(BLU(Str), "\e[1;34m" ++ Str ++ "\e[0m"). -define(BOLD(Str), "\e[1m" ++ Str ++ "\e[0m"). %% -compile([export_all]). %% Implement our own lists functions to get around stacktrace printing %% problems. foldl(F, Accu, [Hd|Tail]) -> foldl(F, F(Hd, Accu), Tail); foldl(F, Accu, []) when is_function(F, 2) -> Accu. %% Shell state. -record(state, {curr,save,base, %Current, save and base env slurp=false}). %Are we slurped? %% run_script(File, Args) -> {Value,State}. %% run_string(File, Args, State) -> {Value,State}. -spec run_script(_, _) -> no_return(). -spec run_script(_, _, _) -> no_return(). run_script(File, Args) -> run_script(File, Args, new_state(File, Args)). run_script(File, Args, St0) -> St1 = upd_state(File, Args, St0), run_file([File], St1). %% run_strings(Strings) -> {Value,State}. %% run_strings(Strings, State) -> {Value,State}. run_strings(Strings) -> run_strings(Strings, new_state("lfe", [])). run_strings(Strings, St) -> lists:foldl(fun (S, St0) -> {_,St1} = run_string(S, St0), St1 end, St, Strings). %% run_string(String) -> {Value,State}. %% run_string(String, State) -> {Value,State}. run_string(String) -> run_string(String, new_state("lfe", [])). run_string(String, St0) -> St1 = upd_state(String, [], St0), case read_script_string(String) of {ok,Forms} -> run_loop(Forms, St1); {error,E} -> slurp_errors("lfe", [E]), {error,St1} end. %% start() -> Pid. %% start(State) -> Pid. start() -> spawn(fun () -> server() end). start(St) -> spawn(fun () -> server(St) end). %% server() -> no_return(). %% server(State) -> no_return(). server() -> %% Create a default base env of predefined shell variables with %% default nil bindings and basic shell macros. St = new_state("lfe", []), server(St). server(St) -> process_flag(trap_exit, true), %Must trap exists display_banner(), %% Set shell io to use LFE expand in edlin, ignore error. io:setopts([{expand_fun,fun (B) -> lfe_edlin_expand:expand(B) end}]), Eval = start_eval(St), %Start an evaluator server_loop(Eval, St). %Run the loop server_loop(Eval0, St0) -> %% Read the form Prompt = prompt(), {Ret,Eval1} = read_expression(Prompt, Eval0, St0), case Ret of {ok,Form} -> {Eval2,St1} = shell_eval(Form, Eval1, St0), server_loop(Eval2, St1); {error,E} -> list_errors([E]), server_loop(Eval1, St0) end. %% shell_eval(From, Evaluator, State) -> {Evaluator,State}. %% Evaluate one shell expression. The evaluator may crash in which %% case we restart it and return the pid of the new evaluator. shell_eval(Form, Eval0, St0) -> Eval0 ! {eval_expr,self(),Form}, receive {eval_value,Eval0,_Value,St1} -> %% We actually don't use the returned value here. {Eval0,St1}; {eval_error,Eval0,{Class,Reason,StackTrace}} -> %% Eval errored out, get the exit signal. receive {'EXIT',Eval0,normal} -> ok end, report_exception(Class, Reason, StackTrace), Eval1 = start_eval(St0), {Eval1,St0}; {'EXIT',Eval0,Reason} -> %% Eval exited or was killed report_exception(exit, Reason, []), Eval1 = start_eval(St0), {Eval1,St0} end. %% prompt() -> Prompt. prompt() -> %% Don't bother flattening the list, no need. case is_alive() of true -> lfe_io:format1("~s~s~s", node_prompt()); false -> %% If a user supplied the ~node formatting option but the %% node is not actually alive, let's get rid of it P1 = user_prompt(), P2 = re:replace(P1, "~node", "", [{return, list}]), lfe_io:format1("~s", [P2]) end. node_prompt () -> Prompt = user_prompt(), Node = atom_to_list(node()), case re:run(Prompt, "~node") of nomatch -> ["(", Node, [")", Prompt]]; _ -> ["", re:replace(Prompt, "~node", Node, [{return, list}]), ""] end. user_prompt () -> %% Allow users to set a prompt with the -prompt flag; note that %% without the flag the default is "lfe> " and to obtain the %% old-style LFE prompt, use -prompt classic. case init:get_argument(prompt) of {ok, [[]]} -> [""]; {ok, [["classic"]]} -> ["> "]; {ok, [P]} -> P; _ -> ["lfe> "] end. report_exception(Class, Reason, Stack) -> %% Use LFE's simplified version of erlang shell's error %% reporting but which LFE prettyprints data. Skip = fun (M, _F, _A) -> %% Don't want to see these in stacktrace. (M == lfe_eval) or (M == ?MODULE) end, Format = fun (T, I) -> lfe_io:prettyprint1(T, 15, I, 80) end, Cs = lfe_error:format_exception(Class, Reason, Stack, Skip, Format, 1), io:put_chars("** " ++ Cs), %Make it more note worthy io:nl(). %% read_expression(Prompt, Evaluator, State) -> {Return,Evaluator}. %% Start a reader process and wait for an expression. We are cunning %% here and just use the exit reason to pass the expression. We must %% also handle the evaluator dying and restart it. read_expression(Prompt, Eval, St) -> Read = fun () -> %% io:put_chars(Prompt), %% Ret = lfe_io:read_line(), Ret = lfe_io:read_line(Prompt), exit(Ret) end, Rdr = spawn_link(Read), read_expression_1(Rdr, Eval, St). read_expression_1(Rdr, Eval, St) -> %% Eval is not doing anything here, so exits from it can only %% occur if it was terminated by a signal from another process. receive {'EXIT',Rdr,Ret} -> {Ret,Eval}; {'EXIT',Eval,{Reason,Stk}} -> report_exception(error, Reason, Stk), read_expression_1(Rdr, start_eval(St), St); {'EXIT',Eval,Reason} -> report_exception(exit, Reason, []), read_expression_1(Rdr, start_eval(St), St) end. banner() -> banner(lfe:version()). banner(Vsn) -> banner(Vsn, quit_message()). banner(Vsn, QuitMsg) -> ?GRN(" ..-~") ++ ?YLW(".~_") ++ ?GRN("~---..") ++ "\n" ++ ?GRN(" ( ") ++ ?YLW("\\\\") ++ ?GRN(" )") ++ " | A Lisp-2+ on the Erlang VM\n" ++ ?GRN(" |`-.._") ++ ?YLW("/") ++ ?GRN("_") ++ ?YLW("\\\\") ++ ?GRN("_.-':") ++ " | Type " ++ ?GRN("(help)") ++ " for usage info.\n" ++ ?GRN(" | ") ++ ?RED("g") ++ ?GRN(" |_ \\") ++ " |\n" ++ ?GRN(" | ") ++ ?RED("n") ++ ?GRN(" | |") ++ " | Docs: " ++ ?BLU("http://docs.lfe.io/") ++ "\n" ++ ?GRN(" | ") ++ ?RED("a") ++ ?GRN(" / /") ++ " | Source: " ++ ?BLU("http://github.com/lfe/lfe") ++ "\n" ++ ?GRN(" \\ ") ++ ?RED("l") ++ ?GRN(" |_/") ++ " |\n" ++ ?GRN(" \\ ") ++ ?RED("r") ++ ?GRN(" /") ++ " | LFE v" ++ Vsn ++ " " ++ QuitMsg ++ "\n" ++ ?GRN(" `-") ++ ?RED("E") ++ ?GRN("___.-'") ++ "\n\n". display_banner() -> %% When LFE is called with -noshell, we want to skip the banner. Also, there may be %% circumstances where the shell is desired, but the banner needs to be disabled, %% thus we want to support both use cases. case init:get_argument(noshell) of error -> case init:get_argument(nobanner) of error -> io:put_chars(banner()); _ -> false end; _ -> false end. quit_message() -> %% We can update this later to check for env variable settings for %% shells that require a different control character to abort, such %% as jlfe. "(abort with ^G)". %% new_state(ScriptName, Args [,Env]) -> State. %% Generate a new shell state with all the default functions, macros %% and variables. new_state(Script, Args) -> new_state(Script, Args, lfe_env:new()). new_state(Script, Args, Env0) -> Env1 = lfe_env:add_vbinding('script-name', Script, Env0), Env2 = lfe_env:add_vbinding('script-args', Args, Env1), Base0 = add_shell_functions(Env2), Base1 = add_shell_macros(Base0), Base2 = add_shell_vars(Base1), #state{curr=Base2,save=Base2,base=Base2,slurp=false}. upd_state(Script, Args, #state{curr=Curr,save=Save,base=Base}=St) -> %% Update an environment with with script name and args. Upd = fun (E0) -> E1 = lfe_env:add_vbinding('script-name', Script, E0), lfe_env:add_vbinding('script-args', Args, E1) end, St#state{curr=Upd(Curr),save=Upd(Save),base=Upd(Base)}. add_shell_vars(Env0) -> %% Add default shell expression variables. Env1 = foldl(fun (Symb, E) -> lfe_env:add_vbinding(Symb, [], E) end, Env0, ['+','++','+++','-','*','**','***']), lfe_env:add_vbinding('$ENV', Env1, Env1). %This gets it all update_shell_vars(Form, Value, Env0) -> Env1 = foldl(fun ({Symb,Val}, E) -> lfe_env:add_vbinding(Symb, Val, E) end, Env0, [{'+++',lfe_env:fetch_vbinding('++', Env0)}, {'++',lfe_env:fetch_vbinding('+', Env0)}, {'+',Form}, {'***',lfe_env:fetch_vbinding('**', Env0)}, {'**',lfe_env:fetch_vbinding('*', Env0)}, {'*',Value}]), %% Be cunning with $ENV, remove self references so it doesn't grow %% indefinitely. Env2 = lfe_env:del_vbinding('$ENV', Env1), lfe_env:add_vbinding('$ENV', Env2, Env2). add_shell_functions(Env0) -> Fs = [ {cd,1,[lambda,[d],[':',lfe_shell,cd,d]]}, {ep,1,[lambda,[e],[':',lfe_shell,ep,e]]}, {ep,2,[lambda,[e,d],[':',lfe_shell,ep,e,d]]}, {epp,1,[lambda,[e],[':',lfe_shell,epp,e]]}, {epp,2,[lambda,[e,d],[':',lfe_shell,epp,e,d]]}, {h,0,[lambda,[],[':',lfe_shell,help]]}, {h,1,[lambda,[m], [':',lfe_shell,h,m]]}, {h,2,[lambda,[m,f], [':',lfe_shell,h,m,f]]}, {h,3,[lambda,[m,f,a], [':',lfe_shell,h,m,f,a]]}, {help,0,[lambda,[],[':',lfe_shell,help]]}, {i,0,[lambda,[],[':',lfe_shell,i]]}, {i,1,[lambda,[ps],[':',lfe_shell,i,ps]]}, {i,3,[lambda,[x,y,z],[':',lfe_shell,i,x,y,z]]}, {clear,0,[lambda,[],[':',lfe_shell,clear]]}, {pid,3,[lambda,[i,j,k],[':',lfe_shell,pid,i,j,k]]}, {p,1,[lambda,[e],[':',lfe_shell,p,e]]}, {p,2,[lambda,[e,d],[':',lfe_shell,p,e,d]]}, {pp,1,[lambda,[e],[':',lfe_shell,pp,e]]}, {pp,2,[lambda,[e,d],[':',lfe_shell,pp,e,d]]}, {pwd,0,[lambda,[],[':',lfe_shell,pwd]]}, {q,0,[lambda,[],[':',lfe_shell,exit]]}, {flush,0,[lambda,[],[':',lfe_shell,flush]]}, {regs,0,[lambda,[],[':',lfe_shell,regs]]}, {nregs,0,[lambda,[],[':',lfe_shell,nregs]]}, {memory,0,[lambda,[],[':',lfe_shell,memory]]}, {memory,1,[lambda,[t],[':',lfe_shell,memory,t]]}, {uptime,0,[lambda,[],[':',lfe_shell,uptime]]}, {exit,0,[lambda,[],[':',lfe_shell,exit]]} ], %% Any errors here will crash shell startup! Add = fun ({N,Ar,Def}, E) -> lfe_eval:add_dynamic_func(N, Ar, Def, E) end, Env1 = foldl(Add, Env0, Fs), Env1. %% Last clause in match-lambda macro to catch-all as undefined function. -define(UNDEF_MATCH_FUNC(Name), [[args,'ENV'],?BQ([error,{undefined_func,{Name,?C([length,args])}}])]). add_shell_macros(Env0) -> %% We KNOW how macros are expanded and write them directly in %% expanded form here. Ms = [{c,[lambda,[args,'$ENV'],?BQ([':',lfe_shell,c,?C_A(args)])]}, {describe,['match-lambda', [[[list,mod],'$ENV'],?BQ([':',lfe_shell,doc,?Q(?C(mod))])], ?UNDEF_MATCH_FUNC(describe)]}, {doc,['match-lambda', [[[list,mod],'$ENV'],?BQ([':',lfe_shell,h,?Q(?C(mod))])], [[[list,mod,func],'$ENV'], ?BQ([':',lfe_shell,h,?Q(?C(mod)),?Q(?C(func))])], [[[list,mod,func,arity],'$ENV'], ?BQ([':',lfe_shell,h,?Q(?C(mod)),?Q(?C(func)),?Q(?C(arity))])], ?UNDEF_MATCH_FUNC(doc)]}, {ec,[lambda,[args,'$ENV'],?BQ([':',lfe_shell,ec,?C_A(args)])]}, {l,[lambda,[args,'$ENV'],?BQ([':',lfe_shell,l,[list|?C(args)]])]}, {ls,[lambda,[args,'$ENV'],?BQ([':',lfe_shell,ls,[list|?C(args)]])]}, {m,['match-lambda', [[[],'$ENV'],?BQ([':',lfe_shell,m])], [[ms,'$ENV'],?BQ([':',lfe_shell,m,[list|?C(ms)]])]]} ], %% Any errors here will crash shell startup! Env1 = lfe_env:add_mbindings(Ms, Env0), %% io:fwrite("asm: ~p\n", [Env1]), Env1. %% start_eval(State) -> Evaluator. %% Start an evaluator process. start_eval(St) -> Self = self(), spawn_link(fun () -> eval_init(Self, St) end). eval_init(Shell, St) -> eval_loop(Shell, St). eval_loop(Shell, St0) -> receive {eval_expr,Shell,Form} -> St1 = eval_form(Form, Shell, St0), eval_loop(Shell, St1) end. %% eval_form(Form, ShellPid, State) -> State. %% Evaluate a form, print is reurn value and send updated state back %% to the shell manager process. Unfortunately we can't just let it %% crash as an error here causes the emulator to generate an error %% report. Being cunning and building our own error return value and %% doing exit on it seem to fix the problem. eval_form(Form, Shell, St0) -> try Ce1 = lfe_env:add_vbinding('-', Form, St0#state.curr), %% Macro expand and evaluate it. {Value,St1} = eval_form(Form, St0#state{curr=Ce1}), %% Print the result, but only to depth 30. VS = lfe_io:prettyprint1(Value, 30), io:requests([{put_chars,unicode,VS},nl]), %% Update bindings. Ce2 = update_shell_vars(Form, Value, St1#state.curr), St2 = St1#state{curr=Ce2}, %% Return value and updated state. Shell ! {eval_value,self(),Value,St2}, St2 catch exit:normal -> exit(normal); ?CATCH(Class, Reason, StackTrace) %% We don't want the ERROR REPORT generated by the %% emulator. Note: exit(kill) needs nothing special. {links,LPs} = process_info(self(), links), ER = nocatch(Class, {Reason,StackTrace}), lists:foreach(fun(P) -> exit(P, ER) end, LPs -- [Shell]), Shell ! {eval_error,self(),{Class,Reason,StackTrace}}, %% Note we are linked to Shell so we don't crash here. exit(normal) end. %% nocatch(throw, {Term,Stack}) -> %% {{nocatch,Term},Stack}; nocatch(_, Reason) -> Reason. %% eval_form(Form, State) -> {Value,State}. %% Macro expand the the top form then treat special case top-level %% forms. eval_form(Form, #state{curr=Ce}=St) -> %% Flatten progn nested forms. %% Don't deep expand, keep everything. case lfe_macro:expand_fileforms([{Form,1}], Ce, false, true) of {ok,Eforms,Ce1,Ws} -> list_warnings(Ws), St1 = St#state{curr=Ce1}, foldl(fun ({F,_}, {_,S}) -> eval_form_1(F, S) end, {[],St1}, Eforms); {error,Es,Ws} -> list_errors(Es), list_warnings(Ws), {error,St} end. eval_form_1([progn|Eforms], St) -> %Top-level nested progn foldl(fun (F, {_,S}) -> eval_form_1(F, S) end, {[],St}, Eforms); eval_form_1([set|Rest], St0) -> {Value,St1} = set(Rest, St0), {Value,St1}; eval_form_1([slurp|Args], St0) -> %Slurp in a file {Value,St1} = slurp(Args, St0), {Value,St1}; eval_form_1([unslurp|_], St) -> %% Forget everything back to before current slurp. unslurp(St); eval_form_1([run|Args], St0) -> {Value,St1} = run_file(Args, St0), {Value,St1}; eval_form_1(['reset-environment'], #state{base=Be}=St) -> {ok,St#state{curr=Be}}; eval_form_1(['extend-module'|_], St) -> %Maybe from macro expansion {[],St}; eval_form_1(['eval-when-compile'|_], St) -> %Maybe from macro expansion %% We can happily ignore this. {[],St}; eval_form_1(['define-record',Name,Fields], #state{curr=Ce0}=St) -> %% Don't fully expand the record definition, push it till its used %% in the same way as function and macro definitions. Ce1 = lfe_env:add_record(Name, Fields, Ce0), {Name,St#state{curr=Ce1}}; eval_form_1(['define-function',Name,_Meta,Def], #state{curr=Ce0}=St) -> Ar = function_arity(Def), Ce1 = lfe_eval:add_dynamic_func(Name, Ar, Def, Ce0), {Name,St#state{curr=Ce1}}; eval_form_1(['define-macro',Name,_Meta,Def], #state{curr=Ce0}=St) -> Ce1 = lfe_env:add_mbinding(Name, Def, Ce0), {Name,St#state{curr=Ce1}}; eval_form_1(Expr, St) -> %% General case just evaluate the expression. {lfe_eval:expr(Expr, St#state.curr),St}. function_arity([lambda,As|_]) -> length(As); function_arity(['match-lambda',[Pats|_]|_]) -> length(Pats). list_errors(Es) -> list_ews("~w: ~s\n", Es). list_warnings(Ws) -> list_ews("~w: Warning: ~s\n", Ws). list_ews(Format, Ews) -> lists:foreach(fun ({L,M,E}) -> Cs = M:format_error(E), lfe_io:format(Format, [L,Cs]) end, Ews). %% set(Args, State) -> {Result,State}. set([], St) -> {[],St}; set([Pat|Rest], #state{curr=Ce}=St) -> Epat = lfe_macro:expand_expr_all(Pat, Ce), %Expand macros in pattern set_1(Epat, Rest, St). set_1(Pat, [['when'|_]=G,Exp], St) -> set_1(Pat, [G], Exp, St); %Just the guard set_1(Pat, [Exp], St) -> set_1(Pat, [], Exp, St); %Empty guard body set_1(_, _, _) -> erlang:error({bad_form,'set'}). set_1(Pat, Guard, Exp, #state{curr=Ce0}=St) -> Val = lfe_eval:expr(Exp, Ce0), %Evaluate expression case lfe_eval:match_when(Pat, Val, Guard, Ce0) of {yes,_,Bs} -> Ce1 = foldl(fun ({N,V}, E) -> lfe_env:add_vbinding(N, V, E) end, Ce0, Bs), {Val,St#state{curr=Ce1}}; no -> erlang:error({badmatch,Val}) end. %% unslurp(State) -> {ok,State}. %% slurp(File, State) -> {{ok,Mod},State}. %% Load in a file making all the functions and macros available. %% There is a bit of trickery to get hold of the compile environment %% to get hold of the macros. We call the compiler directly but don't %% make the LFE-EXPAND-EXPORTED-MACRO/3 function. -record(slurp, {mod,funs=[],imps=[],recs=[]}). %For slurping unslurp(St0) -> St1 = case St0#state.slurp of true -> %Roll-back slurp Se = St0#state.save, St0#state{save=none,curr=Se,slurp=false}; false -> St0 %Do nothing end, {ok,St1}. slurp([File], St0) -> {ok,#state{curr=Ce0}=St1} = unslurp(St0), %Reset the environment Name = lfe_eval:expr(File, Ce0), %Get file name case slurp_1(Name, Ce0) of {ok,Mod,Ce1} -> %Set the new environment {{ok,Mod},St1#state{save=Ce0,curr=Ce1,slurp=true}}; error -> {error,St1} end. slurp_1(Name, Ce) -> case slurp_file(Name) of {ok,Mod,Fs,Env0,Ws} -> slurp_warnings(Ws), %% Collect functions and imports. Sl0 = #slurp{mod=Mod,funs=[],imps=[],recs=[]}, Sl1 = lists:foldl(fun collect_module/2, Sl0, Fs), %% Add imports to environment. Ifun = fun ({M,Is}, Env) -> foldl(fun ({{F,A},R}, E) -> lfe_env:add_ibinding(M, F, A, R, E) end, Env, Is) end, Env1 = foldl(Ifun, Env0, Sl1#slurp.imps), %% Add functions to environment. Env2 = foldl(fun ({N,Ar,Def}, Env) -> lfe_eval:add_dynamic_func(N, Ar, Def, Env) end, Env1, Sl1#slurp.funs), %% Add records to the environment. Env3 = foldl(fun ({R,Rfs}, Env) -> lfe_env:add_record(R, Rfs, Env) end, Env2, Sl1#slurp.recs), {ok,Mod,lfe_env:add_env(Env3, Ce)}; {error,Mews,Es,Ws} -> slurp_errors(Es), slurp_warnings(Ws), %% Now the errors and warnings for each module. lists:foreach(fun ({error,Mes,Mws}) -> slurp_errors(Mes), slurp_warnings(Mws) end, Mews), error end. slurp_file(Name) -> case lfe_comp:file(Name, [binary,to_split,return]) of {ok,[{ok,Mod,Fs0,_}|_],Ws} -> %Only do first module %% Deep expand, don't keep everything. case lfe_macro:expand_fileforms(Fs0, lfe_env:new(), true, false) of {ok,Fs1,Env,_} -> %% Flatten and trim away any eval-when-compile. {Fs2,42} = lfe_lib:proc_forms(fun slurp_form/3, Fs1, 42), case lfe_lint:module(Fs2) of {ok,_,Lws} -> {ok,Mod,Fs2,Env,Ws ++ Lws}; {error,Les,Lws} -> slurp_error_ret(Name, Les, Ws ++ Lws) end; {error,Ees,Ews} -> slurp_error_ret(Name, Ees, Ws ++ Ews) end; Error -> Error end. slurp_error_ret(Name, Es, Ws) -> {error,[],[{Name,Es}],[{Name,Ws}]}. slurp_form(['eval-when-compile'|_], _, D) -> {[],D}; slurp_form(F, L, D) -> {[{F,L}],D}. collect_module({['define-module',Mod,Meta,Atts],_}, Sl0) -> Sl1 = collect_meta(Meta, Sl0), Sl2 = collect_attrs(Atts, Sl1), Sl2#slurp{mod=Mod}; collect_module({['extend-module',Meta,Atts],_}, Sl0) -> Sl1 = collect_meta(Meta, Sl0), collect_attrs(Atts, Sl1); collect_module({['define-function',F,_Meta,Def],_}, #slurp{funs=Fs}=Sl) -> Ar = function_arity(Def), Sl#slurp{funs=[{F,Ar,Def}|Fs]}; collect_module({['define-record',R,Fs],_}, #slurp{recs=Rs}=Sl) -> Sl#slurp{recs=[{R,Fs}|Rs]}; %% Ignore other forms, type and spec defs. collect_module({_,_}, Sl) -> Sl. collect_meta(_, St) -> St. collect_attrs([[import|Is]|Atts], St) -> collect_attrs(Atts, collect_imps(Is, St)); collect_attrs([_|Atts], St) -> %Ignore everything else collect_attrs(Atts, St); collect_attrs([], St) -> St. collect_imps(Is, St) -> foldl(fun (I, S) -> collect_imp(I, S) end, St, Is). collect_imp(['from',Mod|Fs], St) -> collect_imp(fun ([F,A], Imps) -> orddict:store({F,A}, F, Imps) end, Mod, St, Fs); collect_imp(['rename',Mod|Rs], St) -> collect_imp(fun ([[F,A],R], Imps) -> orddict:store({F,A}, R, Imps) end, Mod, St, Rs); collect_imp(_, St) -> St. %Ignore everything else collect_imp(Fun, Mod, St, Fs) -> Imps0 = safe_fetch(Mod, St#slurp.imps, []), Imps1 = foldl(Fun, Imps0, Fs), St#slurp{imps=orddict:store(Mod, Imps1, St#slurp.imps)}. %% slurp_errors([File, ]Errors) -> ok. %% slurp_warnings([File, ]Warnings) -> ok. %% Print errors and warnings. slurp_errors(Errors) -> lists:foreach(fun ({File,Es}) -> slurp_errors(File, Es) end, Errors). slurp_errors(File, Es) -> slurp_ews(File, "~s:~w: ~s\n", Es). slurp_warnings(Warnings) -> lists:foreach(fun ({File,Ws}) -> slurp_warnings(File, Ws) end, Warnings). slurp_warnings(File, Es) -> slurp_ews(File, "~s:~w: Warning: ~s\n", Es). slurp_ews(File, Format, Ews) -> lists:foreach(fun ({Line,Mod,Error}) -> Cs = Mod:format_error(Error), lfe_io:format(Format, [File,Line,Cs]) end, Ews). %% run_file(Args, State) -> {Value,State}. %% Run the shell expressions in a file. Abort on errors and only %% return updated state if there are no errors. run_file([File], #state{curr=Ce}=St) -> Name = lfe_eval:expr(File, Ce), %Get file name case read_script_file(Name) of %Read the file {ok,Forms} -> run_loop(Forms, St); {error,E} -> slurp_errors(Name, [E]), {error,St} end. %% read_script_file(FileName) -> {ok,[Sexpr]} | {error,Error}. %% Read a file returning the sexprs. Almost the same as %% lfe_io:read_file except the we skip the first line if it is a %% script line "#! ... ". read_script_file(File) -> case file:open(File, [read]) of {ok,Fd} -> %% Check if first a script line, if so skip it. case io:get_line(Fd, '') of "#!" ++ _ -> lfe_io:read_file(Fd, 2); _ -> file:position(Fd, bof), %Reset to start of file lfe_io:read_file(Fd, 1) end end. %% read_script_string(FileName) -> {ok,[Sexpr]} | {error,Error}. %% Read a file returning the sexprs. Almost the same as %% lfe_io:read_string except parse all forms. read_script_string(String) -> lfe_io:read_string(String). %% run_loop(Forms, State) -> {Value,State}. %% run_loop(Forms PrevValue, State) -> {Value,State}. run_loop(Fs, St) -> run_loop(Fs, [], St). run_loop([F|Fs], _, St0) -> Ce1 = lfe_env:add_vbinding('-', F, St0#state.curr), {Value,St1} = eval_form(F, St0#state{curr=Ce1}), Ce2 = update_shell_vars(F, Value, St1#state.curr), run_loop(Fs, Value, St1#state{curr=Ce2}); run_loop([], Value, St) -> {Value,St}. %% safe_fetch(Key, Dict, Default) -> Value. safe_fetch(Key, D, Def) -> case orddict:find(Key, D) of {ok,Val} -> Val; error -> Def end. %% The LFE shell command functions. %% These are callable from outside the shell as well. %% c(File [,Args]) -> {ok,Module} | error. %% Compile and load an LFE file. c(File) -> c(File, []). c(File, Opts0) -> Opts1 = [report,verbose|Opts0], %Always report verbosely case lfe_comp:file(File, Opts1) of Ok when element(1, Ok) =:= ok -> %Compilation successful Return = lists:member(return, Opts1), Binary = lists:member(binary, Opts1), OutDir = outdir(Opts1), load_files(Ok, Return, Binary, OutDir); Error -> Error end. load_files(Ok, _, true, _) -> Ok; %Binary output load_files(Ok, Ret, _, Out) -> %Beam files created. Mods = element(2, Ok), lists:map(fun (M) -> load_file(M, Ret, Out) end, Mods). load_file(Ok, _, Out) -> case element(2, Ok) of [] -> Ok; %No module file to load Mod -> %We have a module name Bfile = filename:join(Out, atom_to_list(Mod)), code:purge(Mod), code:load_abs(Bfile, Mod) %Undocumented end. outdir([{outdir,Dir}|_]) -> Dir; %Erlang way outdir([[outdir,Dir]|_]) -> Dir; %LFE way outdir([_|Opts]) -> outdir(Opts); outdir([]) -> ".". %% cd(Dir) -> ok. cd(Dir) -> c:cd(Dir). %% ec(File [,Args]) -> Res. %% Compile and load an Erlang file. ec(F) -> c:c(F). ec(F, Os) -> c:c(F, Os). %% ep(Expr [, Depth]) -> ok. %% epp(Expr [, Depth]) -> ok. %% Print/prettyprint a value in Erlang format. ep(E) -> Cs = io_lib:write(E), io:put_chars([Cs,$\n]). ep(E, D) -> Cs = io_lib:write(E, D), io:put_chars([Cs,$\n]). epp(E) -> Cs = io_lib:format("~p", [E]), io:put_chars([Cs,$\n]). epp(E, D) -> Cs = io_lib:format("~P", [E,D]), io:put_chars([Cs,$\n]). %% help() -> ok. help() -> io:put_chars(<<"\nLFE shell built-in functions\n\n" "(c file) -- compile and load code in \n" "(cd dir) -- change working directory to \n" "(clear) -- clear the REPL output\n" "(doc mod) -- documentation of a module\n" "(doc mod:mac) -- documentation of a macro\n" "(doc m:f/a) -- documentation of a function\n" "(ec file) -- compile and load code in erlang \n" "(ep expr) -- print a term in erlang form\n" "(epp expr) -- pretty print a term in erlang form\n" "(exit) -- quit - an alias for (q)\n" "(flush) -- flush any messages sent to the shell\n" "(h) -- an alias for (help)\n" "(h m) -- help about module\n" "(h m m) -- help about function and macro in module\n" "(h m f a) -- help about function/arity in module\n" "(help) -- help info\n" "(i) -- information about the system\n" "(i pids) -- information about a list of pids\n" "(i x y z) -- information about pid #Pid\n" "(l module) -- load or reload \n" "(ls) -- list files in the current directory\n" "(ls dir) -- list files in directory \n" "(m) -- which modules are loaded\n" "(m mod) -- information about module \n" "(memory) -- memory allocation information\n" "(memory t) -- memory allocation information of type \n" "(p expr) -- print a term\n" "(pp expr) -- pretty print a term\n" "(pid x y z) -- convert x, y, z to a pid\n" "(pwd) -- print working directory\n" "(q) -- quit - shorthand for init:stop/0\n" "(regs) -- information about registered processes\n" "(nregs) -- information about all registered processes\n" "(uptime) -- print node uptime\n" "\n" "LFE shell built-in forms\n\n" "(reset-environment) -- reset the environment to its initial state\n" "(run file) -- execute all the shell commands in a \n" "(set pattern expr)\n" "(set pattern (when guard) expr) -- evaluate and match the result with\n" " pattern binding\n" "(slurp file) -- slurp in a LFE source and makes\n" " everything available in the shell\n" "(unslurp) -- revert back to the state before the last\n" " slurp\n\n" "LFE shell built-in variables\n\n" "+/++/+++ -- the three previous expressions\n" "*/**/*** -- the values of the previous expressions\n" "- -- the current expression output\n" "$ENV -- the current LFE environment\n\n" >>). %% i([Pids]) -> ok. i() -> c:i(). i(Pids) -> c:i(Pids). i(X, Y, Z) -> c:i(X, Y, Z). %% l(Modules) -> ok. %% Load the modules. l(Ms) -> lists:map(fun (M) -> c:l(M) end, Ms). %% ls(Dir) -> ok. ls(Dir) -> apply(c, ls, Dir). %% clear() -> ok. clear() -> io:format("\e[H\e[J"). %% m([Modules]) -> ok. %% Print module information. Instead of using the c module we do %% module info ourselves to get all the data and the right formats. m() -> mformat("Module", "File"), lists:foreach(fun ({Mod,File}) -> Mstr = lists:flatten(lfe_io:print1(Mod)), mformat(Mstr, File) end, lists:sort(code:all_loaded())). mformat(S1, S2) -> Fstr = if length(S1) > 20 -> "~s~n ~s~n"; true -> "~-20s ~s~n" end, lfe_io:format(Fstr, [S1,S2]). m(Ms) -> lists:foreach(fun (M) -> print_module(M) end, Ms). print_module(M) -> Info = M:module_info(), lfe_io:format("Module: ~w~n", [M]), print_object_file(M), print_md5(Info), print_compile_time(Info), print_compile_options(Info), print_exports(Info), print_macros(Info). print_md5(Info) -> case lists:keyfind(md5, 1, Info) of {md5,<>} -> lfe_io:format("MD5: ~.16b~n", [MD5]); false -> ok end. print_compile_time(Info) -> Cstr = case get_compile_info(Info, time) of {Year,Month,Day,Hour,Min,Sec} -> lfe_io:format1("~w-~.2.0w-.2.0~w ~.2.0w:~.2.0w:~.2.0w", [Year,Month,Day,Hour,Min,Sec]); error -> "No compile time info avaliable" end, lfe_io:format("Compiled: ~s~n", [Cstr]). print_object_file(M) -> case code:is_loaded(M) of {file,File} -> lfe_io:format("Object file: ~s~n", [File]); _ -> ok end. print_compile_options(Info) -> case get_compile_info(Info, options) of %Export Opts Opts when is_list(Opts) -> ok; error -> Opts = [] end, lfe_io:format("Compiler options: ~p~n", [Opts]). print_exports(Info) -> lfe_io:format("Exported functions:~n", []), Exps = case lists:keyfind(exports, 1, Info) of {exports,Es} -> Es; false -> [] end, print_names(fun ({N,Ar}) -> lfe_io:format1("~w/~w", [N,Ar]) end, Exps). print_macros(Info) -> lfe_io:format("Exported macros:~n", []), Macs = case lists:keyfind(attributes, 1, Info) of {attributes,Attrs} -> Fun = fun ({'export-macro',Ms}) -> Ms; (_) -> [] end, lists:flatmap(Fun, Attrs); false -> [] end, print_names(fun (N) -> lfe_io:print1(N) end, Macs). print_names(Format, Names) -> %% Generate flattened list of strings. Strs = lists:map(fun (N) -> lists:flatten(Format(N)) end, lists:sort(Names)), %% Split into equal length lists and print out. {S1,S2} = lists:split(round(length(Strs)/2), Strs), print_name_strings(S1, S2). print_name_strings([N1|N1s], [N2|N2s]) -> lfe_io:format(" ~-30s ~-30s~n", [N1,N2]), print_name_strings(N1s, N2s); print_name_strings([N1], []) -> lfe_io:format(" ~s~n", [N1]); print_name_strings([], []) -> ok. get_compile_info(Info, Tag) -> case lists:keyfind(compile, 1, Info) of {compile,C} -> case lists:keyfind(Tag, 1, C) of {Tag,Val} -> Val; false -> error end; false -> error end. %% p(Expr [, Depth]) -> ok. %% pp(Expr [, Depth]) -> ok. %% Print/prettyprint a value in LFE format. p(E) -> Cs = lfe_io:print1(E), io:put_chars([Cs,$\n]). p(E, D) -> Cs = lfe_io:print1(E, D), io:put_chars([Cs,$\n]). pp(E) -> Cs = lfe_io:prettyprint1(E), io:put_chars([Cs,$\n]). pp(E, D) -> Cs = lfe_io:prettyprint1(E, D), io:put_chars([Cs,$\n]). %% pid(A, B, C) -> Pid. %% Build a pid from its 3 "parts". pid(A, B, C) -> c:pid(A, B, C). %% pwd() -> ok. pwd() -> c:pwd(). %% q() -> ok. q() -> c:q(). %% flush() -> ok. flush() -> c:flush(). %% regs() -> ok. regs() -> c:regs(). %% nregs() -> ok. nregs() -> c:nregs(). %% exit() -> ok. exit() -> c:q(). %% memory() -> ok. memory() -> c:memory(). %% memory(Type) -> ok. memory(Type) -> c:memory(Type). %% uptime() -> ok. uptime() -> c:uptime(). %% doc(Mod) -> ok | {error,Error}. %% doc(Mod, Func) -> ok | {error,Error}. %% doc(Mod, Func, Arity) -> ok | {error,Error}. %% Print out documentation of a module/macro/function. Always try to %% find the file and use it as this is the only way to get hold of %% the chunks. This may get a later version than is loaded. %% doc(?Q(What)) -> doc(What); %Be kind if they quote it %% doc(What) -> %% [Mod|F] = lfe_lib:split_name(What), h(Mod) -> case lfe_docs:get_module_docs(Mod) of {ok,#docs_v1{}=Docs} -> Ret = get_module_doc(Mod, Docs), format_doc(Ret); Error -> Error end. h(Mod, Func) -> case lfe_docs:get_module_docs(Mod) of {ok,#docs_v1{}=Docs} -> Ret = get_macro_doc(Mod, Func, Docs), format_doc(Ret); Error -> Error end. h(Mod, Func, Arity) -> case lfe_docs:get_module_docs(Mod) of {ok,#docs_v1{}=Docs} -> Ret = get_function_doc(Mod, Func, Arity, Docs), format_doc(Ret); Error -> Error end. format_doc({error,_}=Error) -> Error; format_doc(Docs) -> {match,Lines} = re:run(Docs, "(.+\n|\n)", [unicode,global,{capture,all_but_first,binary}]), Pline = fun (Line) -> io:put_chars(Line), 1 %Output one line end, paged_output(Pline, Lines), ok. -ifdef(EEP48). get_module_doc(Mod, #docs_v1{format = ?NATIVE_FORMAT}=Docs) -> shell_docs:render(Mod, Docs); get_module_doc(Mod, #docs_v1{format = ?LFE_FORMAT}=Docs) -> lfe_shell_docs:render(Mod, Docs); get_module_doc(_Mod, #docs_v1{format = Enc}) -> {error, {unknown_format, Enc}}. get_macro_doc(Mod, Name, #docs_v1{format = ?NATIVE_FORMAT}=Docs) -> shell_docs:render(Mod, Name, Docs); get_macro_doc(Mod, Name, #docs_v1{format = ?LFE_FORMAT}=Docs) -> lfe_shell_docs:render(Mod, Name, Docs); get_macro_doc(_Mod, _Name, #docs_v1{format = Enc}) -> {error, {unknown_format, Enc}}. get_function_doc(Mod, Name, Arity, #docs_v1{format = ?NATIVE_FORMAT}=Docs) -> shell_docs:render(Mod, Name, Arity, Docs); get_function_doc(Mod, Name, Arity, #docs_v1{format = ?LFE_FORMAT}=Docs) -> lfe_shell_docs:render(Mod, Name, Arity, Docs); get_function_doc(_Mod, _Name, _Arity, #docs_v1{format = Enc}) -> {error, {unknown_format, Enc}}. -else. get_module_doc(Mod, #docs_v1{format = ?LFE_FORMAT}=Docs) -> lfe_shell_docs:render(Mod, Docs); get_module_doc(_Mod, #docs_v1{format = Enc}) -> {error, {unknown_format, Enc}}. get_macro_doc(Mod, Name, #docs_v1{format = ?LFE_FORMAT}=Docs) -> lfe_shell_docs:render(Mod, Name, Docs); get_macro_doc(_Mod, _Name, #docs_v1{format = Enc}) -> {error, {unknown_format, Enc}}. get_function_doc(Mod, Name, Arity, #docs_v1{format = ?LFE_FORMAT}=Docs) -> lfe_shell_docs:render(Mod, Name, Arity, Docs); get_function_doc(_Mod, _Name, _Arity, #docs_v1{format = Enc}) -> {error, {unknown_format, Enc}}. -endif. %% paged_output(PrintItem, Items) -> ok. %% Output item lines a page at a time. This can handle an item %% returning multiple lines. paged_output(Pitem, Items) -> %% How many rows per "page", just set it to 30 for now. Limit = 30, paged_output(Pitem, 0, Limit, Items). paged_output(Pitem, Curr, Limit, Items) when Curr >= Limit -> case more() of more -> paged_output(Pitem, 0, Limit, Items); less -> ok end; paged_output(Pitem, Curr, Limit, [Item|Items]) -> Olines = Pitem(Item), paged_output(Pitem, Curr + Olines, Limit, Items); paged_output(_, _, _, []) -> ok. more() -> case get_line('More (y/n)? (y)', "y\n") of "y\n" -> more; "c\n" -> more; "n\n" -> less; "q\n" -> less; _ -> more() end. get_line(P, Default) -> case line_string(io:get_line(P)) of "\n" -> Default; L -> L end. %% If the standard input is set to binary mode %% convert it to a list so we can properly match. line_string(Binary) when is_binary(Binary) -> unicode:characters_to_list(Binary); line_string(Other) -> Other.