%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 2004-2009. All Rights Reserved.
%%
%% 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 online 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.
%%
%% %CopyrightEnd%
%%
%%
-module(ic_erl_template).
-export([do_gen/3, emit_header/3]).
-import(ic_codegen, [emit/2, emit/3, nl/1]).
-include("icforms.hrl").
-include("ic.hrl").
-include_lib("stdlib/include/erl_compile.hrl").
-define(TAB, " ").
-define(TAB2, "% ").
-define(TEMPLATE_1_A,
"%%----------------------------------------------------------------------\n"
"%% <LICENSE>\n"
"%% \n"
"%% $Id$\n"
"%%\n"
"%%----------------------------------------------------------------------\n"
"%% Module : ~s.erl\n"
"%% \n"
"%% Source : ~s\n"
"%% \n"
"%% Description : \n"
"%% \n"
"%% Creation date: ~s\n"
"%%\n"
"%%----------------------------------------------------------------------\n"
"-module(~p).\n\n").
-define(TEMPLATE_1_B,
"%%----------------------------------------------------------------------\n"
"%% Internal Exports\n"
"%%----------------------------------------------------------------------\n"
"-export([init/1,\n"
" terminate/2,\n"
" code_change/3,\n"
" handle_info/2]).\n\n"
"%%----------------------------------------------------------------------\n"
"%% Include Files\n"
"%%----------------------------------------------------------------------\n"
"\n\n"
"%%----------------------------------------------------------------------\n"
"%% Macros\n"
"%%----------------------------------------------------------------------\n"
"\n\n"
"%%----------------------------------------------------------------------\n"
"%% Records\n"
"%%----------------------------------------------------------------------\n"
"-record(state, {}).\n\n"
"%%======================================================================\n"
"%% API Functions\n"
"%%======================================================================\n").
-define(TEMPLATE_1_C,
"%%======================================================================\n"
"%% Internal Functions\n"
"%%======================================================================\n"
"%%----------------------------------------------------------------------\n"
"%% Function : init/1\n"
"%% Arguments : Env = term()\n"
"%% Returns : {ok, State} |\n"
"%% {ok, State, Timeout} |\n"
"%% ignore |\n"
"%% {stop, Reason}\n"
"%% Raises : -\n"
"%% Description: Initiates the server\n"
"%%----------------------------------------------------------------------\n"
"init(_Env) ->\n"
"\t{ok, #state{}}.\n\n\n"
"%%----------------------------------------------------------------------\n"
"%% Function : terminate/2\n"
"%% Arguments : Reason = normal | shutdown | term()\n"
"%% State = term()\n"
"%% Returns : ok\n"
"%% Raises : -\n"
"%% Description: Invoked when the object is terminating.\n"
"%%----------------------------------------------------------------------\n"
"terminate(_Reason, _State) ->\n"
"\tok.\n\n\n"
"%%----------------------------------------------------------------------\n"
"%% Function : code_change/3\n"
"%% Arguments : OldVsn = undefined | term()\n"
"%% State = NewState = term()\n"
"%% Extra = term()\n"
"%% Returns : {ok, NewState}\n"
"%% Raises : -\n"
"%% Description: Invoked when the object should update its internal state\n"
"%% due to code replacement.\n"
"%%----------------------------------------------------------------------\n"
"code_change(_OldVsn, State, _Extra) ->\n"
"\t{ok, State}.\n\n\n"
"%%----------------------------------------------------------------------\n"
"%% Function : handle_info/2\n"
"%% Arguments : Info = normal | shutdown | term()\n"
"%% State = NewState = term()\n"
"%% Returns : {noreply, NewState} |\n"
"%% {noreply, NewState, Timeout} |\n"
"%% {stop, Reason, NewState}\n"
"%% Raises : -\n"
"%% Description: Invoked when, for example, the server traps exits.\n"
"%%----------------------------------------------------------------------\n"
"handle_info(_Info, State) ->\n"
"\t{noreply, State}.\n\n\n").
-define(TEMPLATE_2_A,
"%%% #0. BASIC INFORMATION\n"
"%%% ----------------------------------------------------------------------\n"
"%%% %CCaseFile : ~s.erl %\n"
"%%% Author : \n"
"%%% Description : \n"
"%%%\n"
"%%% Modules used: \n"
"%%%\n"
"%%%\n"
"%%% ----------------------------------------------------------------------\n"
"-module(~p).\n"
"-author('unknown').\n"
"-id('').\n"
"-vsn('').\n"
"-date('~s').\n\n"
"%%% ----------------------------------------------------------------------\n"
"%%% Template Id: <ID>\n"
"%%%\n"
"%%% #Copyright (C) 2004\n"
"%%% by <COMPANY>\n"
"%%% <ADDRESS>\n"
"%%% <OTHER INFORMATION>\n"
"%%% \n"
"%%% <LICENSE>\n"
"%%% \n"
"%%% \n"
"%%% ----------------------------------------------------------------------\n"
"%%% #1. REVISION LOG\n"
"%%% ----------------------------------------------------------------------\n"
"%%% Rev Date Name What\n"
"%%% ----- ------- -------- --------------------------\n"
"%%% \n"
"%%% ----------------------------------------------------------------------\n"
"%%%\n"
"%%% \n"
"%%% #2. EXPORT LISTS\n"
"%%% ----------------------------------------------------------------------\n"
"%%% #2.1 EXPORTED INTERFACE FUNCTIONS\n"
"%%% ----------------------------------------------------------------------\n").
-define(TEMPLATE_2_B,
"%%% ----------------------------------------------------------------------\n"
"%%% #2.2 EXPORTED INTERNAL FUNCTIONS\n"
"%%% ----------------------------------------------------------------------\n"
"-export([init/1,\n"
" terminate/2,\n"
" code_change/3,\n"
" handle_info/2]).\n\n"
"%%% ----------------------------------------------------------------------\n"
"%%% #2.3 INCLUDE FILES\n"
"%%% ----------------------------------------------------------------------\n"
"\n\n"
"%%% ----------------------------------------------------------------------\n"
"%%% #2.4 MACROS\n"
"%%% ----------------------------------------------------------------------\n"
"\n\n"
"%%% ----------------------------------------------------------------------\n"
"%%% #2.5 RECORDS\n"
"%%% ----------------------------------------------------------------------\n"
"-record(state, {}).\n\n"
"%%% ----------------------------------------------------------------------\n"
"%%% #3. CODE\n"
"%%% #---------------------------------------------------------------------\n"
"%%% #3.1 CODE FOR EXPORTED INTERFACE FUNCTIONS\n"
"%%% #---------------------------------------------------------------------\n").
-define(TEMPLATE_2_C,
"%%% ----------------------------------------------------------------------\n"
"%%% #3.3 CODE FOR INTERNAL FUNCTIONS\n"
"%%% ----------------------------------------------------------------------\n"
"%%% ----------------------------------------------------------------------\n"
"%%% # init/1\n"
"%%% Input : Env = term()\n"
"%%% Output : {ok, State} |\n"
"%%% {ok, State, Timeout} |\n"
"%%% ignore |\n"
"%%% {stop, Reason}\n"
"%%% Exceptions : -\n"
"%%% Description: Initiates the server\n"
"%%% ----------------------------------------------------------------------\n"
"init(_Env) ->\n"
"\t{ok, #state{}}.\n\n\n"
"%%% ----------------------------------------------------------------------\n"
"%%% # terminate/2\n"
"%%% Input : Reason = normal | shutdown | term()\n"
"%%% State = term()\n"
"%%% Output : ok\n"
"%%% Exceptions : -\n"
"%%% Description: Invoked when the object is terminating.\n"
"%%% ----------------------------------------------------------------------\n"
"terminate(_Reason, _State) ->\n"
"\tok.\n\n\n"
"%%% ----------------------------------------------------------------------\n"
"%%% # code_change/3\n"
"%%% Input : OldVsn = undefined | term()\n"
"%%% State = NewState = term()\n"
"%%% Extra = term()\n"
"%%% Output : {ok, NewState}\n"
"%%% Exceptions : -\n"
"%%% Description: Invoked when the object should update its internal state\n"
"%%% due to code replacement.\n"
"%%% ----------------------------------------------------------------------\n"
"code_change(_OldVsn, State, _Extra) ->\n"
"\t{ok, State}.\n\n\n"
"%%% ----------------------------------------------------------------------\n"
"%%% # handle_info/2\n"
"%%% Input : Info = normal | shutdown | term()\n"
"%%% State = NewState = term()\n"
"%%% Output : {noreply, NewState} |\n"
"%%% {noreply, NewState, Timeout} |\n"
"%%% {stop, Reason, NewState}\n"
"%%% Exceptions : -\n"
"%%% Description: Invoked when, for example, the server traps exits.\n"
"%%% ----------------------------------------------------------------------\n"
"handle_info(_Info, State) ->\n"
"\t{noreply, State}.\n\n\n"
"%%% ----------------------------------------------------------------------\n"
"%%% #4 CODE FOR TEMPORARY CORRECTIONS\n"
"%%% ----------------------------------------------------------------------\n\n").
%%------------------------------------------------------------
%%
%% Generate the client side Erlang stubs.
%%
%% Each module is generated to a separate file.
%%
%% Export declarations for all interface functions must be
%% generated. Each function then needs to generate a function head and
%% a body. IDL parameters must be converted into Erlang parameters
%% (variables, capitalised) and a type signature list must be
%% generated (for later encode/decode).
%%
%%------------------------------------------------------------
do_gen(G, _File, Form) ->
gen_head(G, [], Form),
gen(G, [], Form).
gen(G, N, [X|Xs]) when is_record(X, preproc) ->
NewG = ic:handle_preproc(G, N, X#preproc.cat, X),
gen(NewG, N, Xs);
gen(G, N, [X|Xs]) when is_record(X, module) ->
G2 = ic_file:filename_push(G, N, X, erlang_template_no_gen),
N2 = [ic_forms:get_id2(X) | N],
gen_head(G2, N2, X),
gen(G2, N2, ic_forms:get_body(X)),
G3 = ic_file:filename_pop(G2, erlang_template_no_gen),
gen(G3, N, Xs);
gen(G, N, [X|Xs]) when is_record(X, interface) ->
G2 = ic_file:filename_push(G, N, X, erlang_template),
N2 = [ic_forms:get_id2(X) | N],
gen_head(G2, N2, X),
gen(G2, N2, ic_forms:get_body(X)),
lists:foreach(fun({_Name, Body}) -> gen(G2, N2, Body) end,
X#interface.inherit_body),
Fd = ic_genobj:stubfiled(G2),
case get_template_version(G2) of
?IC_FLAG_TEMPLATE_2 ->
emit(Fd, ?TEMPLATE_2_C, []);
_ ->
emit(Fd, ?TEMPLATE_1_C, [])
end,
G3 = ic_file:filename_pop(G2, erlang_template),
gen(G3, N, Xs);
gen(G, N, [X|Xs]) when is_record(X, op) ->
{Name, InArgNames, OutArgNames, Reply} = extract_info(X),
emit_function(G, N, X, ic_genobj:is_stubfile_open(G),
ic_forms:is_oneway(X), Name, InArgNames, OutArgNames, Reply),
gen(G, N, Xs);
gen(G, N, [X|Xs]) when is_record(X, attr) ->
emit_attr(G, N, X, ic_genobj:is_stubfile_open(G), fun emit_function/9),
gen(G, N, Xs);
gen(G, N, [_X|Xs]) ->
gen(G, N, Xs);
gen(_G, _N, []) ->
ok.
%% Module Header
emit_header(G, Fd, Name) ->
Date = get_date(),
case get_template_version(G) of
?IC_FLAG_TEMPLATE_2 ->
emit(Fd, ?TEMPLATE_2_A, [Name, list_to_atom(Name), Date]);
_ ->
IDLFile = ic_genobj:idlfile(G),
emit(Fd, ?TEMPLATE_1_A, [Name, IDLFile, Date, list_to_atom(Name)])
end.
emit_attr(G, N, X, Open, F) ->
XX = #id_of{type=X},
lists:foreach(fun(Id) ->
X2 = XX#id_of{id=Id},
IsOneWay = ic_forms:is_oneway(X2),
{Get, Set} = mk_attr_func_names(N, ic_forms:get_id(Id)),
F(G, N, X2, Open, IsOneWay, Get, [], [],
[{ic_util:mk_var(ic_forms:get_id(Id)),
ic_forms:get_tk(X)}]),
case X#attr.readonly of
{readonly, _} ->
ok;
_ ->
F(G, N, X2, Open, IsOneWay, Set,
[{ic_util:mk_var(ic_forms:get_id(Id)),
ic_forms:get_tk(X)}], [], ["ok"])
end
end, ic_forms:get_idlist(X)).
%% The automaticly generated get and set operation names for an
%% attribute.
mk_attr_func_names(_Scope, Name) ->
{"_get_" ++ Name, "_set_" ++ Name}.
extract_info(X) when is_record(X, op) ->
Name = ic_forms:get_id2(X),
InArgs = ic:filter_params([in,inout], X#op.params),
OutArgs = ic:filter_params([out,inout], X#op.params),
Reply = case ic_forms:get_tk(X) of
tk_void ->
["ok"];
Type ->
[{"OE_Reply", Type}]
end,
InArgsTypeList =
[{ic_util:mk_var(ic_forms:get_id(InArg#param.id)),
ic_forms:get_tk(InArg)} || InArg <- InArgs ],
OutArgsTypeList =
[{ic_util:mk_var(ic_forms:get_id(OutArg#param.id)),
ic_forms:get_tk(OutArg)} || OutArg <- OutArgs ],
{Name, InArgsTypeList, OutArgsTypeList, Reply}.
get_template_version(G) ->
case ic_options:get_opt(G, flags) of
Flags when is_integer(Flags) ->
case ?IC_FLAG_TEST(Flags, ?IC_FLAG_TEMPLATE_2) of
true ->
?IC_FLAG_TEMPLATE_2;
false ->
?IC_FLAG_TEMPLATE_1
end;
_ ->
?IC_FLAG_TEMPLATE_1
end.
get_date() ->
{{Y,M,D}, _} = calendar:now_to_datetime(now()),
if
M < 10, D < 10 ->
lists:concat([Y, "-0", M, "-0",D]);
M < 10 ->
lists:concat([Y, "-0", M, "-", D]);
D < 10 ->
lists:concat([Y, "-", M, "-0", D]);
true ->
lists:concat([Y, "-", M, "-", D])
end.
%%------------------------------------------------------------
%%
%% Export stuff
%%
%% Gathering of all names that should be exported from a stub
%% file.
%%
gen_head_special(G, N, X) when is_record(X, interface) ->
Fd = ic_genobj:stubfiled(G),
lists:foreach(fun({_Name, Body}) ->
ic_codegen:export(Fd, exp_top(G, N, Body, []))
end, X#interface.inherit_body),
nl(Fd),
ok;
gen_head_special(_G, _N, _X) ->
ok.
%% Generate all export declarations
gen_head(G, N, X) ->
case ic_genobj:is_stubfile_open(G) of
true ->
Fd = ic_genobj:stubfiled(G),
ic_codegen:export(Fd, exp_top(G, N, X, [])),
gen_head_special(G, N, X),
case get_template_version(G) of
?IC_FLAG_TEMPLATE_2 ->
emit(Fd, ?TEMPLATE_2_B, []);
_ ->
emit(Fd, ?TEMPLATE_1_B, [])
end;
false ->
ok
end.
exp_top(_G, _N, X, Acc) when element(1, X) == preproc ->
Acc;
exp_top(G, N, L, Acc) when is_list(L) ->
exp_list(G, N, L, Acc);
exp_top(G, N, M, Acc) when is_record(M, module) ->
exp_list(G, N, ic_forms:get_body(M), Acc);
exp_top(G, N, I, Acc) when is_record(I, interface) ->
exp_list(G, N, ic_forms:get_body(I), Acc);
exp_top(G, N, X, Acc) ->
exp3(G, N, X, Acc).
exp3(G, N, Op, Acc) when is_record(Op, op) ->
FuncName = ic_forms:get_id(Op#op.id),
Arity = length(ic:filter_params([in, inout], Op#op.params)) + 1 +
count_extras(G, N, Op),
[{FuncName, Arity} | Acc];
exp3(G, N, A, Acc) when is_record(A, attr) ->
Extra = count_extras(G, N, A),
lists:foldr(fun(Id, Acc2) ->
{Get, Set} = mk_attr_func_names([], ic_forms:get_id(Id)),
case A#attr.readonly of
{readonly, _} ->
[{Get, 1 + Extra} | Acc2];
_ ->
[{Get, 1 + Extra}, {Set, 2 + Extra} | Acc2]
end
end, Acc, ic_forms:get_idlist(A));
exp3(_G, _N, _X, Acc) ->
Acc.
exp_list(G, N, L, OrigAcc) ->
lists:foldr(fun(X, Acc) ->
exp3(G, N, X, Acc)
end, OrigAcc, L).
count_extras(G, N, Op) ->
case {use_this(G, N, Op), use_from(G, N, Op)} of
{[], []} ->
0;
{[], _} ->
1;
{_, []} ->
1;
_ ->
2
end.
%%------------------------------------------------------------
%%
%% Emit stuff
%%
%% Low level generation primitives
%%
emit_function(_G, _N, _X, false, _, _, _, _, _) ->
ok;
emit_function(G, N, X, true, false, Name, InArgs, OutArgs, Reply) ->
Fd = ic_genobj:stubfiled(G),
This = use_this(G, N, Name),
From = use_from(G, N, Name),
State = ["State"],
Vers = get_template_version(G),
case OutArgs of
[] ->
ReplyString = create_string(Reply),
emit_function_header(G, Fd, X, N, Name, create_extra(This, From, Vers),
InArgs, length(InArgs), OutArgs, Reply,
ReplyString, Vers),
emit(Fd, "~p(~s) ->\n\t{reply, ~s, State}.\n\n",
[ic_util:to_atom(Name), create_string(This ++ From ++ State ++ InArgs),
ReplyString]);
_ ->
ReplyString = "{" ++ create_string(Reply ++ OutArgs) ++ "}",
emit_function_header(G, Fd, X, N, Name, create_extra(This, From, Vers),
InArgs, length(InArgs), OutArgs, Reply,
ReplyString, Vers),
emit(Fd, "~p(~s) ->\n\t{reply, ~s, State}.\n\n",
[ic_util:to_atom(Name), create_string(This ++ From ++ State ++ InArgs),
ReplyString])
end;
emit_function(G, N, X, true, true, Name, InArgs, _OutArgs, _Reply) ->
Fd = ic_genobj:stubfiled(G),
This = use_this(G, N, Name),
State = ["State"],
Vers = get_template_version(G),
emit_function_header(G, Fd, X, N, Name, create_extra(This, [], Vers),
InArgs, length(InArgs), "", "", "", Vers),
emit(Fd, "~p(~s) ->\n\t{noreply, State}.\n\n",
[ic_util:to_atom(Name), create_string(This ++ State ++ InArgs)]).
create_string([]) ->
"";
create_string([{Name, _Type}|T]) ->
Name ++ create_string2(T);
create_string([Name|T]) ->
Name ++ create_string2(T).
create_string2([{Name, _Type}|T]) ->
", " ++ Name ++ create_string2(T);
create_string2([Name|T]) ->
", " ++ Name ++ create_string2(T);
create_string2([]) ->
"".
create_extra([], [], _Vers) ->
{"State - term()", 1};
create_extra([], _From, ?IC_FLAG_TEMPLATE_2) ->
{"OE_From - term()\n%%% " ++ ?TAB ++ "State - term()", 2};
create_extra([], _From, _Vers) ->
{"OE_From - term()\n%% " ++ ?TAB ++ "State - term()", 2};
create_extra(_This, [], ?IC_FLAG_TEMPLATE_2) ->
{"OE_This - #objref{} (i.e., self())\n%%% " ++ ?TAB ++ "State - term()", 2};
create_extra(_This, [], _Vers) ->
{"OE_This - #objref{} (i.e., self())\n%% " ++ ?TAB ++ "State - term()", 2};
create_extra(_This, _From, ?IC_FLAG_TEMPLATE_2) ->
{"OE_This - #objref{} (i.e., self())\n%%% " ++ ?TAB ++
"OE_From - term()\n%%% " ++ ?TAB ++ "State - term()", 3};
create_extra(_This, _From, _Vers) ->
{"OE_This - #objref{} (i.e., self())\n%% " ++ ?TAB ++
"OE_From - term()\n%% " ++ ?TAB ++ "State - term()", 3}.
use_this(G, N, OpName) ->
FullOp = ic_util:to_colon([OpName|N]),
FullIntf = ic_util:to_colon(N),
case {ic_options:get_opt(G, {this, FullIntf}),
ic_options:get_opt(G, {this, FullOp}),
ic_options:get_opt(G, {this, true})} of
{_, force_false, _} ->
[];
{force_false, false, _} ->
[];
{false, false, false} ->
[];
_ ->
["OE_This"]
end.
use_from(G, N, OpName) ->
FullOp = ic_util:to_colon([OpName|N]),
FullIntf = ic_util:to_colon(N),
case {ic_options:get_opt(G, {from, FullIntf}),
ic_options:get_opt(G, {from, FullOp}),
ic_options:get_opt(G, {from, true})} of
{_, force_false, _} ->
[];
{force_false, false, _} ->
[];
{false, false, false} ->
[];
_ ->
["OE_From"]
end.
emit_function_header(G, Fd, X, N, Name, {Extra, ExtraNo}, InP, Arity, OutP,
Reply, ReplyString, ?IC_FLAG_TEMPLATE_2) ->
emit(Fd,
"%%% ----------------------------------------------------------------------\n"
"%%% # ~p/~p\n"
"%%% Input : ~s\n",
[ic_util:to_atom(Name), (ExtraNo+Arity), Extra]),
ic_code:type_expand_all(G, N, X, Fd, ?TAB2, InP),
case Reply of
["ok"] ->
emit(Fd, "%%% Output : ReturnValue = ~s\n", [ReplyString]);
_ ->
emit(Fd, "%%% Output : ReturnValue = ~s\n", [ReplyString]),
ic_code:type_expand_all(G, N, X, Fd, "% ", Reply)
end,
ic_code:type_expand_all(G, N, X, Fd, ?TAB2, OutP),
emit(Fd,
"%%% Exceptions : ~s\n"
"%%% Description: \n"
"%%% ----------------------------------------------------------------------\n",
[get_raises(X, ?IC_FLAG_TEMPLATE_2)]);
emit_function_header(G, Fd, X, N, Name, {Extra, ExtraNo}, InP, Arity, OutP,
Reply, ReplyString, Vers) ->
emit(Fd,
"%%----------------------------------------------------------------------\n"
"%% Function : ~p/~p\n"
"%% Arguments : ~s\n",
[ic_util:to_atom(Name), (ExtraNo+Arity), Extra]),
ic_code:type_expand_all(G, N, X, Fd, ?TAB, InP),
case Reply of
["ok"] ->
emit(Fd, "%% Returns : ReturnValue = ~s\n", [ReplyString]);
_ ->
emit(Fd, "%% Returns : ReturnValue = ~s\n", [ReplyString]),
ic_code:type_expand_all(G, N, X, Fd, " ", Reply)
end,
ic_code:type_expand_all(G, N, X, Fd, ?TAB, OutP),
emit(Fd,
"%% Raises : ~s\n"
"%% Description: \n"
"%%----------------------------------------------------------------------\n",
[get_raises(X, Vers)]).
get_raises(#op{raises = []}, _Vers) ->
"";
get_raises(#op{raises = ExcList}, Vers) ->
get_raises2(ExcList, [], Vers);
get_raises(_X, _Vers) ->
[].
get_raises2([H], Acc, _Vers) ->
lists:flatten(lists:reverse([ic_util:to_colon(H)|Acc]));
get_raises2([H|T], Acc, ?IC_FLAG_TEMPLATE_2) ->
get_raises2(T, ["\n%%% ", ic_util:to_colon(H) |Acc],
?IC_FLAG_TEMPLATE_2);
get_raises2([H|T], Acc, _Vers) ->
get_raises2(T, ["\n%% ", ic_util:to_colon(H) |Acc], _Vers).