Alternative compiler messages

Thomas Lindgren <>
Wed Apr 25 17:34:07 CEST 2001


Here's an alternative way of reporting compiler errors and warnings
that I have found useful. What does it do?

- Filename is reported once, rather than per compiler message. This
  reduces clutter when absolute paths are used;

- Warnings about unused functions are summarized into a single line,
  rather than one line per function;

- Errors about undefined functions are reported once per undef.function
  (giving all the lines at once), rather than once per line.

What do you have to do? Save the file below somewhere as:

     compile2.erl

and compile it:

    erlc -I<path-to-OTP>/lib/stdlib/include compile2.erl

Now you can try compile2:file(File) with some rotten File and see
whether you like the results. If so, rename the module to 'compile'
and move the file (after saving the original compile.erl, if you're
being careful) into:

	<path-to-OTP>/lib/compiler/src/compile.erl

Make, and make install. You're set.

Enjoy, and let me know about any bugs (none known),

			Thomas
-- 
Thomas Lindgren					
Alteon WebSystems
----------------------------------------------------------------------
%% ``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 via the world wide web 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 Initial Developer of the Original Code is Ericsson Utvecklings AB.
%% Portions created by Ericsson are Copyright 1999, Ericsson Utvecklings
%% AB. All Rights Reserved.''
%% 
%%     $Id$
%%
%% Purpose: Run the Erlang compiler.

-module(compile2).
-include("erl_compile.hrl").

%% High-level interface.
-export([file/1,file/2,format_error/1,iofile/1,compile/3]).
-export([forms/1,forms/2]).
-export([output_generated/1]).
-export([options/0,options/1]).

%% Internal functions.
-export([internal/3]).


-import(lists, [member/2,reverse/1,keysearch/3,last/1,
		map/2,foreach/2,foldr/3,any/2,filter/2]).

-define(DEF_VERSION, v3).			%Default compiler version.

%% file(FileName)
%% file(FileName, Options)
%%  Compile the module in file FileName.

-define(DEFAULT_OPTIONS, [verbose,report_errors,report_warnings]).

-define(pass(P), {P,fun P/1}).

file(File) -> file(File, ?DEFAULT_OPTIONS).

file(File, Opts) when atom(Opts) ->
    file(File, [Opts|?DEFAULT_OPTIONS]);
file(File, Opts) when list(Opts) ->
    do_compile({file,File}, Opts++env_default_opts()).

forms(File) -> forms(File, ?DEFAULT_OPTIONS).

forms(Forms, Opts) when atom(Opts) ->
    forms(Forms, [Opts|?DEFAULT_OPTIONS]);
forms(Forms, Opts) when list(Opts) ->
    do_compile({forms,Forms}, [binary|Opts++env_default_opts()]).

env_default_opts() ->
    Key = "ERL_COMPILER_OPTIONS",
    case os:getenv(Key) of
	false -> [];
	Str when list(Str) ->
	    case erl_scan:string(Str) of
		{ok,Tokens,_} ->
		    case erl_parse:parse_term(Tokens ++ [{dot, 1}]) of
			{ok,List} when list(List) -> List;
			{ok,Term} -> [Term];
			{error,Reason} ->
			    io:format("Ignoring bad term in ~s\n", [Key]),
			    []
		    end;
		{error, {_,_,Reason}, _} ->
		    io:format("Ignoring bad term in ~s\n", [Key]),
		    []
	    end
    end.
	    
do_compile(Input, Opts0) ->
    Opts = foldr(fun expand_opt/2, [], Opts0),
    Serv = spawn_link(?MODULE, internal, [self(),Input,Opts]),
    receive
	{Serv,Rep} -> Rep
    end.

%% Given a list of compilation options, returns true if compile:file/2
%% would have generated a Beam file, false otherwise (if only a binary or a
%% listing file would have been generated).

output_generated(Opts) ->
    any(fun ({save_binary,F}) -> true;
	    (Other) -> false
	end, passes(file, ?DEF_VERSION, Opts)).

expand_opt(report, Os) -> [report_errors,report_warnings|Os];
expand_opt(return, Os) -> [return_errors,return_warnings|Os];
expand_opt(O, Os) -> [O|Os].

%% format_error(ErrorDescriptor) -> string()

format_error(jam_is_dead) ->
    "JAM is dead!";
format_error(v1_is_dead) ->
    "The v1 compiler is no longer supported.";
format_error(v2_is_dead) ->
    "The v2 compiler is no longer supported.";
format_error({open,E}) ->
    io_lib:format("open error '~s'", [file:format_error(E)]);
format_error(write_error) ->
    "error writing file";
format_error({rename,S}) ->
    io_lib:format("error renaming ~s", [S]);
