Current section
Files
Jump to
Current section
Files
src/lfe_macro_export.erl
%% Copyright (c) 2016 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_macro_export.erl
%% Author : Robert Virding
%% Purpose : Lisp Flavoured Erlang macro export function builder.
%% Build the LFE-EXPAND-EXPORTED-MACRO function which exports macros
%% so they can be found by the macro expander without needing to
%% inlcude them. If the module foo exports macro bar then it can be
%% called by doing (foo:bar ...).
%%
%% This version expands the macros when the defining module is
%% compiled so they are expanded in the context when that module is
%% compiled, not when they are called. This is easy and makes it easy
%% to access all macros in the defining module.
%%
%% An alternative would be to expand the macros when they are called
%% but then it becomes difficult to access all the macros within the
%% defining module. This might be easy if we accept exporting all
%% macros not just specific ones.
%%
%% The matching is done in two steps: first we test whether the call
%% name is one of our known macros; if so we test whether the
%% arguments match against the argument patterns in the macro
%% definition. Doing it like this gives us the same failure handling
%% as when expanding local calls to macros.
%% (defun LFE-EXPAND-EXPORTED-MACRO (name args $ENV)
%% (let ((var-1 val-1) ;Eval-when-compile variables
%% ...)
%% (fletrec ((fun-1 ...) ;Eval-when-compile functions
%% ...)
%% (case name ;Macro name without module
%% ('mac-1 ...) ;Already exported local macros
%% (case args ;Match against args
%% (arg-pat ...)
%% ...))
%% ('mac-2 ...)
%% ...
%% (_ 'no)))))
-module(lfe_macro_export).
%%-compile(export_all).
-include("lfe_comp.hrl").
-export([module/2]).
-import(lists, [reverse/1,reverse/2,member/2,filter/2]).
%% We do a lot of quoting!
-define(Q(E), [quote,E]).
-define(BQ(E), [backquote,E]).
-define(C(E), [comma,E]).
-define(C_A(E), ['comma-at',E]).
-record(umac, {mline=[],expm=[],env=[]}).
%% We need these variables to have a funny name.
-define(NAMEVAR, '|- MACRO NAME -|').
-define(ARGSVAR, '|- CALL ARGS -|').
%% module(ModuleForms, CompState) -> {ModuleForms,CompState}.
%% module(ModuleDef, ModuleForms, UmacState, CompState) ->
%% {ModuleForms,CompState}.
module([Mdef|Fs], Cst) ->
Mst = collect_macros(Fs, #umac{env=lfe_env:new()}),
%% io:format("m: ~p\n", [Umac]),
module(Mdef, Fs, Mst, Cst).
module({['define-module',Name|Mdef],L}, Fs0, Mst0, Cst) ->
Mst1 = collect_mdef(Mdef, Mst0#umac{mline=L}),
Fs1 = add_huf(L, Fs0),
Umac = build_user_macro(Mst1),
%% We need to export the expansion function but leave the rest.
Exp = [export,['LFE-EXPAND-EXPORTED-MACRO',3],
['$handle_undefined_function',2]],
Md1 = {['define-module',Name,Exp|Mdef],L},
{[Md1|Fs1 ++ [{Umac,L}]],Cst}.
collect_macros(Fs, Mst) ->
lists:foldl(fun collect_macro/2, Mst, Fs).
collect_macro({['define-macro',Name,Def,_],_}, #umac{env=Env0}=Mst) ->
Env1 = lfe_env:add_mbinding(Name, Def, Env0),
Mst#umac{env=Env1};
collect_macro({['eval-when-compile'|Fs],_}, Mst) ->
lists:foldl(fun collect_ewc_macro/2, Mst, Fs);
collect_macro({['extend-module'|Mdef],_}, Mst) ->
collect_mdef(Mdef, Mst);
collect_macro(_, Mst) -> Mst.
collect_ewc_macro([set,Name,Val], #umac{env=Env0}=Mst) ->
Env1 = lfe_env:add_vbinding(Name, Val, Env0),
Mst#umac{env=Env1};
collect_ewc_macro(['define-function',Name,Def,_], #umac{env=Env0}=Mst) ->
Ar = function_arity(Def),
Env1 = lfe_env:add_fbinding(Name, Ar, Def, Env0),
Mst#umac{env=Env1};
collect_ewc_macro([progn|Fs], Mst) ->
lists:foldl(fun collect_ewc_macro/2, Mst, Fs).
function_arity([lambda,As|_]) -> length(As);
function_arity(['match-lambda',[Pats|_]|_]) -> length(Pats).
%% collect_mdef(ModuleDef, MacroState) -> MacroState.
%% We are only interested in which macros are exported.
collect_mdef([['export-macro'|Ms]|Mdef], #umac{expm=Expm0}=Mst) ->
Expm1 = add_exports(Expm0, Ms),
collect_mdef(Mdef, Mst#umac{expm=Expm1});
collect_mdef([_|Mdef], Mst) -> collect_mdef(Mdef, Mst);
collect_mdef([], Mst) -> Mst.
%% add_exports(Old, More) -> New.
%% exported_macro(Name, State) -> true | false.
add_exports(all, _) -> all;
add_exports(_, all) -> all;
add_exports(Old, More) ->
ordsets:union(Old, lists:usort(More)).
exported_macro(_, #umac{expm=all}) -> true; %All are exported
exported_macro(Name, #umac{expm=Expm}) ->
member(Name, Expm).
%% build_user_macro(MacroState) -> UserMacFunc.
%% Take the forms in the eval-when-compile and build the
%% LFE-EXPAND-EXPORTED-MACRO function. In this version we expand the
%% macros are compile time.
build_user_macro(#umac{expm=[]}) -> %No macros to export
empty_leum();
build_user_macro(#umac{env=Env}=Mst) ->
Vfun = fun (N, V, Acc) -> [[N,V]|Acc] end,
Sets = lfe_env:fold_vars(Vfun, [], Env),
%% Collect the local functions.
Ffun = fun (N, _, Def, Acc) ->
%% [[N,lfe_macro:expand_expr_all(Def, Env)]|Acc]
[[N,Def]|Acc]
end,
Funs = lfe_env:fold_funs(Ffun, [], Env),
%% Collect the local macros.
Mfun = fun (N, Def0, Acc) ->
case exported_macro(N, Mst) of
true ->
%% Def1 = lfe_macro:expand_expr_all(Def0, Env),
[macro_case_clause(N, Def0)|Acc];
false -> Acc
end
end,
%% Get the macros to export as case clauses.
case lfe_env:fold_macros(Mfun, [], Env) of
[] -> empty_leum(); %No macros to export
Macs ->
%% Build case, flet and let.
Case = ['case',?NAMEVAR|Macs ++ [['_',?Q(no)]]],
Flr = ['letrec-function',Funs,Case],
Fl = ['let',Sets,Flr],
['define-function','LFE-EXPAND-EXPORTED-MACRO',
[lambda,[?NAMEVAR,?ARGSVAR,'$ENV'],Fl],[]]
end.
empty_leum() ->
['define-function','LFE-EXPAND-EXPORTED-MACRO',
[lambda,['_','_','_'],?Q(no)],[]].
%% add_huf(ModLine, Forms) -> Forms.
%% Add the $handle_undefined_function/2 function to catch run-time
%% macro calls. Scan through forms to check if there is an
%% $handle_undefined_function/2 function already defined. If so use
%% that as default when not a macro, otherwise just generate the
%% standard undef error.
add_huf(L, [{['define-function','$handle_undefined_function',Def,Doc],Lf}=F|Fs]) ->
case function_arity(Def) of
2 -> [{make_huf(Def, Doc),Lf}|Fs]; %Found the right $huf
_ -> [F|add_huf(L, Fs)]
end;
add_huf(L, [F|Fs]) ->
[F|add_huf(L, Fs)];
add_huf(L, []) -> %No $huf, so make one.
%% Use the default undef exception handler.
Excep = [lambda,[a,b],
[':',error_handler,raise_undef_exception,['MODULE'],a,b]],
[{make_huf(Excep, []),L}].
make_huf(Huf, Doc) ->
[defun,'$handle_undefined_function',[f,as],Doc,
['case',['LFE-EXPAND-EXPORTED-MACRO',f,as,[':',lfe_env,new]],
[[tuple,?Q(yes),exp],[':',lfe_eval,expr,exp]],
[?Q(no),[funcall,Huf,f,as]]]].
%% macro_case_clause(Name, Def) -> CaseClause.
%% Build a case clause for expanding macr Name.
macro_case_clause(Name, Def) ->
Cls = get_macro_cls(Def),
Ccls = [ macro_clause(Args, B) || {Args,B} <- Cls ],
[?Q(Name),['case',?ARGSVAR|Ccls]]. %Don't catch errors
%% get_macro_cls(MacroDef) -> [{ArgPat,Body}].
%% Build a list of arg pattern and body for each clause. In the
%% definition arguments the first is the argument pattern, the second
%% is the environment variable $ENV. Be nice.
get_macro_cls(['lambda',[Arg|_]|B]) ->
[{Arg,B}]; %Only one clause here
get_macro_cls(['match-lambda'|Cls]) ->
[ {Arg,B} || [[Arg|_]|B] <- Cls ];
get_macro_cls(_) -> []. %Ignore bad formed macros
macro_clause(Args, [['when'|_]=W|Body]) ->
[Args,W,[tuple,?Q(yes),[progn|Body]]];
macro_clause(Args, Body) ->
[Args,[tuple,?Q(yes),[progn|Body]]].