File Coverage

blib/lib/Data/Reuse.pm
Criterion Covered Total %
statement 113 117 96.5
branch 51 62 82.2
condition 5 12 41.6
subroutine 15 16 93.7
pod 3 4 75.0
total 187 211 88.6


line stmt bran cond sub pod time code
1             package Data::Reuse;
2              
3             # runtime settings we need
4 4     4   445063 use 5.008001;
  4         17  
5 4     4   19 use strict;
  4         14  
  4         147  
6 4     4   19 use warnings;
  4         9  
  4         316  
7              
8             # set up version info
9             our $VERSION= '0.13';
10              
11             # we need this otherwise nothing works
12 4     4   2376 use Data::Alias qw( alias copy );
  4         5027  
  4         365  
13              
14             # other modules we need
15 4     4   33 use Carp qw( croak );
  4         7  
  4         212  
16 4     4   19 use Digest::MD5 qw( md5 );
  4         8  
  4         268  
17 4     4   2242 use Encode qw( encode_utf8 );
  4         79509  
  4         462  
18 4     4   40 use Scalar::Util qw( reftype );
  4         7  
  4         253  
19              
20             =for Explanation:
21             Since Data::Alias uses Exporter, we might as well do that also. Otherwise
22             we'd probably hack an import method ourselves
23              
24             =cut
25              
26 4     4   21 use base 'Exporter';
  4         12  
  4         8056  
