File Coverage

blib/lib/FakeHash.pm
Criterion Covered Total %
statement 159 221 71.9
branch 42 70 60.0
condition 4 9 44.4
subroutine 36 49 73.4
pod 0 20 0.0
total 241 369 65.3


line stmt bran cond sub pod time code
1              
2             =head1 NAME
3              
4             FakeHash - Simulate the behavior of a Perl hash variable
5              
6             =head1 SYNOPSIS
7              
8             use FakeHash;
9             my $hash = FakeHash->new;
10             $hash->store($key, $value); # analogous to $h{$key} = $value
11             @keys = $hash->keys; # analogous to @keys = keys %h
12             $hash->delete($key); # analogous to delete $h{$key}
13             $value = $hash->fetch($key); # analogous to $value = $h{$key}
14             $string = $hash->scalarval; # analogous to $string = %h
15             $string = $hash->clear; # analogous to %h = ()
16              
17             $hash->iterate(...); # Invoke callbacks for each bucket and node
18            
19             # Caution: Not tested
20             my $hash = tie %h => FakeHash; # $hash will mirror the changes to %h
21              
22             use FakeHash 'hashval';
23             $n = hashval($string); # hash value for string
24              
25             FakeHash->version(5.005); # Use Perl 5.005 hashval function
26             $version = FakeHash->version; # Return Perl version currently in force
27              
28             =head1 DESCRIPTION
29              
30             C simulates the behavior of a Perl hash variable,
31             maintaining a synthetic data structure that mirrors the true data
32             structure inside of Perl. This can be used to investigate hash
33             performance or behavior. For example, see the C
34             class, described below, which draws a box-and-arrow diagram
35             representing the memory layout of a hash.
36              
37             The C, C, C, and C methods perform the
38             corresponding operations on the simulated hash.
39              
40             The C method iterates over the simulated structure and
41             invokes user-supplied callbacks. The arguments to C are a
42             hash of I, and an optional I.
43              
44             The C hash may have any or all of the following keys:
45              
46             =over
47              
48             =item B
49              
50             A function that is called once for each bucket in the hash, prior to
51             iterating over the nodes in the bucket. The arguments to the
52             C function are: the bucket number; a C
53             object representing the first node in the bucket (or an undefined
54             value of the bucket is empty,) and the user parameter.
55              
56             =item B
57              
58             The same, except that the function is called after iterating over the
59             nodes in the bucket.
60              
61             =item B
62              
63             A function that is called once for each node (key-value pair) in the
64             hash. The node function is called for a node after the C
65             function and before the C function is called for the
66             node's bucket.
67              
68             The arguments to the C function are: The bucket number; a
69             C object representing the first node in the bucket;
70             the node's number within the bucket (0 for the first node in the
71             bucket); the node itself; and the user parameter.
72              
73             =item B
74              
75             If this is a number, say I, C will only iterate over the
76             first I buckets, and will skip the later buckets and their
77             contents. If this is a function, C will call it once, with
78             the user paramater as its argument, and will expect it to return a
79             number I to be used as above. If it is omitted, C will
80             iterate over all buckets and their contents.
81              
82             =back
83              
84             For example, the C method is implemented as a call to C, as follows:
85              
86             sub keys {
87             my $self = shift;
88             my @r;
89             $self->iterate({node => sub { my ($i, $b, $n, $node) = @_;
90             push @r, $node->key;
91             },
92             });
93             @r;
94             }
95              
96             =head2 Other Methods
97              
98             CDEBUG> will return the current setting of the C
99             flag, and will change the value of the flag if given an argument.
100             When the C flag is set to a true value, the module may emit
101             diagnostic messages to C.
102              
103             Each C object may carry auxiliary information. Auxiliary
104             information is not used by C but may be used by subclasses.
105             C<$hash-Eset_defaults(key, value, key, value,...)> sets the
106             specified auxiliariy data values for the C object. A
107             hashref may be passed instead; its contents will be appended to the
108             values already installed. To query the currently-set values, use
109             C<$hash-Edefaults(key, key, ...)>, which will return a list of the
110             corresponding values, or, in scalar context, a reference to an array
111             of the corresponding values.
112              
113             C<$hash-Esize> retrieves the number of buckets in the
114             hash.
115              
116             The Perl hash function changed between versions 5.005 and 5.6, so the
117             behavior of Perl hashes changed at the same time. By default,
118             C will emulate the behavior of whatever version of Perl it
119             is running under. To change this, use the C method. Its
120             argument is the version of Perl that you would like to emulate. It
121             returns the version number prior to setting.
122              
123             =cut
124              
125             package FakeHash;
126             $VERSION = '0.80';
127 1     1   528 use strict 'vars', 'refs';
  1         1  
  1         99  
