File Coverage

lib/Sub/Contract.pm
Criterion Covered Total %
statement 147 149 98.6
branch 49 62 79.0
condition 27 54 50.0
subroutine 33 33 100.0
pod 16 16 100.0
total 272 314 86.6


line stmt bran cond sub pod time code
1             #-------------------------------------------------------------------
2             #
3             # Sub::Contract - Programming by contract and memoizing in one
4             #
5             # $Id: Contract.pm,v 1.35 2009/06/16 12:23:57 erwan_lemonnier Exp $
6             #
7              
8             package Sub::Contract;
9              
10 22     22   1199212 use strict;
  22         52  
  22         889  
11 22     22   116 use warnings;
  22         48  
  22         1149  
12 22     22   117 use Carp qw(croak confess);
  22         41  
  22         2553  
13 22     22   13267 use Data::Dumper;
  22         50671  
  22         1462  
14 22     22   11003 use Sub::Contract::ArgumentChecks;
  22         57  
  22         808  
15 22     22   10838 use Sub::Contract::Debug qw(debug);
  22         63  
  22         1649  
16 22     22   13367 use Sub::Contract::Pool qw(get_contract_pool);
  22         68  
  22         2111  
17 22     22   29744 use Symbol;
  22         25732  
  22         2165  
18              
19             # Add compiling and memoizing abilities through multiple inheritance, to keep code separate
20 22         14263 use base qw( Exporter
21             Sub::Contract::Compiler
22 22     22   148 Sub::Contract::Memoizer );
  22         42  