format_error({parse_transform,M,R}) ->
    io_lib:format("error in transform '~s': ~p", [M, R]);
format_error({crash,Pass,Reason}) ->
    io_lib:format("internal error in ~p;\ncrash reason: ~p", [Pass,Reason]);
format_error({bad_return,Pass,Reason}) ->
    io_lib:format("internal error in ~p;\nbad return value: ~p", 
		  [Pass,Reason]);
format_error({unused_functions,[{F,A}|Fs]}) ->
    %% note: Fs is non-empty list; see s_warn/N below for when this warning is
    %%  generated
    io_lib:format("functions ~p/~p~s are unused.",
		  [F,A,lists:flatten(
			 [io_lib:format(", ~p/~p",[F,A]) || {F,A} <- Fs ])]);
format_error({undefined_function,{F,A},[Line]}) ->
    io_lib:format("line ~w: function ~w/~w undefined.",[Line,F,A]);
format_error({undefined_function,{F,A},Lines}) ->
    io_lib:format("lines ~w: function ~w/~w undefined.",[Lines,F,A]).

%% The compile state record.
-record(compile, {filename="",
		  dir="",
		  base="",
		  ifile="",
		  ofile="",
		  module=[],
		  code=[],
		  abstract_code=[],		%Abstract code for debugger.
		  options=[],
		  errors=[],
		  warnings=[]}).

internal(Master, Input, Opts) ->
    Master ! {self(),
	      case catch internal(Input, Opts) of
		  {'EXIT', Reason} ->
		      {error, Reason};
		  Other ->
		      Other
	      end}.

