Current section

Files

Jump to
efene src fn_erl_macro.erl
Raw

src/fn_erl_macro.erl

-module(fn_erl_macro).
-export([macro_defs/1, expand_macro/2, expand_macro/3, parse_to_include/1,
call_macro/3]).
macro_defs(Path) ->
{ok, _Tokens, Macros} = aleppo:process_file(Path, [{return_macros, true}]),
{ok, Macros}.
parse_to_include(Path) ->
{ok, _Tokens, Macros} = aleppo:process_file(Path, [{return_macros, true}]),
{ok, Ast} = epp:parse_file(Path, []),
{ok, remove_eof(Ast, []), Macros}.
expand_macro(Macros, Name) ->
expand_macro(Macros, Name, #{}).
get_macro(Macros, Name) ->
case dict:find(Name, Macros) of
{ok, Macro} ->
{MacroArgs, Tokens} = case Macro of
{_MArgs, _MTokens} = R -> R;
MTokens when is_list(MTokens) -> {[], MTokens}
end,
{ok, MacroArgs, Tokens};
error -> {error, {macro_not_found, Name}}
end.
call_macro(Macros, MacroName, []) ->
expand_macro(Macros, MacroName, #{});
call_macro(Macros, MacroName, Args) ->
MacroArity = length(Args),
MacroKey = {MacroName, MacroArity},
case get_macro(Macros, MacroKey) of
{ok, MacroArgVarNames, _Tokens} ->
MacroArgNames = unwrap_macro_arg_var_names(MacroArgVarNames),
WrappedArgs = [{ast, Arg} || Arg <- Args],
CallArgsList = lists:zip(MacroArgNames, WrappedArgs),
CallArgs = maps:from_list(CallArgsList),
expand_macro(Macros, MacroKey, CallArgs);
{error, _Reason} = Error -> Error
end.
expand_macro(Macros, Name, Args) ->
case get_macro(Macros, Name) of
{ok, _MacroArgs, Tokens} ->
case expand_macros(Macros, Tokens, wrap_out_args(Args)) of
{ok, ETokens, Refs} -> parse_tokens(ETokens, Refs);
{error, _Reason} = Error -> Error
end;
{error, _Reason} = Error -> Error
end.
expand_macros(Macros, Macro, Args) ->
expand_macros(Macros, Macro, Args, [], #{}).
expand_macros(_Macros, [], _Args, Accum, Refs) ->
{ok, lists:reverse(Accum), Refs};
expand_macros(Macros, [{var, _, VarName}=VarAst|T], Args, Accum, Refs) ->
case maps:get(VarName, Args, undefined) of
undefined ->
expand_macros(Macros, T, Args, [VarAst|Accum], Refs);
% when called from outside vars are ast trees, so we need to put
% a reference disguised as a var so it compiles from tokens to ast
% after this processing and then we look for the fake vars with refs
% and we put the ast trees there
%
{ast, Value} ->
{RefVar, NewRefs} = add_ref_var(Refs, Value),
expand_macros(Macros, T, Args, [RefVar|Accum], NewRefs);
% when replacing a macro inside a macro the arguments are tokens,
% this means we have to insert the tokens (reversed) now because
% we are working on tokens at this level
{lex, Value} ->
expand_macros(Macros, T, Args, lists:reverse(Value) ++ Accum, Refs)
end;
expand_macros(Macros, [{macro, {var, _, MacroName}}|T], Args, Accum, Refs) ->
case get_macro(Macros, MacroName) of
{ok, [], Tokens} ->
expand_macros(Macros, T, Args, lists:reverse(Tokens) ++ Accum, Refs);
{error, _Reason} = Error -> Error
end;
expand_macros(Macros, [{macro_string, {var, MacroInfo, VarName}}|T], Args, Accum, Refs) ->
case maps:get(VarName, Args, undefined) of
undefined -> {error, {var_not_defined, VarName}};
{ast, Value} ->
StrAst = pp_form(MacroInfo, Value),
expand_macros(Macros, T, Args, [StrAst|Accum], Refs);
{lex, Tokens} ->
case parse_aleppo_tokens(Tokens) of
{ok, [AbsForm]} ->
StrAst = pp_form(MacroInfo, AbsForm),
expand_macros(Macros, T, Args, [StrAst|Accum], Refs);
{error, _Reason} = Error -> Error
end
end;
expand_macros(Macros, [{macro, {var, _, MacroName}, MacroArgs}|T], Args, Accum, Refs) ->
MacroArity = length(MacroArgs),
case get_macro(Macros, {MacroName, MacroArity}) of
{ok, MacroArgVarNames, Tokens} ->
MacroArgNames = unwrap_macro_arg_var_names(MacroArgVarNames),
CallArgsList = lists:zip(MacroArgNames, resolve_vars(MacroArgs, Args)),
CallArgs = maps:from_list(CallArgsList),
case expand_macros(Macros, Tokens, CallArgs, [], #{}) of
{ok, ExpandedInnerMacro, InnerRefs} ->
NewRefs = maps:merge(Refs, InnerRefs),
NewAccum = lists:reverse(ExpandedInnerMacro) ++ Accum,
expand_macros(Macros, T, Args, NewAccum, NewRefs);
{error, _Reason} = Error -> Error
end;
{error, _Reason} = Error -> Error
end;
expand_macros(Macros, [H|T], Args, Accum, Refs) ->
expand_macros(Macros, T, Args, [H|Accum], Refs).
unwrap_macro_arg_var_names(Vars) ->
lists:map(fun ([{var, _, Name}]) -> Name end, Vars).
resolve_vars(Items, Args) ->
lists:map(fun ([{var, _, Name}=Ast]) -> maps:get(Name, Args, {lex, [Ast]});
(Other) -> {lex, Other}
end, Items).
add_ref_var(Refs, Value) ->
Ref = make_ref(),
NewRefs = maps:put(Ref, Value, Refs),
{{var, 1, Ref}, NewRefs}.
parse_aleppo_tokens(Tokens) ->
{ok, Tokens1} = aleppo:process_tokens(Tokens ++ [{dot, 1}]),
erl_parse:parse_exprs(remove_eof(Tokens1, [])).
parse_tokens(Tokens, Refs) ->
case parse_aleppo_tokens(Tokens) of
{ok, AstWithRefs} ->
AstReplaced = replace_ast_refs(AstWithRefs, Refs),
{ok, AstReplaced};
{error, _Reason} = Error -> Error
end.
replace_ast_refs(Ast, Refs) ->
Walker = fun (State, {var, _, Val}) when is_reference(Val) ->
{maps:get(Val, Refs), State};
(State, Other) -> {Other, State}
end,
{Node, _NewState} = ast_walk:exprs(Ast, Walker, no_state),
Node.
remove_eof([], Accum) -> lists:reverse(Accum);
remove_eof([{eof, _}|T], Accum) -> remove_eof(T, Accum);
remove_eof([H|T], Accum) -> remove_eof(T, [H|Accum]).
wrap_out_args(Args) ->
maps:map(fun (_K, {lex, _}=V) -> V;
(_K, {ast, _}=V) -> V;
(_K, V) -> {ast, V}
end, Args).
pp_form(MacroInfo, Value) ->
Line = proplists:get_value(line, MacroInfo, 1),
Str = lists:flatten(erl_pp:expr(Value)),
{string, [{line, Line}], Str}.