summaryrefslogtreecommitdiff
path: root/lib/stdlib/src/epp.erl
diff options
context:
space:
mode:
Diffstat (limited to 'lib/stdlib/src/epp.erl')
-rw-r--r--lib/stdlib/src/epp.erl270
1 files changed, 227 insertions, 43 deletions
diff --git a/lib/stdlib/src/epp.erl b/lib/stdlib/src/epp.erl
index 53b91530c5..bdb0bc64a2 100644
--- a/lib/stdlib/src/epp.erl
+++ b/lib/stdlib/src/epp.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1996-2021. All Rights Reserved.
+%% Copyright Ericsson AB 1996-2022. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -72,8 +72,13 @@
uses = #{} %Macro use structure
:: #{name() => [{argspec(), [used()]}]},
default_encoding = ?DEFAULT_ENCODING :: source_encoding(),
- pre_opened = false :: boolean(),
- fname = [] :: function_name_type()
+ pre_opened = false :: boolean(),
+ in_prefix = true :: boolean(),
+ erl_scan_opts = [] :: [_],
+ features = [] :: [atom()],
+ else_reserved = false :: boolean(),
+ fname = [] :: function_name_type(),
+ deterministic = false :: boolean()
}).
%% open(Options)
@@ -115,6 +120,7 @@ open(Name, Path, Pdm) ->
Options :: [{'default_encoding', DefEncoding :: source_encoding()} |
{'includes', IncludePath :: [DirectoryName :: file:name()]} |
{'source_name', SourceName :: file:name()} |
+ {'deterministic', Enabled :: boolean()} |
{'macros', PredefMacros :: macros()} |
{'name',FileName :: file:name()} |
{'location',StartLocation :: erl_anno:location()} |
@@ -131,12 +137,14 @@ open(Options) ->
Name ->
Self = self(),
Epp = spawn(fun() -> server(Self, Name, Options) end),
+ Extra = proplists:get_bool(extra, Options),
case epp_request(Epp) of
- {ok, Pid, Encoding} ->
- case proplists:get_bool(extra, Options) of
- true -> {ok, Pid, [{encoding, Encoding}]};
- false -> {ok, Pid}
- end;
+ {ok, Pid, Encoding} when Extra ->
+ {ok, Pid, [{encoding, Encoding}]};
+ {ok, Pid, _} ->
+ {ok, Pid};
+ {ok, Pid} when Extra ->
+ {ok, Pid, []};
Other ->
Other
end
@@ -239,6 +247,8 @@ format_error({error,Term}) ->
io_lib:format("-error(~tp).", [Term]);
format_error({warning,Term}) ->
io_lib:format("-warning(~tp).", [Term]);
+format_error(ftr_after_prefix) ->
+ "feature directive not allowed after exports or record definitions";
format_error(E) -> file:format_error(E).
-spec scan_file(FileName, Options) ->
@@ -297,6 +307,8 @@ parse_file(Ifile, Path, Predefs) ->
{'macros', PredefMacros :: macros()} |
{'default_encoding', DefEncoding :: source_encoding()} |
{'location',StartLocation :: erl_anno:location()} |
+ {'reserved_word_fun', Fun :: fun((atom()) -> boolean())} |
+ {'features', [Feature :: atom()]} |
'extra'],
Form :: erl_parse:abstract_form()
| {'error', ErrorInfo}
@@ -311,11 +323,13 @@ parse_file(Ifile, Options) ->
{ok,Epp} ->
Forms = parse_file(Epp),
close(Epp),
- {ok,Forms};
+ {ok, Forms};
{ok,Epp,Extra} ->
Forms = parse_file(Epp),
+ Epp ! {get_features, self()},
+ Ftrs = receive {features, X} -> X end,
close(Epp),
- {ok,Forms,Extra};
+ {ok, Forms, [{features, Ftrs} | Extra]};
{error,E} ->
{error,E}
end.
@@ -593,7 +607,8 @@ server(Pid, Name, Options) ->
init_server(Pid, FileName, Options, St0) ->
SourceName = proplists:get_value(source_name, Options, FileName),
Pdm = proplists:get_value(macros, Options, []),
- Ms0 = predef_macros(SourceName),
+ Features = proplists:get_value(features, Options, []),
+ Ms0 = predef_macros(SourceName, Features),
case user_predef(Pdm, Ms0) of
{ok,Ms1} ->
DefEncoding = proplists:get_value(default_encoding, Options,
@@ -604,28 +619,58 @@ init_server(Pid, FileName, Options, St0) ->
%% first in path
Path = [filename:dirname(FileName) |
proplists:get_value(includes, Options, [])],
+ ResWordFun =
+ proplists:get_value(reserved_word_fun, Options,
+ fun erl_scan:f_reserved_word/1),
%% the default location is 1 for backwards compatibility, not {1,1}
AtLocation = proplists:get_value(location, Options, 1),
+
+ Deterministic = proplists:get_value(deterministic, Options, false),
St = St0#epp{delta=0, name=SourceName, name2=SourceName,
path=Path, location=AtLocation, macs=Ms1,
- default_encoding=DefEncoding},
+ default_encoding=DefEncoding,
+ erl_scan_opts =
+ [{text_fun, keep_ftr_keywords()},
+ {reserved_word_fun, ResWordFun}],
+ features = Features,
+ else_reserved = ResWordFun('else'),
+ deterministic = Deterministic},
From = wait_request(St),
Anno = erl_anno:new(AtLocation),
enter_file_reply(From, file_name(SourceName), Anno,
- AtLocation, code),
+ AtLocation, code, Deterministic),
wait_req_scan(St);
{error,E} ->
epp_reply(Pid, {error,E})
end.
+%% Return a function that keeps quoted atoms that are keywords in
+%% configurable features. Need in erl_lint to avoid warning about
+%% them.
+keep_ftr_keywords() ->
+ Features = erl_features:configurable(),
+ Keywords = lists:flatmap(fun erl_features:keywords/1, Features),
+ F = fun(Atom) -> atom_to_list(Atom) ++ "'" end,
+ Strings = lists:map(F, Keywords),
+ fun(atom, [$'|S]) -> lists:member(S, Strings);
+ (_, _) -> false
+ end.
+
%% predef_macros(FileName) -> Macrodict
%% Initialise the macro dictionary with the default predefined macros,
%% FILE, LINE, MODULE as undefined, MACHINE and MACHINE value.
-predef_macros(File) ->
+predef_macros(File, EnabledFeatures0) ->
Machine = list_to_atom(erlang:system_info(machine)),
Anno = line1(),
OtpVersion = list_to_integer(erlang:system_info(otp_release)),
+ AvailableFeatures =
+ [Ftr || Ftr <- erl_features:all(),
+ maps:get(status, erl_features:info(Ftr)) /= rejected],
+ PermanentFeatures =
+ [Ftr || Ftr <- erl_features:all(),
+ maps:get(status, erl_features:info(Ftr)) == permanent],
+ EnabledFeatures = EnabledFeatures0 ++ PermanentFeatures,
Defs = [{'FILE', {none,[{string,Anno,File}]}},
{'FUNCTION_NAME', undefined},
{'FUNCTION_ARITY', undefined},
@@ -636,10 +681,36 @@ predef_macros(File) ->
{'BASE_MODULE_STRING', undefined},
{'MACHINE', {none,[{atom,Anno,Machine}]}},
{Machine, {none,[{atom,Anno,true}]}},
- {'OTP_RELEASE', {none,[{integer,Anno,OtpVersion}]}}
+ {'OTP_RELEASE', {none,[{integer,Anno,OtpVersion}]}},
+ {'FEATURE_AVAILABLE', [ftr_macro(AvailableFeatures)]},
+ {'FEATURE_ENABLED', [ftr_macro(EnabledFeatures)]}
],
maps:from_list(Defs).
+%% Make macro definition from a list of features. The macro takes one
+%% argument and returns true when argument is available as a feature.
+ftr_macro(Features) ->
+ Anno = line1(),
+ Arg = 'X',
+ Fexp = fun(Ftr) -> [{'(', Anno},
+ {var, Anno, Arg},
+ {')', Anno},
+ {'==', Anno},
+ {atom, Anno, Ftr}]
+ end,
+ Body =
+ case Features of
+ [] -> [{atom, Anno, false}];
+ [Ftr| Ftrs] ->
+ [{'(', Anno}|
+ lists:foldl(fun(F, Expr) ->
+ Fexp(F) ++ [{'orelse', Anno} | Expr]
+ end,
+ Fexp(Ftr) ++ [{')', Anno}],
+ Ftrs)]
+ end,
+ {1, {[Arg], Body}}.
+
%% user_predef(PreDefMacros, Macros) ->
%% {ok,MacroDict} | {error,E}
%% Add the predefined macros to the macros dictionary. A macro without a
@@ -674,6 +745,9 @@ user_predef([], Ms) -> {ok,Ms}.
wait_request(St) ->
receive
{epp_request,From,scan_erl_form} -> From;
+ {get_features, From} ->
+ From ! {features, St#epp.features},
+ wait_request(St);
{epp_request,From,macro_defs} ->
%% Return the old format to avoid any incompability issues.
Defs = [{{atom,K},V} || {K,V} <- maps:to_list(St#epp.macs)],
@@ -728,10 +802,15 @@ enter_file(NewName, Inc, From, St) ->
enter_file2(NewF, Pname, From, St0, AtLocation) ->
Anno = erl_anno:new(AtLocation),
- enter_file_reply(From, Pname, Anno, AtLocation, code),
+ enter_file_reply(From, Pname, Anno, AtLocation, code, St0#epp.deterministic),
#epp{macs = Ms0,
- default_encoding = DefEncoding} = St0,
- Ms = Ms0#{'FILE':={none,[{string,Anno,Pname}]}},
+ default_encoding = DefEncoding,
+ in_prefix = InPrefix,
+ erl_scan_opts = ScanOpts,
+ else_reserved = ElseReserved,
+ features = Ftrs,
+ deterministic = Deterministic} = St0,
+ Ms = Ms0#{'FILE':={none,[{string,Anno,source_name(St0,Pname)}]}},
%% update the head of the include path to be the directory of the new
%% source file, so that an included file can always include other files
%% relative to its current location (this is also how C does it); note
@@ -742,16 +821,21 @@ enter_file2(NewF, Pname, From, St0, AtLocation) ->
_ = set_encoding(NewF, DefEncoding),
#epp{file=NewF,location=AtLocation,name=Pname,name2=Pname,delta=0,
sstk=[St0|St0#epp.sstk],path=Path,macs=Ms,
- default_encoding=DefEncoding}.
-
-enter_file_reply(From, Name, LocationAnno, AtLocation, Where) ->
+ in_prefix = InPrefix,
+ features = Ftrs,
+ erl_scan_opts = ScanOpts,
+ else_reserved = ElseReserved,
+ default_encoding=DefEncoding,
+ deterministic=Deterministic}.
+
+enter_file_reply(From, Name, LocationAnno, AtLocation, Where, Deterministic) ->
Anno0 = erl_anno:new(AtLocation),
Anno = case Where of
code -> Anno0;
generated -> erl_anno:set_generated(true, Anno0)
end,
Rep = {ok, [{'-',Anno},{atom,Anno,file},{'(',Anno},
- {string,Anno,Name},{',',Anno},
+ {string,Anno,source_name(Deterministic,Name)},{',',Anno},
{integer,Anno,get_line(LocationAnno)},{')',LocationAnno},
{dot,Anno}]},
epp_reply(From, Rep).
@@ -783,9 +867,17 @@ leave_file(From, St) ->
CurrLoc = add_line(OldLoc, Delta),
Anno = erl_anno:new(CurrLoc),
Ms0 = St#epp.macs,
- Ms = Ms0#{'FILE':={none,[{string,Anno,OldName2}]}},
- NextSt = OldSt#epp{sstk=Sts,macs=Ms,uses=St#epp.uses},
- enter_file_reply(From, OldName, Anno, CurrLoc, code),
+ InPrefix = St#epp.in_prefix,
+ Ftrs = St#epp.features,
+ ElseReserved = St#epp.else_reserved,
+ ScanOpts = St#epp.erl_scan_opts,
+ Ms = Ms0#{'FILE':={none,[{string,Anno,source_name(St,OldName2)}]}},
+ NextSt = OldSt#epp{sstk=Sts,macs=Ms,uses=St#epp.uses,
+ in_prefix = InPrefix,
+ features = Ftrs,
+ else_reserved = ElseReserved,
+ erl_scan_opts = ScanOpts},
+ enter_file_reply(From, OldName, Anno, CurrLoc, code, St#epp.deterministic),
case OldName2 =:= OldName of
true ->
ok;
@@ -793,7 +885,7 @@ leave_file(From, St) ->
NFrom = wait_request(NextSt),
OldAnno = erl_anno:new(OldLoc),
enter_file_reply(NFrom, OldName2, OldAnno,
- CurrLoc, generated)
+ CurrLoc, generated, St#epp.deterministic)
end,
wait_req_scan(NextSt);
[] ->
@@ -806,27 +898,30 @@ leave_file(From, St) ->
%% scan_toks(Tokens, From, EppState)
scan_toks(From, St) ->
- case io:scan_erl_form(St#epp.file, '', St#epp.location) of
- {ok,Toks,Cl} ->
- scan_toks(Toks, From, St#epp{location=Cl});
- {error,E,Cl} ->
- epp_reply(From, {error,E}),
- wait_req_scan(St#epp{location=Cl});
- {eof,Cl} ->
- leave_file(From, St#epp{location=Cl});
- {error,_E} ->
+ #epp{file = File, location = Loc, erl_scan_opts = ScanOpts} = St,
+ case io:scan_erl_form(File, '', Loc, ScanOpts) of
+ {ok,Toks,Cl} ->
+ scan_toks(Toks, From, St#epp{location=Cl});
+ {error,E,Cl} ->
+ epp_reply(From, {error,E}),
+ wait_req_scan(St#epp{location=Cl});
+ {eof,Cl} ->
+ leave_file(From, St#epp{location=Cl});
+ {error,_E} ->
epp_reply(From, {error,{St#epp.location,epp,cannot_parse}}),
- leave_file(wait_request(St), St) %This serious, just exit!
+ leave_file(wait_request(St), St) %This serious, just exit!
end.
+scan_toks([{'-',_Lh},{atom,_Ld,feature}=Feature|Toks], From, St) ->
+ scan_feature(Toks, Feature, From, St);
scan_toks([{'-',_Lh},{atom,_Ld,define}=Define|Toks], From, St) ->
scan_define(Toks, Define, From, St);
scan_toks([{'-',_Lh},{atom,_Ld,undef}=Undef|Toks], From, St) ->
- scan_undef(Toks, Undef, From, St);
+ scan_undef(Toks, Undef, From, leave_prefix(St));
scan_toks([{'-',_Lh},{atom,_Ld,error}=Error|Toks], From, St) ->
- scan_err_warn(Toks, Error, From, St);
+ scan_err_warn(Toks, Error, From, leave_prefix(St));
scan_toks([{'-',_Lh},{atom,_Ld,warning}=Warn|Toks], From, St) ->
- scan_err_warn(Toks, Warn, From, St);
+ scan_err_warn(Toks, Warn, From, leave_prefix(St));
scan_toks([{'-',_Lh},{atom,_Li,include}=Inc|Toks], From, St) ->
scan_include(Toks, Inc, From, St);
scan_toks([{'-',_Lh},{atom,_Li,include_lib}=IncLib|Toks], From, St) ->
@@ -837,6 +932,10 @@ scan_toks([{'-',_Lh},{atom,_Li,ifndef}=IfnDef|Toks], From, St) ->
scan_ifndef(Toks, IfnDef, From, St);
scan_toks([{'-',_Lh},{atom,_Le,'else'}=Else|Toks], From, St) ->
scan_else(Toks, Else, From, St);
+%% conditionally allow else as a keyword
+scan_toks([{'-',_Lh},{'else',_Le}=Else|Toks], From, St)
+ when St#epp.else_reserved ->
+ scan_else(Toks, Else, From, St);
scan_toks([{'-',_Lh},{'if',_Le}=If|Toks], From, St) ->
scan_if(Toks, If, From, St);
scan_toks([{'-',_Lh},{atom,_Le,elif}=Elif|Toks], From, St) ->
@@ -854,13 +953,37 @@ scan_toks([{'-',_Lh},{atom,_Lf,file}=FileToken|Toks0], From, St) ->
scan_toks(Toks0, From, St) ->
case catch expand_macros(Toks0, St#epp{fname=Toks0}) of
Toks1 when is_list(Toks1) ->
+ InPrefix =
+ St#epp.in_prefix
+ andalso case Toks1 of
+ [] -> true;
+ [{'-', _Loc}, Tok | _] ->
+ in_prefix(Tok);
+ _ ->
+ false
+ end,
epp_reply(From, {ok,Toks1}),
- wait_req_scan(St#epp{macs=scan_module(Toks1, St#epp.macs)});
+ wait_req_scan(St#epp{in_prefix = InPrefix,
+ macs=scan_module(Toks1, St#epp.macs)});
{error,ErrL,What} ->
epp_reply(From, {error,{ErrL,epp,What}}),
wait_req_scan(St)
end.
+%% Determine whether we have passed the prefix where a -feature
+%% directive is allowed.
+in_prefix({atom, _, Atom}) ->
+ %% These directives are allowed inside the prefix
+ lists:member(Atom, ['module', 'feature',
+ 'if', 'else', 'elif', 'endif', 'ifdef', 'ifndef',
+ 'define', 'undef',
+ 'include', 'include_lib']);
+in_prefix(_T) ->
+ false.
+
+leave_prefix(#epp{} = St) ->
+ St#epp{in_prefix = false}.
+
scan_module([{'-',_Ah},{atom,_Am,module},{'(',_Al}|Ts], Ms) ->
scan_module_1(Ts, Ms);
scan_module([{'-',_Ah},{atom,_Am,extends},{'(',_Al}|Ts], Ms) ->
@@ -901,6 +1024,54 @@ scan_err_warn(Toks, {atom,_,Tag}=Token, From, St) ->
epp_reply(From, {error,{loc(T),epp,{bad,Tag}}}),
wait_req_scan(St).
+%% scan a feature directive
+scan_feature([{'(', _Ap}, {atom, _Am, Ftr},
+ {',', _}, {atom, _, Ind}, {')', _}, {dot, _}],
+ Feature, From, St)
+ when St#epp.in_prefix,
+ (Ind =:= enable
+ orelse Ind =:= disable) ->
+ case update_features(St, Ind, Ftr, loc(Feature)) of
+ {ok, St1} ->
+ scan_toks(From, St1);
+ {error, {{Mod, Reason}, ErrLoc}} ->
+ epp_reply(From, {error, {ErrLoc, Mod, Reason}}),
+ wait_req_scan(St)
+ end;
+scan_feature([{'(', _Ap}, {atom, _Am, _Ind},
+ {',', _}, {atom, _, _Ftr}, {')', _}, {dot, _}| _Toks],
+ Feature, From, St) when not St#epp.in_prefix ->
+ epp_reply(From, {error, {loc(Feature), epp,
+ ftr_after_prefix}}),
+ wait_req_scan(St);
+scan_feature(Toks, {atom, _, Tag} = Token, From, St) ->
+ T = no_match(Toks, Token),
+ epp_reply(From, {error,{loc(T),epp,{bad,Tag}}}),
+ wait_req_scan(St).
+
+update_features(St0, Ind, Ftr, Loc) ->
+ Ftrs0 = St0#epp.features,
+ ScanOpts0 = St0#epp.erl_scan_opts,
+ KeywordFun =
+ case proplists:get_value(reserved_word_fun, ScanOpts0) of
+ undefined -> fun erl_scan:f_reserved_word/1;
+ Fun -> Fun
+ end,
+ case erl_features:keyword_fun(Ind, Ftr, Ftrs0, KeywordFun) of
+ {error, Reason} ->
+ {error, {Reason, Loc}};
+ {ok, {Ftrs1, ResWordFun1}} ->
+ Macs0 = St0#epp.macs,
+ Macs1 = Macs0#{'FEATURE_ENABLED' => [ftr_macro(Ftrs1)]},
+ ScanOpts1 = proplists:delete(reserved_word_fun, ScanOpts0),
+ St = St0#epp{erl_scan_opts =
+ [{reserved_word_fun, ResWordFun1}| ScanOpts1],
+ features = Ftrs1,
+ else_reserved = ResWordFun1('else'),
+ macs = Macs1},
+ {ok, St}
+ end.
+
%% scan_define(Tokens, DefineToken, From, EppState)
scan_define([{'(',_Ap},{Type,_Am,_}=Mac|Toks], Def, From, St)
@@ -1298,9 +1469,9 @@ scan_file(Tokens0, Tf, From, St) ->
scan_file1([{'(',_Alp},{string,_As,Name},{',',_Ac},{integer,_Ai,Ln},{')',_Arp},
{dot,_Ad}], Tf, From, St) ->
Anno = erl_anno:new(Ln),
- enter_file_reply(From, Name, Anno, loc(Tf), generated),
+ enter_file_reply(From, Name, Anno, loc(Tf), generated, St#epp.deterministic),
Ms0 = St#epp.macs,
- Ms = Ms0#{'FILE':={none,[{string,line1(),Name}]}},
+ Ms = Ms0#{'FILE':={none,[{string,line1(),source_name(St,Name)}]}},
Locf = loc(Tf),
NewLoc = new_location(Ln, St#epp.location, Locf),
Delta = get_line(element(2, Tf))-Ln + St#epp.delta,
@@ -1320,7 +1491,8 @@ new_location(Ln, {Le,_}, {Lf,_}) ->
%% nested conditionals and repeated 'else's.
skip_toks(From, St, [I|Sis]) ->
- case io:scan_erl_form(St#epp.file, '', St#epp.location) of
+ ElseReserved = St#epp.else_reserved,
+ case io:scan_erl_form(St#epp.file, '', St#epp.location, St#epp.erl_scan_opts) of
{ok,[{'-',_Ah},{atom,_Ai,ifdef}|_Toks],Cl} ->
skip_toks(From, St#epp{location=Cl}, [ifdef,I|Sis]);
{ok,[{'-',_Ah},{atom,_Ai,ifndef}|_Toks],Cl} ->
@@ -1329,6 +1501,9 @@ skip_toks(From, St, [I|Sis]) ->
skip_toks(From, St#epp{location=Cl}, ['if',I|Sis]);
{ok,[{'-',_Ah},{atom,_Ae,'else'}=Else|_Toks],Cl}->
skip_else(Else, From, St#epp{location=Cl}, [I|Sis]);
+ %% conditionally allow else as reserved word
+ {ok,[{'-',_Ah},{'else',_Ae}=Else|_Toks],Cl} when ElseReserved ->
+ skip_else(Else, From, St#epp{location=Cl}, [I|Sis]);
{ok,[{'-',_Ah},{atom,_Ae,'elif'}=Elif|Toks],Cl}->
skip_elif(Toks, Elif, From, St#epp{location=Cl}, [I|Sis]);
{ok,[{'-',_Ah},{atom,_Ae,endif}|_Toks],Cl} ->
@@ -1915,3 +2090,12 @@ interpret_file_attr([Form0 | Forms], Delta, Fs) ->
[Form | interpret_file_attr(Forms, Delta, Fs)];
interpret_file_attr([], _Delta, _Fs) ->
[].
+
+-spec source_name(#epp{} | boolean(), file:filename_all()) -> file:filename_all().
+source_name(Deterministic, Name) when is_boolean(Deterministic) ->
+ case Deterministic of
+ true -> filename:basename(Name);
+ false -> Name
+ end;
+source_name(St, Name) ->
+ source_name(St#epp.deterministic, Name).