Ready for 0.01
[p5sagit/B-Hooks-EndOfScope-WithFallback.git] / lib / B / Hooks / EndOfScope / WithFallback.pm
CommitLineData
4befcbc3 1package B::Hooks::EndOfScope::WithFallback;
2
3use strict;
4use warnings;
5
6our $VERSION = '0.01';
7
8# note - the tie() fallback will probably work on 5.6 as well,
9# if you need to go that low - patches passing tests will be accepted
10use 5.008001;
11
12my ($v_m_req, $bheos_ver);
13BEGIN{
14 # Adjust the Makefile.PL if changing this minimum version
15 $v_m_req = '0.34';
16
17 # FIXME - remove if merged with B::H::EOS
18 $bheos_ver = '0.09';
19}
20
21BEGIN {
22 *__HAS_VM = eval {
23 require Variable::Magic;
24 Variable::Magic->VERSION($v_m_req);
25 } ? sub () { 1 } : sub () { 0 };
26}
27
28# FIXME - remove if merged with B::H::EOS
29BEGIN {
30 *__HAS_BHEOS = __HAS_VM && eval {
31 require B::Hooks::EndOfScope;
32 B::Hooks::EndOfScope->VERSION ($bheos_ver)
33 } ? sub () { 1 } : sub () { 0 };
34}
35
36use Sub::Exporter -setup => {
37 exports => ['on_scope_end'],
38 groups => { default => ['on_scope_end'] },
39};
40
41=head1 NAME
42
43B::Hooks::EndOfScope::WithFallback - B::Hooks::EndOfScope without an XS dependency
44
45=head1 SYNOPSIS
46
47 on_scope_end { ... };
48
49=head1 DESCRIPTION
50
51Just like its twin L<B::Hooks::EndOfScope> this module allows you to execute
52code when perl finished compiling the surrounding scope. The only difference
53is that this module will function even without the presence of the XS
54dependency L<Variable::Magic>. The behavior and API is identical to that of
55L<B::Hooks::EndOfScope> with the exception of one caveat as listed below.
56
57=head1 WHY ANOTHER MODULE
58
59While the design of the non-XS implementation is sound and passes every test
60of the original L<B::Hooks::EndOfScope> distribution, the authors of
61L<B::Hooks::EndOfScope> are currently not interested in integrating it into
62their distribution on philosophical grounds.
63
64=head1 CAVEATS
65
66Handling exceptions in scope-end callbacks is tricky business. While
67L<Variable::Magic> has access to some very dark sorcery to make it possible to
68throw an exception from within a callback, the pure-perl impleentation does
69not have access to these hacks. Therefore, what would have been a compile-time
70exception is instead emulated with output on C<STDERR> and an immediate exit
71via L<POSIX/_exit>. This can potentially have an impact on your code, since
72no C<END> blocks, nor C<DESTROY> callbacks will execute.
73
74=head1 FUNCTIONS
75
76=head2 on_scope_end
77
78 on_scope_end { ... };
79
80 on_scope_end $code;
81
82Registers C<$code> to be executed after the surrounding scope has been
83compiled.
84
85This is exported by default. See L<Sub::Exporter> on how to customize it.
86
87=cut
88
89# FIXME - remove if merged with B::H::EOS
90# already loaded - might as well
91if (__HAS_BHEOS) {
92 *on_scope_end = \&B::Hooks::EndOfScope::on_scope_end;
93}
94
95# we have V::M - just replicate everything here, do not
96# even try to load B::H::EOS (may or may not be installed)
97# the amount of code is minimal anyway, and we save on
98# the syscalls to find/load B/H/EOS.pm
99elsif (__HAS_VM) {
100
101 my $wiz = Variable::Magic::wizard (
102 data => sub { [$_[1]] },
103 free => sub { $_->() for @{ $_[1] }; () }
104 );
105
106 # str-eval so it is subnamed correctly
107 eval <<'EOS' or die $@;
108
109 sub on_scope_end (&) {
110 my $cb = shift;
111
112 $^H |= 0x020000;
113
114 if (my $stack = Variable::Magic::getdata %^H, $wiz) {
115 push @{ $stack }, $cb;
116 }
117 else {
118 Variable::Magic::cast %^H, $wiz, $cb;
119 }
120 }
121
122 1; # for the above `or die()`
123
124EOS
125}
126else {
127 # str eval for the above reason and so we do not burn cycles defining
128 # packages we will not use
129 eval <<'EOS' or die $@;
130
131 require Tie::Hash;
132
133 {
134 package B::Hooks::EndOfScope::WithFallback::_TieHintHash;
135
136 use warnings;
137 use strict;
138
139 our @ISA = 'Tie::ExtraHash';
140 }
141
142 # Instead of relying on specific destruction order like the V::M
143 # implementation does, use an explicit array with its own destructor
144 # ordering. This is a potential FIXME for the V::M-using code (it may
145 # very well start unwinding the *other* way), but leaving it as-is
146 # for now to match what B::H::EOS does.
147 {
148 package B::Hooks::EndOfScope::WithFallback::_ScopeGuardArray;
149
150 use warnings;
151 use strict;
152
153 sub new { bless [], ref $_[0] || $_[0] }
154
155 sub DESTROY {
156 local $@ = '';
157 # keep unwinding the stack until something decides to throw
158 while (@{$_[0]} and $@ eq '') {
159 eval { $_[0]->[0]{code}->(); shift @{$_[0]} };
160 }
161
162 if ( (my $err = $@) ne '') {
163 # argh argh argh - why did you have to throw in a scope-end?!
164 # we can not properly throw during a BEGIN from within pure-perl
165 # (V::M does some very weird XS magic to be able to). However - we
166 # are still compiling - so we can very well just exit() with a long
167 # explanation. Exitting with a normal exit() however won't work, as
168 # it causes perl to segfault (even 5.14), so doing the POSIX thing
169 # instead
170 my $exit_code = $ENV{B_HOOKS_EOS_PP_ON_DIE_EXITCODE};
171 $exit_code = 1 if (! defined $exit_code or ! length $exit_code);
172
173 print STDERR <<EOE; require POSIX; POSIX::_exit($exit_code);
174
175========================================================================
176 !!! F A T A L E R R O R !!!
177
178 Exception thrown by scope-end callback
179========================================================================
180
181B::Hooks::EndOfScope::WithFallback is currently operating in pure-perl
182fallback mode, because your system is lacking the necessary dependency
183Variable::Magic $v_m_req
184In this mode B::Hooks::EndOfScope::WithFallback is unable to accomodate
185callbacks throwing exception, due to the design of perl itself. Your
186entire application will terminate immediately using POSIX::_exit (this
187means nothing else beyond this point will execute, including any END
188blocks you may have defined. The callback originally defined around
189$_[0]->[0]{caller} terminated with the following error:
190
191$err
192EOE
193 }
194 }
195 }
196
197 sub on_scope_end (&) {
198 $^H |= 0x020000;
199
200 my $stack;
201 if(my $t = tied( %^H ) ) {
202 if ( (my $c = ref $t) ne 'B::Hooks::EndOfScope::WithFallback::_TieHintHash') {
203 die <<EOE;
204
205========================================================================
206 !!! F A T A L E R R O R !!!
207
208 foreign tie() of %^H detected
209========================================================================
210
211B::Hooks::EndOfScope::WithFallback is currently operating in pure-perl
212fallback mode, because your system is lacking the necessary dependency
213Variable::Magic $v_m_req
214In this mode B::Hooks::EndOfScope::WithFallback expects to be able to tie()
215the hinthash %^H, however it is apparently already tied by means unknown to
216the tie-class $c
217
218Since this is a no-win situation execution will abort here and now. Please
219try to find out which other module is relying on hinthash tie() ability,
220and file a bug for both the perpetrator and B::Hooks::EndOfScope::WithFallback
221so that the authors can figure out an acceptable way of moving forward.
222
223EOE
224 }
225 $stack = $t->[1];
226 }
227 else {
228 tie(
229 %^H,
230 'B::Hooks::EndOfScope::WithFallback::_TieHintHash',
231 ($stack = B::Hooks::EndOfScope::WithFallback::_ScopeGuardArray->new),
232 );
233 }
234
235 my ($f, @callsite);
236 do { @callsite = caller(++$f) }
237 while (@callsite and $callsite[1] =~ /\(eval.+\)/);
238
239 push @$stack, {
240 code => shift(),
241 caller => sprintf '%s line %s', @callsite[1,2]
242 };
243 }
244
245 1; # for the above `or die()`
246
247EOS
248}
249
250=head1 SEE ALSO
251
252L<B::Hooks::EndOfScope>
253
254=head1 AUTHOR
255
256ribasushi: Peter Rabbitson <ribasushi@cpan.org>
257
258=head1 CONTRIBUTORS
259
260None as of yet
261
262=head1 COPYRIGHT
263
264Copyright (c) 2011 the B::Hooks::EndOfScope::WithFalback L</AUTHOR> and L</CONTRIBUTORS>
265as listed above.
266
267=head1 LICENSE
268
269This library is free software and may be distributed under the same terms
270as perl itself.
271
272=cut
273
2741;