[erlang-questions] compile module from string, containing macro definitions

Mats Cronqvist mats.cronqvist@REDACTED
Thu Mar 8 11:44:05 CET 2007


Chris Newcombe wrote:
> I'm using this code from Ulf Wiger to compile a module from a string:

   bad mistake... (hi ulf!)

 > However, when I test it with code containing a macro definition I get an
 > error.

   "it hurts when i do this..." :>

   as it were, i too have needed to compile a string that contains macros.

   the attached code works without any hacking of OTP. it will compile a string 
with macros. be aware that the macro handling is very limited (but hopefully 
easy to extend).

   e.g.

 > complainer:string("-module(x).\n-export([go/0]).\n-define(X,\"that 
sucks\").\ngo()->[erlang,has_macros,?X].\n").
{module,x}
 > x:go().
[erlang,has_macros,"that sucks"]

   mats

%%----------------------------------------------------------------
%%% File        : complainer.erl
%%% Author      : Mats Cronqvist <locmacr@REDACTED>
%%% Created     : 10 Jul 2006
%%% Description : takes a string that should be an erlang module.
%%%               scans, does macro substitution,
%%%               parses, compiles and loads.
%%%               will also make popcorn.
%%%----------------------------------------------------------------

-module(complainer).
-author('Mats Cronqvist').

-export([string/1]).

-import(lists,[reverse/1,keyreplace/4]).

string(Text) ->
     Forms = scan_and_parse(Text,1,[],dict:new()),
     {ok,Mod,Bin} = compile:forms(Forms),
     code:load_binary(Mod,"generated",Bin).

%%%## 'scan_and_parse'
%%%
%%% basically we call the OTP scanner and parser (erl_scan and
%%% erl_parse) line-by-line, but check each scanned line for (or
%%% definitions of) macros before parsing.

scan_and_parse([],_Line,Forms,_MacroDict) -> reverse(Forms);
scan_and_parse(Text,Line,Forms,MacroDict) ->
     case scanner(Text,Line,MacroDict) of
	{macro,NLine,Cont,NMacroDict} ->
	    scan_and_parse(Cont,NLine,Forms,NMacroDict);
	{tokens,NLine,Cont,Toks} ->
	    {ok,Form} = erl_parse:parse_form(Toks),
	    scan_and_parse(Cont,NLine,[Form|Forms],MacroDict)
     end.

scanner(Text,Line,MacroDict) ->
     {done,{ok,Toks,NLine},Cont} = erl_scan:tokens([],Text,Line),
     case pre_proc(Toks,MacroDict) of
	{macro,NMacroDict} ->
	    {macro,NLine,Cont,NMacroDict};
	{tokens,NToks} ->
	    {tokens,NLine,Cont,NToks}
     end.

%%%## 'pre-proc'
%%%
%%% have to implement a subset of the pre-processor, since epp insists
%%% on running on a file.
%%% only handles 2 cases;
%% -define(MACRO,"string").
%% -define(MACRO(VAR1,VARN),{stuff,VAR1,more,stuff,VARN,extra,stuff}).

pre_proc([{'-',_},{atom,_,define},{'(',_},{_,_,Name}|DefToks],MacroDict) ->
     false = dict:is_key(Name,MacroDict),
     case DefToks of
	[{',',_}|Macro] ->
	    {macro,dict:store(Name,{[],macro_wo_vars(Macro,[])},MacroDict)};
	[{'(',_}|Macro] ->
	    {macro,dict:store(Name,macro_w_vars(Macro,[]),MacroDict)}
     end;
pre_proc(Toks,MacroDict) ->
     {tokens,subst_macros(Toks,MacroDict,[])}.

macro_w_vars([{')',_},{',',_}|Toks],Vars) ->
     {reverse(Vars),macro_wo_vars(Toks,[])};
macro_w_vars([{var,_,Var}|Toks],Vars) ->
     macro_w_vars(Toks,[Var|Vars]);
macro_w_vars([{',',_},{var,_,Var}|Toks],Vars) ->
     macro_w_vars(Toks,[Var|Vars]).

macro_wo_vars([{')',_},{dot,_}],Val) -> Val;
macro_wo_vars([H|T],Val) -> macro_wo_vars(T,[H|Val]).

subst_macros([{'?',_},{_,_,Name},{'(',_}|Toks],MacroDict,O) ->
     {NToks,Vars} = subst_macros_get_vars(Toks,[]),
     Macro = dict:fetch(Name,MacroDict),
     subst_macros(NToks,MacroDict,subst_macros_put_vars(Macro,Vars)++O);
subst_macros([{'?',_},{_,_,Name}|Toks],MacroDict,O) ->
     Macro = dict:fetch(Name,MacroDict),
     subst_macros(Toks,MacroDict,subst_macros_put_vars(Macro,[])++O);
subst_macros([H|T],MacroDict,O) -> subst_macros(T,MacroDict,[H|O]);
subst_macros([],_MacroDict,O) -> reverse(O).


subst_macros_get_vars([{')',_}|Toks],O) ->
     {Toks,reverse(O)};
subst_macros_get_vars([{',',_},{var,_,Var}|Toks],O) ->
     subst_macros_get_vars(Toks,[Var|O]);
subst_macros_get_vars([{var,_,Var}|Toks],O) ->
     subst_macros_get_vars(Toks,[Var|O]).

subst_macros_put_vars({[],Macro},[]) -> Macro;
subst_macros_put_vars({[MVar|MVars],Macro},[Var|Vars]) ->
     NMacro = keyreplace(MVar,3,Macro,{var,1,Var}),
     subst_macros_put_vars({MVars,NMacro},Vars).



More information about the erlang-questions mailing list