128             sub croak;
129              
130             my $DEBUG = 0;
131             my $VERSION = $];
132             my $INIT_SIZE = 8; # Do not touch
133              
134             sub import {
135 1     1   7 my $caller = caller;
136 1         2 my $class = shift;
137 1         1709 for (@_) {
138 0 0       0 unless ($_ eq 'hashval') {
139 0         0 croak "$_ not exported by FakeHash";
140             }
141 1     1   5 no strict 'refs';
  1         8  
  1         1832  
142 0         0 *{"$caller\::$_"} = \&{"$class\::$_"};
  0         0  
  0         0  
143             }
144             }
145              
146             # I am a constant-like subroutine *and* a class method
147             sub DEBUG {
148 37     37 0 35 shift; # class name
149 37         29 my $old_debug = $DEBUG;
150 37 50       53 $DEBUG = shift if @_;
151 37         72 $old_debug;
152             }
153              
154             sub version {
155 0     0 0 0 shift; # class name
156 0         0 my $old_version = $VERSION;
157 0 0       0 $VERSION = shift if @_;
158 0         0 $old_version;
159             }
160              
161             sub new {
162 1     1 0 12 my $self = { B => [(undef) x $INIT_SIZE],
163             K => 0,
164             S => $INIT_SIZE,
165             D => {},
166             };
167 1         5 bless $self, shift();
168             }
169              
170             sub set_defaults {
171 2     2 0 3 my $self = shift;
172 2         4 my $kvps;
173 2 100       9 if (@_ == 1) {
    50          
174 1         2 $kvps = shift;
175             } elsif (@_ % 2 == 0) {
176 1         3 my %kvps = @_;
177 1         2 $kvps = \%kvps;
178             } else {
179 0         0 croak "usage: \$fakehash->default(\$hashref) or \$fakehash->default(key, val, ...)";
180             }
181 2         13 while (my ($k => $v) = each %$kvps) {
182 4         25 $self->{D}{$k} = $v;
183             }
184             }
185              
186             sub defaults {
187 46     46 0 67 my ($self) = shift;
188 46         38 my @r ;
189 46         62 for (@_) {
190 46         117 push @r, $self->{D}{$_};
191             }
192 46 50       107 wantarray ? @r : \@r;
193             }
194              
195             sub TIEHASH {
196 0     0   0 my ($pack) = @_;
197 0         0 $pack->new();
198             }
199              
200             sub FETCH {
201 0     0   0 my ($self, $k) = @_;
202 0         0 $self->fetch($k);
203             }
204              
205             sub STORE {
206 0     0   0 my ($self, $k, $v) = @_;
207 0         0 $self->store($k, $v);
208             }
209              
210             sub DELETE {
211 0     0   0 my ($self, $k) = @_;
212 0         0 $self->delete($k);
213             }
214              
215             sub CLEAR {
216 0     0   0 my ($self) = @_;
217 0         0 $self->clear();
218             }
219              
220             sub scalarval {
221 0     0 0 0 my ($self) = @_;
222 0         0 my $n = grep defined, @{$self->{B}};
  0         0  
223 0         0 my $d = $self->size;
224 0         0 "$n/$d";
225             }
226              
227             sub size {
228 30     30 0 30 my $self = shift;
229 30         32 my $old_size = $self->{S};
230 30 100       51 if (@_) {
231 1         3 $self->{S} = round_up(shift());
232 1         2 $#{$self->{B}} = $self->{S} - 1;
  1         4  
233             }
234 30         102 $old_size;
235             }
236              
237              
238             sub iterate {
239 2     2 0 3 my ($self, $actions, $u) = @_;
240 2         5 my $s = $actions->{maxbucket};
241 2 50       9 if (ref $s) {
    50          
242 0         0 $s = $s->($u);
243             } elsif (! defined $s) {
244 2         3 $s = $self->size;
245             }
246 2         6 for (my $i=0;
247             $i < $s;
248             $i++) {
249 32         51 my $b = $self->_bucket($i);
250 32 100       67 $actions->{prebucket}->($i, $b, $u) if exists $actions->{prebucket};
251 32         30 my $nodeno = 0;
252 32         63 for (my $node = $b;
253             $node;
254             $node = $node->next) {
255 26 50       78 $actions->{node}->($i, $b, $nodeno++, $node, $u) if exists $actions->{node};
256             }
257 32 50       96 $actions->{postbucket}->($i, $b, $nodeno, $u) if exists $actions->{postbucket};
258             }
259             }
260              
261             sub store {
262 13     13 0 93 my ($self, $key, $value) = @_;
263 13         24 my $hash = hashval($key);
264 13         26 my $bucket = $hash % $self->size;
265 13         28 $self->h_insert_h($key, $value, $hash, $bucket);
266             }
267              
268             sub h_insert_h {
269 13     13 0 15 my ($self, $key, $value, $hash, $bucket) = @_;
270 13 50       27 if (my $node = $self->_search_bucket($bucket, $key, $hash)) {
271 0         0 $node->value($value);
272             } else {
273 13         19 my $head_node = $self->_bucket($bucket);
274 13         30 $self->_append_bucket($bucket, FakeHash::Node->new($key, $value, $hash));
275 13         13 ++$self->{K}; ## MOVE ME
276 13 100 100     34 $self->double_size() if $self->is_full && ! $head_node;
277             }
278             }
279              
280             sub keys {
281 1     1 0 8 my $self = shift;
282 1         2 my @r;
283 13     13   13 $self->iterate({node => sub { my ($i, $b, $n, $node) = @_;
284 13         22 push @r, $node->key;
285             },
286 1         11 });
287 1         8 @r;
288             }
289              
290             sub is_full {
291 13     13 0 9 my $self = shift;
292 13         66 $self->{K} >= $self->{S};
293             }
294              
295              
296             sub clone {
297 0     0 0 0 my $self = shift;
298 0         0 my $new = (ref $self)->new;
299 0         0 $new->{S} = $self->{S};
300 0         0 $new->{K} = $self->{K};
301 0         0 $new->{B} = [@{$self->{B}}];
  0         0  
302 0         0 bless $new => (ref $self);
303             }
304              
305             sub double_size {
306 1     1 0 2 my ($self) = @_;
307 1         4 my $os = $self->size;
308 1         2 my $ns = $os * 2;
309 1 50       3 print STDERR "Reconstructing from $os -> $ns\n" if DEBUG
310             $self->size($ns);
311              
312             # copied and translated from 5.6.0 hv.c:892 ff
313 1         5 for (my $i=0; $i< $os; $i++) {
314 8 50       12 print STDERR "Bucket #$i:\n" if DEBUG;
315 8         9 my $prev;
316 8 100       13 for (my $entry = $self->_bucket($i);
317             $entry;
318             $entry = $prev ? $prev->next : $self->_bucket($i)) {
319 11 50       14 print STDERR " entry($entry->[0])\n" if DEBUG;
320 11         15 my $hash = $entry->hash;
321 11 50       16 print STDERR " hash = $hash, ", "lowbits = ", $hash & ($ns-1), "\n"
322             if DEBUG;
323 11 100       18 if (($hash & ($ns - 1)) != $i) { # $entry needs to move
324 6 50       9 print STDERR " RELOCATING\n" if DEBUG;
325              
326             # fix pointer that was *to* $entry
327 6 100       10 if ($prev) {
328 3         6 $prev->next($entry->next);
329             } else {
330 3         5 $self->_bucket($i, $entry->next);
331             }
332            
333             # fix pointer *from* $entry
334             # and insert $entry at beginning of bucket b
335 6         11 $entry->next($self->_bucket($i + $os));
336 6         17 $self->_bucket($i + $os, $entry);
337             } else {
338 5         12 $prev = $entry;
339             }
340             }
341             }
342             }
343              
344             sub clear {
345             my $self = shift;
346             my $size = $self->size;
347             @{$self->{B}} = (undef) x $size;
348             }
349              
350             sub _search_bucket {
351 13     13   15 my ($self, $b, $k, $h) = @_;
352 13         23 for (my $node = $self->_bucket($b);
353             $node;
354             $node = $node->next) {
355 9 50 33     19 return $node if $h == $node->hash && $k eq $node->key;
356             }
357 13         26 return;
358             }
359              
360             sub _append_bucket {
361 13     13   39 my ($self, $b, $node) = @_;
362 13         19 $node->next($self->_bucket($b));
363 13         21 $self->_bucket($b, $node);
364             }
365              
366             sub _bucket {
367 110     110   125 my ($self, $b, $new) = @_;
368 110         128 my $old = $self->{B}[$b];
369 110 100       181 $self->{B}[$b] = $new if @_ > 2;
370 110         194 $old;
371             }
372              
373             sub delete {
374 0     0 0 0 my ($self, $key) = @_;
375 0         0 my $h = hashval($key);
376 0         0 my $s = $self->size;
377 0         0 my $b = $h & ($s-1);
378 0         0 my ($prev, $cur);
379 0         0 for ($cur = $self->_bucket($b);
380             $cur;
381             $prev = $cur, $cur = $cur->next) {
382 0 0 0     0 next unless $cur->hash == $h && $cur->key eq $key;
383 0 0       0 if ($prev) {
384 0         0 $prev->next($cur->next);
385             } else {
386 0         0 $self->_bucket($b, $cur->next);
387             }
388             }
389             }
390              
391             sub fetch {
392 0     0 0 0 my ($self, $key) = @_;
393 0         0 my $h = hashval($key);
394 0         0 my $s = $self->size;
395 0         0 my $b = $self->_bucket($h & ($s-1));
396 0         0 $self->_search_bucket($b, $key, $h);
397             }
398              
399             sub clear {
400 0     0 0 0 my $self = shift;
401 0         0 %$self = %{$self->new};
  0         0  
402             }
403              
404             sub croak {
405 0     0 0 0 require Carp;
406 0         0 Carp::croak(@_);
407             }
408              
409             # thanks to I0 from perlmonks for this
410             # extremely clever solution
411             sub round_up {
412 1     1 0 3 my $x = shift;
413 1 50       11 return $x unless $x & ($x-1);
414 0         0 for (1, 2, 4, 8, 16) {
415 0         0 $x |= $x >> $_;
416             }
417 0         0 ++$x;
418             }
419              
420             # sub round_up {
421             # my $z = my $x = shift;
422             # return $x unless $x & ($x-1);
423             # while ($x) {
424             # $z = $x;
425             # $x &= $x-1;
426             # }
427             # $z<<1;
428             # }
429              
430             sub _B32 () { 2**32 - 1} # constant
431              
432             # i am not a method
433             sub hashval {
434 1     1   786 use integer;
  1         12  
  1         4  
435 13     13 0 14 my ($string) = @_;
436 13         12 my $h = 0;
437 13         29 for my $c (split //, $string) {
438 58         60 $h = ($h * 33 + ord($c));
439             }
440 13 50       27 $h += $h >> 5 if $VERSION >= 5.006;
441             # print STDERR "HASH $string => $h ($VERSION)\n";
442 13         18 return $h;
443             }
444              
445              
446             =head1 NAME
447              
448             FakeHash::DrawHash - Draw a C diagram of the internal structure of a hash
449              
450             =head1 SYNOPSIS
451              
452             my $hash = FakeHash::DrawHash->new;
453            
454             # see L for more details
455              
456             $hash->draw($filehandle); # Print 'pic' commands to filehandle
457              
458             =head1 DESCRIPTION
459              
460             C is a subclass of C that can draw a
461             picture of the internal structure of a Perl hash variable. It emits
462             code suitable for the Unix C drawing program.
463              
464             C provides the following methods:
465              
466             =head2 draw
467              
468             Emit C code for a box-and-arrow diagram that represents the
469             current state of the simulated hash. A filehandle argument may be
470             provided to receive the output. If omitted, output goes to C.
471             Additionally, a user parameter argument may be provided, which will be
472             passed to the other C methods.
473              
474             =head2 draw_param
475              
476             Set or retrieve various parameters dermining box size and layout.
477             Takes a name and an optional value argument and returns the old value
478             associated with the name. If the value is provided, sets the new
479             value. Valid names are:
480              
481             =over 4
482              
483             =item B
484              
485             Determines the size of the boxes used to represent each hash bucket.
486             The value should be a reference to an array of the height and width,
487             in inches.
488              
489             Defaults to C<[1, 0.55]>, or one inch wide by 0.55 inches tall.
490              
491             =item B
492              
493             Amount of horizontal space,in inches, between the box that represents
494             a bucket and the bixes that represent the bucket contents. If zero,
495             the buckets will abut their contents.
496              
497             Defaults to 1/5 inch.
498              
499             =item B
500              
501             The size of the boxes used to represent each key-value node. The
502             value should be a reference to an array of the height and width, in
503             inches.
504              
505             Defaults to C<[1, 0.5]>, or one inch wide by half an inch tall.
506              
507             =back
508              
509             =head2 draw_start
510              
511             Called once, each time drawing commences. Arguments: The filehandle
512             and user parameter, if any, that were passed to C.
513              
514             =head2 draw_end
515              
516             Called once, just at the end of each call to C. Arguments: The
517             filehandle and user parameter, if any, that were passed to C.
518              
519             =head2 draw_bucket
520              
521             Called each time C needs to draw a single bucket.
522              
523             Arguments: The filehandle that was passed to C; the bucket
524             number (starting from 0) of the current bucket; a boolean value which
525             is true if and only if the bucket is nonempty; and the user parameter
526             that was passed to C.
527              
528             =head2 draw_node
529              
530             Called each time C needs to draw a single key-value node.
531              
532             Arguments: The filehandle that was passed to C; the bucket
533             number (starting from 0) of the bucket in which the current node
534             resides; the number of the node in the current bucket (the first node
535             is node zero); a C object representing the node
536             itself; and the user parameter that was passed to C.
537              
538             =head1 IDEA
539              
540             The theory here is that it should be easy to override these methods
541             with corresponding methods that draw the diagram in PostScript or GD
542             or whatever.
543              
544             If you do this, please send me the code so that I can distribute it.
545              
546             =cut
547              
548             package FakeHash::DrawHash;
549              
550 1     1   867 BEGIN { @FakeHash::DrawHash::ISA = 'FakeHash' }
551              
552             my %defaults = ( BUCKET => [1, 0.55],
553             KVP => [1, 0.5],
554             BUCKETSPACE => 0.2,
555             );
556              
557             sub new {
558 1     1   273106 my $class = shift;
559 1         16 my $self = $class->SUPER::new(@_);
560 1         12 $self->set_defaults(\%defaults);
561 1         3 $self;
562             }
563              
564             sub draw_param {
565 46     46   90 my ($self, $key, $value) = @_;
566 46         77 my ($old) = $self->defaults($key);
567              
568 46 100       76 if (defined $value) {
569 1         3 $self->set_defaults($key, $value);
570             }
571              
572 46         78 $old;
573             }
574              
575             sub draw {
576 1     1   16 my ($self, $fh, $u) = @_;
577 1         2 local *FH;
578 1 50       44 if (! defined $fh) {
    50          
579 0         0 $fh = \*STDOUT;
580             } elsif (! defined fileno $fh) {
581 0 0       0 FakeHash::croak "Couldn't open file $fh" unless open FH, "< $fh";
582 0         0 $fh = \*FH;
583             }
584            
585 1         5 $self->draw_start($fh, $u);
586             $self->iterate({ prebucket => sub {
587 16     16   17 my ($b, $bucket) = @_;
588 16         36 $self->draw_bucket($fh, $b, defined $bucket, $u);
589             },
590             node => sub {
591 13     13   18 my ($b, $bucket, $n, $node) = @_;
592 13         21 $self->draw_node($fh, $b, $n, $node, $u);
593             },
594 1         9 });
595 1         7 $self->draw_end($fh, $u);
596             }
597              
598             sub draw_bucket {
599 16     16   24 my ($self, $fh, $bucket_no, $nonempty) = @_;
600 16         13 my ($wd, $ht) = @{$self->draw_param('BUCKET')};
  16         28  
601 16         29 my $bs = $self->draw_param('BUCKETSPACE');
602 16         69 print $fh "boxwid:=$wd; boxht:=$ht\n";
603 16         37 printf $fh "B%02d: box ", $bucket_no;
604 16 100       50 printf $fh "with .n at B%02d.s", $bucket_no-1 if $bucket_no > 0;
605 16         25 printf $fh "\n";
606 16 100       29 if ($nonempty) {
607 9         21 printf $fh "circle at B%02d.c rad 0.1 filled\n", $bucket_no;
608 9         44 printf $fh "arrow from B%02d.c right boxwid/2 + $bs\n", $bucket_no;
609             }
610             }
611              
612             # this method assumes that the current 'pic' position is already
613             # correct, which might not be true if one of the other methods is
614             # overriden. Fix it.
615             sub draw_node {
616 13     13   16 my ($self, $fh, $bucket_no, $node_index, $node) = @_;
617 13         18 my ($k, $v, $h, $next) = @$node;
618 13         11 my ($wd, $ht) = @{$self->draw_param('KVP')};
  13         22  
619 13         46 print $fh "boxwid:=$wd; boxht:=$ht\n";
620 13         25 printf $fh qq{N%02d%02d: box "%s" "%s" "%u(%u)"\n}, $bucket_no, $node_index, $k, $v, $h, $h&($self->size * 2 - 1);
621             }
622              
623             sub draw_start {
624 1     1   2 my ($self, $fh) = @_;
625 1         13 print $fh ".PS\n";
626             }
627              
628             sub draw_end {
629 1     1   2 my ($self, $fh) = @_;
630 1         5 print $fh ".PE\n";
631             }
632              
633              
634             =head1 NAME
635              
636             FakeHash::Node - Class used internally by C to represent key-value pairs
637              
638             =head1 SYNOPSIS
639              
640             $key = $node->key;
641             $value = $node->value;
642             $hash = $node->hash;
643             $next = $node->next;
644              
645             =head1 DESCRIPTION
646              
647             C is used internally by C for various
648             purposes. For example, the C function invokes a
649             user-supplied callback for each key-value pair, passing it a series of
650             C objects that represent the key-value pairs.
651              
652             The C and C methods retrieve the key and value of a node.
653             The C method retrieves the key's hash value.
654              
655             C<$node-Enext> method retrieves the node that follows C<$node> in
656             its bucket, or an undefined value if C<$node> is last in its bucket.
657              
658             If any of these methods is passed an additional argument, it will set
659             the corresponding value. It will return the old value in any case.
660              
661             =cut
662              
663             package FakeHash::Node;
664              
665             sub new {
666 13     13   23 my ($class, @data) = @_;
667 13         39 bless \@data => $class;
668             }
669              
670             sub _access {
671 104     104   81 my $self = shift;
672 104         83 my $index = shift;
673 104         99 my $oldval = $self->[$index];
674 104 100       155 $self->[$index] = shift if @_;
675 104         230 $oldval;
676             }
677              
678             sub key {
679 13     13   14 my $self = shift;
680 13         18 $self->_access(0, @_);
681             }
682              
683             sub value {
684 0     0   0 my $self = shift;
685 0         0 $self->_access(1, @_);
686             }
687              
688             sub hash {
689 20     20   18 my $self = shift;
690 20         35 $self->_access(2, @_);
691             }
692              
693             sub next {
694 71     71   63 my $self = shift;
695 71         112 $self->_access(3, @_);
696             }
697              
698             1;
699              
700             =head1 AUTHOR
701              
702             Mark-Jason Dominus (C)
703              
704             =head1 COPYRIGHT
705              
706             C is a Perl module that simulates the behavior of a Perl hash
707             variable. C renders a diagram of a simulated hash.
708              
709             Copyright (C) 200 Mark-Jason Dominus
710              
711             This program is free software; you can redistribute it and/or modify it
712             under the terms of the GNU General Public License as published by the Free
713             Software Foundation; either version 2 of the License, or (at your option)
714             any later version.
715              
716             This program is distributed in the hope that it will be useful, but WITHOUT
717             ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
718             FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
719             more details.
720              
721             You should have received a copy of the GNU General Public License along
722             with this program; if not, write to the Free Software Foundation, Inc., 675
723             Mass Ave, Cambridge, MA 02139, USA.
724              
725             =cut