File Coverage

blib/lib/Logic/Easy.pm
Criterion Covered Total %
statement 125 185 67.5
branch 29 38 76.3
condition 7 15 46.6
subroutine 33 43 76.7
pod 0 19 0.0
total 194 300 64.6


line stmt bran cond sub pod time code
1             package Logic::Easy;
2              
3 2     2   67348 use strict;
  2         4  
  2         86  
4 2     2   11 no warnings;
  2         4  
  2         71  
5              
6 2     2   10 use Exporter;
  2         4  
  2         114  
7 2     2   11 use base 'Exporter';
  2         3  
  2         272  
8              
9 2     2   2183 use Attribute::Handlers;
  2         18288  
  2         16  
10              
11 2     2   1424 use Logic::Stack;
  2         8  
  2         63  
12 2     2   13 use Logic::Variable;
  2         3  
  2         35  
13 2     2   1010 use Logic::Basic;
  2         6  
  2         73  
14 2     2   993 use Logic::Data;
  2         5  
  2         79  
15 2     2   10 use Carp;
  2         3  
  2         139  
16              
17             # Devel::Caller::Perl is loud about its own warnings. Shut it up.
18             our $PREWFLAG;
19 2     2   5 BEGIN { $PREWFLAG = $^W; $^W = 0; }
  2         43  
20 2     2   4176 use Devel::Caller::Perl;
  2         12986  
  2         17  
21 2     2   104 BEGIN { $^W = $PREWFLAG }
22              
23 2     2   17 use Perl6::Attributes;
  2         3  
  2         17  
24 2     2   4175 use Filter::Simple;
  2         4  
  2         16  
25 2     2   181 use Carp;
  2         5  
  2         1239  
