File 2128-Implement-FUNCTION_NAME-and-FUNCTION_ARITY-macros.patch of Package erlang

From 1604b874828e8ef992c8e17e06e7af20a0f1574b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bj=C3=B6rn=20Gustavsson?= <bjorn@erlang.org>
Date: Mon, 26 Oct 2015 12:02:40 +0100
Subject: [PATCH 2/2] Implement ?FUNCTION_NAME and ?FUNCTION_ARITY macros

For a long time, users have asked for one or more macros that would
return the name and arity of the current function.

We could define a single ?FUNCTION macro that would return
a {Name,Arity} tuple.  However, to access just the name or
just the arity for the function, element/2 must be used.
That would limit its usefulness, because element/2 is not
allowed in all contexts.

Therefore, it seems that we will need two macros.
?FUNCTION_NAME that expands to the name of the current function
and ?FUNCTION_ARITY that expands to arity of the current
function.

Converting the function name to a string can be done like this:

  f() ->
    atom_to_list(?FUNCTION_NAME) ++ "/" ++
	integer_to_list(?FUNCTION_ARITY).

f/0 will return "f/0". The BEAM compiler will evaluate the
entire expression at compile-time, so there will not be
any run-time penalty for the function calls.

The implementation is non-trivial because the preprocessor is
run before the parser.

One way to implement the macros would be to replace them with some
placeholder and then let the parser or possibly a later pass replace
the placeholder with correct value. That could potentially slow
down the compiler and cause incompatibilities for parse transforms.

Another way is to let the preprocessor do the whole job. That means
that the preprocessor will have to scan the function head to find
out the name and arity. The scanning of the function head can be
delayed until the first occurrence of a ?FUNCTION_NAME or
?FUNCTION_ARITY.

I have chosen the second way because it seems less likely to cause
weird compatibility problems.
---
 lib/stdlib/src/epp.erl                 | 118 ++++++++++++++++++++++++++++++++-
 lib/stdlib/test/epp_SUITE.erl          |  91 +++++++++++++++++++++++--
 system/doc/reference_manual/macros.xml |   4 ++
 3 files changed, 207 insertions(+), 6 deletions(-)

diff --git a/lib/stdlib/src/epp.erl b/lib/stdlib/src/epp.erl
index f55aff1..936c095 100644
--- a/lib/stdlib/src/epp.erl
+++ b/lib/stdlib/src/epp.erl
@@ -46,6 +46,10 @@
 -type tokens() :: [erl_scan:token()].
 -type used() :: {name(), argspec()}.
 
+-type function_name_type() :: 'undefined'
+			    | {atom(),non_neg_integer()}
+			    | tokens().
+
 -define(DEFAULT_ENCODING, utf8).
 
 %% Epp state record.
@@ -63,7 +67,8 @@
               uses = #{}			%Macro use structure
 	            :: #{name() => [{argspec(), [used()]}]},
               default_encoding = ?DEFAULT_ENCODING :: source_encoding(),
-	      pre_opened = false :: boolean()
+	      pre_opened = false :: boolean(),
+	      fname = [] :: function_name_type()
 	     }).
 
 %% open(Options)
@@ -205,6 +210,10 @@ format_error({include,W,F}) ->
     io_lib:format("can't find include ~s \"~s\"", [W,F]);
 format_error({illegal,How,What}) ->
     io_lib:format("~s '-~s'", [How,What]);
+format_error({illegal_function,Macro}) ->
+    io_lib:format("?~s can only be used within a function", [Macro]);
+format_error({illegal_function_usage,Macro}) ->
+    io_lib:format("?~s must not begin a form", [Macro]);
 format_error({'NYI',What}) ->
     io_lib:format("not yet implemented '~s'", [What]);
 format_error(E) -> file:format_error(E).
