4
%% Copyright Ericsson AB 2010-2011. All Rights Reserved.
6
%% The contents of this file are subject to the Erlang Public License,
7
%% Version 1.1, (the "License"); you may not use this file except in
8
%% compliance with the License. You should have received a copy of the
9
%% Erlang Public License along with this software. If not, it can be
10
%% retrieved online at http://www.erlang.org/.
12
%% Software distributed under the License is distributed on an "AS IS"
13
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
14
%% the License for the specific language governing rights and limitations
21
%% Parse transform for generating record access functions
23
%% This parse transform can be used to reduce compile-time
24
%% dependencies in large systems.
26
%% In the old days, before records, Erlang programmers often wrote
27
%% access functions for tuple data. This was tedious and error-prone.
28
%% The record syntax made this easier, but since records were implemented
29
%% fully in the pre-processor, a nasty compile-time dependency was
32
%% This module automates the generation of access functions for
33
%% records. While this method cannot fully replace the utility of
34
%% pattern matching, it does allow a fair bit of functionality on
35
%% records without the need for compile-time dependencies.
37
%% Whenever record definitions need to be exported from a module,
38
%% inserting a compiler attribute,
40
%% export_records([RecName, ...])
42
%% causes this transform to lay out access functions for the exported
46
%% -compile({parse_transform, diameter_exprecs}).
48
%% -record(r, {a, b, c}).
49
%% -export_records([a]).
51
%% -export(['#info-'/1, '#info-'/2,
52
%% '#new-'/1, '#new-'/2,
53
%% '#get-'/2, '#set-'/2,
54
%% '#new-a'/0, '#new-a'/1,
55
%% '#get-a'/2, '#set-a'/2,
58
%% '#info-'(RecName) ->
59
%% '#info-'(RecName, fields).
61
%% '#info-'(r, Info) ->
64
%% '#new-'(r) -> #r{}.
65
%% '#new-'(r, Vals) -> '#new-r'(Vals)
67
%% '#new-r'() -> #r{}.
68
%% '#new-r'(Vals) -> '#set-r'(Vals, #r{}).
70
%% '#get-'(Attrs, #r{} = Rec) ->
71
%% '#get-r'(Attrs, Rec).
73
%% '#get-r'(Attrs, Rec) when is_list(Attrs) ->
74
%% ['#get-r'(A, Rec) || A <- Attrs];
75
%% '#get-r'(a, Rec) -> Rec#r.a;
76
%% '#get-r'(b, Rec) -> Rec#r.b;
77
%% '#get-r'(c, Rec) -> Rec#r.c.
79
%% '#set-'(Vals, #r{} = Rec) ->
80
%% '#set-r'(Vals, Rec).
82
%% '#set-r'(Vals, Rec) when is_list(Vals) ->
83
%% lists:foldl(fun '#set-r'/2, Rec, Vals);
84
%% '#set-r'({a,V}, Rec) -> Rec#r{a = V};
85
%% '#set-r'({b,V}, Rec) -> Rec#r{b = V};
86
%% '#set-r'({c,V}, Rec) -> Rec#r{c = V}.
88
%% '#info-r'(fields) -> record_info(fields, r);
89
%% '#info-r'(size) -> record_info(size, r);
90
%% '#info-r'({index, a}) -> 1;
91
%% '#info-r'({index, b}) -> 2;
92
%% '#info-r'({index, c}) -> 3;
95
-module(diameter_exprecs).
97
-export([parse_transform/2]).
99
-include("diameter_forms.hrl").
103
parse_transform(Forms, _Options) ->
104
Rs = [R || {attribute, _, record, R} <- Forms],
105
Es = lists:append([E || {attribute, _, export_records, E} <- Forms]),
106
{H,T} = lists:splitwith(fun is_head/1, Forms),
107
H ++ [a_export(Es) | f_accessors(Es, Rs)] ++ T.
110
not lists:member(element(1,T), [function, eof]).
115
{?attribute, export, [{fname(info), 1},
121
| lists:flatmap(fun export/1, Exports)]}.
124
New = fname(new, Rname),
127
{fname(get, Rname), 2},
128
{fname(set, Rname), 2},
129
{fname(info, Rname), 1}].
133
f_accessors(Es, Rs) ->
140
| lists:flatmap(fun(N) -> accessors(N, fields(N, Rs)) end, Es)].
142
accessors(Rname, Fields) ->
145
'#get-X/2'(Rname, Fields),
146
'#set-X/2'(Rname, Fields),
147
'#info-X/1'(Rname, Fields)].
149
fields(Rname, Recs) ->
150
{Rname, Fields} = lists:keyfind(Rname, 1, Recs),
151
lists:map(fun({record_field, _, {atom, _, N}}) -> N;
152
({record_field, _, {atom, _, N}, _}) -> N
157
"#" ++ atom_to_list(Op) ++ "-".
160
list_to_atom(fname_prefix(Op)).
163
Prefix = fname_prefix(Op),
164
list_to_atom(Prefix ++ atom_to_list(Rname)).
166
%% Generated functions.
170
{?function, Fname, 1,
171
[{?clause, [?VAR('RecName')],
173
[?CALL(Fname, [?VAR('RecName'), ?ATOM(fields)])]}]}.
175
'#info-/2'(Exports) ->
176
{?function, fname(info), 2,
177
lists:map(fun 'info-'/1, Exports) ++ [?BADARG(2)]}.
180
{?clause, [?ATOM(R), ?VAR('Info')],
182
[?CALL(fname(info, R), [?VAR('Info')])]}.
184
'#new-/1'(Exports) ->
185
{?function, fname(new), 1,
186
lists:map(fun 'new-'/1, Exports) ++ [?BADARG(1)]}.
189
{?clause, [?ATOM(R)],
193
'#new-/2'(Exports) ->
194
{?function, fname(new), 2,
195
lists:map(fun 'new--'/1, Exports) ++ [?BADARG(2)]}.
198
{?clause, [?ATOM(R), ?VAR('Vals')],
200
[?CALL(fname(new, R), [?VAR('Vals')])]}.
202
'#get-/2'(Exports) ->
203
{?function, fname(get), 2,
204
lists:map(fun 'get-'/1, Exports) ++ [?BADARG(2)]}.
207
{?clause, [?VAR('Attrs'),
208
{?match, {?record, R, []}, ?VAR('Rec')}],
210
[?CALL(fname(get, R), [?VAR('Attrs'), ?VAR('Rec')])]}.
212
'#set-/2'(Exports) ->
213
{?function, fname(set), 2,
214
lists:map(fun 'set-'/1, Exports) ++ [?BADARG(2)]}.
217
{?clause, [?VAR('Vals'), {?match, {?record, R, []}, ?VAR('Rec')}],
219
[?CALL(fname(set, R), [?VAR('Vals'), ?VAR('Rec')])]}.
222
{?function, fname(new, Rname), 0,
225
[{?record, Rname, []}]}]}.
228
{?function, fname(new, Rname), 1,
229
[{?clause, [?VAR('Vals')],
231
[?CALL(fname(set, Rname), [?VAR('Vals'), {?record, Rname, []}])]}]}.
233
'#set-X/2'(Rname, Fields) ->
234
{?function, fname(set, Rname), 2,
235
[{?clause, [?VAR('Vals'), ?VAR('Rec')],
236
[[?CALL(is_list, [?VAR('Vals')])]],
237
[?APPLY(lists, foldl, [{?'fun', {function, fname(set, Rname), 2}},
240
| lists:map(fun(A) -> 'set-X'(Rname, A) end, Fields)]}.
242
'set-X'(Rname, Attr) ->
243
{?clause, [{?tuple, [?ATOM(Attr), ?VAR('V')]}, ?VAR('Rec')],
245
[{?record, ?VAR('Rec'), Rname,
246
[{?record_field, ?ATOM(Attr), ?VAR('V')}]}]}.
248
'#get-X/2'(Rname, Fields) ->
249
FName = fname(get, Rname),
250
{?function, FName, 2,
251
[{?clause, [?VAR('Attrs'), ?VAR('Rec')],
252
[[?CALL(is_list, [?VAR('Attrs')])]],
253
[{?lc, ?CALL(FName, [?VAR('A'), ?VAR('Rec')]),
254
[{?generate, ?VAR('A'), ?VAR('Attrs')}]}]}
255
| lists:map(fun(A) -> 'get-X'(Rname, A) end, Fields)]}.
257
'get-X'(Rname, Attr) ->
258
{?clause, [?ATOM(Attr), ?VAR('Rec')],
260
[{?record_field, ?VAR('Rec'), Rname, ?ATOM(Attr)}]}.
262
'#info-X/1'(Rname, Fields) ->
263
{?function, fname(info, Rname), 1,
264
[{?clause, [?ATOM(fields)],
266
[?CALL(record_info, [?ATOM(fields), ?ATOM(Rname)])]},
267
{?clause, [?ATOM(size)],
269
[?CALL(record_info, [?ATOM(size), ?ATOM(Rname)])]}
270
| lists:map(fun(A) -> 'info-X'(Rname, A) end, Fields)]}.
272
'info-X'(Rname, Attr) ->
273
{?clause, [{?tuple, [?ATOM(index), ?ATOM(Attr)]}],
275
[{?record_index, Rname, ?ATOM(Attr)}]}.