%% %% %CopyrightBegin% %% %% Copyright Ericsson AB 2010-2011. 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(diameter_codegen). %% %% This module generates .erl and .hrl files for encode/decode %% modules from the orddict parsed from a .dia (aka spec) file by %% dis_spec_util. The generated code is very simple (one-liners), the %% generated functions being called by code included from dis_gen.hrl %% in order to encode/decode messages and AVPs. The orddict itself is %% returned by dict/0 in the generated module and dis_spec_util calls %% this function when importing spec files. (That is, beam has to be %% compiled from an imported spec file before it can be imported.) %% -export([from_spec/4]). %% Internal exports (for test). -export([file/1, file/2, file/3]). -include_lib("diameter/src/app/diameter_internal.hrl"). -include("diameter_forms.hrl"). %% Generated functions that could have no generated clauses will have %% a trailing ?UNEXPECTED clause that should never execute. -define(UNEXPECTED(N), {?clause, [?VAR('_') || _ <- lists:seq(1,N)], [], [?APPLY(erlang, error, [?TERM({unexpected, getr(module)})])]}). from_spec(File, Spec, Opts, Mode) -> Outdir = proplists:get_value(outdir, Opts, "."), putr(verbose, lists:member(verbose, Opts)), putr(debug, lists:member(debug, Opts)), codegen(File, Spec, Outdir, Mode). %% Optional reports when running verbosely. report(What, Data) -> report(getr(verbose), What, Data), Data. report(true, Tag, Data) -> io:format(">>~n>> ~p ~p~n", [Tag, Data]); report(false, _, _) -> ok. putr(Key, Value) -> put({?MODULE, Key}, Value). getr(Key) -> get({?MODULE, Key}). %% =========================================================================== %% =========================================================================== %% Generate from parsed spec in a file. file(F) -> file(F, spec). file(F, Mode) -> file(F, ".", Mode). file(F, Outdir, Mode) -> {ok, [Spec]} = file:consult(F), from_spec(F, Spec, Outdir, Mode). %% =========================================================================== %% =========================================================================== choose(true, X, _) -> X; choose(false, _, X) -> X. get_value(Key, Plist) -> proplists:get_value(Key, Plist, []). write(Path, [C|_] = Spec) when is_integer(C) -> w(Path, Spec, "~s"); write(Path, Spec) -> w(Path, Spec, "~p."). w(Path, Spec, Fmt) -> {ok, Fd} = file:open(Path, [write]), io:fwrite(Fd, Fmt ++ "~n", [Spec]), file:close(Fd). codegen(File, Spec, Outdir, Mode) -> Mod = mod(File, orddict:find(name, Spec)), Path = filename:join(Outdir, Mod), %% minus extension gen(Mode, Spec, Mod, Path), ok. mod(File, error) -> filename:rootname(filename:basename(File)); mod(_, {ok, Mod}) -> atom_to_list(Mod). gen(spec, Spec, _Mod, Path) -> write(Path ++ ".spec", Spec); gen(hrl, Spec, Mod, Path) -> gen_hrl(Path ++ ".hrl", Mod, Spec); gen(erl = Mode, Spec, Mod, Path) when is_list(Mod) -> gen(Mode, Spec, list_to_atom(Mod), Path); gen(erl, Spec, Mod, Path) -> putr(module, Mod), %% used by ?UNEXPECTED. Forms = [{?attribute, module, Mod}, {?attribute, compile, [{parse_transform, diameter_exprecs}]}, {?attribute, compile, [nowarn_unused_function]}, {?attribute, export, [{name, 0}, {id, 0}, {vendor_id, 0}, {vendor_name, 0}, {decode_avps, 2}, %% in diameter_gen.hrl {encode_avps, 2}, %% {msg_name, 2}, {msg_header, 1}, {rec2msg, 1}, {msg2rec, 1}, {name2rec, 1}, {avp_name, 2}, {avp_arity, 2}, {avp_header, 1}, {avp, 3}, {grouped_avp, 3}, {enumerated_avp, 3}, {empty_value, 1}, {dict, 0}]}, %% diameter.hrl is included for #diameter_avp {?attribute, include_lib, "diameter/include/diameter.hrl"}, {?attribute, include_lib, "diameter/include/diameter_gen.hrl"}, f_name(Mod), f_id(Spec), f_vendor_id(Spec), f_vendor_name(Spec), f_msg_name(Spec), f_msg_header(Spec), f_rec2msg(Spec), f_msg2rec(Spec), f_name2rec(Spec), f_avp_name(Spec), f_avp_arity(Spec), f_avp_header(Spec), f_avp(Spec), f_enumerated_avp(Spec), f_empty_value(Spec), f_dict(Spec), {eof, ?LINE}], gen_erl(Path, insert_hrl_forms(Spec, Forms)). gen_erl(Path, Forms) -> getr(debug) andalso write(Path ++ ".forms", Forms), write(Path ++ ".erl", header() ++ erl_prettypr:format(erl_syntax:form_list(Forms))). insert_hrl_forms(Spec, Forms) -> {H,T} = lists:splitwith(fun is_header/1, Forms), H ++ make_hrl_forms(Spec) ++ T. is_header({attribute, _, export, _}) -> false; is_header(_) -> true. make_hrl_forms(Spec) -> {_Prefix, MsgRecs, GrpRecs, ImportedGrpRecs} = make_record_forms(Spec), RecordForms = MsgRecs ++ GrpRecs ++ lists:flatmap(fun({_,Fs}) -> Fs end, ImportedGrpRecs), RecNames = lists:map(fun({attribute,_,record,{N,_}}) -> N end, RecordForms), %% export_records is used by the diameter_exprecs parse transform. [{?attribute, export_records, RecNames} | RecordForms]. make_record_forms(Spec) -> Prefix = prefix(Spec), MsgRecs = a_record(Prefix, fun msg_proj/1, get_value(messages, Spec)), GrpRecs = a_record(Prefix, fun grp_proj/1, get_value(grouped, Spec)), ImportedGrpRecs = [{M, a_record(Prefix, fun grp_proj/1, Gs)} || {M,Gs} <- get_value(import_groups, Spec)], {Prefix, MsgRecs, GrpRecs, ImportedGrpRecs}. msg_proj({Name, _, _, _, Avps}) -> {Name, Avps}. grp_proj({Name, _, _, Avps}) -> {Name, Avps}. %% a_record/3 a_record(Prefix, ProjF, L) -> lists:map(fun(T) -> a_record(ProjF(T), Prefix) end, L). a_record({Nm, Avps}, Prefix) -> Name = list_to_atom(Prefix ++ atom_to_list(Nm)), Fields = lists:map(fun field/1, Avps), {?attribute, record, {Name, Fields}}. field(Avp) -> {Name, Arity} = avp_info(Avp), if 1 == Arity -> {?record_field, ?ATOM(Name)}; true -> {?record_field, ?ATOM(Name), ?NIL} end. %%% ------------------------------------------------------------------------ %%% # name/0 %%% ------------------------------------------------------------------------ f_name(Name) -> {?function, name, 0, [{?clause, [], [], [?ATOM(Name)]}]}. %%% ------------------------------------------------------------------------ %%% # id/0 %%% ------------------------------------------------------------------------ f_id(Spec) -> Id = orddict:fetch(id, Spec), {?function, id, 0, [{?clause, [], [], [?INTEGER(Id)]}]}. %%% ------------------------------------------------------------------------ %%% # vendor_id/0 %%% ------------------------------------------------------------------------ f_vendor_id(Spec) -> {Id, _} = orddict:fetch(vendor, Spec), {?function, vendor_id, 0, [{?clause, [], [], [?INTEGER(Id)]}]}. %%% ------------------------------------------------------------------------ %%% # vendor_name/0 %%% ------------------------------------------------------------------------ f_vendor_name(Spec) -> {_, Name} = orddict:fetch(vendor, Spec), {?function, vendor_name, 0, [{?clause, [], [], [?ATOM(Name)]}]}. %%% ------------------------------------------------------------------------ %%% # msg_name/1 %%% ------------------------------------------------------------------------ f_msg_name(Spec) -> {?function, msg_name, 2, msg_name(Spec)}. %% Return the empty name for any unknown command to which %% DIAMETER_COMMAND_UNSUPPORTED should be replied. msg_name(Spec) -> lists:flatmap(fun c_msg_name/1, proplists:get_value(command_codes, Spec, [])) ++ [{?clause, [?VAR('_'), ?VAR('_')], [], [?ATOM('')]}]. c_msg_name({Code, Req, Ans}) -> [{?clause, [?INTEGER(Code), ?ATOM(true)], [], [?ATOM(mname(Req))]}, {?clause, [?INTEGER(Code), ?ATOM(false)], [], [?ATOM(mname(Ans))]}]. mname({N, _Abbr}) -> N; mname(N) -> N. %%% ------------------------------------------------------------------------ %%% # msg2rec/1 %%% ------------------------------------------------------------------------ f_msg2rec(Spec) -> {?function, msg2rec, 1, msg2rec(Spec)}. msg2rec(Spec) -> Pre = prefix(Spec), Dict = dict:from_list(lists:flatmap(fun msgs/1, get_value(command_codes, Spec))), lists:flatmap(fun(T) -> msg2rec(T, Dict, Pre) end, get_value(messages, Spec)) ++ [?UNEXPECTED(1)]. msgs({_Code, Req, Ans}) -> [{mname(Req), Req}, {mname(Ans), Ans}]. msg2rec({N,_,_,_,_}, Dict, Pre) -> c_msg2rec(fetch_names(N, Dict), Pre). fetch_names(Name, Dict) -> case dict:find(Name, Dict) of {ok, N} -> N; error -> Name end. c_msg2rec({N,A}, Pre) -> [c_name2rec(N, N, Pre), c_name2rec(A, N, Pre)]; c_msg2rec(N, Pre) -> [c_name2rec(N, N, Pre)]. %%% ------------------------------------------------------------------------ %%% # rec2msg/1 %%% ------------------------------------------------------------------------ f_rec2msg(Spec) -> {?function, rec2msg, 1, rec2msg(Spec)}. rec2msg(Spec) -> Pre = prefix(Spec), lists:map(fun(T) -> c_rec2msg(T, Pre) end, get_value(messages, Spec)) ++ [?UNEXPECTED(1)]. c_rec2msg({N,_,_,_,_}, Pre) -> {?clause, [?ATOM(rec_name(N, Pre))], [], [?ATOM(N)]}. %%% ------------------------------------------------------------------------ %%% # name2rec/1 %%% ------------------------------------------------------------------------ f_name2rec(Spec) -> {?function, name2rec, 1, name2rec(Spec)}. name2rec(Spec) -> Pre = prefix(Spec), Groups = get_value(grouped, Spec) ++ lists:flatmap(fun avps/1, get_value(import_groups, Spec)), lists:map(fun({N,_,_,_}) -> c_name2rec(N, N, Pre) end, Groups) ++ [{?clause, [?VAR('T')], [], [?CALL(msg2rec, [?VAR('T')])]}]. c_name2rec(Name, Rname, Pre) -> {?clause, [?ATOM(Name)], [], [?ATOM(rec_name(Rname, Pre))]}. avps({_Mod, Avps}) -> Avps. %%% ------------------------------------------------------------------------ %%% # avp_name/1 %%% ------------------------------------------------------------------------ f_avp_name(Spec) -> {?function, avp_name, 2, avp_name(Spec)}. %% 3588, 4.1: %% %% AVP Code %% The AVP Code, combined with the Vendor-Id field, identifies the %% attribute uniquely. AVP numbers 1 through 255 are reserved for %% backward compatibility with RADIUS, without setting the Vendor-Id %% field. AVP numbers 256 and above are used for Diameter, which are %% allocated by IANA (see Section 11.1). avp_name(Spec) -> Avps = get_value(avp_types, Spec) ++ lists:flatmap(fun avps/1, get_value(import_avps, Spec)), {Vid, _} = orddict:fetch(vendor, Spec), Vs = lists:flatmap(fun({V,Ns}) -> [{N,V} || N <- Ns] end, get_value(avp_vendor_id, Spec)), lists:map(fun(T) -> c_avp_name(T, Vid, Vs) end, Avps) ++ [{?clause, [?VAR('_'), ?VAR('_')], [], [?ATOM('AVP')]}]. c_avp_name({Name, Code, Type, Flags, _Encr}, Vid, Vs) -> c_avp_name({Name, Type}, Code, lists:member('V', Flags), Vid, proplists:get_value(Name, Vs)). c_avp_name(T, Code, false, _, undefined = U) -> {?clause, [?INTEGER(Code), ?ATOM(U)], [], [?TERM(T)]}; c_avp_name(T, Code, true, Vid, V) when is_integer(Vid) -> {?clause, [?INTEGER(Code), ?INTEGER(choose(V == undefined, Vid, V))], [], [?TERM(T)]}. %%% ------------------------------------------------------------------------ %%% # avp_arity/2 %%% ------------------------------------------------------------------------ f_avp_arity(Spec) -> {?function, avp_arity, 2, avp_arity(Spec)}. avp_arity(Spec) -> Msgs = get_value(messages, Spec), Groups = get_value(grouped, Spec) ++ lists:flatmap(fun avps/1, get_value(import_groups, Spec)), c_avp_arity(Msgs ++ Groups) ++ [{?clause, [?VAR('_'), ?VAR('_')], [], [?INTEGER(0)]}]. c_avp_arity(L) when is_list(L) -> lists:flatmap(fun c_avp_arity/1, L); c_avp_arity({N,_,_,_,As}) -> c_avp_arity(N,As); c_avp_arity({N,_,_,As}) -> c_avp_arity(N,As). c_avp_arity(Name, Avps) -> lists:map(fun(A) -> c_arity(Name, A) end, Avps). c_arity(Name, Avp) -> {AvpName, Arity} = avp_info(Avp), {?clause, [?ATOM(Name), ?ATOM(AvpName)], [], [?TERM(Arity)]}. %%% ------------------------------------------------------------------------ %%% # avp/3 %%% ------------------------------------------------------------------------ f_avp(Spec) -> {?function, avp, 3, avp(Spec) ++ [?UNEXPECTED(3)]}. avp(Spec) -> Native = get_value(avp_types, Spec), Custom = get_value(custom_types, Spec), Imported = get_value(import_avps, Spec), avp([{N,T} || {N,_,T,_,_} <- Native], Imported, Custom). avp(Native, Imported, Custom) -> Dict = orddict:from_list(Native), report(native, Dict), report(imported, Imported), report(custom, Custom), CustomNames = lists:flatmap(fun({_,Ns}) -> Ns end, Custom), lists:map(fun c_base_avp/1, lists:filter(fun({N,_}) -> false == lists:member(N, CustomNames) end, Native)) ++ lists:flatmap(fun c_imported_avp/1, Imported) ++ lists:flatmap(fun(C) -> c_custom_avp(C, Dict) end, Custom). c_base_avp({AvpName, T}) -> {?clause, [?VAR('T'), ?VAR('Data'), ?ATOM(AvpName)], [], [base_avp(AvpName, T)]}. base_avp(AvpName, 'Enumerated') -> ?CALL(enumerated_avp, [?VAR('T'), ?ATOM(AvpName), ?VAR('Data')]); base_avp(AvpName, 'Grouped') -> ?CALL(grouped_avp, [?VAR('T'), ?ATOM(AvpName), ?VAR('Data')]); base_avp(_, Type) -> ?APPLY(diameter_types, Type, [?VAR('T'), ?VAR('Data')]). c_imported_avp({Mod, Avps}) -> lists:map(fun(A) -> imported_avp(Mod, A) end, Avps). imported_avp(_Mod, {AvpName, _, 'Grouped' = T, _, _}) -> c_base_avp({AvpName, T}); imported_avp(Mod, {AvpName, _, _, _, _}) -> {?clause, [?VAR('T'), ?VAR('Data'), ?ATOM(AvpName)], [], [?APPLY(Mod, avp, [?VAR('T'), ?VAR('Data'), ?ATOM(AvpName)])]}. c_custom_avp({Mod, Avps}, Dict) -> lists:map(fun(N) -> custom_avp(Mod, N, orddict:fetch(N, Dict)) end, Avps). custom_avp(Mod, AvpName, Type) -> {?clause, [?VAR('T'), ?VAR('Data'), ?ATOM(AvpName)], [], [?APPLY(Mod, AvpName, [?VAR('T'), ?ATOM(Type), ?VAR('Data')])]}. %%% ------------------------------------------------------------------------ %%% # enumerated_avp/3 %%% ------------------------------------------------------------------------ f_enumerated_avp(Spec) -> {?function, enumerated_avp, 3, enumerated_avp(Spec) ++ [?UNEXPECTED(3)]}. enumerated_avp(Spec) -> lists:flatmap(fun c_enumerated_avp/1, get_value(enums, Spec)). c_enumerated_avp({AvpName, Values}) -> lists:flatmap(fun(V) -> c_enumerated_avp(AvpName, V) end, Values). c_enumerated_avp(AvpName, {I,_}) -> [{?clause, [?ATOM(decode), ?ATOM(AvpName), ?TERM(<>)], [], [?TERM(I)]}, {?clause, [?ATOM(encode), ?ATOM(AvpName), ?INTEGER(I)], [], [?TERM(<>)]}]. %%% ------------------------------------------------------------------------ %%% msg_header/1 %%% ------------------------------------------------------------------------ f_msg_header(Spec) -> {?function, msg_header, 1, msg_header(Spec) ++ [?UNEXPECTED(1)]}. msg_header(Spec) -> ApplId = orddict:fetch(id, Spec), lists:map(fun({M,C,F,_,_}) -> c_msg_header(M, C, F, ApplId) end, get_value(messages, Spec)). %% Note that any application id in the message header spec is ignored. c_msg_header(Name, Code, Flags, ApplId) -> {?clause, [?ATOM(Name)], [], [?TERM({Code, encode_msg_flags(Flags), ApplId})]}. encode_msg_flags(Flags) -> lists:foldl(fun emf/2, 0, Flags). emf('REQ', N) -> N bor 2#10000000; emf('PXY', N) -> N bor 2#01000000; emf('ERR', N) -> N bor 2#00100000. %%% ------------------------------------------------------------------------ %%% # avp_header/1 %%% ------------------------------------------------------------------------ f_avp_header(Spec) -> {?function, avp_header, 1, avp_header(Spec) ++ [?UNEXPECTED(1)]}. avp_header(Spec) -> Native = get_value(avp_types, Spec), Imported = get_value(import_avps, Spec), {Vid, _} = orddict:fetch(vendor, Spec), Vs = lists:flatmap(fun({V,Ns}) -> [{N,V} || N <- Ns] end, get_value(avp_vendor_id, Spec)), lists:flatmap(fun(A) -> c_avp_header({Vid, Vs}, A) end, Native ++ Imported). c_avp_header({Vid, Vs}, {Name, Code, _Type, Flags, _Encr}) -> [{?clause, [?ATOM(Name)], [], [?TERM({Code, encode_avp_flags(Flags), vid(Name, Flags, Vs, Vid)})]}]; c_avp_header({_, Vs}, {Mod, Avps}) -> lists:map(fun(A) -> c_avp_header(Vs, Mod, A) end, Avps). c_avp_header(Vs, Mod, {Name, _, _, Flags, _}) -> Apply = ?APPLY(Mod, avp_header, [?ATOM(Name)]), {?clause, [?ATOM(Name)], [], [case proplists:get_value(Name, Vs) of undefined -> Apply; Vid -> true = lists:member('V', Flags), %% sanity check ?CALL(setelement, [?INTEGER(3), Apply, ?INTEGER(Vid)]) end]}. encode_avp_flags(Fs) -> lists:foldl(fun eaf/2, 0, Fs). eaf('V', F) -> 2#10000000 bor F; eaf('M', F) -> 2#01000000 bor F; eaf('P', F) -> 2#00100000 bor F. vid(Name, Flags, Vs, Vid) -> v(lists:member('V', Flags), Name, Vs, Vid). v(true, Name, Vs, Vid) -> proplists:get_value(Name, Vs, Vid); v(false, _, _, _) -> undefined. %%% ------------------------------------------------------------------------ %%% # empty_value/0 %%% ------------------------------------------------------------------------ f_empty_value(Spec) -> {?function, empty_value, 1, empty_value(Spec)}. empty_value(Spec) -> Groups = get_value(grouped, Spec) ++ lists:flatmap(fun avps/1, get_value(import_groups, Spec)), Enums = get_value(enums, Spec) ++ lists:flatmap(fun avps/1, get_value(import_enums, Spec)), lists:map(fun c_empty_value/1, Groups ++ Enums) ++ [{?clause, [?VAR('Name')], [], [?CALL(empty, [?VAR('Name')])]}]. c_empty_value({Name, _, _, _}) -> {?clause, [?ATOM(Name)], [], [?CALL(empty_group, [?ATOM(Name)])]}; c_empty_value({Name, _}) -> {?clause, [?ATOM(Name)], [], [?TERM(<<0:32/integer>>)]}. %%% ------------------------------------------------------------------------ %%% # dict/0 %%% ------------------------------------------------------------------------ f_dict(Spec) -> {?function, dict, 0, [{?clause, [], [], [?TERM(Spec)]}]}. %%% ------------------------------------------------------------------------ %%% # gen_hrl/3 %%% ------------------------------------------------------------------------ gen_hrl(Path, Mod, Spec) -> {ok, Fd} = file:open(Path, [write]), {Prefix, MsgRecs, GrpRecs, ImportedGrpRecs} = make_record_forms(Spec), file:write(Fd, hrl_header(Mod)), forms("Message records", Fd, MsgRecs), forms("Grouped AVP records", Fd, GrpRecs), lists:foreach(fun({M,Fs}) -> forms("Grouped AVP records from " ++ atom_to_list(M), Fd, Fs) end, ImportedGrpRecs), PREFIX = to_upper(Prefix), write("ENUM Macros", Fd, m_enums(PREFIX, false, get_value(enums, Spec))), write("RESULT CODE Macros", Fd, m_enums(PREFIX, false, get_value(result_codes, Spec))), lists:foreach(fun({M,Es}) -> write("ENUM Macros from " ++ atom_to_list(M), Fd, m_enums(PREFIX, true, Es)) end, get_value(import_enums, Spec)), file:close(Fd). forms(_, _, []) -> ok; forms(Banner, Fd, Forms) -> write(Banner, Fd, prettypr(Forms)). write(_, _, []) -> ok; write(Banner, Fd, Str) -> banner(Fd, Banner), io:fwrite(Fd, "~s~n", [Str]). prettypr(Forms) -> erl_prettypr:format(erl_syntax:form_list(Forms)). banner(Fd, Heading) -> file:write(Fd, banner(Heading)). banner(Heading) -> ("\n\n" "%%% -------------------------------------------------------\n" "%%% " ++ Heading ++ ":\n" "%%% -------------------------------------------------------\n\n"). z(S) -> string:join(string:tokens(S, "\s\t"), "\s"). m_enums(Prefix, Wrap, Enums) -> lists:map(fun(T) -> m_enum(Prefix, Wrap, T) end, Enums). m_enum(Prefix, B, {Name, Values}) -> P = Prefix ++ to_upper(Name) ++ "_", lists:map(fun({I,A}) -> N = ["'", P, to_upper(z(atom_to_list(A))), "'"], wrap(B, N, ["-define(", N, ", ", integer_to_list(I), ").\n"]) end, Values). wrap(true, Name, Def) -> ["-ifndef(", Name, ").\n", Def, "-endif.\n"]; wrap(false, _, Def) -> Def. to_upper(A) when is_atom(A) -> to_upper(atom_to_list(A)); to_upper(S) -> lists:map(fun tu/1, S). tu(C) when C >= $a, C =< $z -> C + $A - $a; tu(C) -> C. header() -> ("%% -------------------------------------------------------------------\n" "%% This is a generated file.\n" "%% -------------------------------------------------------------------\n" "\n" "%%\n" "%% Copyright (c) Ericsson AB. All rights reserved.\n" "%%\n" "%% The information in this document is the property of Ericsson.\n" "%%\n" "%% Except as specifically authorized in writing by Ericsson, the\n" "%% receiver of this document shall keep the information contained\n" "%% herein confidential and shall protect the same in whole or in\n" "%% part from disclosure and dissemination to third parties.\n" "%%\n" "%% Disclosure and disseminations to the receivers employees shall\n" "%% only be made on a strict need to know basis.\n" "%%\n\n"). hrl_header(Name) -> header() ++ "-hrl_name('" ++ Name ++ ".hrl').\n". %% avp_info/1 avp_info(Entry) -> %% {Name, Arity} case Entry of {'<',A,'>'} -> {A, 1}; {A} -> {A, 1}; [A] -> {A, {0,1}}; {Q,T} -> {A,_} = avp_info(T), {A, arity(Q)} end. %% Normalize arity to 1 or {N,X} where N is an integer. A record field %% for an AVP is list-valued iff the normalized arity is not 1. arity('*' = Inf) -> {0, Inf}; arity({'*', N}) -> {0, N}; arity({1,1}) -> 1; arity(T) -> T. prefix(Spec) -> case orddict:find(prefix, Spec) of {ok, P} -> atom_to_list(P) ++ "_"; error -> "" end. rec_name(Name, Prefix) -> list_to_atom(Prefix ++ atom_to_list(Name)).