%% Copyright (c) 2008-2024 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 onload=[], %Onload records=[], %Records struct=undefined, %Struct definition attrs=[], %Attrubutes metas=[], %Meta data funcs=orddict:new(), %Defined top-level functions opts=[], %Compiler 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("st1 ~p\n", [St1]), 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), %% Build the struct functions then __info__ function last. St2 = build_struct_def(St1), St3 = build_info_func(St2), %Must be last! Attrs = compile_attributes(St3), %% Now we do the meta, function and record forms in order. Here we %% can get translation errors. %% Forms = compile_functions(St1), {Functions,St4} = try {compile_functions(St3),St3} catch error:{illegal_code,Line,Code} -> {[],add_error(Line, {illegal_code,Code}, St3)} end, {Attrs ++ Functions,St4}. %% collect_mod_defs(ModuleForms, State) -> State. %% Collect all the information in the module deinition for processing. collect_mod_defs(Mfs, St) -> lists:foldl(fun collect_mod_def/2, St, Mfs). collect_mod_def({['define-module',Mod,Metas,Attrs],Line}, St0) -> %% And now add the rest of the attributes and metas. St1 = coll_mdef_attrs(Attrs, Line, St0), St2 = coll_mdef_metas(Metas, Line, St1), St2#lfe_cg{module=Mod,mline=Line}; collect_mod_def({['extend-module',Metas,Attrs],Line}, St0) -> St1 = coll_mdef_attrs(Attrs, Line, St0), coll_mdef_metas(Metas, Line, St1); collect_mod_def({['define-type',Type,Def],Line}, St) -> coll_mdef_meta([type,[Type,Def]], Line, St); collect_mod_def({['define-opaque-type',Type,Def],Line}, St) -> coll_mdef_meta([opaque,[Type,Def]], Line, St); collect_mod_def({['define-function-spec',Func,Specs],Line}, St) -> %% io:format("lc ~p\n", [{cmd,Func,Specs}]), coll_func_spec(Func, Specs, Line, St); collect_mod_def({['define-record',Name,Fields],Line}, St) -> %% io:format("cmd ~p\n", [[record,[Name,Fields]]]), coll_mdef_meta([record,[Name,Fields]], Line, St); collect_mod_def({['define-struct',Fields],Line}, St) -> St#lfe_cg{struct={Fields,Line}}; collect_mod_def({['define-function',Name,_Meta,Def],Line}, #lfe_cg{funcs=Funcs0}=St) -> %% Must save all the functions. Arity = func_arity(Def), Funcs1 = orddict:store({Name,Arity}, {Def,Line}, Funcs0), St#lfe_cg{funcs=Funcs1}; collect_mod_def(_Form, St) -> St. %Ignore everything else here %% coll_mdef_attrs(Attributes, Line, State) -> State. %% Collect all the module attributes. Keep the attributes in order. 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_exports(Es, St); coll_mdef_attr([import|Is], _Line, St) -> coll_mdef_imports(Is, St); coll_mdef_attr(['module-alias'|As], _Line, St) -> coll_mdef_aliases(As, St); coll_mdef_attr([on_load,Onload], _Line, St) -> coll_mdef_onload(Onload, St); %% Explicitly ignore any doc here. coll_mdef_attr([doc|_], _Line, St) -> St; coll_mdef_attr([record|Recs], Line, St) -> coll_mdef_records(Recs, Line, St); %% Save anything else and get the format right. coll_mdef_attr([Name,Val], Line, #lfe_cg{attrs=Attrs}=St) -> St#lfe_cg{attrs=Attrs ++ [{Name,Val,Line}]}; coll_mdef_attr([Name|Vals], Line, #lfe_cg{attrs=Attrs}=St) -> St#lfe_cg{attrs=Attrs ++ [{Name,Vals,Line}]}. %% coll_mdef_exports(Export, State) -> State. %% Collect exports special casing 'all'. coll_mdef_exports([all], St) -> St#lfe_cg{exports=all}; coll_mdef_exports(_Exps, #lfe_cg{exports=all}=St) -> St; coll_mdef_exports(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_imports(Imports, State) -> State. %% Collect imports keeping track of local and imported names. coll_mdef_imports(Imps, St) -> lists:foldl(fun (I, S) -> coll_mdef_import(I, S) end, St, Imps). coll_mdef_import(['from',Mod|Fs], St) -> Ifun = fun ([F,A], Ifs) -> orddict:store({F,A}, {Mod,F}, Ifs) end, coll_mdef_import(Ifun, St, Fs); coll_mdef_import(['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_import(Ifun, St, Fs). coll_mdef_import(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}. %% coll_mdef_onload(Onload, State) -> State. %% Collect the on_load function name. coll_mdef_onload([Name,Ar], St) -> St#lfe_cg{onload={Name,Ar}}. %% coll_mdef_records(Records, Line, State) -> State. %% Collect the record definitions. coll_mdef_records(RecordDefs, Line, St) -> Fun = fun ([Name,Fields], #lfe_cg{records=Recs}=S) -> S#lfe_cg{records=Recs ++ [{Name,Fields,Line}]} end, lists:foldl(Fun, St, RecordDefs). %% coll_mdef_metas(Metas, Line, State) -> State. %% Collect all the module metas. Keep the metas in order. coll_mdef_metas(Metas, Line, St) -> lists:foldl(fun (M, S) -> coll_mdef_meta(M, Line, S) end, St, Metas). coll_mdef_meta([type|Tdefs], Line, #lfe_cg{metas=Metas}=St) -> St#lfe_cg{metas=Metas ++ [{type,Tdefs,Line}]}; coll_mdef_meta([opaque|Tdefs], Line, #lfe_cg{metas=Metas}=St) -> St#lfe_cg{metas=Metas ++ [{opaque,Tdefs,Line}]}; coll_mdef_meta([spec|Specs], Line, St) -> coll_mdef_specs(Specs, Line, St); coll_mdef_meta([record|Rdefs], Line, #lfe_cg{metas=Metas}=St) -> St#lfe_cg{metas=Metas ++ [{record,Rdefs,Line}]}; %% Ignore other metas. coll_mdef_meta(_Meta, _Line, St) -> St. coll_mdef_specs(Specs, Line, St) -> Coll = fun ([Func,Spec], S) -> coll_func_spec(Func, Spec, Line, S) end, lists:foldl(Coll, St, Specs). coll_func_spec(Func, Specs, Line, #lfe_cg{metas=Metas}=St) -> St#lfe_cg{metas=Metas ++ [{spec,[Func,Specs],Line}]}. %% build_struct_def(State) -> State. %% build_info_func(State) -> State. %% Create functions which are built from the data collected from the %% file. Here the __info__ function and the struct definition %% functions. build_struct_def(#lfe_cg{struct=undefined}=St) -> %% No struct has been defined. St; build_struct_def(#lfe_cg{module=Mod,struct={Fields,Line},funcs=Funcs0}=St0) -> %% The default struct. DefStr = comp_struct_map(Mod, Fields), %% The default __struct__/0/1 functions. StrFun_0 = struct_fun_0(DefStr), StrFun_1 = struct_fun_1(DefStr), Funcs1 = orddict:store({'__struct__',0}, {StrFun_0,Line}, Funcs0), Funcs2 = orddict:store({'__struct__',1}, {StrFun_1,Line}, Funcs1), St1 = coll_mdef_exports([['__struct__',0],['__struct__',1]], St0), St1#lfe_cg{funcs=Funcs2}. comp_struct_map(Mod, Fields) -> Fun = fun ([F,D|_]) -> {F,D}; ([F]) -> {F,'nil'}; (F) -> {F,'nil'} end, KeyVals = lists:map(Fun, Fields), maps:from_list([{'__struct__',Mod}|KeyVals]). %% struct_fun_0(DefStr) -> FuncDef. %% struct_fun_1(DefStr) -> FuncDef. %% Create the bodies __struct__/0/1 functions. struct_fun_0(DefStr) -> [lambda,[],DefStr]. struct_fun_1(DefStr) -> [lambda,[assocs], [call,?Q(lists),?Q(foldl), ['match-lambda',[[[tuple,x,y],acc], [call,?Q(maps),?Q(update),x,y,acc]]], DefStr,assocs]]. build_info_func(#lfe_cg{module=Mod,mline=Line,funcs=Funcs0}=St0) -> %% The default clauses. InfoCls = [[[?Q(module)],?Q(Mod)], [[?Q(functions)],?Q(info_functions(St0))], [[?Q(macros)],[]], [[?Q(deprecated)],[]], [[?Q(attributes)], [call,?Q(erlang),?Q(get_module_info),?Q(Mod),?Q(attributes)]], [[?Q(compile)], [call,?Q(erlang),?Q(get_module_info),?Q(Mod),?Q(compile)]], [[?Q(md5)], [call,?Q(erlang),?Q(get_module_info),?Q(Mod),?Q(md5)]] ], %% The struct clause if relevant. StrCls = struct_info_clause(St0), %% The function body. InfoFun = ['match-lambda' | InfoCls ++ StrCls ], Funcs1 = orddict:store({'__info__',1}, {InfoFun,Line}, Funcs0), St1 = coll_mdef_exports([['__info__',1]], St0), St1#lfe_cg{funcs=Funcs1}. info_functions(#lfe_cg{exports=Exps,funcs=Funcs}) -> if Exps =:= all -> orddict:fetch_keys(Funcs); true -> Exps end. struct_info_clause(St) -> case St#lfe_cg.struct of undefined -> []; %No struct def, no clause {Fields,_Line} -> Fun = fun ([F|_]) -> #{field => F, required => false}; (F) -> #{field => F, required => false} end, KeyVals = lists:map(Fun, Fields), [ [[?Q(struct)],[list | KeyVals]] ] end. %% compile_attributes(State) -> MdefAST. %% Compile the module attributes, metas, records and structs compile_attributes(St) -> Exp = comp_export(St), Imps = comp_imports(St), Onload = comp_onload(St), Attrs = comp_attributes(St), Metas = comp_metas(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 | Onload ++ Imps ++ Attrs ++ Metas], AST. %% comp_export(State) -> Attribute. %% comp_imports(State) -> [Attribute]. %% comp_on_load(State) -> Attribute. %% comp_attributes(State) -> [Attribute]. %% comp_metas(State) -> [Attribute] %% Currently we don't add the import attributes. comp_export(#lfe_cg{exports=Exps,funcs=Funcs,mline=Line}) -> Es = if Exps =:= all -> orddict:fetch_keys(Funcs); true -> Exps %Already in right format end, make_attribute(export, Es, Line). comp_imports(_St) -> []. comp_onload(#lfe_cg{onload={Func,Ar},mline=Line}) -> [make_attribute(on_load, {Func,Ar}, Line)]; comp_onload(#lfe_cg{onload=[]}) -> []. comp_attributes(#lfe_cg{attrs=Atts}) -> lists:map(fun comp_attribute/1, Atts). 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). comp_metas(#lfe_cg{metas=Metas,mline=Line}) -> %% io:format("lc ~p\n", [{cms,Metas}]), Ms = lists:flatmap(fun (M) -> comp_meta(M, Line) end, Metas), %% io:format("cm ~p\n", [Ms]), Ms. comp_meta({type,Tdefs,_}, Line) -> lists:flatmap(fun (Tdef) -> comp_type_def(type, Tdef, Line) end, Tdefs); comp_meta({opaque,Tdefs,_}, Line) -> lists:flatmap(fun (Tdef) -> comp_type_def(opaque, Tdef, Line) end, Tdefs); comp_meta({spec,[Func,Specs],Line}, _Line) -> %% io:format("lc ~p\n", [{cm,Func,Specs}]), comp_function_specs(Func, Specs, Line); comp_meta({record,Rdefs,_}, Line) -> Fun = fun (Rdef) -> comp_record_def(Rdef, Line) end, As = lists:flatmap(Fun, Rdefs), %% io:format("cmr ~p\n", [As]), As; comp_meta(_Meta, _Line) -> []. %% compile_functions(State) -> [AST]. compile_functions(#lfe_cg{funcs=Funcs0}=St) -> Fun = fun (F, B, Fs) -> Fs ++ compile_function(F, B, St) end, orddict:fold(Fun, [], Funcs0). compile_function({Name,_Arity}, {Def,Line}, St) -> comp_function_def(Name, Def, Line,St). %% comp_type_def(Attr, TypeDef, Line) -> [AST]. %% comp_type_def(Attr, Type, Def, Line) -> [AST]. %% Compile a type definition to an attribute. 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_specs(FuncSpecs, Line) -> [AST]. %% comp_function_specs(Func, Spec, Line) -> [AST]. %% Compile a function specification to an attribute. %% comp_function_specs([Func,Specs], Line) -> %% %% io:format("lc ~p\n", [{cfs1,Func,Specs}]), %% comp_function_specs(Func, Specs, Line). comp_function_specs([Name,Ar], Specs, Line) -> %% io:format("lc ~p\n", [{cfs2,Name,Ar,Specs}]), Sdef = {{Name,Ar},lfe_types:to_func_spec_list(Specs, 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) -> %% io:format("crd ~p ~p\n", [Name,Fields]), Fdefs = [ comp_record_field(Fdef, Line) || Fdef <- Fields ], %% io:format("cra ~p\n", [make_record_attribute(Name, Fdefs, Line)]), [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. %% 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}]}.