26              
27              
28             sub _filter_signature {
29             # filters lines that look like SIG [$x, $y] where is($x, $y)
30 7     7   12 my ($sig, $where) = @_;
31 7         22 my (@vars) = $sig =~ /(\$[a-zA-Z_]\w*)/g;
32 7         10 my $varstr = join(',', @vars);
33 7 100       21 my $str = (@vars ? "my ($varstr); Logic::Easy::vars($varstr); " : "")
34             . "Logic::Easy->is([\@_], $sig)";
35            
36 7 100       18 if ($where =~ /^\s*\{/) {
    50          
37 1         3 $str .= "->bind($varstr, sub { sub { $where }->() or Logic::Easy::fail() });";
38             }
39             elsif ($where) {
40 0         0 $str .= "->$where->bind($varstr);";
41             }
42             else {
43 6         8 $str .= "->bind($varstr);";
44             }
45 7         47 $str;
46             }
47              
48             FILTER {
49             s/^ [ \t]* SIG [ \t]* ([^\n]+?) [ \t]*
50             (?: where [ \t]* ([^\n]+) [ \t]* )? ; [ \t]* $/
51             _filter_signature($1, $2)/mgex;
52             $_;
53             };
54              
55             our @EXPORT = qw;
56              
57             our %MULTI;
58              
59             sub UNIVERSAL::Multi : ATTR(CODE) {
60 10     10 0 671 my (undef, $glob, $code, undef, $name) = @_;
61 10         9 push @{$MULTI{$name}}, $code;
  10         18  
62 10 100       34 if ($glob ne 'ANON') {
63 4     38   21 *$glob = sub { unshift @_, $name; goto &_run_multi };
  38         152  
  38         113  
64             }
65 2     2   13 }
  2         4  
  2         32  
66              
67             sub _run_multi {
68 38     38   54 my $name = shift;
69 38 50       101 if ($MULTI{$name}) {
70 38         41 for my $code (@{$MULTI{$name}}) {
  38         80  
71 57         65 my ($ret, @rets);
72 57         78 my $wantarray = wantarray;
73 57 100       66 if (eval {
74 57 100       91 if ($wantarray) {
75 19         65 @rets = $code->(@_);
76             }
77             else {
78 38         93 $ret = $code->(@_);
79             }
80 38         100 1;
81             }) {
82 38 100       58 if ($wantarray) {
83 13         68 return @rets;
84             }
85             else {
86 25         109 return $ret;
87             }
88             }
89              
90 19 50       945 if ($@ =~ /Logic::/) {
91 19         36 next;
92             }
93             else {
94 0   0     0 croak($@ || "Logic::Easy multi dispatch failed");
95             }
96             }
97             }
98             else {
99 0         0 confess "No such method found: $name (I don't know how you made it to the dispatcher)";
100             }
101             }
102              
103             sub Logic() { 'Logic::Easy' }
104              
105             sub new {
106 145     145 0 239 my ($class, @preds) = @_;
107 145   66     1021 bless {
108             preds => \@preds,
109             } => ref $class || $class;
110             }
111              
112             sub create {
113 0     0 0 0 my ($self) = @_;
114 0         0 _made $self;
115 0         0 Logic::Basic::Sequence->new(@.preds);
  0         0  
116             }
117              
118             sub _make {
119 197 100   197   493 ref $_[0] ? $_[0] : $_[0]->new;
120             }
121              
122             sub _made {
123 73     73   140 $_[0] = _make $_[0];
124             }
125              
126             # XXX clean up this implementation... a lot.
127             sub bind {
128 124     124 0 183 my $self = _make shift;
129 124 100 100     475 if (@_ && ref $_[-1] eq 'CODE') {
130 72         73 my $stack = Logic::Stack->new(@.preds);
  72         285  
131 72 100       258 if ($stack->run) {
132 53         92 AGAIN:
133             my @vars = @_;
134 53         170 for (@_[0..$#_-1]) {
135 48         144 $_ = Logic::Data::resolve($_, $stack->state, vars => 1);
136             }
137 53 100       86 unless (eval { $_[-1]->(); 1 }) {
  53         115  
  41         329  
138 12         33 for (0..$#_-1) {
139 12         37 $_[$_] = $vars[$_];
140             }
141            
142 12 50       76 if ($@ =~ /Logic::/) {
143 12 100       50 if ($stack->backtrack) {
144 5         33 goto AGAIN;
145             }
146             else {
147 7         65 goto FAIL;
148             }
149             }
150             else {
151 0   0     0 croak($@ || "Logic::Easy binding predicate failed");
152             }
153             }
154 41         409 return 1;
155             }
156              
157             FAIL:
158 31 100       74 if (defined wantarray) {
159 1         6 return();
160             }
161             else {
162 30   100     4829 croak($@ || "Logic::Easy predicate failed");
163             }
164             }
165             else { # not given a code argument
166 52     28   298 $self->bind(@_, sub { });
  28         33  
167             }
168             }
169              
170             #### PREDICATES ####
171              
172             sub all { # generally redundant
173 0     0 0 0 my ($self, @cands) = @_;
174 0         0 _made $self;
175 0         0 $self->new(@.preds, Logic::Basic::Sequence->new(@cands));
  0         0  
176             }
177              
178             sub any {
179 0 0 0 0 0 0 if ($_[0] eq 'Logic::Easy' || ref $_[0] eq 'Logic::Easy') {
180 0         0 my ($self, @cands) = @_;
181 0         0 _made $self;
182 0         0 $self->new(@.preds, Logic::Basic::Alternation->new(@cands));
  0         0  
183             }
184             else {
185 0         0 Logic::Data::Disjunction->new(@_);
186             }
187             }
188              
189             sub id {
190 0     0 0 0 my ($self) = @_;
191 0         0 _made $self;
192 0         0 $self->new(@.preds, Logic::Basic::Identity->new);
  0         0  
193             }
194              
195             sub fail {
196 12 50   12 0 714 if (@_) { # method call
197 0         0 my ($self) = @_;
198 0         0 _made $self;
199 0         0 $self->new(@.preds, Logic::Basic::Fail->new);
  0         0  
200             }
201             else { # control operator
202 12         1427 croak "Logic::Easy control failed";
203             }
204             }
205              
206             sub assert {
207 0     0 0 0 my $self = _make shift;
208 0         0 my $code = pop;
209 0         0 my @args = @_;
210 0         0 my @vars = map { \$_[$_] } 0..$#_;
  0         0  
211 0         0 $self->new(@.preds, Logic::Basic::Assertion->new(sub {
212 0     0   0 my $state = shift;
213 0         0 my $result = eval {
214 0         0 for (@vars) {
215 0         0 $$_ = Logic::Data::resolve($$_, $state);
216             }
217 0         0 $code->();
218             };
219 0         0 for (0..$#vars) {
220 0         0 ${$vars[$_]} = $args[$_];
  0         0  
221             }
222 0         0 $result;
223 0         0 }));
224             }
225              
226             sub rule {
227 0     0 0 0 my ($self, $code) = @_;
228 0         0 _made $self;
229 0         0 $self->new(@.preds, Logic::Basic::Rule->new($code));
  0         0  
230             }
231              
232             sub bound {
233 0     0 0 0 my ($self, $var) = @_;
234 0         0 _made $self;
235 0         0 $self->new(@.preds, Logic::Basic::Bound->new($var));
  0         0  
236             }
237              
238             sub is {
239 72     72 0 302 my ($self, $a, $b) = @_;
240 72         121 _made $self;
241 72         93 $self->new(@.preds, Logic::Data::Unify->new($a, $b));
  72         313  
242             }
243              
244             sub assign {
245 0     0 0 0 my ($self, @vars) = @_;
246 0         0 _made $self;
247 0         0 my $code = pop @vars;
248 0 0       0 croak "Usage: Logic->assign(\$var1, \$var2, ..., sub { code })"
249             unless ref $code eq 'CODE';
250 0         0 $self->new(@.preds, Logic::Data::Assign->new($code, @vars));
  0         0  
251             }
252              
253             sub block {
254 0     0 0 0 my ($self) = @_;
255 0         0 _made $self;
256 0         0 $self->new(@.preds, Logic::Data::Stop->new);
  0         0  
257             }
258              
259             sub for {
260 1     1 0 238 my ($self, $var, @values) = @_;
261 1         3 _made $self;
262 1         2 $self->new(@.preds, Logic::Data::For->new($var, @values));
  1         9  
263             }
264              
265             sub sig {
266 9     9 0 35 my ($pattern) = @_;
267 9         32 my @args = Devel::Caller::Perl::called_args(0);
268 9         129 Logic::Easy->is(\@args, $pattern);
269             }
270              
271             #### CONSTRUCTORS (exported) ####
272              
273             sub cons {
274 8     8 0 36 my ($head, $tail) = @_;
275 8         35 Logic::Data::Cons->new($head, $tail);
276             }
277              
278             sub var($) {
279 9     9 0 8228 $_[0] = Logic::Variable->new;
280             }
281              
282             sub vars {
283 41     41 0 15634 for (@_) { $_ = Logic::Variable->new; }
  51         164  
284 41         92 @_;
285             }
286              
287              
288             1;