File Coverage

blib/lib/Memoize.pm
Criterion Covered Total %
statement 142 150 94.6
branch 70 80 87.5
condition 22 27 81.4
subroutine 21 22 95.4
pod 2 3 66.6
total 257 282 91.1


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   200944 use strict; use warnings;
  16     16   30  
  16         501  
  16         71  
  16         54  
  16         1284  
11              
12             package Memoize;
13             our $VERSION = '1.17';
14              
15 16     16   111 use Carp;
  16         22  
  16         1507  
16 16     16   92 use Scalar::Util 1.11 (); # for set_prototype
  16         512  
  16         712  
17              
18 16     16   89 BEGIN { require Exporter; *import = \&Exporter::import }
  16         10035  
19             our @EXPORT = qw(memoize);
20             our @EXPORT_OK = qw(unmemoize flush_cache);
21              
22             my %memotable;
23              
24             sub CLONE {
25 0     0   0 my @info = values %memotable;
26 0         0 %memotable = map +($_->{WRAPPER} => $_), @info;
27             }
28              
29             sub memoize {
30 73     73 0 2528312 my $fn = shift;
31 73         242 my %options = @_;
32              
33 73 100 100     547 unless (defined($fn) &&
      66        
34             (ref $fn eq 'CODE' || ref $fn eq '')) {
35 3         335 croak "Usage: memoize 'functionname'|coderef {OPTIONS}";
36             }
37              
38 70         202 my $uppack = caller; # TCL me Elmo!
39 70 100       214 my $name = (ref $fn ? undef : $fn);
40 70         206 my $cref = _make_cref($fn, $uppack);
41              
42 69         139 my $normalizer = $options{NORMALIZER};
43 69 100 100     215 if (defined $normalizer && ! ref $normalizer) {
44 6         10 $normalizer = _make_cref($normalizer, $uppack);
45             }
46              
47             my $install_name = exists $options{INSTALL}
48             ? $options{INSTALL} # use given name (or, if undef: do not install)
49 69 100       182 : $name; # no INSTALL option provided: default to original name if possible
50              
51 69 100       186 if (defined $install_name) {
52 42 100       184 $install_name = $uppack . '::' . $install_name
53             unless $install_name =~ /::/;
54             }
55              
56             # convert LIST_CACHE => MERGE to SCALAR_CACHE => MERGE
57             # to ensure TIE/HASH will always be checked by _check_suitable
58 69 100 100     374 if (($options{LIST_CACHE} || '') eq 'MERGE') {
59 6         14 $options{LIST_CACHE} = $options{SCALAR_CACHE};
60 6         15 $options{SCALAR_CACHE} = 'MERGE';
61             }
62              
63             # These will be the caches
64 69         121 my %caches;
65 69         167 for my $context (qw(LIST SCALAR)) { # SCALAR_CACHE must be last, to process MERGE
66 130   100     490 my $fullopt = $options{"${context}_CACHE"} ||= 'MEMORY';
67 130 100       317 my ($cache_opt, @cache_opt_args) = ref $fullopt ? @$fullopt : $fullopt;
68 130 100 100     447 if ($cache_opt eq 'FAULT') { # no cache
    100          
    100          
    100          
    100          
69 18         64 $caches{$context} = undef;
70             } elsif ($cache_opt eq 'HASH') { # user-supplied hash
71 18         30 my $cache = $cache_opt_args[0];
72 18         79 _check_suitable($context, ref tied %$cache);
73 16         55 $caches{$context} = $cache;
74             } elsif ($cache_opt eq 'TIE') {
75 8 100       1389 carp("TIE option to memoize() is deprecated; use HASH instead")
76             if warnings::enabled('all');
77 8   50     42 my $module = shift(@cache_opt_args) || '';
78 8         42 _check_suitable($context, $module);
79 5         139 my $hash = $caches{$context} = {};
80 5         30 (my $modulefile = $module . '.pm') =~ s{::}{/}g;
81 5         1000 require $modulefile;
82 4 50       91 tie(%$hash, $module, @cache_opt_args)
83             or croak "Couldn't tie memoize hash to `$module': $!";
84             } elsif ($cache_opt eq 'MEMORY') {
85 74         219 $caches{$context} = {};
86             } elsif ($cache_opt eq 'MERGE' and not ref $fullopt) { # ['MERGE'] was never supported
87 8 50       23 die "cannot MERGE $context\_CACHE" if $context ne 'SCALAR'; # should never happen
88 8 50       22 die 'bad cache setup order' if not exists $caches{LIST}; # should never happen
89 8         15 $options{MERGED} = 1;
90 8         25 $caches{SCALAR} = $caches{LIST};
91             } else {
92 4         637 croak "Unrecognized option to `${context}_CACHE': `$cache_opt' should be one of (MERGE TIE MEMORY FAULT HASH)";
93             }
94             }
95              
96 59         264 my $wrapper = _wrap($install_name, $cref, $normalizer, $options{MERGED}, \%caches);
97              
98 59 100       204 if (defined $install_name) {
99 16     16   113 no strict;
  16         31  
  16         637  
100 16     16   76 no warnings 'redefine';
  16         24  
  16         7752  
101 42         98 *{$install_name} = $wrapper;
  42         186  
102             }
103              
104             $memotable{$wrapper} = {
105             L => $caches{LIST},
106             S => $caches{SCALAR},
107 59         433 U => $cref,
108             NAME => $install_name,
109             WRAPPER => $wrapper,
110             };
111              
112 59         314 $wrapper # Return just memoized version
113             }
114              
115             sub flush_cache {
116 2     2 1 11 my $func = _make_cref($_[0], scalar caller);
117 2         6 my $info = $memotable{$func};
118 2 50       6 die "$func not memoized" unless defined $info;
119 2         6 for my $context (qw(S L)) {
120 4         9 my $cache = $info->{$context};
121 4 50 33     13 if (tied %$cache && ! (tied %$cache)->can('CLEAR')) {
122 0 0       0 my $funcname = defined($info->{NAME}) ?
123             "function $info->{NAME}" : "anonymous function $func";
124 0         0 my $context = {S => 'scalar', L => 'list'}->{$context};
125 0         0 croak "Tied cache hash for $context-context $funcname does not support flushing";
126             } else {
127 4         12 %$cache = ();
128             }
129             }
130             }
131              
132             sub _wrap {
133 59     59   220 my ($name, $orig, $normalizer, $merged, $caches) = @_;
134 59         167 my ($cache_L, $cache_S) = @$caches{qw(LIST SCALAR)};
135 59         96 undef $caches; # keep the pad from keeping the hash alive forever
136             Scalar::Util::set_prototype(sub {
137 326     326   23054772 my $argstr = do {
138 16     16   131 no warnings 'uninitialized';
  16         80  
  16         2443  
139 326 100       1223 defined $normalizer
    100          
140             ? ( wantarray ? ( $normalizer->( @_ ) )[0] : $normalizer->( @_ ) )
141             . '' # coerce undef to string while the warning is off
142             : join chr(28), @_;
143             };
144              
145 326 100       1723 if (wantarray) {
146 48 100       105 _crap_out($name, 'list') unless $cache_L;
147             exists $cache_L->{$argstr} ? (
148 19         77 @{$cache_L->{$argstr}}
149 44 100       74 ) : do {
150 16     16   128 my @q = do { no warnings 'recursion'; &$orig };
  16         20  
  16         1808  
  25         28  
  25         46  
151 25         116 $cache_L->{$argstr} = \@q;
152 25         79 @q;
153             };
154             } else {
155 278 100       672 _crap_out($name, 'scalar') unless $cache_S;
156             exists $cache_S->{$argstr} ? (
157             $merged ? $cache_S->{$argstr}[0] : $cache_S->{$argstr}
158 274 100       1131 ) : do {
    100          
159 16     16   84 my $val = do { no warnings 'recursion'; &$orig };
  16         26  
  16         3622  
  165         420  
  165         416  
160 165 100       699 $cache_S->{$argstr} = $merged ? [$val] : $val;
161 165         528 $val;
162             };
163             }
164 59         687 }, prototype $orig);
165             }
166              
167             sub unmemoize {
168 23     23 1 269512 my $f = shift;
169 23         56 my $uppack = caller;
170 23         66 my $cref = _make_cref($f, $uppack);
171              
172 23 100       83 unless (exists $memotable{$cref}) {
173 2         350 croak "Could not unmemoize function `$f', because it was not memoized to begin with";
174             }
175              
176 21         43 my $tabent = $memotable{$cref};
177 21 50       54 unless (defined $tabent) {
178 0         0 croak "Could not figure out how to unmemoize function `$f'";
179             }
180 21         43 my $name = $tabent->{NAME};
181 21 100       56 if (defined $name) {
182 16     16   102 no strict;
  16         27  
  16         462  
183 16     16   78 no warnings 'redefine';
  16         26  
  16         3156  
184 15         25 *{$name} = $tabent->{U}; # Replace with original function
  15         50  
185             }
186 21         51 delete $memotable{$cref};
187              
188 21         325 $tabent->{U};
189             }
190              
191             sub _make_cref {
192 101     101   156 my $fn = shift;
193 101         148 my $uppack = shift;
194 101         189 my $cref;
195             my $name;
196              
197 101 100       359 if (ref $fn eq 'CODE') {
    50          
198 32         87 $cref = $fn;
199             } elsif (! ref $fn) {
200 69 100       265 if ($fn =~ /::/) {
201 1         1 $name = $fn;
202             } else {
203 68         167 $name = $uppack . '::' . $fn;
204             }
205 16     16   101 no strict;
  16         41  
  16         6169  
206 69 100 66     419 if (defined $name and !defined(&$name)) {
207 1         208 croak "Cannot operate on nonexistent function `$fn'";
208             }
209             # $cref = \&$name;
210 68         109 $cref = *{$name}{CODE};
  68         193  
211             } else {
212 0         0 my $parent = (caller(1))[3]; # Function that called _make_cref
213 0         0 croak "Usage: argument 1 to `$parent' must be a function name or reference.\n";
214             }
215 100 50       269 our $DEBUG and warn "${name}($fn) => $cref in _make_cref\n";
216 100         213 $cref;
217             }
218              
219             sub _crap_out {
220 8     8   46 my ($funcname, $context) = @_;
221 8 100       21 if (defined $funcname) {
222 2         1142 croak "Function `$funcname' called in forbidden $context context; faulting";
223             } else {
224 6         687 croak "Anonymous function called in forbidden $context context; faulting";
225             }
226             }
227              
228             # Raise an error if the user tries to specify one of these packages as a
229             # tie for LIST_CACHE
230             my %scalar_only = map {($_ => 1)} qw(DB_File GDBM_File SDBM_File ODBM_File), map +($_, "Memoize::$_"), qw(AnyDBM_File NDBM_File);
231             sub _check_suitable {
232 26     26   83 my ($context, $package) = @_;
233             croak "You can't use $package for LIST_CACHE because it can only store scalars"
234 26 100 100     775 if $context eq 'LIST' and $scalar_only{$package};
235             }
236              
237             1;
238              
239             __END__