File Coverage

blib/lib/Callback/Frame.pm
Criterion Covered Total %
statement 115 116 99.1
branch 39 46 84.7
condition 4 5 80.0
subroutine 17 17 100.0
pod 0 7 0.0
total 175 191 91.6


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