@@ -545,6 +554,8 @@ predef_macros(File) ->
     Machine = list_to_atom(erlang:system_info(machine)),
     Anno = line1(),
     Defs = [{'FILE', 	           {none,[{string,Anno,File}]}},
+	    {'FUNCTION_NAME',      undefined},
+	    {'FUNCTION_ARITY',     undefined},
 	    {'LINE',		   {none,[{integer,Anno,1}]}},
 	    {'MODULE',	           undefined},
 	    {'MODULE_STRING',      undefined},
@@ -763,7 +774,7 @@ scan_toks([{'-',_Lh},{atom,_Lf,file}=FileToken|Toks0], From, St) ->
 	    wait_req_scan(St)
     end;
 scan_toks(Toks0, From, St) ->
-    case catch expand_macros(Toks0, St) of
+    case catch expand_macros(Toks0, St#epp{fname=Toks0}) of
 	Toks1 when is_list(Toks1) ->
 	    epp_reply(From, {ok,Toks1}),
 	    wait_req_scan(St#epp{macs=scan_module(Toks1, St#epp.macs)});
@@ -1214,6 +1225,22 @@ get_macro_uses({M,Arity}, U) ->
 expand_macros([{'?',_Lq},{atom,_Lm,M}=MacT|Toks], St) ->
     expand_macros(MacT, M, Toks, St);
 %% Special macros
+expand_macros([{'?',_Lq},{var,Lm,'FUNCTION_NAME'}=Token|Toks], St0) ->
+    St = update_fun_name(Token, St0),
+    case St#epp.fname of
+	undefined ->
+	    [{'?',_Lq},Token];
+	{Name,_} ->
+	    [{atom,Lm,Name}]
+    end ++ expand_macros(Toks, St);
+expand_macros([{'?',_Lq},{var,Lm,'FUNCTION_ARITY'}=Token|Toks], St0) ->
+    St = update_fun_name(Token, St0),
+    case St#epp.fname of
+	undefined ->
+	    [{'?',_Lq},Token];
+	{_,Arity} ->
+	    [{integer,Lm,Arity}]
+    end ++ expand_macros(Toks, St);
 expand_macros([{'?',_Lq},{var,Lm,'LINE'}=Tok|Toks], St) ->
     Line = erl_scan:line(Tok),
     [{integer,Lm,Line}|expand_macros(Toks, St)];
@@ -1354,6 +1381,93 @@ expand_arg([A|As], Ts, _L, Rest, Bs) ->
 expand_arg([], Ts, L, Rest, Bs) ->
     expand_macro(Ts, L, Rest, Bs).
 
+%%%
+%%% Here follows support for the ?FUNCTION_NAME and ?FUNCTION_ARITY
+%%% macros. Since the parser has not been run yet, we don't know the
+%%% name and arity of the current function. Therefore, we will need to
+%%% scan the beginning of the current form to extract the name and
+%%% arity of the function.
+%%%
+
+update_fun_name(Token, #epp{fname=Toks0}=St) when is_list(Toks0) ->
+    %% ?FUNCTION_NAME or ?FUNCTION_ARITY is used for the first time in
+    %% a function.  First expand macros (except ?FUNCTION_NAME and
+    %% ?FUNCTION_ARITY) in the form.
+
+    Toks1 = (catch expand_macros(Toks0, St#epp{fname=undefined})),
+
+    %% Now extract the name and arity from the stream of tokens, and store
+    %% the result in the #epp{} record so we don't have to do it
+    %% again.
+
+    case Toks1 of
+	[{atom,_,Name},{'(',_}|Toks] ->
+	    %% This is the beginning of a function definition.
+	    %% Scan the token stream up to the matching right
+	    %% parenthesis and count the number of arguments.
+	    FA = update_fun_name_1(Toks, 1, {Name,0}, St),
+	    St#epp{fname=FA};
+	[{'?',_}|_] ->
+	    %% ?FUNCTION_NAME/?FUNCTION_ARITY used at the beginning
+	    %% of a form. Does not make sense.
+	    {var,_,Macro} = Token,
+	    throw({error,loc(Token),{illegal_function_usage,Macro}});
+	_ when is_list(Toks1) ->
+	    %% Not the beginning of a function (an attribute or a
+	    %% syntax error).
+	    {var,_,Macro} = Token,
+	    throw({error,loc(Token),{illegal_function,Macro}});
+	_ ->
+	    %% A macro expansion error. Return a dummy value and
+	    %% let the caller notice and handle the error.
+	    St#epp{fname={'_',0}}
+    end;
+update_fun_name(_Token, St) ->
+    St.
+
+update_fun_name_1([Tok|Toks], L, FA, St) ->
+    case classify_token(Tok) of
+	comma ->
+	    if
+		L =:= 1 ->
+		    {Name,Arity} = FA,
+		    update_fun_name_1(Toks, L, {Name,Arity+1}, St);
+		true ->
+		    update_fun_name_1(Toks, L, FA, St)
+	    end;
+	left ->
+	    update_fun_name_1(Toks, L+1, FA, St);
+	right when L =:= 1 ->
+	    FA;
+	right ->
+	    update_fun_name_1(Toks, L-1, FA, St);
+	other ->
+	    case FA of
+		{Name,0} ->
+		    update_fun_name_1(Toks, L, {Name,1}, St);
+		{_,_} ->
+		    update_fun_name_1(Toks, L, FA, St)
+	    end
+    end;
+update_fun_name_1([], _, FA, _) ->
+    %% Syntax error, but never mind.
+    FA.
+
+classify_token({C,_}) -> classify_token_1(C);
+classify_token(_) -> other.
+
+classify_token_1(',') -> comma;
+classify_token_1('(') -> left;
+classify_token_1('{') -> left;
+classify_token_1('[') -> left;
+classify_token_1('<<') -> left;
+classify_token_1(')') -> right;
+classify_token_1('}') -> right;
+classify_token_1(']') -> right;
+classify_token_1('>>') -> right;
+classify_token_1(_) -> other.
+
+
 %%% stringify(Ts, L) returns a list of one token: a string which when
 %%% tokenized would yield the token list Ts.
 
diff --git a/lib/stdlib/test/epp_SUITE.erl b/lib/stdlib/test/epp_SUITE.erl
index 955b482..bd8939c 100644
--- a/lib/stdlib/test/epp_SUITE.erl
+++ b/lib/stdlib/test/epp_SUITE.erl
@@ -27,7 +27,7 @@
          pmod/1, not_circular/1, skip_header/1, otp_6277/1, otp_7702/1,
          otp_8130/1, overload_mac/1, otp_8388/1, otp_8470/1, otp_8503/1,
          otp_8562/1, otp_8665/1, otp_8911/1, otp_10302/1, otp_10820/1,
-         otp_11728/1, encoding/1]).
+         otp_11728/1, encoding/1, function_macro/1]).
 
 -export([epp_parse_erl_form/2]).
 
@@ -70,7 +70,7 @@
      not_circular, skip_header, otp_6277, otp_7702, otp_8130,
      overload_mac, otp_8388, otp_8470, otp_8503, otp_8562,
      otp_8665, otp_8911, otp_10302, otp_10820, otp_11728,
-     encoding].
+     encoding, function_macro].
 
 groups() -> 
     [{upcase_mac, [], [upcase_mac_1, upcase_mac_2]},
@@ -827,6 +827,7 @@
                                "t() -> ?a.\n"),
     ?line {ok,Epp} = epp:open(File, []),
     ?line ['BASE_MODULE','BASE_MODULE_STRING','BEAM','FILE','LINE',
+           'FUNCTION_ARITY','FUNCTION_NAME',
            'MACHINE','MODULE','MODULE_STRING'] = macs(Epp),
     ?line {ok,[{'-',_},{atom,_,file}|_]} = epp:scan_erl_form(Epp),
     ?line {ok,[{'-',_},{atom,_,module}|_]} = epp:scan_erl_form(Epp),
