From 0d15f8ab1293a611831840df75ea40eee41b86a2 Mon Sep 17 00:00:00 2001 From: Christopher Faulet Date: Tue, 1 Dec 2009 22:52:11 +0100 Subject: epp: fix bug in the function scan_undef The dict St#epp.uses that helps to find circular macros was not correctly updated. --- lib/stdlib/src/epp.erl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/stdlib/src/epp.erl b/lib/stdlib/src/epp.erl index 8b702c005b..02cfd5a378 100644 --- a/lib/stdlib/src/epp.erl +++ b/lib/stdlib/src/epp.erl @@ -576,11 +576,11 @@ add_macro_uses([{Key, Def} | Rest], D0) -> %% scan_undef(Tokens, UndefToken, From, EppState) scan_undef([{'(',_Llp},{atom,_Lm,M},{')',_Lrp},{dot,_Ld}], _Undef, From, St) -> - scan_toks(From, St#epp{macs=dict:erase({atom,M}, St#epp.macs), - uses=all_macro_uses(St#epp.macs)}); + Macs = dict:erase({atom,M}, St#epp.macs) + scan_toks(From, St#epp{macs=Macs, uses=all_macro_uses(Macs)}); scan_undef([{'(',_Llp},{var,_Lm,M},{')',_Lrp},{dot,_Ld}], _Undef, From,St) -> - scan_toks(From, St#epp{macs=dict:erase({atom,M}, St#epp.macs), - uses=all_macro_uses(St#epp.macs)}); + Macs = dict:erase({atom,M}, St#epp.macs), + scan_toks(From, St#epp{macs=Macs, uses=all_macro_uses(Macs)}); scan_undef(_Toks, Undef, From, St) -> epp_reply(From, {error,{loc(Undef),epp,{bad,undef}}}), wait_req_scan(St). -- cgit v1.2.3 From 5023556e555fd99d6b5bbe77707066371e38abd7 Mon Sep 17 00:00:00 2001 From: Christopher Faulet Date: Fri, 4 Dec 2009 21:58:10 +0100 Subject: epp: Add support of macros overloading This feature simplifies the definition of macros by avoiding to have a different name for each version (with different arities) of the same macros. New rules: - can have multiple definitions of the same macro with different arities - cannot overload macro with the same arity - the overloading of predefined macros (?MODULE, ?LINE, ...) is forbidden - the directive '-undef' removes all definitions of a macro --- lib/stdlib/src/epp.erl | 193 ++++++++++++++++++++++++------------------ lib/stdlib/test/epp_SUITE.erl | 81 ++++++++++++++++-- 2 files changed, 183 insertions(+), 91 deletions(-) diff --git a/lib/stdlib/src/epp.erl b/lib/stdlib/src/epp.erl index 02cfd5a378..b7b759b7c2 100644 --- a/lib/stdlib/src/epp.erl +++ b/lib/stdlib/src/epp.erl @@ -262,14 +262,15 @@ user_predef([{M,Val}|Pdm], Ms) when is_atom(M) -> {error,{redefine,M}}; error -> Exp = erl_parse:tokens(erl_parse:abstract(Val)), - user_predef(Pdm, dict:store({atom,M}, {none,Exp}, Ms)) + user_predef(Pdm, dict:store({atom,M}, [{none, {none,Exp}}], Ms)) end; user_predef([M|Pdm], Ms) when is_atom(M) -> case dict:find({atom,M}, Ms) of {ok,_Def} -> {error,{redefine,M}}; error -> - user_predef(Pdm, dict:store({atom,M}, {none,[{atom,1,true}]}, Ms)) + user_predef(Pdm, + dict:store({atom,M}, [{none, {none,[{atom,1,true}]}}], Ms)) end; user_predef([Md|_Pdm], _Ms) -> {error,{bad,Md}}; user_predef([], Ms) -> {ok,Ms}. @@ -476,57 +477,55 @@ scan_extends(_Ts, _As, Ms) -> Ms. %% scan_define(Tokens, DefineToken, From, EppState) -scan_define([{'(',_Lp},{atom,_Lm,M}=Mac,{',',_Lc}|Toks], _Def, From, St) -> +scan_define([{'(',_Lp},{Type,_Lm,M}=Mac,{',',_Lc}|Toks], _Def, From, St) + when Type =:= atom; Type =:= var -> case dict:find({atom,M}, St#epp.macs) of - {ok,_OldDef} -> - epp_reply(From, {error,{loc(Mac),epp,{redefine,M}}}), - wait_req_scan(St); - error -> - scan_define_cont(From, St, - {atom, M}, - {none,macro_expansion(Toks)}) + {ok, Defs} when is_list(Defs) -> + %% User defined macros: can be overloaded + case proplists:is_defined(none, Defs) of + true -> + epp_reply(From, {error,{loc(Mac),epp,{redefine,M}}}), + wait_req_scan(St); + false -> + scan_define_cont(From, St, + {atom, M}, + {none, {none,macro_expansion(Toks)}}) + end; + {ok, _PreDef} -> + %% Predefined macros: cannot be overloaded + epp_reply(From, {error,{loc(Mac),epp,{redefine,M}}}), + wait_req_scan(St); + error -> + scan_define_cont(From, St, + {atom, M}, + {none, {none,macro_expansion(Toks)}}) end; -scan_define([{'(',_Lp},{atom,_Lm,M}=Mac,{'(',_Lc}|Toks], Def, From, St) -> - case dict:find({atom,M}, St#epp.macs) of - {ok,_Def} -> - epp_reply(From, {error,{loc(Mac),epp,{redefine,M}}}), - wait_req_scan(St); - error -> - case catch macro_pars(Toks, []) of - {ok, {As, Me}} -> - scan_define_cont(From, St, - {atom, M}, - {As, Me}); - _ -> - epp_reply(From, {error,{loc(Def),epp,{bad,define}}}), - wait_req_scan(St) - end - end; -scan_define([{'(',_Lp},{var,_Lm,M}=Mac,{',',_Lc}|Toks], _Def, From, St) -> - case dict:find({atom,M}, St#epp.macs) of - {ok,_OldDef} -> - epp_reply(From, {error,{loc(Mac),epp,{redefine,M}}}), - wait_req_scan(St); - error -> - scan_define_cont(From, St, - {atom, M}, - {none,macro_expansion(Toks)}) - end; -scan_define([{'(',_Lp},{var,_Lm,M}=Mac,{'(',_Lc}|Toks], Def, From, St) -> - case dict:find({atom,M}, St#epp.macs) of - {ok,_Def} -> - epp_reply(From, {error,{loc(Mac),epp,{redefine,M}}}), - wait_req_scan(St); - error -> - case catch macro_pars(Toks, []) of - {ok, {As, Me}} -> - scan_define_cont(From, St, - {atom, M}, - {As, Me}); - _ -> - epp_reply(From, {error,{loc(Def),epp,{bad,define}}}), - wait_req_scan(St) - end +scan_define([{'(',_Lp},{Type,_Lm,M}=Mac,{'(',_Lc}|Toks], Def, From, St) + when Type =:= atom; Type =:= var -> + case catch macro_pars(Toks, []) of + {ok, {As,Me}} -> + Len = length(As), + case dict:find({atom,M}, St#epp.macs) of + {ok, Defs} when is_list(Defs) -> + %% User defined macros: can be overloaded + case proplists:is_defined(Len, Defs) of + true -> + epp_reply(From,{error,{loc(Mac),epp,{redefine,M}}}), + wait_req_scan(St); + false -> + scan_define_cont(From, St, {atom, M}, + {Len, {As, Me}}) + end; + {ok, _PreDef} -> + %% Predefined macros: cannot be overloaded + epp_reply(From, {error,{loc(Mac),epp,{redefine,M}}}), + wait_req_scan(St); + error -> + scan_define_cont(From, St, {atom, M}, {Len, {As, Me}}) + end; + _ -> + epp_reply(From, {error,{loc(Def),epp,{bad,define}}}), + wait_req_scan(St) end; scan_define(_Toks, Def, From, St) -> epp_reply(From, {error,{loc(Def),epp,{bad,define}}}), @@ -541,13 +540,11 @@ scan_define(_Toks, Def, From, St) -> %%% the information from St#epp.uses is traversed, and if a circularity %%% is detected, an error message is thrown. -scan_define_cont(F, St, M, Def) -> - Ms = dict:store(M, Def, St#epp.macs), - U = dict:store(M, macro_uses(Def), St#epp.uses), +scan_define_cont(F, St, M, {Arity, Def}) -> + Ms = dict:append_list(M, [{Arity, Def}], St#epp.macs), + U = dict:append_list(M, [{Arity, macro_uses(Def)}], St#epp.uses), scan_toks(F, St#epp{uses=U, macs=Ms}). -macro_uses(undefined) -> - undefined; macro_uses({_Args, Tokens}) -> Uses0 = macro_ref(Tokens), lists:usort(Uses0). @@ -556,31 +553,27 @@ macro_ref([]) -> []; macro_ref([{'?', _}, {'?', _} | Rest]) -> macro_ref(Rest); -macro_ref([{'?', _}, {atom, _, A} | Rest]) -> - [{atom, A} | macro_ref(Rest)]; -macro_ref([{'?', _}, {var, _, A} | Rest]) -> - [{atom, A} | macro_ref(Rest)]; +macro_ref([{'?', _}, {Type, Lm, A} | Rest]) when Type =:= atom; Type =:= var -> + try + Arity = count_args(Rest, Lm, A), + [{{atom, A}, Arity} | macro_ref(Rest)] + catch + _:_ -> + macro_ref(Rest) + end; macro_ref([_Token | Rest]) -> macro_ref(Rest). -all_macro_uses(D0) -> - L = dict:to_list(D0), - D = dict:new(), - add_macro_uses(L, D). - -add_macro_uses([], D) -> - D; -add_macro_uses([{Key, Def} | Rest], D0) -> - add_macro_uses(Rest, dict:store(Key, macro_uses(Def), D0)). - %% scan_undef(Tokens, UndefToken, From, EppState) scan_undef([{'(',_Llp},{atom,_Lm,M},{')',_Lrp},{dot,_Ld}], _Undef, From, St) -> - Macs = dict:erase({atom,M}, St#epp.macs) - scan_toks(From, St#epp{macs=Macs, uses=all_macro_uses(Macs)}); + Macs = dict:erase({atom,M}, St#epp.macs), + Uses = dict:erase({atom,M}, St#epp.uses), + scan_toks(From, St#epp{macs=Macs, uses=Uses}); scan_undef([{'(',_Llp},{var,_Lm,M},{')',_Lrp},{dot,_Ld}], _Undef, From,St) -> Macs = dict:erase({atom,M}, St#epp.macs), - scan_toks(From, St#epp{macs=Macs, uses=all_macro_uses(Macs)}); + Uses = dict:erase({atom,M}, St#epp.uses), + scan_toks(From, St#epp{macs=Macs, uses=Uses}); scan_undef(_Toks, Undef, From, St) -> epp_reply(From, {error,{loc(Undef),epp,{bad,undef}}}), wait_req_scan(St). @@ -819,42 +812,56 @@ expand_macros(Type, MacT, M, Toks, Ms0) -> %% (Type will always be 'atom') {Ms, U} = Ms0, Lm = loc(MacT), - check_uses([{Type,M}], [], U, Lm), Tinfo = element(2, MacT), - case dict:find({Type,M}, Ms) of + case expand_macro1(Type, Lm, M, Toks, Ms) of {ok,{none,Exp}} -> - expand_macros(expand_macro(Exp, Tinfo, Toks, dict:new()), Ms0); + check_uses([{{Type,M}, none}], [], U, Lm), + Toks1 = expand_macros(expand_macro(Exp, Tinfo, [], dict:new()), Ms0), + expand_macros(Toks1++Toks, Ms0); {ok,{As,Exp}} -> + check_uses([{{Type,M}, length(As)}], [], U, Lm), {Bs,Toks1} = bind_args(Toks, Lm, M, As, dict:new()), - %%io:format("Bound arguments to macro ~w (~w)~n", [M,Bs]), expand_macros(expand_macro(Exp, Tinfo, Toks1, Bs), Ms0); {ok,undefined} -> throw({error,Lm,{undefined,M}}); + {ok,mismatch} -> + throw({error,Lm,{mismatch,M}}); error -> throw({error,Lm,{undefined,M}}) end. -check_uses(undefined, _Anc, _U, _Lm) -> - ok; +expand_macro1(Type, Lm, M, Toks, Ms) -> + Arity = count_args(Toks, Lm, M), + case dict:find({Type,M}, Ms) of + {ok, Defs} when is_list(Defs) -> %% User defined macro + {ok, proplists:get_value(Arity, Defs, + proplists:get_value(none, Defs, + mismatch))}; + {ok, PreDef} -> %% Predefined macro + {ok, PreDef}; + error -> + error + end. + check_uses([], _Anc, _U, _Lm) -> ok; check_uses([M|Rest], Anc, U, Lm) -> case lists:member(M, Anc) of true -> - {_, Name} = M, + {{_, Name},_} = M, throw({error,Lm,{circular,Name}}); false -> L = get_macro_uses(M, U), check_uses(L, [M|Anc], U, Lm), check_uses(Rest, Anc, U, Lm) end. - -get_macro_uses(M, U) -> + +get_macro_uses({M,Arity}, U) -> case dict:find(M, U) of error -> []; {ok, L} -> - L + proplists:get_value(Arity, L, proplists:get_value(none, L, [])) end. %% Macro expansion @@ -907,6 +914,26 @@ store_arg(L, M, _A, [], _Bs) -> store_arg(_L, _M, A, Arg, Bs) -> dict:store(A, Arg, Bs). +%% count_args(Tokens, MacroLine, MacroName) +%% Count the number of arguments in a macro call. +count_args([{'(', _Llp},{')',_Lrp}|_Toks], _Lm, _M) -> + 0; +count_args([{'(',_Llp}|Toks0], Lm, M) -> + {_Arg,Toks1} = macro_arg(Toks0, [], []), + count_args(Toks1, Lm, M, 1); +count_args(_Toks, _Lm, _M) -> + none. + +count_args([{')',_Lrp}|_Toks], _Lm, _M, NbArgs) -> + NbArgs; +count_args([{',',_Lc}|Toks0], Lm, M, NbArgs) -> + {_Arg,Toks1} = macro_arg(Toks0, [], []), + count_args(Toks1, Lm, M, NbArgs+1); +count_args([], Lm, M, _NbArgs) -> + throw({error,Lm,{arg_error,M}}); +count_args(_Toks, Lm, M, _NbArgs) -> + throw({error,Lm,{mismatch,M}}). + %% macro_arg([Tok], [ClosePar], [ArgTok]) -> {[ArgTok],[RestTok]}. %% Collect argument tokens until we hit a ',' or a ')'. We know a %% enough about syntax to recognise "open parentheses" and keep diff --git a/lib/stdlib/test/epp_SUITE.erl b/lib/stdlib/test/epp_SUITE.erl index 67e20fd2e1..0055aedda9 100644 --- a/lib/stdlib/test/epp_SUITE.erl +++ b/lib/stdlib/test/epp_SUITE.erl @@ -23,7 +23,7 @@ upcase_mac/1, upcase_mac_1/1, upcase_mac_2/1, variable/1, variable_1/1, otp_4870/1, otp_4871/1, otp_5362/1, pmod/1, not_circular/1, skip_header/1, otp_6277/1, otp_7702/1, - otp_8130/1]). + otp_8130/1, overload_mac/1]). -export([epp_parse_erl_form/2]). @@ -61,8 +61,9 @@ fin_per_testcase(_, Config) -> all(doc) -> ["Test cases for epp."]; all(suite) -> - [rec_1, upcase_mac, predef_mac, variable, otp_4870, otp_4871, otp_5362, - pmod, not_circular, skip_header, otp_6277, otp_7702, otp_8130]. + [rec_1, upcase_mac, predef_mac, variable, otp_4870, otp_4871, otp_5362, + pmod, not_circular, skip_header, otp_6277, otp_7702, otp_8130, + overload_mac]. rec_1(doc) -> ["Recursive macros hang or crash epp (OTP-1398)."]; @@ -718,22 +719,22 @@ otp_8130(Config) when is_list(Config) -> {otp_8130_c17, <<"\n-define(A(B), B).\n" "-define(A, 1).\n">>, - {errors,[{{3,9},epp,{redefine,'A'}}],[]}}, + []}, {otp_8130_c18, <<"\n-define(A, 1).\n" "-define(A(B), B).\n">>, - {errors,[{{3,9},epp,{redefine,'A'}}],[]}}, + []}, {otp_8130_c19, <<"\n-define(a(B), B).\n" "-define(a, 1).\n">>, - {errors,[{{3,9},epp,{redefine,a}}],[]}}, + []}, {otp_8130_c20, <<"\n-define(a, 1).\n" "-define(a(B), B).\n">>, - {errors,[{{3,9},epp,{redefine,a}}],[]}}, + []}, {otp_8130_c21, <<"\n-define(A(B, B), B).\n">>, @@ -821,7 +822,8 @@ macs(Epp) -> macro(Epp, N) -> case lists:keyfind({atom,N}, 1, epp:macro_defs(Epp)) of false -> false; - {{atom,N},{_,V}} -> V + {{atom,N},{_,V}} -> V; + {{atom,N},Defs} -> lists:append([V || {_,{_,V}} <- Defs]) end. ifdef(Config) -> @@ -1030,6 +1032,69 @@ ifdef(Config) -> ], ?line [] = run(Config, Ts). + + +overload_mac(doc) -> + ["Advanced test on overloading macros."]; +overload_mac(suite) -> + []; +overload_mac(Config) when is_list(Config) -> + Cs = [ + %% '-undef' removes all definitions of a macro + {overload_mac_c1, + <<"-define(A, a).\n" + "-define(A(X), X).\n" + "-undef(A).\n" + "t1() -> ?A.\n", + "t2() -> ?A(1).">>, + {errors,[{{4,9},epp,{undefined,'A'}}, + {{5,9},epp,{undefined,'A'}}],[]}}, + + %% cannot overload predefined macros + {overload_mac_c2, + <<"-define(MODULE(X), X).">>, + {errors,[{{1,9},epp,{redefine,'MODULE'}}],[]}}, + + %% cannot overload macros with same arity + {overload_mac_c3, + <<"-define(A(X), X).\n" + "-define(A(Y), Y).">>, + {errors,[{{2,9},epp,{redefine,'A'}}],[]}} + ], + ?line [] = compile(Config, Cs), + + Ts = [ + {overload_mac_r1, + <<"-define(A, 1).\n" + "-define(A(X), X).\n" + "-define(A(X, Y), {X, Y}).\n" + "t() -> {?A, ?A(2), ?A(3, 4)}.">>, + {1, 2, {3, 4}}}, + + {overload_mac_r2, + <<"-define(A, 1).\n" + "-define(A(X), X).\n" + "t() -> ?A(?A).">>, + 1}, + + {overload_mac_r3, + <<"-define(A, a).\n" + "-define(A(X,Y), {X,Y}).\n" + "a(X) -> ?A(X,X).\n" + "t() -> ?A(1).">>, + {1,1}}, + + {overload_mac_r4, + <<"-define(A, ?B).\n" + "-define(B, a).\n" + "-define(B(X), {b,X}).\n" + "a(X) -> X.\n" + "t() -> ?A(1).">>, + 1} + ], + ?line [] = run(Config, Ts). + + check(Config, Tests) -> eval_tests(Config, fun check_test/2, Tests). -- cgit v1.2.3 From 27d7ca04eb2933345b5be50870a197a3b19588c0 Mon Sep 17 00:00:00 2001 From: Christopher Faulet Date: Mon, 7 Dec 2009 16:47:44 +0100 Subject: epp: change rules to choose the right version of a macro Now, when we have only the constant definition of a macro (without arguments), we always use it. In all other cases, we try to find the exact matching definition. We throw an error if we don't find it. --- lib/stdlib/src/epp.erl | 95 ++++++++++++++++++++++++++----------------- lib/stdlib/test/epp_SUITE.erl | 30 +++++++------- 2 files changed, 72 insertions(+), 53 deletions(-) diff --git a/lib/stdlib/src/epp.erl b/lib/stdlib/src/epp.erl index b7b759b7c2..d91a4408d7 100644 --- a/lib/stdlib/src/epp.erl +++ b/lib/stdlib/src/epp.erl @@ -111,18 +111,24 @@ format_error({bad,W}) -> io_lib:format("badly formed '~s'", [W]); format_error({call,What}) -> io_lib:format("illegal macro call '~s'",[What]); -format_error({undefined,M}) -> - io_lib:format("undefined macro '~w'", [M]); +format_error({undefined,M,none}) -> + io_lib:format("undefined macro '~s'", [M]); +format_error({undefined,M,A}) -> + io_lib:format("undefined macro '~s/~p'", [M,A]); format_error({depth,What}) -> io_lib:format("~s too deep",[What]); format_error({mismatch,M}) -> - io_lib:format("argument mismatch for macro '~w'", [M]); + io_lib:format("argument mismatch for macro '~s'", [M]); format_error({arg_error,M}) -> - io_lib:format("badly formed argument for macro '~w'", [M]); + io_lib:format("badly formed argument for macro '~s'", [M]); format_error({redefine,M}) -> - io_lib:format("redefining macro '~w'", [M]); -format_error({circular,M}) -> - io_lib:format("circular macro '~w'", [M]); + io_lib:format("redefining macro '~s'", [M]); +format_error({redefine_predef,M}) -> + io_lib:format("redefining predefined macro '~s'", [M]); +format_error({circular,M,none}) -> + io_lib:format("circular macro '~s'", [M]); +format_error({circular,M,A}) -> + io_lib:format("circular macro '~s/~p'", [M,A]); format_error({include,W,F}) -> io_lib:format("can't find include ~s \"~s\"", [W,F]); format_error({illegal,How,What}) -> @@ -258,16 +264,20 @@ user_predef([{M,Val,redefine}|Pdm], Ms) when is_atom(M) -> user_predef(Pdm, dict:store({atom,M}, {none,Exp}, Ms)); user_predef([{M,Val}|Pdm], Ms) when is_atom(M) -> case dict:find({atom,M}, Ms) of - {ok,_Def} -> + {ok,_Defs} when is_list(_Defs) -> %% User defined macros {error,{redefine,M}}; + {ok,_Def} -> %% Predefined macros + {error,{redefine_predef,M}}; error -> Exp = erl_parse:tokens(erl_parse:abstract(Val)), user_predef(Pdm, dict:store({atom,M}, [{none, {none,Exp}}], Ms)) end; user_predef([M|Pdm], Ms) when is_atom(M) -> case dict:find({atom,M}, Ms) of - {ok,_Def} -> + {ok,_Defs} when is_list(_Defs) -> %% User defined macros {error,{redefine,M}}; + {ok,_Def} -> %% Predefined macros + {error,{redefine_predef,M}}; error -> user_predef(Pdm, dict:store({atom,M}, [{none, {none,[{atom,1,true}]}}], Ms)) @@ -493,7 +503,7 @@ scan_define([{'(',_Lp},{Type,_Lm,M}=Mac,{',',_Lc}|Toks], _Def, From, St) end; {ok, _PreDef} -> %% Predefined macros: cannot be overloaded - epp_reply(From, {error,{loc(Mac),epp,{redefine,M}}}), + epp_reply(From, {error,{loc(Mac),epp,{redefine_predef,M}}}), wait_req_scan(St); error -> scan_define_cont(From, St, @@ -518,7 +528,7 @@ scan_define([{'(',_Lp},{Type,_Lm,M}=Mac,{'(',_Lc}|Toks], Def, From, St) end; {ok, _PreDef} -> %% Predefined macros: cannot be overloaded - epp_reply(From, {error,{loc(Mac),epp,{redefine,M}}}), + epp_reply(From, {error,{loc(Mac),epp,{redefine_predef,M}}}), wait_req_scan(St); error -> scan_define_cont(From, St, {atom, M}, {Len, {As, Me}}) @@ -541,9 +551,15 @@ scan_define(_Toks, Def, From, St) -> %%% is detected, an error message is thrown. scan_define_cont(F, St, M, {Arity, Def}) -> - Ms = dict:append_list(M, [{Arity, Def}], St#epp.macs), - U = dict:append_list(M, [{Arity, macro_uses(Def)}], St#epp.uses), - scan_toks(F, St#epp{uses=U, macs=Ms}). + try + Ms = dict:append_list(M, [{Arity, Def}], St#epp.macs), + U = dict:append_list(M, [{Arity, macro_uses(Def)}], St#epp.uses), + scan_toks(F, St#epp{uses=U, macs=Ms}) + catch + _:{error, Line, Reason} -> + epp_reply(F, {error,{Line,epp,Reason}}), + wait_req_scan(St) + end. macro_uses({_Args, Tokens}) -> Uses0 = macro_ref(Tokens), @@ -553,14 +569,12 @@ macro_ref([]) -> []; macro_ref([{'?', _}, {'?', _} | Rest]) -> macro_ref(Rest); -macro_ref([{'?', _}, {Type, Lm, A} | Rest]) when Type =:= atom; Type =:= var -> - try - Arity = count_args(Rest, Lm, A), - [{{atom, A}, Arity} | macro_ref(Rest)] - catch - _:_ -> - macro_ref(Rest) - end; +macro_ref([{'?', _}, {atom, Lm, A} | Rest]) -> + Arity = count_args(Rest, Lm, A), + [{{atom, A}, Arity} | macro_ref(Rest)]; +macro_ref([{'?', _}, {var, Lm, A} | Rest]) -> + Arity = count_args(Rest, Lm, A), + [{{atom, A}, Arity} | macro_ref(Rest)]; macro_ref([_Token | Rest]) -> macro_ref(Rest). @@ -821,26 +835,27 @@ expand_macros(Type, MacT, M, Toks, Ms0) -> {ok,{As,Exp}} -> check_uses([{{Type,M}, length(As)}], [], U, Lm), {Bs,Toks1} = bind_args(Toks, Lm, M, As, dict:new()), - expand_macros(expand_macro(Exp, Tinfo, Toks1, Bs), Ms0); - {ok,undefined} -> - throw({error,Lm,{undefined,M}}); - {ok,mismatch} -> - throw({error,Lm,{mismatch,M}}); - error -> - throw({error,Lm,{undefined,M}}) + expand_macros(expand_macro(Exp, Tinfo, Toks1, Bs), Ms0) end. expand_macro1(Type, Lm, M, Toks, Ms) -> Arity = count_args(Toks, Lm, M), case dict:find({Type,M}, Ms) of - {ok, Defs} when is_list(Defs) -> %% User defined macro - {ok, proplists:get_value(Arity, Defs, - proplists:get_value(none, Defs, - mismatch))}; + error -> %% macro not found + throw({error,Lm,{undefined,M,Arity}}); + {ok, undefined} -> %% Predefined macro without definition + throw({error,Lm,{undefined,M,Arity}}); + {ok, [{none, Def}]} -> + {ok, Def}; + {ok, Defs} when is_list(Defs) -> + case proplists:get_value(Arity, Defs) of + undefined -> + throw({error,Lm,{mismatch,M}}); + Def -> + {ok, Def} + end; {ok, PreDef} -> %% Predefined macro - {ok, PreDef}; - error -> - error + {ok, PreDef} end. check_uses([], _Anc, _U, _Lm) -> @@ -848,8 +863,8 @@ check_uses([], _Anc, _U, _Lm) -> check_uses([M|Rest], Anc, U, Lm) -> case lists:member(M, Anc) of true -> - {{_, Name},_} = M, - throw({error,Lm,{circular,Name}}); + {{_, Name},Arity} = M, + throw({error,Lm,{circular,Name,Arity}}); false -> L = get_macro_uses(M, U), check_uses(L, [M|Anc], U, Lm), @@ -918,6 +933,8 @@ store_arg(_L, _M, A, Arg, Bs) -> %% Count the number of arguments in a macro call. count_args([{'(', _Llp},{')',_Lrp}|_Toks], _Lm, _M) -> 0; +count_args([{'(', _Llp},{',',_Lc}|_Toks], Lm, M) -> + throw({error,Lm,{arg_error,M}}); count_args([{'(',_Llp}|Toks0], Lm, M) -> {_Arg,Toks1} = macro_arg(Toks0, [], []), count_args(Toks1, Lm, M, 1); @@ -926,6 +943,8 @@ count_args(_Toks, _Lm, _M) -> count_args([{')',_Lrp}|_Toks], _Lm, _M, NbArgs) -> NbArgs; +count_args([{',',_Lc},{')',_Lrp}|_Toks], Lm, M, _NbArgs) -> + throw({error,Lm,{arg_error,M}}); count_args([{',',_Lc}|Toks0], Lm, M, NbArgs) -> {_Arg,Toks1} = macro_arg(Toks0, [], []), count_args(Toks1, Lm, M, NbArgs+1); diff --git a/lib/stdlib/test/epp_SUITE.erl b/lib/stdlib/test/epp_SUITE.erl index 0055aedda9..25dd69b6dd 100644 --- a/lib/stdlib/test/epp_SUITE.erl +++ b/lib/stdlib/test/epp_SUITE.erl @@ -467,7 +467,7 @@ otp_6277(Config) when is_list(Config) -> -define(ASSERT, ?MODULE). ?ASSERT().">>, - [{error,{{4,16},epp,{undefined,'MODULE'}}}]}], + [{error,{{4,16},epp,{undefined,'MODULE', none}}}]}], ?line [] = check(Config, Ts), ok. @@ -674,7 +674,7 @@ otp_8130(Config) when is_list(Config) -> {otp_8130_c7, <<"\nt() -> ?A.\n">>, - {errors,[{{2,9},epp,{undefined,'A'}}],[]}}, + {errors,[{{2,9},epp,{undefined,'A', none}}],[]}}, {otp_8130_c8, <<"\n-include_lib(\"$apa/foo.hrl\").\n">>, @@ -684,7 +684,7 @@ otp_8130(Config) when is_list(Config) -> {otp_8130_c9, <<"-define(S, ?S).\n" "t() -> ?S.\n">>, - {errors,[{{2,9},epp,{circular,'S'}}],[]}}, + {errors,[{{2,9},epp,{circular,'S', none}}],[]}}, {otp_8130_c10, <<"\n-file.">>, @@ -746,7 +746,7 @@ otp_8130(Config) when is_list(Config) -> {otp_8130_c23, <<"\n-file(?b, 3).\n">>, - {errors,[{{2,8},epp,{undefined,b}}],[]}}, + {errors,[{{2,8},epp,{undefined,b, none}}],[]}}, {otp_8130_c24, <<"\n-include(\"no such file.erl\").\n">>, @@ -1047,19 +1047,26 @@ overload_mac(Config) when is_list(Config) -> "-undef(A).\n" "t1() -> ?A.\n", "t2() -> ?A(1).">>, - {errors,[{{4,9},epp,{undefined,'A'}}, - {{5,9},epp,{undefined,'A'}}],[]}}, + {errors,[{{4,9},epp,{undefined,'A', none}}, + {{5,9},epp,{undefined,'A', 1}}],[]}}, %% cannot overload predefined macros {overload_mac_c2, <<"-define(MODULE(X), X).">>, - {errors,[{{1,9},epp,{redefine,'MODULE'}}],[]}}, + {errors,[{{1,9},epp,{redefine_predef,'MODULE'}}],[]}}, %% cannot overload macros with same arity {overload_mac_c3, <<"-define(A(X), X).\n" "-define(A(Y), Y).">>, - {errors,[{{2,9},epp,{redefine,'A'}}],[]}} + {errors,[{{2,9},epp,{redefine,'A'}}],[]}}, + + {overload_mac_c4, + <<"-define(A, a).\n" + "-define(A(X,Y), {X,Y}).\n" + "a(X) -> X.\n" + "t() -> ?A(1).">>, + {errors,[{{4,9},epp,{mismatch,'A'}}],[]}} ], ?line [] = compile(Config, Cs), @@ -1078,13 +1085,6 @@ overload_mac(Config) when is_list(Config) -> 1}, {overload_mac_r3, - <<"-define(A, a).\n" - "-define(A(X,Y), {X,Y}).\n" - "a(X) -> ?A(X,X).\n" - "t() -> ?A(1).">>, - {1,1}}, - - {overload_mac_r4, <<"-define(A, ?B).\n" "-define(B, a).\n" "-define(B(X), {b,X}).\n" -- cgit v1.2.3 From 4c182740d7180d3bdc8573ca17905898dcc2b819 Mon Sep 17 00:00:00 2001 From: Christopher Faulet Date: Thu, 10 Dec 2009 11:02:23 +0100 Subject: update the documentation on preprocessor in the reference manual New section added on Macros Overloading --- system/doc/reference_manual/macros.xml | 83 ++++++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) diff --git a/system/doc/reference_manual/macros.xml b/system/doc/reference_manual/macros.xml index a1ba182eff..9d09ba9cb9 100644 --- a/system/doc/reference_manual/macros.xml +++ b/system/doc/reference_manual/macros.xml @@ -139,6 +139,89 @@ bar(X) -> +
+ Macros Overloading + +

