File Coverage

blib/lib/XS/Install/ParseXS.pm
Criterion Covered Total %
statement 48 292 16.4
branch 0 132 0.0
condition 1 70 1.4
subroutine 15 32 46.8
pod 0 11 0.0
total 64 537 11.9


line stmt bran cond sub pod time code
1             package # hide from CPAN indexer
2             XS::Install::ParseXS;
3 1     1   105534 use strict;
  1         14  
  1         35  
4 1     1   6 use warnings;
  1         2  
  1         35  
5 1     1   6 use feature 'state';
  1         2  
  1         140  
6 1     1   8 no warnings 'redefine';
  1         1  
  1         44  
7 1     1   6 use ExtUtils::ParseXS;
  1         2  
  1         32  
8 1     1   6 use ExtUtils::ParseXS::Eval;
  1         2  
  1         20  
9 1     1   5 use ExtUtils::ParseXS::Utilities;
  1         2  
  1         33  
10 1     1   565 use ExtUtils::Typemaps;
  1         8098  
  1         34  
11 1     1   7 use ExtUtils::Typemaps::InputMap;
  1         2  
  1         22  
12 1     1   7 use ExtUtils::Typemaps::OutputMap;
  1         2  
  1         3756  
13              
14             my (@pre_callbacks, @no_typemap_callbacks);
15             our ($top_typemaps, $cur_typemaps);
16             our $cplus = grep { /-C\+\+/ } @ARGV;
17             my $re_quot1 = qr/"(?:[^"\\]+|\\.)*"/;
18             my $re_quot2 = qr/'(?:[^'\\]+|\\.)*'/;
19             my $re_quot = qr/(?:$re_quot1|$re_quot2)/;
20             my $re_comment_single = qr#//[^\n]*\n#;
21             my $re_comment_multi = qr#/\*.*?\*/#ms;
22             my $re_ignored = qr/(?:$re_quot|$re_comment_single|$re_comment_multi)/ms;
23             my $re_braces = qr#(?\{(?>[^/"'{}]+|$re_ignored|(?&braces)|/)*\})#ms;
24             our $re_xsub = qr/(XS_EUPXS\(XS_[a-zA-Z0-9_]+\))[^{]+($re_braces)/ms;
25             our $re_boot = qr/(XS_EXTERNAL\(boot_[a-zA-Z0-9_]+\))[^{]+($re_braces)/ms;
26              
27 0     0 0 0 sub add_pre_callback { push @pre_callbacks, shift; }
28 0     0 0 0 sub add_post_callback { push @CatchEnd::post_callbacks, shift; }
29 0     0 0 0 sub add_no_typemap_callback { push @no_typemap_callbacks, shift; }
30              
31             sub call {
32 1     1 0 4 my ($cbs, @args) = @_;
33 1         3 $_->(@args) for @$cbs;
34             }
35              
36             sub code_start_idx {
37 0     0 0   my $lines = shift;
38 0           my $idx;
39 0           for (my $i = 2; $i < @$lines; ++$i) {
40 0 0         return $i+1 if $lines->[$i] =~ /^(PP)?CODE\s*:/;
41             }
42 0           die "code start not found";
43             }
44              
45             sub code_end_idx {
46 0     0 0   my $lines = shift;
47 0           my $idx = code_start_idx($lines);
48 0           for (; $idx < @$lines; ++$idx) {
49 0 0         return $idx if $lines->[$idx] =~ /^[a-zA-Z0-9]+\s*:/;
50             }
51 0           return $idx;
52             }
53              
54             sub is_empty {
55 0     0 0   my $lines = shift;
56 0           return code_start_idx($lines) == code_end_idx($lines);
57             }
58              
59             sub insert_code_top {
60 0     0 0   my ($parser, $code) = @_;
61 0           my $lines = $parser->{line};
62 0           my $linno = $parser->{line_no};
63 0           my $idx = code_start_idx($lines);
64 0           splice(@$lines, $idx, 0, $code);
65 0   0       splice(@$linno, $idx, 0, $linno->[$idx] // $linno->[-1]);
66             }
67              
68             sub insert_code_bottom {
69 0     0 0   my ($parser, $code) = @_;
70 0           my $lines = $parser->{line};
71 0           my $linno = $parser->{line_no};
72 0           my $idx = code_end_idx($lines);
73 0           splice(@$lines, $idx, 0, $code);
74 0   0       splice(@$linno, $idx, 0, $linno->[$idx] // $linno->[-1]);
75             }
76              
77             my $orig_pmxl = \&ExtUtils::ParseXS::_process_module_xs_line;
78             *ExtUtils::ParseXS::_process_module_xs_line = sub {
79 0     0     my ($self, $module, $pkg, $prefix) = @_;
80 0           $orig_pmxl->(@_);
81 0           $self->{xsi}{module} = $module;
82 0           $self->{xsi}{inline_mode} = 0;
83             };
84              
85             sub get_mode {
86 0 0   0 0   return '' unless $_[0] =~ /^MODE\s*:\s*(\w+)\s*$/;
87 0           return uc($1);
88             }
89              
90             # pre process XS function
91             my $orig_fetch_para = \&ExtUtils::ParseXS::fetch_para;
92             *ExtUtils::ParseXS::fetch_para = sub {
93 0     0     my $self = shift;
94 0           my $ret = $orig_fetch_para->($self, @_);
95 0           my $lines = $self->{line};
96 0           my $linno = $self->{line_no};
97 0 0         return $ret unless @$lines;
98            
99 0 0         if (get_mode($lines->[0]) eq 'INLINE') {
100 0           $self->{xsi}{inline_mode} = 1;
101 0           shift @$lines;
102 0           shift @$linno;
103             }
104            
105 0 0         if ($self->{xsi}{inline_mode}) {
106 0           while (@$lines) {
107 0           my $line = shift @$lines;
108 0           shift @$linno;
109 0 0         if (get_mode($line) eq 'XS') {
110 0           $self->{xsi}{inline_mode} = 0;
111 0           last;
112             }
113 0           print "$line\n";
114             }
115 0 0         return $ret unless @$lines;
116             }
117            
118             # concat 2 lines codes (functions with default behaviour) to make it preprocessed like C-like synopsis
119 0 0         if (@$lines == 2) {
120 0           $lines->[0] .= ' '.$lines->[1];
121 0           splice(@$lines, 1, 1);
122 0           splice(@$linno, 1, 1);
123             }
124            
125 0 0 0       if ($lines->[0] and $lines->[0] =~ /^([A-Z]+)\s*\{/) {
126 0           $lines->[0] = "$1:";
127 0 0         if ($lines->[-1] =~ /^\}/) { pop @$lines; pop @$linno; }
  0            
  0            
128             }
129            
130 0           my %attrs;
131            
132 0 0 0       if ($lines->[0] and $lines->[0] =~ /^(.+?)\s+([^\s()]+\s*(\((?:[^()]|(?3))*\)))\s*(.*)/) {
133 0           my ($type, $sig, $rest) = ($1, $2, $4);
134 0           shift @$lines;
135 0           my $deflinno = shift @$linno;
136            
137 0           my $remove_closing;
138 0 0 0       if ((my $idx = index($rest, '{')) >= 0) { # move following text on next line
    0          
139 0           $remove_closing = 1;
140 0           my $content = substr($rest, $idx+1);
141 0           substr($rest, $idx) = '';
142 0 0         if ($content =~ /\S/) {
143 0           unshift @$lines, $content;
144 0           unshift @$linno, $deflinno;
145             }
146             } elsif ($lines->[0] and $lines->[0] =~ s/^\s*\{//) { # '{' on next line
147 0           $remove_closing = 1;
148 0 0         if ($lines->[0] !~ /\S/) { # nothing remains, delete entire line
149 0           shift @$lines;
150 0           shift @$linno;
151             }
152             }
153              
154 0 0         if ($remove_closing) {
155 0           $lines->[-1] =~ s/}\s*;?\s*$//;
156 0 0         if ($lines->[-1] !~ /\S/) { pop @$lines; pop @$linno; }
  0            
  0            
157            
158 0 0 0       if (!$lines->[0] or $lines->[0] !~ /\S/) { # no code remains, but body was present ({}), add empty code to prevent default behaviour
159 0           $lines->[0] = ' ';
160 0   0       $linno->[0] ||= $deflinno;
161             }
162             }
163            
164 0 0         if (length $lines->[0]) {
165 0 0         unshift @$lines, $type =~ /^void(\s|$)/ ? 'PPCODE:' : 'CODE:';
166 0           unshift @$linno, $deflinno;
167             }
168            
169 0 0         if ($rest =~ /:(.+)/) {
170 0           my $attrs_str = $1;
171 0           %attrs = ($attrs_str =~ /\s*([A-Za-z]+)\s*(?:\(([^()]*)\)|)\s*/g);
172             }
173            
174 0           while (my ($attr, $val) = each %attrs) {
175 0           $attr = uc($attr);
176 0 0 0       if ($attr eq 'ALIAS' && (my @alias = split /\s*,\s*/, $val)) {
    0          
    0          
177 0           foreach my $alias_entry (reverse @alias) {
178 0           unshift @$lines, " $alias_entry";
179 0           unshift @$linno, $deflinno;
180             }
181 0           unshift @$lines, 'ALIAS:';
182 0           unshift @$linno, $deflinno;
183             }
184 0           elsif ($attr eq 'CONST') { next }
185             elsif (defined $val) {
186 0           unshift @$lines, "$attr: $val";
187 0           unshift @$linno, $deflinno;
188             }
189             }
190              
191 0           unshift @$lines, $sig;
192 0           unshift @$lines, $type;
193 0           unshift @$linno, $deflinno for 1..2;
194             }
195            
196             # make BOOT's code in personal scope
197 0 0         if ($lines->[0] =~ /^BOOT\s*:/) {
198 0           splice(@$lines, 1, 0, " {");
199 0           splice(@$linno, 1, 0, $linno->[0]);
200 0           push @$lines, " }";
201 0           push @$linno, $linno->[-1];
202             }
203            
204             map {
205 0           s/\b__PACKAGE__\b/"$self->{Package}"/g;
  0            
206 0           s/\b__MODULE__\b/"$self->{xsi}{module}"/g;
207             } @$lines;
208            
209 0 0         my $out_type = $lines->[0] or return $ret;
210             # filter out junk, because first line might be "BOOT:", "PROTOTYPES: ...", "INCLUDE: ...", "#ifdef", etc
211 0 0 0       return $ret if !$out_type or $out_type =~ /^#/ or $out_type =~ /^[_A-Z]+\s*:([^:]|$)/;
      0        
212              
213             # parse signature -> $func and @args
214 0           my $sig = $lines->[1];
215 0 0         $sig =~ /^([^(]+)\((.*)\)\s*$/ or die "bad signature: '$sig', at $self->{filepathname}, function $self->{pname}";
216 0           my $func = $1;
217 0           my $args_str = $2;
218 0           $func =~ s/^\s+//; $func =~ s/\s+$//;
  0            
219 0           my @args;
220             my $variadic;
221 0           for my $str (split /\s*,\s*/, $args_str) {
222 0           my %info;
223 0 0         $info{default} = $1 if $str =~ s/\s*=\s*(.+)$//;
224 0           $info{name} = '';
225 0 0         $info{name} = $1 if $str =~ s/([a-zA-Z0-9_\$]+)\s*$//;
226 0           $info{type} = $str;
227 0 0         if ($str eq '...') {
228 0           $variadic = 1;
229 0           next;
230             }
231 0 0         if (!$info{type}) { # arg with no name
232 0           $info{type} = $info{name};
233 0           $info{name} = '';
234             }
235            
236 0           map { s/^\s+//; s/\s+$// } values %info;
  0            
  0            
237 0           push @args, \%info;
238             }
239            
240 0 0         if ($func =~ s/^(.+):://) { # replace 'Class::meth' with 'meth(Class* THIS)'
241 0 0         unshift @args, $func eq 'new' ? {name => 'CLASS', type => 'SV*', orig_type => $1} :
242             {name => 'THIS', type => "$1*"};
243             }
244            
245 0           my $first_arg = $args[0];
246 0 0 0       $first_arg->{type} = 'const '.$first_arg->{type} if exists($attrs{const}) or exists($attrs{CONST});
247 0   0       my $is_method = $first_arg && $first_arg->{name} eq 'THIS';
248            
249 0           my $para = join("\n", @$lines);
250            
251 0 0         if ($para !~ /^(PP)?CODE\s*:/m) { # empty function, replace with $func(@args) or $first_arg->$func(@rest_args)
252 0           my $void = $out_type =~ /^void(?:\s|$)/;
253 0 0         push @$lines, $void ? 'PPCODE:' : 'CODE:';
254 0           push @$linno, $linno->[-1];
255 0 0 0       if ($func ne 'new' and ($func ne 'DESTROY' or !$is_method)) {
      0        
256 0           my $code = '';
257 0           my @real_args = @args;
258 0 0         if ($is_method) {
259 0           shift @real_args;
260 0           $code = $first_arg->{name}.'->';
261             }
262 0           $code .= $func.'('.join(', ', map { $_->{name} } @real_args).')';
  0            
263 0 0         $code = "RETVAL = $code" unless $void;
264 0           push @$lines, " $code;";
265 0           push @$linno, $linno->[-1];
266             }
267 0           $para = join("\n", @$lines);
268             }
269            
270 0 0 0       if ($para =~ /^CODE\s*:/m and $para !~ /^OUTPUT\s*:/m) { # add OUTPUT:RETVAL unless any
271 0           push @$lines, 'OUTPUT:', ' RETVAL';
272 0           push @$linno, $linno->[-1] for 1..2;
273 0           $para = join("\n", @$lines);
274             }
275            
276 0           my $cb_args = {
277             ret => $out_type,
278             func => $func,
279             args => \@args,
280             variadic => $variadic,
281             };
282 0           call(\@pre_callbacks, $self, $cb_args);
283            
284             # form final signature for ParseXS
285 0 0         my @args_lines = map { "$_->{type} $_->{name}".(defined($_->{default}) ? " = $_->{default}" : '') } @args;
  0            
286 0 0         push @args_lines, '...' if $variadic;
287 0           $sig = $func.' ('.join(', ', @args_lines).')';
288            
289 0           $lines->[0] = $out_type;
290 0           $lines->[1] = $sig;
291            
292 0 0         if (is_empty($lines)) {
293 0 0 0       if ($func eq 'DESTROY' and $is_method) {
    0          
294 0           insert_code_top($self, " delete THIS;");
295             }
296             elsif ($func eq 'new') {
297 0           insert_code_top($self, " RETVAL = ".default_constructor($out_type, \@args).';');
298             }
299             }
300            
301 0           return $ret;
302             };
303              
304             sub default_constructor {
305 0     0 0   my ($ret_type, $args) = @_;
306 0           my @pass_args = @$args;
307 0           my $fa = shift @pass_args;
308 0           my $args_str = join(', ', map { $_->{name} } @pass_args);
  0            
309 0           my $new_type = $fa->{orig_type};
310 0 0         unless ($new_type) {
311 0           $new_type = $ret_type;
312 0           $new_type =~ s/\s*\*$//;
313             }
314 0           my $ret = "new $new_type($args_str)";
315            
316 0 0         $ret = "$ret_type($ret)" unless $ret_type =~ /\*$/;
317            
318 0           return $ret;
319             }
320              
321             {
322             my $orig_merge = \&ExtUtils::Typemaps::merge;
323             my $orig_parse = \&ExtUtils::Typemaps::_parse;
324             my $orig_get = \&ExtUtils::Typemaps::get_typemap;
325            
326             *ExtUtils::Typemaps::get_typemap = sub {
327 0     0     my $ret = $orig_get->(@_);
328 0 0         return $ret if $ret;
329 0           call(\@no_typemap_callbacks, @_);
330 0           return $orig_get->(@_);
331             };
332            
333             *ExtUtils::Typemaps::merge = sub {
334 0     0     $top_typemaps = $_[0];
335 0           return $orig_merge->(@_);
336             };
337            
338             *ExtUtils::Typemaps::_parse = sub {
339 0     0     local $cur_typemaps = $_[0];
340 0           return $orig_parse->(@_);
341             };
342             }
343              
344             {
345             # remove ugly default behaviour, it always overrides typemaps in xsubpp's command line
346             *ExtUtils::ParseXS::Utilities::standard_typemap_locations = sub {
347 0     0     my $inc = shift;
348 0           my @ret;
349 0 0         push @ret, 'typemap' if -e 'typemap';
350 0           return @ret;
351             };
352             }
353              
354             {
355             package # hide from CPAN
356             CatchEnd;
357 1     1   10 use strict;
  1         2  
  1         26  
358 1     1   5 use feature 'say';
  1         3  
  1         1265  
359            
360             our @post_callbacks;
361              
362             my ($out, $orig_stdout);
363             open $orig_stdout, '>&', STDOUT;
364             close STDOUT;
365 1     1   8 open STDOUT, '>', \$out or die $!; # This shouldn't fail
  1         1  
  1         9  
366            
367             #post-process XS out
368             sub END {
369 1   50 1   954 $out //= '';
370 1         4 select $orig_stdout;
371              
372 1         3 $out =~ s/^MODE\s*:.+//mg;
373            
374             # remove XS function C-prototype (it causes warnings on many compilers)
375 1         3 $out =~ s/XS_EUPXS\(XS_[a-zA-Z0-9_]+\);.*\n/\n/mg;
376            
377             # remove XS BOOT function C-prototype
378 1         2 $out =~ s/XS_EXTERNAL\(boot_[a-zA-Z0-9_]+\);.*\n/\n/mg;
379              
380 1         5 XS::Install::ParseXS::call(\@post_callbacks, \$out);
381 1         23 print $out;
382             }
383             }
384              
385             {
386             package #hide from CPAN
387             ExtUtils::ParseXS;
388             my $END = "!End!\n\n";
389             my $BLOCK_regexp = '\s*(' . $ExtUtils::ParseXS::Constants::XSKeywordsAlternation . "|$END)\\s*:";
390             # copy-paste from ExtUtils::ParseXS to fix Typemaps with references (&). ParseXS was simply removing it from type
391             # only one line changed with regexp
392             # my ($var_type, $var_addr, $var_name) = /^(.*?[^&\s])\s*(\&?)\s*\b(\w+)$/s
393             *INPUT_handler = sub {
394 0     0     my $self = shift;
395 0           $_ = shift;
396 0           for (; !/^$BLOCK_regexp/o; $_ = shift(@{ $self->{line} })) {
  0            
397 0 0         last if /^\s*NOT_IMPLEMENTED_YET/;
398 0 0         next unless /\S/; # skip blank lines
399            
400 0           trim_whitespace($_);
401 0           my $ln = $_;
402            
403             # remove trailing semicolon if no initialisation
404 0 0         s/\s*;$//g unless /[=;+].*\S/;
405            
406             # Process the length(foo) declarations
407 0 0         if (s/^([^=]*)\blength\(\s*(\w+)\s*\)\s*$/$1 XSauto_length_of_$2=NO_INIT/x) {
408 0           print "\tSTRLEN\tSTRLEN_length_of_$2;\n";
409 0           $self->{lengthof}->{$2} = undef;
410 0           $self->{deferred} .= "\n\tXSauto_length_of_$2 = STRLEN_length_of_$2;\n";
411             }
412            
413             # check for optional initialisation code
414 0           my $var_init = '';
415 0 0         $var_init = $1 if s/\s*([=;+].*)$//s;
416 0           $var_init =~ s/"/\\"/g;
417             # *sigh* It's valid to supply explicit input typemaps in the argument list...
418 0           my $is_overridden_typemap = $var_init =~ /ST\s*\(|\$arg\b/;
419            
420 0           s/\s+/ /g;
421 0           my $var_addr = '';
422 0 0         my ($var_type, $var_name) = /^(.*?[^\s])\s*\b(\w+)$/s
423             or $self->blurt("Error: invalid argument declaration '$ln'"), next;
424            
425             # Check for duplicate definitions
426             $self->blurt("Error: duplicate definition of argument '$var_name' ignored"), next
427             if $self->{arg_list}->{$var_name}++
428 0 0 0       or defined $self->{argtype_seen}->{$var_name} and not $self->{processing_arg_with_types};
      0        
429            
430 0           $self->{thisdone} |= $var_name eq "THIS";
431 0           $self->{retvaldone} |= $var_name eq "RETVAL";
432 0           $self->{var_types}->{$var_name} = $var_type;
433             # XXXX This check is a safeguard against the unfinished conversion of
434             # generate_init(). When generate_init() is fixed,
435             # one can use 2-args map_type() unconditionally.
436 0           my $printed_name;
437 0 0         if ($var_type =~ / \( \s* \* \s* \) /x) {
438             # Function pointers are not yet supported with output_init()!
439 0           print "\t" . map_type($self, $var_type, $var_name);
440 0           $printed_name = 1;
441             }
442             else {
443 0           print "\t" . map_type($self, $var_type, undef);
444 0           $printed_name = 0;
445             }
446 0           $self->{var_num} = $self->{args_match}->{$var_name};
447            
448 0 0         if ($self->{var_num}) {
449 0           my $typemap = $self->{typemap}->get_typemap(ctype => $var_type);
450 0 0 0       $self->report_typemap_failure($self->{typemap}, $var_type, "death")
451             if not $typemap and not $is_overridden_typemap;
452 0   0       $self->{proto_arg}->[$self->{var_num}] = ($typemap && $typemap->proto) || "\$";
453             }
454 0 0         $self->{func_args} =~ s/\b($var_name)\b/&$1/ if $var_addr;
455 0 0 0       if ($var_init =~ /^[=;]\s*NO_INIT\s*;?\s*$/
    0 0        
    0 0        
456             or $self->{in_out}->{$var_name} and $self->{in_out}->{$var_name} =~ /^OUT/
457             and $var_init !~ /\S/) {
458 0 0         if ($printed_name) {
459 0           print ";\n";
460             }
461             else {
462 0           print "\t$var_name;\n";
463             }
464             }
465             elsif ($var_init =~ /\S/) {
466             $self->output_init( {
467             type => $var_type,
468             num => $self->{var_num},
469 0           var => $var_name,
470             init => $var_init,
471             printed_name => $printed_name,
472             } );
473             }
474             elsif ($self->{var_num}) {
475             $self->generate_init( {
476             type => $var_type,
477             num => $self->{var_num},
478 0           var => $var_name,
479             printed_name => $printed_name,
480             } );
481             }
482             else {
483 0           print ";\n";
484             }
485             }
486             };
487             }
488              
489             1;