File Coverage

blib/lib/Math/Logic.pm
Criterion Covered Total %
statement 224 231 96.9
branch 153 168 91.0
condition 65 96 67.7
subroutine 56 59 94.9
pod 12 12 100.0
total 510 566 90.1


line stmt bran cond sub pod time code
1             package Math::Logic ; # Documented at the __END__.
2              
3             # $Id: Logic.pm,v 1.16 2000/05/25 19:15:01 root Exp root $
4              
5              
6             require 5.004 ;
7              
8 1     1   903 use strict ;
  1         2  
  1         32  
9 1     1   956 use integer ; # Forces us to quote all hash keys in 5.004.
  1         9  
  1         5  
10              
11 1     1   28 use Carp qw( croak carp ) ;
  1         5  
  1         78  
12              
13 1     1   5 use vars qw( $VERSION @ISA @EXPORT_OK %EXPORT_TAGS ) ;
  1         2  
  1         61  
14             $VERSION = '1.19' ;
15              
16 1     1   4 use Exporter() ;
  1         2  
  1         61  
17              
18             @ISA = qw( Exporter ) ;
19              
20             @EXPORT_OK = qw( $TRUE $FALSE $UNDEF TRUE FALSE UNDEF
21             $STR_TRUE $STR_FALSE $STR_UNDEF STR_TRUE STR_FALSE STR_UNDEF ) ;
22             %EXPORT_TAGS = (
23             ALL => [ @EXPORT_OK ],
24             NUM => [ qw( $TRUE $FALSE $UNDEF TRUE FALSE UNDEF ) ],
25             STR => [ qw( $STR_TRUE $STR_FALSE $STR_UNDEF STR_TRUE STR_FALSE STR_UNDEF ) ],
26             ) ;
27              
28              
29             ### Public class constants
30              
31 1     1   4 use vars qw( $TRUE $FALSE $UNDEF $STR_TRUE $STR_FALSE $STR_UNDEF ) ;
  1         2  
  1         126  
32             *TRUE = \1 ;
33             *FALSE = \0 ;
34             *UNDEF = \-1 ;
35              
36             *STR_TRUE = \'TRUE' ;
37             *STR_FALSE = \'FALSE' ;
38             *STR_UNDEF = \'UNDEF' ;
39              
40             ### Public class constants -- DEPRECATED
41              
42 1     1   5 use constant TRUE => $TRUE ;
  1         1  
  1         82  
43 1     1   4 use constant FALSE => $FALSE ;
  1         2  
  1         41  
44 1     1   4 use constant UNDEF => $UNDEF ;
  1         2  
  1         43  
45              
46 1     1   5 use constant STR_TRUE => $STR_TRUE ;
  1         2  
  1         51  
47 1     1   4 use constant STR_FALSE => $STR_FALSE ;
  1         1  
  1         40  
48 1     1   4 use constant STR_UNDEF => $STR_UNDEF ;
  1         6  
  1         1375  