@@ -1476,6 +1477,88 @@
 	epp_parse_file(ErlFile, [{default_encoding,utf8},extra]),
     ok.
 
+function_macro(Config) ->
+    Cs = [{f_c1,
+	   <<"-define(FUNCTION_NAME, a).\n"
+	     "-define(FUNCTION_ARITY, a).\n"
+	     "-define(FS,\n"
+	     " atom_to_list(?FUNCTION_NAME) ++ \"/\" ++\n"
+	     " integer_to_list(?FUNCTION_ARITY)).\n"
+	     "-attr({f,?FUNCTION_NAME}).\n"
+	     "-attr2(?FS).\n"
+	     "-file(?FUNCTION_ARITY, 1).\n"
+	     "f1() ?FUNCTION_NAME/?FUNCTION_ARITY.\n"
+	     "f2(?FUNCTION_NAME.\n">>,
+	   {errors,[{1,epp,{redefine_predef,'FUNCTION_NAME'}},
+		    {2,epp,{redefine_predef,'FUNCTION_ARITY'}},
+		    {6,epp,{illegal_function,'FUNCTION_NAME'}},
+		    {7,epp,{illegal_function,'FUNCTION_NAME'}},
+		    {8,epp,{illegal_function,'FUNCTION_ARITY'}},
+		    {9,erl_parse,["syntax error before: ","f1"]},
+		    {10,erl_parse,["syntax error before: ","'.'"]}],
+	    []}},
+
+	  {f_c2,
+	   <<"a({a) -> ?FUNCTION_NAME.\n"
+	     "b(}{) -> ?FUNCTION_ARITY.\n"
+	     "c(?FUNCTION_NAME, ?not_defined) -> ok.\n">>,
+	   {errors,[{1,erl_parse,["syntax error before: ","')'"]},
+		    {2,erl_parse,["syntax error before: ","'}'"]},
+		    {3,epp,{undefined,not_defined,none}}],
+	    []}},
+
+	  {f_c3,
+	   <<"?FUNCTION_NAME() -> ok.\n"
+	     "?FUNCTION_ARITY() -> ok.\n">>,
+	   {errors,[{1,epp,{illegal_function_usage,'FUNCTION_NAME'}},
+		    {2,epp,{illegal_function_usage,'FUNCTION_ARITY'}}],
+	    []}}
+	 ],
+
+    [] = compile(Config, Cs),
+
+    Ts = [{f_1,
+	   <<"t() -> {a,0} = a(), {b,1} = b(1), {c,2} = c(1, 2),\n"
+	     "  {d,1} = d({d,1}), {foo,1} = foo(foo), ok.\n"
+	     "a() -> {?FUNCTION_NAME,?FUNCTION_ARITY}.\n"
+	     "b(_) -> {?FUNCTION_NAME,?FUNCTION_ARITY}.\n"
+	     "c(_, (_)) -> {?FUNCTION_NAME,?FUNCTION_ARITY}.\n"
+	     "d({?FUNCTION_NAME,?FUNCTION_ARITY}=F) -> F.\n"
+	     "-define(FOO, foo).\n"
+	     "?FOO(?FOO) -> {?FUNCTION_NAME,?FUNCTION_ARITY}.\n">>,
+	   ok},
+
+	  {f_2,
+	   <<"t() ->\n"
+             "  A = {a,[<<0:24>>,#{a=>1,b=>2}]},\n"
+	     "  1 = a(A),\n"
+	     "  ok.\n"
+	     "a({a,[<<_,_,_>>,#{a:=1,b:=2}]}) -> ?FUNCTION_ARITY.\n">>,
+	   ok},
+
+	  {f_3,
+	   <<"-define(FS,\n"
+	     " atom_to_list(?FUNCTION_NAME) ++ \"/\" ++\n"
+	     " integer_to_list(?FUNCTION_ARITY)).\n"
+	     "t() ->\n"
+	     "  {t,0} = {?FUNCTION_NAME,?FUNCTION_ARITY},\n"
+	     "  \"t/0\" = ?FS,\n"
+	     "  ok.\n">>,
+	   ok},
+
+	  {f_4,
+	   <<"-define(__, _, _).\n"
+	     "-define(FF, ?FUNCTION_NAME, ?FUNCTION_ARITY).\n"
+	     "a(?__) -> 2 = ?FUNCTION_ARITY.\n"
+	     "b(?FUNCTION_ARITY, ?__) -> ok.\n"
+	     "c(?FF) -> ok.\n"
+	     "t() -> a(1, 2), b(3, 1, 2), c(c, 2), ok.\n">>,
+	   ok}
+	 ],
+    [] = run(Config, Ts),
+
+    ok.
+
 
 check(Config, Tests) ->
     eval_tests(Config, fun check_test/2, Tests).
diff --git a/system/doc/reference_manual/macros.xml b/system/doc/reference_manual/macros.xml
index 3b1f72e..42ea639 100644
--- a/system/doc/reference_manual/macros.xml
+++ b/system/doc/reference_manual/macros.xml
@@ -146,6 +146,10 @@ bar(X) ->
       <item>The current line number.</item>
       <tag><c>?MACHINE</c>.</tag>
       <item>The machine name, <c>'BEAM'</c>.</item>
+      <tag><c>?FUNCTION_NAME</c></tag>
+      <item>The name of the current function.</item>
+      <tag><c>?FUNCTION_ARITY</c></tag>
+      <item>The arity (number of arguments) for the current function.</item>
     </taglist>
   </section>
 
-- 
2.1.4

openSUSE Build Service is sponsored by