23              
24             our @EXPORT = qw();
25             our @EXPORT_OK = qw( contract
26             undef_or
27             defined_and
28             is_defined_and
29             is_undefined_or
30             is_not
31             is_one_of
32             is_all_of
33             is_a
34             );
35              
36             our $VERSION = '0.12';
37              
38             my $pool = Sub::Contract::Pool::get_contract_pool();
39              
40             # the argument list passed to the contractor
41             local @Sub::Contract::args;
42             local $Sub::Contract::wantarray;
43             local @Sub::Contract::results;
44              
45             #---------------------------------------------------------------
46             #
47             # contract - declare a contract
48             #
49              
50             sub contract {
51 50 100 100 50 1 10761 croak "contract() expects only one argument, a subroutine name" if (scalar @_ != 1 || !defined $_[0]);
52 47         141 my $caller = caller;
53 47         362 return new Sub::Contract($_[0], caller => $caller);
54             }
55              
56             #---------------------------------------------------------------
57             #
58             #
59             # functions to combine constraints
60             #
61             #
62             #---------------------------------------------------------------
63              
64             sub is_undefined_or {
65 5 50 33 5 1 70 croak "is_undefined_or() expects a coderef" if (scalar @_ != 1 || !defined $_[0] || ref $_[0] ne 'CODE');
      33        
66 5         21 my $test = shift;
67             return sub {
68 22 100   22   71 return 1 if (!defined $_[0]);
69 16         61 return &$test(@_);
70 5         35 };
71             }
72              
73             # backward compatibility
74             *undef_or = *is_undefined_or;
75              
76             sub is_defined_and {
77 3 50 33 3 1 57 croak "is_defined_and() expects a coderef" if (scalar @_ != 1 || !defined $_[0] || ref $_[0] ne 'CODE');
      33        
78 3         6 my $test = shift;
79             return sub {
80 21 100   21   72 return 0 if (!defined $_[0]);
81 15         40 return &$test(@_);
82 3         24 };
83             }
84              
85             # backward compatibility
86             *defined_and = *is_defined_and;
87              
88             sub is_not {
89 2 50 33 2 1 24 croak "is_not() expects a coderef" if (scalar @_ != 1 || !defined $_[0] || ref $_[0] ne 'CODE');
      33        
90 2         3 my $test = shift;
91             return sub {
92 7     7   22 return !&$test(@_);
93 2         18 };
94             }
95              
96             sub is_one_of {
97 2     2 1 6 my @tests = @_;
98 2 50       8 croak "is_one_of() expects at least two coderefs" if (scalar @tests < 2);
99 2         6 foreach my $test (@tests) {
100 5 50 33     28 croak "is_one_of() expects only coderefs" if (!defined $test || ref $test ne 'CODE');
101             }
102              
103             return sub {
104 8     8   16 my @args = @_;
105 8         15 foreach my $test (@tests) {
106 14 100       55 if (&$test(@args)) {
107 6         51 return 1;
108             }
109             }
110 2         15 return 0;
111 2         29 };
112             }
113              
114             sub is_all_of {
115 1     1 1 3 my @tests = @_;
116 1 50       4 croak "is_all_of() expects at least two coderefs" if (scalar @tests < 2);
117 1         3 foreach my $test (@tests) {
118 2 50 33     15 croak "is_all_of() expects only coderefs" if (!defined $test || ref $test ne 'CODE');
119             }
120              
121             return sub {
122 2     2   3 my @args = @_;
123 2         5 foreach my $test (@tests) {
124 4 100       36 if (!&$test(@args)) {
125 1         7 return 0;
126             }
127             }
128 1         10 return 1;
129 1         16 };
130             }
131              
132             sub is_a {
133 3 50 33 3 1 37 croak "is_a() expects a package name" if (scalar @_ != 1 || !defined $_[0] || ref $_[0] ne '');
      33        
134 3         6 my $type = shift;
135             return sub {
136 11 100   11   1146 return 0 if (!defined $_[0]);
137 10 100       49 return (ref $_[0] eq $type) ? 1:0;
138 3         20 };
139             }
140              
141             ################################################################
142             #
143             #
144             # Object API
145             #
146             #
147             ################################################################
148              
149             #---------------------------------------------------------------
150             #
151             # new - instantiate a new subroutine contract
152             #
153              
154             sub new {
155 56     56 1 18626 my ($class,$fullname,%args) = @_;
156 56   33     288 $class = ref $class || $class;
157 56   66     252 my $caller = delete $args{caller} || caller();
158              
159 56 100       412 croak "new() expects a subroutine name as first argument" if (!defined $fullname);
160 55 100       273 croak "new() got unknown arguments: ".Dumper(%args) if (keys %args != 0);
161              
162             # identify the subroutine to contract and make sure it exists
163 54         88 my $contractor_cref;
164             my $contractor;
165              
166 54 100       215 if ($fullname !~ /::/) {
167 46         66 $contractor_cref = *{ qualify_to_ref($fullname,$caller) }{CODE};
  46         219  
168 46         1210 $contractor = qualify($fullname,$caller);
169             } else {
170 8         14 $contractor_cref = *{ qualify_to_ref($fullname) }{CODE};
  8         133  
171 8         148 $contractor = qualify($fullname);
172             }
173              
174 54 100       688 if (!defined $contractor_cref) {
175 4         641 croak "Can't find subroutine named '".$contractor."'";
176             }
177              
178             # create instance of contract
179 50         156 my $self = bless({}, $class);
180 50         439 $self->{is_enabled} = 0; # 1 if contract is enabled
181 50         1076 $self->{is_memoized} = 0; # TODO: needed?
182 50         130 $self->{contractor} = $contractor; # The fully qualified name of the contracted subroutine
183 50         104 $self->{contractor_cref} = $contractor_cref; # A code reference to the contracted subroutine
184              
185 50         252 $self->reset;
186              
187             # add self to the contract pool (if not already in)
188 50 100       241 croak "trying to contract function [$contractor] twice"
189             if ($pool->has_contract($contractor));
190              
191 49         199 $pool->_add_contract($self);
192              
193 49         325 return $self;
194             }
195              
196             #---------------------------------------------------------------
197             #
198             # reset - reset all constraints in a contract
199             #
200              
201             sub reset {
202 57     57 1 5603 my $self = shift;
203 57         161 $self->{in} = undef; # An array of coderefs checking respective input arguments
204 57         121 $self->{out} = undef; # An array of coderefs checking respective return arguments
205 57         106 $self->{pre} = undef; # Coderef checking pre conditions
206 57         159 $self->{post} = undef; # Coderef checking post conditions
207 57         121 $self->{invariant} = undef; # Coderef checking an invariant condition
208 57 50       202 if (exists $self->{cache}) {
209 0         0 $self->{cache}->clear;
210 0         0 delete $self->{cache};
211             }
212 57         259 return $self;
213             }
214              
215             #---------------------------------------------------------------
216             #
217             # in, out - declare conditions for each of the subroutine's in- and out-arguments
218             #
219              
220             sub _set_in_out {
221 107     107   313 my ($type,$self,@checks) = @_;
222 107         184 local $Carp::CarpLevel = 2;
223 107         466 my $validator = new Sub::Contract::ArgumentChecks($type);
224              
225 107         578 my $pos = 0;
226              
227             # check arguments passed in list-style
228 107         376 while (@checks) {
229 181         267 my $check = shift @checks;
230              
231 181 100 100     869 if (!defined $check || ref $check eq 'CODE') {
    100          
232             # ok
233 113         307 $validator->add_list_check($check);
234             } elsif (ref $check eq '') {
235             # this is a hash key. we expect hash syntax from there on
236 60         116 unshift @checks, $check;
237 60         97 last;
238             } else {
239 8         2201 croak "invalid contract definition: argument at position $pos in $type() should be undef or a coderef or a string";
240             }
241 113         805 $pos++;
242             }
243              
244             # @checks should be either empty or describe hash checks (sequence of string => coderef)
245 99 100       379 if (scalar @checks % 2) {
246 12         3544 croak "invalid contract definition: odd number of arguments from position $pos in $type(), can't ".
247             "constrain hash-style passed arguments";
248             }
249              
250             # check arguments passed in hash-style
251 87         134 my %known_keys;
252 87         294 while (@checks) {
253 88         130 my $key = shift @checks;
254 88         124 my $check = shift @checks;
255              
256 88 100 66     437 if (defined $key && ref $key eq '') {
257             # is this key defined more than once?
258 82 100       179 if (exists $known_keys{$key}) {
259 2         471 croak "invalid contract definition: constraining argument \'$key\' twice in $type()";
260             }
261 80         149 $known_keys{$key} = 1;
262              
263             # ok with key. verify $check
264 80 100 100     370 if (!defined $check || ref $check eq 'CODE') {
265             # ok
266 72         214 $validator->add_hash_check($key,$check);
267             } else {
268 8         1931 croak "invalid contract definition: check for \'$key\' should be undef or a coderef in $type()";
269             }
270             } else {
271 6         1583 croak "invalid contract definition: argument at position $pos should be a string in $type()";
272             }
273 72         470 $pos += 2;
274             }
275              
276             # everything ok!
277 71         139 $self->{$type} = $validator;
278 71         554 return $self;
279             }
280              
281 58     58 1 43787 sub in { return _set_in_out('in',@_); }
282 49     49 1 30474 sub out { return _set_in_out('out',@_); }
283              
284             #---------------------------------------------------------------
285             #
286             # pre, post - declare pre and post conditions on subroutine
287             #
288              
289             sub _set_pre_post {
290 8     8   20 my ($type,$self,$subref) = @_;
291 8         27 local $Carp::CarpLevel = 2;
292              
293 8 50       27 croak "the method $type() expects exactly one argument"
294             if (scalar @_ != 3);
295 8 50 66     53 croak "the method $type() expects a code reference as argument"
296             if (defined $subref && ref $subref ne 'CODE');
297              
298 8         20 $self->{$type} = $subref;
299              
300 8         72 return $self;
301             }
302              
303 4     4 1 564 sub pre { return _set_pre_post('pre',@_); }
304 4     4 1 674 sub post { return _set_pre_post('post',@_); }
305              
306             #---------------------------------------------------------------
307             #
308             # invariant - adds an invariant condition
309             #
310              
311             sub invariant {
312 8     8 1 16 my ($self,$subref) = @_;
313              
314 8 50       29 croak "the method invariant() expects exactly one argument"
315             if (scalar @_ != 2);
316 8 50 33     54 croak "the method invariant() expects a code reference as argument"
317             if (defined $subref && ref $subref ne 'CODE');
318              
319 8         17 $self->{invariant} = $subref;
320              
321 8         78 return $self;
322             }
323              
324             #---------------------------------------------------------------
325             #
326             # contractor - returns the contractor subroutine's fully qualified name
327             #
328              
329             sub contractor {
330 451     451 1 13562 return $_[0]->{contractor};
331             }
332              
333             #---------------------------------------------------------------
334             #
335             # contractor_cref - return a code ref to the contractor subroutine
336             #
337              
338             sub contractor_cref {
339 56     56 1 168 return $_[0]->{contractor_cref};
340             }
341              
342              
343             # TODO: implement return?
344              
345             1;
346              
347             __END__