49              
50              
51             ### Private class constants
52              
53             my $DEF_VALUE = $FALSE ;
54             my $DEF_DEGREE = 3 ;
55             my $MIN_DEGREE = 2 ;
56             my $DEF_PROPAGATE = $FALSE ;
57              
58              
59             ### Object keys (there are no class keys)
60             #
61             # -value
62             # -degree
63             # -propagate
64              
65              
66             ### Private data and methods
67             #
68             # _set object
69             # _get object
70             # _cmp object
71             #
72              
73             {
74             sub _set { # Object method
75             # Caller is responsible for ensuring the assigned value is valid
76 239     239   232 my $self = shift ;
77             # my $class = ref( $self ) || $self ;
78 239         269 my $field = shift ;
79              
80 239         467 $self->{$field} = shift ;
81             }
82              
83              
84             sub _get { # Object method
85 6371     6371   6464 my $self = shift ;
86             # my $class = ref( $self ) || $self ;
87              
88 6371         23109 $self->{shift()} ;
89             }
90              
91              
92             sub _cmp { # Object method
93 250     250   3127 my $self = shift ;
94             # my $class = ref( $self ) || $self ;
95 250         252 my $comp = shift ;
96              
97 250 100       777 $comp = $self->new( '-value' => $comp ) unless ref $comp ;
98 250 100       276 { my $err ; croak $err if $err = $self->incompatible( $comp ) }
  250         213  
  250         448  
99              
100 248         523 $self->value <=> $comp->value ;
101             }
102            
103             }
104              
105              
106             ### Public methods
107              
108             sub new_from_string { # Class and object method
109 15     15 1 734 my $self = shift ;
110 15   33     64 my $class = ref( $self ) || $self ;
111 15         19 my $string = shift ;
112              
113 15         108 my @arg = $string =~ /\(?\s*([^,\s\%]+)\%?,\s*([^,\s]+)(?:,\s*([^,\s]+))?\)?/o ;
114              
115 15 50       35 if( defined $arg[0] ) {
116             # 1, 0 and -1 pass through unchanged; -1 will be silently converted to
117             # 0 except for 3-degree logic in $class->new
118 15 100       41 $arg[0] = $TRUE if $arg[0] =~ /^-?[tT]/o ;
119 15 100       74 $arg[0] = $FALSE if $arg[0] =~ /^-?[fF]/o ;
120 15 100       106 $arg[0] = $UNDEF if $arg[0] =~ /^-?[uU]/o ;
121             }
122 15 100       36 $arg[2] = $arg[2] =~ /^-?[tTpP1]/o ?
    100          
123             $TRUE : $FALSE if defined $arg[2] ;
124              
125             # Ignores settings of calling object if called as an object method.
126 15   66     129 $class->new(
      33        
      66        
127             '-value' => $arg[0] || $DEF_VALUE,
128             '-degree' => $arg[1] || $DEF_DEGREE,
129             '-propagate' => $arg[2] || $DEF_PROPAGATE,
130             ) ;
131             }
132              
133              
134             sub new { # Class and object method
135 505     505 1 1177 my $self = shift ;
136 505   66     1198 my $class = ref( $self ) || $self ;
137 505 100       884 my $object = ref $self ? $self : undef ;
138 505         918 my %arg = @_ ;
139              
140             # Set defaults plus parameters
141 505         1866 $self = {
142             '-value' => $DEF_VALUE,
143             '-degree' => $DEF_DEGREE,
144             '-propagate' => $DEF_PROPAGATE,
145             %arg
146             } ;
147              
148             # If called as an object method use the calling object's settings unless a
149             # parameter has overridden
150 505 100       2093 if( defined $object ) {
151 451 100       1070 $self->{'-value'} = $object->value
152             unless exists $arg{'-value'} ;
153 451 50       1234 $self->{'-degree'} = $object->degree
154             unless exists $arg{'-degree'} ;
155 451 100       1257 $self->{'-propagate'} = $object->propagate
156             unless exists $arg{'-propagate'} ;
157             }
158            
159             # Ensure the settings are valid
160 505 100       1070 $self->{'-propagate'} = $self->{'-propagate'} ? $TRUE : $FALSE ;
161              
162 505 100       1746 $self->{'-degree'} = $DEF_DEGREE
163             unless $self->{'-degree'} =~ /^\d+$/o ;
164 505 50       1007 $self->{'-degree'} = $MIN_DEGREE
165             if $self->{'-degree'} < $MIN_DEGREE ;
166              
167 505 100 66     2315 $self->{'-value'} = $DEF_VALUE
168             if not defined $self->{'-value'} or $self->{'-value'} !~ /^(?:\d+|-1)$/o ;
169              
170 505 100       2451 if( $self->{'-degree'} == 2 ) { # 2-degree logic
    100          
171 74 100 66     865 $self->{'-value'} = ( $self->{'-value'} CORE::and
172             $self->{'-value'} != $UNDEF ) ?
173             $TRUE : $FALSE ;
174 74         118 delete $self->{'-propagate'} ; # Don't store what we don't use
175             }
176             elsif( $self->{'-degree'} == 3 ) { # 3-degree logic
177 279 100       558 if( $self->{'-value'} != $UNDEF ) {
178 170 100       365 $self->{'-value'} = $self->{'-value'} ? $TRUE : $FALSE ;
179             }
180             }
181             else { # Multi-degree logic
182 152 100       288 $self->{'-value'} = $FALSE if $self->{'-value'} == $UNDEF ;
183 152 50       276 $self->{'-value'} = $self->{'-degree'}
184             if $self->{'-value'} > $self->{'-degree'} ;
185 152         225 delete $self->{'-propagate'} ; # Don't store what we don't use
186             }
187              
188 505         1708 bless $self, $class ;
189             }
190              
191              
192             use overload
193             '""' => \&as_string,
194             '0+' => \&value,
195             'bool' => \&value,
196             '<=>' => \&_cmp,
197             '&' => \&and,
198             '|' => \&or,
199             '^' => \&xor,
200             '!' => \¬,
201             # Avoid surprises
202 2     2   658 '=' => sub { croak "=() not overloaded" },
203 1     1   311 '+' => sub { croak "+() unsupported" },
204 1     1   367 '-' => sub { croak "-() unsupported" },
205 1     1   341 '*' => sub { croak "*() unsupported" },
206 1     1   269 '/' => sub { croak "/() unsupported" },
207 1     1   309 '%' => sub { croak "%() unsupported" },
208 1     1   434 'x' => sub { croak "x() unsupported" },
209 1     1   367 '**' => sub { croak "**() unsupported" },
210 1     1   311 '<<' => sub { croak "<<() unsupported" },
211 1     1   317 '>>' => sub { croak ">>() unsupported" },
212 1     1   298 '+=' => sub { croak "+=() unsupported" },
213 1     1   273 '-=' => sub { croak "-=() unsupported" },
214 1     1   340 '*=' => sub { croak "*=() unsupported" },
215 1     1   262 '/=' => sub { croak "/=() unsupported" },
216 1     1   288 '%=' => sub { croak "%=() unsupported" },
217 1     1   327 'x=' => sub { croak "x=() unsupported" },
218 1     1   324 '++' => sub { croak "++() unsupported" },
219 1     1   331 '--' => sub { croak "--() unsupported" },
220 1     1   359 'lt' => sub { croak "lt() unsupported" },
221 1     1   367 'le' => sub { croak "le() unsupported" },
222 1     1   297 'gt' => sub { croak "gt() unsupported" },
223 1     1   337 'ge' => sub { croak "ge() unsupported" },
224 1     1   433 'eq' => sub { croak "eq() unsupported; use == instead" },
225 1     1   336 'ne' => sub { croak "ne() unsupported; use != instead" },
226 1     1   362 '**=' => sub { croak "**=() unsupported" },
227 1     1   400 '<<=' => sub { croak "<<=() unsupported" },
228 1     1   427 '>>=' => sub { croak ">>=() unsupported" },
229 1     1   319 'cmp' => sub { croak "cmp() unsupported; use <=> instead" },
230 1     1   296 'neg' => sub { croak "neg() unsupported" },
231 0     0   0 'nomethod' => sub { croak @_ . "() unsupported" },
232 1     1   2357 ;
  1         1055  
  1         53  
