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