Devel-Pragma-0.60/ 0000755 0001750 0001750 00000000000 12231016033 015602 5 ustar chocolateboy chocolateboy Devel-Pragma-0.60/Pragma.xs 0000644 0001750 0001750 00000016375 12231015242 017402 0 ustar chocolateboy chocolateboy #define PERL_NO_GET_CONTEXT
#include "EXTERN.h"
#include "perl.h"
#define NO_XSLOCKS /* trap exceptions in pp_require */
#include "XSUB.h"
#define NEED_sv_2pv_flags
#include "ppport.h"
#include "hook_op_check.h"
#include "hook_op_annotation.h"
#define DEVEL_PRAGMA_ON_REQUIRE_KEY "Devel::Pragma::on_require"
#define DEVEL_PRAGMA_ON_REQUIRE_ENABLED(table, svp) \
((PL_hints & 0x20000) && \
PL_hintgv && \
(table = GvHV(PL_hintgv)) && \
(svp = hv_fetch(table, DEVEL_PRAGMA_ON_REQUIRE_KEY, sizeof(DEVEL_PRAGMA_ON_REQUIRE_KEY) - 1, FALSE)) && \
*svp && \
SvOK(*svp))
STATIC OP * devel_pragma_check_require(pTHX_ OP * o, void *user_data);
STATIC OP * devel_pragma_require(pTHX);
STATIC void devel_pragma_call(pTHX_ const char * const callback, HV * const hv);
STATIC void devel_pragma_enable_check_hooks();
STATIC hook_op_check_id devel_pragma_check_do_file_id = 0;
STATIC hook_op_check_id devel_pragma_check_require_id = 0;
STATIC OPAnnotationGroup DEVEL_PRAGMA_ANNOTATIONS = NULL;
STATIC U32 DEVEL_PRAGMA_CHECK_HOOKS_ENABLED = 0;
STATIC void devel_pragma_call(pTHX_ const char * const callback, HV * const hv) {
dSP;
ENTER;
SAVETMPS;
PUSHMARK(SP);
XPUSHs(sv_2mortal(newRV_inc((SV *)hv)));
PUTBACK;
call_pv(callback, G_DISCARD);
FREETMPS;
LEAVE;
}
STATIC OP * devel_pragma_check_require(pTHX_ OP * o, void *user_data) {
HV * table;
SV ** svp;
PERL_UNUSED_VAR(user_data);
if (!DEVEL_PRAGMA_ON_REQUIRE_ENABLED(table, svp)) {
goto done;
}
/* make sure it's still a require; the previous checker may have turned it into something else */
if (!((o->op_type == OP_REQUIRE) || (o->op_type == OP_DOFILE))) {
goto done;
}
/* */
if (o->op_type != OP_DOFILE) {
if (o->op_flags & OPf_KIDS) {
SVOP * const kid = (SVOP*)cUNOPo->op_first;
if (kid->op_type == OP_CONST) { /* weed out use VERSION */
SV * const sv = kid->op_sv;
if (SvNIOKp(sv)) { /* exclude use 5 and use 5.008 &c. */
goto done;
}
#ifdef SvVOK
if (SvVOK(sv)) { /* exclude use v5.008 and use 5.6.1 &c. */
goto done;
}
#endif
if (!SvPOKp(sv)) { /* err on the side of caution */
goto done;
}
}
}
}
/* */
op_annotate(DEVEL_PRAGMA_ANNOTATIONS, o, NULL, NULL);
o->op_ppaddr = devel_pragma_require;
done:
return o;
}
/* much of this is copypasta from pp_require in pp_ctl.c */
STATIC OP * devel_pragma_require(pTHX) {
/* */
dSP;
SV * sv;
HV *hh, *copy_of_hh;
const char *name;
STRLEN len;
char * unixname;
STRLEN unixlen;
#ifdef VMS
int vms_unixname = 0;
#endif
/* */
OP * o = NULL;
/* used as a boolean to determine whether any require callbacks are registered */
SV ** callbacks = NULL;
/* we always need this (to get the ppaddr to delegate to) so define it upfront */
OPAnnotation *annotation = op_annotation_get(DEVEL_PRAGMA_ANNOTATIONS, PL_op);
/* */
sv = TOPs;
if (PL_op->op_type != OP_DOFILE) {
if (SvNIOKp(sv)) { /* exclude use 5 and use 5.008 &c. */
goto done;
}
#ifdef SvVOK
if (SvVOK(sv)) { /* exclude use v5.008 and use 5.6.1 &c. */
goto done;
}
#endif
if (!SvPOKp(sv)) { /* err on the side of caution */
goto done;
}
}
name = SvPV_const(sv, len);
if (!(name && (len > 0) && *name)) {
goto done;
}
TAINT_PROPER("require");
#ifdef VMS
/* The key in the %ENV hash is in the syntax of file passed as the argument
* usually this is in UNIX format, but sometimes in VMS format, which
* can result in a module being pulled in more than once.
* To prevent this, the key must be stored in UNIX format if the VMS
* name can be translated to UNIX.
*/
if ((unixname = tounixspec(name, NULL)) != NULL) {
unixlen = strlen(unixname);
vms_unixname = 1;
}
else
#endif
{
/* if not VMS or VMS name can not be translated to UNIX, pass it
* through.
*/
unixname = (char *) name;
unixlen = len;
}
if (PL_op->op_type == OP_REQUIRE) {
SV * const * const svp = hv_fetch(GvHVn(PL_incgv), unixname, unixlen, 0);
if (svp) { /* already loaded: see pp_require */
goto done;
}
}
/* */
hh = GvHV(PL_hintgv); /* %^H */
copy_of_hh = newHVhv(hh); /* create a snapshot of %^H */
callbacks = hv_fetchs(copy_of_hh, "Devel::Pragma::on_require", FALSE);
/* make sure the on_require callbacks are still defined i.e. this is not being called at runtime */
if (!callbacks) {
hv_clear(copy_of_hh);
hv_undef(copy_of_hh);
goto done;
}
devel_pragma_call(aTHX_ "Devel::Pragma::_pre_require", copy_of_hh); /* invoke the pre-require callbacks */
{
dXCPT; /* set up variables for try/catch */
XCPT_TRY_START {
o = annotation->op_ppaddr(aTHX);
} XCPT_TRY_END
XCPT_CATCH {
devel_pragma_call(aTHX_ "Devel::Pragma::_post_require", copy_of_hh); /* invoke the post-require callbacks */
hv_clear(copy_of_hh);
hv_undef(copy_of_hh);
XCPT_RETHROW;
}
}
devel_pragma_call(aTHX_ "Devel::Pragma::_post_require", copy_of_hh); /* invoke the post-require callbacks */
hv_clear(copy_of_hh);
hv_undef(copy_of_hh);
return o;
done:
return annotation->op_ppaddr(aTHX);
}
STATIC void devel_pragma_enable_check_hooks() {
if (DEVEL_PRAGMA_CHECK_HOOKS_ENABLED != 1) {
devel_pragma_check_do_file_id = hook_op_check(OP_DOFILE, devel_pragma_check_require, NULL);
devel_pragma_check_require_id = hook_op_check(OP_REQUIRE, devel_pragma_check_require, NULL);
/* work around B::Hooks::OP::Check issue on 5.8.1 */
SvREFCNT_inc(devel_pragma_check_do_file_id);
SvREFCNT_inc(devel_pragma_check_require_id);
DEVEL_PRAGMA_CHECK_HOOKS_ENABLED = 1;
}
}
MODULE = Devel::Pragma PACKAGE = Devel::Pragma
BOOT:
DEVEL_PRAGMA_ANNOTATIONS = op_annotation_group_new();
devel_pragma_enable_check_hooks();
void
DESTROY(SV * sv)
PROTOTYPE:$
CODE:
PERL_UNUSED_VAR(sv); /* silence warning */
if (DEVEL_PRAGMA_ANNOTATIONS) { /* make sure it was initialised */
op_annotation_group_free(aTHX_ DEVEL_PRAGMA_ANNOTATIONS);
}
SV *
ccstash()
PROTOTYPE:
CODE:
/* FIXME: this should probably croak or return NULL at runtime */
RETVAL = newSVpv(HvNAME(PL_curstash ? PL_curstash : PL_defstash), 0);
OUTPUT:
RETVAL
void
xs_scope()
PROTOTYPE:
CODE:
XSRETURN_UV(PTR2UV(GvHV(PL_hintgv)));
Devel-Pragma-0.60/MANIFEST 0000644 0001750 0001750 00000001537 12231016033 016741 0 ustar chocolateboy chocolateboy Changes
lib/Devel/Pragma.pm
Makefile.PL
MANIFEST This list of files
META.yml
ppport.h
Pragma.xs
README
t/01_use.t
t/ccstash.t
t/exception.t
t/fqname.t
t/hints.t
t/leak.t
t/lib/leak.pm
t/lib/lexical1.pm
t/lib/lexical2.pm
t/lib/require_1.pm
t/lib/require_2.pm
t/lib/require_3.pm
t/lib/require_4.pm
t/lib/require_5.pm
t/lib/require_6.pm
t/lib/require_7.pm
t/lib/subclass_1.pm
t/lib/subclass_2.pm
t/lib/subclass_3.pm
t/lib/subclass_4.pm
t/lib/subclass_5.pm
t/lib/superclass_1.pm
t/lib/superclass_2.pm
t/lib/superclass_3.pm
t/lib/superclass_4.pm
t/lib/superclass_5.pm
t/lib/test_2.pm
t/lib/test_3.pm
t/lib/test_4.pm
t/lib/test_5.pm
t/lib/test_6.pm
t/lib/test_7.pm
t/lib/test_8.pm
t/lib/test_9.pm
t/lib/test_10.pm
t/lib/test_11.pm
t/new_scope.t
t/pod.t
t/require.t
t/scope.t
t/tag.t
META.json Module JSON meta-data (added by MakeMaker)
Devel-Pragma-0.60/META.yml 0000644 0001750 0001750 00000001354 12231016033 017056 0 ustar chocolateboy chocolateboy ---
abstract: 'helper functions for developers of lexical pragmas'
author:
- 'chocolateboy '
build_requires:
ExtUtils::MakeMaker: 0
configure_requires:
B::Hooks::OP::Annotation: 0.44
B::Hooks::OP::Check: 0.19
ExtUtils::Depends: 0.302
ExtUtils::MakeMaker: 0
dynamic_config: 1
generated_by: 'ExtUtils::MakeMaker version 6.62, CPAN::Meta::Converter version 2.130880'
license: perl
meta-spec:
url: http://module-build.sourceforge.net/META-spec-v1.4.html
version: 1.4
name: Devel-Pragma
no_index:
directory:
- t
- inc
requires:
B::Hooks::OP::Annotation: 0.44
B::Hooks::OP::Check: 0.19
Lexical::SealRequireHints: 0.007
resources:
repository: http://github.com/chocolateboy/Devel-Pragma
version: 0.60
Devel-Pragma-0.60/lib/ 0000755 0001750 0001750 00000000000 12231016033 016350 5 ustar chocolateboy chocolateboy Devel-Pragma-0.60/lib/Devel/ 0000755 0001750 0001750 00000000000 12231016033 017407 5 ustar chocolateboy chocolateboy Devel-Pragma-0.60/lib/Devel/Pragma.pm 0000644 0001750 0001750 00000030623 12224306531 021167 0 ustar chocolateboy chocolateboy package Devel::Pragma;
use 5.008;
# make sure this is loaded first
use Lexical::SealRequireHints;
use strict;
use warnings;
use B::Hooks::OP::Annotation;
use B::Hooks::OP::Check;
use Carp qw(carp croak);
use Scalar::Util;
use XSLoader;
use base qw(Exporter);
our $VERSION = '0.60';
our @EXPORT_OK = qw(my_hints hints new_scope ccstash scope fqname on_require);
our %EXPORT_TAGS = (all => [ @EXPORT_OK ]);
# Perform (XS) cleanup on global destruction (DESTROY is defined in Pragma.xs).
# END blocks don't work for this: see https://rt.cpan.org/Ticket/Display.html?id=80400
# according to perlvar, package variables are garbage collected after END blocks
our $__GLOBAL_DESTRUCTION_MONITOR__ = bless {};
XSLoader::load(__PACKAGE__, $VERSION);
# return a reference to the hints hash
sub my_hints() {
# set HINT_LOCALIZE_HH (0x20000)
$^H |= 0x20000;
return \%^H;
}
BEGIN { *hints = \&my_hints }
# make sure the "enable lexically-scoped %^H" flag is set (on by default in 5.10)
sub check_hints() {
unless ($^H & 0x20000) {
carp('Devel::Pragma: unexpected $^H (HINT_LOCALIZE_HH bit not set) - setting it now, but results may be unreliable');
}
return hints; # create it if it doesn't exist - in some perls, it starts out NULL
}
# return a unique integer ID for the current scope
sub scope() {
check_hints;
xs_scope();
}
# return a boolean indicating whether this is the first time "use MyPragma" has been called in this scope
sub new_scope(;$) {
my $caller = shift || caller;
my $hints = check_hints();
# this is %^H as an integer - it changes as scopes are entered/exited i.e. it's a unique
# identifier for the currently-compiling scope (the scope in which new_scope
# is called)
#
# we don't need to stack/unstack it in %^H as %^H itself takes care of that
# note: we need to call this *after* %^H is referenced (and possibly autovivified) above
#
# every time new_scope is called, we write this scope ID to $^H{"Devel::Pragma::new_scope::$caller"}.
# if $^H{"Devel::Pragma::new_scope::$caller"} == scope() (i.e. the stored scope ID is the same as the
# current scope ID), then we're augmenting the current scope; otherwise we're in a new scope - i.e.
# a nested or outer scope that didn't previously "use MyPragma"
my $current_scope = scope();
my $id = "Devel::Pragma::new_scope::$caller";
my $old_scope = exists($hints->{$id}) ? $hints->{$id} : 0;
my $new_scope; # is this a scope in which new_scope has not previously been called?
if ($current_scope == $old_scope) {
$new_scope = 0;
} else {
$hints->{$id} = $current_scope;
$new_scope = 1;
}
return $new_scope;
}
# given a short name (e.g. "foo"), expand it into a fully-qualified name with the caller's package prefixed
# e.g. "main::foo"
#
# if the name is already fully-qualified, return it unchanged
sub fqname ($;$) {
my $name = shift;
my ($package, $subname);
$name =~ s{'}{::}g;
if ($name =~ /::/) {
($package, $subname) = $name =~ m{^(.+)::(\w+)$};
} else {
my $caller = @_ ? shift : ccstash();
($package, $subname) = ($caller, $name);
}
return wantarray ? ($package, $subname) : "$package\::$subname";
}
# helper function: return true if $ref ISA $class - works with non-references, unblessed references and objects
sub _isa($$) {
my ($ref, $class) = @_;
return Scalar::Util::blessed($ref) ? $ref->isa($class) : ref($ref) eq $class;
}
# run registered callbacks before performing a compile-time require or do FILE
sub _pre_require($) {
_callback(0, shift);
}
# run registered callbacks after performing a compile-time require or do FILE
sub _post_require($) {
local $@; # if there was an exception on require, make sure we don't clobber it
_callback(1, shift)
}
# common code for pre- and post-require hooks
sub _callback($$) {
my ($index, $hints) = @_;
my $pairs = $hints->{'Devel::Pragma::on_require'} || [];
for my $pair (@$pairs) {
eval { $pair->[$index]->($hints) };
if ($@) {
my $stage = [ qw(pre post) ]->[$index];
carp __PACKAGE__ . ": exception in $stage-require callback: $@";
}
}
}
# register pre- and/or post-require hooks
# these are only called if the require occurs at compile-time
sub on_require($$) {
my $hints = hints();
for my $index (0 .. 1) {
my $arg = $_[$index];
my $ref = defined($arg) ? ref($arg) : '';
croak(sprintf('%s: invalid arg %d; expected CODE, got %s', __PACKAGE__, $index + 1, $ref))
unless ($arg and _isa($arg, 'CODE'));
}
my $old_callbacks = $hints->{'Devel::Pragma::on_require'} || [];
$hints->{'Devel::Pragma::on_require'} = [ @$old_callbacks, [ @_ ] ];
return;
}
# make sure "enable lexically-scoped %^H" is set in older perls, and export the requested functions
sub import {
my $class = shift;
$^H |= 0x20000; # set HINT_LOCALIZE_HH (0x20000)
$class->export_to_level(1, undef, @_);
}
1;
__END__
=head1 NAME
Devel::Pragma - helper functions for developers of lexical pragmas
=head1 SYNOPSIS
package MyPragma;
use Devel::Pragma qw(:all);
sub import {
my ($class, %options) = @_;
my $hints = hints; # lexically-scoped %^H
my $caller = ccstash(); # currently-compiling stash
unless ($hints->{MyPragma}) { # top-level
$hints->{MyPragma} = 1;
# disable/enable this pragma before/after compile-time requires
on_require \&teardown, \&setup;
}
if (new_scope($class)) {
...
}
my $scope_id = scope();
}
=head1 DESCRIPTION
This module provides helper functions for developers of lexical pragmas. These can be used both in older versions of
perl (from 5.8.1), which have limited support for lexical pragmas, and in the most recent versions, which have improved
support.
=head1 EXPORTS
C exports the following functions on demand. They can all be imported at once by using the C<:all> tag. e.g.
use Devel::Pragma qw(:all);
=head2 hints
This function enables the scoped behaviour of the hints hash (C<%^H>) and then returns a reference to it.
The hints hash is a compile-time global variable (which is also available at runtime in recent perls) that
can be used to implement lexically-scoped features and pragmas. This function provides a convenient
way to access this hash without the need to perform the bit-twiddling that enables it on older perls.
In addition, this module loads L, which implements bugfixes
that are required for the correct operation of the hints hash on older perls (< 5.12.0).
Typically, C should be called from a pragma's C (and optionally C) method:
package MyPragma;
use Devel::Pragma qw(hints);
sub import {
my $class = shift;
my $hints = hints;
if ($hints->{MyPragma}) {
# ...
} else {
$hints->{MyPragma} = ...;
}
# ...
}
=head2 new_scope
This function returns true if the currently-compiling scope differs from the scope being compiled the last
time C was called. Subsequent calls will return false while the same scope is being compiled.
C takes an optional parameter that is used to uniquely identify its caller. This should usually be
supplied as the pragma's class name unless C is called by a module that is not intended
to be subclassed. e.g.
package MyPragma;
sub import {
my ($class, %options) = @_;
if (new_scope($class)) {
...
}
}
If not supplied, the identifier defaults to the name of the calling package.
=head2 scope
This returns an integer that uniquely identifies the currently-compiling scope. It can be used to
distinguish or compare scopes.
A warning is issued if C (or C) is called in a context in which it doesn't make sense i.e. if the
scoped behaviour of C<%^H> has not been enabled - either by explicitly modifying C<$^H>, or by calling
L<"hints"> or L<"on_require">.
=head2 ccstash
This returns the name of the currently-compiling stash. It can be used as a replacement for the scalar form of
C to provide the name of the package in which C