aboutsummaryrefslogtreecommitdiffstats
path: root/lib/diameter/src/compiler
diff options
context:
space:
mode:
authorAnders Svensson <[email protected]>2011-12-07 16:51:46 +0100
committerAnders Svensson <[email protected]>2011-12-07 16:51:46 +0100
commit24f0d3ee266d56cc83435401230a8bb85a0464d3 (patch)
treeecb3a5e9d8539e87efb5677494894f3ced97a2cb /lib/diameter/src/compiler
parent977f7510818925d9293361c14788fdb872614ac1 (diff)
parentf2a4059d06f8b76d2c1da14197f170deebd64f45 (diff)
downloadotp-24f0d3ee266d56cc83435401230a8bb85a0464d3.tar.gz
otp-24f0d3ee266d56cc83435401230a8bb85a0464d3.tar.bz2
otp-24f0d3ee266d56cc83435401230a8bb85a0464d3.zip
Merge branch 'anders/diameter/dict_error_identification/OTP-9639'
* anders/diameter/dict_error_identification/OTP-9639: (27 commits) Update documentation Improve base_rfc3588.dia formatting Make typo fix backwards compatible Fix base_rfc3588.dia typo Check compiler dependencies in app suite Move type definitions into diameter.erl Fix interpretation of vendor id in @grouped Add range checks on dictionary integers Don't explicitly load inherited modules Tweak diameter_make interface Add format testcase to compiler suite Add diameter_dict_util:format/1 for reconstructing a dictionary file Make diameter_types usable with @codecs Minor codegen tweaks Remove unnecessary includes Add compiler suite Update app suite Update codec suite Vendor id fixes No longer inherit common dictionary in relay dictionary ...
Diffstat (limited to 'lib/diameter/src/compiler')
-rw-r--r--lib/diameter/src/compiler/diameter_codegen.erl372
-rw-r--r--lib/diameter/src/compiler/diameter_dict_parser.yrl324
-rw-r--r--lib/diameter/src/compiler/diameter_dict_scanner.erl274
-rw-r--r--lib/diameter/src/compiler/diameter_dict_util.erl1302
-rw-r--r--lib/diameter/src/compiler/diameter_exprecs.erl44
-rw-r--r--lib/diameter/src/compiler/diameter_forms.hrl7
-rw-r--r--lib/diameter/src/compiler/diameter_make.erl106
-rw-r--r--lib/diameter/src/compiler/diameter_spec_scan.erl157
-rw-r--r--lib/diameter/src/compiler/diameter_spec_util.erl1089
-rw-r--r--lib/diameter/src/compiler/diameter_vsn.hrl22
10 files changed, 2219 insertions, 1478 deletions
diff --git a/lib/diameter/src/compiler/diameter_codegen.erl b/lib/diameter/src/compiler/diameter_codegen.erl
index 0fd4a0b301..6763e06140 100644
--- a/lib/diameter/src/compiler/diameter_codegen.erl
+++ b/lib/diameter/src/compiler/diameter_codegen.erl
@@ -20,17 +20,18 @@
-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.)
+%% This module generates erl/hrl files for encode/decode modules
+%% from the orddict parsed from a dictionary file (.dia) by
+%% diameter_dict_util. The generated code is simple (one-liners),
+%% the generated functions being called by code included iin the
+%% generated modules from diameter_gen.hrl. The orddict itself is
+%% returned by dict/0 in the generated module and diameter_dict_util
+%% calls this function when importing dictionaries as a consequence
+%% of @inherits sections. That is, @inherits introduces a dependency
+%% on the beam file of another dictionary.
%%
--export([from_spec/4]).
+-export([from_dict/4]).
%% Internal exports (for test).
-export([file/1,
@@ -38,17 +39,23 @@
file/3]).
-include("diameter_forms.hrl").
+-include("diameter_vsn.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)})])]}).
+-define(S, atom_to_list).
+-define(A, list_to_atom).
+-define(Atom(T), ?ATOM(?A(T))).
-from_spec(File, Spec, Opts, Mode) ->
+%% ===========================================================================
+
+-spec from_dict(File, Spec, Opts, Mode)
+ -> ok
+ when File :: string(),
+ Spec :: orddict:orddict(),
+ Opts :: list(),
+ Mode :: spec | erl | hrl.
+
+from_dict(File, Spec, Opts, Mode) ->
Outdir = proplists:get_value(outdir, Opts, "."),
putr(verbose, lists:member(verbose, Opts)),
putr(debug, lists:member(debug, Opts)),
@@ -73,7 +80,7 @@ getr(Key) ->
%% ===========================================================================
%% ===========================================================================
-%% Generate from parsed spec in a file.
+%% Generate from parsed dictionary in a file.
file(F) ->
file(F, spec).
@@ -83,14 +90,11 @@ file(F, Mode) ->
file(F, Outdir, Mode) ->
{ok, [Spec]} = file:consult(F),
- from_spec(F, Spec, Outdir, Mode).
+ from_dict(F, Spec, Outdir, Mode).
%% ===========================================================================
%% ===========================================================================
-choose(true, X, _) -> X;
-choose(false, _, X) -> X.
-
get_value(Key, Plist) ->
proplists:get_value(Key, Plist, []).
@@ -108,27 +112,21 @@ w(Path, Spec, Fmt) ->
codegen(File, Spec, Outdir, Mode) ->
Mod = mod(File, orddict:find(name, Spec)),
Path = filename:join(Outdir, Mod), %% minus extension
- gen(Mode, Spec, Mod, Path),
+ gen(Mode, Spec, ?A(Mod), Path),
ok.
mod(File, error) ->
filename:rootname(filename:basename(File));
mod(_, {ok, Mod}) ->
- atom_to_list(Mod).
+ Mod.
gen(spec, Spec, _Mod, Path) ->
- write(Path ++ ".spec", Spec);
+ write(Path ++ ".spec", [?VERSION | 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]},
@@ -224,16 +222,16 @@ 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)),
+ Name = list_to_atom(Prefix ++ 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)};
+ {?record_field, ?Atom(Name)};
true ->
- {?record_field, ?ATOM(Name), ?NIL}
+ {?record_field, ?Atom(Name), ?NIL}
end.
%%% ------------------------------------------------------------------------
@@ -256,25 +254,33 @@ c_id({ok, Id}) ->
{?clause, [], [], [?INTEGER(Id)]};
c_id(error) ->
- ?UNEXPECTED(0).
+ ?BADARG(0).
%%% ------------------------------------------------------------------------
%%% # vendor_id/0
%%% ------------------------------------------------------------------------
f_vendor_id(Spec) ->
- {Id, _} = orddict:fetch(vendor, Spec),
{?function, vendor_id, 0,
- [{?clause, [], [], [?INTEGER(Id)]}]}.
+ [{?clause, [], [], [b_vendor_id(orddict:find(vendor, Spec))]}]}.
+
+b_vendor_id({ok, {Id, _}}) ->
+ ?INTEGER(Id);
+b_vendor_id(error) ->
+ ?APPLY(erlang, error, [?TERM(undefined)]).
%%% ------------------------------------------------------------------------
%%% # vendor_name/0
%%% ------------------------------------------------------------------------
f_vendor_name(Spec) ->
- {_, Name} = orddict:fetch(vendor, Spec),
{?function, vendor_name, 0,
- [{?clause, [], [], [?ATOM(Name)]}]}.
+ [{?clause, [], [], [b_vendor_name(orddict:find(vendor, Spec))]}]}.
+
+b_vendor_name({ok, {_, Name}}) ->
+ ?Atom(Name);
+b_vendor_name(error) ->
+ ?APPLY(erlang, error, [?TERM(undefined)]).
%%% ------------------------------------------------------------------------
%%% # msg_name/1
@@ -287,22 +293,18 @@ f_msg_name(Spec) ->
%% DIAMETER_COMMAND_UNSUPPORTED should be replied.
msg_name(Spec) ->
- lists:flatmap(fun c_msg_name/1,
- proplists:get_value(command_codes, 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))]},
+ [?Atom(Req)]},
{?clause, [?INTEGER(Code), ?ATOM(false)],
[],
- [?ATOM(mname(Ans))]}].
-
-mname({N, _Abbr}) ->
- N;
-mname(N) ->
- N.
+ [?Atom(Ans)]}].
%%% ------------------------------------------------------------------------
%%% # msg2rec/1
@@ -313,30 +315,11 @@ f_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.
+ lists:map(fun(T) -> c_msg2rec(T, Pre) end, get_value(messages, Spec))
+ ++ [?BADARG(1)].
-c_msg2rec({N,A}, Pre) ->
- [c_name2rec(N, N, Pre), c_name2rec(A, N, Pre)];
-c_msg2rec(N, Pre) ->
- [c_name2rec(N, N, Pre)].
+c_msg2rec({N,_,_,_,_}, Pre) ->
+ c_name2rec(N, Pre).
%%% ------------------------------------------------------------------------
%%% # rec2msg/1
@@ -348,10 +331,10 @@ f_rec2msg(Spec) ->
rec2msg(Spec) ->
Pre = prefix(Spec),
lists:map(fun(T) -> c_rec2msg(T, Pre) end, get_value(messages, Spec))
- ++ [?UNEXPECTED(1)].
+ ++ [?BADARG(1)].
c_rec2msg({N,_,_,_,_}, Pre) ->
- {?clause, [?ATOM(rec_name(N, Pre))], [], [?ATOM(N)]}.
+ {?clause, [?Atom(rec_name(N, Pre))], [], [?Atom(N)]}.
%%% ------------------------------------------------------------------------
%%% # name2rec/1
@@ -364,11 +347,11 @@ 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)
+ lists:map(fun({N,_,_,_}) -> c_name2rec(N, Pre) end, Groups)
++ [{?clause, [?VAR('T')], [], [?CALL(msg2rec, [?VAR('T')])]}].
-c_name2rec(Name, Rname, Pre) ->
- {?clause, [?ATOM(Name)], [], [?ATOM(rec_name(Rname, Pre))]}.
+c_name2rec(Name, Pre) ->
+ {?clause, [?Atom(Name)], [], [?Atom(rec_name(Name, Pre))]}.
avps({_Mod, Avps}) ->
Avps.
@@ -390,32 +373,47 @@ f_avp_name(Spec) ->
%% 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)),
+ Avps = get_value(avp_types, Spec),
+ Imported = get_value(import_avps, Spec),
+ Vid = orddict:find(vendor, Spec),
+ Vs = vendor_id_map(Spec),
- lists:map(fun(T) -> c_avp_name(T, Vid, Vs) end, Avps)
+ lists:map(fun(T) -> c_avp_name(T, Vs, Vid) end, Avps)
+ ++ lists:flatmap(fun(T) -> c_imported_avp_name(T, Vs) end, Imported)
++ [{?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({Name, Code, Type, Flags}, Vs, Vid) ->
+ c_avp_name_(?TERM({?A(Name), ?A(Type)}),
+ ?INTEGER(Code),
+ vid(Name, Flags, Vs, Vid)).
-c_avp_name(T, Code, false, _, undefined = U) ->
- {?clause, [?INTEGER(Code), ?ATOM(U)],
+%% Note that an imported AVP's vendor id is determined by
+%% avp_vendor_id in the inheriting module and vendor in the inherited
+%% module. In particular, avp_vendor_id in the inherited module is
+%% ignored so can't just call Mod:avp_header/1 to retrieve the vendor
+%% id. A vendor id specified in @grouped is equivalent to one
+%% specified as avp_vendor_id.
+
+c_imported_avp_name({Mod, Avps}, Vs) ->
+ lists:map(fun(A) -> c_avp_name(A, Vs, {module, Mod}) end, Avps).
+
+c_avp_name_(T, Code, undefined = U) ->
+ {?clause, [Code, ?ATOM(U)],
[],
- [?TERM(T)]};
+ [T]};
-c_avp_name(T, Code, true, Vid, V)
- when is_integer(Vid) ->
- {?clause, [?INTEGER(Code), ?INTEGER(choose(V == undefined, Vid, V))],
+c_avp_name_(T, Code, Vid) ->
+ {?clause, [Code, ?INTEGER(Vid)],
[],
- [?TERM(T)]}.
+ [T]}.
+
+vendor_id_map(Spec) ->
+ lists:flatmap(fun({V,Ns}) -> [{N,V} || N <- Ns] end,
+ get_value(avp_vendor_id, Spec))
+ ++ lists:flatmap(fun({_,_,[],_}) -> [];
+ ({N,_,[V],_}) -> [{N,V}]
+ end,
+ get_value(grouped, Spec)).
%%% ------------------------------------------------------------------------
%%% # avp_arity/2
@@ -445,60 +443,75 @@ c_avp_arity(Name, Avps) ->
c_arity(Name, Avp) ->
{AvpName, Arity} = avp_info(Avp),
- {?clause, [?ATOM(Name), ?ATOM(AvpName)], [], [?TERM(Arity)]}.
+ {?clause, [?Atom(Name), ?Atom(AvpName)], [], [?TERM(Arity)]}.
%%% ------------------------------------------------------------------------
%%% # avp/3
%%% ------------------------------------------------------------------------
f_avp(Spec) ->
- {?function, avp, 3, avp(Spec) ++ [?UNEXPECTED(3)]}.
+ {?function, avp, 3, avp(Spec) ++ [?BADARG(3)]}.
avp(Spec) ->
- Native = get_value(avp_types, Spec),
- Custom = get_value(custom_types, Spec),
- Imported = get_value(import_avps, Spec),
- Enums = get_value(enums, Spec),
- avp([{N,T} || {N,_,T,_,_} <- Native], Imported, Custom, Enums).
+ Native = get_value(avp_types, Spec),
+ CustomMods = get_value(custom_types, Spec),
+ TypeMods = get_value(codecs, Spec),
+ Imported = get_value(import_avps, Spec),
+ Enums = get_value(enum, Spec),
-avp(Native, Imported, Custom, Enums) ->
- Dict = orddict:from_list(Native),
+ Custom = lists:map(fun({M,As}) -> {M, custom_types, As} end,
+ CustomMods)
+ ++ lists:map(fun({M,As}) -> {M, codecs, As} end,
+ TypeMods),
+ avp(types(Native), Imported, Custom, Enums).
+
+types(Avps) ->
+ lists:map(fun({N,_,T,_}) -> {N,T} end, Avps).
- report(native, Dict),
+avp(Native, Imported, Custom, Enums) ->
+ report(native, Native),
report(imported, Imported),
report(custom, Custom),
- CustomNames = lists:flatmap(fun({_,Ns}) -> Ns end, Custom),
+ TypeDict = lists:foldl(fun({N,_,T,_}, D) -> orddict:store(N,T,D) end,
+ orddict:from_list(Native),
+ lists:flatmap(fun avps/1, Imported)),
+
+ 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,
+ lists:filter(fun({N,_}) -> not_in(CustomNames, N) end,
Native))
- ++ lists:flatmap(fun(I) -> cs_imported_avp(I, Enums) end, Imported)
- ++ lists:flatmap(fun(C) -> cs_custom_avp(C, Dict) end, Custom).
+ ++ lists:flatmap(fun(I) -> cs_imported_avp(I, Enums, CustomNames) end,
+ Imported)
+ ++ lists:flatmap(fun(C) -> cs_custom_avp(C, TypeDict) end, Custom).
+
+not_in(List, X) ->
+ not lists:member(X, List).
c_base_avp({AvpName, T}) ->
- {?clause, [?VAR('T'), ?VAR('Data'), ?ATOM(AvpName)],
+ {?clause, [?VAR('T'), ?VAR('Data'), ?Atom(AvpName)],
[],
- [base_avp(AvpName, T)]}.
+ [b_base_avp(AvpName, T)]}.
-base_avp(AvpName, 'Enumerated') ->
- ?CALL(enumerated_avp, [?VAR('T'), ?ATOM(AvpName), ?VAR('Data')]);
+b_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')]);
+b_base_avp(AvpName, "Grouped") ->
+ ?CALL(grouped_avp, [?VAR('T'), ?Atom(AvpName), ?VAR('Data')]);
-base_avp(_, Type) ->
- ?APPLY(diameter_types, Type, [?VAR('T'), ?VAR('Data')]).
+b_base_avp(_, Type) ->
+ ?APPLY(diameter_types, ?A(Type), [?VAR('T'), ?VAR('Data')]).
-cs_imported_avp({Mod, Avps}, Enums) ->
- lists:map(fun(A) -> imported_avp(Mod, A, Enums) end, Avps).
+cs_imported_avp({Mod, Avps}, Enums, CustomNames) ->
+ lists:map(fun(A) -> imported_avp(Mod, A, Enums) end,
+ lists:filter(fun({N,_,_,_}) -> not_in(CustomNames, N) end,
+ Avps)).
-imported_avp(_Mod, {AvpName, _, 'Grouped' = T, _, _}, _) ->
+imported_avp(_Mod, {AvpName, _, "Grouped" = T, _}, _) ->
c_base_avp({AvpName, T});
-imported_avp(Mod, {AvpName, _, 'Enumerated' = T, _, _}, Enums) ->
+imported_avp(Mod, {AvpName, _, "Enumerated" = T, _}, Enums) ->
case lists:keymember(AvpName, 1, Enums) of
true ->
c_base_avp({AvpName, T});
@@ -506,34 +519,40 @@ imported_avp(Mod, {AvpName, _, 'Enumerated' = T, _, _}, Enums) ->
c_imported_avp(Mod, AvpName)
end;
-imported_avp(Mod, {AvpName, _, _, _, _}, _) ->
+imported_avp(Mod, {AvpName, _, _, _}, _) ->
c_imported_avp(Mod, AvpName).
c_imported_avp(Mod, AvpName) ->
- {?clause, [?VAR('T'), ?VAR('Data'), ?ATOM(AvpName)],
+ {?clause, [?VAR('T'), ?VAR('Data'), ?Atom(AvpName)],
[],
[?APPLY(Mod, avp, [?VAR('T'),
?VAR('Data'),
- ?ATOM(AvpName)])]}.
+ ?Atom(AvpName)])]}.
-cs_custom_avp({Mod, Avps}, Dict) ->
- lists:map(fun(N) -> c_custom_avp(Mod, N, orddict:fetch(N, Dict)) end,
+cs_custom_avp({Mod, Key, Avps}, Dict) ->
+ lists:map(fun(N) -> c_custom_avp(Mod, Key, N, orddict:fetch(N, Dict)) end,
Avps).
-c_custom_avp(Mod, AvpName, Type) ->
- {?clause, [?VAR('T'), ?VAR('Data'), ?ATOM(AvpName)],
+c_custom_avp(Mod, Key, AvpName, Type) ->
+ {F,A} = custom(Key, AvpName, Type),
+ {?clause, [?VAR('T'), ?VAR('Data'), ?Atom(AvpName)],
[],
- [?APPLY(Mod, AvpName, [?VAR('T'), ?ATOM(Type), ?VAR('Data')])]}.
+ [?APPLY(?A(Mod), ?A(F), [?VAR('T'), ?Atom(A), ?VAR('Data')])]}.
+
+custom(custom_types, AvpName, Type) ->
+ {AvpName, Type};
+custom(codecs, AvpName, Type) ->
+ {Type, AvpName}.
%%% ------------------------------------------------------------------------
%%% # enumerated_avp/3
%%% ------------------------------------------------------------------------
f_enumerated_avp(Spec) ->
- {?function, enumerated_avp, 3, enumerated_avp(Spec) ++ [?UNEXPECTED(3)]}.
+ {?function, enumerated_avp, 3, enumerated_avp(Spec) ++ [?BADARG(3)]}.
enumerated_avp(Spec) ->
- Enums = get_value(enums, Spec),
+ Enums = get_value(enum, Spec),
lists:flatmap(fun cs_enumerated_avp/1, Enums)
++ lists:flatmap(fun({M,Es}) -> enumerated_avp(M, Es, Enums) end,
get_value(import_enums, Spec)).
@@ -554,11 +573,11 @@ cs_enumerated_avp(false, _, _) ->
cs_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(<<I:32/integer>>)],
+c_enumerated_avp(AvpName, {_,I}) ->
+ [{?clause, [?ATOM(decode), ?Atom(AvpName), ?TERM(<<I:32/integer>>)],
[],
[?TERM(I)]},
- {?clause, [?ATOM(encode), ?ATOM(AvpName), ?INTEGER(I)],
+ {?clause, [?ATOM(encode), ?Atom(AvpName), ?INTEGER(I)],
[],
[?TERM(<<I:32/integer>>)]}].
@@ -567,7 +586,7 @@ c_enumerated_avp(AvpName, {I,_}) ->
%%% ------------------------------------------------------------------------
f_msg_header(Spec) ->
- {?function, msg_header, 1, msg_header(Spec) ++ [?UNEXPECTED(1)]}.
+ {?function, msg_header, 1, msg_header(Spec) ++ [?BADARG(1)]}.
msg_header(Spec) ->
msg_header(get_value(messages, Spec), Spec).
@@ -582,7 +601,7 @@ msg_header(Msgs, Spec) ->
%% Note that any application id in the message header spec is ignored.
c_msg_header(Name, Code, Flags, ApplId) ->
- {?clause, [?ATOM(Name)],
+ {?clause, [?Atom(Name)],
[],
[?TERM({Code, encode_msg_flags(Flags), ApplId})]}.
@@ -598,50 +617,61 @@ emf('ERR', N) -> N bor 2#00100000.
%%% ------------------------------------------------------------------------
f_avp_header(Spec) ->
- {?function, avp_header, 1, avp_header(Spec) ++ [?UNEXPECTED(1)]}.
+ {?function, avp_header, 1, avp_header(Spec) ++ [?BADARG(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)),
+ Vid = orddict:find(vendor, Spec),
+ Vs = vendor_id_map(Spec),
- lists:flatmap(fun(A) -> c_avp_header({Vid, Vs}, A) end,
+ lists:flatmap(fun(A) -> c_avp_header(A, Vs, Vid) end,
Native ++ Imported).
-c_avp_header({Vid, Vs}, {Name, Code, _Type, Flags, _Encr}) ->
- [{?clause, [?ATOM(Name)],
+c_avp_header({Name, Code, _Type, Flags}, Vs, Vid) ->
+ [{?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({Mod, Avps}, Vs, _Vid) ->
+ lists:map(fun(A) -> c_imported_avp_header(A, Mod, Vs) end, Avps).
-c_avp_header(Vs, Mod, {Name, _, _, Flags, _}) ->
- Apply = ?APPLY(Mod, avp_header, [?ATOM(Name)]),
- {?clause, [?ATOM(Name)],
+%% Note that avp_vendor_id in the inherited dictionary is ignored. The
+%% value must be changed in the inheriting dictionary. This is
+%% consistent with the semantics of avp_name/2.
+
+c_imported_avp_header({Name, _Code, _Type, _Flags}, Mod, Vs) ->
+ 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.
+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(lists:member($V, Flags), Name, Vs, Vid).
+
+v(true = T, Name, Vs, {module, Mod}) ->
+ v(T, Name, Vs, {ok, {Mod:vendor_id(), Mod:vendor_name()}});
v(true, Name, Vs, Vid) ->
- proplists:get_value(Name, Vs, Vid);
+ case proplists:get_value(Name, Vs) of
+ undefined ->
+ {ok, {Id, _}} = Vid,
+ Id;
+ Id ->
+ Id
+ end;
v(false, _, _, _) ->
undefined.
@@ -656,19 +686,19 @@ empty_value(Spec) ->
Imported = lists:flatmap(fun avps/1, get_value(import_enums, Spec)),
Groups = get_value(grouped, Spec)
++ lists:flatmap(fun avps/1, get_value(import_groups, Spec)),
- Enums = [T || {N,_} = T <- get_value(enums, Spec),
+ Enums = [T || {N,_} = T <- get_value(enum, Spec),
not lists:keymember(N, 1, Imported)]
++ Imported,
lists:map(fun c_empty_value/1, Groups ++ Enums)
++ [{?clause, [?VAR('Name')], [], [?CALL(empty, [?VAR('Name')])]}].
c_empty_value({Name, _, _, _}) ->
- {?clause, [?ATOM(Name)],
+ {?clause, [?Atom(Name)],
[],
- [?CALL(empty_group, [?ATOM(Name)])]};
+ [?CALL(empty_group, [?Atom(Name)])]};
c_empty_value({Name, _}) ->
- {?clause, [?ATOM(Name)],
+ {?clause, [?Atom(Name)],
[],
[?TERM(<<0:32/integer>>)]}.
@@ -678,7 +708,7 @@ c_empty_value({Name, _}) ->
f_dict(Spec) ->
{?function, dict, 0,
- [{?clause, [], [], [?TERM(Spec)]}]}.
+ [{?clause, [], [], [?TERM([?VERSION | Spec])]}]}.
%%% ------------------------------------------------------------------------
%%% # gen_hrl/3
@@ -706,10 +736,10 @@ gen_hrl(Path, Mod, Spec) ->
write("ENUM Macros",
Fd,
- m_enums(PREFIX, false, get_value(enums, Spec))),
+ m_enums(PREFIX, false, get_value(enum, Spec))),
write("DEFINE Macros",
Fd,
- m_enums(PREFIX, false, get_value(defines, Spec))),
+ m_enums(PREFIX, false, get_value(define, Spec))),
lists:foreach(fun({M,Es}) ->
write("ENUM Macros from " ++ atom_to_list(M),
@@ -751,8 +781,8 @@ m_enums(Prefix, Wrap, 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))), "'"],
+ lists:map(fun({A,I}) ->
+ N = ["'", P, to_upper(z(A)), "'"],
wrap(B,
N,
["-define(", N, ", ", integer_to_list(I), ").\n"])
@@ -794,15 +824,15 @@ header() ->
"%%\n\n").
hrl_header(Name) ->
- header() ++ "-hrl_name('" ++ Name ++ ".hrl').\n".
+ header() ++ "-hrl_name('" ++ ?S(Name) ++ ".hrl').\n".
%% avp_info/1
avp_info(Entry) -> %% {Name, Arity}
case Entry of
- {'<',A,'>'} -> {A, 1};
- {A} -> {A, 1};
- [A] -> {A, {0,1}};
+ {{A}} -> {A, 1};
+ {A} -> {A, 1};
+ [A] -> {A, {0,1}};
{Q,T} ->
{A,_} = avp_info(T),
{A, arity(Q)}
@@ -818,10 +848,10 @@ arity(T) -> T.
prefix(Spec) ->
case orddict:find(prefix, Spec) of
{ok, P} ->
- atom_to_list(P) ++ "_";
+ P ++ "_";
error ->
""
end.
rec_name(Name, Prefix) ->
- list_to_atom(Prefix ++ atom_to_list(Name)).
+ Prefix ++ Name.
diff --git a/lib/diameter/src/compiler/diameter_dict_parser.yrl b/lib/diameter/src/compiler/diameter_dict_parser.yrl
new file mode 100644
index 0000000000..6fd4cedd23
--- /dev/null
+++ b/lib/diameter/src/compiler/diameter_dict_parser.yrl
@@ -0,0 +1,324 @@
+%% -*- erlang -*-
+%%
+%% %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%
+%%
+
+%%
+%% A grammar for dictionary specification.
+%%
+
+Nonterminals
+ application_id avp avp_code avp_def avp_defs avp_flags avp_header
+ avp_header_tok avp_name avp_names avp_ref avp_spec avp_type
+ avp_vendor avps bit bits command_def command_id diameter_name
+ dictionary enum_def enum_defs group_def group_defs header header_tok
+ ident idents message_defs module qual section sections.
+
+Terminals
+ avp_types avp_vendor_id codecs custom_types define enum grouped
+ id inherits messages name prefix vendor
+ number word
+ '{' '}' '<' '>' '[' ']' '*' '::=' ':' ',' '-'
+ code
+ 'answer-message'
+ 'AVP' 'AVP-Header'
+ 'Diameter' 'Diameter-Header' 'Header'
+ 'REQ' 'PXY' 'ERR'.
+
+Rootsymbol dictionary.
+
+Endsymbol '$end'.
+
+%% ===========================================================================
+
+dictionary -> sections : '$1'.
+
+sections -> '$empty' : [].
+sections -> section sections : ['$1' | '$2'].
+
+section -> name ident : ['$1', '$2'].
+section -> prefix ident : ['$1', '$2'].
+section -> id number : ['$1', '$2'].
+section -> vendor number ident : ['$1', '$2', '$3'].
+section -> inherits module avp_names : ['$1', '$2' | '$3'].
+section -> avp_types avp_defs : ['$1' | '$2'].
+section -> avp_vendor_id number avp_names : ['$1', '$2' | '$3'].
+section -> custom_types module avp_names : ['$1', '$2' | '$3'].
+section -> codecs module avp_names : ['$1', '$2' | '$3'].
+section -> messages message_defs : ['$1' | '$2'].
+section -> grouped group_defs : ['$1' | '$2'].
+section -> enum ident enum_defs : ['$1', '$2' | '$3'].
+section -> define ident enum_defs : ['$1', '$2' | '$3'].
+
+%% =====================================
+
+module -> ident : '$1'.
+
+avp_names -> idents : '$1'. %% Note: not 'AVP'
+
+avp_defs -> '$empty' : [].
+avp_defs -> avp_def avp_defs : ['$1' | '$2'].
+
+avp_def -> ident number avp_type avp_flags : ['$1', '$2', '$3', '$4'].
+
+avp_type -> ident : '$1'.
+
+idents -> '$empty' : [].
+idents -> ident idents : ['$1' | '$2'].
+
+avp_flags -> '-' :
+ {_, Lineno} = '$1',
+ {word, Lineno, ""}.
+avp_flags -> ident :
+ '$1'.
+%% Could support lowercase here if there's a use for distinguishing
+%% between Must and Should in the future in deciding whether or not
+%% to set a flag.
+
+ident -> word : '$1'.
+
+%% Don't bother mapping reserved words to make these usable in this
+%% context. That an AVP can't be named Diameter-Header is probably no
+%% great loss, and that it can't be named AVP may even save someone
+%% from themselves. (Temporarily at least.)
+
+group_defs -> '$empty' : [].
+group_defs -> group_def group_defs : ['$1' | '$2'].
+
+message_defs -> '$empty' : [].
+message_defs -> command_def message_defs : ['$1' | '$2'].
+
+enum_defs -> '$empty' : [].
+enum_defs -> enum_def enum_defs : ['$1' | '$2'].
+
+enum_def -> ident number : ['$1', '$2'].
+
+%% =====================================
+%% 3.2. Command Code ABNF specification
+%%
+%% Every Command Code defined MUST include a corresponding ABNF
+%% specification, which is used to define the AVPs that MUST or MAY be
+%% present when sending the message. The following format is used in
+%% the definition:
+
+%% command-def = <command-name> "::=" diameter-message
+%%
+%% command-name = diameter-name
+%%
+%% diameter-name = ALPHA *(ALPHA / DIGIT / "-")
+%%
+%% diameter-message = header [ *fixed] [ *required] [ *optional]
+
+%% answer-message is a special case.
+command_def -> 'answer-message' '::=' '<' header_tok ':' code
+ ',' 'ERR' '[' 'PXY' ']' '>'
+ avps
+ : ['$1', false | '$13'].
+
+command_def -> diameter_name '::=' header avps
+ : ['$1', '$3' | '$4'].
+%% Ensure the order fixed/required/optional by semantic checks rather
+%% than grammatically since the latter requires more lookahead: don't
+%% know until after a leading qual which of the three it is that's
+%% being parsed.
+
+diameter_name -> ident : '$1'.
+
+%% header = "<" "Diameter Header:" command-id
+%% [r-bit] [p-bit] [e-bit] [application-id] ">"
+%%
+%% command-id = 1*DIGIT
+%% ; The Command Code assigned to the command
+%%
+%% r-bit = ", REQ"
+%% ; If present, the 'R' bit in the Command
+%% ; Flags is set, indicating that the message
+%% ; is a request, as opposed to an answer.
+%%
+%% p-bit = ", PXY"
+%% ; If present, the 'P' bit in the Command
+%% ; Flags is set, indicating that the message
+%% ; is proxiable.
+%%
+%% e-bit = ", ERR"
+%% ; If present, the 'E' bit in the Command
+%% ; Flags is set, indicating that the answer
+%% ; message contains a Result-Code AVP in
+%% ; the "protocol error" class.
+%%
+%% application-id = 1*DIGIT
+
+header -> '<' header_tok ':' command_id bits application_id '>'
+ : ['$4', '$5', '$6'].
+
+command_id -> number : '$1'.
+
+%% Accept both the form of the base definition and the typo (fixed in
+%% 3588bis) of the grammar.
+header_tok -> 'Diameter' 'Header'.
+header_tok -> 'Diameter-Header'.
+
+bits -> '$empty' : [].
+bits -> ',' bit bits : ['$2' | '$3'].
+
+%% ERR only makes sense for answer-message so don't allow it here
+%% (despite 3588).
+bit -> 'REQ' : '$1'.
+bit -> 'PXY' : '$1'.
+
+application_id -> '$empty' : false.
+application_id -> number : '$1'.
+
+%% fixed = [qual] "<" avp-spec ">"
+%% ; Defines the fixed position of an AVP
+%%
+%% required = [qual] "{" avp-spec "}"
+%% ; The AVP MUST be present and can appear
+%% ; anywhere in the message.
+%%
+%% optional = [qual] "[" avp-name "]"
+%% ; The avp-name in the 'optional' rule cannot
+%% ; evaluate to any AVP Name which is included
+%% ; in a fixed or required rule. The AVP can
+%% ; appear anywhere in the message.
+%% ;
+%% ; NOTE: "[" and "]" have a slightly different
+%% ; meaning than in ABNF (RFC 5234]). These braces
+%% ; cannot be used to express optional fixed rules
+%% ; (such as an optional ICV at the end). To do this,
+%% ; the convention is '0*1fixed'.
+
+avps -> '$empty' : [].
+avps -> avp avps : ['$1' | '$2'].
+
+avp -> avp_ref : [false | '$1'].
+avp -> qual avp_ref : ['$1' | '$2'].
+
+avp_ref -> '<' avp_spec '>' : [$<, '$2'].
+avp_ref -> '{' avp_name '}' : [${, '$2'].
+avp_ref -> '[' avp_name ']' : [$[, '$2'].
+%% Note that required can be an avp_name, not just avp_spec. 'AVP'
+%% is specified as required by Failed-AVP for example.
+
+%% qual = [min] "*" [max]
+%% ; See ABNF conventions, RFC 5234 Section 4.
+%% ; The absence of any qualifiers depends on
+%% ; whether it precedes a fixed, required, or
+%% ; optional rule. If a fixed or required rule has
+%% ; no qualifier, then exactly one such AVP MUST
+%% ; be present. If an optional rule has no
+%% ; qualifier, then 0 or 1 such AVP may be
+%% ; present. If an optional rule has a qualifier,
+%% ; then the value of min MUST be 0 if present.
+%%
+%% min = 1*DIGIT
+%% ; The minimum number of times the element may
+%% ; be present. If absent, the default value is zero
+%% ; for fixed and optional rules and one for required
+%% ; rules. The value MUST be at least one for for
+%% ; required rules.
+%%
+%% max = 1*DIGIT
+%% ; The maximum number of times the element may
+%% ; be present. If absent, the default value is
+%% ; infinity. A value of zero implies the AVP MUST
+%% ; NOT be present.
+
+qual -> number '*' number : {'$1', '$3'}.
+qual -> number '*' : {'$1', true}.
+qual -> '*' number : {true, '$2'}.
+qual -> '*' : true.
+
+%% avp-spec = diameter-name
+%% ; The avp-spec has to be an AVP Name, defined
+%% ; in the base or extended Diameter
+%% ; specifications.
+
+avp_spec -> diameter_name : '$1'.
+
+%% avp-name = avp-spec / "AVP"
+%% ; The string "AVP" stands for *any* arbitrary AVP
+%% ; Name, not otherwise listed in that command code
+%% ; definition. Addition this AVP is recommended for
+%% ; all command ABNFs to allow for extensibility.
+
+avp_name -> 'AVP' : '$1'.
+avp_name -> avp_spec : '$1'.
+
+%% The following is a definition of a fictitious command code:
+%%
+%% Example-Request ::= < Diameter Header: 9999999, REQ, PXY >
+%% { User-Name }
+%% * { Origin-Host }
+%% * [ AVP ]
+
+%% =====================================
+%% 4.4. Grouped AVP Values
+%%
+%% The Diameter protocol allows AVP values of type 'Grouped'. This
+%% implies that the Data field is actually a sequence of AVPs. It is
+%% possible to include an AVP with a Grouped type within a Grouped type,
+%% that is, to nest them. AVPs within an AVP of type Grouped have the
+%% same padding requirements as non-Grouped AVPs, as defined in Section
+%% 4.
+%%
+%% The AVP Code numbering space of all AVPs included in a Grouped AVP is
+%% the same as for non-grouped AVPs. Receivers of a Grouped AVP that
+%% does not have the 'M' (mandatory) bit set and one or more of the
+%% encapsulated AVPs within the group has the 'M' (mandatory) bit set
+%% MAY simply be ignored if the Grouped AVP itself is unrecognized. The
+%% rule applies even if the encapsulated AVP with its 'M' (mandatory)
+%% bit set is further encapsulated within other sub-groups; i.e. other
+%% Grouped AVPs embedded within the Grouped AVP.
+%%
+%% Every Grouped AVP defined MUST include a corresponding grammar, using
+%% ABNF [RFC5234] (with modifications), as defined below.
+
+%% grouped-avp-def = <name> "::=" avp
+%%
+%% name-fmt = ALPHA *(ALPHA / DIGIT / "-")
+%%
+%% name = name-fmt
+%% ; The name has to be the name of an AVP,
+%% ; defined in the base or extended Diameter
+%% ; specifications.
+%%
+%% avp = header [ *fixed] [ *required] [ *optional]
+
+group_def -> ident '::=' avp_header avps : ['$1', '$3' | '$4'].
+
+%% header = "<" "AVP-Header:" avpcode [vendor] ">"
+%%
+%% avpcode = 1*DIGIT
+%% ; The AVP Code assigned to the Grouped AVP
+%%
+%% vendor = 1*DIGIT
+%% ; The Vendor-ID assigned to the Grouped AVP.
+%% ; If absent, the default value of zero is
+%% ; used.
+
+avp_header -> '<' avp_header_tok ':' avp_code avp_vendor '>'
+ : ['$4', '$5'].
+
+avp_header_tok -> 'AVP-Header'.
+avp_header_tok -> 'AVP' 'Header'.
+
+avp_code -> number : '$1'.
+
+avp_vendor -> '$empty' : false.
+avp_vendor -> number : '$1'.
diff --git a/lib/diameter/src/compiler/diameter_dict_scanner.erl b/lib/diameter/src/compiler/diameter_dict_scanner.erl
new file mode 100644
index 0000000000..963b58fdfc
--- /dev/null
+++ b/lib/diameter/src/compiler/diameter_dict_scanner.erl
@@ -0,0 +1,274 @@
+%%
+%% %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_dict_scanner).
+
+%%
+%% A scanner for dictionary files of the form expected by yecc.
+%%
+
+-export([scan/1,
+ format_error/1]).
+
+-export([is_name/1]).
+
+%% -----------------------------------------------------------
+%% # scan/1
+%% -----------------------------------------------------------
+
+-spec scan(string()) -> {ok, [Token]} | {error, {atom(), string(), Lineno}}
+ when Token :: {word, Lineno, string()}
+ | {number, Lineno, non_neg_integer()}
+ | {Symbol, Lineno},
+ Lineno :: pos_integer(),
+ Symbol :: '{' | '}' | '<' | '>' | '[' | ']'
+ | '*' | '::=' | ':' | ',' | '-'
+ | avp_types
+ | avp_vendor_id
+ | codecs
+ | custom_types
+ | define
+ | grouped
+ | id
+ | inherits
+ | messages
+ | name
+ | prefix
+ | vendor
+ | '$end'
+ | code
+ | 'answer-message'
+ | 'AVP'
+ | 'AVP-Header'
+ | 'Diameter'
+ | 'Diameter-Header'
+ | 'Header'
+ | 'REQ'
+ | 'PXY'
+ | 'ERR'.
+
+scan(B)
+ when is_binary(B) ->
+ scan(binary_to_list(B));
+scan(S) ->
+ scan(S, {1, []}).
+
+scan(S, {Lineno, Acc}) ->
+ case split(S) of
+ '$end' = E ->
+ {ok, lists:reverse([{E, Lineno} | Acc])};
+ {Tok, Rest} ->
+ scan(Rest, acc(Tok, Lineno, Acc));
+ Reason when is_list(Reason) ->
+ {error, {Reason, S, Lineno}}
+ end.
+
+%% format_error/1
+
+format_error({Reason, Input, Lineno}) ->
+ io_lib:format("~s at line ~p: ~s",
+ [Reason, Lineno, head(Input, [], 20, true)]).
+
+%% is_name/1
+
+is_name([H|T]) ->
+ is_alphanum(H) andalso lists:all(fun is_name_ch/1, T).
+
+%% ===========================================================================
+
+head(Str, Acc, N, _)
+ when [] == Str;
+ 0 == N;
+ $\r == hd(Str);
+ $\n == hd(Str) ->
+ lists:reverse(Acc);
+head([C|Rest], Acc, N, true = T) %% skip leading whitespace
+ when C == $\s;
+ C == $\t;
+ C == $\f;
+ C == $\v ->
+ head(Rest, Acc, N, T);
+head([C|Rest], Acc, N, _) ->
+ head(Rest, [C|Acc], N-1, false).
+
+acc(endline, Lineno, Acc) ->
+ {Lineno + 1, Acc};
+acc(T, Lineno, Acc) ->
+ {Lineno, [tok(T, Lineno) | Acc]}.
+
+tok({Cat, Sym}, Lineno) ->
+ {Cat, Lineno, Sym};
+tok(Sym, Lineno) ->
+ {Sym, Lineno}.
+
+%% # split/1
+%%
+%% Output: {Token, Rest} | atom()
+
+%% Finito.
+split("") ->
+ '$end';
+
+%% Skip comments. This precludes using semicolon for any other purpose.
+split([$;|T]) ->
+ split(lists:dropwhile(fun(C) -> not is_eol_ch(C) end, T));
+
+%% Beginning of a section.
+split([$@|T]) ->
+ {Name, Rest} = lists:splitwith(fun is_name_ch/1, T),
+ case section(Name) of
+ false ->
+ "Unknown section";
+ 'end' ->
+ '$end';
+ A ->
+ {A, Rest}
+ end;
+
+split("::=" ++ T) ->
+ {'::=', T};
+
+split([H|T])
+ when H == ${; H == $};
+ H == $<; H == $>;
+ H == $[; H == $];
+ H == $*; H == $:; H == $,; H == $- ->
+ {list_to_atom([H]), T};
+
+%% RFC 3588 requires various names to begin with a letter but 3GPP (for
+%% one) abuses this. (eg 3GPP-Charging-Id in TS32.299.)
+split([H|_] = L) when $0 =< H, H =< $9 ->
+ {P, Rest} = splitwith(fun is_name_ch/1, L),
+ Tok = try
+ {number, read_int(P)}
+ catch
+ error:_ ->
+ word(P)
+ end,
+ {Tok, Rest};
+
+split([H|_] = L) when $a =< H, H =< $z;
+ $A =< H, H =< $Z ->
+ {P, Rest} = splitwith(fun is_name_ch/1, L),
+ {word(P), Rest};
+
+split([$'|T]) ->
+ case splitwith(fun(C) -> not lists:member(C, "'\r\n") end, T) of
+ {[_|_] = A, [$'|Rest]} ->
+ {{word, A}, Rest};
+ {[], [$'|_]} ->
+ "Empty string";
+ _ -> %% not terminated on same line
+ "Unterminated string"
+ end;
+
+%% Line ending of various forms.
+split([$\r,$\n|T]) ->
+ {endline, T};
+split([C|T])
+ when C == $\r;
+ C == $\n ->
+ {endline, T};
+
+%% Ignore whitespace.
+split([C|T])
+ when C == $\s;
+ C == $\t;
+ C == $\f;
+ C == $\v ->
+ split(T);
+
+split(_) ->
+ "Unexpected character".
+
+%% word/1
+
+%% Reserved words significant in parsing ...
+word(S)
+ when S == "answer-message";
+ S == "code";
+ S == "AVP";
+ S == "AVP-Header";
+ S == "Diameter";
+ S == "Diameter-Header";
+ S == "Header";
+ S == "REQ";
+ S == "PXY";
+ S == "ERR" ->
+ list_to_atom(S);
+
+%% ... or not.
+word(S) ->
+ {word, S}.
+
+%% section/1
+
+section(N)
+ when N == "avp_types";
+ N == "avp_vendor_id";
+ N == "codecs";
+ N == "custom_types";
+ N == "define";
+ N == "end";
+ N == "enum";
+ N == "grouped";
+ N == "id";
+ N == "inherits";
+ N == "messages";
+ N == "name";
+ N == "prefix";
+ N == "vendor" ->
+ list_to_atom(N);
+section(_) ->
+ false.
+
+%% read_int/1
+
+read_int([$0,X|S])
+ when X == $X;
+ X == $x ->
+ {ok, [N], []} = io_lib:fread("~16u", S),
+ N;
+
+read_int(S) ->
+ list_to_integer(S).
+
+%% splitwith/3
+
+splitwith(Fun, [H|T]) ->
+ {SH, ST} = lists:splitwith(Fun, T),
+ {[H|SH], ST}.
+
+is_eol_ch(C) ->
+ C == $\n orelse C == $\r.
+
+is_name_ch(C) ->
+ is_alphanum(C) orelse C == $- orelse C == $_.
+
+is_alphanum(C) ->
+ is_lower(C) orelse is_upper(C) orelse is_digit(C).
+
+is_lower(C) ->
+ $a =< C andalso C =< $z.
+
+is_upper(C) ->
+ $A =< C andalso C =< $Z.
+
+is_digit(C) ->
+ $0 =< C andalso C =< $9.
diff --git a/lib/diameter/src/compiler/diameter_dict_util.erl b/lib/diameter/src/compiler/diameter_dict_util.erl
new file mode 100644
index 0000000000..2207925e49
--- /dev/null
+++ b/lib/diameter/src/compiler/diameter_dict_util.erl
@@ -0,0 +1,1302 @@
+%%
+%% %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%
+%%
+
+%%
+%% This module turns a dictionary file into the orddict that
+%% diameter_codegen.erl in turn morphs into .erl and .hrl files for
+%% encode and decode of Diameter messages and AVPs.
+%%
+
+-module(diameter_dict_util).
+
+-export([parse/2,
+ format_error/1,
+ format/1]).
+
+-include("diameter_vsn.hrl").
+
+-define(RETURN(T), throw({T, ?MODULE, ?LINE})).
+-define(RETURN(T, Args), ?RETURN({T, Args})).
+
+-define(A, list_to_atom).
+-define(L, atom_to_list).
+-define(I, integer_to_list).
+-define(F, io_lib:format).
+
+%% ===========================================================================
+%% parse/2
+%% ===========================================================================
+
+-spec parse(File, Opts)
+ -> {ok, orddict:orddict()}
+ | {error, term()}
+ when File :: {path, string()}
+ | iolist()
+ | binary(),
+ Opts :: list().
+
+parse(File, Opts) ->
+ putr(verbose, lists:member(verbose, Opts)),
+ try
+ {ok, do_parse(File, Opts)}
+ catch
+ {Reason, ?MODULE, _Line} ->
+ {error, Reason}
+ after
+ eraser(verbose)
+ end.
+
+%% ===========================================================================
+%% format_error/1
+%% ===========================================================================
+
+format_error({read, Reason}) ->
+ file:format_error(Reason);
+format_error({scan, Reason}) ->
+ diameter_dict_scanner:format_error(Reason);
+format_error({parse, {Line, _Mod, Reason}}) ->
+ lists:flatten(["Line ", ?I(Line), ", ", Reason]);
+
+format_error(T) ->
+ {Fmt, As} = fmt(T),
+ lists:flatten(io_lib:format(Fmt, As)).
+
+fmt({avp_code_already_defined = E, [Code, false, Name, Line, L]}) ->
+ {fmt(E), [Code, "", Name, Line, L]};
+fmt({avp_code_already_defined = E, [Code, Vid, Name, Line, L]}) ->
+ {fmt(E), [Code, ?F("/~p", [Vid]), Name, Line, L]};
+
+fmt({uint32_out_of_range = E, [id | T]}) ->
+ {fmt(E), ["@id", "application identifier" | T]};
+fmt({uint32_out_of_range = E, [K | T]})
+ when K == vendor;
+ K == avp_vendor_id ->
+ {fmt(E), [?F("@~p", [K]), "vendor id" | T]};
+fmt({uint32_out_of_range = E, [K, Name | T]})
+ when K == enum;
+ K == define ->
+ {fmt(E), [?F("@~p ~s", [K, Name]), "value" | T]};
+fmt({uint32_out_of_range = E, [avp_types, Name | T]}) ->
+ {fmt(E), ["AVP " ++ Name, "AVP code" | T]};
+fmt({uint32_out_of_range = E, [grouped, Name | T]}) ->
+ {fmt(E), ["Grouped AVP " ++ Name | T]};
+fmt({uint32_out_of_range = E, [messages, Name | T]}) ->
+ {fmt(E), ["Message " ++ Name, "command code" | T]};
+
+fmt({Reason, As}) ->
+ {fmt(Reason), As};
+
+fmt(avp_code_already_defined) ->
+ "AVP ~p~s (~s) at line ~p already defined at line ~p";
+
+fmt(uint32_out_of_range) ->
+ "~s specifies ~s ~p at line ~p that is out of range for a value of "
+ "Diameter type Unsigned32";
+
+fmt(imported_avp_already_defined) ->
+ "AVP ~s imported by @inherits ~p at line ~p defined at line ~p";
+fmt(duplicate_import) ->
+ "AVP ~s is imported by more than one @inherits, both at line ~p "
+ "and at line ~p";
+
+fmt(duplicate_section) ->
+ "Section @~s at line ~p already declared at line ~p";
+
+fmt(already_declared) ->
+ "Section @~p ~s at line ~p already declared at line ~p";
+
+fmt(inherited_avp_already_defined) ->
+ "AVP ~s inherited at line ~p defined in @avp_types at line ~p";
+fmt(avp_already_defined) ->
+ "AVP ~s at line ~p already in @~p at line ~p";
+fmt(key_already_defined) ->
+ "Value for ~s:~s in @~p at line ~p already provided at line ~p";
+
+fmt(messages_without_id) ->
+ "@messages at line ~p but @id not declared";
+
+fmt(avp_name_already_defined) ->
+ "AVP ~s at line ~p already defined at line ~p";
+fmt(avp_has_unknown_type) ->
+ "AVP ~s at line ~p defined with unknown type ~s";
+fmt(avp_has_invalid_flag) ->
+ "AVP ~s at line ~p specifies invalid flag ~c";
+fmt(avp_has_duplicate_flag) ->
+ "AVP ~s at line ~p specifies duplicate flag ~c";
+fmt(avp_has_vendor_id) ->
+ "AVP ~s at line ~p does not specify V flag "
+ "but is assigned vendor id ~p at line ~p";
+fmt(avp_has_no_vendor) ->
+ "AVP ~s at line ~p specifies V flag "
+ "but neither @vendor_avp_id nor @vendor supplies a value";
+
+fmt(group_already_defined) ->
+ "Group ~s at line ~p already defined at line ~p";
+fmt(grouped_avp_code_mismatch) ->
+ "AVP ~s at line ~p has with code ~p "
+ "but @avp_types specifies ~p at line ~p";
+fmt(grouped_avp_has_wrong_type) ->
+ "Grouped AVP ~s at line ~p defined with type ~s at line ~p";
+fmt(grouped_avp_not_defined) ->
+ "Grouped AVP ~s on line ~p not defined in @avp_types";
+fmt(grouped_vendor_id_without_flag) ->
+ "Grouped AVP ~s at line ~p has vendor id "
+ "but definition at line ~p does not specify V flag";
+fmt(grouped_vendor_id_mismatch) ->
+ "Grouped AVP ~s at line ~p has vendor id ~p "
+ "but ~p specified at line ~p";
+
+fmt(message_name_already_defined) ->
+ "Message ~s at line ~p already defined at line ~p";
+fmt(message_code_already_defined) ->
+ "~s message with code ~p at line ~p already defined at line ~p";
+fmt(message_has_duplicate_flag) ->
+ "Message ~s has duplicate flag ~s at line ~p";
+fmt(message_application_id_mismatch) ->
+ "Message ~s has application id ~p at line ~p "
+ "but @id specifies ~p at line ~p";
+
+fmt(invalid_avp_order) ->
+ "AVP reference ~c~s~c at line ~p breaks fixed/required/optional order";
+fmt(invalid_qualifier) ->
+ "Qualifier ~p*~p at line ~p has Min > Max";
+fmt(avp_already_referenced) ->
+ "AVP ~s at line ~p already referenced at line ~p";
+
+fmt(message_missing) ->
+ "~s message at line ~p but no ~s message is defined";
+
+fmt(requested_avp_not_found) ->
+ "@inherit ~s at line ~p requests AVP ~s at line ~p "
+ "but module does not define that AVP";
+
+fmt(enumerated_avp_has_wrong_local_type) ->
+ "Enumerated AVP ~s in @enum at line ~p defined with type ~s at line ~p";
+fmt(enumerated_avp_has_wrong_inherited_type) ->
+ "Enumerated AVP ~s in @enum at line ~p "
+ "inherited with type ~s from module ~s at line ~p";
+fmt(enumerated_avp_not_defined) ->
+ "Enumerated AVP ~s in @enum at line ~p neither defined nor inherited";
+
+fmt(avp_not_defined) ->
+ "AVP ~s referenced at line ~p neither defined nor inherited";
+
+fmt(recompile) ->
+ "Module ~p appears to have been compiler with an incompatible "
+ "version of the dictionary compiler and must be recompiled";
+fmt(not_loaded) ->
+ "Module ~p is not on the code path or could not be loaded";
+fmt(no_dict) ->
+ "Module ~p does not appear to be a diameter dictionary".
+
+%% ===========================================================================
+%% format/1
+%%
+%% Turn dict/0 output back into a dictionary file (with line ending = $\n).
+
+-spec format(Dict)
+ -> iolist()
+ when Dict :: orddict:orddict().
+
+-define(KEYS, [id, name, prefix, vendor,
+ inherits, codecs, custom_types,
+ avp_types,
+ messages,
+ grouped,
+ enum, define]).
+
+format(Dict) ->
+ Io = orddict:fold(fun io/3, [], Dict),
+ [S || {_,S} <- lists:sort(fun keysort/2, Io)].
+
+keysort({A,_}, {B,_}) ->
+ [HA, HB] = [H || K <- [A,B],
+ H <- [lists:takewhile(fun(X) -> X /= K end, ?KEYS)]],
+ HA < HB.
+
+%% ===========================================================================
+
+-define(INDENT, " ").
+-define(SP, " ").
+-define(NL, $\n).
+
+%% io/3
+
+io(K, _, Acc)
+ when K == command_codes;
+ K == import_avps;
+ K == import_groups;
+ K == import_enums ->
+ Acc;
+
+io(Key, Body, Acc) ->
+ [{Key, io(Key, Body)} | Acc].
+
+%% io/2
+
+io(K, Id)
+ when K == id;
+ K == name;
+ K == prefix ->
+ [?NL, section(K), ?SP, tok(Id)];
+
+io(vendor = K, {Id, Name}) ->
+ [?NL, section(K) | [[?SP, tok(X)] || X <- [Id, Name]]];
+
+io(avp_types = K, Body) ->
+ [?NL, ?NL, section(K), ?NL, [body(K,A) || A <- Body]];
+
+io(K, Body)
+ when K == messages;
+ K == grouped ->
+ [?NL, ?NL, section(K), [body(K,A) || A <- Body]];
+
+io(K, Body)
+ when K == avp_vendor_id;
+ K == inherits;
+ K == custom_types;
+ K == codecs;
+ K == enum;
+ K == define ->
+ [[?NL, pairs(K, T)] || T <- Body].
+
+pairs(K, {Id, Avps}) ->
+ [?NL, section(K), ?SP, tok(Id), ?NL, [[?NL, body(K, A)] || A <- Avps]].
+
+body(K, AvpName)
+ when K == avp_vendor_id;
+ K == inherits;
+ K == custom_types;
+ K == codecs ->
+ [?INDENT, word(AvpName)];
+
+body(K, {Name, N})
+ when K == enum;
+ K == define ->
+ [?INDENT, word(Name), ?SP, ?I(N)];
+
+body(avp_types = K, {Name, Code, Type, ""}) ->
+ body(K, {Name, Code, Type, "-"});
+body(avp_types, {Name, Code, Type, Flags}) ->
+ [?NL, ?INDENT, word(Name),
+ [[?SP, ?SP, S] || S <- [?I(Code), Type, Flags]]];
+
+body(messages, {"answer-message", _, _, [], Avps}) ->
+ [?NL, ?NL, ?INDENT,
+ "answer-message ::= < Diameter Header: code, ERR [PXY] >",
+ f_avps(Avps)];
+body(messages, {Name, Code, Flags, ApplId, Avps}) ->
+ [?NL, ?NL, ?INDENT, word(Name), " ::= ", header(Code, Flags, ApplId),
+ f_avps(Avps)];
+
+body(grouped, {Name, Code, Vid, Avps}) ->
+ [?NL, ?NL, ?INDENT, word(Name), " ::= ", avp_header(Code, Vid),
+ f_avps(Avps)].
+
+header(Code, Flags, ApplId) ->
+ ["< Diameter Header: ",
+ ?I(Code),
+ [[", ", ?L(F)] || F <- Flags],
+ [[" ", ?I(N)] || N <- ApplId],
+ " >"].
+
+avp_header(Code, Vid) ->
+ ["< AVP Header: ",
+ ?I(Code),
+ [[" ", ?I(V)] || V <- Vid],
+ " >"].
+
+f_avps(L) ->
+ [[?NL, ?INDENT, ?INDENT, f_avp(A)] || A <- L].
+
+f_avp({Q, A}) ->
+ f_avp(f_qual(Q), f_delim(A));
+f_avp(A) ->
+ f_avp("", f_delim(A)).
+
+f_delim({{A}}) ->
+ [$<, word(A), $>];
+f_delim({A}) ->
+ [${, word(A), $}];
+f_delim([A]) ->
+ [$[, word(A), $]].
+
+f_avp(Q, Avp) ->
+ Len = length(lists:flatten([Q])),
+ [io_lib:format("~*s", [-1*max(Len+1, 6) , Q]), Avp].
+
+f_qual('*') ->
+ "*";
+f_qual({'*', N}) ->
+ [$*, ?I(N)];
+f_qual({N, '*'}) ->
+ [?I(N), $*];
+f_qual({M,N}) ->
+ [?I(M), $*, ?I(N)].
+
+section(Key) ->
+ ["@", ?L(Key)].
+
+tok(N)
+ when is_integer(N) ->
+ ?I(N);
+tok(N) ->
+ word(N).
+
+word(Str) ->
+ word(diameter_dict_scanner:is_name(Str), Str).
+
+word(true, Str) ->
+ Str;
+word(false, Str) ->
+ [$', Str, $'].
+
+%% ===========================================================================
+
+do_parse(File, Opts) ->
+ Bin = do([fun read/1, File], read),
+ Toks = do([fun diameter_dict_scanner:scan/1, Bin], scan),
+ Tree = do([fun diameter_dict_parser:parse/1, Toks], parse),
+ make_dict(Tree, Opts).
+
+do([F|A], E) ->
+ case apply(F,A) of
+ {ok, T} ->
+ T;
+ {error, Reason} ->
+ ?RETURN({E, Reason})
+ end.
+
+read({path, Path}) ->
+ file:read_file(Path);
+read(File) ->
+ {ok, iolist_to_binary([File])}.
+
+make_dict(Parse, Opts) ->
+ make_orddict(pass4(pass3(pass2(pass1(reset(make_dict(Parse),
+ Opts))),
+ Opts))).
+
+%% make_orddict/1
+
+make_orddict(Dict) ->
+ dict:fold(fun mo/3,
+ orddict:from_list([{K,[]} || K <- [avp_types,
+ messages,
+ grouped,
+ inherits,
+ custom_types,
+ codecs,
+ avp_vendor_id,
+ enum,
+ define]]),
+ Dict).
+
+mo(K, Sects, Dict)
+ when is_atom(K) ->
+ orddict:store(K, make(K, Sects), Dict);
+
+mo(_, _, Dict) ->
+ Dict.
+
+make(K, [[_Line, {_, _, X}]])
+ when K == id;
+ K == name;
+ K == prefix ->
+ X;
+
+make(vendor, [[_Line, {_, _, Id}, {_, _, Name}]]) ->
+ {Id, Name};
+
+make(K, T)
+ when K == command_codes;
+ K == import_avps;
+ K == import_groups;
+ K == import_enums ->
+ T;
+
+make(K, Sects) ->
+ post(K, foldl(fun([_L|B], A) -> make(K,B,A) end,
+ [],
+ Sects)).
+
+post(avp_types, L) ->
+ lists:sort(L);
+
+post(K, L)
+ when K == grouped;
+ K == messages;
+ K == enum;
+ K == define ->
+ lists:reverse(L);
+
+post(_, L) ->
+ L.
+
+make(K, [{_,_,Name} | Body], Acc)
+ when K == enum;
+ K == define;
+ K == avp_vendor_id;
+ K == custom_types;
+ K == inherits;
+ K == codecs ->
+ [{Name, mk(K, Body)} | Acc];
+
+make(K, Body, Acc) ->
+ foldl(fun(T,A) -> [mk(K, T) | A] end, Acc, Body).
+
+mk(avp_types, [{_,_,Name}, {_,_,Code}, {_,_,Type}, {_,_,Flags}]) ->
+ {Name, Code, type(Type), Flags};
+
+mk(messages, [{'answer-message' = A, _}, false | Avps]) ->
+ {?L(A), -1, ['ERR', 'PXY'], [], make_body(Avps)};
+
+mk(messages, [{_,_,Name}, [{_,_,Code}, Flags, ApplId] | Avps]) ->
+ {Name,
+ Code,
+ lists:map(fun({F,_}) -> F end, Flags),
+ opt(ApplId),
+ make_body(Avps)};
+
+mk(grouped, [{_,_,Name}, [{_,_,Code}, Vid] | Avps]) ->
+ {Name, Code, opt(Vid), make_body(Avps)};
+
+mk(K, Body)
+ when K == enum;
+ K == define ->
+ lists:map(fun([{_,_,Name}, {_,_,Value}]) -> {Name, Value} end, Body);
+
+mk(K, Avps)
+ when K == avp_vendor_id;
+ K == custom_types;
+ K == inherits;
+ K == codecs ->
+ lists:map(fun({_,_,N}) -> N end, Avps).
+
+opt(false) ->
+ [];
+opt({_,_,X}) ->
+ [X].
+
+make_body(Avps) ->
+ lists:map(fun avp/1, Avps).
+
+avp([false, D, Avp]) ->
+ avp(D, Avp);
+avp([Q, D, Avp]) ->
+ {qual(Q), avp(D, Avp)}.
+
+avp(D, {'AVP', _}) ->
+ delim(D, "AVP");
+avp(D, {_, _, Name}) ->
+ delim(D, Name).
+
+delim($<, N) ->
+ {{N}};
+delim(${, N) ->
+ {N};
+delim($[, N) ->
+ [N].
+
+qual({true, {_,_,N}}) ->
+ {'*', N};
+qual({{_,_,N}, true}) ->
+ {N, '*'};
+qual({{_,_,N},{_,_,M}}) ->
+ {N, M};
+qual(true) ->
+ '*'.
+
+%% Optional reports when running verbosely.
+report(What, [F | A])
+ when is_function(F) ->
+ report(What, apply(F, A));
+report(What, Data) ->
+ report(getr(verbose), What, Data).
+
+report(true, Tag, Data) ->
+ io:format("##~n## ~p ~p~n", [Tag, Data]);
+report(false, _, _) ->
+ ok.
+
+%% ------------------------------------------------------------------------
+%% make_dict/1
+%%
+%% Turn a parsed dictionary into an dict.
+
+make_dict(Parse) ->
+ foldl(fun(T,A) ->
+ report(section, T),
+ section(T,A)
+ end,
+ dict:new(),
+ Parse).
+
+section([{T, L} | Rest], Dict)
+ when T == name;
+ T == prefix;
+ T == id;
+ T == vendor ->
+ case find(T, Dict) of
+ [] ->
+ dict:store(T, [[L | Rest]], Dict);
+ [[Line | _]] ->
+ ?RETURN(duplicate_section, [T, L, Line])
+ end;
+
+section([{T, L} | Rest], Dict)
+ when T == avp_types;
+ T == messages;
+ T == grouped;
+ T == inherits;
+ T == custom_types;
+ T == codecs;
+ T == avp_vendor_id;
+ T == enum;
+ T == define ->
+ dict:append(T, [L | Rest], Dict).
+
+%% ===========================================================================
+%% reset/2
+%%
+%% Reset sections from options.
+
+reset(Dict, Opts) ->
+ foldl([fun reset/3, Opts], Dict, [name, prefix, inherits]).
+
+reset(K, Dict, Opts) ->
+ foldl(fun opt/2, Dict, [T || {A,_} = T <- Opts, A == K]).
+
+opt({inherits = Key, "-"}, Dict) ->
+ dict:erase(Key, Dict);
+opt({inherits = Key, Mod}, Dict) ->
+ dict:append(Key, [0, {word, 0, Mod}], Dict);
+opt({Key, Val}, Dict) ->
+ dict:store(Key, [0, {word, 0, Val}], Dict);
+opt(_, Dict) ->
+ Dict.
+
+%% ===========================================================================
+%% pass1/1
+%%
+%% Explode sections into additional dictionary entries plus semantic
+%% checks.
+
+pass1(Dict) ->
+ true = no_messages_without_id(Dict),
+
+ foldl(fun(K,D) -> foldl([fun p1/3, K], D, find(K,D)) end,
+ Dict,
+ [id,
+ vendor,
+ avp_types, %% must precede inherits, grouped, enum
+ avp_vendor_id,
+ custom_types,
+ codecs,
+ inherits,
+ grouped,
+ messages,
+ enum,
+ define]).
+
+%% Multiple sections are allowed as long as their bodies don't
+%% overlap. (Except enum/define.)
+
+p1([_Line, N], Dict, id = K) ->
+ true = is_uint32(N, [K]),
+ Dict;
+
+p1([_Line, Id, _Name], Dict, vendor = K) ->
+ true = is_uint32(Id, [K]),
+ Dict;
+
+p1([_Line, X | Body], Dict, K)
+ when K == avp_vendor_id;
+ K == custom_types;
+ K == codecs;
+ K == inherits ->
+ foldl([fun explode/4, X, K], Dict, Body);
+
+p1([_Line, X | Body], Dict, K)
+ when K == define;
+ K == enum ->
+ {_, L, Name} = X,
+ foldl([fun explode2/4, X, K],
+ store_new({K, Name},
+ [L, Body],
+ Dict,
+ [K, Name, L],
+ already_declared),
+ Body);
+
+p1([_Line | Body], Dict, K)
+ when K == avp_types;
+ K == grouped;
+ K == messages ->
+ foldl([fun explode/3, K], Dict, Body).
+
+no_messages_without_id(Dict) ->
+ case find(messages, Dict) of
+ [] ->
+ true;
+ [[Line | _] | _] ->
+ [] /= find(id, Dict) orelse ?RETURN(messages_without_id, [Line])
+ end.
+
+%% Note that the AVP's in avp_vendor_id, custom_types, codecs and
+%% enum can all be inherited, as can the AVP content of messages and
+%% grouped AVP's. Check that the referenced AVP's exist after
+%% importing definitions.
+
+%% explode/4
+%%
+%% {avp_vendor_id, AvpName} -> [Lineno, Id::integer()]
+%% {custom_types|codecs|inherits, AvpName} -> [Lineno, Mod::string()]
+
+explode({_, Line, AvpName}, Dict, {_, _, X} = T, K) ->
+ true = K /= avp_vendor_id orelse is_uint32(T, [K]),
+ true = K /= inherits orelse avp_not_local(AvpName, Line, Dict),
+
+ store_new({key(K), AvpName},
+ [Line, X],
+ Dict,
+ [AvpName, Line, K],
+ avp_already_defined).
+
+%% explode2/4
+
+%% {define, {Name, Key}} -> [Lineno, Value::integer(), enum|define]
+
+explode2([{_, Line, Key}, {_, _, Value} = T], Dict, {_, _, Name}, K) ->
+ true = is_uint32(T, [K, Name]),
+
+ store_new({key(K), {Name, Key}},
+ [Line, Value, K],
+ Dict,
+ [Name, Key, K, Line],
+ key_already_defined).
+
+%% key/1
+%%
+%% Conflate keys that are equivalent as far as uniqueness of
+%% definition goes.
+
+key(K)
+ when K == enum;
+ K == define ->
+ define;
+key(K)
+ when K == custom_types;
+ K == codecs ->
+ custom;
+key(K) ->
+ K.
+
+%% explode/3
+
+%% {avp_types, AvpName} -> [Line | Toks]
+%% {avp_types, {Code, IsReq}} -> [Line, AvpName]
+%%
+%% where AvpName = string()
+%% Code = integer()
+%% IsReq = boolean()
+
+explode([{_, Line, Name} | Toks], Dict0, avp_types = K) ->
+ %% Each AVP can be defined only once.
+ Dict = store_new({K, Name},
+ [Line | Toks],
+ Dict0,
+ [Name, Line],
+ avp_name_already_defined),
+
+ [{number, _, _Code} = C, {word, _, Type}, {word, _, _Flags}] = Toks,
+
+ true = avp_type_known(Type, Name, Line),
+ true = is_uint32(C, [K, Name]),
+
+ Dict;
+
+%% {grouped, Name} -> [Line, HeaderTok | AvpToks]
+%% {grouped, {Name, AvpName}} -> [Line, Qual, Delim]
+%%
+%% where Name = string()
+%% AvpName = string()
+%% Qual = {Q, Q} | boolean()
+%% Q = true | NumberTok
+%% Delim = $< | ${ | $[
+
+explode([{_, Line, Name}, Header | Avps], Dict0, grouped = K) ->
+ Dict = store_new({K, Name},
+ [Line, Header | Avps],
+ Dict0,
+ [Name, Line],
+ group_already_defined),
+
+ [{_,_, Code} = C, Vid] = Header,
+ {DefLine, {_, _, Flags}} = grouped_flags(Name, Code, Dict0, Line),
+ V = lists:member($V, Flags),
+
+ true = is_uint32(C, [K, Name, "AVP code"]),
+ true = is_uint32(Vid, [K, Name, "vendor id"]),
+ false = vendor_id_mismatch(Vid, V, Name, Dict0, Line, DefLine),
+
+ explode_avps(Avps, Dict, K, Name);
+
+%% {messages, Name} -> [Line, HeaderTok | AvpToks]
+%% {messages, {Code, IsReq}} -> [Line, NameTok]
+%% {messages, Code} -> [[Line, NameTok, IsReq]]
+%% {messages, {Name, Flag}} -> [Line]
+%% {messages, {Name, AvpName}} -> [Line, Qual, Delim]
+%%
+%% where Name = string()
+%% Code = integer()
+%% IsReq = boolean()
+%% Flag = 'REQ' | 'PXY'
+%% AvpName = string()
+%% Qual = true | {Q,Q}
+%% Q = true | NumberTok
+%% Delim = $< | ${ | ${
+
+explode([{'answer-message' = A, Line}, false = H | Avps],
+ Dict0,
+ messages = K) ->
+ Name = ?L(A),
+ Dict1 = store_new({K, Name},
+ [Line, H, Avps],
+ Dict0,
+ [Name, Line],
+ message_name_already_defined),
+
+ explode_avps(Avps, Dict1, K, Name);
+
+explode([{_, Line, MsgName} = M, Header | Avps],
+ Dict0,
+ messages = K) ->
+ %% There can be at most one message with a given name.
+ Dict1 = store_new({K, MsgName},
+ [Line, Header | Avps],
+ Dict0,
+ [MsgName, Line],
+ message_name_already_defined),
+
+ [{_, _, Code} = C, Bits, ApplId] = Header,
+
+ %% Don't check any application id since it's required to be
+ %% the same as @id.
+ true = is_uint32(C, [K, MsgName]),
+
+ %% An application id specified as part of the message definition
+ %% has to agree with @id. The former is parsed just because RFC
+ %% 3588 specifies it.
+ false = application_id_mismatch(ApplId, Dict1, MsgName),
+
+ IsReq = lists:keymember('REQ', 1, Bits),
+
+ %% For each command code, there can be at most one request and
+ %% one answer.
+ Dict2 = store_new({K, {Code, IsReq}},
+ [Line, M],
+ Dict1,
+ [choose(IsReq, "Request", "Answer"), Code, Line],
+ message_code_already_defined),
+
+ %% For each message, each flag can occur at most once.
+ Dict3 = foldl(fun({F,L},D) ->
+ store_new({K, {MsgName, F}},
+ [L],
+ D,
+ [MsgName, ?L(F)],
+ message_has_duplicate_flag)
+ end,
+ Dict2,
+ Bits),
+
+ dict:append({K, Code},
+ [Line, M, IsReq],
+ explode_avps(Avps, Dict3, K, MsgName)).
+
+%% explode_avps/4
+%%
+%% Ensure required AVP order and sane qualifiers. Can't check for AVP
+%% names until after they've been imported.
+%%
+%% RFC 3588 allows a trailing fixed while 3588bis doesn't. Parse the
+%% former.
+
+explode_avps(Avps, Dict, Key, Name) ->
+ xa("<{[<", Avps, Dict, Key, Name).
+
+xa(_, [], Dict, _, _) ->
+ Dict;
+
+xa(Ds, [[Qual, D, {'AVP', Line}] | Avps], Dict, Key, Name) ->
+ xa(Ds, [[Qual, D, {word, Line, "AVP"}] | Avps], Dict, Key, Name);
+
+xa([], [[_Qual, D, {_, Line, Name}] | _], _, _, _) ->
+ ?RETURN(invalid_avp_order, [D, Name, close(D), Line]);
+
+xa([D|_], [[{{_, Line, Min}, {_, _, Max}}, D, _] | _], _, _, _)
+ when Min > Max ->
+ ?RETURN(invalid_qualifier, [Min, Max, Line]);
+
+xa([D|_] = Ds, [[Qual, D, {_, Line, AvpName}] | Avps], Dict, Key, Name) ->
+ xa(Ds,
+ Avps,
+ store_new({Key, {Name, AvpName}},
+ [Line, Qual, D],
+ Dict,
+ [Name, Line],
+ avp_already_referenced),
+ Key,
+ Name);
+
+xa([_|Ds], Avps, Dict, Key, Name) ->
+ xa(Ds, Avps, Dict, Key, Name).
+
+close($<) -> $>;
+close(${) -> $};
+close($[) -> $].
+
+%% is_uint32/2
+
+is_uint32(false, _) ->
+ true;
+is_uint32({Line, _, N}, Args) ->
+ N < 1 bsl 32 orelse ?RETURN(uint32_out_of_range, Args ++ [N, Line]).
+%% Can't call diameter_types here since it may not exist yet.
+
+%% application_id_mismatch/3
+
+application_id_mismatch({number, Line, Id}, Dict, MsgName) ->
+ [[_, {_, L, I}]] = dict:fetch(id, Dict),
+
+ I /= Id andalso ?RETURN(message_application_id_mismatch,
+ [MsgName, Id, Line, I, L]);
+
+application_id_mismatch(false = No, _, _) ->
+ No.
+
+%% avp_not_local/3
+
+avp_not_local(Name, Line, Dict) ->
+ A = find({avp_types, Name}, Dict),
+
+ [] == A orelse ?RETURN(inherited_avp_already_defined,
+ [Name, Line, hd(A)]).
+
+%% avp_type_known/3
+
+avp_type_known(Type, Name, Line) ->
+ false /= type(Type)
+ orelse ?RETURN(avp_has_unknown_type, [Name, Line, Type]).
+
+%% vendor_id_mismatch/6
+%%
+%% Require a vendor id specified on a group to match any specified
+%% in @avp_vendor_id. Note that both locations for the value are
+%% equivalent, both in the value being attributed to a locally
+%% defined AVP and ignored when imported from another dictionary.
+
+vendor_id_mismatch({_,_,_}, false, Name, _, Line, DefLine) ->
+ ?RETURN(grouped_vendor_id_without_flag, [Name, Line, DefLine]);
+
+vendor_id_mismatch({_, _, I}, true, Name, Dict, Line, _) ->
+ case vendor_id(Name, Dict) of
+ {avp_vendor_id, L, N} ->
+ I /= N andalso
+ ?RETURN(grouped_vendor_id_mismatch, [Name, Line, I, N, L]);
+ _ ->
+ false
+ end;
+
+vendor_id_mismatch(_, _, _, _, _, _) ->
+ false.
+
+%% grouped_flags/4
+
+grouped_flags(Name, Code, Dict, Line) ->
+ case find({avp_types, Name}, Dict) of
+ [L, {_, _, Code}, {_, _, "Grouped"}, Flags] ->
+ {L, Flags};
+ [_, {_, L, C}, {_, _, "Grouped"}, _Flags] ->
+ ?RETURN(grouped_avp_code_mismatch, [Name, Line, Code, C, L]);
+ [_, _Code, {_, L, T}, _] ->
+ ?RETURN(grouped_avp_has_wrong_type, [Name, Line, T, L]);
+ [] ->
+ ?RETURN(grouped_avp_not_defined, [Name, Line])
+ end.
+
+%% vendor_id/2
+
+%% Look for a vendor id in @avp_vendor_id, then @vendor.
+vendor_id(Name, Dict) ->
+ case find({avp_vendor_id, Name}, Dict) of
+ [Line, Id] when is_integer(Id) ->
+ {avp_vendor_id, Line, Id};
+ [] ->
+ vendor(Dict)
+ end.
+
+vendor(Dict) ->
+ case find(vendor, Dict) of
+ [[_Line, {_, _, Id}, {_, _, _}]] ->
+ {vendor, Id};
+ [] ->
+ false
+ end.
+
+%% find/2
+
+find(Key, Dict) ->
+ case dict:find(Key, Dict) of
+ {ok, L} when is_list(L) ->
+ L;
+ error ->
+ []
+ end.
+
+%% store_new/5
+
+store_new(Key, Value, Dict, Args, Err) ->
+ case dict:find(Key, Dict) of
+ {ok, [L | _]} ->
+ ?RETURN(Err, Args ++ [L]);
+ error ->
+ dict:store(Key, Value, Dict)
+ end.
+
+%% type/1
+
+type("DiamIdent") ->
+ "DiameterIdentity";
+type("DiamURI") ->
+ "DiameterURI";
+type(T)
+ when T == "OctetString";
+ T == "Integer32";
+ T == "Integer64";
+ T == "Unsigned32";
+ T == "Unsigned64";
+ T == "Float32";
+ T == "Float64";
+ T == "Grouped";
+ T == "Enumerated";
+ T == "Address";
+ T == "Time";
+ T == "UTF8String";
+ T == "DiameterIdentity";
+ T == "DiameterURI";
+ T == "IPFilterRule";
+ T == "QoSFilterRule" ->
+ T;
+type(_) ->
+ false.
+
+%% ===========================================================================
+%% pass2/1
+%%
+%% More explosion, but that requires the previous pass to write its
+%% entries.
+
+pass2(Dict) ->
+ foldl(fun(K,D) -> foldl([fun p2/3, K], D, find(K,D)) end,
+ Dict,
+ [avp_types]).
+
+p2([_Line | Body], Dict, avp_types) ->
+ foldl(fun explode_avps/2, Dict, Body);
+
+p2([], Dict, _) ->
+ Dict.
+
+explode_avps([{_, Line, Name} | Toks], Dict) ->
+ [{number, _, Code}, {word, _, _Type}, {word, _, Flags}] = Toks,
+
+ true = avp_flags_valid(Flags, Name, Line),
+
+ Vid = avp_vendor_id(Flags, Name, Line, Dict),
+
+ %% An AVP is uniquely defined by its AVP code and vendor id (if any).
+ %% Ensure there are no duplicate.
+ store_new({avp_types, {Code, Vid}},
+ [Line, Name],
+ Dict,
+ [Code, Vid, Name, Line],
+ avp_code_already_defined).
+
+%% avp_flags_valid/3
+
+avp_flags_valid(Flags, Name, Line) ->
+ Bad = lists:filter(fun(C) -> not lists:member(C, "MVP") end, Flags),
+ [] == Bad
+ orelse ?RETURN(avp_has_invalid_flag, [Name, Line, hd(Bad)]),
+
+ Dup = Flags -- "MVP",
+ [] == Dup
+ orelse ?RETURN(avp_has_duplicate_flag, [Name, Line, hd(Dup)]).
+
+%% avp_vendor_id/4
+
+avp_vendor_id(Flags, Name, Line, Dict) ->
+ V = lists:member($V, Flags),
+
+ case vendor_id(Name, Dict) of
+ {avp_vendor_id, _, I} when V ->
+ I;
+ {avp_vendor_id, L, I} ->
+ ?RETURN(avp_has_vendor_id, [Name, Line, I, L]);
+ {vendor, I} when V ->
+ I;
+ false when V ->
+ ?RETURN(avp_has_no_vendor, [Name, Line]);
+ _ ->
+ false
+ end.
+
+%% ===========================================================================
+%% pass3/2
+%%
+%% Import AVPs.
+
+pass3(Dict, Opts) ->
+ import_enums(import_groups(import_avps(insert_codes(Dict), Opts))).
+
+%% insert_codes/1
+%%
+%% command_codes -> [{Code, ReqNameTok, AnsNameTok}]
+
+insert_codes(Dict) ->
+ dict:store(command_codes,
+ dict:fold(fun make_code/3, [], Dict),
+ Dict).
+
+make_code({messages, Code}, Names, Acc)
+ when is_integer(Code) ->
+ [mk_code(Code, Names) | Acc];
+make_code(_, _, Acc) ->
+ Acc.
+
+mk_code(Code, [[_, _, false] = Ans, [_, _, true] = Req]) ->
+ mk_code(Code, [Req, Ans]);
+
+mk_code(Code, [[_, {_,_,Req}, true], [_, {_,_,Ans}, false]]) ->
+ {Code, Req, Ans};
+
+mk_code(_Code, [[Line, _Name, IsReq]]) ->
+ ?RETURN(message_missing, [choose(IsReq, "Request", "Answer"),
+ Line,
+ choose(IsReq, "answer", "request")]).
+
+%% import_avps/2
+
+import_avps(Dict, Opts) ->
+ Import = inherit(Dict, Opts),
+ report(imported, Import),
+
+ %% pass4/1 tests that all referenced AVP's are either defined
+ %% or imported.
+
+ dict:store(import_avps,
+ lists:map(fun({M, _, As}) -> {M, [A || {_,A} <- As]} end,
+ lists:reverse(Import)),
+ foldl(fun explode_imports/2, Dict, Import)).
+
+explode_imports({Mod, Line, Avps}, Dict) ->
+ foldl([fun xi/4, Mod, Line], Dict, Avps).
+
+xi({L, {Name, _Code, _Type, _Flags} = A}, Dict, Mod, Line) ->
+ store_new({avp_types, Name},
+ [0, Mod, Line, L, A],
+ store_new({import, Name},
+ [Line],
+ Dict,
+ [Name, Line],
+ duplicate_import),
+ [Name, Mod, Line],
+ imported_avp_already_defined).
+
+%% import_groups/1
+%% import_enums/1
+%%
+%% For each inherited module, store the content of imported AVP's of
+%% type grouped/enumerated in a new key.
+
+import_groups(Dict) ->
+ dict:store(import_groups, import(grouped, Dict), Dict).
+
+import_enums(Dict) ->
+ dict:store(import_enums, import(enum, Dict), Dict).
+
+import(Key, Dict) ->
+ flatmap([fun import_key/2, Key], dict:fetch(import_avps, Dict)).
+
+import_key({Mod, Avps}, Key) ->
+ As = lists:flatmap(fun(T) ->
+ N = element(1,T),
+ choose(lists:keymember(N, 1, Avps), [T], [])
+ end,
+ orddict:fetch(Key, dict(Mod))),
+ if As == [] ->
+ [];
+ true ->
+ [{Mod, As}]
+ end.
+
+%% ------------------------------------------------------------------------
+%% inherit/2
+%%
+%% Return a {Mod, Line, [{Lineno, Avp}]} list, where Mod is a module
+%% name, Line points to the corresponding @inherit and each Avp is
+%% from Mod:dict(). Lineno is 0 if the import is implicit.
+
+inherit(Dict, Opts) ->
+ code:add_pathsa([D || {include, D} <- Opts]),
+ foldl(fun inherit_avps/2, [], find(inherits, Dict)).
+%% Note that the module order of the returned lists is reversed
+%% relative to @inherits.
+
+inherit_avps([Line, {_,_,M} | Names], Acc) ->
+ Mod = ?A(M),
+ report(inherit_from, Mod),
+ case find_avps(Names, avps_from_module(Mod)) of
+ {_, [{_, L, N} | _]} ->
+ ?RETURN(requested_avp_not_found, [Mod, Line, N, L]);
+ {Found, []} ->
+ [{Mod, Line, lists:sort(Found)} | Acc]
+ end.
+
+%% Import everything not defined locally ...
+find_avps([], Avps) ->
+ {[{0, A} || A <- Avps], []};
+
+%% ... or specified AVPs.
+find_avps(Names, Avps) ->
+ foldl(fun acc_avp/2, {[], Names}, Avps).
+
+acc_avp({Name, _Code, _Type, _Flags} = A, {Found, Not} = Acc) ->
+ case lists:keyfind(Name, 3, Not) of
+ {_, Line, Name} ->
+ {[{Line, A} | Found], lists:keydelete(Name, 3, Not)};
+ false ->
+ Acc
+ end.
+
+%% avps_from_module/2
+
+avps_from_module(Mod) ->
+ orddict:fetch(avp_types, dict(Mod)).
+
+dict(Mod) ->
+ try Mod:dict() of
+ [?VERSION | Dict] ->
+ Dict;
+ _ ->
+ ?RETURN(recompile, [Mod])
+ catch
+ error: _ ->
+ ?RETURN(choose(false == code:is_loaded(Mod),
+ not_loaded,
+ no_dict),
+ [Mod])
+ end.
+
+%% ===========================================================================
+%% pass4/1
+%%
+%% Sanity checks.
+
+pass4(Dict) ->
+ dict:fold(fun(K, V, _) -> p4(K, V, Dict) end, ok, Dict),
+ Dict.
+
+%% Ensure enum AVP's have type Enumerated.
+p4({enum, Name}, [Line | _], Dict)
+ when is_list(Name) ->
+ true = is_enumerated_avp(Name, Dict, Line);
+
+%% Ensure all referenced AVP's are either defined locally or imported.
+p4({K, {Name, AvpName}}, [Line | _], Dict)
+ when (K == grouped orelse K == messages),
+ is_list(Name),
+ is_list(AvpName),
+ AvpName /= "AVP" ->
+ true = avp_is_defined(AvpName, Dict, Line);
+
+%% Ditto.
+p4({K, AvpName}, [Line | _], Dict)
+ when K == avp_vendor_id;
+ K == custom_types;
+ K == codecs ->
+ true = avp_is_defined(AvpName, Dict, Line);
+
+p4(_, _, _) ->
+ ok.
+
+%% has_enumerated_type/3
+
+is_enumerated_avp(Name, Dict, Line) ->
+ case find({avp_types, Name}, Dict) of
+ [_Line, _Code, {_, _, "Enumerated"}, _Flags] -> %% local
+ true;
+ [_Line, _Code, {_, L, T}, _] ->
+ ?RETURN(enumerated_avp_has_wrong_local_type,
+ [Name, Line, T, L]);
+ [0, _, _, _, {_Name, _Code, "Enumerated", _Flags}] -> %% inherited
+ true;
+ [0, Mod, LM, LA, {_Name, _Code, Type, _Flags}] ->
+ ?RETURN(enumerated_avp_has_wrong_inherited_type,
+ [Name, Line, Type, Mod, choose(0 == LA, LM, LA)]);
+ [] ->
+ ?RETURN(enumerated_avp_not_defined, [Name, Line])
+ end.
+
+avp_is_defined(Name, Dict, Line) ->
+ case find({avp_types, Name}, Dict) of
+ [_Line, _Code, _Type, _Flags] -> %% local
+ true;
+ [0, _, _, _, {Name, _Code, _Type, _Flags}] -> %% inherited
+ true;
+ [] ->
+ ?RETURN(avp_not_defined, [Name, Line])
+ end.
+
+%% ===========================================================================
+
+putr(Key, Value) ->
+ put({?MODULE, Key}, Value).
+
+getr(Key) ->
+ get({?MODULE, Key}).
+
+eraser(Key) ->
+ erase({?MODULE, Key}).
+
+choose(true, X, _) -> X;
+choose(false, _, X) -> X.
+
+foldl(F, Acc, List) ->
+ lists:foldl(fun(T,A) -> eval([F,T,A]) end, Acc, List).
+
+flatmap(F, List) ->
+ lists:flatmap(fun(T) -> eval([F,T]) end, List).
+
+eval([[F|X] | A]) ->
+ eval([F | A ++ X]);
+eval([F|A]) ->
+ apply(F,A).
diff --git a/lib/diameter/src/compiler/diameter_exprecs.erl b/lib/diameter/src/compiler/diameter_exprecs.erl
index 5e120d6f44..191f53f29d 100644
--- a/lib/diameter/src/compiler/diameter_exprecs.erl
+++ b/lib/diameter/src/compiler/diameter_exprecs.erl
@@ -96,41 +96,15 @@
-export([parse_transform/2]).
-%% Form tag with line number.
--define(F(T), T, ?LINE).
-%% Yes, that's right. The replacement is to the first unmatched ')'.
-
--define(attribute, ?F(attribute)).
--define(clause, ?F(clause)).
--define(function, ?F(function)).
--define(call, ?F(call)).
--define('fun', ?F('fun')).
--define(generate, ?F(generate)).
--define(lc, ?F(lc)).
--define(match, ?F(match)).
--define(remote, ?F(remote)).
--define(record, ?F(record)).
--define(record_field, ?F(record_field)).
--define(record_index, ?F(record_index)).
--define(tuple, ?F(tuple)).
-
--define(ATOM(T), {atom, ?LINE, T}).
--define(VAR(V), {var, ?LINE, V}).
-
--define(CALL(F,A), {?call, ?ATOM(F), A}).
--define(APPLY(M,F,A), {?call, {?remote, ?ATOM(M), ?ATOM(F)}, A}).
+-include("diameter_forms.hrl").
%% parse_transform/2
parse_transform(Forms, _Options) ->
Rs = [R || {attribute, _, record, R} <- Forms],
- case lists:append([E || {attribute, _, export_records, E} <- Forms]) of
- [] ->
- Forms;
- Es ->
- {H,T} = lists:splitwith(fun is_head/1, Forms),
- H ++ [a_export(Es) | f_accessors(Es, Rs)] ++ T
- end.
+ Es = lists:append([E || {attribute, _, export_records, E} <- Forms]),
+ {H,T} = lists:splitwith(fun is_head/1, Forms),
+ H ++ [a_export(Es) | f_accessors(Es, Rs)] ++ T.
is_head(T) ->
not lists:member(element(1,T), [function, eof]).
@@ -200,7 +174,7 @@ fname(Op, Rname) ->
'#info-/2'(Exports) ->
{?function, fname(info), 2,
- lists:map(fun 'info-'/1, Exports)}.
+ lists:map(fun 'info-'/1, Exports) ++ [?BADARG(2)]}.
'info-'(R) ->
{?clause, [?ATOM(R), ?VAR('Info')],
@@ -209,7 +183,7 @@ fname(Op, Rname) ->
'#new-/1'(Exports) ->
{?function, fname(new), 1,
- lists:map(fun 'new-'/1, Exports)}.
+ lists:map(fun 'new-'/1, Exports) ++ [?BADARG(1)]}.
'new-'(R) ->
{?clause, [?ATOM(R)],
@@ -218,7 +192,7 @@ fname(Op, Rname) ->
'#new-/2'(Exports) ->
{?function, fname(new), 2,
- lists:map(fun 'new--'/1, Exports)}.
+ lists:map(fun 'new--'/1, Exports) ++ [?BADARG(2)]}.
'new--'(R) ->
{?clause, [?ATOM(R), ?VAR('Vals')],
@@ -227,7 +201,7 @@ fname(Op, Rname) ->
'#get-/2'(Exports) ->
{?function, fname(get), 2,
- lists:map(fun 'get-'/1, Exports)}.
+ lists:map(fun 'get-'/1, Exports) ++ [?BADARG(2)]}.
'get-'(R) ->
{?clause, [?VAR('Attrs'),
@@ -237,7 +211,7 @@ fname(Op, Rname) ->
'#set-/2'(Exports) ->
{?function, fname(set), 2,
- lists:map(fun 'set-'/1, Exports)}.
+ lists:map(fun 'set-'/1, Exports) ++ [?BADARG(2)]}.
'set-'(R) ->
{?clause, [?VAR('Vals'), {?match, {?record, R, []}, ?VAR('Rec')}],
diff --git a/lib/diameter/src/compiler/diameter_forms.hrl b/lib/diameter/src/compiler/diameter_forms.hrl
index d93131df34..4cd86c32aa 100644
--- a/lib/diameter/src/compiler/diameter_forms.hrl
+++ b/lib/diameter/src/compiler/diameter_forms.hrl
@@ -21,6 +21,13 @@
%% Macros used when building abstract code.
%%
+%% Generated functions that could have no generated clauses will have
+%% a trailing ?BADARG clause that should never execute as called
+%% by diameter.
+-define(BADARG(N), {?clause, [?VAR('_') || _ <- lists:seq(1,N)],
+ [],
+ [?APPLY(erlang, error, [?ATOM(badarg)])]}).
+
%% Form tag with line number.
-define(F(T), T, ?LINE).
%% Yes, that's right. The replacement is to the first unmatched ')'.
diff --git a/lib/diameter/src/compiler/diameter_make.erl b/lib/diameter/src/compiler/diameter_make.erl
index 5380ee56ca..16e30c1ffb 100644
--- a/lib/diameter/src/compiler/diameter_make.erl
+++ b/lib/diameter/src/compiler/diameter_make.erl
@@ -20,59 +20,113 @@
%%
%% Module alternative to diameterc for dictionary compilation.
%%
-%% Eg. 1> diameter_make:dict("mydict.dia").
+%% Eg. 1> diameter_make:codec("mydict.dia").
%%
-%% $ erl -noshell \
+%% $ erl -noinput \
%% -boot start_clean \
-%% -s diameter_make dict mydict.dia \
+%% -eval 'ok = diameter_make:codec("mydict.dia")' \
%% -s init stop
%%
-module(diameter_make).
--export([dict/1,
+-export([codec/1,
+ codec/2,
+ dict/1,
dict/2,
- spec/1,
- spec/2]).
+ format/1,
+ reformat/1]).
--type opt() :: {outdir|include|name|prefix|inherits, string()}
+-export_type([opt/0]).
+
+-type opt() :: {include|outdir|name|prefix|inherits, string()}
| verbose
| debug.
-%% dict/1-2
+%% ===========================================================================
+
+%% codec/1-2
+%%
+%% Parse a dictionary file and generate a codec module.
+
+-spec codec(Path, [opt()])
+ -> ok
+ | {error, Reason}
+ when Path :: string(),
+ Reason :: string().
+
+codec(File, Opts) ->
+ case dict(File, Opts) of
+ {ok, Dict} ->
+ make(File,
+ Opts,
+ Dict,
+ [spec || _ <- [1], lists:member(debug, Opts)] ++ [erl, hrl]);
+ {error, _} = E ->
+ E
+ end.
+
+codec(File) ->
+ codec(File, []).
+
+%% dict/2
+%%
+%% Parse a dictionary file and return the orddict that a codec module
+%% returns from dict/0.
-spec dict(string(), [opt()])
- -> ok.
+ -> {ok, orddict:orddict()}
+ | {error, string()}.
-dict(File, Opts) ->
- make(File,
- Opts,
- spec(File, Opts),
- [spec || _ <- [1], lists:member(debug, Opts)] ++ [erl, hrl]).
+dict(Path, Opts) ->
+ case diameter_dict_util:parse({path, Path}, Opts) of
+ {ok, _} = Ok ->
+ Ok;
+ {error = E, Reason} ->
+ {E, diameter_dict_util:format_error(Reason)}
+ end.
dict(File) ->
dict(File, []).
-%% spec/2
+%% format/1
+%%
+%% Turn an orddict returned by dict/1-2 back into a dictionary file
+%% in the form of an iolist().
+
+-spec format(orddict:orddict())
+ -> iolist().
+
+format(Dict) ->
+ diameter_dict_util:format(Dict).
--spec spec(string(), [opt()])
- -> orddict:orddict().
+%% reformat/1
+%%
+%% Parse a dictionary file and return its formatted equivalent.
-spec(File, Opts) ->
- diameter_spec_util:parse(File, Opts).
+-spec reformat(File)
+ -> {ok, iolist()}
+ | {error, Reason}
+ when File :: string(),
+ Reason :: string().
-spec(File) ->
- spec(File, []).
+reformat(File) ->
+ case dict(File) of
+ {ok, Dict} ->
+ {ok, format(Dict)};
+ {error, _} = No ->
+ No
+ end.
%% ===========================================================================
make(_, _, _, []) ->
ok;
-make(File, Opts, Spec, [Mode | Rest]) ->
- try diameter_codegen:from_spec(File, Spec, Opts, Mode) of
- ok ->
- make(File, Opts, Spec, Rest)
+make(File, Opts, Dict, [Mode | Rest]) ->
+ try
+ ok = diameter_codegen:from_dict(File, Dict, Opts, Mode),
+ make(File, Opts, Dict, Rest)
catch
error: Reason ->
- {error, {Reason, Mode, erlang:get_stacktrace()}}
+ erlang:error({Reason, Mode, erlang:get_stacktrace()})
end.
diff --git a/lib/diameter/src/compiler/diameter_spec_scan.erl b/lib/diameter/src/compiler/diameter_spec_scan.erl
deleted file mode 100644
index bc0448882a..0000000000
--- a/lib/diameter/src/compiler/diameter_spec_scan.erl
+++ /dev/null
@@ -1,157 +0,0 @@
-%%
-%% %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_spec_scan).
-
-%%
-%% Functions used by the spec file parser in diameter_spec_util.
-%%
-
--export([split/1,
- split/2,
- parse/1]).
-
-%%% -----------------------------------------------------------
-%%% # parse/1
-%%%
-%%% Output: list of Token
-%%%
-%%% Token = '{' | '}' | '<' | '>' | '[' | ']'
-%%% | '*' | '::=' | ':' | ',' | '-'
-%%% | {name, string()}
-%%% | {tag, atom()}
-%%% | {number, integer() >= 0}
-%%%
-%%% Tokenize a string. Fails if the string does not parse.
-%%% -----------------------------------------------------------
-
-parse(S) ->
- parse(S, []).
-
-%% parse/2
-
-parse(S, Acc) ->
- acc(split(S), Acc).
-
-acc({T, Rest}, Acc) ->
- parse(Rest, [T | Acc]);
-acc("", Acc) ->
- lists:reverse(Acc).
-
-%%% -----------------------------------------------------------
-%%% # split/2
-%%%
-%%% Output: {list() of Token, Rest}
-%%%
-%%% Extract a specified number of tokens from a string. Returns a list
-%%% of length less than the specified number if there are less than
-%%% this number of tokens to be parsed.
-%%% -----------------------------------------------------------
-
-split(Str, N)
- when N >= 0 ->
- split(N, Str, []).
-
-split(0, Str, Acc) ->
- {lists:reverse(Acc), Str};
-
-split(N, Str, Acc) ->
- case split(Str) of
- {T, Rest} ->
- split(N-1, Rest, [T|Acc]);
- "" = Rest ->
- {lists:reverse(Acc), Rest}
- end.
-
-%%% -----------------------------------------------------------
-%%% # split/1
-%%%
-%%% Output: {Token, Rest} | ""
-%%%
-%%% Extract the next token from a string.
-%%% -----------------------------------------------------------
-
-split("" = Rest) ->
- Rest;
-
-split("::=" ++ T) ->
- {'::=', T};
-
-split([H|T])
- when H == ${; H == $};
- H == $<; H == $>;
- H == $[; H == $];
- H == $*; H == $:; H == $,; H == $- ->
- {list_to_atom([H]), T};
-
-split([H|T]) when $A =< H, H =< $Z;
- $0 =< H, H =< $9 ->
- {P, Rest} = splitwith(fun is_name_ch/1, [H], T),
- Tok = try
- {number, read_int(P)}
- catch
- error:_ ->
- {name, P}
- end,
- {Tok, Rest};
-
-split([H|T]) when $a =< H, H =< $z ->
- {P, Rest} = splitwith(fun is_name_ch/1, [H], T),
- {{tag, list_to_atom(P)}, Rest};
-
-split([H|T]) when H == $\t;
- H == $\s;
- H == $\n ->
- split(T).
-
-%% read_int/1
-
-read_int([$0,X|S])
- when X == $X;
- X == $x ->
- {ok, [N], []} = io_lib:fread("~16u", S),
- N;
-
-read_int(S) ->
- list_to_integer(S).
-
-%% splitwith/3
-
-splitwith(Fun, Acc, S) ->
- split([] /= S andalso Fun(hd(S)), Fun, Acc, S).
-
-split(true, Fun, Acc, [H|T]) ->
- splitwith(Fun, [H|Acc], T);
-split(false, _, Acc, S) ->
- {lists:reverse(Acc), S}.
-
-is_name_ch(C) ->
- is_alphanum(C) orelse C == $- orelse C == $_.
-
-is_alphanum(C) ->
- is_lower(C) orelse is_upper(C) orelse is_digit(C).
-
-is_lower(C) ->
- $a =< C andalso C =< $z.
-
-is_upper(C) ->
- $A =< C andalso C =< $Z.
-
-is_digit(C) ->
- $0 =< C andalso C =< $9.
diff --git a/lib/diameter/src/compiler/diameter_spec_util.erl b/lib/diameter/src/compiler/diameter_spec_util.erl
deleted file mode 100644
index 62536bf06d..0000000000
--- a/lib/diameter/src/compiler/diameter_spec_util.erl
+++ /dev/null
@@ -1,1089 +0,0 @@
-%%
-%% %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%
-%%
-
-%%
-%% This module turns a .dia (aka spec) file into the orddict that
-%% diameter_codegen.erl in turn morphs into .erl and .hrl files for
-%% encode and decode of Diameter messages and AVPs.
-%%
-
--module(diameter_spec_util).
-
--export([parse/2]).
-
--define(ERROR(T), erlang:error({T, ?MODULE, ?LINE})).
--define(ATOM, list_to_atom).
-
-%% parse/1
-%%
-%% Output: orddict()
-
-parse(Path, Opts) ->
- put({?MODULE, verbose}, lists:member(verbose, Opts)),
- {ok, B} = file:read_file(Path),
- Chunks = chunk(B),
- Spec = reset(make_spec(Chunks), Opts, [name, prefix, inherits]),
- true = groups_defined(Spec), %% sanity checks
- true = customs_defined(Spec), %%
- Full = import_enums(import_groups(import_avps(insert_codes(Spec), Opts))),
- true = enums_defined(Full), %% sanity checks
- true = v_flags_set(Spec),
- Full.
-
-reset(Spec, Opts, Keys) ->
- lists:foldl(fun(K,S) ->
- reset([{A,?ATOM(V)} || {A,V} <- Opts, A == K], S)
- end,
- Spec,
- Keys).
-
-reset(L, Spec)
- when is_list(L) ->
- lists:foldl(fun reset/2, Spec, L);
-
-reset({inherits = Key, '-'}, Spec) ->
- orddict:erase(Key, Spec);
-reset({inherits = Key, Dict}, Spec) ->
- orddict:append(Key, Dict, Spec);
-reset({Key, Atom}, Spec) ->
- orddict:store(Key, Atom, Spec);
-reset(_, Spec) ->
- Spec.
-
-%% Optional reports when running verbosely.
-report(What, Data) ->
- report(get({?MODULE, verbose}), What, Data).
-
-report(true, Tag, Data) ->
- io:format("##~n## ~p ~p~n", [Tag, Data]);
-report(false, _, _) ->
- ok.
-
-%% chunk/1
-
-chunk(B) ->
- chunkify(normalize(binary_to_list(B))).
-
-%% normalize/1
-%%
-%% Replace CR NL by NL, multiple NL by one, tab by space, and strip
-%% comments and leading/trailing space from each line. Precludes
-%% semicolons being used for any other purpose than comments.
-
-normalize(Str) ->
- nh(Str, []).
-
-nh([], Acc) ->
- lists:reverse(Acc);
-
-%% Trim leading whitespace.
-nh(Str, Acc) ->
- nb(trim(Str), Acc).
-
-%% tab -> space
-nb([$\t|Rest], Acc) ->
- nb(Rest, [$\s|Acc]);
-
-%% CR NL -> NL
-nb([$\r,$\n|Rest], Acc) ->
- nt(Rest, Acc);
-
-%% Gobble multiple newlines before starting over again.
-nb([$\n|Rest], Acc) ->
- nt(Rest, Acc);
-
-%% Comment.
-nb([$;|Rest], Acc) ->
- nb(lists:dropwhile(fun(C) -> C /= $\n end, Rest), Acc);
-
-%% Just an ordinary character. Boring ...
-nb([C|Rest], Acc) ->
- nb(Rest, [C|Acc]);
-
-nb([] = Str, Acc) ->
- nt(Str, Acc).
-
-%% Discard a subsequent newline.
-nt(T, [$\n|_] = Acc) ->
- nh(T, trim(Acc));
-
-%% Trim whitespace from the end of the line before continuing.
-nt(T, Acc) ->
- nh(T, [$\n|trim(Acc)]).
-
-trim(S) ->
- lists:dropwhile(fun(C) -> lists:member(C, "\s\t") end, S).
-
-%% chunkify/1
-%%
-%% Split the spec file into pieces delimited by lines starting with
-%% @Tag. Returns a list of {Tag, Args, Chunk} where Chunk is the
-%% string extending to the next delimiter. Note that leading
-%% whitespace has already been stripped.
-
-chunkify(Str) ->
- %% Drop characters to the start of the first chunk.
- {_, Rest} = split_chunk([$\n|Str]),
- chunkify(Rest, []).
-
-chunkify([], Acc) ->
- lists:reverse(Acc);
-
-chunkify(Rest, Acc) ->
- {H,T} = split_chunk(Rest),
- chunkify(T, [split_tag(H) | Acc]).
-
-split_chunk(Str) ->
- split_chunk(Str, []).
-
-split_chunk([] = Rest, Acc) ->
- {lists:reverse(Acc), Rest};
-split_chunk([$@|Rest], [$\n|_] = Acc) ->
- {lists:reverse(Acc), Rest};
-split_chunk([C|Rest], Acc) ->
- split_chunk(Rest, [C|Acc]).
-
-%% Expect a tag and its arguments on a single line.
-split_tag(Str) ->
- {L, Rest} = get_until($\n, Str),
- [{tag, Tag} | Toks] = diameter_spec_scan:parse(L),
- {Tag, Toks, trim(Rest)}.
-
-get_until(EndT, L) ->
- {H, [EndT | T]} = lists:splitwith(fun(C) -> C =/= EndT end, L),
- {H,T}.
-
-%% ------------------------------------------------------------------------
-%% make_spec/1
-%%
-%% Turn chunks into spec.
-
-make_spec(Chunks) ->
- lists:foldl(fun(T,A) -> report(chunk, T), chunk(T,A) end,
- orddict:new(),
- Chunks).
-
-chunk({T, [X], []}, Dict)
- when T == name;
- T == prefix ->
- store(T, atomize(X), Dict);
-
-chunk({id = T, [{number, I}], []}, Dict) ->
- store(T, I, Dict);
-
-chunk({vendor = T, [{number, I}, N], []}, Dict) ->
- store(T, {I, atomize(N)}, Dict);
-
-%% inherits -> [{Mod, [AvpName, ...]}, ...]
-chunk({inherits = T, [_,_|_] = Args, []}, Acc) ->
- Mods = [atomize(A) || A <- Args],
- append_list(T, [{M,[]} || M <- Mods], Acc);
-chunk({inherits = T, [Mod], Body}, Acc) ->
- append(T, {atomize(Mod), parse_avp_names(Body)}, Acc);
-
-%% avp_types -> [{AvpName, Code, Type, Flags, Encr}, ...]
-chunk({avp_types = T, [], Body}, Acc) ->
- store(T, parse_avp_types(Body), Acc);
-
-%% custom_types -> [{Mod, [AvpName, ...]}, ...]
-chunk({custom_types = T, [Mod], Body}, Dict) ->
- [_|_] = Avps = parse_avp_names(Body),
- append(T, {atomize(Mod), Avps}, Dict);
-
-%% messages -> [{MsgName, Code, Type, Appl, Avps}, ...]
-chunk({messages = T, [], Body}, Acc) ->
- store(T, parse_messages(Body), Acc);
-
-%% grouped -> [{AvpName, Code, Vendor, Avps}, ...]
-chunk({grouped = T, [], Body}, Acc) ->
- store(T, parse_groups(Body), Acc);
-
-%% avp_vendor_id -> [{Id, [AvpName, ...]}, ...]
-chunk({avp_vendor_id = T, [{number, I}], Body}, Dict) ->
- [_|_] = Names = parse_avp_names(Body),
- append(T, {I, Names}, Dict);
-
-%% enums -> [{AvpName, [{Value, Name}, ...]}, ...]
-chunk({enum, [N], Str}, Dict) ->
- append(enums, {atomize(N), parse_enums(Str)}, Dict);
-
-%% defines -> [{DefineName, [{Value, Name}, ...]}, ...]
-chunk({define, [N], Str}, Dict) ->
- append(defines, {atomize(N), parse_enums(Str)}, Dict);
-chunk({result_code, [_] = N, Str}, Dict) -> %% backwards compatibility
- chunk({define, N, Str}, Dict);
-
-%% commands -> [{Name, Abbrev}, ...]
-chunk({commands = T, [], Body}, Dict) ->
- store(T, parse_commands(Body), Dict);
-
-chunk(T, _) ->
- ?ERROR({unknown_tag, T}).
-
-store(Key, Value, Dict) ->
- error == orddict:find(Key, Dict) orelse ?ERROR({duplicate, Key}),
- orddict:store(Key, Value, Dict).
-append(Key, Value, Dict) ->
- orddict:append(Key, Value, Dict).
-append_list(Key, Values, Dict) ->
- orddict:append_list(Key, Values, Dict).
-
-atomize({tag, T}) ->
- T;
-atomize({name, T}) ->
- ?ATOM(T).
-
-get_value(Keys, Spec)
- when is_list(Keys) ->
- [get_value(K, Spec) || K <- Keys];
-get_value(Key, Spec) ->
- proplists:get_value(Key, Spec, []).
-
-%% ------------------------------------------------------------------------
-%% enums_defined/1
-%% groups_defined/1
-%% customs_defined/1
-%%
-%% Ensure that every local enum/grouped/custom is defined as an avp
-%% with an appropriate type.
-
-enums_defined(Spec) ->
- Avps = get_value(avp_types, Spec),
- Import = get_value(import_enums, Spec),
- lists:all(fun({N,_}) ->
- true = enum_defined(N, Avps, Import)
- end,
- get_value(enums, Spec)).
-
-enum_defined(Name, Avps, Import) ->
- case lists:keyfind(Name, 1, Avps) of
- {Name, _, 'Enumerated', _, _} ->
- true;
- {Name, _, T, _, _} ->
- ?ERROR({avp_has_wrong_type, Name, 'Enumerated', T});
- false ->
- lists:any(fun({_,Is}) -> lists:keymember(Name, 1, Is) end, Import)
- orelse ?ERROR({avp_not_defined, Name, 'Enumerated'})
- end.
-%% Note that an AVP is imported only if referenced by a message or
-%% grouped AVP, so the final branch will fail if an enum definition is
-%% extended without this being the case.
-
-groups_defined(Spec) ->
- Avps = get_value(avp_types, Spec),
- lists:all(fun({N,_,_,_}) -> true = group_defined(N, Avps) end,
- get_value(grouped, Spec)).
-
-group_defined(Name, Avps) ->
- case lists:keyfind(Name, 1, Avps) of
- {Name, _, 'Grouped', _, _} ->
- true;
- {Name, _, T, _, _} ->
- ?ERROR({avp_has_wrong_type, Name, 'Grouped', T});
- false ->
- ?ERROR({avp_not_defined, Name, 'Grouped'})
- end.
-
-customs_defined(Spec) ->
- Avps = get_value(avp_types, Spec),
- lists:all(fun(A) -> true = custom_defined(A, Avps) end,
- lists:flatmap(fun last/1, get_value(custom_types, Spec))).
-
-custom_defined(Name, Avps) ->
- case lists:keyfind(Name, 1, Avps) of
- {Name, _, T, _, _} when T == 'Grouped';
- T == 'Enumerated' ->
- ?ERROR({avp_has_invalid_custom_type, Name, T});
- {Name, _, _, _, _} ->
- true;
- false ->
- ?ERROR({avp_not_defined, Name})
- end.
-
-last({_,Xs}) -> Xs.
-
-%% ------------------------------------------------------------------------
-%% v_flags_set/1
-
-v_flags_set(Spec) ->
- Avps = get_value(avp_types, Spec)
- ++ lists:flatmap(fun last/1, get_value(import_avps, Spec)),
- Vs = lists:flatmap(fun last/1, get_value(avp_vendor_id, Spec)),
-
- lists:all(fun(N) -> vset(N, Avps) end, Vs).
-
-vset(Name, Avps) ->
- A = lists:keyfind(Name, 1, Avps),
- false == A andalso ?ERROR({avp_not_defined, Name}),
- {Name, _Code, _Type, Flags, _Encr} = A,
- lists:member('V', Flags) orelse ?ERROR({v_flag_not_set, A}).
-
-%% ------------------------------------------------------------------------
-%% insert_codes/1
-
-insert_codes(Spec) ->
- [Msgs, Cmds] = get_value([messages, commands], Spec),
-
- %% Code -> [{Name, Flags}, ...]
- Dict = lists:foldl(fun({N,C,Fs,_,_}, D) -> dict:append(C,{N,Fs},D) end,
- dict:new(),
- Msgs),
-
- %% list() of {Code, {ReqName, ReqAbbr}, {AnsName, AnsAbbr}}
- %% If the name and abbreviation are the same then the 2-tuples
- %% are replaced by the common atom()-valued name.
- Codes = dict:fold(fun(C,Ns,A) -> [make_code(C, Ns, Cmds) | A] end,
- [],
- dict:erase(-1, Dict)), %% answer-message
-
- orddict:store(command_codes, Codes, Spec).
-
-make_code(Code, [_,_] = Ns, Cmds) ->
- {Req, Ans} = make_names(Ns, lists:map(fun({_,Fs}) ->
- lists:member('REQ', Fs)
- end,
- Ns)),
- {Code, abbrev(Req, Cmds), abbrev(Ans, Cmds)};
-
-make_code(Code, Cs, _) ->
- ?ERROR({missing_request_or_answer, Code, Cs}).
-
-%% 3.3. Diameter Command Naming Conventions
-%%
-%% Diameter command names typically includes one or more English words
-%% followed by the verb Request or Answer. Each English word is
-%% delimited by a hyphen. A three-letter acronym for both the request
-%% and answer is also normally provided.
-
-make_names([{Rname,_},{Aname,_}], [true, false]) ->
- {Rname, Aname};
-make_names([{Aname,_},{Rname,_}], [false, true]) ->
- {Rname, Aname};
-make_names([_,_] = Names, _) ->
- ?ERROR({inconsistent_command_flags, Names}).
-
-abbrev(Name, Cmds) ->
- case abbr(Name, get_value(Name, Cmds)) of
- Name ->
- Name;
- Abbr ->
- {Name, Abbr}
- end.
-
-%% No explicit abbreviation: construct.
-abbr(Name, []) ->
- ?ATOM(abbr(string:tokens(atom_to_list(Name), "-")));
-
-%% Abbreviation was specified.
-abbr(_Name, Abbr) ->
- Abbr.
-
-%% No hyphens: already abbreviated.
-abbr([Abbr]) ->
- Abbr;
-
-%% XX-Request/Answer ==> XXR/XXA
-abbr([[_,_] = P, T])
- when T == "Request";
- T == "Answer" ->
- P ++ [hd(T)];
-
-%% XXX-...-YYY-Request/Answer ==> X...YR/X...YA
-abbr([_,_|_] = L) ->
- lists:map(fun erlang:hd/1, L).
-
-%% ------------------------------------------------------------------------
-%% import_avps/2
-
-import_avps(Spec, Options) ->
- Msgs = get_value(messages, Spec),
- Groups = get_value(grouped, Spec),
-
- %% Messages and groups require AVP's referenced by them.
- NeededAvps
- = ordsets:from_list(lists:flatmap(fun({_,_,_,_,As}) ->
- [avp_name(A) || A <- As]
- end,
- Msgs)
- ++ lists:flatmap(fun({_,_,_,As}) ->
- [avp_name(A) || A <- As]
- end,
- Groups)),
- MissingAvps = missing_avps(NeededAvps, Spec),
-
- report(needed, NeededAvps),
- report(missing, MissingAvps),
-
- Import = inherit(get_value(inherits, Spec), Options),
-
- report(imported, Import),
-
- ImportedAvps = lists:map(fun({N,_,_,_,_}) -> N end,
- lists:flatmap(fun last/1, Import)),
-
- Unknown = MissingAvps -- ImportedAvps,
-
- [] == Unknown orelse ?ERROR({undefined_avps, Unknown}),
-
- orddict:store(import_avps, Import, orddict:erase(inherits, Spec)).
-
-%% missing_avps/2
-%%
-%% Given a list of AVP names and parsed spec, return the list of
-%% AVP's that aren't defined in this spec.
-
-missing_avps(NeededNames, Spec) ->
- Avps = get_value(avp_types, Spec),
- Groups = lists:map(fun({N,_,_,As}) ->
- {N, [avp_name(A) || A <- As]}
- end,
- get_value(grouped, Spec)),
- Names = ordsets:from_list(['AVP' | lists:map(fun({N,_,_,_,_}) -> N end,
- Avps)]),
- missing_avps(NeededNames, [], {Names, Groups}).
-
-avp_name({'<',A,'>'}) -> A;
-avp_name({A}) -> A;
-avp_name([A]) -> A;
-avp_name({_, A}) -> avp_name(A).
-
-missing_avps(NeededNames, MissingNames, {Names, _} = T) ->
- missing(ordsets:filter(fun(N) -> lists:member(N, NeededNames) end, Names),
- ordsets:union(NeededNames, MissingNames),
- T).
-
-%% Nothing found locally.
-missing([], MissingNames, _) ->
- MissingNames;
-
-%% Or not. Keep looking for for the AVP's needed by the found AVP's of
-%% type Grouped.
-missing(FoundNames, MissingNames, {_, Groups} = T) ->
- NeededNames = lists:flatmap(fun({N,As}) ->
- choose(lists:member(N, FoundNames), As, [])
- end,
- Groups),
- missing_avps(ordsets:from_list(NeededNames),
- ordsets:subtract(MissingNames, FoundNames),
- T).
-
-%% inherit/2
-
-inherit(Inherits, Options) ->
- Dirs = [D || {include, D} <- Options] ++ ["."],
- lists:foldl(fun(T,A) -> find_avps(T, A, Dirs) end, [], Inherits).
-
-find_avps({Mod, AvpNames}, Acc, Path) ->
- report(inherit_from, Mod),
- Avps = avps_from_beam(find_beam(Mod, Path), Mod), %% could be empty
- [{Mod, lists:sort(find_avps(AvpNames, Avps))} | Acc].
-
-find_avps([], Avps) ->
- Avps;
-find_avps(Names, Avps) ->
- lists:filter(fun({N,_,_,_,_}) -> lists:member(N, Names) end, Avps).
-
-%% find_beam/2
-
-find_beam(Mod, Dirs)
- when is_atom(Mod) ->
- find_beam(atom_to_list(Mod), Dirs);
-find_beam(Mod, Dirs) ->
- Beam = Mod ++ code:objfile_extension(),
- case try_path(Dirs, Beam) of
- {value, Path} ->
- Path;
- false ->
- ?ERROR({beam_not_on_path, Beam, Dirs})
- end.
-
-try_path([D|Ds], Fname) ->
- Path = filename:join(D, Fname),
- case file:read_file_info(Path) of
- {ok, _} ->
- {value, Path};
- _ ->
- try_path(Ds, Fname)
- end;
-try_path([], _) ->
- false.
-
-%% avps_from_beam/2
-
-avps_from_beam(Path, Mod) ->
- report(beam, Path),
- ok = load_module(code:is_loaded(Mod), Mod, Path),
- orddict:fetch(avp_types, Mod:dict()).
-
-load_module(false, Mod, Path) ->
- R = filename:rootname(Path, code:objfile_extension()),
- {module, Mod} = code:load_abs(R),
- ok;
-load_module({file, _}, _, _) ->
- ok.
-
-choose(true, X, _) -> X;
-choose(false, _, X) -> X.
-
-%% ------------------------------------------------------------------------
-%% import_groups/1
-%% import_enums/1
-%%
-%% For each inherited module, store the content of imported AVP's of
-%% type grouped/enumerated in a new key.
-
-import_groups(Spec) ->
- orddict:store(import_groups, import(grouped, Spec), Spec).
-
-import_enums(Spec) ->
- orddict:store(import_enums, import(enums, Spec), Spec).
-
-import(Key, Spec) ->
- lists:flatmap(fun(T) -> import_key(Key, T) end,
- get_value(import_avps, Spec)).
-
-import_key(Key, {Mod, Avps}) ->
- Imports = lists:flatmap(fun(T) ->
- choose(lists:keymember(element(1,T),
- 1,
- Avps),
- [T],
- [])
- end,
- get_value(Key, Mod:dict())),
- if Imports == [] ->
- [];
- true ->
- [{Mod, Imports}]
- end.
-
-%% ------------------------------------------------------------------------
-%% parse_enums/1
-%%
-%% Enums are specified either as the integer value followed by the
-%% name or vice-versa. In the former case the name of the enum is
-%% taken to be the string up to the end of line, which may contain
-%% whitespace. In the latter case the integer may be parenthesized,
-%% specified in hex and followed by an inline comment. This is
-%% historical and will likely be changed to require a precise input
-%% format.
-%%
-%% Output: list() of {integer(), atom()}
-
-parse_enums(Str) ->
- lists:flatmap(fun(L) -> parse_enum(trim(L)) end, string:tokens(Str, "\n")).
-
-parse_enum([]) ->
- [];
-
-parse_enum(Str) ->
- REs = [{"^(0[xX][0-9A-Fa-f]+|[0-9]+)\s+(.*?)\s*$", 1, 2},
- {"^(.+?)\s+(0[xX][0-9A-Fa-f]+|[0-9]+)(\s+.*)?$", 2, 1},
- {"^(.+?)\s+\\((0[xX][0-9A-Fa-f]+|[0-9]+)\\)(\s+.*)?$", 2, 1}],
- parse_enum(Str, REs).
-
-parse_enum(Str, REs) ->
- try lists:foreach(fun(R) -> enum(Str, R) end, REs) of
- ok ->
- ?ERROR({bad_enum, Str})
- catch
- throw: {enum, T} ->
- [T]
- end.
-
-enum(Str, {Re, I, N}) ->
- case re:run(Str, Re, [{capture, all_but_first, list}]) of
- {match, Vs} ->
- T = list_to_tuple(Vs),
- throw({enum, {to_int(element(I,T)), ?ATOM(element(N,T))}});
- nomatch ->
- ok
- end.
-
-to_int([$0,X|Hex])
- when X == $x;
- X == $X ->
- {ok, [I], _} = io_lib:fread("~#", "16#" ++ Hex),
- I;
-to_int(I) ->
- list_to_integer(I).
-
-%% ------------------------------------------------------------------------
-%% parse_messages/1
-%%
-%% Parse according to the ABNF for message specifications in 3.2 of
-%% RFC 3588 (shown below). We require all message and AVP names to
-%% start with a digit or uppercase character, except for the base
-%% answer-message, which is treated as a special case. Allowing names
-%% that start with a digit is more than the RFC specifies but the name
-%% doesn't affect what's sent over the wire. (Certains 3GPP standards
-%% use names starting with a digit. eg 3GPP-Charging-Id in TS32.299.)
-
-%%
-%% Sadly, not even the RFC follows this grammar. In particular, except
-%% in the example in 3.2, it wraps each command-name in angle brackets
-%% ('<' '>') which makes parsing a sequence of specifications require
-%% lookahead: after 'optional' avps have been parsed, it's not clear
-%% whether a '<' is a 'fixed' or whether it's the start of a
-%% subsequent message until we see whether or not '::=' follows the
-%% closing '>'. Require the grammar as specified.
-%%
-%% Output: list of {Name, Code, Flags, ApplId, Avps}
-%%
-%% Name = atom()
-%% Code = integer()
-%% Flags = integer()
-%% ApplId = [] | [integer()]
-%% Avps = see parse_avps/1
-
-parse_messages(Str) ->
- p_cmd(trim(Str), []).
-
-%% command-def = command-name "::=" diameter-message
-%%
-%% command-name = diameter-name
-%%
-%% diameter-name = ALPHA *(ALPHA / DIGIT / "-")
-%%
-%% diameter-message = header [ *fixed] [ *required] [ *optional]
-%% [ *fixed]
-%%
-%% header = "<" Diameter-Header:" command-id
-%% [r-bit] [p-bit] [e-bit] [application-id]">"
-%%
-%% The header spec (and example that follows it) is slightly mangled
-%% and, given the examples in the RFC should as follows:
-%%
-%% header = "<" "Diameter Header:" command-id
-%% [r-bit] [p-bit] [e-bit] [application-id]">"
-%%
-%% This is what's required/parsed below, modulo whitespace. This is
-%% also what's specified in the current draft standard at
-%% http://ftp.ietf.org/drafts/wg/dime.
-%%
-%% Note that the grammar specifies the order fixed, required,
-%% optional. In practise there seems to be little difference between
-%% the latter two since qualifiers can be used to change the
-%% semantics. For example 1*[XXX] and *1{YYY} specify 1 or more of the
-%% optional avp XXX and 0 or 1 of the required avp YYY, making the
-%% iotional avp required and the required avp optional. The current
-%% draft addresses this somewhat by requiring that min for a qualifier
-%% on an optional avp must be 0 if present. It doesn't say anything
-%% about required avps however, so specifying a min of 0 would still
-%% be possible. The draft also does away with the trailing *fixed.
-%%
-%% What will be parsed here will treat required and optional
-%% interchangeably. That is. only require that required/optional
-%% follow and preceed fixed, not that optional avps must follow
-%% required ones. We already have several specs for which this parsing
-%% is necessary and there seems to be no harm in accepting it.
-
-p_cmd("", Acc) ->
- lists:reverse(Acc);
-
-p_cmd(Str, Acc) ->
- {Next, Rest} = split_def(Str),
- report(command, Next),
- p_cmd(Rest, [p_cmd(Next) | Acc]).
-
-p_cmd("answer-message" ++ Str) ->
- p_header([{name, 'answer-message'} | diameter_spec_scan:parse(Str)]);
-
-p_cmd(Str) ->
- p_header(diameter_spec_scan:parse(Str)).
-
-%% p_header/1
-
-p_header(['<', {name, _} = N, '>' | Toks]) ->
- p_header([N | Toks]);
-
-p_header([{name, 'answer-message' = N}, '::=',
- '<', {name, "Diameter"}, {name, "Header"}, ':', {tag, code},
- ',', {name, "ERR"}, '[', {name, "PXY"}, ']', '>'
- | Toks]) ->
- {N, -1, ['ERR', 'PXY'], [], parse_avps(Toks)};
-
-p_header([{name, Name}, '::=',
- '<', {name, "Diameter"}, {name, "Header"}, ':', {number, Code}
- | Toks]) ->
- {Flags, Rest} = p_flags(Toks),
- {ApplId, [C|_] = R} = p_appl(Rest),
- '>' == C orelse ?ERROR({invalid_flag, {Name, Code, Flags, ApplId}, R}),
- {?ATOM(Name), Code, Flags, ApplId, parse_avps(tl(R))};
-
-p_header(Toks) ->
- ?ERROR({invalid_header, Toks}).
-
-%% application-id = 1*DIGIT
-%%
-%% command-id = 1*DIGIT
-%% ; The Command Code assigned to the command
-%%
-%% r-bit = ", REQ"
-%% ; If present, the 'R' bit in the Command
-%% ; Flags is set, indicating that the message
-%% ; is a request, as opposed to an answer.
-%%
-%% p-bit = ", PXY"
-%% ; If present, the 'P' bit in the Command
-%% ; Flags is set, indicating that the message
-%% ; is proxiable.
-%%
-%% e-bit = ", ERR"
-%% ; If present, the 'E' bit in the Command
-%% ; Flags is set, indicating that the answer
-%% ; message contains a Result-Code AVP in
-%% ; the "protocol error" class.
-
-p_flags(Toks) ->
- lists:foldl(fun p_flags/2, {[], Toks}, ["REQ", "PXY", "ERR"]).
-
-p_flags(N, {Acc, [',', {name, N} | Toks]}) ->
- {[?ATOM(N) | Acc], Toks};
-
-p_flags(_, T) ->
- T.
-
-%% The RFC doesn't specify ',' before application-id but this seems a
-%% bit inconsistent. Accept a comma if it exists.
-p_appl([',', {number, I} | Toks]) ->
- {[I], Toks};
-p_appl([{number, I} | Toks]) ->
- {[I], Toks};
-p_appl(Toks) ->
- {[], Toks}.
-
-%% parse_avps/1
-%%
-%% Output: list() of Avp | {Qual, Avp}
-%%
-%% Qual = '*' | {Min, '*'} | {'*', Max} | {Min, Max}
-%% Avp = {'<', Name, '>'} | {Name} | [Name]
-%%
-%% Min, Max = integer() >= 0
-
-parse_avps(Toks) ->
- p_avps(Toks, ['<', '|', '<'], []).
-%% The list corresponds to the delimiters expected at the front, middle
-%% and back of the avp specification, '|' representing '{' and '['.
-
-%% fixed = [qual] "<" avp-spec ">"
-%% ; Defines the fixed position of an AVP
-%%
-%% required = [qual] "{" avp-spec "}"
-%% ; The AVP MUST be present and can appear
-%% ; anywhere in the message.
-%%
-%% optional = [qual] "[" avp-name "]"
-%% ; The avp-name in the 'optional' rule cannot
-%% ; evaluate to any AVP Name which is included
-%% ; in a fixed or required rule. The AVP can
-%% ; appear anywhere in the message.
-%%
-%% qual = [min] "*" [max]
-%% ; See ABNF conventions, RFC 2234 Section 6.6.
-%% ; The absence of any qualifiers depends on whether
-%% ; it precedes a fixed, required, or optional
-%% ; rule. If a fixed or required rule has no
-%% ; qualifier, then exactly one such AVP MUST
-%% ; be present. If an optional rule has no
-%% ; qualifier, then 0 or 1 such AVP may be
-%% ; present.
-%% ;
-%% ; NOTE: "[" and "]" have a different meaning
-%% ; than in ABNF (see the optional rule, above).
-%% ; These braces cannot be used to express
-%% ; optional fixed rules (such as an optional
-%% ; ICV at the end). To do this, the convention
-%% ; is '0*1fixed'.
-%%
-%% min = 1*DIGIT
-%% ; The minimum number of times the element may
-%% ; be present. The default value is zero.
-%%
-%% max = 1*DIGIT
-%% ; The maximum number of times the element may
-%% ; be present. The default value is infinity. A
-%% ; value of zero implies the AVP MUST NOT be
-%% ; present.
-%%
-%% avp-spec = diameter-name
-%% ; The avp-spec has to be an AVP Name, defined
-%% ; in the base or extended Diameter
-%% ; specifications.
-%%
-%% avp-name = avp-spec / "AVP"
-%% ; The string "AVP" stands for *any* arbitrary
-%% ; AVP Name, which does not conflict with the
-%% ; required or fixed position AVPs defined in
-%% ; the command code definition.
-%%
-
-p_avps([], _, Acc) ->
- lists:reverse(Acc);
-
-p_avps(Toks, Delim, Acc) ->
- {Qual, Rest} = p_qual(Toks),
- {Avp, R, D} = p_avp(Rest, Delim),
- T = if Qual == false ->
- Avp;
- true ->
- {Qual, Avp}
- end,
- p_avps(R, D, [T | Acc]).
-
-p_qual([{number, Min}, '*', {number, Max} | Toks]) ->
- {{Min, Max}, Toks};
-p_qual([{number, Min}, '*' = Max | Toks]) ->
- {{Min, Max}, Toks};
-p_qual(['*' = Min, {number, Max} | Toks]) ->
- {{Min, Max}, Toks};
-p_qual(['*' = Q | Toks]) ->
- {Q, Toks};
-p_qual(Toks) ->
- {false, Toks}.
-
-p_avp([B, {name, Name}, E | Toks], [_|_] = Delim) ->
- {avp(B, ?ATOM(Name), E),
- Toks,
- delim(choose(B == '<', B, '|'), Delim)};
-p_avp(Toks, Delim) ->
- ?ERROR({invalid_avp, Toks, Delim}).
-
-avp('<' = B, Name, '>' = E) ->
- {B, Name, E};
-avp('{', Name, '}') ->
- {Name};
-avp('[', Name, ']') ->
- [Name];
-avp(B, Name, E) ->
- ?ERROR({invalid_avp, B, Name, E}).
-
-delim(B, D) ->
- if B == hd(D) -> D; true -> tl(D) end.
-
-%% split_def/1
-%%
-%% Strip one command definition off head of a string.
-
-split_def(Str) ->
- sdh(Str, []).
-
-%% Look for the "::=" starting off the definition.
-sdh("", _) ->
- ?ERROR({missing, '::='});
-sdh("::=" ++ Rest, Acc) ->
- sdb(Rest, [$=,$:,$:|Acc]);
-sdh([C|Rest], Acc) ->
- sdh(Rest, [C|Acc]).
-
-%% Look for the "::=" starting off the following definition.
-sdb("::=" ++ _ = Rest, Acc) ->
- sdt(trim(Acc), Rest);
-sdb("" = Rest, Acc) ->
- sd(Acc, Rest);
-sdb([C|Rest], Acc) ->
- sdb(Rest, [C|Acc]).
-
-%% Put name characters of the subsequent specification back into Rest.
-sdt([C|Acc], Rest)
- when C /= $\n, C /= $\s ->
- sdt(Acc, [C|Rest]);
-
-sdt(Acc, Rest) ->
- sd(Acc, Rest).
-
-sd(Acc, Rest) ->
- {trim(lists:reverse(Acc)), Rest}.
-%% Note that Rest is already trimmed of leading space.
-
-%% ------------------------------------------------------------------------
-%% parse_groups/1
-%%
-%% Parse according to the ABNF for message specifications in 4.4 of
-%% RFC 3588 (shown below). Again, allow names starting with a digit
-%% and also require "AVP Header" without "-" since this is what
-%% the RFC uses in all examples.
-%%
-%% Output: list of {Name, Code, Vendor, Avps}
-%%
-%% Name = atom()
-%% Code = integer()
-%% Vendor = [] | [integer()]
-%% Avps = see parse_avps/1
-
-parse_groups(Str) ->
- p_group(trim(Str), []).
-
-%% grouped-avp-def = name "::=" avp
-%%
-%% name-fmt = ALPHA *(ALPHA / DIGIT / "-")
-%%
-%% name = name-fmt
-%% ; The name has to be the name of an AVP,
-%% ; defined in the base or extended Diameter
-%% ; specifications.
-%%
-%% avp = header [ *fixed] [ *required] [ *optional]
-%% [ *fixed]
-%%
-%% header = "<" "AVP-Header:" avpcode [vendor] ">"
-%%
-%% avpcode = 1*DIGIT
-%% ; The AVP Code assigned to the Grouped AVP
-%%
-%% vendor = 1*DIGIT
-%% ; The Vendor-ID assigned to the Grouped AVP.
-%% ; If absent, the default value of zero is
-%% ; used.
-
-p_group("", Acc) ->
- lists:reverse(Acc);
-
-p_group(Str, Acc) ->
- {Next, Rest} = split_def(Str),
- report(group, Next),
- p_group(Rest, [p_group(diameter_spec_scan:parse(Next)) | Acc]).
-
-p_group([{name, Name}, '::=', '<', {name, "AVP"}, {name, "Header"},
- ':', {number, Code}
- | Toks]) ->
- {Id, [C|_] = R} = p_vendor(Toks),
- C == '>' orelse ?ERROR({invalid_group_header, R}),
- {?ATOM(Name), Code, Id, parse_avps(tl(R))};
-
-p_group(Toks) ->
- ?ERROR({invalid_group, Toks}).
-
-p_vendor([{number, I} | Toks]) ->
- {[I], Toks};
-p_vendor(Toks) ->
- {[], Toks}.
-
-%% ------------------------------------------------------------------------
-%% parse_avp_names/1
-
-parse_avp_names(Str) ->
- [p_name(N) || N <- diameter_spec_scan:parse(Str)].
-
-p_name({name, N}) ->
- ?ATOM(N);
-p_name(T) ->
- ?ERROR({invalid_avp_name, T}).
-
-%% ------------------------------------------------------------------------
-%% parse_avp_types/1
-%%
-%% Output: list() of {Name, Code, Type, Flags, Encr}
-
-parse_avp_types(Str) ->
- p_avp_types(Str, []).
-
-p_avp_types(Str, Acc) ->
- p_type(diameter_spec_scan:split(Str, 3), Acc).
-
-p_type({[],[]}, Acc) ->
- lists:reverse(Acc);
-
-p_type({[{name, Name}, {number, Code}, {name, Type}], Str}, Acc) ->
- {Flags, Encr, Rest} = try
- p_avp_flags(trim(Str), [])
- catch
- throw: {?MODULE, Reason} ->
- ?ERROR({invalid_avp_type, Reason})
- end,
- p_avp_types(Rest, [{?ATOM(Name), Code, ?ATOM(type(Type)), Flags, Encr}
- | Acc]);
-
-p_type(T, _) ->
- ?ERROR({invalid_avp_type, T}).
-
-p_avp_flags([C|Str], Acc)
- when C == $M;
- C == $P;
- C == $V ->
- p_avp_flags(Str, [?ATOM([C]) | Acc]);
-%% Could support lowercase here if there's a use for distinguishing
-%% between Must and Should in the future in deciding whether or not
-%% to set a flag.
-
-p_avp_flags([$-|Str], Acc) ->
- %% Require encr on same line as flags if specified.
- {H,T} = lists:splitwith(fun(C) -> C /= $\n end, Str),
-
- {[{name, [$X|X]} | Toks], Rest} = diameter_spec_scan:split([$X|H], 2),
-
- "" == X orelse throw({?MODULE, {invalid_avp_flag, Str}}),
-
- Encr = case Toks of
- [] ->
- "-";
- [{_, E}] ->
- (E == "Y" orelse E == "N")
- orelse throw({?MODULE, {invalid_encr, E}}),
- E
- end,
-
- Flags = ordsets:from_list(lists:reverse(Acc)),
-
- {Flags, ?ATOM(Encr), Rest ++ T};
-
-p_avp_flags(Str, Acc) ->
- p_avp_flags([$-|Str], Acc).
-
-type("DiamIdent") -> "DiameterIdentity"; %% RFC 3588
-type("DiamURI") -> "DiameterURI"; %% RFC 3588
-type("IPFltrRule") -> "IPFilterRule"; %% RFC 4005
-type("QoSFltrRule") -> "QoSFilterRule"; %% RFC 4005
-type(N)
- when N == "OctetString";
- N == "Integer32";
- N == "Integer64";
- N == "Unsigned32";
- N == "Unsigned64";
- N == "Float32";
- N == "Float64";
- N == "Grouped";
- N == "Enumerated";
- N == "Address";
- N == "Time";
- N == "UTF8String";
- N == "DiameterIdentity";
- N == "DiameterURI";
- N == "IPFilterRule";
- N == "QoSFilterRule" ->
- N;
-type(N) ->
- ?ERROR({invalid_avp_type, N}).
-
-%% ------------------------------------------------------------------------
-%% parse_commands/1
-
-parse_commands(Str) ->
- p_abbr(diameter_spec_scan:parse(Str), []).
-
- p_abbr([{name, Name}, {name, Abbrev} | Toks], Acc)
- when length(Abbrev) < length(Name) ->
- p_abbr(Toks, [{?ATOM(Name), ?ATOM(Abbrev)} | Acc]);
-
-p_abbr([], Acc) ->
- lists:reverse(Acc);
-
-p_abbr(T, _) ->
- ?ERROR({invalid_command, T}).
diff --git a/lib/diameter/src/compiler/diameter_vsn.hrl b/lib/diameter/src/compiler/diameter_vsn.hrl
new file mode 100644
index 0000000000..024d047adc
--- /dev/null
+++ b/lib/diameter/src/compiler/diameter_vsn.hrl
@@ -0,0 +1,22 @@
+%%
+%% %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%
+%%
+
+%% The version of the format of the return value of dict/0 in
+%% generated dictionary modules.
+-define(VERSION, 1).