File Coverage

blib/lib/Sub/Curry.pm
Criterion Covered Total %
statement 111 120 92.5
branch 41 52 78.8
condition 12 20 60.0
subroutine 18 21 85.7
pod 4 8 50.0
total 186 221 84.1


line stmt bran cond sub pod time code
1             package Sub::Curry;
2              
3             $VERSION = 0.80;
4              
5 2     2   52869 use 5.006;
  2         10  
  2         89  
6 2     2   12 use strict;
  2         4  
  2         211  
7 2     2   11 use base 'Exporter';
  2         9  
  2         353  
8 2     2   13 use Carp;
  2         3  
  2         326  
9              
10             BEGIN {
11 2     2   6 my @consts = qw/
12             ANTISPICE
13             HOLE
14             ANTIHOLE
15             BLACKHOLE
16             WHITEHOLE
17             /;
18              
19 2         6 for (@consts) {
20 10         23 my $val = \(__PACKAGE__ . ".$_");
21 2     2   10 no strict 'refs';
  2         3  
  2         407  
22 10         101 *$_ = sub () { $val };
  0         0  
23 10         48 *{"is$_"} = sub {
24             return @_
25 0 0 0 0   0 ? ref $_[0] && $_[0] == $val
      0        
26             : ref $_ && $_ == $val;
27 10         26 };
28             }
29              
30 2         15 our %EXPORT_TAGS = (
31             CONST => [ @consts ],
32             );
33 2         9 our @EXPORT_OK = qw/ curry /;
34 2         13 push @EXPORT_OK => map @$_ => values %EXPORT_TAGS;
35 2         288 $EXPORT_TAGS{ALL} = \@EXPORT_OK;
36             }
37              
38             sub spice_eq {
39 930     930 0 971 my $x = shift;
40 930 100       1648 my $y = @_ ? shift : $_;
41              
42 930   100     5789 return ref $x && ref $y && $x == $y;
43             }
44              
45             our (
46             $Verbose,
47             );
48              
49             # Compability:
50             sub Hole (;$){
51 0 0 0 0 0 0 return map { HOLE() } ( 1 .. ( shift || 1 ) ) if wantarray;
  0         0  
52 0         0 return HOLE();
53             }
54              
55             ###############################################################################
56              
57             BEGIN {
58 2     2   4 my %Instance_Data;
59              
60 2         5 foreach (qw/ uncurried _real_spice _code_str /) {
61 2     2   8 no strict 'refs';
  2         5  
  2         390  
62 6         9 my $prop = $_;
63             *$prop = sub {
64 468     468   557 my $self = shift;
65 468 100       1010 if (@_) {
66 354 50       762 croak("Don't set '$prop' property outside " . __PACKAGE__)
67             unless caller eq __PACKAGE__;
68 354 50       1629 return $Instance_Data{$self}->{$prop} = $_[0] if @_;
69             }
70 114         556 return $Instance_Data{$self}->{$prop};
71 6         2434 };
72             }
73              
74             =cut
75             foreach ('holes', 'antispices', 'blackholes') {
76             no strict 'refs';
77             my $prop = $_;
78             *$prop = sub {
79             my $self = shift;
80             if (@_) {
81             require Carp;
82             Carp::croak("Don't set '$prop' property outside " . __PACKAGE__)
83             unless caller eq __PACKAGE__;
84             return @{$Instance_Data{$self}->{$prop} = $_[0]} if @_;
85             }
86             return @{$Instance_Data{$self}->{$prop}};
87             };
88             }
89             =cut
90              
91 118     118   20868 DESTROY { delete $Instance_Data{+shift} }
92             }
93              
94             # Note that the curry sub isn't equivalent to the new method.
95             # &curry doesn't care if the code ref is an object or not,
96             # while new does.
97              
98 55     55   348 sub _alias { \@_ }
99              
100 62     62 0 3445 sub curry { __PACKAGE__->new(@_) }
101             sub new {
102 173 100   173 1 4030 if (not ref $_[0]) {
103 118         176 my $class = shift;
104              
105 118         129 my $cb = shift;
106 118         202 my $spice = \@_;
107              
108 118         139 my @str;
109 118         126 my $arg_offset = 0;
110             my $inc_arg_offset = sub {
111 36 100   36   106 $arg_offset =~ /^\@_/
112             ? $arg_offset .= '+1'
113             : $arg_offset++;
114 36         141 return;
115 118         566 };
116 118         320 for (my $c = 0; $c < @$spice; $c++) {
117 212         391 local $_ = $spice->[$c];
118 212 50       497 if (! defined $spice->[$c]) {
    100          
    100          
    100          
119 0         0 push @str => "\$spice->[$c]";
120             }
121             elsif (spice_eq(HOLE)) {
122 23         74 push @str => sprintf '$_[%s]', $arg_offset;
123             #$arg_offset .= '+1';
124 23         38 $inc_arg_offset->();
125             }
126             elsif (spice_eq(ANTISPICE)) {
127 13         23 $arg_offset .= '+1';
128 13         26 $inc_arg_offset->();
129             }
130             elsif (spice_eq(BLACKHOLE)) {
131 20         66 push @str => sprintf '@_[%s .. $#_]', $arg_offset;
132 20         73 $arg_offset = '@_';
133             }
134             else {
135 156         615 push @str => "\$spice->[$c]";
136             }
137             }
138              
139             #push @str, sprintf '@_[%s .. $#_]', $arg_offset;
140 118         119 if (1) {
141 118 100       202 if ($arg_offset) {
142 49 100       363 if ($arg_offset !~ /^\@_/) {
143 29         102 push @str, sprintf '@_[%s .. $#_]', $arg_offset;
144             }
145             # Otherwise you'll get something bigger than @_ in the range,
146             # e.g. @_+1 .. $#_ and that will always evaluate to a
147             # zero-length slice.
148             }
149             else {
150             # No spice. Just do a regular pass-along.
151 69         97 push @str, '@_';
152             }
153             }
154              
155 118         126 my $code_str = "sub { \$cb->(@{[join ', ', @str]}) }";
  118         426  
156 118 50       9311 my $self = eval $code_str or die;
157              
158             #return $self if $nobless;
159              
160 118         245 bless $self => $class;
161              
162 118         206 _code_str($self => $code_str);
163 118         279 _real_spice($self => $spice);
164 118         269 $self->uncurried($cb);
165              
166 118         761 return $self;
167             }
168             else {
169 55         101 my $self = shift;
170              
171 55         81 my $spice = _real_spice($self);
172              
173 91 100 100     136 my $special = grep {
174 55         79 spice_eq(HOLE)
175             or spice_eq(ANTISPICE)
176             or spice_eq(BLACKHOLE)
177             } @$spice;
178              
179 55         55 my $new_spice;
180 55 100       83 if ($special) {
181 25         29 my $arg_offset = 0;
182 25         27 my @str;
183             #my $blackhole;
184             my $c;
185 25   100     803 for ($c = 0; $c < @$spice and $arg_offset < @_; $c++) {
186 38         58 local $_ = $spice->[$c];
187 38 50       86 if (not defined) {
    100          
    100          
    100          
188 0         0 push @str => "\$spice->[$c]";
189             }
190             elsif (spice_eq(ANTISPICE)) {
191 5         27 $arg_offset++;
192             }
193             elsif (spice_eq(HOLE)) {
194 10 100       19 push @str => sprintf '$_[%d]', $arg_offset
195             unless spice_eq(ANTIHOLE, $_[$arg_offset]);
196 10         52 $arg_offset++;
197             }
198             elsif (spice_eq(BLACKHOLE)) {
199 7   100     26 while ($arg_offset < @_ and not spice_eq(WHITEHOLE, $_[$arg_offset])) {
200 9         36 push @str => sprintf '$_[%d]', $arg_offset++;
201             }
202              
203 7 100       13 if ($arg_offset < @_) {
204 2         10 $arg_offset++; # Skip the whitehole.
205             }
206             else {
207 5         23 push @str => "\$spice->[$c]"; # Keep the blackhole.
208             }
209             }
210             else {
211 16         83 push @str => "\$spice->[$c]";
212             }
213             }
214              
215 25 100       93 if ($c < @$spice) {
216 12         59 push @str => map "\$spice->[$_]" => $c .. $#$spice;
217             }
218             else {
219 13         35 push @str, sprintf '@_[%d .. $#_]', $arg_offset;
220             }
221              
222 25 50       33 $new_spice = eval "_alias(@{[join ', ', @str]})" or die;
  25         1631  
223             }
224             else {
225 30         56 $new_spice = _alias(@$spice, @_);
226             }
227              
228 55         156 return ref($self)->new($self->uncurried, @$new_spice);
229             }
230             }
231              
232 1     1 0 795 sub clone { $_[0]->new }
233              
234 0     0 1 0 sub call { goto &{$_[0]} }
  0         0  
235              
236 1     1 1 207 sub spice { @{_real_spice($_[0])} }
  1         3  
237              
238             sub cursed {
239 1     1 1 208 my $self = shift;
240              
241 1         2 my $cb = $self->uncurried;
242 1         2 my $spice = _real_spice($self);
243 1         2 my $cursed = eval _code_str($self);
244 1 50       3 die "Internal error: $@" if $@;
245              
246 1         2 return $cursed;
247             }
248              
249             __PACKAGE__;
250              
251             __END__