File Coverage

blib/lib/Callback/Frame.pm
Criterion Covered Total %
statement 115 124 92.7
branch 39 46 84.7
condition 4 5 80.0
subroutine 17 19 89.4
pod 0 9 0.0
total 175 203 86.2


line stmt bran cond sub pod time code
1             package Callback::Frame;
2              
3 10     10   3836 use strict;
  10         11  
  10         380  
4              
5             our $VERSION = '1.102';
6              
7             require Exporter;
8 10     10   32 use base 'Exporter';
  10         8  
  10         966  
9             our @EXPORT = qw(frame fub frame_try frame_try_void frame_catch frame_local frame_void);
10              
11 10     10   32 use Scalar::Util;
  10         13  
  10         440  
12 10     10   36 use Carp qw/croak/;
  10         13  
  10         437  
13 10     10   3932 use Guard;
  10         3507  
  10         3819  
14              
15              
16             our $top_of_stack;
17             our $active_frames = {};
18              
19              
20             sub frame {
21 31     31 0 5550 my ($name, $code, $catcher, $locals, $existing_frame);
22              
23 31         122 while ((my $k, my $v, @_) = @_) {
24 66 100       164 if ($k eq 'name') {
    100          
    100          
    100          
    50          
25 8         11 $name = $v;
26             } elsif ($k eq 'code') {
27 31         31 $code = $v;
28             } elsif ($k eq 'catch') {
29 12         27 $catcher = $v;
30             } elsif ($k eq 'local') {
31 10         21 $locals->{$v} = undef;
32             } elsif ($k eq 'existing_frame') {
33 5         4 $existing_frame = $v;
34             } else {
35 0         0 croak "Unknown frame option: $k";
36             }
37              
38 66 50       209 croak "value missing for key $k" if !defined $v;
39             }
40              
41 31   100     102 $name ||= 'ANONYMOUS FRAME';
42 31         72 my ($package, $filename, $line) = caller;
43 31 100       70 ($package, $filename, $line) = caller(1) if $package eq __PACKAGE__; ## if we're called from fub or frame_try
44 31         67 $name = "$filename:$line - $name";
45              
46 31 50       44 defined $code || croak "frame needs a 'code' callback";
47              
48 31         26 my $existing_top_of_stack;
49 31 100       45 if (defined $existing_frame) {
50 5         9 $existing_top_of_stack = $active_frames->{"$existing_frame"};
51 5 50       7 croak "existing_frame isn't a frame" unless $existing_top_of_stack;
52 5 50       8 croak "can't install new catcher if using existing_frame" if defined $catcher;
53 5 50       8 croak "can't install new local if using existing_frame" if defined $locals;
54             }
55              
56              
57 31         22 my ($ret_cb, $internal_cb);
58              
59             $ret_cb = sub {
60 33     33   1471 return $internal_cb->(@_);
61 31         65 };
62              
63 31         50 my $cb_address = "$ret_cb";
64              
65 31         21 my $new_frame;
66              
67 31 100       43 if ($existing_top_of_stack) {
68 5         5 $new_frame = $existing_top_of_stack;
69             } else {
70             $new_frame = {
71             name => $name,
72             down => $top_of_stack,
73             guard => guard {
74 22     22   1209 undef $ret_cb;
75 22         141 delete $active_frames->{$cb_address};
76             },
77 26         124 };
78              
79 26 100       57 $new_frame->{catcher} = $catcher if defined $catcher;
80 26 100       45 $new_frame->{locals} = $locals if defined $locals;
81              
82 26         37 $active_frames->{$cb_address} = $new_frame;
83 26         85 Scalar::Util::weaken($active_frames->{$cb_address});
84             }
85              
86             $internal_cb = sub {
87 33     33   45 my $orig_error = $@;
88              
89 33         36 local $top_of_stack = $new_frame;
90              
91 33         46 my $frame_i = $top_of_stack;
92              
93 33         51 my $val = eval {
94             ## Find applicable local vars
95              
96 33         61 my $local_refs = {};
97 33         33 my $temp_copies = {};
98              
99 33         64 for(; $frame_i; $frame_i = $frame_i->{down}) {
100 59 100       127 next unless exists $frame_i->{locals};
101 30         25 foreach my $k (keys %{$frame_i->{locals}}) {
  30         64  
102 34 100       57 next if exists $local_refs->{$k};
103 27         79 $local_refs->{$k} = \$frame_i->{locals}->{$k};
104             }
105             }
106              
107             ## Backup local vars
108              
109 33         60 foreach my $var (keys %$local_refs) {
110 10     10   46 no strict qw/refs/;
  10         11  
  10         653  
111 27         53 $temp_copies->{$var} = $$var;
112 27         27 $$var = ${$local_refs->{$var}};
  27         44  
113             }
114              
115             ## Install code that will restore local vars
116              
117             scope_guard {
118 33         2183 foreach my $var (keys %$local_refs) {
119 10     10   37 no strict qw/refs/;
  10         12  
  10         4870  
120 27         32 ${$local_refs->{$var}} = $$var;
  27         31  
121 27         75 $$var = $temp_copies->{$var};
122             }
123 33         129 };
124              
125             ## Actually run the callback
126              
127 33         34 $@ = $orig_error;
128              
129 33         50 $code->(@_);
130             };
131              
132 33         143 my $err = $@;
133              
134 33 100       62 if ($err) {
135 11         25 my $trace = generate_trace($top_of_stack, $err);
136              
137 11         23 for (my $frame_i = $top_of_stack; $frame_i; $frame_i = $frame_i->{down}) {
138 19 100       33 next unless exists $frame_i->{catcher};
139              
140 15         15 my $val = eval {
141 15         15 $@ = $err;
142 15         23 $frame_i->{catcher}->($trace);
143 4         1962 1
144             };
145              
146 14 100 66     1971 return if defined $val && $val == 1;
147              
148 10         23 $err = $@;
149             }
150              
151             ## No catcher available: just re-throw error
152 6         17 die $err;
153             }
154              
155 22         43 return $val;
156 31         87 };
157              
158 31         26 my $final_cb = $ret_cb;
159 31         49 Scalar::Util::weaken($ret_cb);
160              
161 31         87 return $final_cb;
162             }
163              
164              
165             sub fub (&@) {
166 3     3 0 314 my ($code, @args) = @_;
167              
168 3         9 return frame(code => $code, @args);
169             }
170              
171              
172             sub is_frame {
173 7     7 0 8 my $coderef = shift;
174              
175 7 100       17 return 0 unless ref $coderef;
176              
177 5 100       18 return 1 if exists $active_frames->{$coderef};
178              
179 2         4 return 0;
180             }
181              
182              
183             sub generate_trace {
184 11     11 0 17 my ($frame_pointer, $err) = @_;
185              
186 11         16 my $err_str = "$err";
187 11         21 chomp $err_str;
188 11         21 my $trace = "$err_str\n----- Callback::Frame stack-trace -----\n";
189              
190 11         31 for (my $frame_i = $frame_pointer; $frame_i; $frame_i = $frame_i->{down}) {
191 22         57 $trace .= "$frame_i->{name}\n";
192             }
193              
194 11         19 return $trace;
195             }
196              
197              
198             sub frame_void (&) {
199 0     0 0 0 my ($block) = @_;
200              
201 0         0 local $top_of_stack;
202 0         0 local $active_frames = {};
203              
204 0         0 $block->();
205             }
206              
207             sub frame_try (&;@) {
208 1     1 0 1 my ($try_block, $catch_block) = @_;
209              
210 1         2 return frame(code => $try_block, catch => $catch_block)->();
211             }
212              
213             sub frame_try_void (&;@) {
214 0     0 0 0 my ($try_block, $catch_block) = @_;
215              
216 0         0 local $top_of_stack;
217 0         0 local $active_frames = {};
218              
219 0         0 return frame(code => $try_block, catch => $catch_block)->();
220             }
221              
222             sub frame_catch (&) {
223 1     1 0 8 my ($block) = @_;
224              
225 1 50       3 croak "Useless bare frame_catch" unless wantarray;
226              
227 1         3 return $block;
228             }
229              
230             sub frame_local ($&) {
231 1     1 0 8 my ($local, $block) = @_;
232              
233 1         3 return frame(code => $block, local => $local)->();
234             }
235              
236              
237             1;
238              
239              
240             __END__