%% %% %CopyrightBegin% %% %% Copyright Ericsson AB 2002-2018. All Rights Reserved. %% %% 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. %% %% %CopyrightEnd% %% -module(ms_transform). -export([format_error/1,transform_from_shell/3,parse_transform/2]). %% Error codes. -define(ERROR_BASE_GUARD,0). -define(ERROR_BASE_BODY,100). -define(ERR_NOFUN,1). -define(ERR_ETS_HEAD,2). -define(ERR_DBG_HEAD,3). -define(ERR_HEADMATCH,4). -define(ERR_SEMI_GUARD,5). -define(ERR_UNBOUND_VARIABLE,6). -define(ERR_HEADBADREC,7). -define(ERR_HEADBADFIELD,8). -define(ERR_HEADMULTIFIELD,9). -define(ERR_HEADDOLLARATOM,10). -define(ERR_HEADBINMATCH,11). -define(ERR_GENMATCH,16). -define(ERR_GENLOCALCALL,17). -define(ERR_GENELEMENT,18). -define(ERR_GENBADFIELD,19). -define(ERR_GENBADREC,20). -define(ERR_GENMULTIFIELD,21). -define(ERR_GENREMOTECALL,22). -define(ERR_GENBINCONSTRUCT,23). -define(ERR_GENDISALLOWEDOP,24). -define(WARN_SHADOW_VAR,50). -define(ERR_GUARDMATCH,?ERR_GENMATCH+?ERROR_BASE_GUARD). -define(ERR_BODYMATCH,?ERR_GENMATCH+?ERROR_BASE_BODY). -define(ERR_GUARDLOCALCALL,?ERR_GENLOCALCALL+?ERROR_BASE_GUARD). -define(ERR_BODYLOCALCALL,?ERR_GENLOCALCALL+?ERROR_BASE_BODY). -define(ERR_GUARDELEMENT,?ERR_GENELEMENT+?ERROR_BASE_GUARD). -define(ERR_BODYELEMENT,?ERR_GENELEMENT+?ERROR_BASE_BODY). -define(ERR_GUARDBADFIELD,?ERR_GENBADFIELD+?ERROR_BASE_GUARD). -define(ERR_BODYBADFIELD,?ERR_GENBADFIELD+?ERROR_BASE_BODY). -define(ERR_GUARDBADREC,?ERR_GENBADREC+?ERROR_BASE_GUARD). -define(ERR_BODYBADREC,?ERR_GENBADREC+?ERROR_BASE_BODY). -define(ERR_GUARDMULTIFIELD,?ERR_GENMULTIFIELD+?ERROR_BASE_GUARD). -define(ERR_BODYMULTIFIELD,?ERR_GENMULTIFIELD+?ERROR_BASE_BODY). -define(ERR_GUARDREMOTECALL,?ERR_GENREMOTECALL+?ERROR_BASE_GUARD). -define(ERR_BODYREMOTECALL,?ERR_GENREMOTECALL+?ERROR_BASE_BODY). -define(ERR_GUARDBINCONSTRUCT,?ERR_GENBINCONSTRUCT+?ERROR_BASE_GUARD). -define(ERR_BODYBINCONSTRUCT,?ERR_GENBINCONSTRUCT+?ERROR_BASE_BODY). -define(ERR_GUARDDISALLOWEDOP,?ERR_GENDISALLOWEDOP+?ERROR_BASE_GUARD). -define(ERR_BODYDISALLOWEDOP,?ERR_GENDISALLOWEDOP+?ERROR_BASE_BODY). %% %% Called by compiler or ets/dbg:fun2ms when errors/warnings occur %% -spec(format_error(Error) -> Chars when Error :: {error, module(), term()}, Chars :: io_lib:chars()). format_error({?WARN_SHADOW_VAR,Name}) -> lists:flatten( io_lib:format("variable ~p shadowed in ms_transform fun head", [Name])); format_error(?ERR_NOFUN) -> "Parameter of ets/dbg:fun2ms/1 is not a literal fun"; format_error(?ERR_ETS_HEAD) -> "ets:fun2ms requires fun with single variable or tuple parameter"; format_error(?ERR_DBG_HEAD) -> "dbg:fun2ms requires fun with single variable or list parameter"; format_error(?ERR_HEADMATCH) -> "in fun head, only matching (=) on toplevel can be translated into match_spec"; format_error(?ERR_SEMI_GUARD) -> "fun with semicolon (;) in guard cannot be translated into match_spec"; format_error(?ERR_GUARDMATCH) -> "fun with guard matching ('=' in guard) is illegal as match_spec as well"; format_error({?ERR_GUARDLOCALCALL, Name, Arithy}) -> lists:flatten(io_lib:format("fun containing the local function call " "'~tw/~w' (called in guard) " "cannot be translated into match_spec", [Name, Arithy])); format_error({?ERR_GUARDREMOTECALL, Module, Name, Arithy}) -> lists:flatten(io_lib:format("fun containing the remote function call " "'~w:~tw/~w' (called in guard) " "cannot be translated into match_spec", [Module,Name,Arithy])); format_error({?ERR_GUARDELEMENT, Str}) -> lists:flatten( io_lib:format("the language element ~ts (in guard) cannot be translated " "into match_spec", [Str])); format_error({?ERR_GUARDBINCONSTRUCT, Var}) -> lists:flatten( io_lib:format("bit syntax construction with variable ~w (in guard) " "cannot be translated " "into match_spec", [Var])); format_error({?ERR_GUARDDISALLOWEDOP, Operator}) -> %% There is presently no operators that are allowed in bodies but %% not in guards. lists:flatten( io_lib:format("the operator ~w is not allowed in guards", [Operator])); format_error(?ERR_BODYMATCH) -> "fun with body matching ('=' in body) is illegal as match_spec"; format_error({?ERR_BODYLOCALCALL, Name, Arithy}) -> lists:flatten(io_lib:format("fun containing the local function " "call '~tw/~w' (called in body) " "cannot be translated into match_spec", [Name,Arithy])); format_error({?ERR_BODYREMOTECALL, Module, Name, Arithy}) -> lists:flatten(io_lib:format("fun containing the remote function call " "'~w:~tw/~w' (called in body) " "cannot be translated into match_spec", [Module,Name,Arithy])); format_error({?ERR_BODYELEMENT, Str}) -> lists:flatten( io_lib:format("the language element ~ts (in body) cannot be translated " "into match_spec", [Str])); format_error({?ERR_BODYBINCONSTRUCT, Var}) -> lists:flatten( io_lib:format("bit syntax construction with variable ~w (in body) " "cannot be translated " "into match_spec", [Var])); format_error({?ERR_BODYDISALLOWEDOP, Operator}) -> %% This will probably never happen, Are there op's that are allowed in %% guards but not in bodies? Not at time of writing anyway... lists:flatten( io_lib:format("the operator ~w is not allowed in function bodies", [Operator])); format_error({?ERR_UNBOUND_VARIABLE, Str}) -> lists:flatten( io_lib:format("the variable ~s is unbound, cannot translate " "into match_spec", [Str])); format_error({?ERR_HEADBADREC,Name}) -> lists:flatten( io_lib:format("fun head contains unknown record type ~tw",[Name])); format_error({?ERR_HEADBADFIELD,RName,FName}) -> lists:flatten( io_lib:format("fun head contains reference to unknown field ~tw in " "record type ~tw",[FName, RName])); format_error({?ERR_HEADMULTIFIELD,RName,FName}) -> lists:flatten( io_lib:format("fun head contains already defined field ~tw in " "record type ~tw",[FName, RName])); format_error({?ERR_HEADDOLLARATOM,Atom}) -> lists:flatten( io_lib:format("fun head contains atom ~w, which conflics with reserved " "atoms in match_spec heads",[Atom])); format_error({?ERR_HEADBINMATCH,Atom}) -> lists:flatten( io_lib:format("fun head contains bit syntax matching of variable ~w, " "which cannot be translated into match_spec", [Atom])); format_error({?ERR_GUARDBADREC,Name}) -> lists:flatten( io_lib:format("fun guard contains unknown record type ~tw",[Name])); format_error({?ERR_GUARDBADFIELD,RName,FName}) -> lists:flatten( io_lib:format("fun guard contains reference to unknown field ~tw in " "record type ~tw",[FName, RName])); format_error({?ERR_GUARDMULTIFIELD,RName,FName}) -> lists:flatten( io_lib:format("fun guard contains already defined field ~tw in " "record type ~tw",[FName, RName])); format_error({?ERR_BODYBADREC,Name}) -> lists:flatten( io_lib:format("fun body contains unknown record type ~tw",[Name])); format_error({?ERR_BODYBADFIELD,RName,FName}) -> lists:flatten( io_lib:format("fun body contains reference to unknown field ~tw in " "record type ~tw",[FName, RName])); format_error({?ERR_BODYMULTIFIELD,RName,FName}) -> lists:flatten( io_lib:format("fun body contains already defined field ~tw in " "record type ~tw",[FName, RName])); format_error(Else) -> lists:flatten(io_lib:format("Unknown error code ~tw",[Else])). %% %% Called when translating in shell %% -spec transform_from_shell(Dialect, Clauses, BoundEnvironment) -> term() when Dialect :: ets | dbg, Clauses :: [erl_parse:abstract_clause()], BoundEnvironment :: erl_eval:binding_struct(). transform_from_shell(Dialect, Clauses, BoundEnvironment) -> SaveFilename = setup_filename(), case catch ms_clause_list(1,Clauses,Dialect,gb_sets:new()) of {'EXIT',Reason} -> cleanup_filename(SaveFilename), exit(Reason); {error,Line,R} -> {error, [{cleanup_filename(SaveFilename), [{Line, ?MODULE, R}]}], []}; Else -> case (catch fixup_environment(Else,BoundEnvironment)) of {error,Line1,R1} -> {error, [{cleanup_filename(SaveFilename), [{Line1, ?MODULE, R1}]}], []}; Else1 -> Ret = normalise(Else1), cleanup_filename(SaveFilename), Ret end end. %% %% Called when translating during compiling %% -spec parse_transform(Forms, Options) -> Forms2 | Errors | Warnings when Forms :: [erl_parse:abstract_form() | erl_parse:form_info()], Forms2 :: [erl_parse:abstract_form() | erl_parse:form_info()], Options :: term(), Errors :: {error, ErrInfo :: [tuple()], WarnInfo :: []}, Warnings :: {warning, Forms2, WarnInfo :: [tuple()]}. parse_transform(Forms, _Options) -> SaveFilename = setup_filename(), %io:format("Forms: ~p~n",[Forms]), case catch forms(Forms) of {'EXIT',Reason} -> cleanup_filename(SaveFilename), exit(Reason); {error,Line,R} -> {error, [{cleanup_filename(SaveFilename), [{Line, ?MODULE, R}]}], []}; Else -> %io:format("Transformed into: ~p~n",[Else]), case get_warnings() of [] -> cleanup_filename(SaveFilename), Else; WL -> FName = cleanup_filename(SaveFilename) , WList = [ {FName, [{L, ?MODULE, R}]} || {L,R} <- WL ], {warning, Else, WList} end end. get_warnings() -> case get(warnings) of undefined -> []; Else -> Else end. add_warning(Line,R) -> put(warnings,[{Line,R}| get_warnings()]). setup_filename() -> {erase(filename),erase(records),erase(warnings)}. put_filename(Name) -> put(filename,Name). put_records(R) -> put(records,R), ok. get_records() -> case get(records) of undefined -> []; Else -> Else end. cleanup_filename({Old,OldRec,OldWarnings}) -> Ret = case erase(filename) of undefined -> "TOP_LEVEL"; X -> X end, case OldRec of undefined -> erase(records); Rec -> put(records,Rec) end, case OldWarnings of undefined -> erase(warnings); Warn -> put(warnings,Warn) end, case Old of undefined -> Ret; Y -> put(filename,Y), Ret end. add_record_definition({Name,FieldList}) -> {KeyList,_} = lists:foldl( fun(F, {L,C}) -> {[record_field(F, C)|L],C+1} end, {[],2}, FieldList), put_records([{Name,KeyList}|get_records()]). record_field({record_field,_,{atom,Line0,FieldName}}, C) -> {FieldName,C,{atom,Line0,undefined}}; record_field({record_field,_,{atom,_,FieldName},Def}, C) -> {FieldName,C,Def}; record_field({typed_record_field,Field,_Type}, C) -> record_field(Field, C). forms([F0|Fs0]) -> F1 = form(F0), Fs1 = forms(Fs0), [F1|Fs1]; forms([]) -> []. form({attribute,_,file,{Filename,_}}=Form) -> put_filename(Filename), Form; form({attribute,_,record,Definition}=Form) -> add_record_definition(Definition), Form; form({function,Line,Name0,Arity0,Clauses0}) -> {Name,Arity,Clauses} = function(Name0, Arity0, Clauses0), {function,Line,Name,Arity,Clauses}; form(AnyOther) -> AnyOther. function(Name, Arity, Clauses0) -> Clauses1 = clauses(Clauses0), {Name,Arity,Clauses1}. clauses([C0|Cs]) -> C1 = clause(C0,gb_sets:new()), C2 = clauses(Cs), [C1|C2]; clauses([]) -> []. clause({clause,Line,H0,G0,B0},Bound) -> {H1,Bound1} = copy(H0,Bound), {B1,_Bound2} = copy(B0,Bound1), {clause,Line,H1,G0,B1}. copy({call,Line,{remote,_Line2,{atom,_Line3,ets},{atom,_Line4,fun2ms}}, As0},Bound) -> {transform_call(ets,Line,As0,Bound),Bound}; copy({call,Line,{remote,_Line2,{record_field,_Line3, {atom,_Line4,''},{atom,_Line5,ets}}, {atom,_Line6,fun2ms}}, As0},Bound) -> %% Packages... {transform_call(ets,Line,As0,Bound),Bound}; copy({call,Line,{remote,_Line2,{atom,_Line3,dbg},{atom,_Line4,fun2ms}}, As0},Bound) -> {transform_call(dbg,Line,As0,Bound),Bound}; copy({match,Line,A,B},Bound) -> {B1,Bound1} = copy(B,Bound), {A1,Bound2} = copy(A,Bound), {{match,Line,A1,B1},gb_sets:union(Bound1,Bound2)}; copy({var,_Line,'_'} = VarDef,Bound) -> {VarDef,Bound}; copy({var,_Line,Name} = VarDef,Bound) -> Bound1 = gb_sets:add(Name,Bound), {VarDef,Bound1}; copy({'fun',Line,{clauses,Clauses}},Bound) -> % Dont export bindings from funs {NewClauses,_IgnoredBindings} = copy_list(Clauses,Bound), {{'fun',Line,{clauses,NewClauses}},Bound}; copy({named_fun,Line,Name,Clauses},Bound) -> % Dont export bindings from funs Bound1 = case Name of '_' -> Bound; Name -> gb_sets:add(Name,Bound) end, {NewClauses,_IgnoredBindings} = copy_list(Clauses,Bound1), {{named_fun,Line,Name,NewClauses},Bound}; copy({'case',Line,Of,ClausesList},Bound) -> % Dont export bindings from funs {NewOf,NewBind0} = copy(Of,Bound), {NewClausesList,NewBindings} = copy_case_clauses(ClausesList,NewBind0,[]), {{'case',Line,NewOf,NewClausesList},NewBindings}; copy(T,Bound) when is_tuple(T) -> {L,Bound1} = copy_list(tuple_to_list(T),Bound), {list_to_tuple(L),Bound1}; copy(L,Bound) when is_list(L) -> copy_list(L,Bound); copy(AnyOther,Bound) -> {AnyOther,Bound}. copy_case_clauses([],Bound,AddSets) -> ReallyAdded = gb_sets:intersection(AddSets), {[],gb_sets:union(Bound,ReallyAdded)}; copy_case_clauses([{clause,Line,Match,Guard,Clauses}|T],Bound,AddSets) -> {NewMatch,MatchBinds} = copy(Match,Bound), {NewGuard,GuardBinds} = copy(Guard,MatchBinds), %% Really no new binds {NewClauses,AllBinds} = copy(Clauses,GuardBinds), %% To limit the setsizes, I subtract what I had before the case clause %% and add it in the end AddedBinds = gb_sets:subtract(AllBinds,Bound), {NewTail,ExportedBindings} = copy_case_clauses(T,Bound,[AddedBinds | AddSets]), {[{clause,Line,NewMatch,NewGuard,NewClauses}|NewTail],ExportedBindings}. copy_list([H|T],Bound) -> {C1,Bound1} = copy(H,Bound), {C2,Bound2} = copy_list(T,Bound1), {[C1|C2],Bound2}; copy_list([],Bound) -> {[],Bound}. transform_call(Type,_Line,[{'fun',Line2,{clauses, ClauseList}}],Bound) -> ms_clause_list(Line2, ClauseList,Type,Bound); transform_call(_Type,Line,_NoAbstractFun,_) -> throw({error,Line,?ERR_NOFUN}). % Fixup semicolons in guards ms_clause_expand({clause, Line, Parameters, Guard = [_,_|_], Body}) -> [ {clause, Line, Parameters, [X], Body} || X <- Guard ]; ms_clause_expand(_Other) -> false. ms_clause_list(Line,[H|T],Type,Bound) -> case ms_clause_expand(H) of NewHead when is_list(NewHead) -> ms_clause_list(Line,NewHead ++ T, Type, Bound); false -> {cons, Line, ms_clause(H, Type, Bound), ms_clause_list(Line, T, Type, Bound)} end; ms_clause_list(Line,[],_,_) -> {nil,Line}. ms_clause({clause, Line, Parameters, Guards, Body},Type,Bound) -> check_type(Line,Parameters,Type), {MSHead,Bindings} = transform_head(Parameters,Bound), MSGuards = transform_guards(Line, Guards, Bindings), MSBody = transform_body(Line,Body,Bindings), {tuple, Line, [MSHead,MSGuards,MSBody]}. check_type(_,[{var,_,_}],_) -> ok; check_type(_,[{tuple,_,_}],ets) -> ok; check_type(_,[{record,_,_,_}],ets) -> ok; check_type(_,[{cons,_,_,_}],dbg) -> ok; check_type(_,[{nil,_}],dbg) -> ok; check_type(Line0,[{match,_,{var,_,_},X}],Any) -> check_type(Line0,[X],Any); check_type(Line0,[{match,_,X,{var,_,_}}],Any) -> check_type(Line0,[X],Any); check_type(Line,_Type,ets) -> throw({error,Line,?ERR_ETS_HEAD}); check_type(Line,_,dbg) -> throw({error,Line,?ERR_DBG_HEAD}). -record(tgd,{ b, %Bindings p, %Part of spec eb %Error code base, 0 for guards, 100 for bodies }). transform_guards(Line,[],_Bindings) -> {nil,Line}; transform_guards(Line,[G],Bindings) -> B = #tgd{b = Bindings, p = guard, eb = ?ERROR_BASE_GUARD}, tg0(Line,G,B); transform_guards(Line,_,_) -> throw({error,Line,?ERR_SEMI_GUARD}). transform_body(Line,Body,Bindings) -> B = #tgd{b = Bindings, p = body, eb = ?ERROR_BASE_BODY}, tg0(Line,Body,B). guard_top_trans({call,Line0,{atom,Line1,OldTest},Params}) -> case old_bool_test(OldTest,length(Params)) of undefined -> {call,Line0,{atom,Line1,OldTest},Params}; Trans -> {call,Line0,{atom,Line1,Trans},Params} end; guard_top_trans(Else) -> Else. tg0(Line,[],_) -> {nil,Line}; tg0(Line,[H0|T],B) when B#tgd.p =:= guard -> H = guard_top_trans(H0), {cons,Line, tg(H,B), tg0(Line,T,B)}; tg0(Line,[H|T],B) -> {cons,Line, tg(H,B), tg0(Line,T,B)}. tg({match,Line,_,_},B) -> throw({error,Line,?ERR_GENMATCH+B#tgd.eb}); tg({op, Line, Operator, O1, O2}=Expr, B) -> case erl_eval:partial_eval(Expr) of Expr -> {tuple, Line, [{atom, Line, Operator}, tg(O1, B), tg(O2, B)]}; Value -> Value end; tg({op, Line, Operator, O1}=Expr, B) -> case erl_eval:partial_eval(Expr) of Expr -> {tuple, Line, [{atom, Line, Operator}, tg(O1, B)]}; Value -> Value end; tg({call, _Line, {atom, Line2, bindings},[]},_B) -> {atom, Line2, '$*'}; tg({call, _Line, {atom, Line2, object},[]},_B) -> {atom, Line2, '$_'}; tg({call, Line, {atom, _, is_record}=Call,[Object, {atom,Line3,RName}=R]},B) -> MSObject = tg(Object,B), RDefs = get_records(), case lists:keysearch(RName,1,RDefs) of {value, {RName, FieldList}} -> RSize = length(FieldList)+1, {tuple, Line, [Call, MSObject, R, {integer, Line3, RSize}]}; _ -> throw({error,Line3,{?ERR_GENBADREC+B#tgd.eb,RName}}) end; tg({call, Line, {atom, Line2, FunName},ParaList},B) -> case is_ms_function(FunName,length(ParaList), B#tgd.p) of true -> {tuple, Line, [{atom, Line2, FunName} | lists:map(fun(X) -> tg(X,B) end, ParaList)]}; _ -> throw({error,Line,{?ERR_GENLOCALCALL+B#tgd.eb, FunName,length(ParaList)}}) end; tg({call, Line, {remote,_,{atom,_,erlang},{atom, Line2, FunName}},ParaList}, B) -> L = length(ParaList), case is_imported_from_erlang(FunName,L,B#tgd.p) of true -> case is_operator(FunName,L,B#tgd.p) of false -> tg({call, Line, {atom, Line2, FunName},ParaList},B); true -> tg(list_to_tuple([op,Line2,FunName | ParaList]),B) end; _ -> throw({error,Line,{?ERR_GENREMOTECALL+B#tgd.eb,erlang, FunName,length(ParaList)}}) end; tg({call, Line, {remote,_,{atom,_,ModuleName}, {atom, _, FunName}},ParaList},B) -> throw({error,Line,{?ERR_GENREMOTECALL+B#tgd.eb,ModuleName,FunName,length(ParaList)}}); tg({cons,Line, H, T},B) -> {cons, Line, tg(H,B), tg(T,B)}; tg({nil, Line},_B) -> {nil, Line}; tg({tuple,Line,L},B) -> {tuple,Line,[{tuple,Line,lists:map(fun(X) -> tg(X,B) end, L)}]}; tg({integer,Line,I},_) -> {integer,Line,I}; tg({char,Line,C},_) -> {char,Line,C}; tg({float, Line,F},_) -> {float,Line,F}; tg({atom,Line,A},_) -> case atom_to_list(A) of [$$|_] -> {tuple, Line,[{atom, Line, 'const'},{atom,Line,A}]}; _ -> {atom,Line,A} end; tg({string,Line,S},_) -> {string,Line,S}; tg({var,Line,VarName},B) -> case lkup_bind(VarName, B#tgd.b) of undefined -> {tuple, Line,[{atom, Line, 'const'},{var,Line,VarName}]}; AtomName -> {atom, Line, AtomName} end; tg({record_field,Line,Object,RName,{atom,_Line1,KeyName}},B) -> RDefs = get_records(), case lists:keysearch(RName,1,RDefs) of {value, {RName, FieldList}} -> case lists:keysearch(KeyName,1, FieldList) of {value, {KeyName,Position,_}} -> NewObject = tg(Object,B), {tuple, Line, [{atom, Line, 'element'}, {integer, Line, Position}, NewObject]}; _ -> throw({error,Line,{?ERR_GENBADFIELD+B#tgd.eb, RName, KeyName}}) end; _ -> throw({error,Line,{?ERR_GENBADREC+B#tgd.eb,RName}}) end; tg({record,Line,RName,RFields},B) -> RDefs = get_records(), KeyList0 = lists:foldl(fun({record_field,_,{atom,_,Key},Value}, L) -> NV = tg(Value,B), [{Key,NV}|L]; ({record_field,_,{var,_,'_'},Value}, L) -> NV = tg(Value,B), [{{default},NV}|L]; (_,_) -> throw({error,Line, {?ERR_GENBADREC+B#tgd.eb, RName}}) end, [], RFields), DefValue = case lists:keysearch({default},1,KeyList0) of {value,{{default},OverriddenDefValue}} -> {true,OverriddenDefValue}; _ -> false end, KeyList = lists:keydelete({default},1,KeyList0), case lists:keysearch({default},1,KeyList) of {value,{{default},_}} -> throw({error,Line,{?ERR_GENMULTIFIELD+B#tgd.eb,RName,'_'}}); _ -> ok end, case lists:keysearch(RName,1,RDefs) of {value, {RName, FieldList0}} -> FieldList1 = lists:foldl( fun({FN,_,Def},Acc) -> El = case lists:keysearch(FN,1,KeyList) of {value, {FN, X0}} -> X0; _ -> case DefValue of {true,Overridden} -> Overridden; false -> Def end end, [El | Acc] end, [], FieldList0), check_multi_field(RName,Line,KeyList, ?ERR_GENMULTIFIELD+B#tgd.eb), check_undef_field(RName,Line,KeyList,FieldList0, ?ERR_GENBADFIELD+B#tgd.eb), {tuple,Line,[{tuple,Line,[{atom,Line,RName}|FieldList1]}]}; _ -> throw({error,Line,{?ERR_GENBADREC+B#tgd.eb,RName}}) end; tg({record_index,Line,RName,{atom,Line2,KeyName}},B) -> RDefs = get_records(), case lists:keysearch(RName,1,RDefs) of {value, {RName, FieldList}} -> case lists:keysearch(KeyName,1, FieldList) of {value, {KeyName,Position,_}} -> {integer, Line2, Position}; _ -> throw({error,Line2,{?ERR_GENBADFIELD+B#tgd.eb, RName, KeyName}}) end; _ -> throw({error,Line,{?ERR_GENBADREC+B#tgd.eb,RName}}) end; tg({record,Line,{var,Line2,_VName}=AVName, RName,RFields},B) -> RDefs = get_records(), MSVName = tg(AVName,B), KeyList = lists:foldl(fun({record_field,_,{atom,_,Key},Value}, L) -> NV = tg(Value,B), [{Key,NV}|L]; (_,_) -> throw({error,Line,?ERR_HEADBADREC}) end, [], RFields), case lists:keysearch(RName,1,RDefs) of {value, {RName, FieldList0}} -> FieldList1 = lists:foldl( fun({FN,Pos,_},Acc) -> El = case lists:keysearch(FN,1,KeyList) of {value, {FN, X0}} -> X0; _ -> {tuple, Line2, [{atom, Line2, element}, {integer, Line2, Pos}, MSVName]} end, [El | Acc] end, [], FieldList0), check_multi_field(RName,Line,KeyList, ?ERR_GENMULTIFIELD+B#tgd.eb), check_undef_field(RName,Line,KeyList,FieldList0, ?ERR_GENBADFIELD+B#tgd.eb), {tuple,Line,[{tuple,Line,[{atom,Line,RName}|FieldList1]}]}; _ -> throw({error,Line,{?ERR_GENBADREC+B#tgd.eb,RName}}) end; tg({bin_element,_Line0,{var, Line, A},_,_} = Whole,B) -> case lkup_bind(A, B#tgd.b) of undefined -> Whole; % exists in environment hopefully _AtomName -> throw({error,Line,{?ERR_GENBINCONSTRUCT+B#tgd.eb,A}}) end; tg(default,_B) -> default; tg({bin_element,Line,X,Y,Z},B) -> {bin_element, Line, tg(X,B), tg(Y,B), Z}; tg({bin,Line,List},B) -> {bin,Line,[tg(X,B) || X <- List]}; tg(T,B) when is_tuple(T), tuple_size(T) >= 2 -> Element = element(1,T), Line = element(2,T), throw({error,Line,{?ERR_GENELEMENT+B#tgd.eb, translate_language_element(Element)}}); tg(Other,B) -> Element = io_lib:format("unknown element ~tw", [Other]), throw({error,unknown,{?ERR_GENELEMENT+B#tgd.eb,Element}}). transform_head([V],OuterBound) -> Bind = cre_bind(), {NewV,NewBind} = toplevel_head_match(V,Bind,OuterBound), th(NewV,NewBind,OuterBound). toplevel_head_match({match,_,{var,Line,VName},Expr},B,OB) -> warn_var_clash(Line,VName,OB), {Expr,new_bind({VName,'$_'},B)}; toplevel_head_match({match,_,Expr,{var,Line,VName}},B,OB) -> warn_var_clash(Line,VName,OB), {Expr,new_bind({VName,'$_'},B)}; toplevel_head_match(Other,B,_OB) -> {Other,B}. th({record,Line,RName,RFields},B,OB) -> % youch... RDefs = get_records(), {KeyList0,NewB} = lists:foldl(fun({record_field,_,{atom,_,Key},Value}, {L,B0}) -> {NV,B1} = th(Value,B0,OB), {[{Key,NV}|L],B1}; ({record_field,_,{var,_,'_'},Value}, {L,B0}) -> {NV,B1} = th(Value,B0,OB), {[{{default},NV}|L],B1}; (_,_) -> throw({error,Line,{?ERR_HEADBADREC, RName}}) end, {[],B}, RFields), DefValue = case lists:keysearch({default},1,KeyList0) of {value,{{default},OverriddenDefValue}} -> OverriddenDefValue; _ -> {atom,Line,'_'} end, KeyList = lists:keydelete({default},1,KeyList0), case lists:keysearch({default},1,KeyList) of {value,{{default},_}} -> throw({error,Line,{?ERR_HEADMULTIFIELD,RName,'_'}}); _ -> ok end, case lists:keysearch(RName,1,RDefs) of {value, {RName, FieldList0}} -> FieldList1 = lists:foldl( fun({FN,_,_},Acc) -> El = case lists:keysearch(FN,1,KeyList) of {value, {FN, X0}} -> X0; _ -> DefValue end, [El | Acc] end, [], FieldList0), check_multi_field(RName,Line,KeyList, ?ERR_HEADMULTIFIELD), check_undef_field(RName,Line,KeyList,FieldList0, ?ERR_HEADBADFIELD), {{tuple,Line,[{atom,Line,RName}|FieldList1]},NewB}; _ -> throw({error,Line,{?ERR_HEADBADREC,RName}}) end; th({match,Line,_,_},_,_) -> throw({error,Line,?ERR_HEADMATCH}); th({atom,Line,A},B,_OB) -> case atom_to_list(A) of [$$|NL] -> case (catch list_to_integer(NL)) of N when is_integer(N) -> throw({error,Line,{?ERR_HEADDOLLARATOM,A}}); _ -> {{atom,Line,A},B} end; _ -> {{atom,Line,A},B} end; th({bin_element,_Line0,{var, Line, A},_,_},_,_) -> throw({error,Line,{?ERR_HEADBINMATCH,A}}); th({var,Line,Name},B,OB) -> warn_var_clash(Line,Name,OB), case lkup_bind(Name,B) of undefined -> NewB = new_bind(Name,B), {{atom,Line,lkup_bind(Name,NewB)},NewB}; Trans -> {{atom,Line,Trans},B} end; th([H|T],B,OB) -> {NH,NB} = th(H,B,OB), {NT,NNB} = th(T,NB,OB), {[NH|NT],NNB}; th(T,B,OB) when is_tuple(T) -> {L,NB} = th(tuple_to_list(T),B,OB), {list_to_tuple(L),NB}; th(Nonstruct,B,_OB) -> {Nonstruct,B}. warn_var_clash(Anno,Name,OuterBound) -> case gb_sets:is_member(Name,OuterBound) of true -> Line = erl_anno:line(Anno), add_warning(Line,{?WARN_SHADOW_VAR,Name}); _ -> ok end. %% Could be more efficient... check_multi_field(_, _, [], _) -> ok; check_multi_field(RName, Line, [{Key,_}|T], ErrCode) -> case lists:keymember(Key,1,T) of true -> throw({error,Line,{ErrCode,RName,Key}}); false -> check_multi_field(RName, Line, T, ErrCode) end. check_undef_field(_, _, [], _, _) -> ok; check_undef_field(RName, Line, [{Key,_}|T], FieldList, ErrCode) -> case lists:keymember(Key, 1, FieldList) of true -> check_undef_field(RName, Line, T, FieldList, ErrCode); false -> throw({error,Line,{ErrCode,RName,Key}}) end. cre_bind() -> {1,[{'_','_'}]}. lkup_bind(Name,{_,List}) -> case lists:keysearch(Name,1,List) of {value, {Name, Trans}} -> Trans; _ -> undefined end. new_bind({Name,Trans},{Next,L}) -> {Next,[{Name,Trans}|L]}; new_bind(Name,{Next,L}) -> Trans = list_to_atom([$$|integer_to_list(Next)]), {Next+1,[{Name,Trans}|L]}. translate_language_element(Atom) -> Transtab = [ {lc,"list comprehension"}, {bc,"binary comprehension"}, {block, "begin/end block"}, {'if', "if"}, {'case', "case"}, {'receive', "receive"}, {'try', "try"}, {'catch', "catch"}, {'match', "match (=)"}, {remote, "external function call"} ], case lists:keysearch(Atom,1,Transtab) of {value,{Atom, String}} -> String; _ -> atom_to_list(Atom) end. old_bool_test(atom,1) -> is_atom; old_bool_test(float,1) -> is_float; old_bool_test(integer,1) -> is_integer; old_bool_test(list,1) -> is_list; old_bool_test(number,1) -> is_number; old_bool_test(pid,1) -> is_pid; old_bool_test(port,1) -> is_port; old_bool_test(reference,1) -> is_reference; old_bool_test(tuple,1) -> is_tuple; old_bool_test(binary,1) -> is_binary; old_bool_test(function,1) -> is_function; old_bool_test(record,2) -> is_record; old_bool_test(_,_) -> undefined. bool_test(is_atom,1) -> true; bool_test(is_float,1) -> true; bool_test(is_integer,1) -> true; bool_test(is_list,1) -> true; bool_test(is_number,1) -> true; bool_test(is_pid,1) -> true; bool_test(is_port,1) -> true; bool_test(is_reference,1) -> true; bool_test(is_tuple,1) -> true; bool_test(is_map,1) -> true; bool_test(is_map_key, 2) -> true; bool_test(is_binary,1) -> true; bool_test(is_function,1) -> true; bool_test(is_record,2) -> true; bool_test(is_seq_trace,0) -> true; bool_test(_,_) -> false. real_guard_function(abs,1) -> true; real_guard_function(element,2) -> true; real_guard_function(hd,1) -> true; real_guard_function(length,1) -> true; real_guard_function(node,0) -> true; real_guard_function(node,1) -> true; real_guard_function(round,1) -> true; real_guard_function(size,1) -> true; real_guard_function(bit_size,1) -> true; real_guard_function(map_size,1) -> true; real_guard_function(map_get,2) -> true; real_guard_function(tl,1) -> true; real_guard_function(trunc,1) -> true; real_guard_function(self,0) -> true; real_guard_function(float,1) -> true; real_guard_function(_,_) -> false. pseudo_guard_function(get_tcw,0) -> true; pseudo_guard_function(_,_) -> false. guard_function(X,A) -> real_guard_function(X,A) or pseudo_guard_function(X,A). action_function(set_seq_token,2) -> true; action_function(get_seq_token,0) -> true; action_function(message,1) -> true; action_function(return_trace,0) -> true; action_function(exception_trace,0) -> true; action_function(process_dump,0) -> true; action_function(enable_trace,1) -> true; action_function(enable_trace,2) -> true; action_function(disable_trace,1) -> true; action_function(disable_trace,2) -> true; action_function(display,1) -> true; action_function(caller,0) -> true; action_function(set_tcw,1) -> true; action_function(silent,1) -> true; action_function(trace,2) -> true; action_function(trace,3) -> true; action_function(_,_) -> false. bool_operator('and',2) -> true; bool_operator('or',2) -> true; bool_operator('xor',2) -> true; bool_operator('not',1) -> true; bool_operator('andalso',2) -> true; bool_operator('orelse',2) -> true; bool_operator(_,_) -> false. arith_operator('+',1) -> true; arith_operator('+',2) -> true; arith_operator('-',1) -> true; arith_operator('-',2) -> true; arith_operator('*',2) -> true; arith_operator('/',2) -> true; arith_operator('div',2) -> true; arith_operator('rem',2) -> true; arith_operator('band',2) -> true; arith_operator('bor',2) -> true; arith_operator('bxor',2) -> true; arith_operator('bnot',1) -> true; arith_operator('bsl',2) -> true; arith_operator('bsr',2) -> true; arith_operator(_,_) -> false. cmp_operator('>',2) -> true; cmp_operator('>=',2) -> true; cmp_operator('<',2) -> true; cmp_operator('=<',2) -> true; cmp_operator('==',2) -> true; cmp_operator('=:=',2) -> true; cmp_operator('/=',2) -> true; cmp_operator('=/=',2) -> true; cmp_operator(_,_) -> false. is_operator(X,A,_) -> bool_operator(X,A) or arith_operator(X,A) or cmp_operator(X,A). is_imported_from_erlang(X,A,_) -> real_guard_function(X,A) or bool_test(X,A) or bool_operator(X,A) or arith_operator(X,A) or cmp_operator(X,A). is_ms_function(X,A,body) -> action_function(X,A) or guard_function(X,A) or bool_test(X,A); is_ms_function(X,A,guard) -> guard_function(X,A) or bool_test(X,A). fixup_environment(L,B) when is_list(L) -> lists:map(fun(X) -> fixup_environment(X,B) end, L); fixup_environment({var,Line,Name},B) -> case lists:keysearch(Name,1,B) of {value,{Name,Value}} -> freeze(Line,Value); _ -> throw({error,Line,{?ERR_UNBOUND_VARIABLE,atom_to_list(Name)}}) end; fixup_environment(T,B) when is_tuple(T) -> list_to_tuple( lists:map(fun(X) -> fixup_environment(X,B) end, tuple_to_list(T))); fixup_environment(Other,_B) -> Other. freeze(Line,Term) -> {frozen,Line,Term}. %% Most of this is bluntly stolen from erl_parse. normalise({frozen,_,Term}) -> Term; normalise({char,_,C}) -> C; normalise({integer,_,I}) -> I; normalise({float,_,F}) -> F; normalise({atom,_,A}) -> A; normalise({string,_,S}) -> S; normalise({nil,_}) -> []; normalise({bin,_,Fs}) -> {value, B, _} = eval_bits:expr_grp(Fs, [], fun(E, _) -> {value, normalise(E), []} end, [], true), B; normalise({cons,_,Head,Tail}) -> [normalise(Head)|normalise(Tail)]; normalise({op,_,'++',A,B}) -> normalise(A) ++ normalise(B); normalise({tuple,_,Args}) -> list_to_tuple(normalise_list(Args)); normalise({map,_,Pairs0}) -> Pairs1 = lists:map(fun ({map_field_exact,_,K,V}) -> {normalise(K),normalise(V)} end, Pairs0), maps:from_list(Pairs1); %% Special case for unary +/-. normalise({op,_,'+',{char,_,I}}) -> I; normalise({op,_,'+',{integer,_,I}}) -> I; normalise({op,_,'+',{float,_,F}}) -> F; normalise({op,_,'-',{char,_,I}}) -> -I; % Weird, but compatible! normalise({op,_,'-',{integer,_,I}}) -> -I; normalise({op,_,'-',{float,_,F}}) -> -F. normalise_list([H|T]) -> [normalise(H)|normalise_list(T)]; normalise_list([]) -> [].