This section describes a feature introduced in R13B04. It remains fully + compatible with old code but previous versions of erlang does not support + the macros overloading.

+
+

It is possible to have more than one definition of the same macro, except + for predefined macros. Only user-defined macros are considered here. In + order to overload a macro, a different signature should be used for each + overloaded version. A macro signature consists of its name and its number of + arguments.

+ + + Object-like macro has no arguments + +-define(M, io:format("object-like macro")). + + Function-like macro has 0 or more arguments. + +-define(M(), io:format("function-like macro with 0 argument")). +-define(M(X), io:format("function-like macro with 1 argument: ~p", [X])). +-define(M(X, Y), io:format("function-like macro with 2 arguments: (~p, ~p)", [X,Y])). + + +

An object-like macro should not be confused with a function-like macro + with 0 argument.

+ +

When a macro is used, the preprocessor selects the proper macro following + these rules:

+ + if there is only the object-like definition, the preprocessor uses + it. Example: + +-define(FUN, now). +... +call() -> + {?FUN, ?FUN()}. +This will be expended to: + +call() -> + {now, now()}. + + if the preprocessor recognizes an object-like macro call, and if its + definition exists, the preprocessor uses it. Example: + +-define(MOD, erlang). +-define(MOD(X), X). +... +call() -> + ?MOD:module_info(). +This will be expended to: + +call() -> + erlang:module_info(). + + if the preprocessor recognizes a function-like macro call, the + preprocessor chooses the definition with the same number of + arguments. Example: + +-define(CALL(Fun), Fun()). +-define(CALL(Mod, Fun), Mod:Fun()). +... +call() -> + ?CALL(kernel, module_info). +This will be expended to: + +call() -> + kernel:module_info(). + + in all other cases, the preprocessor throws an + error. Example: + +-define(M, erlang). +-define(M(X), X). +... +call() -> + ?M(kernel, module_info). +The compiler will fail with error argument mismatch for macro 'M' + + +
+
Flow Control in Macros

