Current section
Files
Jump to
Current section
Files
src/lfe_codegen.erl
%% 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_codegen.erl
%%% Author : Robert Virding
%%% Purpose : Lisp Flavoured Erlang code generator (to Erlang AST).
%%% We must be careful to generate code in the right order so as not
%%% to generate something the Erlang AST compiler won't find errors
%%% that are due to ordering but don't really exist. We first collect
%%% and generate all the "real" attributes from the module forms which
%%% must be first but we keep type/spec/record declarations in the
%%% same relative place as in the original file.
%%%
%%% Note that for (export all) we only export the top-level functions
%%% defined in the module, not any of the lambda lifted
%%% functions. This means that we cannot generate
%%% "-compile(export_all)." but must explicitly export the functions.
%%%
%%% Having import from and rename forces us to explicitly convert the
%%% call as we can't use an import attribute to do this properly for
%%% us. Hence we collect the imports here and pass them into
%%% lfe_translate.
%%%
%%% Module aliases are also collected here and passed on to
%%% lfe_translate.
-module(lfe_codegen).
-export([module/2,format_error/1]).
%% -compile(export_all).
-include("lfe.hrl").
-include("lfe_comp.hrl").
-record(lfe_cg, {module=[], %Module name
mline=0, %Module definition line
exports=ordsets:new(), %Exports
imports=orddict:new(), %Imports
aliases=orddict:new(), %Aliases
atts=[], %Attrubutes
defs=[], %Defined top-level functions
opts=[], %Options
file=[], %File name
func=[], %Current function
errors=[], %Errors
warnings=[] %Warnings
}).
%% Errors.
format_error({illegal_code,Code}) ->
lfe_io:format1(<<"illegal ~w code">>, [Code]).
%% module(ModuleForms, CompInfo) ->
%% {ok,ModuleName,ASTModule,[Warning]} | {error,[Error],[Warning]}.
module(Mfs, #cinfo{opts=Opts,file=File}) ->
St0 = #lfe_cg{opts=Opts,file=File},
{AST,St1} = compile_module(Mfs, St0),
%% io:format("imps ~p\n", [St1#lfe_cg.imports]),
return_status(AST, St1).
return_status(AST, #lfe_cg{module=M,errors=[]}=St) ->
{ok,M,AST,St#lfe_cg.warnings};
return_status(_AST, St) ->
{error,St#lfe_cg.errors,St#lfe_cg.warnings}.
compile_module(Mfs, St0) ->
%% Collect all the module attributes and output them first.
St1 = collect_mod_defs(Mfs, St0),
Attrs = compile_attributes(St1),
%% Now we do the meta, function and record forms in order. Here we
%% can get translation errors.
%% Forms = compile_forms(Mfs, St1),
{Forms,St2} =
try
{compile_forms(Mfs, St1),St1}
catch
error:{illegal_code,Line,Code} ->
{[],add_error(Line, {illegal_code,Code}, St1)}
end,
{Attrs ++ Forms,St2}.
%% collect_mod_defs(ModuleForms, State) -> State.
%% Collect the attribute information in define-module and
%% extend-module's which must be first in the output file.
collect_mod_defs(Mfs, St) ->
lists:foldl(fun collect_mod_def/2, St, Mfs).
collect_mod_def({['define-module',Mod,_Metas,Attrs],Line}, St0) ->
St1 = coll_mdef_attrs(Attrs, Line, St0),
St1#lfe_cg{module=Mod,mline=Line};
collect_mod_def({['extend-module',_Metas,Attrs],Line}, St0) ->
coll_mdef_attrs(Attrs, Line, St0);
collect_mod_def({['define-function',Name,_Meta,Def],Line},
#lfe_cg{defs=Defs}=St) ->
%% Must save all functions for export all.
St#lfe_cg{defs=Defs ++ [{Name,Def,Line}]};
collect_mod_def(_Form, St) -> St. %Ignore everything else here
%% coll_mdef_attrs(Attributes, Line, State) -> State.
%% Collect all the module attributes.
coll_mdef_attrs(Attrs, Line, St) ->
lists:foldl(fun (A, S) -> coll_mdef_attr(A, Line, S) end, St, Attrs).
coll_mdef_attr([export|Es], _Line, St) ->
coll_mdef_exps(Es, St);
coll_mdef_attr([import|Is], _Line, St) ->
coll_mdef_imps(Is, St);
coll_mdef_attr(['module-alias'|As], _Line, St) ->
coll_mdef_aliases(As, St);
%% Explicitly ignore any doc or record information here.
coll_mdef_attr([doc|_], _Line, St) -> St;
coll_mdef_attr([record|_], _Line, St) -> St;
%% Save anything else and get the format right.
coll_mdef_attr([Name,Val], Line, #lfe_cg{atts=As}=St) ->
St#lfe_cg{atts=As ++ [{Name,Val,Line}]};
coll_mdef_attr([Name|Vals], Line, #lfe_cg{atts=As}=St) ->
St#lfe_cg{atts=As ++ [{Name,Vals,Line}]}.
%% coll_mdef_exps(Export, State) -> State.
%% Collect exports special casing 'all'.
coll_mdef_exps([all], St) -> St#lfe_cg{exports=all};
coll_mdef_exps(_Exps, #lfe_cg{exports=all}=St) -> St;
coll_mdef_exps(Exps, #lfe_cg{exports=Exps0}=St) ->
Exps1 = lists:foldl(fun ([F,A], E) -> ordsets:add_element({F,A}, E) end,
Exps0, Exps),
St#lfe_cg{exports=Exps1}.
%% coll_mdef_imps(Imports, State) -> State.
%% Collect imports keeping track of local and imported names.
coll_mdef_imps(Imps, St) ->
lists:foldl(fun (I, S) -> coll_mdef_imp(I, S) end, St, Imps).
coll_mdef_imp(['from',Mod|Fs], St) ->
Ifun = fun ([F,A], Ifs) -> orddict:store({F,A}, {Mod,F}, Ifs) end,
coll_mdef_imp(Ifun, St, Fs);
coll_mdef_imp(['rename',Mod|Fs], St) ->
%% Get it right here, R is the renamed local called function, F is
%% the name in the other module.
Ifun = fun ([[F,A],R], Ifs) -> orddict:store({R,A}, {Mod,F}, Ifs) end,
coll_mdef_imp(Ifun, St, Fs).
coll_mdef_imp(Fun, #lfe_cg{imports=Imps0}=St, Fs) ->
Imps1 = lists:foldl(Fun, Imps0, Fs),
St#lfe_cg{imports=Imps1}.
%% coll_mdef_aliases(Aliases, State) -> State.
%% Collect the module aliases.
coll_mdef_aliases(As, #lfe_cg{aliases=Als0}=St) ->
Als1 = lists:foldl(fun ([M,A], Mas) -> orddict:store(A, M, Mas) end,
Als0, As),
St#lfe_cg{aliases=Als1}.
%% compile_attributes(State) -> MdefAST.
%% Compile the module attributes.
compile_attributes(St) ->
Exp = comp_export(St),
Imps = comp_imports(St),
Atts = comp_attributes(St),
Mline = St#lfe_cg.mline,
%% Collect all the attributes.
AST = [make_attribute(file, {St#lfe_cg.file,Mline}, Mline),
make_attribute(module, St#lfe_cg.module, Mline),
Exp |
Imps ++ Atts],
AST.
%% compile_forms(ModuleForms, State) -> [AST].
%% Compile the function and record forms into Erlang ASTs.
compile_forms(Forms, St) ->
lists:flatmap(fun (F) -> compile_form(F, St) end, Forms).
compile_form({['define-module',_Mod,Metas,_Attrs],Line}, St) ->
comp_mod_metas(Metas, Line, St);
compile_form({['extend-module',Metas,_Attrs],Line}, St) ->
comp_mod_metas(Metas, Line, St);
compile_form({['define-type',Type,Def],Line}, _St) ->
comp_type_def(type, Type, Def, Line);
compile_form({['define-opaque-type',Type,Def],Line}, _St) ->
comp_type_def(opaque, Type, Def, Line);
compile_form({['define-function-spec',Func,Spec],Line}, _St) ->
comp_function_spec(Func, Spec, Line);
compile_form({['define-record',Name,Fields],Line}, _St) ->
comp_record_def(Name, Fields, Line);
compile_form({['define-function',Name,_Meta,Def],Line}, St) ->
comp_function_def(Name, Def, Line, St);
%% Ignore anything else for now. Hopefully there shouldn't be anything
%% else.
compile_form(_Other, _St) -> [].
%% comp_mod_metas(Metas, Line, State) -> [AST].
comp_mod_metas(Metas, Line, _St) ->
lists:flatmap(fun (M) -> comp_mod_meta(M, Line) end, Metas).
comp_mod_meta([type|Tdefs], Line) ->
lists:flatmap(fun (Tdef) -> comp_type_def(type, Tdef, Line) end, Tdefs);
comp_mod_meta([opaque|Tdefs], Line) ->
lists:flatmap(fun (Tdef) -> comp_type_def(opaque, Tdef, Line) end, Tdefs);
comp_mod_meta([spec|Fspecs], Line) ->
Fun = fun (Fspec) -> comp_function_spec(Fspec, Line) end,
lists:flatmap(Fun, Fspecs);
comp_mod_meta([record|Rdefs], Line) ->
Fun = fun (Rdef) -> comp_record_def(Rdef, Line) end,
lists:flatmap(Fun, Rdefs);
comp_mod_meta(_Meta, _Line) -> [].
%% comp_type_def(Attr, TypeDef, Line) -> [AST].
%% comp_type_def(Attr, Type, Def, Line) -> [AST].
comp_type_def(Attr, [Type,Def], Line) ->
comp_type_def(Attr, Type, Def, Line).
comp_type_def(Attr, [Type|Args], Def, Line) ->
Tdef = {Type,
lfe_types:to_type_def(Def, Line),
lfe_types:to_type_defs(Args, Line)},
[make_attribute(Attr, Tdef, Line)].
%% comp_function_spec(FuncSpec, Line) -> [AST].
%% comp_function_spec(Func, Spec, Line) -> [AST].
comp_function_spec([Func|Spec], Line) ->
comp_function_spec(Func, Spec, Line).
comp_function_spec([Name,Ar], Spec, Line) ->
Sdef = {{Name,Ar},lfe_types:to_func_spec_list(Spec, Line)},
[make_attribute(spec, Sdef, Line)].
%% comp_function_def(Func, Def, Line, State) -> [AST].
%% Lambda lift the function returning all the functions.
comp_function_def(Name, Def, Line, #lfe_cg{imports=Imps,aliases=Aliases}) ->
%% This also returns the defined top function.
Lfs = lfe_codelift:function(Name, Def, Line),
lists:map(fun ({N,D,L}) ->
{'fun',_,{clauses,Clauses}} =
lfe_translate:to_expr(D, L, {Imps,Aliases}),
{function,L,N,func_arity(D),Clauses}
end, Lfs).
%% comp_record_def(RecordDef, Line) -> [Attribute].
%% comp_record_def(Name, Fields, Line) -> [Attribute].
%% Format depends on whether 18 and older or newer. Meta is not
%% passed on.
comp_record_def([Name,Fields], Line) ->
comp_record_def(Name, Fields, Line).
comp_record_def(Name, Fields, Line) ->
Fdefs = [ comp_record_field(Fdef, Line) || Fdef <- Fields ],
[make_record_attribute(Name, Fdefs, Line)].
comp_record_field([F,D,T], Line) ->
{typed_record_field,
comp_untyped_field([F,D], Line),
lfe_types:to_type_def(T, Line)};
comp_record_field(Fd, Line) ->
comp_untyped_field(Fd, Line).
comp_untyped_field([F,?Q(undefined)], Line) ->
%% No need for undefined default.
{record_field,Line,{atom,Line,F}};
comp_untyped_field([F,D], Line) ->
{record_field,Line,{atom,Line,F},lfe_translate:to_expr(D, Line)};
comp_untyped_field([F], Line) ->
{record_field,Line,{atom,Line,F}};
comp_untyped_field(F, Line) ->
{record_field,Line,{atom,Line,F}}.
-ifdef(NEW_REC_CORE).
make_record_attribute(Name, Fdefs, Line) ->
make_attribute(record, {Name,Fdefs}, Line).
-else.
make_record_attribute(Name, Fdefs, Line) ->
make_attribute(type, {{record,Name},Fdefs}, Line).
-endif.
%% comp_export(State) -> Attribute.
%% comp_imports(State) -> [Attribute].
%% comp_attributes(State) -> [Attribute].
%% Currently we don't add the import attributes.
comp_export(#lfe_cg{exports=Exps,defs=Defs,mline=Line}) ->
Es = if Exps =:= all ->
[ {F,func_arity(Def)} || {F,Def,_} <- Defs ];
true -> Exps %Already in right format
end,
make_attribute(export, Es, Line).
comp_imports(_St) -> [].
comp_attributes(#lfe_cg{atts=Atts}) ->
lists:map(fun comp_attribute/1, Atts).
%% comp_attribute({spec,[Func|Spec],Line}) ->
%% hd(comp_func_spec(Func, Spec, Line)); %We know!
comp_attribute({'export-type',Ts,Line}) ->
Ets = lists:map(fun ([T,A]) -> {T,A} end, Ts),
make_attribute(export_type, Ets, Line);
comp_attribute({Name,Val,Line}) ->
make_attribute(Name, Val, Line).
%% make_attribute(Name, Value, Line) -> Atttribute.
make_attribute(Name, Val, Line) ->
{attribute,Line,Name,Val}.
%% func_arity(FuncDef) -> Arity.
%% Return the arity of a function definition.
func_arity([lambda,Args|_]) -> length(Args);
func_arity(['match-lambda'|Cls]) ->
match_lambda_arity(Cls).
%% match_lambda_arity(MatchClauses) -> int().
match_lambda_arity([[Pats|_]|_]) -> length(Pats).
%% safe_fetch(Key, Dict, Default) -> Value.
%% Fetch a value with a default if it doesn't exist.
%% safe_fetch(Key, D, Def) ->
%% case orddict:find(Key, D) of
%% {ok,Val} -> Val;
%% error -> Def
%% end.
%% add_error(Line, Error, State) -> State.
add_error(L, E, #lfe_cg{errors=Errs}=St) ->
St#lfe_cg{errors=Errs ++ [{L,?MODULE,E}]}.