B-Hooks-AtRuntime
view release on metacpan or search on metacpan
lib/B/Hooks/AtRuntime.pm view on Meta::CPAN
package B::Hooks::AtRuntime;
use warnings;
use strict;
use XSLoader;
use Sub::Name "subname";
use Carp;
use parent "Exporter::Tiny";
our @EXPORT = qw/at_runtime/;
our @EXPORT_OK = qw/at_runtime after_runtime lex_stuff/;
BEGIN {
our $VERSION = "8";
XSLoader::load __PACKAGE__, $VERSION;
}
use constant USE_FILTER =>
defined $ENV{PERL_B_HOOKS_ATRUNTIME}
? $ENV{PERL_B_HOOKS_ATRUNTIME} eq "filter"
: not defined &lex_stuff;
if (USE_FILTER) {
require Filter::Util::Call;
# This isn't an exact replacement: it inserts the text at the start
# of the next line, rather than immediately after the current BEGIN.
#
# In theory I could use B::Hooks::Parser, which aims to emulate
# lex_stuff on older perls, but that uses a source filter to ensure
# PL_linebuf has some extra space in it (since it can't be
# reallocated without adjusting pointers we can't get to). This
# means BHP::setup needs to be called at least one source line
# before we want to insert any text (so the filter has a chance to
# run), which makes it precisely useless for our purposes :(.
no warnings "redefine";
*lex_stuff = subname "lex_stuff", sub {
my ($str) = @_;
compiling_string_eval() and croak
"Can't stuff into a string eval";
if (defined(my $extra = remaining_text())) {
$extra =~ s/\n+\z//;
carp "Extra text '$extra' after call to lex_stuff";
}
Filter::Util::Call::filter_add(sub {
$_ = $str;
Filter::Util::Call::filter_del();
return 1;
});
};
}
# In order to avoid needing to stuff text into perl's lexer too often,
# the code stuffed looks like this
#
# B::Hooks::AtRuntime::run(@B::Hooks::Runtime::hooks);
# BEGIN { B::Hooks::Runtime::clear(1) }
#
# The way this works is as follows.
#
# - The @hooks global refers to a different array every time it is used.
# The sub replace_hooks is responsible for making sure that the global
# points to the correct array at the time the stuffed code is
# compiled.
#
# - The lexical array @Hooks below contains one entry for each time we
# have recursively entered compile time. So, for example, if the user
# writes
#
# BEGIN {
# BEGIN {
# at_runtime { ... };
# }
# }
#
# then the at_runtime sub is pushed onto @Hooks[2], because we are in
# our second recursive BEGIN. (@Hooks[0] is never used.)
#
# - The XS sub count_BEGINs is responsible for finding which level of
# @Hooks to push onto. It does this by looking for BEGIN blocks,
# because even use compiles out as a BEGIN block.
#
# - The call to clear() ensures that if we leave and re-enter compile
# time at this level we get a new array of hooks and a new code-stuff
# to call them. The number passed (interpolated into the compiled
# code) is the level of @Hooks to clear.
my @Hooks;
sub replace_hooks {
my ($new) = @_;
# By deleting the stash entry we ensure the only ref to the glob is
# through the optree it was compiled into. This means that if that
# optree is ever freed, the glob will disappear along with anything
# closed over by the user's callbacks.
delete $B::Hooks::AtRuntime::{hooks};
no strict "refs";
$new and *{"hooks"} = $new;
}
sub clear {
my ($depth) = @_;
$Hooks[$depth] = undef;
replace_hooks $Hooks[$depth - 1];
}
sub find_hooks {
USE_FILTER and compiling_string_eval() and croak
"Can't use at_runtime from a string eval";
my $depth = count_BEGINs()
or croak "You must call at_runtime at compile time";
( run in 1.592 second using v1.01-cache-2.11-cpan-39bf76dae61 )