Current section

Files

Jump to
lfe src lfe_comp.erl
Raw

src/lfe_comp.erl

%% -*- mode: erlang; indent-tabs-mode: nil -*-
%% Copyright (c) 2008-2020 Robert Virding
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%% http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%%% File : lfe_comp.erl
%%% Author : Robert Virding
%%% Purpose : Lisp Flavoured Erlang compiler (to core Erlang).
%% All the code in the file is treated as one sequence of forms until
%% after the macroexpansion pass when it is split into separate
%% modules. However up until after the lint pass all errors and
%% warnings are collected together in the errors and warnings
%% fields. After this the errors become more module specific and are
%% kept together with the compiled code, both for core and the
%% following erlang formats.
-module(lfe_comp).
-export([format_error/1]).
-export([file/1,file/2,forms/1,forms/2,default_options/0]).
%% -compile(export_all).
-import(lists, [member/2,keyfind/3,filter/2,foreach/2,all/2,any/2,
map/2,flatmap/2,foldl/3,foldr/3,mapfoldl/3,mapfoldr/3]).
-include("lfe.hrl").
-include("lfe_comp.hrl").
%% Mightn't use all commands in do_passes yet.
-dialyzer({[no_match],do_passes/2}).
%% The main compiler state.
-record(comp, {base="", %Base name
ldir=".", %Lisp file dir
lfile="", %Lisp file
odir=".", %Output directory
opts=[], %User options
ipath=[], %Include path
cinfo=none, %Common compiler info
module=[], %Module name
code=[], %Code after last pass.
return=[], %What is returned [Val] | []
errors=[],
warnings=[],
extra=[] %Pass specific options, plist
}).
%% default_options() -> Options.
%% Return the default compiler options.
-define(DEFAULT_OPTS, [verbose,report]).
%% Errors.
format_error(write_file) -> "error writing file".
default_options() -> ?DEFAULT_OPTS.
%% file(Name) ->
%% {ok,Mod,Warns} | {ok,Mod,Binary,Ws} | {error,Errors,Warns} | error.
%% file(Name, Options) ->
%% {ok,Mod,Warns} | {ok,Mod,Binary,Ws} | {error,Errors,Warns} | error.
%% Compile the LFE file Name.
file(Name) -> file(Name, default_options()).
file(Name, Opts) -> do_compile({file,Name}, Opts).
%% forms(Forms) -> {ok,Mod,Bin,Warnings} | {error,Errors,Warnings}.
%% forms(Forms, Options) -> {ok,Mod,Bin,Warnings} | {error,Errors,Warnings}.
%% Compile the LFE forms Forms, always return a binary.
forms(Forms) -> forms(Forms, default_options()).
forms(Forms, Opts) -> do_compile({forms,Forms}, Opts).
do_compile(Input, Opts) ->
Ifun = fun () ->
Ret = try
internal(Input, Opts)
catch
?CATCH(error, Reason, St)
{error,{Reason,St}}
end,
exit(Ret)
end,
{Pid,Ref} = spawn_monitor(Ifun),
receive
{'DOWN',Ref,_,Pid,Res} -> Res
end.
%% internal(Input, Options) -> Result.
internal({file,Name}, Opts) -> do_file(Name, Opts);
internal({forms,Forms}, Opts) -> do_forms(Forms, Opts).
do_file(Name, Opts0) ->
Opts1 = lfe_comp_opts(Opts0),
St0 = #comp{opts=Opts1,code=[]}, %Code must be list!
St1 = filenames(Name, ".lfe", St0),
St2 = include_path(St1),
case lfe_io:parse_file(St2#comp.lfile) of
{ok,Fs} ->
%% Do the actual compilation work.
do_forms(St2#comp{code=Fs});
{error,Error} -> do_error_return(St2#comp{errors=[Error]})
end.
do_forms(Fs0, Opts0) ->
Source = lprop(source, Opts0, ""),
Opts1 = lfe_comp_opts(Opts0),
St0 = #comp{opts=[binary|Opts1]}, %Implicit binary option
St1 = filenames(Source, "", St0),
St2 = include_path(St1),
%% Tag forms with a "line number", just use their index.
{Fs1,_} = mapfoldl(fun (F, N) -> {{F,N},N+1} end, 1, Fs0),
do_forms(St2#comp{code=Fs1}).
%% filenames(File, Suffix, State) -> State.
%% The default output dir is the current directory unless an
%% explicit one has been given in the options.
filenames(File, Suffix, St) ->
%% Test for explicit outdir.
Odir = outdir(St#comp.opts, "."),
Ldir = filename:dirname(File),
Base = filename:basename(File, Suffix),
Lfile = lfefile(Ldir, Base, Suffix),
St#comp{base=Base,
ldir=Ldir,
lfile=Lfile,
odir=Odir
}.
lfefile(".", Base, Suffix) -> Base ++ Suffix;
lfefile(Dir, Base, Suffix) ->
filename:join(Dir, Base ++ Suffix).
outdir(Opts, Def) -> lprop(outdir, Opts, Def).
%% lprop(Key, PropList, Default) -> Value.
%% Find Key, Val from PropList else Default.
lprop(Key, [{Key,Val}|_], _) -> Val; %Erlang way
lprop(Key, [[Key,Val]|_], _) -> Val; %LFE way
lprop(Key, [_|List], Def) -> lprop(Key, List, Def);
lprop(_, [], Def) -> Def.
%% include_path(State) -> State.
%% Set the include path, we permit {i,Dir} and [i,Dir].
include_path(#comp{ldir=Ldir,opts=Opts}=St) ->
Ifun = fun ({i,I}, Is) -> [I|Is]; %Erlang way
([i,I], Is) -> [I|Is]; %LFE way
(_, Is) -> Is
end,
%% Same ordering as in the erlang compiler.
Is = [".",Ldir|foldr(Ifun, [], Opts)], %Default entries
St#comp{ipath=Is}.
%% compiler_info(State) -> CompInfo.
compiler_info(#comp{lfile=F,opts=Os,ipath=Is}) ->
#cinfo{file=F,opts=Os,ipath=Is}.
%% lfe_comp_opts(Opts) -> Opts.
%% Translate from LFE to erlang standard options for lfe compiler.
lfe_comp_opts(Opts) ->
Fun = fun ('to-split') -> to_split;
('to-expmac') -> to_expmac;
('to-expand') -> to_expand;
('to-lint') -> to_lint;
('no-docs') -> no_docs;
('to-erlang') -> to_erlang;
('to-core0') -> to_core0;
('to-core') -> to_core;
('to-kernel') -> to_kernel;
('to-asm') -> to_asm;
('to-ast') -> to_ast; %The output should be an AST
('debug-info') -> debug_info;
('no-export-macros') -> no_export_macros;
('warnings-as-errors') -> warnings_as_errors;
('report-warnings') -> report_warnings;
('report-errors') -> report_errors;
('debug-print') -> debug_print;
(O) -> O
end,
map(Fun, Opts).
%% do_forms(State) ->
%% {ok,Mod,[Core],[Warnings]} | {error,Errors,Warnings} | error.
%% Run the actual LFE compiler passes.
do_forms(St0) ->
%% Fill in the common compiler info.
St1 = St0#comp{cinfo=compiler_info(St0)},
Ps = passes(),
case do_passes(Ps, St1) of
{ok,St2} -> do_ok_return(St2);
{error,St2} -> do_error_return(St2)
end.
%% passes() -> [Pass].
%% do_passes(Passes, State) -> {ok,State} | {error,State}.
%%
%% {when_flag,Flag,Cmd} Do Cmd if Flag is or is not in the
%% {unless_flag,Flag,Cmd} option list.
%%
%% {when_test,Test,Cmd} Do Cmd if the Test function returns 'true'
%% {unless_test,Test,Cmd} or 'false'.
%%
%% {do,Fun} Call Fun and then continue.
%%
%% {listing,PrintFun} End compilation calling PrintFun to output
%% file.
%%
%% done End compilation.
%%
%% {done,PrintFun} End compilation calling PrintFun to output
%% file, unless 'binary' is specified in which
%% current code will be returned.
passes() ->
[
%% Split input file into separate modules.
{do,fun do_split_file/1},
{when_flag,to_split,{done,fun split_pp/1}},
%% Do per-module macro processing.
{unless_flag,no_export_macros,{do,fun do_export_macros/1}},
{when_flag,to_expmac,{done,fun expmac_pp/1}},
%% Now we expand and trim remaining macros.
{do,fun do_expand_macros/1},
{when_flag,to_expand,{done,fun expand_pp/1}},
{do,fun do_lfe_lint/1},
{when_flag,to_lint,{done,fun lint_pp/1}},
{unless_flag,no_docs,{do,fun do_get_docs/1}},
{do,fun do_lfe_codegen/1},
{when_flag,to_erlang,{done,fun erlang_pp/1}},
{do,fun do_erl_comp/1},
%% These options will have made erlang compiler return internal
%% form after pass.
{when_flag,to_core0,{done,fun erl_core_pp/1}},
{when_flag,to_core,{done,fun erl_core_pp/1}},
{when_flag,to_kernel,{done,fun erl_kernel_pp/1}},
{when_flag,to_asm,{done,fun erl_asm_pp/1}},
%% Stop at non-binary returns, either return or drop.
{unless_test,fun is_binary_module/1,done},
%% Now we just write the beam file unless warnings-as-errors is
%% set and we have warnings.
{when_test,fun is_werror/1,error},
%% Write docs beam chunks.
{do,fun add_chunks/1},
{done,fun beam_write/1} %Should be last
].
do_passes([{when_flag,Flag,Cmd}|Ps], #comp{opts=Opts}=St) ->
do_passes(?IF(member(Flag, Opts), [Cmd|Ps], Ps), St);
do_passes([{unless_flag,Flag,Cmd}|Ps], #comp{opts=Opts}=St) ->
do_passes(?IF(member(Flag, Opts), Ps, [Cmd|Ps]), St);
do_passes([{when_test,Test,Cmd}|Ps], St) ->
do_passes(?IF(Test(St), [Cmd|Ps], Ps), St);
do_passes([{unless_test,Test,Cmd}|Ps], St) ->
do_passes(?IF(Test(St), Ps, [Cmd|Ps]), St);
do_passes([{do,Fun}|Ps], St0) ->
case Fun(St0) of
{ok,St1} -> do_passes(Ps, St1);
{error,St1} -> {error,St1}
end;
do_passes([{listing,PrintFun}|_], St) ->
PrintFun(St);
do_passes([done|_], St) -> {ok,St}; %Just end now
do_passes([error|_], St) -> {error,St};
do_passes([{done,Fun}|_], St) ->
%% Print unless binary, in which case end.
do_passes([{unless_flag,binary,{listing,Fun}}], St);
do_passes([], St) -> {ok,St}. %Got to the end, everything ok!
%% do_split_file(State) -> {ok,State} | {error,State}.
%% Split a file into separate modules. Everything defined before the
%% first module is available in every module, after that things are
%% local to the module in which they are defined. We need to expand
%% top-level macros in forms so we can safelt detect the start of
%% each module (with define-module form).
do_split_file(#comp{lfile=Lfile,cinfo=Ci,code=Code0}=St) ->
%% Add a FILE macro with the file name at the beginning.
Mac = {[defmacro,'FILE',[],?BQ(?Q(Lfile))],1},
Code1 = [Mac|Code0],
case collect_pre_forms(Code1, Ci) of %Expand pre module forms
{Pfs,Fs,Env0,Mst0} ->
%% Expand the modules using the pre forms and environment.
case collect_modules(Fs, Pfs, Env0, Mst0) of
{ok,Ms,_Mst1} ->
{ok,St#comp{code=Ms,
warnings=St#comp.warnings}};
{error,Es,Ws} ->
{error,St#comp{code=[], %Pseudo module list.
errors=St#comp.errors ++ Es,
warnings=St#comp.warnings ++ Ws}}
end;
{error,Es,Ws} ->
{error,St#comp{code=[], %Pseudo module list.
errors=St#comp.errors ++ Es,
warnings=St#comp.warnings ++ Ws}}
end.
%% collect_pre_forms(Forms, CompInfo) ->
%% {PreForms,RestForms,Env,State}.
collect_pre_forms(Fs, Ci) ->
Env = lfe_env:new(),
%% Don't deep expand, keep everything.
Mst = lfe_macro:expand_form_init(Ci, false, true),
collect_mod_forms(Fs, Env, Mst).
%% collect_modules(Forms, PreForms, PreEnv, MacroState) ->
%% {Modules,MacroState}.
%% Collect and expand modules upto the end. Each module initially has
%% the pre environment and all pre forms are appended to it.
collect_modules(Fs, PreFs, PreEnv, Mst) ->
collect_modules(Fs, [], PreFs, PreEnv, Mst).
collect_modules([{['define-module',Name|_],_}=Mdef|Fs0], Ms, PreFs, PreEnv, Mst0) ->
%% Expand and collect all forms upto next define-module or end.
case collect_mod_forms(Fs0, PreEnv, Mst0) of
{Mfs0,Fs1,_,Mst1} ->
M = #module{name=Name,code=[Mdef] ++ PreFs ++ Mfs0},
collect_modules(Fs1, [M|Ms], PreFs, PreEnv, Mst1);
Error -> Error
end;
collect_modules([], Ms, _PreFs, _PreEnv, Mst) ->
{ok,lists:reverse(Ms),Mst}.
%% collect_mod_forms(Forms, Env, MacroState) ->
%% collect_mod_forms(Forms, Acc, Env, MacroState) ->
%% {Modforms,RestForms,Env,MacroState}.
%% Expand and collect forms upto the next define-module or end. We
%% also flatten top-level nested progn code.
collect_mod_forms(Fs, Env0, Mst0) ->
case collect_mod_forms(Fs, [], Env0, Mst0) of
{Acc,Rest,Env1,Mst1} ->
{lists:reverse(Acc),Rest,Env1,Mst1};
{error,_,_}=Error -> Error
end.
collect_mod_forms([F0|Fs0], Acc, Env0, Mst0) ->
case lfe_macro:expand_fileform(F0, Env0, Mst0) of
{ok,{['define-module'|_],_}=F1,Env1,Mst1} ->
{Acc,[F1|Fs0],Env1,Mst1};
{ok,{['progn'|Pfs],L},Env1,Mst1} -> %Flatten progn's
Fs1 = [ {F,L} || F <- Pfs ] ++ Fs0,
collect_mod_forms(Fs1, Acc, Env1, Mst1);
{ok,F1,Env1,Mst1} ->
collect_mod_forms(Fs0, [F1|Acc], Env1, Mst1);
{error,Es,Ws,_} -> {error,Es,Ws}
end;
collect_mod_forms([], Acc, Env, Mst) -> {Acc,[],Env,Mst}.
%% do_export_macros(State) -> {ok,State} | {error,State}.
%% do_expand_macros(State) -> {ok,State} | {error,State}.
%% Process the macros in each module. Do_expand_macros is the last
%% pass which fully expands all remaining macros and flattens the
%% output.
do_export_macros(#comp{cinfo=Ci,code=Ms0}=St) ->
Umac = fun (#module{code=Mfs0}=Mod) ->
{Mfs1,_} = lfe_macro_export:module(Mfs0, Ci),
Mod#module{code=Mfs1}
end,
Ms1 = lists:map(Umac, Ms0),
{ok,St#comp{code=Ms1}}.
do_expand_macros(#comp{cinfo=Ci,code=Ms0}=St0) ->
Emac = fun (#module{code=Fs0}=Mod) ->
Env = lfe_env:new(),
%% Deep expand, keep everything.
Mst = lfe_macro:expand_form_init(Ci, true, true),
case process_forms(fun expand_form/3, Fs0, {Env,Mst}) of
{Fs1,_} -> Mod#module{code=Fs1};
{error,_,_}=Error -> Error
end
end,
Ms1 = lists:map(Emac, Ms0),
St1 = St0#comp{code=Ms1},
?IF(all_module(Ms1), {ok,St1}, {error,St1}).
expand_form(F0, L, {Env0,St0}) ->
case lfe_macro:expand_form(F0, L, Env0, St0) of
{ok,[progn|Pfs],Env1,St1} ->
process_forms(fun expand_form/3, Pfs, L, {Env1,St1});
%%{ok,['eval-when-compile'|_],Env1,St1} ->
%% {[],{Env1,St1}};
{ok,F1,Env1,St1} ->
{[{F1,L}],{Env1,St1}};
{error,Es,Ws,_} -> throw({expand_form,{error,Es,Ws}})
end.
%% process_forms(Fun, Forms, State) -> {Forms,State} | Error.
%% process_forms(Fun, Forms, Line, State) -> {Forms,State} | Error.
%% Wrappers around lfe_lib:proc_forms which catch thrown errors.
process_forms(Fun, Fs, St) ->
try lfe_lib:proc_forms(Fun, Fs, St)
catch
throw:{expand_form,Error} -> Error
end.
process_forms(Fun, Fs, L, St) ->
try lfe_lib:proc_forms(Fun, Fs, L, St)
catch
throw:{expand_form,Error} -> Error
end.
%% do_lint(State) -> {ok,State} | {error,State}.
%% do_get_docs(State) -> {ok,State} | {error,State}.
%% do_lfe_codegen(State) -> {ok,State} | {error,State}.
%% do_erl_comp(State) -> {ok,State} | {error,State}.
%% The actual compiler passes.
do_lfe_lint(#comp{cinfo=Ci,code=Ms0}=St0) ->
Lint = fun (#module{code=Mfs,warnings=Ws}=Mod) ->
case lfe_lint:module(Mfs, Ci) of
{ok,Name,Lws} -> Mod#module{name=Name,warnings=Ws++Lws};
{error,Les,Lws} -> {error,Les,Ws++Lws}
end
end,
%% Lint the modules, then check if all are ok.
Ms1 = lists:map(Lint, Ms0),
St1 = St0#comp{code=Ms1},
?IF(all_module(Ms1), {ok,St1}, {error,St1}).
do_get_docs(#comp{code=Ms0,opts=Opts}=St) ->
Doc = fun (#module{code=Mfs,chunks=Chks}=Mod) ->
{ok,Chunk} = lfe_docs:make_chunk(Mfs, Opts),
Mod#module{chunks=[Chunk|Chks]}
end,
Ms1 = lists:map(Doc, Ms0),
{ok,St#comp{code=Ms1}}.
do_lfe_codegen(#comp{cinfo=Ci,code=Ms0}=St0) ->
Code = fun (#module{name=Name,code=Mfs,warnings=Ws}=Mod) ->
case lfe_codegen:module(Mfs, Ci) of
{ok,Name,AST,Gws} -> %Name consistency check!
Mod#module{code=AST,warnings=Ws ++ Gws};
{error,Ges,Gws} ->
{error,Ges,Gws}
end
end,
Ms1 = lists:map(Code, Ms0),
St1 = St0#comp{code=Ms1},
?IF(all_module(Ms1), {ok,St1}, {error,St1}).
do_erl_comp(#comp{code=Ms0}=St0) ->
ErlOpts = erl_comp_opts(St0), %Options to erlang compiler
%% Compile all the modules, then if all are ok.
Ms1 = lists:map(fun (M) -> do_erl_comp_mod(M, ErlOpts) end, Ms0),
St1 = St0#comp{code=Ms1},
?IF(all_module(Ms1), {ok,St1}, {error,St1}).
do_erl_comp_mod(#module{code=Core,warnings=Ws}=Mod, ErlOpts) ->
%% lfe_io:format("~p\n", [Core]),
case compile:forms(Core, ErlOpts) of
{ok,_,Result,Ews} ->
Mod#module{code=Result,warnings=Ws ++ fix_erl_errors(Ews)};
{error,Ees,Ews} ->
{error,fix_erl_errors(Ees),fix_erl_errors(Ews)}
end.
all_module(Res) -> lists:all(fun (X) -> is_record(X, module) end, Res).
%% erl_comp_opts(State) -> Options.
%% Strip out report options and make sure erlang compiler returns
%% errors and warnings. Also remove other options which might cause
%% strange behaviour.
erl_comp_opts(St) ->
Os0 = St#comp.opts,
Filter = fun (report) -> false; %No reporting!
(report_warnings) -> false;
(report_errors) -> false;
(warnings_as_errors) -> false; %We handle these ourselves
({source,_}) -> false;
(_) -> true %Everything else
end,
Os1 = filter(Filter, Os0),
%% Now build options for the erlang compiler. 'no_bopt' turns off
%% an optimisation in the guard which crashes our code.
[{source,St#comp.lfile}, %Set the source file
return, %Ensure we return something
binary, %We want a binary
nowarn_unused_vars| %Don't need to know here
Os1].
%% split_pp(State) -> {ok,State} | {error,State}.
%% expmac_pp(State) -> {ok,State} | {error,State}.
%% expand_pp(State) -> {ok,State} | {error,State}.
%% lint_pp(State) -> {ok,State} | {error,State}.
%% sexpr_pp(State) -> {ok,State} | {error,State}.
%% erl_core_pp(State) -> {ok,State} | {error,State}.
%% erl_kernel_pp(State) -> {ok,State} | {error,State}.
%% erl_asm_pp(State) -> {ok,State} | {error,State}.
%% beam_write(State) -> {ok,State} | {error,State}.
%% Output the various file types. The XXX_pp functions output with
%% the same name as the input file while beam_write outputs to the
%% module name.
%% This just print the whole file structure.
split_pp(St) -> sexpr_pp(St, "split").
expmac_pp(St) -> sexpr_pp(St, "expmac").
expand_pp(St) -> sexpr_pp(St, "expand").
lint_pp(St) -> sexpr_pp(St, "lint").
sexpr_pp(St, Ext) ->
Save = fun (File, #module{code=Code}) ->
lfe_io:prettyprint(File, Code), io:nl(File)
end,
do_list_save_file(Save, Ext, St).
%% These print a list of module structures.
erlang_pp(#comp{opts=Opts}=St) ->
Format = ?IF(member(to_ast, Opts),
fun (F) -> io_lib:format("~p.\n", [F]) end,
fun (F) -> [erl_pp:form(F),$\n] end),
Save = fun (File, #module{code=AST}) ->
Chars = [ Format(F) || F <- AST ],
io:put_chars(File, Chars)
end,
do_list_save_file(Save, "erl", St).
erl_core_pp(#comp{opts=Opts}=St) ->
Format = ?IF(member(to_ast, Opts),
fun (F) -> io_lib:format("~p.\n", [F]) end,
fun (F) -> [core_pp:format(F),$\n] end),
Save = fun (File, #module{code=Core}) ->
io:put_chars(File, Format(Core))
end,
do_list_save_file(Save, "core", St).
erl_kernel_pp(#comp{opts=Opts}=St) ->
Format = ?IF(member(to_ast, Opts),
fun (F) -> io_lib:format("~p.\n", [F]) end,
fun (F) -> [v3_kernel_pp:format(F),$\n] end),
Save = fun (File, #module{code=Kern}) ->
io:put_chars(File, Format(Kern))
end,
do_list_save_file(Save, "kernel", St).
erl_asm_pp(St) ->
Save = fun (File, #module{code=Asm}) ->
beam_listing:module(File, Asm), io:nl(File)
end,
do_list_save_file(Save, "S", St).
do_list_save_file(SaveOne, Ext, St) ->
SaveAll = fun (File, Code) ->
lists:foreach(fun (C) -> SaveOne(File, C) end, Code)
end,
do_save_file(SaveAll, Ext, St).
do_save_file(SaveAll, Ext, St) ->
Name = filename:join(St#comp.odir, St#comp.base ++ ["."|Ext]),
%% delayed_write useful here but plays havoc with erjang.
case file:open(Name, [write]) of
{ok,File} ->
Ret = SaveAll(File, St#comp.code),
ok = file:close(File),
case Ret of
ok -> {ok,St};
{error,_} ->
%% Just signal we couldn't write the file.
{error,St#comp{errors=[{lfe_comp,write_file}]}}
end;
{error,_} ->
%% Just signal we couldn't write the file.
{error,St#comp{errors=[{lfe_comp,write_file}]}}
end.
add_chunks(#comp{code=Ms0}=St) ->
Add = fun (#module{name=Name,code=Beam0,chunks=Chks}=Mod) ->
if Chks =:= [] -> Mod; %Nothing to do
true ->
{ok,Name,All} = beam_lib:all_chunks(Beam0),
{ok,Beam1} = beam_lib:build_module(Chks ++ All),
Mod#module{code=Beam1}
end
end,
Ms1 = lists:map(Add, Ms0),
{ok,St#comp{code=Ms1}}.
beam_write(St0) ->
Ms1 = lists:map(fun (M) -> beam_write_module(M, St0) end, St0#comp.code),
St1 = St0#comp{code=Ms1},
%% Check return status.
?IF(all_module(Ms1), {ok,St1}, {error,St1}).
beam_write_module(#module{name=M,code=Beam}=Mod, St) ->
Name = filename:join(St#comp.odir, lists:concat([M,".beam"])),
case file:write_file(Name, Beam) of
ok -> Mod;
{error,_} ->
%% Just signal we couldn't write the file.
{error,[{lfe_comp,write_file}],[]}
end.
%% fix_erl_errors([{File,Errors}]) -> Errors.
fix_erl_errors(Fes) -> flatmap(fun ({_,Es}) -> Es end, Fes).
%% is_binary_module(State) -> true | false.
%% Check whether the module code is a binary or not.
is_binary_module(#comp{code=Mods}) ->
case Mods of
[#module{code=Code}|_] when is_binary(Code) -> true;
_ -> false
%% _ -> io:format("ibr: ~p\n", [Mods]), false
end.
%% is_werror(State) -> true | false.
%% Check if warnings_as_errors is set and we have warnings.
is_werror(#comp{code=Code,opts=Opts,warnings=Ws}) ->
case member(warnings_as_errors, Opts) of
true ->
(Ws =/= []) orelse
any(fun (#module{warnings=Mws}) -> Mws =/= [] end, Code);
false -> false
end.
%% do_ok_return(State) -> {ok,Mod,...}.
%% do_error_return(State) -> {error,...} | error.
%% Note that this handling of 'warnings_as_errors' differs from the
%% vanilla erlang compiler 'compile'. We explicitly check for it.
do_ok_return(#comp{code=Code,lfile=Lfile,opts=Opts,warnings=Ws}) ->
?WHEN_OPT(report, Opts, fun () -> list_warnings(Lfile, Ws) end),
%% Fix right return.
Report = member(report, Opts),
Return = member(return, Opts),
Binary = member(binary, Opts),
RetMod = fun (M) -> ok_return_mod(M, Report, Return, Binary, Lfile) end,
Ret0 = lists:map(RetMod, Code),
Ret1 = if Return -> [Ret0,return_ews(Lfile, Ws)];
true -> [Ret0]
end,
list_to_tuple([ok|Ret1]). %And build the ok tuple
ok_return_mod(#module{name=Name,code=Mods,warnings=Ws}, Report, Return, Binary, Lfile) ->
Report andalso list_warnings(Lfile, Ws),
Ret0 = if Return -> [return_ews(Lfile, Ws)];
true -> []
end,
Ret1 = if Binary -> [Mods|Ret0];
true -> Ret0
end,
list_to_tuple([ok,Name|Ret1]). %And build the ok tuple
do_error_return(#comp{code=Code,lfile=Lfile,opts=Opts,errors=Es,warnings=Ws}) ->
?WHEN_OPT(report, Opts, fun () -> list_errors(Lfile, Es) end),
?WHEN_OPT(report, Opts, fun () -> list_warnings(Lfile, Ws) end),
Report = lists:member(report, Opts),
Return = lists:member(return, Opts),
RetMod = fun (M) -> error_return_mod(M, Report, Return, Lfile) end,
Err = lists:map(RetMod, Code),
%% Fix right return.
?IF(Return, {error,Err,return_ews(Lfile, Es),return_ews(Lfile, Ws)}, error).
error_return_mod(#module{warnings=Ws}, Rep, _, Lfile) ->
Rep andalso list_warnings(Lfile, Ws),
{error,[],return_ews(Lfile, Ws)}; %No errors, only warnings
error_return_mod({error,Es,Ws}, Rep, _, Lfile) ->
Rep andalso list_errors(Lfile, Es),
Rep andalso list_warnings(Lfile, Ws),
{error,return_ews(Lfile, Es),return_ews(Lfile, Ws)}.
return_ews(_, []) -> [];
return_ews(Lfile, Es) -> [{Lfile,Es}].
list_warnings(F, Ws) ->
foreach(fun ({Line,Mod,Warn}) ->
Cs = Mod:format_error(Warn),
lfe_io:format("~s:~w: Warning: ~s\n", [F,Line,Cs]);
({Mod,Warn}) ->
Cs = Mod:format_error(Warn),
lfe_io:format("~s: Warning: ~s\n", [F,Cs])
end, Ws).
list_errors(F, Es) ->
foreach(fun ({Line,Mod,Error}) ->
Cs = Mod:format_error(Error),
lfe_io:format("~s:~w: ~s\n", [F,Line,Cs]);
({Mod,Error}) ->
Cs = Mod:format_error(Error),
lfe_io:format("~s: ~s\n", [F,Cs])
end, Es).