internal({forms,Forms}, Opts0) ->
    {Ver,Opts} = compiler_version(Opts0),
    Ps = passes(forms, Ver, Opts),
    internal_comp(Ps, "", "", #compile{code=Forms,options=Opts});
internal({file,File}, Opts0) ->
    {Ver,Opts} = compiler_version(Opts0),
    Ps = passes(file, Ver, Opts),
    case member(asm, Opts) of
	false -> internal_comp(Ps, File, ".erl", #compile{options=Opts});
	true -> internal_comp(Ps, File, ".S", #compile{options=Opts})
    end.

internal_comp(Passes, File, Suffix, St0) ->
    Dir = filename:dirname(File),
    Base = filename:basename(File, Suffix),
    St1 = St0#compile{filename=File, dir=Dir, base=Base,
		      ifile=erlfile(Dir, Base, Suffix),
		      ofile=objfile(Base, St0)},
    Run = case member(time, St1#compile.options) of
	      true  -> fun run_tc/2;
	      false -> fun({Name,Fun}, St) -> catch Fun(St) end
	  end,
    case fold_comp(Passes, Run, St1) of
	{ok,St2} -> comp_ret_ok(St2);
	{error,St2} -> comp_ret_err(St2)
    end.

fold_comp([{Name,Pass}|Ps], Run, St0) ->
    case Run({Name,Pass}, St0) of
	{ok,St1} -> fold_comp(Ps, Run, St1);
	{error,St1} -> {error,St1};
	{'EXIT',Reason} ->
	    Es = [{St0#compile.ifile,[{none,?MODULE,{crash,Name,Reason}}]}],
	    {error,St0#compile{errors=St0#compile.errors ++ Es}};
	Other ->
	    Es = [{St0#compile.ifile,[{none,?MODULE,{bad_return,Name,Other}}]}],
	    {error,St0#compile{errors=St0#compile.errors ++ Es}}
    end;
fold_comp([], Run, St) -> {ok,St}.

os_process_size() ->
    case os:type() of
	{unix, sunos} ->
	    Size0 = os:cmd("ps -o vsz -p " ++ os:getpid() ++ " | tail -1"),
	    Size = list_to_integer(lib:nonl(Size0));
	_ ->
	    0
    end.	    

run_tc(NameFun, St) ->
    run_tc(NameFun, statistics(runtime), St).

run_tc({Name,Fun}, Before0, St) ->
    %% This division into two functions is a hack.  If we would had stack
    %% trimming, dead variables would have been removed from the stack.
    %% Well, anyway, the St variable will not be saved on the stack,
    %% because it is not referenced after the catch.

    Val = (catch Fun(St)),
    After0 = statistics(runtime),
    {Before_c, _} = Before0,
    {After_c, _} = After0,
    io:format(" ~-30s: ~10.3f s (~w k)\n",
	      [Name, (After_c-Before_c) / 1000, os_process_size()]),
    Val.

comp_ret_ok(St) ->
    report_warnings(St),
    Ret1 = case member(binary, St#compile.options) of
	       true -> [St#compile.code];
	       false -> []
	   end,
    Ret2 = case member(return_warnings, St#compile.options) of
	       true -> Ret1 ++ [St#compile.warnings];
	       false -> Ret1
	   end,
    list_to_tuple([ok,St#compile.module|Ret2]).

comp_ret_err(St) ->
    report_errors(St),
    report_warnings(St),
    case member(return_errors, St#compile.options) of
	true -> {error,St#compile.errors,St#compile.warnings};
	false -> error
    end.

%% passes(form|file, [Option]) -> [{Name,PassFun}]
%%  Figure out which passes that need to be run.

passes(forms, Ver, Opts) ->
    select_passes(standard_passes(Ver), Opts);
passes(file, Ver, Opts) ->
    Ps = case member(asm, Opts) of
	     true ->
		 [?pass(beam_consult_asm),?pass(beam_asm),
		  {unless,binary,?pass(save_binary)}];
	     false ->
		 [?pass(parse_module)|standard_passes(Ver)]
	 end,
    Fs = select_passes(Ps, Opts),

    %% If the last pass saves the resulting binary to a file,
    %% insert a first pass to remove the file.

    [?pass(error_if_jam)|
     case last(Fs) of
	 {save_binary,Fun} -> [?pass(remove_file)|Fs];
	 Other -> Fs
     end].

%% select_passes([Command], Opts) ->  [{Name,Function}]
%%  Interpret the lists of commands to return a pure list of passes.
%%
%%  Command can be one of:
%%
%%    {pass,Mod}	Will be expanded to a call to the external
%%			function Mod:module(Code, Options).  This
%%			function must transform the code and return
%%			{ok,NewCode} or {error,Term}.
%%			Example: {pass,beam_codegen}
%%
%%    {Name,Fun}	Name is an atom giving the name of the pass.
%%    			Fun is an 'fun' taking one argument: a compile record.
%%			The fun should return {ok,NewCompileRecord} or
%%			{error,NewCompileRecord}.
%%			Note: ?pass(Name) is equvivalent to {Name, fun Name/1}.
%%			Example: ?pass(parse_module)
%%
%%    {src_listing,Ext}	Produces an Erlang source listing with the
%%			the file extension Ext.  (Ext should not contain
%%			a period.)  No more passes will be run.
%%
%%    {listing,Ext}	Produce an listing of the terms in the internal
%%			representation.  The extension of the listing
%%			file will be Ext.  (Ext should not contain
%%			a period.)   No more passes will be run.
%%
%%    {iff,Flag,Cmd}	If the given Flag is given in the option list,
%%			Cmd will be interpreted as a command.
%%			Otherwise, Cmd will be ignored.
%%			Example: {iff,dcg,{listing,"codegen}}
%%
%%    {unless,Flag,Cmd}	If the given Flag is NOT given in the option list,
%%			Cmd will be interpreted as a command.
%%			Otherwise, Cmd will be ignored.
%%			Example: {unless,no_kernopt,{pass,sys_kernopt}}
%%

select_passes([{pass,Mod}|Ps], Opts) ->
    F = fun(St) ->
		case catch Mod:module(St#compile.code, St#compile.options) of
		    {ok,Code} -> {ok,St#compile{code=Code}};
		    {error,Es} -> {error,St#compile{errors=St#compile.errors ++ Es}}
		end
	end,
    [{Mod,F}|select_passes(Ps, Opts)];
select_passes([{src_listing,Ext}|Ps], Opts) ->
    [{listing,fun (St) -> src_listing(Ext, St) end}];
select_passes([{listing,Ext}|Ps], Opts) ->
    [{listing,fun (St) -> listing(Ext, St) end}];
select_passes([{iff,Flag,Pass}|Ps], Opts) ->
    select_cond(Flag, true, Pass, Ps, Opts);
select_passes([{unless,Flag,Pass}|Ps], Opts) ->
    select_cond(Flag, false, Pass, Ps, Opts);
select_passes([{Name,Fun}|Ps], Opts) when function(Fun) ->
    [{Name,Fun}|select_passes(Ps, Opts)];
select_passes([], Opts) -> [];
select_passes([List|Ps], Opts) when list(List) ->
    Nested = select_passes(List, Opts),
    case last(Nested) of
	{listing,Fun} -> Nested;
	Other         -> Nested ++ select_passes(Ps, Opts)
    end.

select_cond(Flag, ShouldBe, Pass, Ps, Opts) ->
    ShouldNotBe = not ShouldBe,
    case member(Flag, Opts) of 
	ShouldBe    -> select_passes([Pass|Ps], Opts);
	ShouldNotBe -> select_passes(Ps, Opts)
    end.

%% The standard passes (almost) always run.

standard_passes(v1) -> [?pass(v1_is_dead)];
standard_passes(v2) -> [?pass(v2_is_dead)];
standard_passes(v3) ->
    [?pass(transform_module),
     {iff,'P',{src_listing,"P"}},
     ?pass(lint_module),

     %% Note: erl_lint only warns for obviously unused functions, not
     %% for self-recursive functions never called. 
%%     ?pass(remove_unused_functions),

     ?pass(expand_module),
     {iff,dexp,{listing,"expand"}},
     {iff,'E',{src_listing,"E"}},
     {iff,'abstr',{listing,"abstr"}},
     {iff,debug_info,?pass(save_abstract_code)},

     %% Core Erlang passes.
     {pass,v3_core},
     {iff,dcore,{listing,"core"}},
     {unless,no_copt,{pass,v3_core_opt}},
     {iff,dcopt,{listing,"coreopt"}},
     {iff,clint,?pass(core_lint_module)},

     %% Kernel Erlang and code generation.
     {pass,v3_kernel},
     {iff,dkern,{listing,"kernel"}},
     {pass,v3_life},
     {iff,dlife,{listing,"life"}},
     {pass,v3_codegen},
     {iff,dcg,{listing,"codegen"}},

     %% Assembly level optimisations.
     {unless,no_postopt,
      [{pass,beam_block},
       {iff,dblk,{listing,"block"}},
       {pass,beam_bs},
       {iff,dbs,{listing,"bs"}},
       {unless,no_jopt,{pass,beam_jump}},
       {iff,djmp,{listing,"jump"}},
       {unless,no_topt,{pass,beam_type}},
       {iff,dtype,{listing,"type"}},
       {pass,beam_flatten}]},
     {iff,dopt,{listing,"optimize"}},
     {iff,'S',{listing,"S"}},

     ?pass(beam_asm),
     {unless,binary,?pass(save_binary)}].

compiler_version(Opts) ->
    compiler_version(Opts, []).

compiler_version([O|Opts], Acc) ->
    case is_compiler_version(O) of
	true  -> {O,[O|[Opt || Opt <- Opts, not is_compiler_version(Opt)]++Acc]};
	false -> compiler_version(Opts, [O|Acc])
    end;
compiler_version([], Acc) -> {?DEF_VERSION,[?DEF_VERSION|Acc]}.

is_compiler_version(v1) -> true;
is_compiler_version(v2) -> true;
is_compiler_version(v3) -> true;
is_compiler_version(_)  -> false.

%%%
%%% Compiler passes.
%%%

%% Remove the target file so we don't have an old one if the compilation fail.
remove_file(St) ->
    file:delete(St#compile.ofile),
    {ok,St}.

-record(asm_module, {module, exports, labels, functions=[], cfun, code}).

preprocess_asm_forms(Forms) ->
    R = #asm_module{},
    R1 = collect_asm(Forms, R),
    {R1#asm_module.module,
     {R1#asm_module.module,
      R1#asm_module.exports,
      R1#asm_module.functions,
      R1#asm_module.labels}}.

collect_asm([], R) ->
    case R#asm_module.cfun of
	undefined ->
	    R;
	{A,B,C} ->
	    R#asm_module{functions=R#asm_module.functions++
			 [{function,A,B,C,R#asm_module.code}]}
    end;
collect_asm([{module,M} | Rest], R) ->
    collect_asm(Rest, R#asm_module{module=M});
collect_asm([{exports,M} | Rest], R) ->
    collect_asm(Rest, R#asm_module{exports=M});
collect_asm([{labels,M} | Rest], R) ->
    collect_asm(Rest, R#asm_module{labels=M});
collect_asm([{function,A,B,C} | Rest], R) ->
    R1 = case R#asm_module.cfun of
	     undefined ->
		 R;
	     {A0,B0,C0} ->
		 R#asm_module{functions=R#asm_module.functions++
			      [{function,A0,B0,C0,R#asm_module.code}]}
	 end,
    collect_asm(Rest, R1#asm_module{cfun={A,B,C}, code=[]});
collect_asm([X | Rest], R) ->
    collect_asm(Rest, R#asm_module{code=R#asm_module.code++[X]}).

beam_consult_asm(St) ->
    case file:consult(St#compile.ifile) of
	{ok, Forms0} ->
	    {Module, Forms} = preprocess_asm_forms(Forms0),
	    {ok,St#compile{module=Module, code=Forms}};
	{error,E} ->
	    Es = [{St#compile.ifile,[{none,compile,{open,E}}]}],
	    {error,St#compile{errors=St#compile.errors ++ Es}}
    end.

error_if_jam(St) ->	
    case member(jam, St#compile.options) of
	true  ->
	    Es = [{St#compile.ifile,[{none,compile,jam_is_dead}]}],
	    {error,St#compile{errors=St#compile.errors ++ Es}};
	false ->
	    {ok,St}
    end.

v1_is_dead(St) ->
    Es = [{St#compile.ifile,[{none,compile,v1_is_dead}]}],
    {error,St#compile{errors=St#compile.errors ++ Es}}.

v2_is_dead(St) ->
    Es = [{St#compile.ifile,[{none,compile,v2_is_dead}]}],
    {error,St#compile{errors=St#compile.errors ++ Es}}.

parse_module(St) ->
    Opts = St#compile.options,
    Cwd = case keysearch(cwd, 1, Opts) of
	      {value, {cwd, Dir}} -> Dir;
	      _ -> "."
	  end,
    IncludePath = [Cwd, St#compile.dir|inc_paths(Opts)],
    case epp:parse_file(St#compile.ifile, IncludePath, pre_defs(Opts)) of
	{ok,Forms} ->
	    {ok,St#compile{code=Forms}};
	{error,E} ->
	    Es = [{St#compile.ifile,[{none,compile,{open,E}}]}],
	    {error,St#compile{errors=St#compile.errors ++ Es}}
    end.

compile_options([{attribute,L,compile,C}|Fs]) when list(C) ->
    C ++ compile_options(Fs);
compile_options([{attribute,L,compile,C}|Fs]) ->
    [C|compile_options(Fs)];
compile_options([_F|Fs]) -> compile_options(Fs);
compile_options([]) -> [].


transforms(Os) -> [ M || {parse_transform,M} <- Os ]. 

transform_module(St) ->
    %% Extract compile options from code into options field.
    Ts = transforms(St#compile.options ++ compile_options(St#compile.code)),
    foldl_transform(St, Ts).

foldl_transform(St, [T|Ts]) ->
    Name = "transform " ++ atom_to_list(T),
    Fun = fun(S) -> T:parse_transform(S#compile.code, S#compile.options) end,
    Run = case member(time, St#compile.options) of
	      true  -> fun run_tc/2;
	      false -> fun({N,F}, S) -> catch F(S) end
	  end,
    case Run({Name, Fun}, St) of
	{'EXIT',R} ->
	    Es = [{St#compile.ifile,[{none,compile,{parse_transform,T,R}}]}],
	    {error,St#compile{errors=St#compile.errors ++ Es}};
	Forms ->
	    foldl_transform(St#compile{code=Forms}, Ts)
    end;
foldl_transform(St, []) -> {ok,St}.

%%% Fetches the module name from a list of forms. The module attribute must
%%% be present.
get_module([{attribute,_,module,M} | _]) -> M;
get_module([_ | Rest]) ->
    get_module(Rest).

%%% A #compile state is returned, where St.base has been filled in
%%% with the module name from Forms, as a string, in case it wasn't
%%% set in St (i.e., it was "").
add_default_base(St, Forms) ->
    F = St#compile.filename,
    case F of
	"" ->
	    M = get_module(Forms),
	    St#compile{base = atom_to_list(M)};
	_ ->
	    St
    end.

lint_module(St) ->
    case erl_lint:module(St#compile.code,
			 St#compile.ifile, St#compile.options) of
	{ok,Ws} ->
	    %% Insert name of module as base name, if needed. This is
	    %% for compile:forms to work with listing files.
	    St1 = add_default_base(St, St#compile.code),
	    {ok,St1#compile{warnings=St1#compile.warnings ++ Ws}};
	{error,Es,Ws} ->
	    {error,St#compile{warnings=St#compile.warnings ++ Ws,
			      errors=St#compile.errors ++ Es}}
    end.

core_lint_module(St) ->
    case core_lint:module(St#compile.code, St#compile.options) of
	{ok,Ws} ->
	    {ok,St#compile{warnings=St#compile.warnings ++ Ws}};
	{error,Es,Ws} ->
	    {error,St#compile{warnings=St#compile.warnings ++ Ws,
			      errors=St#compile.errors ++ Es}}
    end.

%% remove_unused_functions(State) -> State'
%%  Remove any local functions not called in the module, based on the warnings
%%  generated by erl_lint.

%% See comment in standard_passes/1.
% remove_unused_functions(St0) ->
%     Eds = flatmap(fun({F,Ws}) -> Ws end,  St0#compile.warnings),
%     case [{Func,Arity} || {L,erl_lint,{not_called,{Func,Arity}}} <- Eds] of
% 	[] -> {ok,St0};
% 	NotCalled ->
% 	    Code = filter(fun({function,L,N,A,Cs}) -> not member({N,A}, NotCalled);
% 			     (Other) -> true end,
% 			  St0#compile.code),
% 	    {ok,St0#compile{code=Code}}
%     end.

%% expand_module(State) -> State'
%%  Do the common preprocessing of the input forms.

expand_module(St0) ->
    {Mod,Exp,Forms,Opts} = sys_pre_expand:module(St0#compile.code,
						 St0#compile.options),
    {ok,St0#compile{module=Mod,options=Opts,code={Mod,Exp,Forms}}}.

save_abstract_code(St) ->
    {ok,St#compile{abstract_code=abstract_code(St)}}.

abstract_code(#compile{code={Mod,Exp,Forms}}) ->
    Abstr = {abstract_v1,Forms},
    case catch erlang:term_to_binary(Abstr, [compressed]) of
	{'EXIT',_} -> term_to_binary(Abstr);
	Other -> Other
    end.

beam_asm(#compile{code=Code0,abstract_code=Abst,options=Opts0}=St) ->
    Opts = filter(fun is_informative_option/1, Opts0),
    case beam_asm:module(Code0, Abst, Opts) of
	{ok,Code} -> {ok,St#compile{code=Code,abstract_code=[]}};
	{error,Es} -> {error,St#compile{errors=St#compile.errors ++ Es}}
    end.

%% Returns true if the option is informative and therefore should be included
%% in the option list of the compiled module.

is_informative_option(beam) -> false;
is_informative_option(report_warnings) -> false;
is_informative_option(report_errors) -> false;
is_informative_option(binary) -> false;
is_informative_option(verbose) -> false;
is_informative_option(_) -> true.
    
save_binary(St) ->
    Tfile = tmpfile(St#compile.ofile),		%Temp working file
    case write_binary(Tfile, St#compile.code, St) of
	ok ->
	    case file:rename(Tfile, St#compile.ofile) of
		ok ->
		    {ok,St};
		{error,E} ->
		    file:delete(Tfile),
		    Es = [{St#compile.ofile,[{none,?MODULE,{rename,Tfile}}]}],
		    {error,St#compile{errors=St#compile.errors ++ Es}}
	    end;
	{error,E} ->
	    Es = [{Tfile,[{compile,write_error}]}],
	    {error,St#compile{errors=St#compile.errors ++ Es}}
    end.

write_binary(Name, Bin, St) ->
    Opts = case member(compressed, St#compile.options) of
	       true -> [compressed];
	       false -> []
	   end,
    case file:open(Name, [write, raw|Opts]) of
	{ok, Fd} ->
	    Res = case file:write(Fd, Bin) of
		      ok ->
			  ok;
		      {error, Reason} ->
			  {error, Reason}
		  end,
	    file:close(Fd),
	    Res;
	{error, Reason} ->
	    {error, Reason}
    end.

%% report_errors(State) -> ok
%% report_warnings(State) -> ok

report_errors(St) ->
    case member(report_errors, St#compile.options) of
	true ->
	    foreach(fun ({{F,L},Eds}) -> list_errors(F, Eds);
			({F,Eds}) -> list_errors(F, Eds) end,
		    St#compile.errors);
	false -> ok
    end.

report_warnings(St) ->
    case member(report_warnings, St#compile.options) of
	true ->
	    foreach(fun ({{F,L},Eds}) -> list_warnings(F, Eds);
			({F,Eds}) -> list_warnings(F, Eds) end,
		    St#compile.warnings);
	false -> ok
    end.

%% ftl -- extras

list_errors(F,Errs) ->
    list_errors2(F,Errs).

list_warnings(F,Warnings) ->
    list_warnings2(F,Warnings).

%% list_errors(File, ErrorDescriptors) -> ok

list_errors1(F, [{Line,Mod,E}|Es]) ->
    io:fwrite("~s:~w: ~s\n", [F,Line,apply(Mod, format_error, [E])]),
    list_errors1(F, Es);
list_errors1(F, [{Mod,E}|Es]) ->
    io:fwrite("~s: ~s\n", [F,apply(Mod, format_error, [E])]),
    list_errors1(F, Es);
list_errors1(F, []) ->
    ok.

%% list_warnings(File, ErrorDescriptors) -> ok

list_warnings1(F, [{Line,Mod,E}|Es]) ->
    io:fwrite("~s:~w: Warning: ~s\n", [F,Line,apply(Mod, format_error, [E])]),
    list_warnings1(F, Es);
list_warnings1(F, [{Mod,E}|Es]) ->
    io:fwrite("~s: Warning: ~s\n", [F,apply(Mod, format_error, [E])]),
    list_warnings1(F, Es);
list_warnings1(F, []) ->
    ok.

%% ftl -- better (shorter, clearer) error and warning listings

list_errors2(F,[]) ->
    ok;
list_errors2(F,Errs) ->
    io:fwrite("*** ERRORS ~s:~n",[F]),
    Errors = summarize_errors(Errs),
    lists:foreach(
      fun({Line,Mod,E}) ->
	      io:fwrite("line ~w: ~s~n",[Line,Mod:format_error(E)]);
	 ({Mod,E}) ->
	      io:fwrite("~s~n",[Mod:format_error(E)])
      end,
      Errors).

list_warnings2(F,[]) ->
    ok;
list_warnings2(F, Warns0) ->
    io:fwrite("WARNINGS ~s:~n",[F]),
    Warnings = summarize_warnings(Warns0),
    lists:foreach(
      fun({Line,Mod,E}) ->
	      io:fwrite("line ~w: Warning: ~s~n",[Line,Mod:format_error(E)]);
	 ({Mod,E}) -> 
	      io:fwrite("Warning: ~s~n",[Mod:format_error(E)])
	 end,
      Warnings).

%% collapse some errors into one (eg, all undefined_function errors)
%%
%% Ordinary: untreated errors
%% UDs: undefined functions, [{{F,A},Lines}]

summarize_errors(Errs) ->
    s_err(Errs,[],[]).

s_err([X|Xs],Ordinary,UDs) ->
  case X of
      {Line,Mod,{undefined_function,FA}} ->
	  s_err(Xs,Ordinary,insert_line(FA,Line,UDs));
      _ ->
	  s_err(Xs,[X|Ordinary],UDs)
  end;
s_err([],Ordinary,UDs) ->
    Ordinary ++ undefined_function_errors(UDs).

%% insert another line error for function F/A

insert_line(FA,Line,[{FA,Lines}|Xs]) ->
    %% append lines to get correct order at once (we could keep the list
    %% reversed and reverse it at error-print time)
    [{FA,Lines ++ [Line]}|Xs];
insert_line(FA,Line,[X|Xs]) ->
    [X|insert_line(FA,Line,Xs)];
insert_line(FA,Line,[]) ->
    [{FA,[Line]}].

%% convert {FA,[Line]} into real error info
%% - Note: currently, we ALWAYS print a list of error lines, even if the
%%   list has length 1. This could be prettified.
%% - The _new_ undefined_function error is defined in this module, rather
%%   than in erl_lint; we should perhaps give it a new name?

undefined_function_errors(Errs) ->
    [ {?MODULE,{undefined_function,FA,Lines}} || {FA,Lines}<- Errs ].

%% collapse warnings about unused functions etc into a single extended
%% warning (see also format_error/1 in this file, which handles the
%% new error)
%%

summarize_warnings(Warns) ->
    s_warn(Warns,[],[]).

%% s_warn: summarize warnings
%%
%% Ordinary: untreated warnings
%% UFs: unused function warnings, reversed order
%%
%% returns new list of warnings

s_warn([X|Xs],Ordinary,UFs) ->
    case X of
	{Line,Mod,{unused_function,{F,A}}} ->
	    s_warn(Xs,Ordinary,[X|UFs]);
	_ ->
	    s_warn(Xs,[X|Ordinary],UFs)
    end;
s_warn([],Ordinary,UFs) ->
    UF_warns = unused_function_warnings(UFs),
    Ordinary ++ UF_warns.

%%

unused_function_warnings(UFs) ->
    if 
	length(UFs) < 2 ->
	    %% too few unused functions to bother
	    UFs;
	true ->
	    [{?MODULE,
	      {unused_functions,
	       lists:reverse(
		 [ {F,A} 
		   || {Line,Mod,{unused_function,{F,A}}} <- UFs ])}}]
    end.

%% erlfile(Dir, Base) -> ErlFile
%% outfile(Base, Extension, Options) -> OutputFile
%% objfile(Base, Target, Options) -> ObjFile
%% tmpfile(ObjFile) -> TmpFile
%%  Work out the correct input and output file names.

iofile(File) when atom(File) ->
    iofile(atom_to_list(File));
iofile(File) ->
    {filename:dirname(File), filename:basename(File, ".erl")}.

erlfile(Dir, Base, Suffix) ->
    filename:join(Dir, Base++Suffix).

outfile(Base, Ext, Opts) when atom(Ext) ->
    outfile(Base, atom_to_list(Ext), Opts);
outfile(Base, Ext, Opts) ->
    Obase = case keysearch(outdir, 1, Opts) of
		{value, {outdir, Odir}} -> filename:join(Odir, Base);
		Other -> Base			% Not found or bad format
	    end,
    Obase++"."++Ext.

objfile(Base, St) ->
    outfile(Base, "beam", St#compile.options).

tmpfile(Ofile) ->
    reverse([$#|tl(reverse(Ofile))]).

%% pre_defs(Options)
%% inc_paths(Options)
%%  Extract the predefined macros and include paths from the option list.

pre_defs([{d,M,V}|Opts]) ->
    [{M,V}|pre_defs(Opts)];
pre_defs([{d,M}|Opts]) ->
    [M|pre_defs(Opts)];
pre_defs([O|Opts]) ->
    pre_defs(Opts);
pre_defs([]) -> [].

inc_paths(Opts) ->
    [ P || {i,P} <- Opts, list(P) ].

src_listing(Ext, St) ->
    listing(fun (Lf, {Mod,Exp,Fs}) -> do_src_listing(Lf, Fs);
		(Lf, Fs) -> do_src_listing(Lf, Fs) end,
	    Ext, St).

do_src_listing(Lf, Fs) ->
    foreach(fun (F) -> io:put_chars(Lf, [erl_pp:form(F),"\n"]) end,
	    Fs).

listing(Ext, St) ->
    listing(fun(Lf, Fs) -> beam_listing:module(Lf, Fs) end, Ext, St).

listing(LFun, Ext, St) ->
    Lfile = outfile(St#compile.base, Ext, St#compile.options),
    case file:open(Lfile, [write]) of
	{ok,Lf} -> 
	    LFun(Lf, St#compile.code),
	    ok = file:close(Lf),
	    {ok,St};
	{error,E} ->
	    Es = [{Lfile,[{none,compile,write_error}]}],
	    {error,St#compile{errors=St#compile.errors ++ Es}}
    end.

options() ->
    help(standard_passes(?DEF_VERSION)).

%% Intentionally undocumented.

options(Version) ->
    help(standard_passes(Version)).

help([{iff,Flag,{src_listing,Ext}}|T]) ->
    io:fwrite("~p - Generate .~s source listing file\n", [Flag,Ext]),
    help(T);
help([{iff,Flag,{listing,Ext}}|T]) ->
    io:fwrite("~p - Generate .~s file\n", [Flag,Ext]),
    help(T);
help([{iff,Flag,{Name,Fun}}|T]) when function(Fun) ->
    io:fwrite("~p - Run ~s\n", [Flag,Name]),
    help(T);
help([{iff,Flag,Action}|T]) ->
    help(Action),
    help(T);
help([{unless,Flag,{pass,Pass}}|T]) ->
    io:fwrite("~p - Skip the ~s pass\n", [Flag,Pass]),
    help(T);
help([{unless,no_postopt=Flag,List}|T]) when list(List) ->
    %% Hard-coded knowledgde here.
    io:fwrite("~p - Skip all post optimisation\n", [Flag]),
    help(List),
    help(T);
help([{unless,Flag,Action}|T]) ->
    help(Action),
    help(T);
help([H|T]) ->
    help(T);
help(_) ->
    ok.


%% compile(AbsFileName, Outfilename, Options)
%%   Compile entry point for erl_compile.

compile(File, _OutFile, Options) ->
    case file(File, make_erl_options(Options)) of
	{ok, _Mod} -> ok;
	Other -> Other
    end.

%% Converts generic compiler options to specific options.

make_erl_options(Opts) ->

    %% This way of extracting will work even if the record passed
    %% has more fields than known during compilation.

    Includes0 = Opts#options.includes,
    Defines = Opts#options.defines,
    Outdir = Opts#options.outdir,
    Warning = Opts#options.warning,
    Verbose = Opts#options.verbose,
    Specific = Opts#options.specific,
    Optimize = Opts#options.optimize,
    OutputType = Opts#options.output_type,
    Cwd = Opts#options.cwd,

    Includes = 
	case Opts#options.ilroot of
	    undefined ->
		Includes0;
	    Ilroot ->
		[Ilroot|Includes0]
	end,

    Options =
	case Verbose of
	    true ->  [verbose];
	    false -> []
	end ++
	case Warning of
	    0 -> [];
	    _ -> [report_warnings]
 	end ++
 	case Optimize of
	    0 -> [no_kernopt,no_postopt];
	    1 -> [no_postopt];
	    Other -> []
	end ++
	map(
	  fun ({Name, Value}) ->
		  {d, Name, Value};
	      (Name) ->
		  {d, Name}
	  end,
	  Defines) ++
	case OutputType of
	    undefined -> [];
	    jam -> [jam];
	    beam -> [beam]
	end,

    Options++[report_errors, {cwd, Cwd}, {outdir, Outdir}|
	      map(fun(Dir) -> {i, Dir} end, Includes)]++Specific.



More information about the erlang-questions mailing list