File Coverage

blib/lib/Memoize.pm
Criterion Covered Total %
statement 162 171 94.7
branch 73 82 89.0
condition 23 29 79.3
subroutine 19 20 95.0
pod 2 3 66.6
total 279 305 91.4


line stmt bran cond sub pod time code
1             # -*- mode: perl; perl-indent-level: 2; -*-
2             # vim: ts=8 sw=2 sts=2 noexpandtab
3              
4             # Memoize.pm
5             #
6             # Copyright 1998, 1999, 2000, 2001, 2012 M. J. Dominus.
7             # You may copy and distribute this program under the
8             # same terms as Perl itself.
9              
10 16     16   78240 use strict; use warnings;
  16     16   110  
  16         345  
  16         63  
  16         23  
  16         619  
11              
12             package Memoize;
13             our $VERSION = '1.13';
14              
15 16     16   67 use Carp;
  16         27  
  16         1221  
16 16     16   81 use Config; # Dammit.
  16         29  
  16         866  
17              
18 16     16   81 BEGIN { require Exporter; *import = \&Exporter::import }
  16         5240  
19             our @EXPORT = qw(memoize);
20             our @EXPORT_OK = qw(unmemoize flush_cache);
21              
22             my %memotable;
23             my @CONTEXT_TAGS = qw(MERGE TIE MEMORY FAULT HASH);
24             my %IS_CACHE_TAG = map {($_ => 1)} @CONTEXT_TAGS;
25              
26             sub CLONE {
27 0     0   0 my @info = values %memotable;
28 0         0 %memotable = map +($_->{WRAPPER} => $_), @info;
29             }
30              
31             # Raise an error if the user tries to specify one of thesepackage as a
32             # tie for LIST_CACHE
33             my %scalar_only = map {($_ => 1)} qw(DB_File GDBM_File SDBM_File ODBM_File), map +($_, "Memoize::$_"), qw(AnyDBM_File NDBM_File);
34              
35             sub memoize {
36 68     68 0 349922 my $fn = shift;
37 68         170 my %options = @_;
38              
39 68 100 100     348 unless (defined($fn) &&
      66        
40             (ref $fn eq 'CODE' || ref $fn eq '')) {
41 3         232 croak "Usage: memoize 'functionname'|coderef {OPTIONS}";
42             }
43              
44 65         116 my $uppack = caller; # TCL me Elmo!
45 65 100       140 my $name = (ref $fn ? undef : $fn);
46 65         118 my $cref = _make_cref($fn, $uppack);
47 64         96 my $proto = prototype $cref;
48 64 100       127 $proto = defined $proto ? "($proto)" : '';
49              
50             # I would like to get rid of the eval, but there seems not to be any
51             # other way to set the prototype properly. The switch here for
52             # 'usethreads' works around a bug in threadperl having to do with
53             # magic goto. It would be better to fix the bug and use the magic
54             # goto version everywhere.
55 64         68 my $info;
56             my $wrapper =
57             $Config{usethreads}
58 64 50   48   5909 ? eval "sub $proto { &_memoizer(\$info, \@_); }"
  48         23009189  
  48         292  
  21         1645  
  21         45  
  10         859  
  10         22  
  89         447  
  89         153  
  2         285  
  2         9  
  1         231  
  1         4  
59             : eval "sub $proto { unshift \@_, \$info; goto &_memoizer; }";
60              
61 64         176 my $normalizer = $options{NORMALIZER};
62 64 100 100     173 if (defined $normalizer && ! ref $normalizer) {
63 5         11 $normalizer = _make_cref($normalizer, $uppack);
64             }
65              
66             my $install_name = exists $options{INSTALL}
67             ? $options{INSTALL} # use given name (or, if undef: do not install)
68 64 100       137 : $name; # no INSTALL option provided: default to original name if possible
69              
70 64 100       120 if (defined $install_name) {
71 40 100       125 $install_name = $uppack . '::' . $install_name
72             unless $install_name =~ /::/;
73 16     16   93 no strict;
  16         24  
  16         460  
74 16     16   76 no warnings 'redefine';
  16         27  
  16         10532  
75 40         52 *{$install_name} = $wrapper; # Install memoized version
  40         126  
76             }
77              
78             # These will be the caches
79 64         83 my %caches;
80 64         113 for my $context (qw(SCALAR LIST)) {
81             # suppress subsequent 'uninitialized value' warnings
82 127   100     422 my $fullopt = $options{"${context}_CACHE"} ||= '';
83 127 100       258 my ($cache_opt, @cache_opt_args) = ref $fullopt ? @$fullopt : $fullopt;
84 127 100 66     331 if ($cache_opt eq 'FAULT') { # no cache
    100          
    100          
85 18         38 $caches{$context} = undef;
86             } elsif ($cache_opt eq 'HASH') { # user-supplied hash
87 18         26 my $cache = $cache_opt_args[0];
88 18         44 my $package = ref(tied %$cache);
89 18 100 100     59 if ($context eq 'LIST' && $scalar_only{$package}) {
90 2         299 croak("You can't use $package for LIST_CACHE because it can only store scalars");
91             }
92 16         41 $caches{$context} = $cache;
93             } elsif ($cache_opt eq '' || $IS_CACHE_TAG{$cache_opt}) {
94             # default is that we make up an in-memory hash
95 89         189 $caches{$context} = {};
96             # (this might get tied later, or MERGEd away)
97             } else {
98 2         174 croak "Unrecognized option to `${context}_CACHE': `$cache_opt' should be one of (@CONTEXT_TAGS)";
99             }
100             }
101              
102             # Perhaps I should check here that you didn't supply *both* merge
103             # options. But if you did, it does do something reasonable: They
104             # both get merged to the same in-memory hash.
105 60 100       168 if ($options{SCALAR_CACHE} eq 'MERGE') {
    100          
106 3         6 $options{MERGED} = 1;
107 3         6 $caches{SCALAR} = $caches{LIST};
108             } elsif ($options{LIST_CACHE} eq 'MERGE') {
109 5         9 $options{MERGED} = 1;
110 5         9 $caches{LIST} = $caches{SCALAR};
111             }
112              
113             # Now deal with the TIE options
114             {
115 60         73 my $context;
  60         57  
116 60         80 foreach $context (qw(SCALAR LIST)) {
117             # If the relevant option wasn't `TIE', this call does nothing.
118 120         270 _my_tie($context, $caches{$context}, $options{"${context}_CACHE"}); # Croaks on failure
119             }
120             }
121              
122             $info =
123             {
124             N => $normalizer,
125             U => $cref,
126             NAME => $install_name,
127             S => $caches{SCALAR},
128             L => $caches{LIST},
129             MERGED => $options{MERGED},
130 57         274 };
131              
132 57         167 $memotable{$wrapper} = {
133             INFO => $info,
134             WRAPPER => $wrapper, # cannot be in $info because $wrapper captures $info
135             };
136              
137 57         197 $wrapper # Return just memoized version
138             }
139              
140             # This function tries to load a tied hash class and tie the hash to it.
141             sub _my_tie {
142 122     120   440 my ($context, $hash, $fullopt) = @_;
143              
144             # We already checked to make sure that this works.
145 122 100       217 my ($shortopt, $module, @args) = ref $fullopt ? @$fullopt : $fullopt;
146              
147 124 100 66     1483 return unless defined $shortopt && $shortopt eq 'TIE';
148 11 100       532 carp("TIE option to memoize() is deprecated; use HASH instead")
149             if warnings::enabled('all');
150              
151 7 100 100     101 if ($context eq 'LIST' && $scalar_only{$module}) {
152 2         162 croak("You can't use $module for LIST_CACHE because it can only store scalars");
153             }
154 5         13 my $modulefile = $module . '.pm';
155 5         16 $modulefile =~ s{::}{/}g;
156 5         281 require $modulefile;
157 4         83 my $rc = (tie %$hash => $module, @args);
158 4 50       14 unless ($rc) {
159 0         0 croak "Couldn't tie memoize hash to `$module': $!";
160             }
161 4         11 1;
162             }
163              
164             sub flush_cache {
165 2     2 1 8 my $func = _make_cref($_[0], scalar caller);
166 2         6 my $info = $memotable{$func}{INFO};
167 2 50       8 die "$func not memoized" unless defined $info;
168 2         3 for my $context (qw(S L)) {
169 4         8 my $cache = $info->{$context};
170 4 50 33     10 if (tied %$cache && ! (tied %$cache)->can('CLEAR')) {
171 0 0       0 my $funcname = defined($info->{NAME}) ?
172             "function $info->{NAME}" : "anonymous function $func";
173 0         0 my $context = {S => 'scalar', L => 'list'}->{$context};
174 0         0 croak "Tied cache hash for $context-context $funcname does not support flushing";
175             } else {
176 4         9 %$cache = ();
177             }
178             }
179             }
180              
181             # This is the function that manages the memo tables.
182             sub _memoizer {
183 224     224   311 my $info = shift;
184              
185 224         358 my $normalizer = $info->{N};
186 224         223 my $argstr = do {
187 16     16   118 no warnings 'uninitialized';
  16         24  
  16         5295  
188 224 100       568 defined $normalizer
    100          
189             ? ( wantarray ? ( &$normalizer )[0] : &$normalizer )
190             . '' # coerce undef to string while the warning is off
191             : join chr(28), @_;
192             };
193              
194 224 100       1470 if (wantarray) {
195 48         52 my $cache = $info->{L};
196 48 100       80 _crap_out($info->{NAME}, 'list') unless $cache;
197 44 100       65 if (exists $cache->{$argstr}) {
198 19         23 return @{$cache->{$argstr}};
  19         167  
199             } else {
200 25         31 my @q = &{$info->{U}};
  25         42  
201 25         108 $cache->{$argstr} = \@q;
202 25         330 @q;
203             }
204             } else {
205 176         203 my $cache = $info->{S};
206 176 100       291 _crap_out($info->{NAME}, 'scalar') unless $cache;
207 172 100       562 if (exists $cache->{$argstr}) {
208             return $info->{MERGED}
209 109 100       841 ? $cache->{$argstr}[0] : $cache->{$argstr};
210             } else {
211 63         95 my $val = &{$info->{U}};
  63         130  
212             # Scalars are considered to be lists; store appropriately
213 63 100       213 if ($info->{MERGED}) {
214 4         10 $cache->{$argstr} = [$val];
215             } else {
216 59         234 $cache->{$argstr} = $val;
217             }
218 63         523 $val;
219             }
220             }
221             }
222              
223             sub unmemoize {
224 23     23 1 2980 my $f = shift;
225 23         42 my $uppack = caller;
226 23         50 my $cref = _make_cref($f, $uppack);
227              
228 23 100       58 unless (exists $memotable{$cref}) {
229 2         261 croak "Could not unmemoize function `$f', because it was not memoized to begin with";
230             }
231              
232 21         37 my $tabent = $memotable{$cref}{INFO};
233 21 50       46 unless (defined $tabent) {
234 0         0 croak "Could not figure out how to unmemoize function `$f'";
235             }
236 21         29 my $name = $tabent->{NAME};
237 21 100       35 if (defined $name) {
238 16     16   98 no strict;
  16         27  
  16         453  
239 16     16   81 no warnings 'redefine';
  16         42  
  16         1946  
240 15         21 *{$name} = $tabent->{U}; # Replace with original function
  15         37  
241             }
242 21         48 delete $memotable{$cref};
243              
244 21         240 $tabent->{U};
245             }
246              
247             sub _make_cref {
248 95     95   123 my $fn = shift;
249 95         108 my $uppack = shift;
250 95         117 my $cref;
251             my $name;
252              
253 95 100       214 if (ref $fn eq 'CODE') {
    50          
254 29         44 $cref = $fn;
255             } elsif (! ref $fn) {
256 66 100       155 if ($fn =~ /::/) {
257 1         2 $name = $fn;
258             } else {
259 65         113 $name = $uppack . '::' . $fn;
260             }
261 16     16   126 no strict;
  16         33  
  16         3522  
262 66 100 66     294 if (defined $name and !defined(&$name)) {
263 1         133 croak "Cannot operate on nonexistent function `$fn'";
264             }
265             # $cref = \&$name;
266 65         77 $cref = *{$name}{CODE};
  65         171  
267             } else {
268 0         0 my $parent = (caller(1))[3]; # Function that called _make_cref
269 0         0 croak "Usage: argument 1 to `$parent' must be a function name or reference.\n";
270             }
271 94 50       191 our $DEBUG and warn "${name}($fn) => $cref in _make_cref\n";
272 94         155 $cref;
273             }
274              
275             sub _crap_out {
276 8     8   11 my ($funcname, $context) = @_;
277 8 100       13 if (defined $funcname) {
278 2         190 croak "Function `$funcname' called in forbidden $context context; faulting";
279             } else {
280 6         386 croak "Anonymous function called in forbidden $context context; faulting";
281             }
282             }
283              
284             1;
285              
286             __END__