diff options
Diffstat (limited to 'lib/stdlib/src/epp.erl')
-rw-r--r-- | lib/stdlib/src/epp.erl | 270 |
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). |