1%%% Copyright (C) 2017  Tomas Abrahamsson
2%%%
3%%% Author: Tomas Abrahamsson <tab@lysator.liu.se>
4%%%
5%%% This library is free software; you can redistribute it and/or
6%%% modify it under the terms of the GNU Lesser General Public
7%%% License as published by the Free Software Foundation; either
8%%% version 2.1 of the License, or (at your option) any later version.
9%%%
10%%% This library is distributed in the hope that it will be useful,
11%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
12%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13%%% Lesser General Public License for more details.
14%%%
15%%% You should have received a copy of the GNU Lesser General Public
16%%% License along with this library; if not, write to the Free Software
17%%% Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
18%%% MA  02110-1301  USA
19
20%%% @doc Generation of introspection functions.
21%%% @private
22
23-module(gpb_gen_introspect).
24
25-export([format_enum_value_symbol_converter_exports/1]).
26-export([format_introspection/2]).
27
28-include("../include/gpb.hrl").
29-include("gpb_codegen.hrl").
30-include("gpb_compile.hrl").
31
32-import(gpb_lib, [replace_term/2, replace_tree/2, repeat_clauses/2]).
33
34format_introspection(Defs, Opts) ->
35    MsgDefs  = [Item || {{msg, _}, _}=Item <- Defs],
36    EnumDefs = [Item || {{enum, _}, _}=Item <- Defs],
37    GroupDefs = [Item || {{group, _}, _}=Item <- Defs],
38    ServiceDefs = [Item || {{service, _}, _}=Item <- Defs],
39    [gpb_codegen:format_fn(
40       get_msg_defs, fun() -> '<Defs>' end,
41       [replace_tree('<Defs>', msg_def_trees(EnumDefs, MsgDefs, GroupDefs,
42                                             Opts))]),
43     "\n",
44     gpb_codegen:format_fn(
45       get_msg_names, fun() -> '<Names>' end,
46       [replace_term('<Names>', [MsgName || {{msg,MsgName}, _} <- Defs])]),
47     "\n",
48     gpb_codegen:format_fn(
49       get_group_names, fun() -> '<Names>' end,
50       [replace_term('<Names>', [Name || {{group,Name}, _} <- Defs])]),
51     "\n",
52     gpb_codegen:format_fn(
53       get_msg_or_group_names, fun() -> '<Names>' end,
54       [replace_term('<Names>', gpb_lib:msg_or_group_names(Defs))]),
55     "\n",
56     gpb_codegen:format_fn(
57       get_enum_names, fun() -> '<Names>' end,
58       [replace_term('<Names>', [EnumName || {{enum,EnumName}, _} <- Defs])]),
59     "\n",
60     format_fetch_msg_defs(gpb_lib:msgs_or_groups(Defs)),
61     ?f("~n"),
62     format_fetch_enum_defs(EnumDefs),
63     ?f("~n"),
64     format_find_msg_defs(Defs, Opts),
65     ?f("~n"),
66     format_find_enum_defs(EnumDefs),
67     ?f("~n"),
68     format_enum_value_symbol_converters(EnumDefs),
69     ?f("~n"),
70     format_get_service_names(ServiceDefs),
71     ?f("~n"),
72     format_get_service_defs(ServiceDefs, Opts),
73     ?f("~n"),
74     format_get_rpc_names(ServiceDefs),
75     ?f("~n"),
76     format_find_rpc_defs(ServiceDefs),
77     ?f("~n"),
78     [format_find_service_rpc_defs(ServiceName, Rpcs, Opts) || {{service, ServiceName}, Rpcs} <- ServiceDefs],
79     ?f("~n"),
80     format_fetch_rpc_defs(ServiceDefs, Opts),
81     ?f("~n"),
82     format_get_package_name(Defs)
83    ].
84
85msg_def_trees(EnumDefs, MsgDefs, GroupDefs, Opts) ->
86    EnumDefTrees = [erl_parse:abstract(EnumDef) || EnumDef <- EnumDefs],
87    MsgDefTrees = [msg_def_tree(MsgDef, Opts) || MsgDef <- MsgDefs],
88    GroupDefTrees = [group_def_tree(GroupDef, Opts) || GroupDef <- GroupDefs],
89    erl_syntax:list(EnumDefTrees ++ MsgDefTrees ++ GroupDefTrees).
90
91msg_def_tree({{msg, MsgName}, Fields}, Opts) ->
92    erl_syntax:tuple(
93      [erl_syntax:tuple([erl_syntax:atom(msg), erl_syntax:atom(MsgName)]),
94       fields_tree(Fields, Opts)]).
95
96group_def_tree({{group, Name}, Fields}, Opts) ->
97    erl_syntax:tuple(
98      [erl_syntax:tuple([erl_syntax:atom(group), erl_syntax:atom(Name)]),
99       fields_tree(Fields, Opts)]).
100
101fields_tree(Fields, Opts) ->
102    case gpb_lib:get_field_format_by_opts(Opts) of
103        fields_as_records ->
104            erl_syntax:list([field_tree(Field, Opts) || Field <- Fields]);
105        fields_as_maps ->
106            erl_syntax:list([field_tree(Field, Opts) || Field <- Fields]);
107        fields_as_proplists ->
108            erl_parse:abstract(gpb:field_records_to_proplists(Fields))
109    end.
110
111field_tree(#?gpb_field{}=F, Opts) ->
112    [?gpb_field | FValues] = tuple_to_list(F),
113    FNames = record_info(fields, ?gpb_field),
114    gpb_lib:mapping_create(
115      ?gpb_field,
116      lists:zip(FNames,
117                [erl_parse:abstract(FValue) || FValue <- FValues]),
118      gpb_lib:mk_get_defs_as_maps_or_records_fn(Opts),
119      Opts);
120field_tree(#gpb_oneof{fields=OFields}=F, Opts) ->
121    [gpb_oneof | FValues] = tuple_to_list(F),
122    FNames = record_info(fields, gpb_oneof),
123    gpb_lib:mapping_create(
124      gpb_oneof,
125      [if FName == fields -> {FName, fields_tree(OFields, Opts)};
126          FName /= fields -> {FName, erl_parse:abstract(FValue)}
127       end
128       || {FName, FValue} <- lists:zip(FNames, FValues)],
129      gpb_lib:mk_get_defs_as_maps_or_records_fn(Opts),
130      Opts).
131
132format_fetch_msg_defs([]) ->
133    ["-spec fetch_msg_def(_) -> no_return().\n",
134     gpb_codegen:format_fn(
135       fetch_msg_def,
136       fun(MsgName) -> erlang:error({no_such_msg, MsgName}) end)];
137format_fetch_msg_defs(_MsgDefs) ->
138    gpb_codegen:format_fn(
139      fetch_msg_def,
140      fun(MsgName) ->
141              case find_msg_def(MsgName) of
142                  Fs when is_list(Fs) -> Fs;
143                  error               -> erlang:error({no_such_msg, MsgName})
144              end
145      end).
146
147format_fetch_enum_defs([]) ->
148    ["-spec fetch_enum_def(_) -> no_return().\n",
149     gpb_codegen:format_fn(
150       fetch_enum_def,
151       fun(EnumName) -> erlang:error({no_such_enum, EnumName}) end)];
152format_fetch_enum_defs(_EnumDefs) ->
153    gpb_codegen:format_fn(
154      fetch_enum_def,
155      fun(EnumName) ->
156              case find_enum_def(EnumName) of
157                  Es when is_list(Es) -> Es;
158                  error               -> erlang:error({no_such_enum, EnumName})
159              end
160      end).
161
162format_find_msg_defs(Defs, Opts) ->
163    gpb_codegen:format_fn(
164      find_msg_def,
165      fun('<Name>') -> '<Fields>';
166         (_) -> error
167      end,
168      [repeat_clauses('<Name>',
169                      [[replace_term('<Name>', Name),
170                        replace_tree('<Fields>', fields_tree(Fields, Opts))]
171                       || {_, Name, Fields} <- gpb_lib:msgs_or_groups(Defs)])]).
172
173format_find_enum_defs(Enums) ->
174    gpb_codegen:format_fn(
175      find_enum_def,
176      fun('<EnumName>') -> '<Values>';
177         (_) -> error
178      end,
179      [repeat_clauses('<EnumName>',
180                      [[replace_term('<EnumName>', EnumName),
181                        replace_term('<Values>', Values)]
182                       || {{enum, EnumName}, Values} <- Enums])]).
183
184
185format_enum_value_symbol_converter_exports(Defs) ->
186    [?f("-export([enum_symbol_by_value/2, enum_value_by_symbol/2]).~n"),
187     [begin
188         ToSymFnName = gpb_lib:mk_fn(enum_symbol_by_value_, EnumName),
189         ToValFnName = gpb_lib:mk_fn(enum_value_by_symbol_, EnumName),
190         ?f("-export([~p/1, ~p/1]).~n", [ToSymFnName, ToValFnName])
191     end
192     || {{enum, EnumName}, _EnumDef} <- Defs]].
193
194format_enum_value_symbol_converters(EnumDefs) when EnumDefs /= [] ->
195    %% A difference between this function and `d_enum_X' as generated
196    %% by `format_enum_decoders' is that this function generates
197    %% value/symbol converters for all enums, not only for the ones
198    %% that are used in messages.
199    [gpb_codegen:format_fn(
200       enum_symbol_by_value,
201       fun('<EnumName>', Value) -> 'cvt'(Value) end,
202       [repeat_clauses(
203          '<EnumName>',
204          [[replace_term('<EnumName>', EnumName),
205            replace_term('cvt', gpb_lib:mk_fn(enum_symbol_by_value_, EnumName))]
206           || {{enum, EnumName}, _EnumDef} <- EnumDefs])]),
207     "\n",
208     gpb_codegen:format_fn(
209       enum_value_by_symbol,
210       fun('<EnumName>', Sym) -> 'cvt'(Sym) end,
211       [repeat_clauses(
212          '<EnumName>',
213          [[replace_term('<EnumName>', EnumName),
214            replace_term('cvt', gpb_lib:mk_fn(enum_value_by_symbol_, EnumName))]
215           || {{enum, EnumName}, _EnumDef} <- EnumDefs])]),
216     "\n",
217     [[gpb_codegen:format_fn(
218         gpb_lib:mk_fn(enum_symbol_by_value_, EnumName),
219         fun('<Value>') -> '<Sym>' end,
220         [repeat_clauses('<Value>',
221                         [[replace_term('<Value>', EnumValue),
222                           replace_term('<Sym>', EnumSym)]
223                          || {EnumSym, EnumValue} <- gpb_lib:unalias_enum(
224                                                       EnumDef)])]),
225       "\n",
226       gpb_codegen:format_fn(
227         gpb_lib:mk_fn(enum_value_by_symbol_, EnumName),
228         fun('<Sym>') -> '<Value>' end,
229         [repeat_clauses('<Sym>',
230                         [[replace_term('<Value>', EnumValue),
231                           replace_term('<Sym>', EnumSym)]
232                          || {EnumSym, EnumValue} <- EnumDef])])]
233      || {{enum, EnumName}, EnumDef} <- EnumDefs]];
234format_enum_value_symbol_converters([]=_EnumDefs) ->
235    ["-spec enum_symbol_by_value(_, _) -> no_return().\n",
236     gpb_codegen:format_fn(
237       enum_symbol_by_value,
238       fun(E, V) -> erlang:error({no_enum_defs, E, V}) end),
239     "\n",
240     "-spec enum_value_by_symbol(_, _) -> no_return().\n",
241     gpb_codegen:format_fn(
242       enum_value_by_symbol,
243       fun(E, V) -> erlang:error({no_enum_defs, E, V}) end),
244     "\n"].
245
246format_get_package_name(Defs) ->
247    case lists:keyfind(package, 1, Defs) of
248        false ->
249            gpb_codegen:format_fn(
250              get_package_name, fun() -> undefined end);
251        {package, Package} ->
252            gpb_codegen:format_fn(
253              get_package_name, fun() -> '<Package>' end,
254              [replace_term('<Package>', Package)])
255    end.
256
257% --- service introspection methods
258
259format_get_service_names(ServiceDefs) ->
260    gpb_codegen:format_fn(
261      get_service_names,
262      fun() -> '<ServiceNames>' end,
263      [replace_term(
264         '<ServiceNames>',
265         [ServiceName || {{service, ServiceName}, _Rpcs} <- ServiceDefs])]).
266
267format_get_service_defs(ServiceDefs, Opts) ->
268    gpb_codegen:format_fn(
269      get_service_def,
270      fun('<ServiceName>') -> '<ServiceDef>';
271         (_) -> error
272      end,
273      [repeat_clauses(
274         '<ServiceName>',
275         [[replace_term('<ServiceName>', ServiceName),
276           replace_tree('<ServiceDef>', service_def_tree(ServiceDef, Opts))]
277          || {{service, ServiceName}, _Rpcs} = ServiceDef <- ServiceDefs])]).
278
279format_get_rpc_names(ServiceDefs) ->
280    gpb_codegen:format_fn(
281      get_rpc_names,
282      fun('<ServiceName>') -> '<ServiceRpcNames>';
283         (_) -> error
284      end,
285      [repeat_clauses('<ServiceName>',
286                      [[replace_term('<ServiceName>', ServiceName),
287                        replace_term('<ServiceRpcNames>',
288                                     [RpcName
289                                      || #?gpb_rpc{name=RpcName} <- Rpcs])]
290                       || {{service, ServiceName}, Rpcs} <- ServiceDefs])]).
291
292format_find_rpc_defs(ServiceDefs) ->
293    gpb_codegen:format_fn(
294      find_rpc_def,
295      fun('<ServiceName>', RpcName) -> '<ServiceFindRpcDef>'(RpcName);
296         (_, _) -> error
297      end,
298      [repeat_clauses(
299         '<ServiceName>',
300         [[replace_term('<ServiceName>', ServiceName),
301           replace_term('<ServiceFindRpcDef>',
302                        gpb_lib:mk_fn(find_rpc_def_, ServiceName))]
303          || {{service, ServiceName}, _} <- ServiceDefs])]).
304
305format_find_service_rpc_defs(ServiceName, Rpcs, Opts) ->
306    gpb_codegen:format_fn(
307      gpb_lib:mk_fn(find_rpc_def_, ServiceName),
308      fun('<RpcName>') -> '<RpcDef>';
309         (_) -> error
310      end,
311      [repeat_clauses('<RpcName>',
312                      [[replace_term('<RpcName>', RpcName),
313                        replace_tree('<RpcDef>', rpc_def_tree(Rpc, Opts))]
314                       || #?gpb_rpc{name=RpcName} = Rpc <- Rpcs])]).
315
316format_fetch_rpc_defs([], _Opts) ->
317    ["-spec fetch_rpc_def(_, _) -> no_return().\n",
318     gpb_codegen:format_fn(
319       fetch_rpc_def,
320       fun(ServiceName, RpcName) ->
321               erlang:error({no_such_rpc, ServiceName, RpcName})
322       end)];
323format_fetch_rpc_defs(_ServiceDefs, Opts) ->
324    gpb_codegen:format_fn(
325      fetch_rpc_def,
326      fun(ServiceName, RpcName) ->
327              case find_rpc_def(ServiceName, RpcName) of
328                  Def when is_X(Def) -> Def;
329                  error -> erlang:error({no_such_rpc, ServiceName, RpcName})
330              end
331      end,
332      [replace_term(is_X,
333                    case get_rpc_format_by_opts(Opts) of
334                        rpcs_as_proplists ->
335                            is_list;
336                        rpcs_as_records ->
337                            case gpb_lib:get_records_or_maps_by_opts(Opts) of
338                                maps    -> is_map;
339                                records -> is_tuple
340                            end
341                    end)]).
342
343service_def_tree({{service, ServiceName}, Rpcs}, Opts) ->
344    erl_syntax:tuple(
345      [erl_syntax:tuple([erl_syntax:atom(service),
346                         erl_syntax:atom(ServiceName)]),
347       rpcs_def_tree(Rpcs, Opts)]).
348
349get_rpc_format_by_opts(Opts) ->
350    case proplists:get_bool(defs_as_proplists, proplists:unfold(Opts)) of
351        false -> rpcs_as_records; %% default
352        true  -> rpcs_as_proplists
353    end.
354
355rpc_record_def_tree(#?gpb_rpc{}=Rpc, Opts) ->
356    [?gpb_rpc | RValues] = tuple_to_list(Rpc),
357    RNames = record_info(fields, ?gpb_rpc),
358    gpb_lib:mapping_create(
359      ?gpb_rpc,
360      lists:zip(RNames,
361                [erl_parse:abstract(RValue) || RValue <- RValues]),
362      gpb_lib:mk_get_defs_as_maps_or_records_fn(Opts),
363      Opts).
364
365rpcs_def_tree(Rpcs, Opts) ->
366    case get_rpc_format_by_opts(Opts) of
367        rpcs_as_records   ->
368            erl_syntax:list([rpc_record_def_tree(Rpc, Opts) || Rpc <- Rpcs]);
369        rpcs_as_proplists ->
370            erl_parse:abstract(gpb:rpc_records_to_proplists(Rpcs))
371    end.
372
373rpc_def_tree(#?gpb_rpc{}=Rpc, Opts) ->
374    case get_rpc_format_by_opts(Opts) of
375        rpcs_as_records   ->
376            rpc_record_def_tree(Rpc, Opts);
377        rpcs_as_proplists ->
378            erl_parse:abstract(gpb:rpc_record_to_proplist(Rpc))
379    end.
380
381