Callback-Frame
view release on metacpan or search on metacpan
lib/Callback/Frame.pm view on Meta::CPAN
while ((my $k, my $v, @_) = @_) {
if ($k eq 'name') {
$name = $v;
} elsif ($k eq 'code') {
$code = $v;
} elsif ($k eq 'catch') {
$catcher = $v;
} elsif ($k eq 'local') {
$locals->{$v} = undef;
} elsif ($k eq 'existing_frame') {
$existing_frame = $v;
} else {
croak "Unknown frame option: $k";
}
croak "value missing for key $k" if !defined $v;
}
$name ||= 'ANONYMOUS FRAME';
my ($package, $filename, $line) = caller;
($package, $filename, $line) = caller(1) if $package eq __PACKAGE__; ## if we're called from fub or frame_try
$name = "$filename:$line - $name";
defined $code || croak "frame needs a 'code' callback";
my $existing_top_of_stack;
if (defined $existing_frame) {
$existing_top_of_stack = $active_frames->{"$existing_frame"};
croak "existing_frame isn't a frame" unless $existing_top_of_stack;
croak "can't install new catcher if using existing_frame" if defined $catcher;
croak "can't install new local if using existing_frame" if defined $locals;
}
my ($ret_cb, $internal_cb);
$ret_cb = sub {
return $internal_cb->(@_);
};
my $cb_address = "$ret_cb";
my $new_frame;
if ($existing_top_of_stack) {
$new_frame = $existing_top_of_stack;
} else {
$new_frame = {
name => $name,
down => $top_of_stack,
guard => guard {
undef $ret_cb;
delete $active_frames->{$cb_address};
},
};
$new_frame->{catcher} = $catcher if defined $catcher;
$new_frame->{locals} = $locals if defined $locals;
$active_frames->{$cb_address} = $new_frame;
Scalar::Util::weaken($active_frames->{$cb_address});
}
$internal_cb = sub {
my $orig_error = $@;
local $top_of_stack = $new_frame;
my $frame_i = $top_of_stack;
my $val = eval {
## Find applicable local vars
my $local_refs = {};
my $temp_copies = {};
for(; $frame_i; $frame_i = $frame_i->{down}) {
next unless exists $frame_i->{locals};
foreach my $k (keys %{$frame_i->{locals}}) {
next if exists $local_refs->{$k};
$local_refs->{$k} = \$frame_i->{locals}->{$k};
}
}
## Backup local vars
foreach my $var (keys %$local_refs) {
no strict qw/refs/;
$temp_copies->{$var} = $$var;
$$var = ${$local_refs->{$var}};
}
## Install code that will restore local vars
scope_guard {
foreach my $var (keys %$local_refs) {
no strict qw/refs/;
${$local_refs->{$var}} = $$var;
$$var = $temp_copies->{$var};
}
};
## Actually run the callback
$@ = $orig_error;
$code->(@_);
};
my $err = $@;
if ($err) {
my $trace = generate_trace($top_of_stack, $err);
for (my $frame_i = $top_of_stack; $frame_i; $frame_i = $frame_i->{down}) {
next unless exists $frame_i->{catcher};
my $val = eval {
$@ = $err;
$frame_i->{catcher}->($trace);
1
};
return if defined $val && $val == 1;
$err = $@;
}
## No catcher available: just re-throw error
die $err;
}
return $val;
};
my $final_cb = $ret_cb;
Scalar::Util::weaken($ret_cb);
return $final_cb;
}
sub fub (&@) {
my ($code, @args) = @_;
return frame(code => $code, @args);
}
sub is_frame {
my $coderef = shift;
return 0 unless ref $coderef;
return 1 if exists $active_frames->{$coderef};
return 0;
}
sub generate_trace {
my ($frame_pointer, $err) = @_;
my $err_str = "$err";
chomp $err_str;
my $trace = "$err_str\n----- Callback::Frame stack-trace -----\n";
for (my $frame_i = $frame_pointer; $frame_i; $frame_i = $frame_i->{down}) {
$trace .= "$frame_i->{name}\n";
}
return $trace;
}
sub frame_void (&) {
my ($block) = @_;
local $top_of_stack;
local $active_frames = {};
$block->();
}
sub frame_try (&;@) {
my ($try_block, $catch_block) = @_;
return frame(code => $try_block, catch => $catch_block)->();
}
sub frame_try_void (&;@) {
my ($try_block, $catch_block) = @_;
local $top_of_stack;
local $active_frames = {};
return frame(code => $try_block, catch => $catch_block)->();
( run in 0.492 second using v1.01-cache-2.11-cpan-39bf76dae61 )