File Coverage

blib/lib/Sub/Quote.pm
Criterion Covered Total %
statement 185 185 100.0
branch 150 164 91.4
condition 49 61 80.3
subroutine 35 35 100.0
pod 8 8 100.0
total 427 453 94.2


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