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__ |