File Coverage

blib/lib/Sub/Quote.pm
Criterion Covered Total %
statement 197 197 100.0
branch 147 170 86.4
condition 49 70 70.0
subroutine 35 35 100.0
pod 8 8 100.0
total 436 480 90.8


line stmt bran cond sub pod time code
1             package Sub::Quote;
2              
3 5     5   128 sub _clean_eval { eval $_[0] }
  5     40   17  
  5     1   184  
  40     1   6044  
        1      
        1      
        1      
        1      
        1      
        1      
        1      
        1      
4              
5 10     10   290080 use strict;
  9         66  
  9         308  
6 10     10   55 use warnings;
  10         24  
  10         534  
7              
8             our $VERSION = '2.006008';
9             $VERSION =~ tr/_//d;
10              
11 10     10   2661 use Sub::Defer qw(defer_sub);
  9         20  
  9         493  
12 10     10   77 use Scalar::Util qw(weaken);
  10         20  
  10         449  
13 10     10   58 use Exporter ();
  10         18  
  10         455  
14 10     10   203 BEGIN { *import = \&Exporter::import }
15 11     10   48 use Carp qw(croak);
  11         157  
  10         473  
16 10     10   5038 BEGIN { our @CARP_NOT = qw(Sub::Defer) }
17             BEGIN {
18 11     10   90 my $TRUE = sub(){!!1};
19 11         155 my $FALSE = sub(){!!0};
20 10 50       163 *_HAVE_IS_UTF8 = defined &utf8::is_utf8 ? $TRUE : $FALSE;
21 10 50       65 *_CAN_TRACK_BOOLEANS = defined &builtin::is_bool ? $TRUE : $FALSE;
22 10 50       33 *_CAN_TRACK_NUMBERS = defined &builtin::created_as_number ? $TRUE : $FALSE;
23 10 100 66     299 *_HAVE_HEX_FLOAT = !$ENV{SUB_QUOTE_NO_HEX_FLOAT} && "$]" >= 5.022 ? $TRUE : $FALSE;
24              
25             # This may not be perfect, as we can't tell the format purely from the size
26             # but it should cover the common cases, and other formats are more likely to
27             # be less precise.
28 10         155 my $nvsize = 8 * length pack 'F', 0;
29 9 0       41 my $nvmantbits
    0          
    0          
    50          
    50          
    50          
30             = $nvsize == 16 ? 11
31             : $nvsize == 32 ? 24
32             : $nvsize == 64 ? 53
33             : $nvsize == 80 ? 64
34             : $nvsize == 128 ? 113
35             : $nvsize == 256 ? 237
36             : 237 # unknown float format
37             ;
38 9         84 my $precision = int( log(2)/log(10)*$nvmantbits );
39              
40 8         108 *_NVSIZE = sub(){ $nvsize };
  1         7  
41 8         36 *_NVMANTBITS = sub(){ $nvmantbits };
  1         103  
42 8         31 *_FLOAT_PRECISION = sub(){ $precision };
  1         3  
43              
44 8         67 local $@;
45             # if B is already loaded, just use its perlstring
46 8 50 33     102 if ("$]" >= 5.008_000 && "$]" != 5.010_000 && defined &B::perlstring) {
    0 33        
    0 0        
    0 0        
47 8         710 *_perlstring = \&B::perlstring;
48             }
49             # XString is smaller than B, so prefer to use it. Buggy until 0.003.
50 1         83 elsif (eval { require XString; XString->VERSION(0.003) }) {
  1         5  
51 1         3 *_perlstring = \&XString::perlstring;
52             }
53             # B::perlstring in perl 5.10 handles escaping incorrectly on utf8 strings
54             elsif ("$]" == 5.010_000) {
55 1         57 my %escape = (
56             (map +(chr($_) => sprintf '\x%02x', $_), 0 .. 0x31, 0x7f),
57             "\t" => "\\t",
58             "\n" => "\\n",
59             "\r" => "\\r",
60             "\f" => "\\f",
61             "\b" => "\\b",
62             "\a" => "\\a",
63             "\e" => "\\e",
64             (map +($_ => "\\$_"), qw(" \ $ @)),
65             );
66             *_perlstring = sub {
67 1         2 my $value = shift;
68 1         99 $value =~ s{(["\$\@\\[:cntrl:]]|[^\x00-\x7f])}{
69 1 0       5 $escape{$1} || sprintf('\x{%x}', ord($1))
70             }ge;
71 1         2 qq["$value"];
72 1         8 };
73             }
74 1         56 elsif ("$]" >= 5.008_000 && eval { require B; 1 } && defined &B::perlstring ) {
  1         7  
75 1         2 *_perlstring = \&B::perlstring;
76             }
77             # on perl 5.6, perlstring is not available. quotemeta will mostly serve as a
78             # replacement. it quotes just by adding lots of backslashes though. if a
79             # utf8 string was written out directly as bytes, it wouldn't get interpreted
80             # correctly if not under 'use utf8'. this is mostly a theoretical concern,
81             # but enough to stick with perlstring when possible.
82             else {
83 1         80 *_perlstring = sub { qq["\Q$_[0]\E"] };
  1         5  
84             }
85             }
86              
87             our @EXPORT = qw(quote_sub unquote_sub quoted_from_sub qsub);
88             our @EXPORT_OK = qw(quotify capture_unroll inlinify sanitize_identifier);
89              
90             our %QUOTED;
91              
92             sub quotify {
93 1875     1875 1 3422073 my $value = $_[0];
94 8     10   51 no warnings 'numeric';
  8         71  
  8         409  
95             BEGIN {
96 8     10   18276 warnings->unimport(qw(experimental::builtin))
97             if _CAN_TRACK_BOOLEANS || _CAN_TRACK_NUMBERS;
98             }
99             ! defined $value ? 'undef()'
100             : _CAN_TRACK_BOOLEANS && builtin::is_bool($value) ? (
101             $value ? '(!!1)' : '(!!0)'
102             )
103             # numeric detection
104             : (
105             _CAN_TRACK_NUMBERS
106             ? builtin::created_as_number($value)
107             : (
108             !(_HAVE_IS_UTF8 && utf8::is_utf8($value))
109             && length( (my $dummy = '') & $value )
110             && 0 + $value eq $value
111             )
112             ) ? (
113             $value != $value ? (
114             $value eq (9**9**9*0)
115             ? '(9**9**9*0)' # nan
116             : '(-(9**9**9*0))' # -nan
117             )
118             : $value == 9**9**9 ? '(9**9**9)' # inf
119             : $value == -9**9**9 ? '(-9**9**9)' # -inf
120             : $value == 0 ? (
121             sprintf('%g', $value) eq '-0' ? '-0.0' : '0',
122             )
123             : $value !~ /[e.]/i ? (
124             $value > 0 ? (sprintf '%u', $value)
125             : (sprintf '%d', $value)
126             )
127 1875 100 100     24817 : do {
    100 100        
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
128 180         404 my $float = $value;
129 180         788 my $max_factor = int( log( abs($value) ) / log(2) ) - _NVMANTBITS;
130 180 100       406 my $ex_sign = $max_factor > 0 ? 1 : -1;
131 180         460 FACTOR: for my $ex (0 .. abs($max_factor)) {
132 188         469 my $num = $value / 2**($ex_sign * $ex);
133 188         302 for my $precision (_FLOAT_PRECISION .. _FLOAT_PRECISION+2) {
134 289         1681 my $formatted = sprintf '%0.'.$precision.'g', $num;
135 289 100       713 $float = $formatted
136             if $ex == 0;
137 289 100       968 if ($formatted == $num) {
138 177 100       360 if ($ex) {
139 5 50       24 $float
    50          
140             = $formatted
141             . ($ex_sign == 1 ? '*' : '/')
142             . (
143             $ex > _NVMANTBITS
144             ? "2**$ex"
145             : sprintf('%u', 2**$ex)
146             );
147             }
148 177         539 last FACTOR;
149             }
150             }
151 16         46 if (_HAVE_HEX_FLOAT) {
152 97         303 $float = sprintf '%a', $value;
153 8         106 last FACTOR;
154             }
155             }
156 93         269 "$float";
157             }
158             )
159             : !_CAN_TRACK_BOOLEANS && !length($value) && length( (my $dummy2 = '') & $value ) ? '(!!0)' # false
160             : _perlstring($value);
161             }
162              
163             sub sanitize_identifier {
164 3     3 1 560 my $name = shift;
165 3         92 $name =~ s/([_\W])/sprintf('_%x', ord($1))/ge;
  6         23  
166 2         15 $name;
167             }
168              
169             sub capture_unroll {
170 43     44 1 1682 my ($from, $captures, $indent) = @_;
171             join(
172             '',
173             map {
174 43 100       122 /^([\@\%\$])/
  88         616  
175             or croak "capture key should start with \@, \% or \$: $_";
176 86         319 (' ' x $indent).qq{my ${_} = ${1}{${from}->{${\quotify $_}}};\n};
  86         160  
177             } keys %$captures
178             );
179             }
180              
181             sub inlinify {
182 9     10 1 10430 my ($code, $args, $extra, $local) = @_;
183 9 100       83 $args = '()'
184             if !defined $args;
185 9   100     33 my $do = 'do { '.($extra||'');
186 9 100       40 if ($code =~ s/^(\s*package\s+([a-zA-Z0-9:]+);)//) {
187 2         73 $do .= $1;
188             }
189 9 100 100     59 if ($code =~ s{
    100          
190             \A((?:\#\ BEGIN\ quote_sub\ PRELUDE\n.*?\#\ END\ quote_sub\ PRELUDE\n)?\s*)
191             (^\s*) my \s* \(([^)]+)\) \s* = \s* \@_;
192             }{}xms) {
193 4         16 my ($pre, $indent, $code_args) = ($1, $2, $3);
194 4         352 $do .= $pre;
195 4 100       834 if ($code_args ne $args) {
196 2         16 $do .= $indent . 'my ('.$code_args.') = ('.$args.'); ';
197             }
198             }
199             elsif ($local || $args ne '@_') {
200 3 100       10 $do .= ($local ? 'local ' : '').'@_ = ('.$args.'); ';
201             }
202 8         26 $do.$code.' }';
203             }
204              
205             sub quote_sub {
206             # HOLY DWIMMERY, BATMAN!
207             # $name => $code => \%captures => \%options
208             # $name => $code => \%captures
209             # $name => $code
210             # $code => \%captures => \%options
211             # $code
212 59 100 100 61 1 23661 my $options =
213             (ref($_[-1]) eq 'HASH' and ref($_[-2]) eq 'HASH')
214             ? pop
215             : {};
216 59 100       154 my $captures = ref($_[-1]) eq 'HASH' ? pop : undef;
217 59 100 100     181 undef($captures) if $captures && !keys %$captures;
218 59         133 my $code = pop;
219 59         95 my $name = $_[0];
220 59 100       122 if ($name) {
221 21         27 my $subname = $name;
222 21 100       179 my $package = $subname =~ s/(.*)::// ? $1 : caller;
223 21         65 $name = join '::', $package, $subname;
224 21 100       399 croak qq{package name "$package" too long!}
225             if length $package > 252;
226 19 100       289 croak qq{package name "$package" is not valid!}
227             unless $package =~ /^[^\d\W]\w*(?:::\w+)*$/;
228 17 100       131 croak qq{sub name "$subname" too long!}
229             if length $subname > 252;
230 16 100       235 croak qq{sub name "$subname" is not valid!}
231             unless $subname =~ /^[^\d\W]\w*$/;
232             }
233 52         314 my @caller = caller(0);
234 52         97 my ($attributes, $file, $line) = @{$options}{qw(attributes file line)};
  52         157  
235 52 100       117 if ($attributes) {
236             /\A\w+(?:\(.*\))?\z/s || croak "invalid attribute $_"
237 3   66     118 for @$attributes;
238             }
239             my $quoted_info = {
240             name => $name,
241             code => $code,
242             captures => $captures,
243             package => (exists $options->{package} ? $options->{package} : $caller[0]),
244             hints => (exists $options->{hints} ? $options->{hints} : $caller[8]),
245             warning_bits => (exists $options->{warning_bits} ? $options->{warning_bits} : $caller[9]),
246 51 100       446 hintshash => (exists $options->{hintshash} ? $options->{hintshash} : $caller[10]),
    100          
    100          
    100          
    100          
    100          
    100          
247             ($attributes ? (attributes => $attributes) : ()),
248             ($file ? (file => $file) : ()),
249             ($line ? (line => $line) : ()),
250             };
251 51         86 my $unquoted;
252 51         235 weaken($quoted_info->{unquoted} = \$unquoted);
253 51 100       106 if ($options->{no_defer}) {
254 4         6 my $fake = \my $var;
255 4         13 local $QUOTED{$fake} = $quoted_info;
256 4         9 my $sub = unquote_sub($fake);
257 4 100 100     20 Sub::Defer::_install_coderef($name, $sub) if $name && !$options->{no_install};
258 4         22 return $sub;
259             }
260             else {
261             my $deferred = defer_sub(
262             ($options->{no_install} ? undef : $name),
263             sub {
264 30     32   39 $unquoted if 0;
265 30         61 unquote_sub($quoted_info->{deferred});
266             },
267             {
268             ($attributes ? ( attributes => $attributes ) : ()),
269 47 100       310 ($name ? () : ( package => $quoted_info->{package} )),
    100          
    100          
270             },
271             );
272 47         144 weaken($quoted_info->{deferred} = $deferred);
273 47         152 weaken($QUOTED{$deferred} = $quoted_info);
274 47         226 return $deferred;
275             }
276             }
277              
278             sub _context {
279 47     49   91 my $info = shift;
280 47   66     138 $info->{context} ||= do {
281             my ($package, $hints, $warning_bits, $hintshash, $file, $line)
282 41         65 = @{$info}{qw(package hints warning_bits hintshash file line)};
  41         111  
283              
284 41 100 50     85 $line ||= 1
285             if $file;
286              
287 41         64 my $line_mark = '';
288 41 100       83 if ($line) {
289 2         5 $line_mark = "#line ".($line-1);
290 2 100       8 if ($file) {
291 1         3 $line_mark .= qq{ "$file"};
292             }
293 2         3 $line_mark .= "\n";
294             }
295              
296             $info->{context}
297             ="# BEGIN quote_sub PRELUDE\n"
298             ."package $package;\n"
299             ."BEGIN {\n"
300             ." \$^H = ".quotify($hints).";\n"
301             ." \${^WARNING_BITS} = ".quotify($warning_bits).";\n"
302             ." \%^H = (\n"
303             . join('', map
304             " ".quotify($_)." => ".quotify($hintshash->{$_}).",\n",
305 41   33     136 grep !(ref $hintshash->{$_} && $hintshash->{$_} =~ /\A(?:\w+(?:::\w+)*=)?[A-Z]+\(0x[[0-9a-fA-F]+\)\z/),
306             keys %$hintshash)
307             ." );\n"
308             ."}\n"
309             .$line_mark
310             ."# END quote_sub PRELUDE\n";
311             };
312             }
313              
314             sub quoted_from_sub {
315 10     15 1 463 my ($sub) = @_;
316 10 100 50     49 my $quoted_info = $QUOTED{$sub||''} or return undef;
317             my ($name, $code, $captures, $unquoted, $deferred)
318 8         19 = @{$quoted_info}{qw(name code captures unquoted deferred)};
  8         26  
319 8         17 $code = _context($quoted_info) . $code;
320 8   66     52 $unquoted &&= $$unquoted;
321 8 100 100     75 if (($deferred && $deferred eq $sub)
      66        
      100        
322             || ($unquoted && $unquoted eq $sub)) {
323 7         51 return [ $name, $code, $captures, $unquoted, $deferred ];
324             }
325 1         7 return undef;
326             }
327              
328             sub unquote_sub {
329 43     45 1 694 my ($sub) = @_;
330 43 100       135 my $quoted_info = $QUOTED{$sub} or return undef;
331 42         71 my $unquoted = $quoted_info->{unquoted};
332 42 100 66     155 unless ($unquoted && $$unquoted) {
333             my ($name, $code, $captures, $package, $attributes)
334 40         57 = @{$quoted_info}{qw(name code captures package attributes)};
  40         111  
335              
336 40 100       135 ($package, $name) = $name =~ /(.*)::(.*)/
337             if $name;
338              
339 40 100       91 my %captures = $captures ? %$captures : ();
340 40         73 $captures{'$_UNQUOTED'} = \$unquoted;
341 40         65 $captures{'$_QUOTED'} = \$quoted_info;
342              
343 40 100       86 my $make_sub
    100          
    100          
344             = "{\n"
345             . capture_unroll("\$_[1]", \%captures, 2)
346             . " package ${package};\n"
347             . (
348             $name
349             # disable the 'variable $x will not stay shared' warning since
350             # we're not letting it escape from this scope anyway so there's
351             # nothing trying to share it
352             ? " no warnings 'closure';\n sub ${name} "
353             : " \$\$_UNQUOTED = sub "
354             )
355             . ($attributes ? join('', map ":$_ ", @$attributes) : '') . "{\n"
356             . " (\$_QUOTED,\$_UNQUOTED) if 0;\n"
357             . _context($quoted_info)
358             . $code
359             . " }".($name ? "\n \$\$_UNQUOTED = \\&${name}" : '') . ";\n"
360             . "}\n"
361             . "1;\n";
362 39 100       139 if (my $debug = $ENV{SUB_QUOTE_DEBUG}) {
363 12 100       100 if ($debug =~ m{^([^\W\d]\w*(?:::\w+)*(?:::)?)$}) {
    100          
364 9         29 my $filter = $1;
365 9 100 50     55 my $match
    100 100        
366             = $filter =~ /::$/ ? $package.'::'
367             : $filter =~ /::/ ? $package.'::'.($name||'__ANON__')
368             : ($name||'__ANON__');
369 9 100       47 warn $make_sub
370             if $match eq $filter;
371             }
372             elsif ($debug =~ m{\A/(.*)/\z}s) {
373 2         5 my $filter = $1;
374 2 100       25 warn $make_sub
375             if $code =~ $filter;
376             }
377             else {
378 1         11 warn $make_sub;
379             }
380             }
381             {
382 8     8   137 no strict 'refs';
  8         19  
  8         2597  
  39         89  
383 39 100       72 local *{"${package}::${name}"} if $name;
  13         59  
384 39         96 my ($success, $e);
385             {
386 39         51 local $@;
  39         52  
387 39         102 $success = _clean_eval($make_sub, \%captures);
388 39         117 $e = $@;
389             }
390 39 100       103 unless ($success) {
391 2         12 my $space = length($make_sub =~ tr/\n//);
392 2         4 my $line = 0;
393 2         13 $make_sub =~ s/^/sprintf "%${space}d: ", ++$line/emg;
  39         113  
394 2         210 croak "Eval went very, very wrong:\n\n${make_sub}\n\n$e";
395             }
396 37         249 weaken($QUOTED{$$unquoted} = $quoted_info);
397             }
398             }
399 39         144 $$unquoted;
400             }
401              
402             sub qsub ($) {
403 1     2 1 555 goto "e_sub;
404             }
405              
406             sub CLONE {
407 5     6   43 my @quoted = map { defined $_ ? (
408 2         10 $_->{unquoted} && ${$_->{unquoted}} ? (${ $_->{unquoted} } => $_) : (),
409 8 100 100     26 $_->{deferred} ? ($_->{deferred} => $_) : (),
    100          
    100          
410             ) : () } values %QUOTED;
411 5         16 %QUOTED = @quoted;
412 5         21 weaken($_) for values %QUOTED;
413             }
414              
415             1;
416             __END__