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