The following macro directives are supplied:

-- cgit v1.2.3 From c41689666b0410b51c1fe6acb9e2ec517fdfcad9 Mon Sep 17 00:00:00 2001 From: Hans Bolinder Date: Mon, 1 Feb 2010 14:38:07 +0100 Subject: documentation: Macros overloading partly rewritten --- system/doc/reference_manual/macros.xml | 113 +++++++++------------------------ 1 file changed, 31 insertions(+), 82 deletions(-) diff --git a/system/doc/reference_manual/macros.xml b/system/doc/reference_manual/macros.xml index 9d09ba9cb9..9dd5fc79bd 100644 --- a/system/doc/reference_manual/macros.xml +++ b/system/doc/reference_manual/macros.xml @@ -4,7 +4,7 @@
- 20032009 + 20032010 Ericsson AB. All Rights Reserved. @@ -13,12 +13,12 @@ compliance with the License. You should have received a copy of the Erlang Public License along with this software. If not, it can be retrieved online at http://www.erlang.org/. - + Software distributed under the License is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the specific language governing rights and limitations under the License. - + The Preprocessor @@ -141,85 +141,34 @@ bar(X) ->
Macros Overloading - -

This section describes a feature introduced in R13B04. It remains fully - compatible with old code but previous versions of erlang does not support - the macros overloading.

