Current section
Files
Jump to
Current section
Files
src/scm.erl
%% Copyright (c) 2020 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 : scm.erl
%% Author : Robert Virding
%% Purpose : Lisp Flavoured Erlang scheme like macros
-module(scm).
-export(['LFE-EXPAND-EXPORTED-MACRO'/3]).
-export([mbe_syntax_rules_proc/4,mbe_syntax_rules_proc/5,
mbe_match_pat/3,mbe_get_bindings/3,mbe_expand_pattern/3]).
-import(lists, [member/2,map/2,all/2,any/2]).
%% 'LFE-EXPAND-EXPORTED-MACRO'(Name, Args, Env) -> {yes,Expansion} | no.
%% Explicitly define this function so we can call the begin, define,
%% define-syntax and let-syntax macros without having to include
%% scm.lfe. These are exactly equivalent to those macros.
'LFE-EXPAND-EXPORTED-MACRO'(MacroName, MacroArgs, _Env) ->
case [MacroName|MacroArgs] of
%% These are easy.
['begin'|Body] ->
{yes,[progn|Body]};
['define',Head|Body] ->
Exp = case lfe_lib:is_symb_list(Head) of
true ->
[hd(Head),[],[lambda,tl(Head)|Body]];
false ->
[Head,[],Body]
end,
{yes,['define-function'|Exp]};
%% Now for the syntax macros.
['define-syntax',Name,Def] ->
{Meta,Mdef} = exp_syntax(Name, Def),
{yes,['define-macro',Name,Meta,Mdef]};
['let-syntax',Defs|Body] ->
Fun = fun ([Name,Def]) ->
{_,Def} = exp_syntax(Name, Def),
[Name,Def]
end,
Mdefs = map(Fun, Defs),
{yes,['let-macro',Mdefs|Body]};
[defsyntax,Name|Rules] ->
{Meta,Mdef} = exp_rules(Name, [], Rules),
{yes,['define-macro',Name,Meta,Mdef]};
_ -> no
end.
%% exp_syntax(Name, Def) -> {Meta,Lambda | MatchLambda}.
%% N.B. New macro definition is function of 2 arguments, the whole
%% argument list of macro call, and the current macro environment.
exp_syntax(Name, Def) ->
case Def of
[macro|Cls] ->
Mcls = map(fun ([Pat|Body]) -> [[Pat,'$ENV']|Body] end, Cls),
{[],['match-lambda'|Mcls]};
['syntax-rules'|Rules] ->
exp_rules(Name, [], Rules)
end.
%% exp_rules(Name, Keywords, Rules) -> {Meta,Lambda}.
%% Expand into call function which expands macro an invocation time,
%% this saves much space and costs us nothing.
%% N.B. New macro definition is function of 2 arguments, the whole
%% argument list of macro call, and the current macro environment.
exp_rules(Name, Keywords, Rules) ->
{[],[lambda,[args,'$ENV'],
[':',scm,mbe_syntax_rules_proc,
[quote,Name],[quote,Keywords],[quote,Rules],args]]}.
%% Macro by Example
%% Proper syntax-rules which can handle ... ellipsis by Dorai Sitaram.
%%
%% While we extend patterns to include tuples and binaries as in
%% normal LFE we leave the keyword handling in even though it is
%% subsumed by quotes and not really used.
%% To make it more lispy!
-define(car(L), hd(L)).
-define(cdr(L), tl(L)).
-define(cadr(L), hd(tl(L))).
-define(cddr(L), tl(tl(L))).
-define(mbe_ellipsis(Car, Cddr), [Car,'...'|Cddr]).
is_mbe_symbol(S) ->
is_atom(S) andalso not is_boolean(S).
%% Tests if ellipsis pattern, (p ... . rest)
%% is_mbe_ellipsis(?mbe_ellipsis(_, _)) -> true;
%% is_mbe_ellipsis(_) -> false.
mbe_match_pat([quote,P], E, _) -> P =:= E;
mbe_match_pat([tuple|Ps], [tuple|Es], Ks) -> %Match tuple constructor
mbe_match_pat(Ps, Es, Ks);
mbe_match_pat([tuple|Ps], E, Ks) -> %Match literal tuple
case is_tuple(E) of
true -> mbe_match_pat(Ps, tuple_to_list(E), Ks);
false -> false
end;
mbe_match_pat(?mbe_ellipsis(Pcar, _), E, Ks) ->
case lfe_lib:is_proper_list(E) of
true ->
all(fun (X) -> mbe_match_pat(Pcar, X, Ks) end, E);
false -> false
end;
mbe_match_pat([Pcar|Pcdr], E, Ks) ->
case E of
[Ecar|Ecdr] ->
mbe_match_pat(Pcar, Ecar, Ks) andalso
mbe_match_pat(Pcdr, Ecdr, Ks);
_ -> false
end;
mbe_match_pat(Pat, E, Ks) ->
case is_mbe_symbol(Pat) of
true ->
case member(Pat, Ks) of
true -> Pat =:= E;
false -> true
end;
false -> Pat =:= E
end.
mbe_get_ellipsis_nestings(Pat, Ks) ->
m_g_e_n(Pat, Ks).
m_g_e_n([quote,_], _) -> [];
m_g_e_n([tuple|Ps], Ks) -> m_g_e_n(Ps, Ks);
m_g_e_n(?mbe_ellipsis(Pcar, Pcddr), Ks) ->
[m_g_e_n(Pcar, Ks)|m_g_e_n(Pcddr, Ks)];
m_g_e_n([Pcar|Pcdr], Ks) ->
m_g_e_n(Pcar, Ks) ++ m_g_e_n(Pcdr, Ks);
m_g_e_n(Pat, Ks) ->
case is_mbe_symbol(Pat) of
true ->
case member(Pat, Ks) of
true -> [];
false -> [Pat]
end;
false -> []
end.
mbe_ellipsis_sub_envs(Nestings, R) ->
ormap(fun (C) ->
case mbe_intersect(Nestings, ?car(C)) of
true -> ?cdr(C);
false -> false
end end, R).
%% Return first value of F applied to elements in list which is not false.
ormap(F, [H|T]) ->
case F(H) of
false -> ormap(F, T);
V -> V
end;
ormap(_, []) -> false.
mbe_intersect(V, Y) ->
case is_mbe_symbol(V) orelse is_mbe_symbol(Y) of
true -> V =:= Y;
false ->
any(fun (V0) ->
any(fun (Y0) -> mbe_intersect(V0, Y0) end, Y)
end, V)
end.
%% mbe_get_bindings(Pattern, Expression, Keywords) -> Bindings.
mbe_get_bindings([quote,_], _, _) -> [];
mbe_get_bindings([tuple|Ps], [tuple|Es], Ks) -> %Tuple constructor
mbe_get_bindings(Ps, Es, Ks);
mbe_get_bindings([tuple|Ps], E, Ks) -> %Literal tuple
mbe_get_bindings(Ps, tuple_to_list(E), Ks);
mbe_get_bindings(?mbe_ellipsis(Pcar, _), E, Ks) ->
[[mbe_get_ellipsis_nestings(Pcar, Ks) |
map(fun (X) -> mbe_get_bindings(Pcar, X, Ks) end, E)]];
mbe_get_bindings([Pcar|Pcdr], [Ecar|Ecdr], Ks) ->
mbe_get_bindings(Pcar, Ecar, Ks) ++
mbe_get_bindings(Pcdr, Ecdr, Ks);
mbe_get_bindings(Pat, E, Ks) ->
case is_mbe_symbol(Pat) of
true ->
case member(Pat, Ks) of
true -> [];
false -> [[Pat|E]]
end;
false -> []
end.
%% mbe_expand_pattern(Pattern, Bindings, Keywords) -> Form.
mbe_expand_pattern([quote,P], R, Ks) ->
[quote,mbe_expand_pattern(P, R, Ks)];
mbe_expand_pattern([tuple|Ps], R, Ks) ->
[tuple|mbe_expand_pattern(Ps, R, Ks)];
mbe_expand_pattern(?mbe_ellipsis(Pcar, Pcddr), R, Ks) ->
Nestings = mbe_get_ellipsis_nestings(Pcar, Ks),
Rr = mbe_ellipsis_sub_envs(Nestings, R),
map(fun (R0) -> mbe_expand_pattern(Pcar, R0 ++ R, Ks) end, Rr) ++
mbe_expand_pattern(Pcddr, R, Ks);
mbe_expand_pattern([Pcar|Pcdr], R, Ks) ->
[mbe_expand_pattern(Pcar, R, Ks)|
mbe_expand_pattern(Pcdr, R, Ks)];
mbe_expand_pattern(Pat, R, Ks) ->
case is_mbe_symbol(Pat) of
true ->
case member(Pat, Ks) of
true -> Pat;
false ->
case lfe:assoc(Pat, R) of
[_|Cdr] -> Cdr;
[] -> Pat
end
end;
false -> Pat
end.
%% mbe_syntax_rules_proc(Name, Keywords, Rules, Argsym, Keywordsym) ->
%% Sexpr.
%% Generate the sexpr to evaluate in a macro from Name and
%% Rules. When the sexpr is applied to arguments (in Argsym) and
%% evaluated then expansion is returned.
%% Return sexpr to evaluate.
mbe_syntax_rules_proc(Name, Ks0, Cls, Argsym, Ksym) ->
Ks = [Name|Ks0],
%% Don't prepend the macro name to the arguments!
['let',[[Ksym,[quote,Ks]]],
['cond'] ++
map(fun (C) ->
Inpat = hd(C),
Outpat = hd(tl(C)),
[[':',lfe_macro,mbe_match_pat,[quote,Inpat], Argsym, Ksym],
['let',
[[r,[':',lfe_macro,mbe_get_bindings,
[quote,Inpat],Argsym,Ksym]]],
[':',lfe_macro,mbe_expand_pattern,
[quote,Outpat],r,Ksym]]]
end, Cls) ++
[[[quote,true],[':',erlang,error,
[tuple,
[quote,expand_macro],
[cons,[quote,Name],Argsym], %??? Must check this
[quote,macro_clause]]]]]].
%% Do it all directly.
mbe_syntax_rules_proc(Name, Ks0, Cls, Args) ->
Ks = [Name|Ks0],
case ormap(fun ([Pat,Exp]) ->
case mbe_match_pat(Pat, Args, Ks) of
true ->
R = mbe_get_bindings(Pat, Args, Ks),
[mbe_expand_pattern(Exp, R, Ks)];
false -> false
end
end, Cls) of
[Res] -> Res;
false -> erlang:error({expand_macro,[Name|Args],macro_clause})
end.