File Coverage

blib/lib/Sub/Quote.pm
Criterion Covered Total %
statement 197 197 100.0
branch 144 172 83.7
condition 43 64 67.1
subroutine 35 35 100.0
pod 8 8 100.0
total 427 476 89.7


line stmt bran cond sub pod time code
1             package Sub::Quote;
2              
3 5     5   65 sub _clean_eval { eval $_[0] }
  5     40   40  
  5     1   193  
  40     1   6448  
        1      
        1      
        1      
        1      
        1      
        1      
        1      
        1      
4              
5 10     10   738413 use strict;
  9         25  
  9         428  
6 10     10   121 use warnings;
  10         22  
  10         814  
7              
8             our $VERSION = '2.006009';
9             $VERSION =~ tr/_//d;
10              
11 10     10   3071 use Sub::Defer qw(defer_sub);
  9         25  
  9         688  
12 10     10   88 use Scalar::Util qw(weaken);
  10         19  
  10         497  
13 10     10   56 use Exporter ();
  10         21  
  10         502  
14 10     10   230 BEGIN { *import = \&Exporter::import }
15 11     10   49 use Carp qw(croak);
  11         122  
  10         681  
16 10     10   6842 BEGIN { our @CARP_NOT = qw(Sub::Defer) }
17             BEGIN {
18 11     10   90 my $TRUE = sub(){!!1};
19 11         112 my $FALSE = sub(){!!0};
20 10 50       189 *_HAVE_IS_UTF8 = defined &utf8::is_utf8 ? $TRUE : $FALSE;
21 10 50       62 *_CAN_TRACK_BOOLEANS = defined &builtin::is_bool ? $TRUE : $FALSE;
22 10 50       38 *_CAN_TRACK_NUMBERS = defined &builtin::created_as_number ? $TRUE : $FALSE;
23 10 100 66     216 *_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         147 my $nvsize = 8 * length pack 'F', 0;
29 9 0       50 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         64 my $precision = int( log(2)/log(10)*$nvmantbits );
39              
40 8         97 *_NVSIZE = sub(){ $nvsize };
  1         6  
41 8         43 *_NVMANTBITS = sub(){ $nvmantbits };
  1         70  
42 8         31 *_FLOAT_PRECISION = sub(){ $precision };
  1         2  
43              
44 8         44 local $@;
45             # if B is already loaded, just use its perlstring
46 8 50 33     220 if ("$]" >= 5.008_000 && "$]" != 5.010_000 && defined &B::perlstring) {
    0 33        
    0 0        
    0 0        
47 8         907 *_perlstring = \&B::perlstring;
48             }
49             # XString is smaller than B, so prefer to use it. Buggy until 0.003.
50 1         72 elsif (eval { require XString; XString->VERSION(0.003) }) {
  1         3  
51 1         2 *_perlstring = \&XString::perlstring;
52             }
53             # B::perlstring in perl 5.10 handles escaping incorrectly on utf8 strings
54             elsif ("$]" == 5.010_000) {
55 1         31 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         75 $value =~ s{(["\$\@\\[:cntrl:]]|[^\x00-\x7f])}{
69 1 0       5 $escape{$1} || sprintf('\x{%x}', ord($1))
70             }ge;
71 1         4 qq["$value"];
72 1         7 };
73             }
74 1         52 elsif ("$]" >= 5.008_000 && eval { require B; 1 } && defined &B::perlstring ) {
  1         9  
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         86 *_perlstring = sub { qq["\Q$_[0]\E"] };
  1         3  
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 1973     1973 1 5579261 my $value = $_[0];
94 8     10   60 no warnings 'numeric';
  8         41  
  8         718  
95             BEGIN {
96 8     10   42432 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 1973 100       20001 : do {
    100          
    50          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
128 104         302 my $float = $value;
129 104         520 my $max_factor = int( log( abs($value) ) / log(2) ) - _NVMANTBITS;
130 104 100       363 my $ex_sign = $max_factor > 0 ? 1 : -1;
131 104         365 FACTOR: for my $ex (0 .. abs($max_factor)) {
132 104         338 my $num = $value / 2**($ex_sign * $ex);
133 104         231 for my $precision (_FLOAT_PRECISION .. _FLOAT_PRECISION+2) {
134 180         1131 my $formatted = sprintf '%0.'.$precision.'g', $num;
135 180 50       631 $float = $formatted
136             if $ex == 0;
137 180 100       957 if ($formatted == $num) {
138 104 50       291 if ($ex) {
139 2 0       2 $float
    0          
140             = $formatted
141             . ($ex_sign == 1 ? '*' : '/')
142             . (
143             $ex > _NVMANTBITS
144             ? "2**$ex"
145             : sprintf('%u', 2**$ex)
146             );
147             }
148 104         414 last FACTOR;
149             }
150             }
151 5         11 if (_HAVE_HEX_FLOAT) {
152 56         188 $float = sprintf '%a', $value;
153 5         73 last FACTOR;
154             }
155             }
156 55         221 "$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 635 my $name = shift;
165 3         121 $name =~ s/([_\W])/sprintf('_%x', ord($1))/ge;
  6         19  
166 2         5 $name;
167             }
168              
169             sub capture_unroll {
170 43     44 1 200545 my ($from, $captures, $indent) = @_;
171             join(
172             '',
173             map {
174 43 100       144 /^([\@\%\$])/
  88         661  
175             or croak "capture key should start with \@, \% or \$: $_";
176 86         202 (' ' x $indent).qq{my ${_} = ${1}{${from}->{${\quotify $_}}};\n};
  86         134  
177             } keys %$captures
178             );
179             }
180              
181             sub inlinify {
182 9     10 1 15985 my ($code, $args, $extra, $local) = @_;
183 9 100       73 $args = '()'
184             if !defined $args;
185 9   100     33 my $do = 'do { '.($extra||'');
186 9 100       47 if ($code =~ s/^(\s*package\s+([a-zA-Z0-9:]+);)//) {
187 2         120 $do .= $1;
188             }
189 9 100 100     71 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         19 my ($pre, $indent, $code_args) = ($1, $2, $3);
194 4         445 $do .= $pre;
195 4 100       1094 if ($code_args ne $args) {
196 2         8 $do .= $indent . 'my ('.$code_args.') = ('.$args.'); ';
197             }
198             }
199             elsif ($local || $args ne '@_') {
200 3 100       10 $do .= ($local ? 'local ' : '').'@_ = ('.$args.'); ';
201             }
202 8         33 $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 649927 my $options =
213             (ref($_[-1]) eq 'HASH' and ref($_[-2]) eq 'HASH')
214             ? pop
215             : {};
216 59 100       143 my $captures = ref($_[-1]) eq 'HASH' ? pop : undef;
217 59 100 100     216 undef($captures) if $captures && !keys %$captures;
218 59         125 my $code = pop;
219 59         102 my $name = $_[0];
220 59 100       147 if ($name) {
221 21         26 my $subname = $name;
222 21 100       179 my $package = $subname =~ s/(.*)::// ? $1 : caller;
223 21         53 $name = join '::', $package, $subname;
224 21 100       418 croak qq{package name "$package" too long!}
225             if length $package > 252;
226 19 100       303 croak qq{package name "$package" is not valid!}
227             unless $package =~ /^[^\d\W]\w*(?:::\w+)*$/;
228 17 100       124 croak qq{sub name "$subname" too long!}
229             if length $subname > 252;
230 16 100       229 croak qq{sub name "$subname" is not valid!}
231             unless $subname =~ /^[^\d\W]\w*$/;
232             }
233 52         360 my @caller = caller(0);
234 52         96 my ($attributes, $file, $line) = @{$options}{qw(attributes file line)};
  52         154  
235 52 100       120 if ($attributes) {
236             /\A\w+(?:\(.*\))?\z/s || croak "invalid attribute $_"
237 3   66     242 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       515 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         74 my $unquoted;
252 51         153 weaken($quoted_info->{unquoted} = \$unquoted);
253 51 100       131 if ($options->{no_defer}) {
254 4         5 my $fake = \my $var;
255 4         19 local $QUOTED{$fake} = $quoted_info;
256 4         7 my $sub = unquote_sub($fake);
257 4 100 100     22 Sub::Defer::_install_coderef($name, $sub) if $name && !$options->{no_install};
258 4         24 return $sub;
259             }
260             else {
261             my $deferred = defer_sub(
262             ($options->{no_install} ? undef : $name),
263             sub {
264 30     32   36 $unquoted if 0;
265 30         66 unquote_sub($quoted_info->{deferred});
266             },
267             {
268             ($attributes ? ( attributes => $attributes ) : ()),
269 47 100       442 ($name ? () : ( package => $quoted_info->{package} )),
    100          
    100          
270             },
271             );
272 47         121 weaken($quoted_info->{deferred} = $deferred);
273 47         99 weaken($QUOTED{$deferred} = $quoted_info);
274 47         247 return $deferred;
275             }
276             }
277              
278             sub _context {
279 47     49   109 my $info = shift;
280 47   66     152 $info->{context} ||= do {
281             my ($package, $hints, $warning_bits, $hintshash, $file, $line)
282 41         57 = @{$info}{qw(package hints warning_bits hintshash file line)};
  41         133  
283              
284 41 100 50     81 $line ||= 1
285             if $file;
286              
287 41         50 my $line_mark = '';
288 41 100       89 if ($line) {
289 2         6 $line_mark = "#line ".($line-1);
290 2 100       7 if ($file) {
291 1         3 $line_mark .= qq{ "$file"};
292             }
293 2         5 $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     76 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 393 my ($sub) = @_;
316 10 100 50     57 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         48  
319 8         21 $code = _context($quoted_info) . $code;
320 8   66     46 $unquoted &&= $$unquoted;
321 8 100 100     138 if (($deferred && $deferred eq $sub)
      66        
      100        
322             || ($unquoted && $unquoted eq $sub)) {
323 7         65 return [ $name, $code, $captures, $unquoted, $deferred ];
324             }
325 1         3 return undef;
326             }
327              
328             sub unquote_sub {
329 43     45 1 1162 my ($sub) = @_;
330 43 100       138 my $quoted_info = $QUOTED{$sub} or return undef;
331 42         92 my $unquoted = $quoted_info->{unquoted};
332 42 100 66     134 unless ($unquoted && $$unquoted) {
333             my ($name, $code, $captures, $package, $attributes)
334 40         62 = @{$quoted_info}{qw(name code captures package attributes)};
  40         123  
335              
336 40 100       129 ($package, $name) = $name =~ /(.*)::(.*)/
337             if $name;
338              
339 40 100       89 my %captures = $captures ? %$captures : ();
340 40         75 $captures{'$_UNQUOTED'} = \$unquoted;
341 40         70 $captures{'$_QUOTED'} = \$quoted_info;
342              
343 40 100       89 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       137 if (my $debug = $ENV{SUB_QUOTE_DEBUG}) {
363 12 100       69 if ($debug =~ m{^([^\W\d]\w*(?:::\w+)*(?:::)?)$}) {
    100          
364 9         28 my $filter = $1;
365 9 100 50     41 my $match
    100 100        
366             = $filter =~ /::$/ ? $package.'::'
367             : $filter =~ /::/ ? $package.'::'.($name||'__ANON__')
368             : ($name||'__ANON__');
369 9 100       36 warn $make_sub
370             if $match eq $filter;
371             }
372             elsif ($debug =~ m{\A/(.*)/\z}s) {
373 2         4 my $filter = $1;
374 2 100       30 warn $make_sub
375             if $code =~ $filter;
376             }
377             else {
378 1         16 warn $make_sub;
379             }
380             }
381             {
382 8     8   180 no strict 'refs';
  8         21  
  8         3579  
  39         80  
383 39 100       68 local *{"${package}::${name}"} if $name;
  13         47  
384 39         66 my ($success, $e);
385             {
386 39         42 local $@;
  39         52  
387 39         85 $success = _clean_eval($make_sub, \%captures);
388 39         819 $e = $@;
389             }
390 39 100       101 unless ($success) {
391 2         8 my $space = length($make_sub =~ tr/\n//);
392 2         41 my $line = 0;
393 2         11 $make_sub =~ s/^/sprintf "%${space}d: ", ++$line/emg;
  39         96  
394 2         334 croak "Eval went very, very wrong:\n\n${make_sub}\n\n$e";
395             }
396 37         200 weaken($QUOTED{$$unquoted} = $quoted_info);
397             }
398             }
399 39         167 $$unquoted;
400             }
401              
402             sub qsub ($) {
403 1     2 1 686 goto "e_sub;
404             }
405              
406             sub CLONE {
407 5     6   56 my @quoted = map { defined $_ ? (
408 2         11 $_->{unquoted} && ${$_->{unquoted}} ? (${ $_->{unquoted} } => $_) : (),
409 8 100 100     36 $_->{deferred} ? ($_->{deferred} => $_) : (),
    100          
    100          
410             ) : () } values %QUOTED;
411 5         22 %QUOTED = @quoted;
412 5         24 weaken($_) for values %QUOTED;
413             }
414              
415             1;
416             __END__