-
-

It is possible to have more than one definition of the same macro, except - for predefined macros. Only user-defined macros are considered here. In - order to overload a macro, a different signature should be used for each - overloaded version. A macro signature consists of its name and its number of - arguments.

- - - Object-like macro has no arguments - --define(M, io:format("object-like macro")). - - Function-like macro has 0 or more arguments. - --define(M(), io:format("function-like macro with 0 argument")). --define(M(X), io:format("function-like macro with 1 argument: ~p", [X])). --define(M(X, Y), io:format("function-like macro with 2 arguments: (~p, ~p)", [X,Y])). - - -

An object-like macro should not be confused with a function-like macro - with 0 argument.

- -

When a macro is used, the preprocessor selects the proper macro following - these rules:

- - if there is only the object-like definition, the preprocessor uses - it. Example: - --define(FUN, now). -... -call() -> - {?FUN, ?FUN()}. -This will be expended to: - -call() -> - {now, now()}. - - if the preprocessor recognizes an object-like macro call, and if its - definition exists, the preprocessor uses it. Example: - --define(MOD, erlang). --define(MOD(X), X). -... -call() -> - ?MOD:module_info(). -This will be expended to: - -call() -> - erlang:module_info(). - - if the preprocessor recognizes a function-like macro call, the - preprocessor chooses the definition with the same number of - arguments. Example: - --define(CALL(Fun), Fun()). --define(CALL(Mod, Fun), Mod:Fun()). -... -call() -> - ?CALL(kernel, module_info). -This will be expended to: - -call() -> - kernel:module_info(). - - in all other cases, the preprocessor throws an - error. Example: - --define(M, erlang). --define(M(X), X). -... -call() -> - ?M(kernel, module_info). -The compiler will fail with error argument mismatch for macro 'M' - - +