27             our @EXPORT= qw();
28             our @EXPORT_OK= qw( alias fixate forget reuse spread );
29             our %EXPORT_TAGS= ( all => \@EXPORT_OK );
30              
31             # ID prefixes
32             my $U= "\1U\1";
33             my $S= "\1S\1";
34             my $A= "\1A\1";
35             my $H= "\1H\1";
36 52         50  
37             # set up data store with predefined ro undef value
38             my %reuse;
39             forget();
40              
41             # mark constants as read only
42             Internals::SvREADONLY $_, 1 foreach ( $U, $S, $A, $H );
43              
44             # recursion level
45             my $level= 0;
46              
47             # references being handled
48             my %handling;
49              
50             # satisfy -require-
51             1;
52              
53             #---------------------------------------------------------------------------
54             # reuse
55             #
56             # Make given values, store them in the constant hash and return them
57             #
58             # IN: 1..N values to be folded into constants
59             # OUT: 1..N same as input, but read-only and folded
60              
61             sub reuse (@); # needed because of recursion
62             sub reuse (@) {
63              
64 6         9 # being called with return values
  2         4  
65 96     96 1 230004 alias my $not_void = defined wantarray;
66             # we're one level deeper
67 96         137 $level++;
68              
69             # there are values specified that haven't been folded yet or are undef
70 96 100 100     204 if ( alias my @special= grep { !defined or !exists $reuse{$_} } @_ ) {
  143         652  
71              
72             # values specified that haven't been folded yet
73 70 100       113 if ( alias my @new= grep { defined() } @special ) {
  84         196  
74 66         99 foreach (@new) { # natural aliasing
75              
76             # reference being handled, make sure it is known
77 80 100       231 if ( exists $handling{$_} ) {
    100          
    100          
78 3   0     11 alias $reuse{$_} ||= $_;
79             }
80              
81             # handle references
82             elsif ( my $reftype= reftype $_ ) {
83 52         111 $handling{$_}= $level;
84 52         61 my $id;
85              
86             # aliasing everything in here
87             alias {
88              
89             # all elements of list
90 52 100       133 if ( $reftype eq 'ARRAY' ) {
    100          
    50          
91 34         60 $id= _list_id( $_ );
92              
93             # not seen, recurse
94 34 100       79 if ( !exists $reuse{$id} ) {
95 25         26 my @list= @{$_};
  25         27  
96 25         42 (@list)= reuse @list;
97              
98             =for Explanation:
99             We need to use copy semantics, because aliasing semantics segfaults.
100             Or as Matthijs put it:
101             =
102             Hah, het is een combinatie van:
103             1. bug in perl m.b.t. \-prototypes (\[$%@] in dit geval)
104             2. de refgen operator van Data::Alias (de impliciete \)
105             3. het in void context zijn van dit blok
106             4. totaal gebrek aan argument-checking in Internals::SvREADONLY
107             het prototype maakt van:
108             =
109             Internals::SvREADONLY my @array, 1
110             =
111             zoiets als:
112             =
113             &Internals::SvREADONLY(\my @array, 1);
114             =
115             Echter hij markeert de \ zonder enige context. Dit hoort normaal alleen
116             de gebeuren voor de laatste expressie in een block, en betekent "evalueer
117             dit in de context van deze block". In dit geval is het omliggende block
118             de top-level code, en die is altijd in void context. Perl evalueert dus
119             de \ in void context.
120             =
121             Hier ben je echter in perl zelf geen last van, omdat perl's ingebouwde
122             refgen op (\) alleen maar test op list-context, en gaat er van uit dat
123             het anders scalar context is. D::A's refgen onderscheid alle drie de
124             contexts, en produceert dus niets omdat het in void context is.
125             =
126             Hierdoor wordt de call dus:
127             =
128             &Internals::SvREADONLY(1);
129             =
130             En zoals ik al zei, Internals::SvREADONLY heeft geen argument validatie
131             en probeert dus fijn op adres 0x00000001 te lezen.. SEGV
132              
133             =cut
134              
135             # mark readonly, see above
136 25         54 copy Internals::SvREADONLY @list, 1;
137              
138             # recursive structures may be replaced
139 25         33 $id= _list_id( $_ );
140             }
141             }
142              
143             # all values of hash
144             elsif ( $reftype eq 'HASH' ) {
145 12         27 $id= _hash_id( $_ );
146              
147             # not seen, recurse, set result if first
148 12 100       30 if ( !$reuse{$id} ) {
149 6         8 my %hash= %{$_};
  6         11  
150 6         56 ( @hash{ keys %hash } )= reuse values %hash;
151              
152             # mark readonly, see above
153 6         17 copy Internals::SvREADONLY %hash, 1;
154              
155             # recursive structures may be replaced
156 6         13 $id= _hash_id( $_ );
157             }
158             }
159              
160             # the value of a scalar ref
161             elsif ( $reftype eq 'SCALAR' ) {
162 6         6 my $scalar= ${$_};
  6         10  
163              
164             # may be reused
165 6 100       13 if ( defined $scalar ) {
166 5         25 $id= md5( $S . $scalar );
167              
168             # not seen, recurse, set result if first
169 5 100       15 if ( !$reuse{$id} ) {
170 2         8 ($scalar) = reuse $scalar;
171 2         7 copy Internals::SvREADONLY $scalar, 1;
172             # recursive structures may be replaced
173 2         6 $id= md5( $S . $scalar );
174             }
175             }
176              
177             # always reuse the default undef value
178             else {
179 1         2 $id= $U;
180             }
181             }
182             # huh?
183             else {
184 0         0 $id = md5( $S . $_ );
185             }
186              
187             =for Explanation:
188             When called in void context, perl may actually have used a memory location
189             for a temporary data structure that may return later with a different
190             content. As we don't want to equate those two different structures, we
191             are not going to save this reference if called in void context. And we
192             are also not going to overwrite anything that's there already.
193              
194             =cut
195 52 100 0     118 $reuse{$id} ||= $_ if $not_void;
196              
197             # store in data store
198 52   66     155 $reuse{$_} = $reuse{$id} || $_;
199             }; #alias
200              
201             # done handling this ref
202 52         110 delete $handling{$_};
203             }
204            
205             # not a ref, but now already in store
206             elsif ( exists $reuse{$_} ) {
207             }
208              
209             # not a ref, and not in store either
210             else {
211              
212             # not readonly already, make a read only copy
213 23 100       69 $_= $_, Internals::SvREADONLY $_, 1
214             if !Internals::SvREADONLY $_;
215              
216             # store in data store
217 23         64 alias $reuse{$_}= $_;
218             }
219             }
220             }
221             }
222              
223             # done on this level
224 96         120 $level--;
225              
226             # return aliases of the specified values if needed
227 96 100       204 alias return @reuse{ map { defined() ? $_ : $U } @_ } if $not_void;
  127 100       390  
228             } #reuse
229              
230             #---------------------------------------------------------------------------
231             # fixate
232             #
233             # Fixate the values of the given hash / array ref
234             #
235             # IN: 1 hash / array ref
236             # 2..N values for fixation
237              
238             sub fixate (\[@%]@) {
239              
240             # fetch structure
241 9     9 1 361063 alias my $struct= shift;
242 9 100       296 croak "Must specify a hash or array as first parameter to fixate"
243             unless my $reftype= reftype $struct;
244              
245             # just fixate existing structure
246 8 100       27 reuse($struct), return if !@_;
247              
248             # alias semantices from here on
249             alias {
250              
251             # it's a hash
252 6 100       80 if ( $reftype eq 'HASH' ) {
    50          
253 1         2 my %hash= %{$struct};
  1         4  
254 1 50       5 croak "Can only fixate specific values on an empty hash"
255             if keys %hash;
256              
257             # fill the hash and make sure only its values are reused
258 1         5 (%hash)= @_;
259 1         4 reuse \%hash; # also makes hash ro
260             }
261              
262             # it's is an array
263             elsif ( $reftype eq 'ARRAY' ) {
264 5         7 my @array= @{$struct};
  5         17  
265 5 50       14 croak "Can only fixate specific values on an empty array"
266             if @array;
267              
268             # fill the array and make sure its values are reused
269 5         14 (@array)= reuse @_;
270 5         16 copy Internals::SvREADONLY @array, 1; # must copy, see above
271             }
272              
273             # huh?
274             else {
275 0         0 croak "Don't know how to fixate '$reftype' references";
276             }
277             };
278              
279 6         17 return;
280             } #fixate
281              
282             #---------------------------------------------------------------------------
283             # spread
284             #
285             # Spread a shared constant value in a data structure
286             #
287             # IN: 1 data structure (hash / array ref)
288             # 2 value to be set (default: undef )
289             # 3..N keys / indexes to set
290              
291             sub spread (\[@%]@) {
292              
293             # find out where to spread
294 3     3 1 222225 alias my $struct= shift;
295 3 50       19 croak "Must specify a hash or array as first parameter to spread"
296             unless my $reftype= reftype $struct;
297              
298             # huh? no value?
299 3 100       274 croak "Must specify a value as second parameter to spread"
300             if !@_;
301              
302             # fetch proper constant alias
303 2         10 alias my $value= reuse shift;
304              
305             # nothing to be done
306 2 50       8 return if !@_;
307              
308             # alias semantics from here on
309             alias {
310              
311             # it's a hash, but can we do it?
312 2 100       10 if ( $reftype eq 'HASH' ) {
    50          
313 1         2 my %hash= %{$struct};
  1         3  
314 1 50       6 croak "Cannot spread values in a restricted hash"
315             if Internals::SvREADONLY %hash;
316              
317             # spread the values in the hash
318 1         8 $hash{$_}= $value foreach @_;
319             }
320              
321             # it's an array, but can we do it?
322             elsif ( $reftype eq 'ARRAY' ) {
323 1         3 my @array= @{$struct};
  1         3  
324 1 50       4 croak "Cannot spread values in a restricted array"
325             if Internals::SvREADONLY @array;
326              
327             # spread the values in the list
328 1         6 $array[$_]= $value foreach @_;
329             }
330              
331             # huh?
332             else {
333 0         0 croak "Don't know how to spread values in '$reftype' references";
334             }
335             };
336              
337 2         6 return;
338             } #spread
339              
340             #---------------------------------------------------------------------------
341             # forget
342             #
343             # Forget about the values that have been reused so far, or since the last
344             # time "forget" was called.
345              
346             sub forget {
347              
348             # copy a fresh undef value (shouldn't alias the system undef!)
349 5     5 0 2501 %reuse= ( $U => undef );
350              
351             # make sure this undef can't be changed
352 5         18 Internals::SvREADONLY $reuse{$U}, 1;
353              
354 5         10 return;
355             } #forget
356              
357             #---------------------------------------------------------------------------
358             #
359             # Internal methods
360             #
361             #---------------------------------------------------------------------------
362             # _hash_id
363             #
364             # Return the ID for a hash ref
365             #
366             # IN: 1 hash ref
367             # OUT: 1 id
368              
369             sub _hash_id {
370 18     18   19 alias my %hash= %{ $_[0] };
  18         28  
371              
372             return md5( encode_utf8( $H . join $;, map {
373 18 50       56 $_ => ( defined $hash{$_} ? $hash{$_} : $U )
  35         169  
374             } sort keys %hash ) );
375             } #_hash_id
376              
377             #---------------------------------------------------------------------------
378             # _list_id
379             #
380             # Return the ID for a list ref
381             #
382             # IN: 1 list ref
383             # OUT: 1 id
384              
385             sub _list_id {
386 59     59   90 alias my @list= @{ $_[0] };
  59         72  
387              
388 59 50       74 return md5( $A . join $;, map { defined() ? $_ : $U } @list );
  118         346  
389             } #_list_id
390              
391             #---------------------------------------------------------------------------
392             #
393             # Debug methods
394             #
395             #---------------------------------------------------------------------------
396             # _constants
397             #
398             # Return hash ref of hash containing the constant values
399             #
400             # OUT: 1 hash ref
401              
402 0     0     sub _constants { return \%reuse } #_constants
403              
404             #---------------------------------------------------------------------------
405              
406             __END__