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   93821 use strict; use warnings;
  16     16   130  
  16         494  
  16         88  
  16         28  
  16         731  
11              
12             package Memoize;
13             our $VERSION = '1.16';
14              
15 16     16   81 use Carp;
  16         29  
  16         1716  
16 16     16   112 use Scalar::Util 1.11 (); # for set_prototype
  16         687  
  16         668  
17              
18 16     16   98 BEGIN { require Exporter; *import = \&Exporter::import }
  16         9740  
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 492815 my $fn = shift;
31 73         235 my %options = @_;
32              
33 73 100 100     468 unless (defined($fn) &&
      66        
34             (ref $fn eq 'CODE' || ref $fn eq '')) {
35 3         286 croak "Usage: memoize 'functionname'|coderef {OPTIONS}";
36             }
37              
38 70         160 my $uppack = caller; # TCL me Elmo!
39 70 100       166 my $name = (ref $fn ? undef : $fn);
40 70         174 my $cref = _make_cref($fn, $uppack);
41              
42 69         133 my $normalizer = $options{NORMALIZER};
43 69 100 100     207 if (defined $normalizer && ! ref $normalizer) {
44 6         11 $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       163 : $name; # no INSTALL option provided: default to original name if possible
50              
51 69 100       168 if (defined $install_name) {
52 42 100       152 $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     247 if (($options{LIST_CACHE} || '') eq 'MERGE') {
59 6         12 $options{LIST_CACHE} = $options{SCALAR_CACHE};
60 6         10 $options{SCALAR_CACHE} = 'MERGE';
61             }
62              
63             # These will be the caches
64 69         105 my %caches;
65 69         130 for my $context (qw(LIST SCALAR)) { # SCALAR_CACHE must be last, to process MERGE
66 130   100     470 my $fullopt = $options{"${context}_CACHE"} ||= 'MEMORY';
67 130 100       314 my ($cache_opt, @cache_opt_args) = ref $fullopt ? @$fullopt : $fullopt;
68 130 100 100     418 if ($cache_opt eq 'FAULT') { # no cache
    100          
    100          
    100          
    100          
69 18         51 $caches{$context} = undef;
70             } elsif ($cache_opt eq 'HASH') { # user-supplied hash
71 18         32 my $cache = $cache_opt_args[0];
72 18         75 _check_suitable($context, ref tied %$cache);
73 16         55 $caches{$context} = $cache;
74             } elsif ($cache_opt eq 'TIE') {
75 8 100       676 carp("TIE option to memoize() is deprecated; use HASH instead")
76             if warnings::enabled('all');
77 8   50     145 my $module = shift(@cache_opt_args) || '';
78 8         26 _check_suitable($context, $module);
79 5         16 my $hash = $caches{$context} = {};
80 5         29 (my $modulefile = $module . '.pm') =~ s{::}{/}g;
81 5         4282 require $modulefile;
82 4 50       101 tie(%$hash, $module, @cache_opt_args)
83             or croak "Couldn't tie memoize hash to `$module': $!";
84             } elsif ($cache_opt eq 'MEMORY') {
85 74         206 $caches{$context} = {};
86             } elsif ($cache_opt eq 'MERGE' and not ref $fullopt) { # ['MERGE'] was never supported
87 8 50       19 die "cannot MERGE $context\_CACHE" if $context ne 'SCALAR'; # should never happen
88 8 50       16 die 'bad cache setup order' if not exists $caches{LIST}; # should never happen
89 8         10 $options{MERGED} = 1;
90 8         18 $caches{SCALAR} = $caches{LIST};
91             } else {
92 4         353 croak "Unrecognized option to `${context}_CACHE': `$cache_opt' should be one of (MERGE TIE MEMORY FAULT HASH)";
93             }
94             }
95              
96 59         239 my $wrapper = _wrap($install_name, $cref, $normalizer, $options{MERGED}, \%caches);
97              
98 59 100       229 if (defined $install_name) {
99 16     16   126 no strict;
  16         34  
  16         530  
100 16     16   106 no warnings 'redefine';
  16         36  
  16         7130  
101 42         70 *{$install_name} = $wrapper;
  42         169  
102             }
103              
104             $memotable{$wrapper} = {
105             L => $caches{LIST},
106             S => $caches{SCALAR},
107 59         340 U => $cref,
108             NAME => $install_name,
109             WRAPPER => $wrapper,
110             };
111              
112 59         244 $wrapper # Return just memoized version
113             }
114              
115             sub flush_cache {
116 2     2 1 8 my $func = _make_cref($_[0], scalar caller);
117 2         5 my $info = $memotable{$func};
118 2 50       6 die "$func not memoized" unless defined $info;
119 2         5 for my $context (qw(S L)) {
120 4         5 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         11 %$cache = ();
128             }
129             }
130             }
131              
132             sub _wrap {
133 59     59   227 my ($name, $orig, $normalizer, $merged, $caches) = @_;
134 59         145 my ($cache_L, $cache_S) = @$caches{qw(LIST SCALAR)};
135 59         99 undef $caches; # keep the pad from keeping the hash alive forever
136             Scalar::Util::set_prototype(sub {
137 326     326   23024731 my $argstr = do {
138 16     16   124 no warnings 'uninitialized';
  16         30  
  16         2251  
139 326 100       1321 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       1758 if (wantarray) {
146 48 100       97 _crap_out($name, 'list') unless $cache_L;
147             exists $cache_L->{$argstr} ? (
148 19         85 @{$cache_L->{$argstr}}
149 44 100       81 ) : do {
150 16     16   160 my @q = do { no warnings 'recursion'; &$orig };
  16         40  
  16         1760  
  25         37  
  25         51  
151 25         123 $cache_L->{$argstr} = \@q;
152 25         114 @q;
153             };
154             } else {
155 278 100       653 _crap_out($name, 'scalar') unless $cache_S;
156             exists $cache_S->{$argstr} ? (
157             $merged ? $cache_S->{$argstr}[0] : $cache_S->{$argstr}
158 274 100       1441 ) : do {
    100          
159 16     16   125 my $val = do { no warnings 'recursion'; &$orig };
  16         56  
  16         3547  
  165         299  
  165         377  
160 165 100       670 $cache_S->{$argstr} = $merged ? [$val] : $val;
161 165         447 $val;
162             };
163             }
164 59         634 }, prototype $orig);
165             }
166              
167             sub unmemoize {
168 23     23 1 4361 my $f = shift;
169 23         47 my $uppack = caller;
170 23         55 my $cref = _make_cref($f, $uppack);
171              
172 23 100       112 unless (exists $memotable{$cref}) {
173 2         274 croak "Could not unmemoize function `$f', because it was not memoized to begin with";
174             }
175              
176 21         48 my $tabent = $memotable{$cref};
177 21 50       219 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       47 if (defined $name) {
182 16     16   152 no strict;
  16         44  
  16         561  
183 16     16   96 no warnings 'redefine';
  16         50  
  16         2663  
184 15         31 *{$name} = $tabent->{U}; # Replace with original function
  15         52  
185             }
186 21         52 delete $memotable{$cref};
187              
188 21         282 $tabent->{U};
189             }
190              
191             sub _make_cref {
192 101     101   152 my $fn = shift;
193 101         144 my $uppack = shift;
194 101         161 my $cref;
195             my $name;
196              
197 101 100       278 if (ref $fn eq 'CODE') {
    50          
198 32         55 $cref = $fn;
199             } elsif (! ref $fn) {
200 69 100       229 if ($fn =~ /::/) {
201 1         2 $name = $fn;
202             } else {
203 68         173 $name = $uppack . '::' . $fn;
204             }
205 16     16   129 no strict;
  16         52  
  16         5710  
206 69 100 66     357 if (defined $name and !defined(&$name)) {
207 1         188 croak "Cannot operate on nonexistent function `$fn'";
208             }
209             # $cref = \&$name;
210 68         107 $cref = *{$name}{CODE};
  68         184  
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       231 our $DEBUG and warn "${name}($fn) => $cref in _make_cref\n";
216 100         195 $cref;
217             }
218              
219             sub _crap_out {
220 8     8   17 my ($funcname, $context) = @_;
221 8 100       14 if (defined $funcname) {
222 2         322 croak "Function `$funcname' called in forbidden $context context; faulting";
223             } else {
224 6         543 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   66 my ($context, $package) = @_;
233             croak "You can't use $package for LIST_CACHE because it can only store scalars"
234 26 100 100     781 if $context eq 'LIST' and $scalar_only{$package};
235             }
236              
237             1;
238              
239             __END__