File Coverage

blib/lib/Logic/Expr.pm
Criterion Covered Total %
statement 73 73 100.0
branch 44 44 100.0
condition 9 9 100.0
subroutine 14 14 100.0
pod 7 7 100.0
total 147 147 100.0


line stmt bran cond sub pod time code
1             # -*- Perl -*-
2             #
3             # logic expression related routines. see Logic::Expr::Parser for how the
4             # expressions are built
5              
6             package Logic::Expr;
7             our $VERSION = '0.02';
8 3     3   21 use Scalar::Util 'refaddr';
  3         6  
  3         127  
9 3     3   971 use parent qw(Exporter);
  3         652  
  3         14  
10              
11             # 'atoms' contains a name to a scalar reference in 'bools'; 'bools' is
12             # an array of unique atoms in an expression (for easy iteration by the
13             # "solutions" method); 'expr' is the parse tree of the expression as
14             # probably generated by Logic::Expr::Parser. 'bools' must be modified
15             # in-place to not break the scalar references from the other two
16             # structures. for example, given X&Y,
17             # * atoms - { X => \$bools[0], Y => \$bools[1] }
18             # * bools - [ 1, 1 ]
19             # * expr - [ LE_AND, ... ]
20             our ( @EXPORT_OK, %EXPORT_TAGS, %atoms, @bools );
21              
22             BEGIN {
23 3     3   227 my @all = qw(TRUE FALSE LE_NOT LE_AND LE_OR LE_COND LE_BICOND);
24 3         6 @EXPORT_OK = @all;
25 3         2510 %EXPORT_TAGS = ( all => \@all );
26             }
27              
28             sub TRUE () { 1 }
29             sub FALSE () { 0 }
30             # -1 is reserved for testing (FAKE_OP); 0 or 1 for ops would conflate
31             # with the prior should a truth value be assigned to an op slot
32             sub LE_NOT () { 2 } # ! ~
33             sub LE_AND () { 3 } # &
34             sub LE_OR () { 4 } # | v
35             sub LE_COND () { 5 } # ->
36             sub LE_BICOND () { 6 } # ==
37              
38 4     4 1 2205 sub expr { $_[0]->{expr} }
39              
40             # generate a function that with suitable arguments evaluates the expr
41             sub codex
42             {
43 8     8 1 677 my ($self) = @_;
44 8         18 my %atom2symbol = map { refaddr($atoms{$_}) => $_ } keys %atoms;
  16         48  
45             my $code = $self->walk(
46             sub {
47 8     8   13 my ( $op, $arg ) = @_;
48 8 100       22 if ( $op == LE_NOT ) {
    100          
    100          
    100          
    100          
49 1         3 return "!$arg->[0]";
50             } elsif ( $op == LE_AND ) {
51 1         3 return "($arg->[0] and $arg->[1])";
52             } elsif ( $op == LE_OR ) {
53 1         5 return "($arg->[0] or $arg->[1])";
54             } elsif ( $op == LE_COND ) {
55 3         10 return "(!$arg->[0] or $arg->[1])";
56             } elsif ( $op == LE_BICOND ) {
57 1         4 return "!($arg->[0] xor $arg->[1])";
58             } else {
59 1         6 die "unknown op $op";
60             }
61             },
62 13     13   44 sub { q($p{) . $atom2symbol{ refaddr($_[0]) } . q(}) },
63 8         45 );
64 6         558 eval "sub { my \%p = \@_; $code ? TRUE : FALSE }";
65             }
66              
67             sub new
68             {
69 10     10 1 24 my ( $class, %param ) = @_;
70 10         19 my $self = { expr => $param{expr} };
71 10         16 bless $self, $class;
72 10         18 return $self;
73             }
74              
75 3     3 1 783 sub reset { %atoms = @bools = (); }
76              
77             # brute force all possible boolean states for an expression
78             sub solutions
79             {
80 6     6 1 34 my ($self, $noprefix) = @_;
81 6         6 my (@orig, $ret, @solutions);
82 6         11 for my $x (@bools) {
83 12         12 push @orig, $x;
84 12         15 $x = TRUE;
85             }
86 6 100       21 $ret = _solve( $self->{expr} ) ? TRUE : FALSE;
87 6 100       15 push @solutions, $noprefix ? $ret : [ [@bools], $ret ];
88             # the reverse index ordering is to match that of the logic book,
89             # backwards binary counting
90 6         8 my $i = $#bools;
91 6         8 while ( $i >= 0 ) {
92 36 100       44 if ( $bools[$i] ) {
93 18         17 $bools[$i] = FALSE;
94 18 100       21 $ret = _solve( $self->{expr} ) ? TRUE : FALSE;
95 18 100       34 push @solutions, $noprefix ? $ret : [ [@bools], $ret ];
96 18         31 $i = $#bools;
97             } else {
98 18         28 $bools[ $i-- ] = TRUE;
99             }
100             }
101 6         7 for my $x (@bools) { $x = shift @orig }
  12         14  
102 6         35 return \@solutions;
103             }
104              
105             # solve the expression using the current state in bools
106 4 100   4 1 320 sub solve { _solve( $_[0]->{expr} ) ? TRUE : FALSE }
107              
108             sub _solve
109             {
110 75     75   73 my ($ptr) = @_;
111 75         84 my $rt = ref $ptr;
112 75 100       152 return $$ptr if $rt eq 'SCALAR'; # lookup from bools
113 34 100       41 if ( $rt eq 'ARRAY' ) {
114 33 100       64 if ( $ptr->[0] == LE_NOT ) {
    100          
    100          
    100          
    100          
115 6         10 return !_solve( $ptr->[1] );
116             } elsif ( $ptr->[0] == LE_AND ) {
117 4   100     7 return _solve( $ptr->[1] ) && _solve( $ptr->[2] );
118             } elsif ( $ptr->[0] == LE_OR ) {
119 8   100     11 return _solve( $ptr->[1] ) || _solve( $ptr->[2] );
120             } elsif ( $ptr->[0] == LE_COND ) {
121 10   100     16 return !_solve( $ptr->[1] ) || _solve( $ptr->[2] );
122             } elsif ( $ptr->[0] == LE_BICOND ) {
123 4         5 return !( _solve( $ptr->[1] ) ^ _solve( $ptr->[2] ) );
124             }
125 1         8 die "unknown op $ptr->[0]";
126             }
127 1         7 die "unexpected reference type '$rt'";
128             }
129              
130             sub walk
131             {
132 8     8 1 13 my ( $self, $opfn, $atomfn ) = @_;
133 8         15 _walk( $self->{expr}, $opfn, $atomfn );
134             }
135              
136             sub _walk
137             {
138 10     10   14 my ( $ptr, $opfn, $atomfn ) = @_;
139 10         14 my $rt = ref $ptr;
140 10 100       19 if ( $rt eq 'SCALAR' ) {
    100          
141 1         3 return $atomfn->($ptr);
142             } elsif ( $rt eq 'ARRAY' ) {
143             return $opfn->(
144             $ptr->[0],
145             [ map {
146 8         15 my $type = ref $ptr->[$_];
  16         21  
147 16 100       24 if ( $type eq 'SCALAR' ) {
    100          
148 12         22 $atomfn->( $ptr->[$_] );
149             } elsif ( $type eq 'ARRAY' ) {
150 2         7 _walk( $ptr->[$_], $opfn, $atomfn );
151             } else {
152             () # unary negation, probably
153 2         4 }
154             } 1 .. 2
155             ]
156             );
157             } else {
158 1         32 die "unexpected reference type '$rt'";
159             }
160             }
161              
162             1;
163             __END__