aboutsummaryrefslogtreecommitdiffstats
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.erl1146
1 files changed, 1146 insertions, 0 deletions
diff --git a/lib/stdlib/src/epp.erl b/lib/stdlib/src/epp.erl
new file mode 100644
index 0000000000..8b702c005b
--- /dev/null
+++ b/lib/stdlib/src/epp.erl
@@ -0,0 +1,1146 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1996-2009. 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).
+
+%% An Erlang code preprocessor.
+
+-export([open/2,open/3,open/5,close/1,format_error/1]).
+-export([scan_erl_form/1,parse_erl_form/1,macro_defs/1]).
+-export([parse_file/1, parse_file/3]).
+-export([interpret_file_attribute/1]).
+-export([normalize_typed_record_fields/1,restore_typed_record_fields/1]).
+
+%%------------------------------------------------------------------------
+
+-type macros() :: [{atom(), term()}].
+
+%% Epp state record.
+-record(epp, {file, %Current file
+ location, %Current location
+ name="", %Current file name
+ istk=[], %Ifdef stack
+ sstk=[], %State stack
+ path=[], %Include-path
+ macs = dict:new() :: dict(), %Macros (don't care locations)
+ uses = dict:new() :: dict(), %Macro use structure
+ pre_opened = false :: boolean()
+ }).
+
+%%% Note on representation: as tokens, both {var, Location, Name} and
+%%% {atom, Location, Name} can occur as macro identifiers. However, keeping
+%%% this distinction here is done for historical reasons only: previously,
+%%% ?FOO and ?'FOO' were not the same, but now they are. Removing the
+%%% distinction in the internal representation would simplify the code
+%%% a little.
+
+%% open(FileName, IncludePath)
+%% open(FileName, IncludePath, PreDefMacros)
+%% open(FileName, IoDevice, StartLocation, IncludePath, PreDefMacros)
+%% close(Epp)
+%% scan_erl_form(Epp)
+%% parse_erl_form(Epp)
+%% parse_file(Epp)
+%% parse_file(FileName, IncludePath, PreDefMacros)
+%% macro_defs(Epp)
+
+-spec open(file:name(), [file:name()]) ->
+ {'ok', pid()} | {'error', term()}.
+
+open(Name, Path) ->
+ open(Name, Path, []).
+
+-spec open(file:name(), [file:name()], macros()) ->
+ {'ok', pid()} | {'error', term()}.
+
+open(Name, Path, Pdm) ->
+ Self = self(),
+ Epp = spawn(fun() -> server(Self, Name, Path, Pdm) end),
+ epp_request(Epp).
+
+open(Name, File, StartLocation, Path, Pdm) ->
+ Self = self(),
+ Epp = spawn(fun() -> server(Self, Name, File, StartLocation,Path,Pdm) end),
+ epp_request(Epp).
+
+-spec close(pid()) -> 'ok'.
+
+close(Epp) ->
+ %% Make sure that close is synchronous as a courtesy to test
+ %% cases that test for resource leaks.
+ Ref = erlang:monitor(process, Epp),
+ R = epp_request(Epp, close),
+ receive {'DOWN',Ref,_,_,_} -> ok end,
+ R.
+
+scan_erl_form(Epp) ->
+ epp_request(Epp, scan_erl_form).
+
+parse_erl_form(Epp) ->
+ case epp_request(Epp, scan_erl_form) of
+ {ok,Toks} ->
+ erl_parse:parse_form(Toks);
+ Other ->
+ Other
+ end.
+
+macro_defs(Epp) ->
+ epp_request(Epp, macro_defs).
+
+%% format_error(ErrorDescriptor) -> String
+%% Return a string describing the error.
+
+format_error(cannot_parse) ->
+ io_lib:format("cannot parse file, giving up", []);
+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({depth,What}) ->
+ io_lib:format("~s too deep",[What]);
+format_error({mismatch,M}) ->
+ io_lib:format("argument mismatch for macro '~w'", [M]);
+format_error({arg_error,M}) ->
+ io_lib:format("badly formed argument for macro '~w'", [M]);
+format_error({redefine,M}) ->
+ io_lib:format("redefining macro '~w'", [M]);
+format_error({circular,M}) ->
+ io_lib:format("circular macro '~w'", [M]);
+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({'NYI',What}) ->
+ io_lib:format("not yet implemented '~s'", [What]);
+format_error(E) -> file:format_error(E).
+
+%% parse_file(FileName, IncludePath, [PreDefMacro]) ->
+%% {ok,[Form]} | {error,OpenError}
+
+parse_file(Ifile, Path, Predefs) ->
+ case open(Ifile, Path, Predefs) of
+ {ok,Epp} ->
+ Forms = parse_file(Epp),
+ close(Epp),
+ {ok,Forms};
+ {error,E} ->
+ {error,E}
+ end.
+
+%% parse_file(Epp) ->
+%% [Form]
+
+parse_file(Epp) ->
+ case parse_erl_form(Epp) of
+ {ok,Form} ->
+ case Form of
+ {attribute,La,record,{Record, Fields}} ->
+ case normalize_typed_record_fields(Fields) of
+ {typed, NewFields} ->
+ [{attribute, La, record, {Record, NewFields}},
+ {attribute, La, type,
+ {{record, Record}, Fields, []}}
+ |parse_file(Epp)];
+ not_typed ->
+ [Form|parse_file(Epp)]
+ end;
+ _ ->
+ [Form|parse_file(Epp)]
+ end;
+ {error,E} ->
+ [{error,E}|parse_file(Epp)];
+ {eof,Location} ->
+ [{eof,Location}]
+ end.
+
+normalize_typed_record_fields(Fields) ->
+ normalize_typed_record_fields(Fields, [], false).
+
+normalize_typed_record_fields([], NewFields, Typed) ->
+ case Typed of
+ true -> {typed, lists:reverse(NewFields)};
+ false -> not_typed
+ end;
+normalize_typed_record_fields([{typed_record_field,Field,_}|Rest],
+ NewFields, _Typed) ->
+ normalize_typed_record_fields(Rest, [Field|NewFields], true);
+normalize_typed_record_fields([Field|Rest], NewFields, Typed) ->
+ normalize_typed_record_fields(Rest, [Field|NewFields], Typed).
+
+restore_typed_record_fields([]) ->
+ [];
+restore_typed_record_fields([{attribute,La,record,{Record,_NewFields}},
+ {attribute,La,type,{{record,Record},Fields,[]}}|
+ Forms]) ->
+ [{attribute,La,record,{Record,Fields}}|
+ restore_typed_record_fields(Forms)];
+restore_typed_record_fields([{attribute,La,type,{{record,Record},Fields,[]}}|
+ Forms]) ->
+ %% This clause is due to the compiler's 'E' option.
+ %% Record information kept by erl_expand_records.
+ [{attribute,La,record,{Record,Fields}}|
+ restore_typed_record_fields(Forms)];
+restore_typed_record_fields([Form|Forms]) ->
+ [Form|restore_typed_record_fields(Forms)].
+
+%% server(StarterPid, FileName, Path, PreDefMacros)
+
+server(Pid, Name, Path, Pdm) ->
+ process_flag(trap_exit, true),
+ case file:open(Name, [read]) of
+ {ok,File} ->
+ Location = 1,
+ init_server(Pid, Name, File, Location, Path, Pdm, false);
+ {error,E} ->
+ epp_reply(Pid, {error,E})
+ end.
+
+%% server(StarterPid, FileName, IoDevice, Location, Path, PreDefMacros)
+server(Pid, Name, File, AtLocation, Path, Pdm) ->
+ process_flag(trap_exit, true),
+ init_server(Pid, Name, File, AtLocation, Path, Pdm, true).
+
+init_server(Pid, Name, File, AtLocation, Path, Pdm, Pre) ->
+ Ms0 = predef_macros(Name),
+ case user_predef(Pdm, Ms0) of
+ {ok,Ms1} ->
+ epp_reply(Pid, {ok,self()}),
+ St = #epp{file=File, location=AtLocation, name=Name,
+ path=Path, macs=Ms1, pre_opened = Pre},
+ From = wait_request(St),
+ enter_file_reply(From, Name, AtLocation, AtLocation),
+ wait_req_scan(St);
+ {error,E} ->
+ epp_reply(Pid, {error,E})
+ 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) ->
+ Machine = list_to_atom(erlang:system_info(machine)),
+ dict:from_list([
+ {{atom,'FILE'}, {none,[{string,1,File}]}},
+ {{atom,'LINE'}, {none,[{integer,1,1}]}},
+ {{atom,'MODULE'}, undefined},
+ {{atom,'MODULE_STRING'}, undefined},
+ {{atom,'BASE_MODULE'}, undefined},
+ {{atom,'BASE_MODULE_STRING'}, undefined},
+ {{atom,'MACHINE'}, {none,[{atom,1,Machine}]}},
+ {{atom,Machine}, {none,[{atom,1,true}]}}
+ ]).
+
+%% user_predef(PreDefMacros, Macros) ->
+%% {ok,MacroDict} | {error,E}
+%% Add the predefined macros to the macros dictionary. A macro without a
+%% value gets the value 'true'.
+
+user_predef([{M,Val,redefine}|Pdm], Ms) when is_atom(M) ->
+ Exp = erl_parse:tokens(erl_parse:abstract(Val)),
+ 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} ->
+ {error,{redefine,M}};
+ error ->
+ Exp = erl_parse:tokens(erl_parse:abstract(Val)),
+ user_predef(Pdm, dict:store({atom,M}, {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))
+ end;
+user_predef([Md|_Pdm], _Ms) -> {error,{bad,Md}};
+user_predef([], Ms) -> {ok,Ms}.
+
+%% wait_request(EppState) -> RequestFrom
+%% wait_req_scan(EppState)
+%% wait_req_skip(EppState, SkipIstack)
+%% Handle requests, processing trivial requests directly. Either return
+%% requestor or scan/skip tokens.
+
+wait_request(St) ->
+ receive
+ {epp_request,From,scan_erl_form} -> From;
+ {epp_request,From,macro_defs} ->
+ epp_reply(From, dict:to_list(St#epp.macs)),
+ wait_request(St);
+ {epp_request,From,close} ->
+ close_file(St),
+ epp_reply(From, ok),
+ exit(normal);
+ {'EXIT',_,R} ->
+ exit(R);
+ Other ->
+ io:fwrite("Epp: unknown '~w'\n", [Other]),
+ wait_request(St)
+ end.
+
+close_file(#epp{pre_opened = true}) ->
+ ok;
+close_file(#epp{pre_opened = false, file = File}) ->
+ ok = file:close(File).
+
+wait_req_scan(St) ->
+ From = wait_request(St),
+ scan_toks(From, St).
+
+wait_req_skip(St, Sis) ->
+ From = wait_request(St),
+ skip_toks(From, St, Sis).
+
+%% enter_file(Path, FileName, IncludeToken, From, EppState)
+%% leave_file(From, EppState)
+%% Handle entering and leaving included files. Notify caller when the
+%% current file is changed. Note it is an error to exit a file if we are
+%% in a conditional. These functions never return.
+
+enter_file(_Path, _NewName, Inc, From, St)
+ when length(St#epp.sstk) >= 8 ->
+ epp_reply(From, {error,{abs_loc(Inc),epp,{depth,"include"}}}),
+ wait_req_scan(St);
+enter_file(Path, NewName, Inc, From, St) ->
+ case file:path_open(Path, NewName, [read]) of
+ {ok,NewF,Pname} ->
+ Loc = start_loc(St#epp.location),
+ wait_req_scan(enter_file2(NewF, Pname, From, St, Loc));
+ {error,_E} ->
+ epp_reply(From, {error,{abs_loc(Inc),epp,{include,file,NewName}}}),
+ wait_req_scan(St)
+ end.
+
+%% enter_file2(File, FullName, From, EppState, AtLocation) -> EppState.
+%% Set epp to use this file and "enter" it.
+
+enter_file2(NewF, Pname, From, St, AtLocation) ->
+ enter_file2(NewF, Pname, From, St, AtLocation, []).
+
+enter_file2(NewF, Pname, From, St, AtLocation, ExtraPath) ->
+ Loc = start_loc(AtLocation),
+ enter_file_reply(From, Pname, Loc, AtLocation),
+ Ms = dict:store({atom,'FILE'}, {none,[{string,Loc,Pname}]}, St#epp.macs),
+ Path = St#epp.path ++ ExtraPath,
+ #epp{location=Loc,file=NewF,
+ name=Pname,sstk=[St|St#epp.sstk],path=Path,macs=Ms}.
+
+enter_file_reply(From, Name, Location, AtLocation) ->
+ Attr = loc_attr(AtLocation),
+ Rep = {ok, [{'-',Attr},{atom,Attr,file},{'(',Attr},
+ {string,Attr,file_name(Name)},{',',Attr},
+ {integer,Attr,get_line(Location)},{')',Location},
+ {dot,Attr}]},
+ epp_reply(From, Rep).
+
+%% Flatten filename to a string. Must be a valid filename.
+
+file_name([C | T]) when is_integer(C), C > 0, C =< 255 ->
+ [C | file_name(T)];
+file_name([H|T]) ->
+ file_name(H) ++ file_name(T);
+file_name([]) ->
+ [];
+file_name(N) when is_atom(N) ->
+ atom_to_list(N).
+
+leave_file(From, St) ->
+ case St#epp.istk of
+ [I|Cis] ->
+ epp_reply(From,
+ {error,{St#epp.location,epp,
+ {illegal,"unterminated",I}}}),
+ leave_file(wait_request(St),St#epp{istk=Cis});
+ [] ->
+ case St#epp.sstk of
+ [OldSt|Sts] ->
+ close_file(St),
+ enter_file_reply(From, OldSt#epp.name,
+ OldSt#epp.location, OldSt#epp.location),
+ Ms = dict:store({atom,'FILE'},
+ {none,
+ [{string,OldSt#epp.location,
+ OldSt#epp.name}]},
+ St#epp.macs),
+ wait_req_scan(OldSt#epp{sstk=Sts,macs=Ms});
+ [] ->
+ epp_reply(From, {eof,St#epp.location}),
+ wait_req_scan(St)
+ end
+ end.
+
+%% scan_toks(From, EppState)
+%% 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_reply(From, {error,{St#epp.location,epp,cannot_parse}}),
+ leave_file(From, St) %This serious, just exit!
+ end.
+
+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_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) ->
+ scan_include_lib(Toks, IncLib, From, St);
+scan_toks([{'-',_Lh},{atom,_Li,ifdef}=IfDef|Toks], From, St) ->
+ scan_ifdef(Toks, IfDef, From, St);
+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);
+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) ->
+ scan_elif(Toks, Elif, From, St);
+scan_toks([{'-',_Lh},{atom,_Le,endif}=Endif|Toks], From, St) ->
+ scan_endif(Toks, Endif, From, St);
+scan_toks([{'-',_Lh},{atom,_Lf,file}=FileToken|Toks0], From, St) ->
+ case catch expand_macros(Toks0, {St#epp.macs, St#epp.uses}) of
+ Toks1 when is_list(Toks1) ->
+ scan_file(Toks1, FileToken, From, St);
+ {error,ErrL,What} ->
+ epp_reply(From, {error,{ErrL,epp,What}}),
+ wait_req_scan(St)
+ end;
+scan_toks(Toks0, From, St) ->
+ case catch expand_macros(Toks0, {St#epp.macs, St#epp.uses}) of
+ Toks1 when is_list(Toks1) ->
+ epp_reply(From, {ok,Toks1}),
+ wait_req_scan(St#epp{macs=scan_module(Toks1, St#epp.macs)});
+ {error,ErrL,What} ->
+ epp_reply(From, {error,{ErrL,epp,What}}),
+ wait_req_scan(St)
+ end.
+
+scan_module([{'-',_Lh},{atom,_Lm,module},{'(',_Ll}|Ts], Ms) ->
+ scan_module_1(Ts, [], Ms);
+scan_module([{'-',_Lh},{atom,_Lm,extends},{'(',_Ll}|Ts], Ms) ->
+ scan_extends(Ts, [], Ms);
+scan_module(_Ts, Ms) -> Ms.
+
+scan_module_1([{atom,_,_}=A,{',',L}|Ts], As, Ms) ->
+ %% Parameterized modules.
+ scan_module_1([A,{')',L}|Ts], As, Ms);
+scan_module_1([{atom,Ln,A},{')',_Lr}|_Ts], As, Ms0) ->
+ Mod = lists:concat(lists:reverse([A|As])),
+ Ms = dict:store({atom,'MODULE'},
+ {none,[{atom,Ln,list_to_atom(Mod)}]}, Ms0),
+ dict:store({atom,'MODULE_STRING'}, {none,[{string,Ln,Mod}]}, Ms);
+scan_module_1([{atom,_Ln,A},{'.',_Lr}|Ts], As, Ms) ->
+ scan_module_1(Ts, [".",A|As], Ms);
+scan_module_1([{'.',_Lr}|Ts], As, Ms) ->
+ scan_module_1(Ts, As, Ms);
+scan_module_1(_Ts, _As, Ms) -> Ms.
+
+scan_extends([{atom,Ln,A},{')',_Lr}|_Ts], As, Ms0) ->
+ Mod = lists:concat(lists:reverse([A|As])),
+ Ms = dict:store({atom,'BASE_MODULE'},
+ {none,[{atom,Ln,list_to_atom(Mod)}]}, Ms0),
+ dict:store({atom,'BASE_MODULE_STRING'}, {none,[{string,Ln,Mod}]}, Ms);
+scan_extends([{atom,_Ln,A},{'.',_Lr}|Ts], As, Ms) ->
+ scan_extends(Ts, [".",A|As], Ms);
+scan_extends([{'.',_Lr}|Ts], As, Ms) ->
+ scan_extends(Ts, As, Ms);
+scan_extends(_Ts, _As, Ms) -> Ms.
+
+%% scan_define(Tokens, DefineToken, From, EppState)
+
+scan_define([{'(',_Lp},{atom,_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},{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
+ end;
+scan_define(_Toks, Def, From, St) ->
+ epp_reply(From, {error,{loc(Def),epp,{bad,define}}}),
+ wait_req_scan(St).
+
+%%% Detection of circular macro expansions (which would either keep
+%%% the compiler looping forever, or run out of memory):
+%%% When a macro is defined, we store the names of other macros it
+%%% uses in St#epp.uses. If any macro is undef'ed, that information
+%%% becomes invalid, so we redo it for all remaining macros.
+%%% The circularity detection itself is done when a macro is expanded:
+%%% 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_toks(F, St#epp{uses=U, macs=Ms}).
+
+macro_uses(undefined) ->
+ undefined;
+macro_uses({_Args, Tokens}) ->
+ Uses0 = macro_ref(Tokens),
+ lists:usort(Uses0).
+
+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([_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) ->
+ scan_toks(From, St#epp{macs=dict:erase({atom,M}, St#epp.macs),
+ uses=all_macro_uses(St#epp.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)});
+scan_undef(_Toks, Undef, From, St) ->
+ epp_reply(From, {error,{loc(Undef),epp,{bad,undef}}}),
+ wait_req_scan(St).
+
+%% scan_include(Tokens, IncludeToken, From, St)
+
+scan_include([{'(',_Llp},{string,_Lf,NewName0},{')',_Lrp},{dot,_Ld}], Inc,
+ From, St) ->
+ NewName = expand_var(NewName0),
+ enter_file(St#epp.path, NewName, Inc, From, St);
+scan_include(_Toks, Inc, From, St) ->
+ epp_reply(From, {error,{abs_loc(Inc),epp,{bad,include}}}),
+ wait_req_scan(St).
+
+%% scan_include_lib(Tokens, IncludeToken, From, EppState)
+%% For include_lib we first test if we can find the file through the
+%% normal search path, if not we assume that the first directory name
+%% is a library name, find its true directory and try with that.
+
+find_lib_dir(NewName) ->
+ [Lib | Rest] = filename:split(NewName),
+ {code:lib_dir(list_to_atom(Lib)), Rest}.
+
+scan_include_lib([{'(',_Llp},{string,_Lf,_NewName0},{')',_Lrp},{dot,_Ld}],
+ Inc, From, St)
+ when length(St#epp.sstk) >= 8 ->
+ epp_reply(From, {error,{abs_loc(Inc),epp,{depth,"include_lib"}}}),
+ wait_req_scan(St);
+scan_include_lib([{'(',_Llp},{string,_Lf,NewName0},{')',_Lrp},{dot,_Ld}],
+ Inc, From, St) ->
+ NewName = expand_var(NewName0),
+ Loc = start_loc(St#epp.location),
+ case file:path_open(St#epp.path, NewName, [read]) of
+ {ok,NewF,Pname} ->
+ wait_req_scan(enter_file2(NewF, Pname, From, St, Loc));
+ {error,_E1} ->
+ case catch find_lib_dir(NewName) of
+ {LibDir, Rest} when is_list(LibDir) ->
+ LibName = filename:join([LibDir | Rest]),
+ case file:open(LibName, [read]) of
+ {ok,NewF} ->
+ ExtraPath = [filename:dirname(LibName)],
+ wait_req_scan(enter_file2(NewF, LibName, From,
+ St, Loc, ExtraPath));
+ {error,_E2} ->
+ epp_reply(From,
+ {error,{abs_loc(Inc),epp,
+ {include,lib,NewName}}}),
+ wait_req_scan(St)
+ end;
+ _Error ->
+ epp_reply(From, {error,{abs_loc(Inc),epp,
+ {include,lib,NewName}}}),
+ wait_req_scan(St)
+ end
+ end;
+scan_include_lib(_Toks, Inc, From, St) ->
+ epp_reply(From, {error,{abs_loc(Inc),epp,{bad,include_lib}}}),
+ wait_req_scan(St).
+
+%% scan_ifdef(Tokens, IfdefToken, From, EppState)
+%% scan_ifndef(Tokens, IfdefToken, From, EppSate)
+%% Handle the conditional parsing of a file.
+%% Report a badly formed if[n]def test and then treat as undefined macro.
+
+scan_ifdef([{'(',_Llp},{atom,_Lm,M},{')',_Lrp},{dot,_Ld}], _IfD, From, St) ->
+ case dict:find({atom,M}, St#epp.macs) of
+ {ok,_Def} ->
+ scan_toks(From, St#epp{istk=[ifdef|St#epp.istk]});
+ error ->
+ skip_toks(From, St, [ifdef])
+ end;
+scan_ifdef([{'(',_Llp},{var,_Lm,M},{')',_Lrp},{dot,_Ld}], _IfD, From, St) ->
+ case dict:find({atom,M}, St#epp.macs) of
+ {ok,_Def} ->
+ scan_toks(From, St#epp{istk=[ifdef|St#epp.istk]});
+ error ->
+ skip_toks(From, St, [ifdef])
+ end;
+scan_ifdef(_Toks, IfDef, From, St) ->
+ epp_reply(From, {error,{loc(IfDef),epp,{bad,ifdef}}}),
+ wait_req_skip(St, [ifdef]).
+
+scan_ifndef([{'(',_Llp},{atom,_Lm,M},{')',_Lrp},{dot,_Ld}], _IfnD, From, St) ->
+ case dict:find({atom,M}, St#epp.macs) of
+ {ok,_Def} ->
+ skip_toks(From, St, [ifndef]);
+ error ->
+ scan_toks(From, St#epp{istk=[ifndef|St#epp.istk]})
+ end;
+scan_ifndef([{'(',_Llp},{var,_Lm,M},{')',_Lrp},{dot,_Ld}], _IfnD, From, St) ->
+ case dict:find({atom,M}, St#epp.macs) of
+ {ok,_Def} ->
+ skip_toks(From, St, [ifndef]);
+ error ->
+ scan_toks(From, St#epp{istk=[ifndef|St#epp.istk]})
+ end;
+scan_ifndef(_Toks, IfnDef, From, St) ->
+ epp_reply(From, {error,{loc(IfnDef),epp,{bad,ifndef}}}),
+ wait_req_skip(St, [ifndef]).
+
+%% scan_else(Tokens, ElseToken, From, EppState)
+%% If we are in an if body then convert to else and skip, if we are in an
+%% else or not in anything report an error.
+
+scan_else([{dot,_Ld}], Else, From, St) ->
+ case St#epp.istk of
+ ['else'|Cis] ->
+ epp_reply(From, {error,{loc(Else),
+ epp,{illegal,"repeated",'else'}}}),
+ wait_req_skip(St#epp{istk=Cis}, ['else']);
+ [_I|Cis] ->
+ skip_toks(From, St#epp{istk=Cis}, ['else']);
+ [] ->
+ epp_reply(From, {error,{loc(Else),epp,
+ {illegal,"unbalanced",'else'}}}),
+ wait_req_scan(St)
+ end;
+scan_else(_Toks, Else, From, St) ->
+ epp_reply(From, {error,{loc(Else),epp,{bad,'else'}}}),
+ wait_req_scan(St).
+
+%% scan_if(Tokens, EndifToken, From, EppState)
+%% Handle the conditional parsing of a file.
+%% Report a badly formed if test and then treat as false macro.
+
+scan_if(_Toks, If, From, St) ->
+ epp_reply(From, {error,{loc(If),epp,{'NYI','if'}}}),
+ wait_req_skip(St, ['if']).
+
+%% scan_elif(Tokens, EndifToken, From, EppState)
+%% Handle the conditional parsing of a file.
+%% Report a badly formed if test and then treat as false macro.
+
+scan_elif(_Toks, Elif, From, St) ->
+ epp_reply(From, {error,{loc(Elif),epp,{'NYI','elif'}}}),
+ wait_req_scan(St).
+
+%% scan_endif(Tokens, EndifToken, From, EppState)
+%% If we are in an if body then exit it, else report an error.
+
+scan_endif([{dot,_Ld}], Endif, From, St) ->
+ case St#epp.istk of
+ [_I|Cis] ->
+ scan_toks(From, St#epp{istk=Cis});
+ [] ->
+ epp_reply(From, {error,{loc(Endif),epp,
+ {illegal,"unbalanced",endif}}}),
+ wait_req_scan(St)
+ end;
+scan_endif(_Toks, Endif, From, St) ->
+ epp_reply(From, {error,{loc(Endif),epp,{bad,endif}}}),
+ wait_req_scan(St).
+
+%% scan_file(Tokens, FileToken, From, EppState)
+%% Set the current file and line to the given file and line.
+%% Note that the line of the attribute itself is kept.
+
+scan_file([{'(',_Llp},{string,_Ls,Name},{',',_Lc},{integer,_Li,Ln},{')',_Lrp},
+ {dot,_Ld}], Tf, From, St) ->
+ enter_file_reply(From, Name, Ln, neg_line(abs_loc(Tf))),
+ Ms = dict:store({atom,'FILE'}, {none,[{string,1,Name}]}, St#epp.macs),
+ Locf = loc(Tf),
+ NewLoc = new_location(Ln, St#epp.location, Locf),
+ scan_toks(From, St#epp{name=Name,location=NewLoc,macs=Ms});
+scan_file(_Toks, Tf, From, St) ->
+ epp_reply(From, {error,{loc(Tf),epp,{bad,file}}}),
+ wait_req_scan(St).
+
+new_location(Ln, Le, Lf) when is_integer(Lf) ->
+ Ln+(Le-Lf);
+new_location(Ln, {Le,_}, {Lf,_}) ->
+ {Ln+(Le-Lf),1}.
+
+%% skip_toks(From, EppState, SkipIstack)
+%% Skip over forms until current conditional has been exited. Handle
+%% nested conditionals and repeated 'else's.
+
+skip_toks(From, St, [I|Sis]) ->
+ case io:scan_erl_form(St#epp.file, '', St#epp.location) of
+ {ok,[{'-',_Lh},{atom,_Li,ifdef}|_Toks],Cl} ->
+ skip_toks(From, St#epp{location=Cl}, [ifdef,I|Sis]);
+ {ok,[{'-',_Lh},{atom,_Li,ifndef}|_Toks],Cl} ->
+ skip_toks(From, St#epp{location=Cl}, [ifndef,I|Sis]);
+ {ok,[{'-',_Lh},{'if',_Li}|_Toks],Cl} ->
+ skip_toks(From, St#epp{location=Cl}, ['if',I|Sis]);
+ {ok,[{'-',_Lh},{atom,_Le,'else'}=Else|_Toks],Cl}->
+ skip_else(Else, From, St#epp{location=Cl}, [I|Sis]);
+ {ok,[{'-',_Lh},{atom,_Le,endif}|_Toks],Cl} ->
+ skip_toks(From, St#epp{location=Cl}, Sis);
+ {ok,_Toks,Cl} ->
+ skip_toks(From, St#epp{location=Cl}, [I|Sis]);
+ {error,_E,Cl} ->
+ skip_toks(From, St#epp{location=Cl}, [I|Sis]);
+ {eof,Cl} ->
+ leave_file(From, St#epp{location=Cl,istk=[I|Sis]});
+ {error,_E} ->
+ epp_reply(From, {error,{St#epp.location,epp,cannot_parse}}),
+ leave_file(From, St) %This serious, just exit!
+ end;
+skip_toks(From, St, []) ->
+ scan_toks(From, St).
+
+skip_else(Else, From, St, ['else'|Sis]) ->
+ epp_reply(From, {error,{loc(Else),epp,{illegal,"repeated",'else'}}}),
+ wait_req_skip(St, ['else'|Sis]);
+skip_else(_Else, From, St, [_I]) ->
+ scan_toks(From, St#epp{istk=['else'|St#epp.istk]});
+skip_else(_Else, From, St, Sis) ->
+ skip_toks(From, St, Sis).
+
+%% macro_pars(Tokens, ArgStack)
+%% macro_expansion(Tokens)
+%% Extract the macro parameters and the expansion from a macro definition.
+
+macro_pars([{')',_Lp}, {',',_Ld}|Ex], Args) ->
+ {ok, {lists:reverse(Args), macro_expansion(Ex)}};
+macro_pars([{var,_,Name}, {')',_Lp}, {',',_Ld}|Ex], Args) ->
+ false = lists:member(Name, Args), %Prolog is nice
+ {ok, {lists:reverse([Name|Args]), macro_expansion(Ex)}};
+macro_pars([{var,_L,Name}, {',',_}|Ts], Args) ->
+ false = lists:member(Name, Args),
+ macro_pars(Ts, [Name|Args]).
+
+macro_expansion([{')',_Lp},{dot,_Ld}]) -> [];
+macro_expansion([{dot,_Ld}]) -> []; %Be nice, allow no right paren!
+macro_expansion([T|Ts]) ->
+ [T|macro_expansion(Ts)].
+
+%% expand_macros(Tokens, Macros)
+%% expand_macro(Tokens, MacroToken, RestTokens)
+%% Expand the macros in a list of tokens, making sure that an expansion
+%% gets the same location as the macro call.
+
+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
+ {ok,{none,Exp}} ->
+ expand_macros(expand_macro(Exp, Tinfo, Toks, dict:new()), Ms0);
+ {ok,{As,Exp}} ->
+ {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}});
+ error ->
+ throw({error,Lm,{undefined,M}})
+ end.
+
+check_uses(undefined, _Anc, _U, _Lm) ->
+ ok;
+check_uses([], _Anc, _U, _Lm) ->
+ ok;
+check_uses([M|Rest], Anc, U, Lm) ->
+ case lists:member(M, Anc) of
+ true ->
+ {_, 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) ->
+ case dict:find(M, U) of
+ error ->
+ [];
+ {ok, L} ->
+ L
+ end.
+
+%% Macro expansion
+%% Note: io:scan_erl_form() does not return comments or white spaces.
+expand_macros([{'?',_Lq},{atom,_Lm,M}=MacT|Toks], Ms) ->
+ expand_macros(atom, MacT, M, Toks, Ms);
+%% Special macros
+expand_macros([{'?',_Lq},{var,Lm,'LINE'}=Tok|Toks], Ms) ->
+ {line,Line} = erl_scan:token_info(Tok, line),
+ [{integer,Lm,Line}|expand_macros(Toks, Ms)];
+expand_macros([{'?',_Lq},{var,_Lm,M}=MacT|Toks], Ms) ->
+ expand_macros(atom, MacT, M, Toks, Ms);
+%% Illegal macros
+expand_macros([{'?',_Lq},Token|_Toks], _Ms) ->
+ T = case erl_scan:token_info(Token, text) of
+ {text,Text} ->
+ Text;
+ undefined ->
+ {symbol,Symbol} = erl_scan:token_info(Token, symbol),
+ io_lib:write(Symbol)
+ end,
+ throw({error,loc(Token),{call,[$?|T]}});
+expand_macros([T|Ts], Ms) ->
+ [T|expand_macros(Ts, Ms)];
+expand_macros([], _Ms) -> [].
+
+%% bind_args(Tokens, MacroLocation, MacroName, ArgumentVars, Bindings)
+%% Collect the arguments to a macro call and check for correct number.
+
+bind_args([{'(',_Llp},{')',_Lrp}|Toks], _Lm, _M, [], Bs) ->
+ {Bs,Toks};
+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}}).
+
+macro_args([{')',_Lrp}|Toks], _Lm, _M, [], Bs) ->
+ {Bs,Toks};
+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}});
+macro_args(_Toks, Lm, M, _As, _Bs) ->
+ throw({error,Lm,{mismatch,M}}).
+
+store_arg(L, M, _A, [], _Bs) ->
+ throw({error,L,{mismatch,M}});
+store_arg(_L, _M, A, Arg, Bs) ->
+ dict:store(A, Arg, Bs).
+
+%% 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
+%% scanning until matching "close parenthesis".
+
+macro_arg([{',',Lc}|Toks], [], Arg) ->
+ {lists:reverse(Arg),[{',',Lc}|Toks]};
+macro_arg([{')',Lrp}|Toks], [], Arg) ->
+ {lists:reverse(Arg),[{')',Lrp}|Toks]};
+macro_arg([{'(',Llp}|Toks], E, Arg) ->
+ macro_arg(Toks, [')'|E], [{'(',Llp}|Arg]);
+macro_arg([{'<<',Lls}|Toks], E, Arg) ->
+ macro_arg(Toks, ['>>'|E], [{'<<',Lls}|Arg]);
+macro_arg([{'[',Lls}|Toks], E, Arg) ->
+ macro_arg(Toks, [']'|E], [{'[',Lls}|Arg]);
+macro_arg([{'{',Llc}|Toks], E, Arg) ->
+ macro_arg(Toks, ['}'|E], [{'{',Llc}|Arg]);
+macro_arg([{'begin',Lb}|Toks], E, Arg) ->
+ macro_arg(Toks, ['end'|E], [{'begin',Lb}|Arg]);
+macro_arg([{'if',Li}|Toks], E, Arg) ->
+ macro_arg(Toks, ['end'|E], [{'if',Li}|Arg]);
+macro_arg([{'case',Lc}|Toks], E, Arg) ->
+ macro_arg(Toks, ['end'|E], [{'case',Lc}|Arg]);
+macro_arg([{'fun',Lc}|[{'(',_}|_]=Toks], E, Arg) ->
+ macro_arg(Toks, ['end'|E], [{'fun',Lc}|Arg]);
+macro_arg([{'receive',Lr}|Toks], E, Arg) ->
+ macro_arg(Toks, ['end'|E], [{'receive',Lr}|Arg]);
+macro_arg([{'try',Lr}|Toks], E, Arg) ->
+ macro_arg(Toks, ['end'|E], [{'try',Lr}|Arg]);
+macro_arg([{'cond',Lr}|Toks], E, Arg) ->
+ macro_arg(Toks, ['end'|E], [{'cond',Lr}|Arg]);
+macro_arg([{'query',Lr}|Toks], E, Arg) ->
+ macro_arg(Toks, ['end'|E], [{'query',Lr}|Arg]);
+macro_arg([{Rb,Lrb}|Toks], [Rb|E], Arg) -> %Found matching close
+ macro_arg(Toks, E, [{Rb,Lrb}|Arg]);
+macro_arg([T|Toks], E, Arg) ->
+ macro_arg(Toks, E, [T|Arg]);
+macro_arg([], _E, Arg) ->
+ {lists:reverse(Arg),[]}.
+
+%% expand_macro(MacroDef, MacroTokenInfo, RestTokens, Bindings)
+%% expand_arg(Argtokens, MacroTokens, MacroLocation, RestTokens, Bindings)
+%% Insert the macro expansion replacing macro parameters with their
+%% argument values, inserting the location of first the macro call
+%% and then the macro arguments, i.e. simulate textual expansion.
+
+expand_macro([{var,_Lv,V}|Ts], L, Rest, Bs) ->
+ case dict:find(V, Bs) of
+ {ok,Val} ->
+ %% lists:append(Val, expand_macro(Ts, L, Rest, Bs));
+ expand_arg(Val, Ts, L, Rest, Bs);
+ error ->
+ [{var,L,V}|expand_macro(Ts, L, Rest, Bs)]
+ end;
+expand_macro([{'?', _}, {'?', _}, {var,_Lv,V}|Ts], L, Rest, Bs) ->
+ case dict:find(V, Bs) of
+ {ok,Val} ->
+ %% lists:append(Val, expand_macro(Ts, L, Rest, Bs));
+ expand_arg(stringify(Val, L), Ts, L, Rest, Bs);
+ error ->
+ [{var,L,V}|expand_macro(Ts, L, Rest, Bs)]
+ end;
+expand_macro([T|Ts], L, Rest, Bs) ->
+ [setelement(2, T, L)|expand_macro(Ts, L, Rest, Bs)];
+expand_macro([], _L, Rest, _Bs) -> Rest.
+
+expand_arg([A|As], Ts, _L, Rest, Bs) ->
+ %% It is not obvious that the location of arguments should replace L.
+ NextL = element(2, A),
+ [A|expand_arg(As, Ts, NextL, Rest, Bs)];
+expand_arg([], Ts, L, Rest, Bs) ->
+ expand_macro(Ts, L, Rest, Bs).
+
+%%% stringify(Ts, L) returns a list of one token: a string which when
+%%% tokenized would yield the token list Ts.
+
+%% erl_scan:token_info(T, text) is not backward compatible with this.
+token_src({dot, _}) ->
+ ".";
+token_src({X, _}) when is_atom(X) ->
+ atom_to_list(X);
+token_src({var, _, X}) ->
+ atom_to_list(X);
+token_src({char,_,C}) ->
+ io_lib:write_char(C);
+token_src({string, _, X}) ->
+ lists:flatten(io_lib:format("~p", [X]));
+token_src({_, _, X}) ->
+ lists:flatten(io_lib:format("~w", [X])).
+
+stringify1([]) ->
+ [];
+stringify1([T | Tokens]) ->
+ [io_lib:format(" ~s", [token_src(T)]) | stringify1(Tokens)].
+
+stringify(Ts, L) ->
+ [$\s | S] = lists:flatten(stringify1(Ts)),
+ [{string, L, S}].
+
+%% epp_request(Epp)
+%% epp_request(Epp, Request)
+%% epp_reply(From, Reply)
+%% Handle communication with the epp.
+
+epp_request(Epp) ->
+ wait_epp_reply(Epp, erlang:monitor(process, Epp)).
+
+epp_request(Epp, Req) ->
+ Epp ! {epp_request,self(),Req},
+ wait_epp_reply(Epp, erlang:monitor(process, Epp)).
+
+epp_reply(From, Rep) ->
+ From ! {epp_reply,self(),Rep},
+ ok.
+
+wait_epp_reply(Epp, Mref) ->
+ receive
+ {epp_reply,Epp,Rep} ->
+ erlang:demonitor(Mref),
+ receive {'DOWN',Mref,_,_,_} -> ok after 0 -> ok end,
+ Rep;
+ {'DOWN',Mref,_,_,E} ->
+ receive {epp_reply,Epp,Rep} -> Rep
+ after 0 -> exit(E)
+ end
+ end.
+
+expand_var([$$ | _] = NewName) ->
+ case catch expand_var1(NewName) of
+ {ok, ExpName} ->
+ ExpName;
+ _ ->
+ NewName
+ end;
+expand_var(NewName) ->
+ NewName.
+
+expand_var1(NewName) ->
+ [[$$ | Var] | Rest] = filename:split(NewName),
+ Value = os:getenv(Var),
+ true = Value =/= false,
+ {ok, filename:join([Value | Rest])}.
+
+%% The line only. (Other tokens may have the column and text as well...)
+loc_attr(Line) when is_integer(Line) ->
+ Line;
+loc_attr({Line,_Column}) ->
+ Line.
+
+loc(Token) ->
+ {location,Location} = erl_scan:token_info(Token, location),
+ Location.
+
+abs_loc(Token) ->
+ loc(setelement(2, Token, abs_line(element(2, Token)))).
+
+neg_line(L) ->
+ erl_scan:set_attribute(line, L, fun(Line) -> -abs(Line) end).
+
+abs_line(L) ->
+ erl_scan:set_attribute(line, L, fun(Line) -> abs(Line) end).
+
+start_loc(Line) when is_integer(Line) ->
+ 1;
+start_loc({_Line, _Column}) ->
+ {1,1}.
+
+get_line(Line) when is_integer(Line) ->
+ Line;
+get_line({Line,_Column}) ->
+ Line.
+
+%% epp has always output -file attributes when entering and leaving
+%% included files (-include, -include_lib). Starting with R11B the
+%% -file attribute is also recognized in the input file. This is
+%% mainly aimed at yecc, the parser generator, which uses the -file
+%% attribute to get correct lines in messages referring to code
+%% supplied by the user (actions etc in .yrl files).
+%%
+%% In a perfect world (read: perfectly implemented applications such
+%% as Xref, Cover, Debugger, etc.) it would not be necessary to
+%% distinguish -file attributes from epp and the input file. The
+%% Debugger for example could have one window for each referred file,
+%% each window with its own set of breakpoints etc. The line numbers
+%% of the abstract code would then point into different windows
+%% depending on the -file attribute. [Note that if, as is the case for
+%% yecc, code has been copied into the file, then it is possible that
+%% the copied code differ from the one referred to by the -file
+%% attribute, which means that line numbers can mismatch.] In practice
+%% however it is very rare with Erlang functions in included files, so
+%% only one window is used per module. This means that the line
+%% numbers of the abstract code have to be adjusted to refer to the
+%% top-most source file. The function interpret_file_attributes/1
+%% below interprets the -file attribute and returns forms where line
+%% numbers refer to the top-most file. The -file attribute forms that
+%% have been output by epp (corresponding to -include and
+%% -include_lib) are kept, but the user's -file attributes are
+%% removed. This seems sufficient for now.
+%%
+%% It turns out to be difficult to distinguish -file attributes in the
+%% input file from the ones added by epp unless some action is taken.
+%% The (less than perfect) solution employed is to let epp assign
+%% negative line numbers to user supplied -file attributes.
+
+%% Note: it is assumed that the second element is a line or a key-list
+%% where 'line' can be found.
+
+interpret_file_attribute(Forms) ->
+ interpret_file_attr(Forms, 0, []).
+
+interpret_file_attr([{attribute,Loc,file,{File,Line}}=Form | Forms],
+ Delta, Fs) ->
+ {line, L} = erl_scan:attributes_info(Loc, line),
+ if
+ L < 0 ->
+ %% -file attribute
+ interpret_file_attr(Forms, (abs(L) + Delta) - Line, Fs);
+ true ->
+ %% -include or -include_lib
+ % true = L =:= Line,
+ case Fs of
+ [_, Delta1, File | Fs1] -> % end of included file
+ [Form | interpret_file_attr(Forms, Delta1, [File | Fs1])];
+ _ -> % start of included file
+ [Form | interpret_file_attr(Forms, 0, [File, Delta | Fs])]
+ end
+ end;
+interpret_file_attr([Form0 | Forms], Delta, Fs) ->
+ F = fun(Attrs) ->
+ F2 = fun(L) -> abs(L) + Delta end,
+ erl_scan:set_attribute(line, Attrs, F2)
+ end,
+ Form = erl_lint:modify_line(Form0, F),
+ [Form | interpret_file_attr(Forms, Delta, Fs)];
+interpret_file_attr([], _Delta, _Fs) ->
+ [].
+