233              
234              
235             sub value { # Object method
236 1585     1585 1 1850 my $self = shift ;
237             # my $class = ref( $self ) || $self ;
238 1585         1733 my $value = shift ;
239              
240 1585 100       2741 if( defined $value ) {
241 238         203 my $result ;
242              
243 238 100       431 if( $self->degree == 2 ) { # 2-degree logic
    100          
244 29 100 66     87 $result = ( $value CORE::and $value != $UNDEF ) ? $TRUE : $FALSE ;
245             }
246             elsif( $self->degree == 3 ) { # 3-degree logic
247 121 100       191 $result = $value ? $TRUE : $FALSE ;
248 121 100       268 $result = $UNDEF if $value == $UNDEF ;
249             }
250             else { # Multi-degree logic
251 88         93 $result = $value ;
252             # $UNDEF is -1 which doesn't match the pattern, hence we can
253             # abbreviate the following line
254             # $result = $FALSE if $value == $UNDEF CORE::or $value !~ /^\d+$/o ;
255 88 50       331 $result = $FALSE if $value !~ /^\d+$/o ;
256 88 50       167 $result = $self->degree if $result > $self->degree ;
257             }
258              
259 238         524 $self->_set( '-value' => $result ) ;
260             }
261            
262 1585         2786 $self->_get( '-value' ) ;
263             }
264              
265              
266             sub degree { # Object method
267 3943     3943 1 4457 my $self = shift ;
268             # my $class = ref( $self ) || $self ;
269              
270 3943 50       6895 carp "degree is read-only" if @_ ;
271            
272 3943         6438 $self->_get( '-degree' ) ;
273             }
274              
275              
276             sub propagate { # Object method
277 1452     1452 1 1802 my $self = shift ;
278             # my $class = ref( $self ) || $self ;
279              
280 1452 50       2389 carp "propagate is read-only" if @_ ;
281              
282 1452 100       2153 $self->degree == 3 ? $self->_get( '-propagate' ) : $FALSE ;
283             }
284              
285              
286             sub incompatible { # Object method
287 462     462 1 1099 my $self = shift ;
288 462   66     914 my $class = ref( $self ) || $self ;
289 462         428 my $comp = shift ;
290              
291 462 50 33     3218 croak "operator can only be applied to $class objects not " .
      66        
      33        
292             ( ref( $comp ) || $comp )
293             if ( CORE::not ref $comp ) CORE::or
294             ( CORE::not $comp->can( 'degree' ) ) CORE::or
295             ( CORE::not $comp->can( 'propagate' ) ) ;
296            
297 460 100 66     1802 ( $self->degree == $comp->degree CORE::and
298             $self->propagate == $comp->propagate ) ? 0 :
299             ref( $self ) . "(" . $self->degree . "," . $self->propagate . ")" .
300             " and " .
301             ref( $comp ) . "(" . $comp->degree . "," . $comp->propagate . ")" .
302             " are incompatible" ;
303             }
304              
305              
306             sub compatible { # DEPRECATED Object method
307 0     0 1 0 my $self = shift ;
308 0   0     0 my $class = ref( $self ) || $self ;
309 0         0 my $comp = shift ;
310              
311 0 0 0     0 croak "can only be applied to $class objects not " . ( ref( $comp ) || $comp )
      0        
      0        
312             if ( CORE::not ref $comp ) CORE::or
313             ( CORE::not $comp->can( 'degree' ) ) CORE::or
314             ( CORE::not $comp->can( 'propagate' ) ) ;
315            
316 0 0       0 $self->degree == $comp->degree CORE::and
317             $self->propagate == $comp->propagate ;
318             }
319              
320              
321             sub as_string { # Object method
322 33     33 1 254 my $self = shift ;
323             # my $class = ref( $self ) || $self ;
324 33   100     111 my $full = shift || 0 ;
325 33 100 66     127 $full = 0 unless $full eq '1' CORE::or $full eq '-full' ;
326              
327 33         36 my $result = '' ;
328              
329 33 100       65 if( $self->degree == 2 ) { # 2-degree logic
    100          
330 4 100       8 $result = $self->value ? $STR_TRUE : $STR_FALSE ;
331             }
332             elsif( $self->degree == 3 ) { # 3-degree logic
333 13 100       25 $result = $self->value ? $STR_TRUE : $STR_FALSE ;
334 13 100       28 $result = $STR_UNDEF if $self->value == $UNDEF ;
335             }
336             else { # Multi-degree logic
337 16 100       35 if( $self->value == $FALSE ) {
    100          
338 5         10 $result = $STR_FALSE ;
339             }
340             elsif( $self->value == $self->degree ) {
341 3         8 $result = $STR_TRUE ;
342             }
343             else {
344 8         16 $result = $self->value ;
345 8 100 66     20 $result .= '%' if $self->degree == 100 CORE::and $full ;
346             }
347             }
348              
349             # e.g. $logic->as_string( -full ) ;
350 33 100       89 $result = "($result," . $self->degree .
    100          
351             ( $self->propagate ? "," . '-propagate' : '' ) . ")" if $full ;
352              
353 33         91 $result ;
354             }
355              
356              
357             sub and { # Object method
358 82     82 1 1182 my $self = shift ;
359             # my $class = ref( $self ) || $self ;
360 82         93 my $comp = shift ;
361              
362 82 100       170 $comp = $self->new( '-value' => $comp ) unless ref $comp ;
363 82 100       77 { my $err ; croak $err if $err = $self->incompatible( $comp ) }
  82         70  
  82         182  
364              
365 79         111 my $value ;
366 79         150 my $result = $self->new ;
367              
368 79 100       166 if( $self->degree == 2 ) { # 2-degree logic
    100          
369 9 100 100     16 $value = ( $self->value CORE::and $comp->value ) ? $TRUE : $FALSE ;
370             }
371             elsif( $self->degree == 3 ) { # 3-degree logic
372 36 100       60 if( $self->propagate ) {
373 18 100 100     31 if( $self->value == $UNDEF CORE::or $comp->value == $UNDEF ) {
    100 100        
374             # At least one is undefined which propagates.
375 10         18 $value = $UNDEF ;
376             }
377             elsif( $self->value == $TRUE CORE::and $comp->value == $TRUE ) {
378             # They're both defined and true.
379 2         46 $value = $TRUE ;
380             }
381             else {
382             # They're both defined and at least one is false.
383 6         8 $value = $FALSE ;
384             }
385             }
386             else {
387 18 100 100     30 if( $self->value == $TRUE CORE::and $comp->value == $TRUE ) {
    100 100        
388             # Both are defined and true.
389 2         3 $value = $TRUE ;
390             }
391             elsif( $self->value == $FALSE CORE::or $comp->value == $FALSE ) {
392             # At least one is defined and false.
393 10         12 $value = $FALSE ;
394             }
395             else {
396             # Either both are undefined or only one is defined and true.
397 6         10 $value = $UNDEF ;
398             }
399             }
400             }
401             else { # Multi-degree logic
402             # and is the lowest value
403 34 100       64 $value = $self->value < $comp->value ? $self->value : $comp->value ;
404             }
405              
406 79         168 $result->value( $value ) ;
407              
408 79         201 $result ;
409             }
410              
411              
412             sub or { # Object method
413 68     68 1 1598 my $self = shift ;
414             # my $class = ref( $self ) || $self ;
415 68         71 my $comp = shift ;
416              
417 68 50       138 $comp = $self->new( '-value' => $comp ) unless ref $comp ;
418 68 100       67 { my $err ; croak $err if $err = $self->incompatible( $comp ) }
  68         72  
  68         120  
419            
420 66         95 my $value ;
421 66         109 my $result = $self->new ;
422              
423 66 100       123 if( $self->degree == 2 ) { # 2-degree logic
    100          
424 8 100 100     19 $value = ( $self->value CORE::or $comp->value ) ? $TRUE : $FALSE ;
425             }
426             elsif( $self->degree == 3 ) { # 3-degree logic
427 36 100       66 if( $self->propagate ) {
428 18 100 100     32 if( $self->value == $UNDEF CORE::or $comp->value == $UNDEF ) {
    100 100        
429             # At least one is undefined which propagates.
430 10         15 $value = $UNDEF ;
431             }
432             elsif( $self->value == $TRUE CORE::or $comp->value == $TRUE ) {
433             # They're both defined and at least one is true.
434 6         10 $value = $TRUE ;
435             }
436             else {
437             # They're both defined and both are false.
438 2         4 $value = $FALSE ;
439             }
440             }
441             else {
442 18 100 100     36 if( $self->value == $TRUE CORE::or $comp->value == $TRUE ) {
    100 100        
443             # At least one is defined and true.
444 10         13 $value = $TRUE ;
445             }
446             elsif( $self->value == $FALSE CORE::and $comp->value == $FALSE ) {
447             # They're both defined and false.
448 2         5 $value = $FALSE ;
449             }
450             else {
451             # Either both are undefined or one is defined and false.
452 6         8 $value = $UNDEF ;
453             }
454             }
455             }
456             else { # Multi-degree logic
457             # or is the greatest value
458 22 100       45 $value = $self->value > $comp->value ? $self->value : $comp->value ;
459             }
460              
461 66         141 $result->value( $value ) ;
462              
463 66         191 $result ;
464             }
465              
466              
467             sub xor { # Object method
468 58     58 1 1659 my $self = shift ;
469             # my $class = ref( $self ) || $self ;
470 58         68 my $comp = shift ;
471              
472 58 50       139 $comp = $self->new( '-value' => $comp ) unless ref $comp ;
473 58 100       57 { my $err ; croak $err if $err = $self->incompatible( $comp ) }
  58         52  
  58         102  
474            
475 56         84 my $value ;
476 56         97 my $result = $self->new ;
477              
478 56 100       104 if( $self->degree == 2 ) { # 2-degree logic
    100          
479 8 100 100     12 $value = ( $self->value CORE::xor $comp->value ) ? $TRUE : $FALSE ;
480             }
481             elsif( $self->degree == 3 ) { # 3-degree logic
482             # Same truth table whether propagating or not.
483 36 100 100     62 if( $self->value == $UNDEF CORE::or $comp->value == $UNDEF ) {
    100          
484             # At least one is undefined which propagates.
485 20         32 $value = $UNDEF ;
486             }
487             elsif( $self->value == $comp->value ) {
488             # Both are defined and they're both the same.
489 8         13 $value = $FALSE ;
490             }
491             else {
492             # Both are defined and they're different.
493 8         11 $value = $TRUE ;
494             }
495             }
496             else { # Multi-degree logic
497             # By truth table xor(a,b) == and(or(a,b),not(and(a,b)))
498             # We could write it thus, but prefer not to use overloading within the
499             # module itself:
500             # my $temp = ( $self | $comp ) & ( ! ( $self & $comp ) ) ;
501             # $value = $temp->value ;
502 12         25 $value = $self->or( $comp )->and( $self->and( $comp )->not )->value ;
503             }
504              
505 56         145 $result->value( $value ) ;
506              
507 56         153 $result ;
508             }
509              
510              
511             sub not { # Object method
512 37     37 1 1377 my $self = shift ;
513             # my $class = ref( $self ) || $self ;
514              
515 37         40 my $value ;
516 37         63 my $result = $self->new ;
517              
518 37 100       72 if( $self->degree == 2 ) { # 2-degree logic
    100          
519 4 100       9 $value = ( $self->value ? $FALSE : $TRUE ) ;
520             }
521             elsif( $self->degree == 3 ) { # 3-degree logic
522             # Same truth table whether propagating or not.
523 12 100       34 if( $self->value == $UNDEF ) {
    100          
524             # It's undefined which propogates.
525 4         6 $value = $UNDEF ;
526             }
527             elsif( $self->value == $TRUE ) {
528             # It's defined and true so return false.
529 4         6 $value = $FALSE ;
530             }
531             else {
532             # It's defined and false so return true.
533 4         14 $value = $TRUE ;
534             }
535             }
536             else { # Multi-degree logic
537 20         36 $value = $self->degree - $self->value ;
538             }
539              
540 36         74 $result->value( $value ) ;
541              
542 36         84 $result ;
543             }
544              
545              
546 0     0     DESTROY { # Object method
547             ; # Noop
548             }
549              
550              
551             1 ;
552              
553              
554             __END__