records and macros from the shell
Peter Andersson
Peter.Andersson@REDACTED
Wed Dec 6 18:16:11 CET 2000
Hi again,
I wrote a simple little program a while back that I find useful now and then.
It's a program that lets you declare records, define macros and include header
files from the Erlang shell, similar to what you would do in a normal Erlang
source file. You can then evaluate a sequence of Erlang expressions in the shell
using normal record and macro syntax. I find this especially useful for testing
functions that take records as input, directly from the shell. For this I would,
for example, first include a header file with the program, then evaluate an
expression calling the function in question. (An expression to be evaluated is
written to a temporary file together with the current definitions and is then
executed, so it's not extremely fast). The program can "steal" declarations and
definitions from a source file as well.
I thought I'd post it if someone's interested. It hasn't been updated in a
while, but it seemed to work ok last time I used it. Again, it's really simple
so it shouldn't take long for you to update or modify if you wish. (It's
intentionally free from comments *only* to make it a bit more challenging to
understand! :-).
I have attached the code and an interface description (+ a messy example).
Cheers
/Peter
-------------- next part --------------
%%%----------------------------------------------------------------------
%%% File : p.erl
%%% Author : Andersson, Peter <peppe@REDACTED>
%%% Purpose :
%%% Created : 15 Sep 1998 by Andersson, Peter <peppe@REDACTED>
%%%----------------------------------------------------------------------
-module(p).
-author('peppe@REDACTED').
-export([include/1, record/2, define/2, steal/1,
e/1, e/2,
f/0, finclude/0, frecord/0, fdefine/0, fsteal/0,
finclude/1, frecord/1, fdefine/1]).
-define(TabName, p_decls).
%% include header
include(Hs) ->
case Hs of
[Head | _] when list(Head) ->
lists:foreach(fun(H) -> declare(header, H) end,
Hs),
true;
Head ->
declare(header, Head)
end.
%% steal from source file
steal(File) ->
case file:open(File, read) of
{ok, Port} ->
Ts = scan(Port, 1, []),
extract_and_decl(Ts),
true;
_ ->
no_such_file
end.
%% declare record
record(Name, Rec) ->
declare(record, {Name, Rec}).
%% declare macro
define(Name, Def) when atom(Name) ->
io:format("~nDEF ERROR: Macro must be string~n"),
error;
define(Name, Def) when list(Name) ->
declare(macro, {Name, Def}).
declare(Key, Data) ->
case catch ets:match_object(?TabName, {Key, Data}) of
{'EXIT', _} ->
ets:new(?TabName, [bag, public, named_table]),
ets:insert(?TabName, {Key, Data});
[_] ->
not_again;
[] ->
ets:insert(?TabName, {Key, Data})
end.
%% "forget all"
f() ->
catch ets:delete(?TabName).
finclude() ->
ets:delete(?TabName, header).
frecord() ->
ets:delete(?TabName, record).
fdefine() ->
ets:delete(?TabName, macro).
fsteal() ->
ets:delete(?TabName, steal).
%% "forget specific"
finclude(H) ->
catch ets:match_delete(?TabName, {header, H}).
frecord(R) ->
catch ets:match_delete(?TabName, {record, {R, '_'}}).
fdefine(M) ->
catch ets:match_delete(?TabName, {macro, {M, '_'}}).
%% evaluate expression
e(Expr) ->
e(Expr, remove).
e(Expr, SaveFile) ->
case ets:info(?TabName, name) of
undefined ->
ets:new(?TabName, [public, bag, named_table]);
_ ->
ok
end,
file:delete("p_eval.erl"),
file:delete("p_eval.jam"),
file:delete("p_eval.beam"),
{ok, Port} = file:open("p_eval.erl", write),
%% create file
io:format(Port, "-module(p_eval).~n", []),
io:format(Port, "-export([eval/0]).~n", []),
%% includes
lists:foreach(fun({_, H}) ->
io:format(Port, "-include(\"~s\").~n", [H])
end, ets:lookup(?TabName, header)),
%% macros
lists:foreach(fun({_, {N,D}}) ->
io:format(Port, "-define(~s, ~s).~n", [N, D])
end, ets:lookup(?TabName, macro)),
%% records
lists:foreach(fun({_, {N,R}}) ->
io:format(Port, "-record(~s, ~s).~n", [N, R])
end, ets:lookup(?TabName, record)),
%% steals
lists:foreach(fun({_, Defs}) ->
lists:foreach(fun({Form,E}) ->
io:format(Port,Form,E)
end, Defs)
end, ets:lookup(?TabName, steal)),
%% eval function
io:format(Port, "~neval() -> ~n ~s.~n~n", [Expr]),
file:close(Port),
%% compile file
code:soft_purge(p_eval),
Result =
case compile:file(p_eval, [report_errors]) of
error ->
io:format("~nEVAL ERROR: compilation fails~n"),
error;
_ ->
code:load_file(p_eval),
%% evaluate expr
p_eval:eval()
end,
case SaveFile of
remove ->
file:delete("p_eval.erl"),
file:delete("p_eval.jam"),
file:delete("p_eval.beam");
_ ->
ok
end,
Result.
%% steal functions
scan(P, L, SoFar) ->
case io:scan_erl_form(P, '', L) of
{ok, Ts, L1} ->
scan(P, L1, SoFar++Ts);
{eof, L1} ->
SoFar
end.
%% valid pp stmnt
extract_and_decl([{'-',LM}, T , {'(',LP} | Ts]) ->
{Extract,PP} =
case T of
{atom,L,record} ->
{true,record};
{atom,L,define} ->
{true,define};
_ ->
{false,void}
end,
if Extract == true ->
{Acc,Rest} = extract_spec(Ts, [], 1),
Def = convert([ {'-',LM}, {atom,0,PP}, {'(',LP} | Acc ], []),
declare(steal, Def),
extract_and_decl(Rest);
true ->
extract_and_decl(Ts)
end;
extract_and_decl([ _| Ts]) -> extract_and_decl(Ts);
extract_and_decl([]) -> done.
extract_spec([T | Ts], Acc, PC) when PC/=0 ->
PC1 =
case T of
{'(',_} -> PC+1;
{')',_} -> PC-1;
_ -> PC
end,
extract_spec(Ts, [T|Acc], PC1);
extract_spec([{dot,L} | Ts], Acc, PC) when PC==0 ->
{lists:reverse([{'.',L} | Acc]),Ts};
extract_spec(_, _, _) ->
exit('unbalanced parenthesis').
convert([T | Ts], Acc) ->
C = case T of
{X,_} -> X;
{_,_,X} -> X;
_ -> exit('unknown token')
end,
convert(Ts, [get_format(C) | Acc]);
convert([], Acc) ->
lists:reverse([{"~n",[]} | Acc]).
get_format(Term) when atom(Term) ->
{"~s",[atom_to_list(Term)]};
get_format(Term) when number(Term) ->
{"~w",[Term]};
get_format(Term) when list(Term) -> % string
{"~p",[Term]};
get_format(Term) ->
exit(cannot_be_matched).
-------------- next part --------------
Module 'p'
Peter Andersson, 2000-12-06
=== INTERFACE ===
Func: include(File)
Descr: Includes File (string).
Func: include(Files)
Descr: Includes Files = [File1, File2, ...].
Func: define(Macro, Def)
Desrc: Defines Macro (constant or function, string) as Def (string).
Func: record(Name, Def)
Descr: Defines record with name Name (atom) as Def (string).
Func: steal(File)
Descr: Extracts all record-, macro- and constant definitions from File.
Func: f()
Descr: Forgets all definitions.
Func: finclude(), fdefine(), frecord()
Desrc: Forgets all includes/defines/records.
Func: finclude(File), fdefine(Macro), frecord(Name)
Descr: Forgets specific definition (see types above).
Func: fsteal()
Descr: Forgets all "stolen" definitions.
Func: e(Expr)
Descr: Evaluates arbitrary expression Expr (string).
=== EXAMPLES ===
(in the Erlang shell)
%%% definitions
1> p:include("ph.hrl").
true
2> p:record(r1, "{a, b=void}").
true
3> p:define("C", "42").
true
4> p:define("out(F,A)", "io:format(F,A)").
true
%%% evaluate expression
5> p:e(" R = #r1{a=#ph_r{}},
5> R1 = R#r1{b=?ph_m(?C)},
5> ?out(\"~n- ~p - ~p -~n\", [R1#r1.a, R1#r1.b]),
5> ?C/2 ").
- {ph_r,1,ok} - 84 -
21.0000
%%% "steal" definitions
1> p:steal("ph.erl").
true
2> p:e(" R = #ph_sr{}, ph:foo(R#ph_sr{x=?ph_sm(42)}) ").
21.0000
------ file ph.hrl -----
-define(ph_m(X), X*2).
-record(ph_r, {x=1, y=ok}).
------------------------
------ file ph.erl -----
-module(ph).
-export([foo/1]).
-define(ph_sm(X), X/2).
-record(ph_sr, {x,y}).
foo(R) -> R#ph_sr.x.
------------------------
More information about the erlang-questions
mailing list