It is possible to overload macros, except for predefined + macros. An overloaded macro has more than one definition, + each with a different number of arguments.

+

The feature was added in Erlang 5.7.5/OTP R13B04.

+

A macro ?Func(Arg1,...,ArgN) with a (possibly empty) + list of arguments results in an error message if there is at + least one definition of Func with arguments, but none + with N arguments.

+

Assuming these definitions:

+ +-define(F0(), c). +-define(F1(A), A). +-define(C, m:f). +

the following will not work:

+ +f0() -> + ?F0. % No, an empty list of arguments expected. + +f1(A) -> + ?F1(A, A). % No, exactly one argument expected. +

On the other hand,

+ +f() -> + ?C(). +

will expand to

+ +f() -> + m:f().
-- cgit v1.2.3 From ec618ee396decf7afe9ef86b80d205649914badb Mon Sep 17 00:00:00 2001 From: Hans Bolinder Date: Mon, 1 Feb 2010 16:28:30 +0100 Subject: Minor corrections and clean-ups --- lib/stdlib/src/epp.erl | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/lib/stdlib/src/epp.erl b/lib/stdlib/src/epp.erl index d91a4408d7..424aed3d2e 100644 --- a/lib/stdlib/src/epp.erl +++ b/lib/stdlib/src/epp.erl @@ -1,19 +1,19 @@ %% %% %CopyrightBegin% -%% -%% Copyright Ericsson AB 1996-2009. All Rights Reserved. -%% +%% +%% Copyright Ericsson AB 1996-2010. All Rights Reserved. +%% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in %% compliance with the License. You should have received a copy of the %% Erlang Public License along with this software. If not, it can be %% retrieved online at http://www.erlang.org/. -%% +%% %% Software distributed under the License is distributed on an "AS IS" %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See %% the License for the specific language governing rights and limitations %% under the License. -%% +%% %% %CopyrightEnd% -module(epp). @@ -528,6 +528,7 @@ scan_define([{'(',_Lp},{Type,_Lm,M}=Mac,{'(',_Lc}|Toks], Def, From, St) end; {ok, _PreDef} -> %% Predefined macros: cannot be overloaded + %% (There are currently no predefined F(...) macros.) epp_reply(From, {error,{loc(Mac),epp,{redefine_predef,M}}}), wait_req_scan(St); error -> @@ -551,12 +552,12 @@ scan_define(_Toks, Def, From, St) -> %%% is detected, an error message is thrown. scan_define_cont(F, St, M, {Arity, Def}) -> - try - Ms = dict:append_list(M, [{Arity, Def}], St#epp.macs), - U = dict:append_list(M, [{Arity, macro_uses(Def)}], St#epp.uses), - scan_toks(F, St#epp{uses=U, macs=Ms}) + Ms = dict:append_list(M, [{Arity, Def}], St#epp.macs), + try dict:append_list(M, [{Arity, macro_uses(Def)}], St#epp.uses) of + U -> + scan_toks(F, St#epp{uses=U, macs=Ms}) catch - _:{error, Line, Reason} -> + {error, Line, Reason} -> epp_reply(F, {error,{Line,epp,Reason}}), wait_req_scan(St) end. @@ -904,7 +905,7 @@ expand_macros([T|Ts], Ms) -> expand_macros([], _Ms) -> []. %% bind_args(Tokens, MacroLocation, MacroName, ArgumentVars, Bindings) -%% Collect the arguments to a macro call and check for correct number. +%% Collect the arguments to a macro call. bind_args([{'(',_Llp},{')',_Lrp}|Toks], _Lm, _M, [], Bs) -> {Bs,Toks}; @@ -912,7 +913,7 @@ bind_args([{'(',_Llp}|Toks0], Lm, M, [A|As], Bs) -> {Arg,Toks1} = macro_arg(Toks0, [], []), macro_args(Toks1, Lm, M, As, store_arg(Lm, M, A, Arg, Bs)); bind_args(_Toks, Lm, M, _As, _Bs) -> - throw({error,Lm,{mismatch,M}}). + throw({error,Lm,{mismatch,M}}). % Cannot happen. macro_args([{')',_Lrp}|Toks], _Lm, _M, [], Bs) -> {Bs,Toks}; @@ -920,9 +921,9 @@ macro_args([{',',_Lc}|Toks0], Lm, M, [A|As], Bs) -> {Arg,Toks1} = macro_arg(Toks0, [], []), macro_args(Toks1, Lm, M, As, store_arg(Lm, M, A, Arg, Bs)); macro_args([], Lm, M, _As, _Bs) -> - throw({error,Lm,{arg_error,M}}); + throw({error,Lm,{arg_error,M}}); % Cannot happen. macro_args(_Toks, Lm, M, _As, _Bs) -> - throw({error,Lm,{mismatch,M}}). + throw({error,Lm,{mismatch,M}}). % Cannot happen. store_arg(L, M, _A, [], _Bs) -> throw({error,L,{mismatch,M}}); @@ -951,7 +952,7 @@ count_args([{',',_Lc}|Toks0], Lm, M, NbArgs) -> count_args([], Lm, M, _NbArgs) -> throw({error,Lm,{arg_error,M}}); count_args(_Toks, Lm, M, _NbArgs) -> - throw({error,Lm,{mismatch,M}}). + throw({error,Lm,{mismatch,M}}). % Cannot happen. %% macro_arg([Tok], [ClosePar], [ArgTok]) -> {[ArgTok],[RestTok]}. %% Collect argument tokens until we hit a ',' or a ')'. We know a -- cgit v1.2.3 From 77a1272d90f93fba24d56b5121ad13cb4ab57f84 Mon Sep 17 00:00:00 2001 From: Hans Bolinder Date: Mon, 1 Feb 2010 16:20:16 +0100 Subject: epp_SUITE: Increase code coverage --- lib/stdlib/test/epp_SUITE.erl | 58 +++++++++++++++++++++++++++++++++++++------ 1 file changed, 51 insertions(+), 7 deletions(-) diff --git a/lib/stdlib/test/epp_SUITE.erl b/lib/stdlib/test/epp_SUITE.erl index 25dd69b6dd..9a3ae0baf5 100644 --- a/lib/stdlib/test/epp_SUITE.erl +++ b/lib/stdlib/test/epp_SUITE.erl @@ -1,19 +1,19 @@ %% %% %CopyrightBegin% -%% -%% Copyright Ericsson AB 1998-2009. All Rights Reserved. -%% +%% +%% Copyright Ericsson AB 1998-2010. All Rights Reserved. +%% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in %% compliance with the License. You should have received a copy of the %% Erlang Public License along with this software. If not, it can be %% retrieved online at http://www.erlang.org/. -%% +%% %% Software distributed under the License is distributed on an "AS IS" %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See %% the License for the specific language governing rights and limitations %% under the License. -%% +%% %% %CopyrightEnd% -module(epp_SUITE). @@ -23,7 +23,7 @@ upcase_mac/1, upcase_mac_1/1, upcase_mac_2/1, variable/1, variable_1/1, otp_4870/1, otp_4871/1, otp_5362/1, pmod/1, not_circular/1, skip_header/1, otp_6277/1, otp_7702/1, - otp_8130/1, overload_mac/1]). + otp_8130/1, overload_mac/1, otp_8388/1]). -export([epp_parse_erl_form/2]). @@ -63,7 +63,7 @@ all(doc) -> all(suite) -> [rec_1, upcase_mac, predef_mac, variable, otp_4870, otp_4871, otp_5362, pmod, not_circular, skip_header, otp_6277, otp_7702, otp_8130, - overload_mac]. + overload_mac, otp_8388]. rec_1(doc) -> ["Recursive macros hang or crash epp (OTP-1398)."]; @@ -1095,6 +1095,50 @@ overload_mac(Config) when is_list(Config) -> ?line [] = run(Config, Ts). +otp_8388(doc) -> + ["OTP-8388. More tests on overloaded macros."]; +otp_8388(suite) -> + []; +otp_8388(Config) when is_list(Config) -> + Dir = ?config(priv_dir, Config), + ?line File = filename:join(Dir, "otp_8388.erl"), + ?line ok = file:write_file(File, <<"-module(otp_8388)." + "-define(LINE, a).">>), + fun() -> + PreDefMacros = [{'LINE', a}], + ?line {error,{redefine_predef,'LINE'}} = + epp:open(File, [], PreDefMacros) + end(), + + fun() -> + PreDefMacros = ['LINE'], + ?line {error,{redefine_predef,'LINE'}} = + epp:open(File, [], PreDefMacros) + end(), + + Ts = [ + {macro_1, + <<"-define(m(A), A).\n" + "t() -> ?m(,).\n">>, + {errors,[{{2,11},epp,{arg_error,m}}],[]}}, + {macro_2, + <<"-define(m(A), A).\n" + "t() -> ?m(a,).\n">>, + {errors,[{{2,12},epp,{arg_error,m}}],[]}}, + {macro_3, + <<"-define(LINE, a).\n">>, + {errors,[{{1,9},epp,{redefine_predef,'LINE'}}],[]}}, + {macro_4, + <<"-define(A(B, C, D), {B,C,D}).\n" + "t() -> ?A(a,,3).\n">>, + {errors,[{{2,8},epp,{mismatch,'A'}}],[]}}, + {macro_5, + <<"-define(Q, {?F0(), ?F1(,,4)}).\n">>, + {errors,[{{1,24},epp,{arg_error,'F1'}}],[]}} + ], + ?line [] = compile(Config, Ts), + ok. + check(Config, Tests) -> eval_tests(Config, fun check_test/2, Tests). -- cgit v1.2.3 From 1ab56cb24a8a037d61076314230839fdcc325d7d Mon Sep 17 00:00:00 2001 From: Hans Bolinder Date: Mon, 1 Feb 2010 16:34:22 +0100 Subject: yecc_SUITE: Adjustment for modified error tuple --- lib/parsetools/test/yecc_SUITE.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/parsetools/test/yecc_SUITE.erl b/lib/parsetools/test/yecc_SUITE.erl index 212557194c..b5da414f7b 100644 --- a/lib/parsetools/test/yecc_SUITE.erl +++ b/lib/parsetools/test/yecc_SUITE.erl @@ -312,7 +312,7 @@ syntax(Config) when is_list(Config) -> ?line {ok, _, []} = yecc:file(Filename, ParserFile3 ++ Ret), %% Note: checking the line numbers. Changes when yeccpre.hrl changes. fun() -> - ?line {error,[{_,[{5,_,{undefined,'F'}}]}, + ?line {error,[{_,[{5,_,{undefined,'F',1}}]}, {_,[{L1,_,{undefined_function,{yeccpars2_2_,1}}}, {L2,_,{bad_inline,{yeccpars2_2_,1}}}]}], []} = compile:file(Parserfile1, [basic_validation,return]), -- cgit v1.2.3