File Coverage

blib/lib/Amb.pm
Criterion Covered Total %
statement 94 135 69.6
branch 25 74 33.7
condition 4 15 26.6
subroutine 12 14 85.7
pod 2 7 28.5
total 137 245 55.9


line stmt bran cond sub pod time code
1             # $Id: Amb.pm,v 1.7 2008/09/03 12:56:14 dk Exp $
2             package Amb;
3 3     2   32055 use strict;
  2         5  
  2         105  
4              
5             require Exporter;
6             require DynaLoader;
7 2     2   13 use vars qw(@ISA @EXPORT @EXPORT_OK $VERSION);
  2         4  
  3         278  
8             @ISA = qw(Exporter DynaLoader);
9             $VERSION = '0.02';
10             @EXPORT = qw(amb);
11             @EXPORT_OK = qw(angelic demonic);
12              
13             bootstrap Amb $VERSION;
14              
15 2     2   1876 use B::Generate 1.13;
  2         5855  
  2         76  
16 2     2   17 use Carp qw(confess croak);
  2         4  
  2         143  
17 2     2   1655 use PadWalker;
  2         1909  
  2         171  
18              
19 2     2   1718 BEGIN { *CORE::GLOBAL::die = \&dier } ;
20              
21             my (%patched, %cv, @stack, $charged, $debug);
22             $debug = $ENV{AMB_DEBUG}||0;
23              
24             sub dier
25             {
26 1 50   1 0 10 CORE::die(@_) if $^S; # eval
27              
28 1         2 AGAIN:
29             my $c = pop @stack;
30 1 50       6 unless ( $c) {
31 0         0 my @c = caller;
32 0 0       0 push @_, " at $c[1] line $c[2]\n" unless join('', @_) =~ /\n/;
33 0         0 CORE::die @_;
34             }
35              
36 1 50       3 if ( $c-> {angelic}) {
37 1         2 $charged = $c;
38 1 50       3 print "angelic/die in branch # $c->{state} at $c->{label}\n" if $debug;
39 1         2 $c-> {state}++;
40 1         13 goto $c-> {label};
41             } else {
42 0 0       0 print "demonic/die in branch # $c->{state} at $c->{label}\n" if $debug;
43 0         0 $c-> {state} = 0;
44 0         0 goto AGAIN; # that means die again
45             }
46             }
47              
48             sub after
49             {
50 1     1 0 2 my $c = pop @stack;
51 1 50       5 if ( $c) {
52 1 50       4 if ( $c-> {angelic}) {
53 1 50       3 print "angelic/after\n" if $debug;
54 1         2 $c-> {state} = 0;
55             } else {
56 0 0       0 print "demonic/after\n" if $debug;
57 0         0 $c-> {state}++;
58 0         0 $charged = $c;
59 0         0 goto $c-> {label};
60             }
61             }
62 1         7 undef $charged;
63             }
64              
65             sub fail($)
66             {
67 0 0   0 0 0 local $Carp::CarpLevel = 3 unless $debug;
68 0         0 confess "Can't call $_[0]\(\) that way";
69             }
70              
71             sub patch
72             {
73 1     1 0 562 my ($name, $xop, $cv, $upcontext) = @_;
74            
75 1 50       6 printf("$name: patch at COP( 0x%x)\n", $$xop) if $debug;
76              
77 1 50       7 my $cv_frame = $cv ? B::svref_2object($cv) : B::main_cv;
78              
79             # enter other CV's padlist
80 1         10 my $savecp = B::cv_pad;
81 1         6 B::cv_pad( $cv_frame);
82              
83 1         14 my $psm = B::GVOP-> new( 'gv', 0, \&after);
84              
85             # calling ops
86 1         14 my $gc2 = B::UNOP-> new( 'null', 0, $psm);
87 1         33 my $gc3 = B::UNOP-> new( 'entersub', 0, $gc2);
88 1         10 my $cop = B::COP-> new( 0, '', 0); # this line appears as a calling point for after()
89             # this is the COP we put $cop after
90 1         12 my $gs = $xop-> sibling-> sibling;
91 1 50       7 if ( ref($gs) eq 'B::NULL') {
    50          
92             # there's no COP -- it was last already
93             # create an artificial cop then
94 0         0 $gs = B::COP-> new( 1, '', 0);
95 0         0 $xop-> sibling-> sibling( $gs);
96             } elsif ( ref($gs) ne 'B::COP') {
97 0         0 fail $name;
98             }
99              
100 1         4 my $gss = $gs-> sibling;
101 1         7 my $gsn = $gs-> next;
102              
103 1         4 $gs-> next($cop);
104 1         4 $cop-> next($psm);
105 1         14 $psm-> next($gc2);
106 1         6 $gc2-> next($gc3);
107 1         4 $gc3-> next($gsn);
108              
109 1         9 $gs-> sibling($cop);
110 1         3 $cop-> sibling($gc3);
111 1         3 $gc3-> sibling($gss);
112              
113             # create COP with label and put it before the entry COP
114 1         10 my $id = sprintf "$name\:\:0x%x/0x%x", $$xop, $upcontext;
115 1         6 my $lab = B::COP-> new( 0, $id, 0);
116 1         6 $lab-> sibling( $xop-> sibling);
117 1         3 $xop-> sibling( $lab);
118 1         5 $lab-> next( $xop-> next);
119 1         4 $xop-> next($lab);
120              
121             # restore padlist
122 1         3 B::cv_pad( $savecp);
123              
124 1 50       5 if ( $debug > 1) {
125 2     2   17 no strict;
  2         4  
  2         1807  
126 0         0 local $SIG{__WARN__};
127 0 0       0 eval "*B::CV::NAME = sub { 'fake' };" unless exists ${'B::CV'}{NAME};
  0         0  
128 0 0       0 eval "*B::NV::int_value = sub { '0.0' };" unless exists ${'B::NV'}{int_value};
  0         0  
129              
130 0         0 require B::Concise;
131 0 0       0 my $walker = B::Concise::compile('-terse',($cv?$cv:()));
132 0         0 $walker->();
133             }
134              
135 1         4 return $id;
136            
137             }
138              
139             sub find_ctx
140             {
141             # get the COP that is right before the call of amb()
142 2     2 0 4 my $what = shift;
143 2         9 my $up = PadWalker::_upcontext(1);
144 2 50       8 fail $what unless $up;
145 2         12 my $op = Amb::caller_op($up);
146 2 50 33     13 fail $what unless $op and ref($op) eq 'B::COP';
147              
148             # ensure that the call is inside if(...) statement
149 2         17 my $x = $op-> sibling;
150 2 50 33     20 fail $what unless $x and ref($x) eq 'B::UNOP';
151 2         10 $x = $x-> first;
152 2 50 33     42 fail $what unless $x and ref($x) eq 'B::LOGOP' and $x-> name =~ /^(cond_expr|and)$/;
      33        
153              
154             # get the cv frame that has called
155 2         5 my $upper = PadWalker::_upcontext(2);
156 2         3 my $cx;
157 2 100       5 if ( $upper) {
158 1         236 $cx = Amb::context_cv($upper);
159 0 0 0     0 fail $what unless $cx and ref($cx) eq 'CODE';
160             }
161              
162 1         12 return $op, $cx, $up;
163             }
164              
165             sub amb
166             {
167 3 50   3 1 18 croak "format: amb(arg1,arg2)" if 1 != $#_;
168            
169 3         4 my $c;
170 3 100       9 unless ( $charged) {
171 2         6 my ($op, $cx, $up) = find_ctx('amb');
172 1 50       4 printf("amb: 1st call at %x\n", $$op) if $debug;
173              
174 1         1 my $id;
175 1 50       5 unless ( exists $patched{$$op}) {
176 1         4 $id = patch( 'amb', $op, $cx, $up);
177 1         5 $patched{$$op} = {
178             angelic => 1,
179             label => $id,
180             }
181             }
182 1         2 $c = $patched{$$op};
183 1         3 $c-> {state} = 0;
184             } else {
185 1         1 $c = $charged;
186 1         2 undef $charged;
187 1 50       3 print "amb: jump from $c->{label}\n" if $debug;
188             }
189              
190 2 50       7 die "amb: all branches fail" if $c-> {state} > $#_;
191              
192 2         5 push @stack, $c;
193 2         7 return $_[ $c-> {state} ];
194             }
195              
196             *angelic = \&amb;
197              
198             sub demonic
199             {
200 0 0   0 1   croak "format: demonic(arg1,arg2)" if 1 != $#_;
201              
202 0           my $c;
203 0 0         unless ( $charged) {
204 0           my ($op, $cx, $up) = find_ctx('demonic');
205 0 0         printf("demonic: 1st call at %x\n", $op) if $debug;
206              
207 0           my $id;
208 0 0         unless ( exists $patched{$$op}) {
209 0           $id = patch( 'demonic', $op, $cx, $up);
210 0           $patched{$$op} = {
211             angelic => 0,
212             label => $id,
213             }
214             }
215 0           $c = $patched{$$op};
216 0           $c-> {state} = 0;
217             } else {
218 0           $c = $charged;
219 0           undef $charged;
220 0 0         print "demonic: jump from $c->{label}\n" if $debug;
221             }
222              
223 0 0         die "demonic: all branches succeed" if $c-> {state} > $#_;
224              
225 0           push @stack, $c;
226 0           return $_[ $c-> {state} ];
227             }
228              
229             1;
230              
231             __DATA__