File Coverage

blib/lib/ModPerl/CScan.pm
Criterion Covered Total %
statement 94 508 18.5
branch 21 224 9.3
condition 10 49 20.4
subroutine 18 37 48.6
pod 0 25 0.0
total 143 843 16.9


line stmt bran cond sub pod time code
1             package ModPerl::CScan;
2            
3             require Exporter;
4 4     4   26 use Config '%Config';
  4         9  
  4         200  
5 4     4   327 use File::Basename;
  4         9  
  4         920  
6            
7             # NOTE to distributors: this module is needed only for mp2 developers,
8             # it's not a requirement for mod_perl users
9 4     4   10752 use Data::Flow qw(0.05);
  4         61000  
  4         1071  
10            
11 4     4   43 use strict; # Earlier it catches ISA and EXPORT.
  4         7  
  4         51229  
12            
13             @ModPerl::CScan::ISA = qw(Exporter Data::Flow);
14            
15             # Items to export into callers namespace by default. Note: do not export
16             # names by default without a very good reason. Use EXPORT_OK instead.
17             # Do not simply export all your public functions/methods/constants.
18            
19             @ModPerl::CScan::EXPORT = qw(
20             );
21             @ModPerl::CScan::EXPORT_OK = qw(
22             );
23             # this flag tells cpp to only output macros
24             $ModPerl::CScan::MACROS_ONLY = '-dM';
25            
26             $ModPerl::CScan::VERSION = '0.75';
27            
28             my (%keywords,%style_keywords);
29             for (qw(asm auto break case char continue default do double else enum
30             extern float for fortran goto if int long register return short
31             sizeof static struct switch typedef union unsigned signed while void volatile)) {
32             $keywords{$_}++;
33             }
34             for (qw(bool class const delete friend inline new operator overload private
35             protected public virtual)) {
36             $style_keywords{'C++'}{$_}++;
37             }
38             for (qw(__func__ _Complex _Imaginary _Bool inline restrict)) {
39             $style_keywords{'C9X'}{$_}++;
40             }
41             for (qw(inline const asm noreturn section
42             constructor destructor unused weak)) {
43             $style_keywords{'GNU'}{$_}++;
44             $style_keywords{'GNU'}{"__$ {_}__"}++;
45             }
46             $style_keywords{'GNU'}{__attribute__}++;
47             $style_keywords{'GNU'}{__extension__}++;
48             $style_keywords{'GNU'}{__consts}++;
49             $style_keywords{'GNU'}{__const}++;
50             $style_keywords{'GNU'}{__restrict}++;
51            
52             my $recipes
53             = { Defines => { default => '' },
54             cppstdin => { default => $Config{cppstdin} },
55             cppflags => { default => $Config{cppflags} },
56             cppminus => { default => $Config{cppminus} },
57             c_styles => { default => [qw(C++ GNU C9X)] },
58             add_cppflags => { default => '' },
59             keywords => { prerequisites => ['c_styles'],
60             output => sub {
61             my %kw = %keywords;
62             my %add;
63             for ( @{ shift->{c_styles} } ) {
64             %add = %{ $style_keywords{$_} };
65             %kw = (%kw, %add);
66             }
67             \%kw;
68             }, },
69             'undef' => { default => undef },
70             filename_filter => { default => undef },
71             full_text => { class_filter => [ 'text', 'C::Preprocessed',
72             qw(undef filename Defines includeDirs Cpp)] },
73             text => { class_filter => [ 'text', 'C::Preprocessed',
74             qw(filename_filter filename Defines includeDirs Cpp)] },
75             text_only_from => { class_filter => [ 'text_only_from', 'C::Preprocessed',
76             qw(filename_filter filename Defines includeDirs Cpp)] },
77             includes => { filter => [ \&includes,
78             qw(filename Defines includeDirs Cpp) ], },
79             includeDirs => { prerequisites => ['filedir'],
80             output => sub {
81             my $data = shift;
82             [ $data->{filedir}, '/usr/local/include', '.'];
83             } },
84             Cpp => { prerequisites => [qw(cppminus add_cppflags cppflags cppstdin)],
85             output => sub {
86             my $data = shift;
87             return { cppstdin => $data->{cppstdin},
88             cppflags => "$data->{cppflags} $data->{add_cppflags}",
89             cppminus => $data->{cppminus} };
90             } },
91             filedir => { output => sub { dirname ( shift->{filename} || '.' ) } },
92             sanitized => { filter => [ \&sanitize, 'text'], },
93             toplevel => { filter => [ \&top_level, 'sanitized'], },
94             full_sanitized => { filter => [ \&sanitize, 'full_text'], },
95             full_toplevel => { filter => [ \&top_level, 'full_sanitized'], },
96             no_type_decl => { filter => [ \&remove_type_decl, 'toplevel'], },
97             typedef_chunks => { filter => [ \&typedef_chunks, 'full_toplevel'], },
98             struct_chunks => { filter => [ \&struct_chunks, 'full_toplevel'], },
99             typedefs_whited => { filter => [ \&typedefs_whited,
100             'full_sanitized', 'typedef_chunks',
101             'keywords_rex'], },
102             typedef_texts => { filter => [ \&typedef_texts,
103             'full_text', 'typedef_chunks'], },
104             struct_texts => { filter => [ \&typedef_texts,
105             'full_text', 'struct_chunks'], },
106             typedef_hash => { filter => [ \&typedef_hash,
107             'typedef_texts', 'typedefs_whited'], },
108             typedef_structs => { filter => [ \&typedef_structs,
109             'typedef_hash', 'struct_texts'], },
110             typedefs_maybe => { filter => [ sub {[keys %{+shift}]},
111             'typedef_hash'], },
112             defines_maybe => { filter => [ \&defines_maybe, 'filename'], },
113             defines_no_args => { prerequisites => ['defines_maybe'],
114             output => sub { shift->{defines_maybe}->[0] }, },
115             defines_args => { prerequisites => ['defines_maybe'],
116             output => sub { shift->{defines_maybe}->[1] }, },
117            
118             defines_full => { filter => [ \&defines_full,
119             qw(filename Defines includeDirs Cpp) ], },
120             defines_no_args_full => { prerequisites => ['defines_full'],
121             output => sub { shift->{defines_full}->[0] }, },
122             defines_args_full => { prerequisites => ['defines_full'],
123             output => sub { shift->{defines_full}->[1] }, },
124            
125             decl_inlines => { filter => [ \&functions_in, 'no_type_decl'], },
126             inline_chunks => { filter => [ sub { shift->[0] }, 'decl_inlines'], },
127             inlines => { filter => [ \&from_chunks, 'inline_chunks', 'text'], },
128             decl_chunks => { filter => [ sub { shift->[1] }, 'decl_inlines'], },
129             decls => { filter => [ \&from_chunks, 'decl_chunks', 'text'], },
130             fdecl_chunks => { filter => [ sub { shift->[4] }, 'decl_inlines'], },
131             fdecls => { filter => [ \&from_chunks, 'fdecl_chunks', 'text'], },
132             mdecl_chunks => { filter => [ sub { shift->[2] }, 'decl_inlines'], },
133             mdecls => { filter => [ \&from_chunks, 'mdecl_chunks', 'text'], },
134             vdecl_chunks => { filter => [ sub { shift->[3] }, 'decl_inlines'], },
135             vdecls => { filter => [ \&from_chunks, 'vdecl_chunks', 'text'], },
136             vdecl_hash => { filter => [ \&vdecl_hash, 'vdecls', 'mdecls' ], },
137             parsed_fdecls => { filter => [ \&do_declarations, 'fdecls',
138             'typedef_hash', 'keywords'], },
139             keywords_rex => { filter => [ sub { my @k = keys %{ shift() };
140             local $" = '|';
141             my $r = "(?:@k)";
142             eval 'qr/$r/' or $r # Older Perls
143             }, 'keywords'], },
144             };
145            
146             sub from_chunks {
147 0     0 0 0 my $chunks = shift;
148 0         0 my $txt = shift;
149 0         0 my @out;
150 0         0 my $i = 0;
151 0         0 while ($i < @$chunks) {
152 0         0 push @out, substr $txt, $chunks->[$i], $chunks->[ $i + 1 ] - $chunks->[$i];
153 0         0 $i += 2;
154             }
155 0         0 \@out;
156             }
157            
158             #sub process { request($recipes, @_) }
159             # Preloaded methods go here.
160            
161             sub includes {
162 0     0 0 0 my %seen;
163 0 0       0 my $stream = new C::Preprocessed (@_)
164             or die "Cannot open pipe from cppstdin: $!\n";
165            
166 0         0 while (<$stream>) {
167 0 0       0 next unless m(^\s*\#\s* # Leading hash
168             (line\s*)? # 1: Optional line
169             ([0-9]+)\s* # 2: Line number
170             (.*) # 3: The rest
171             )x;
172 0         0 my $include = $3;
173 0 0       0 $include = $1 if $include =~ /"(.*)"/; # Filename may be in quotes
174 0 0       0 $include =~ s,\\\\,/,g if $^O eq 'os2';
175 0 0       0 $seen{$include}++ if $include ne "";
176             }
177 0         0 [keys %seen];
178             }
179            
180             sub defines_maybe {
181 5     5 0 361 my $file = shift;
182 5         23 my ($mline,$line,%macros,%macrosargs,$sym,$args);
183 5 50       194 open(C, $file) or die "Cannot open file $file: $!\n";
184 5   66     154 while (not eof(C) and $line = ) {
185             next unless
186 1343 100       13621 ( $line =~ s[
187             ^ \s* \# \s* # Start of directive
188             define \s+
189             (\w+) # 1: symbol
190             (?:
191             \( (.*?) \s* \) # 2: Minimal match for arguments
192             # in parenths (without trailing
193             # spaces)
194             )? # optional, no grouping
195             \s* # rest is the definition
196             ([\s\S]*) # 3: the rest
197             ][]x );
198 599         2313 ($sym, $args, $mline) = ($1, $2, $3);
199 599   66     3106 $mline .= while not eof(C) and $mline =~ s/\\\n/\n/;
200 599         671 chomp $mline;
201             #print "sym: `$sym', args: `$args', mline: `$mline'\n";
202 599 100       6398 if (defined $args) {
203 22         265 $macrosargs{$sym} = [ [split /\s*,\s*/, $args], $mline];
204             } else {
205 577         3639 $macros{$sym} = $mline;
206             }
207             }
208 5 50       76 close(C) or die "Cannot close file $file: $!\n";
209 5         56 [\%macros, \%macrosargs];
210             }
211            
212             sub defines_full {
213 0     0 0 0 my $Cpp = $_[3];
214 0         0 my ($mline,$line,%macros,%macrosargs,$sym,$args);
215            
216             # save the old cppflags and add the flag for only ouputting macro definitions
217 0         0 my $old_cppstdin = $Cpp->{'cppstdin'};
218 0         0 $Cpp->{'cppstdin'} = $old_cppstdin . " " . $ModPerl::CScan::MACROS_ONLY;
219            
220 0 0       0 my $stream = new C::Preprocessed (@_)
221             or die "Cannot open pipe from cppstdin: $!\n";
222            
223 0         0 while (defined ($line = <$stream>)) {
224             next unless
225 0 0       0 ( $line =~ s[
226             ^ \s* \# \s* # Start of directive
227             define \s+
228             (\w+) # 1: symbol
229             (?:
230             \( (.*?) \s* \) # 2: Minimal match for arguments
231             # in parenths (without trailing
232             # spaces)
233             )? # optional, no grouping
234             \s* # rest is the definition
235             ([\s\S]*) # 3: the rest
236             ][]x );
237 0         0 ($sym, $args, $mline) = ($1, $2, $3);
238 0         0 $mline .= <$stream> while ($mline =~ s/\\\n/\n/);
239 0         0 chomp $mline;
240             #print STDERR "sym: `$sym', args: `$args', mline: `$mline'\n";
241 0 0       0 if (defined $args) {
242 0         0 $macrosargs{$sym} = [ [split /\s*,\s*/, $args], $mline];
243             } else {
244 0         0 $macros{$sym} = $mline;
245             }
246             }
247             # restore the original cppflags
248 0         0 $Cpp->{'cppstdin'} = $old_cppstdin;
249 0         0 [\%macros, \%macrosargs];
250             }
251            
252             sub typedef_chunks { # Input is toplevel, output: starts and ends
253 5     5 0 110 my $txt = shift;
254 5         38 pos $txt = 0;
255 5         17 my ($b, $e, @out);
256 5         118 while ($txt =~ /\btypedef\b/g) {
257 141         584 push @out, pos $txt;
258 141         1022 $txt =~ /(?=;)|\Z/g;
259 141         352 push @out, pos $txt;
260             }
261 5         76 \@out;
262             }
263            
264             sub struct_chunks {
265 0     0 0 0 my $txt = shift;
266 0         0 pos $txt = 0;
267 0         0 my ($b, $e, @out);
268 0         0 while ($txt =~ /\b(?=struct\s*(\w*\s*)?\{)/g) {
269 0         0 push @out, pos $txt;
270 0         0 $txt =~ /(?=;)|\Z/g;
271 0         0 push @out, pos $txt;
272             }
273 0         0 \@out;
274             }
275            
276             sub typedefs_whited { # Input is sanitized text, and list of beg/end.
277 0     0 0 0 my @lst = @{$_[1]};
  0         0  
278 0         0 my @out;
279 0         0 my ($b, $e);
280 0         0 while ($b = shift @lst) {
281 0         0 $e = shift @lst;
282 0         0 push @out, whited_decl($_[2], substr $_[0], $b, $e - $b);
283             }
284 0         0 \@out;
285             }
286            
287             sub structs_whited {
288 0     0 0 0 my @lst = @{$_[1]};
  0         0  
289 0         0 my @out;
290 0         0 my ($b, $e, $in);
291 0         0 while ($b = shift @lst) {
292 0         0 $e = shift @lst;
293 0         0 $in = substr $_[0], $b, $e - $b;
294 0         0 $in =~ s/^(struct\s*(\w*\s*)?)(.*)$/$1 . " " x length($3)/es;
  0         0  
295 0         0 push @out, $in;
296             }
297 0         0 \@out;
298             }
299            
300             sub typedef_texts {
301 5     5 0 360 my ($txt, $chunks) = (shift, shift);
302 5         13 my ($b, $e, $in, @out);
303 5         70 my @in = @$chunks;
304 5         30 while (($b, $e) = splice @in, 0, 2) {
305 141         181 $in = substr($txt, $b, $e - $b);
306             # remove any remaining directives
307 141         165 $in =~ s/^ ( \s* \# .* ( \\ $ \n .* )* ) / ' ' x length($1)/xgem;
  2         18  
308 141         289 push @out, $in;
309             }
310 5         75 \@out;
311             }
312            
313             sub typedef_hash {
314 0     0 0 0 my ($typedefs, $whited) = (shift,shift);
315 0         0 my %out;
316            
317             loop:
318 0         0 for my $o (0..$#$typedefs) {
319 0         0 my $wh = $whited->[$o];
320 0         0 my $td = $typedefs->[$o];
321             #my $verb = $td =~ /apr_child_errfn_t/ ? 1 : 0;
322             #warn "$wh || $td\n" if $verb;
323 0 0 0     0 if ($wh =~ /,/ or not $wh =~ /\w/) { # Hard case, guessimates ...
    0          
324             # Determine whether the new thingies are inside parens
325 0         0 $wh =~ /,/g;
326 0         0 my $p = pos $wh;
327 0         0 my ($s, $e);
328 0 0       0 if (matchingbrace($wh)) { # Inside. Easy part: just split on /,/...
329 0         0 $e = pos($wh) - 1;
330 0         0 $s = $e;
331 0         0 my $d = 0;
332             # Skip back
333 0         0 while (--$s >= 0) {
334 0         0 my $c = substr $wh, $s, 1;
335 0 0       0 if ($c =~ /[\(\{\[]/) {
    0          
336 0         0 $d--;
337             } elsif ($c =~ /[\)\]\}]/) {
338 0         0 $d++;
339             }
340 0 0       0 last if $d < 0;
341             }
342 0 0       0 if ($s < 0) { # Should not happen
343 0         0 warn("panic: could not match braces in\n\t$td\nwhited as\n\t$wh\n");
344 0         0 next loop;
345             }
346 0         0 $s++;
347             } else { # We are at toplevel
348             # We need to skip back all the modifiers attached to the first thingy
349             # Guesstimates: everything after the first '*' (inclusive)
350 0         0 pos $wh = 0;
351 0         0 $wh = /(?=\w)/g;
352 0         0 my $ws = pos $wh;
353 0         0 my $pre = substr $wh, 0, $ws;
354 0         0 $s = $ws;
355 0 0       0 $s = pos $pre if $pre =~ /(?=\*)/g;
356 0         0 $e = length $wh;
357             }
358             # Now: need to split $td based on commas in $wh!
359             # And need to split each chunk of $td based on word in the chunk of $wh!
360 0         0 my $td_decls = substr($td, $s, $e - $s);
361 0         0 my ($pre, $post) = (substr($td, 0, $s), substr($td, $e));
362 0         0 my $wh_decls = substr($wh, $s, $e - $s);
363 0         0 my @wh_decls = split /,/, $wh_decls;
364 0         0 my $td_s = 0;
365 0         0 my (@td_decl, @td_pre, @td_post, @td_word);
366 0         0 for my $wh_d (@wh_decls) {
367 0         0 my $td_d = substr $td, $td_s, length $wh_d;
368 0         0 push @td_decl, $td_d;
369 0         0 $wh_d =~ /(\w+)/g;
370 0         0 push @td_word, $1;
371 0         0 push @td_post, substr $td_d, pos($wh_d);
372 0         0 push @td_pre, substr $td_d, pos($wh_d) - length $1, length $1;
373 0         0 $td_s += 1 + length $wh_d; # Skip over ','
374             }
375 0         0 for my $i (0..$#wh_decls) {
376 0         0 my $p = "$td_post[$i]$post";
377 0 0       0 $p = '' unless $p =~ /\S/;
378 0         0 $out{$td_word[$i]} = ["$pre$td_pre[$i]", $p];
379             }
380             } elsif ($td =~ /\(\s* \*? \s* ([^)]+) \s* \) \s* \(.*\)/gxs){ # XXX: function pointer typedef
381 0         0 $out{$1} = ['XXX: pre_foo', 'XXX: post_bar']; # XXX: not sure what to stuff here
382             #warn "[$1] [$td]" if $verb;
383             } else { # Only one thing defined...
384 0         0 $wh =~ /(\w+)/g;
385 0         0 my $e = pos $wh;
386 0         0 my $s = $e - length $1;
387 0         0 my $type = $1;
388 0         0 my $pre = substr $td, 0, $s;
389 0         0 my $post = substr $td, $e, length($td) - $e;
390 0 0       0 $post = '' unless $post =~ /\S/;
391 0         0 $out{$type} = [$pre, $post];
392             }
393            
394             #die if $verb;
395            
396             }
397 0         0 \%out;
398             }
399            
400             sub typedef_structs {
401 0     0 0 0 my($typehash, $structs) = @_;
402 0         0 my %structs;
403 0         0 for (0 .. $#$structs) {
404 0         0 my $in = $structs->[$_];
405 0         0 my $key;
406 0 0       0 next unless $in =~ /^struct\s*(\w+)/;
407 0 0       0 next unless $in =~ s{^(struct\s*)(\w+)}{
408 0         0 $key = "struct $2";
409 0         0 $1 . " " x length($2)
410             }e;
411 0         0 my $name = parse_struct($in, \%structs);
412 0 0       0 $structs{$key} = defined($name) ? $structs{$name} : undef;
413             }
414 0         0 while (my($key, $text) = each %$typehash) {
415 0         0 my $name = parse_struct($text->[0], \%structs);
416 0 0       0 $structs{$key} = defined($name) ? $structs{$name} : undef;
417             }
418 0         0 \%structs;
419             }
420            
421             sub parse_struct {
422 0     0 0 0 my($in, $structs) = @_;
423 0         0 my($b, $e, $chunk, $vars, $struct, $structname);
424 0 0       0 return "$1 $2" if $in =~ /
425             ^ \s* (struct | union) \s+ (\w+) \s* $
426             /x;
427 0 0       0 ($structname, $in) = $in =~ /
428             ^ \s* ( (?: struct | union ) (?: \s+ \w+ )? ) \s* { \s* (.*?) \s* } \s* $
429             /gisx or return;
430 0 0       0 $structname .= " _ANON" unless $structname =~ /\s/;
431 0 0       0 $structname .= " 0" if exists $structs->{$structname};
432 0         0 $structname =~ s/(\d+$)/$1 + 1/e while exists $structs->{$structname};
  0         0  
433 0         0 $structname =~ s/\s+/ /g;
434 0         0 $b = 0;
435 0         0 while ($in =~ /(\{|;|$)/g) {
436 0 0       0 matchingbrace($in), next if $1 eq '{';
437 0         0 $e = pos($in);
438 0 0       0 next if $b == $e;
439 0         0 $chunk = substr($in, $b, $e - $b);
440 0         0 $b = $e;
441 0 0       0 if ($chunk =~ /\G\s*(struct|union|enum).*\}/gs) {
442 0         0 my $term = pos $chunk;
443 0         0 my $name = parse_struct(substr($chunk, 0, $term), $structs);
444 0         0 $vars = parse_vars(join ' ', $name, substr $chunk, $term);
445             } else {
446 0         0 $vars = parse_vars($chunk);
447             }
448 0 0       0 push @$struct, @{$vars||[]};
  0         0  
449             }
450 0         0 $structs->{$structname} = $struct;
451 0         0 $structname;
452             }
453            
454             sub parse_vars {
455 0     0 0 0 my $in = shift;
456 0         0 my($vars, $type, $word, $id, $post, $func);
457            
458 0         0 while ($in =~ /\G\s*([\[;,(]|\*+|:\s*\d+|\S+?\b|$)\s*/gc) {
459 0         0 $word = $1;
460 0 0 0     0 if ($word eq ';' || $word eq '') {
    0          
    0          
    0          
    0          
461 0 0       0 next unless defined $id;
462 0 0       0 $type = 'int' unless defined $type; # or is this an error?
463 0         0 push @$vars, [ $type, $post, $id ];
464 0         0 ($type, $post, $id, $func) = (undef, undef, undef);
465             } elsif ($word eq ',') {
466 0 0       0 warn "panic: expecting name before comma in '$in'\n" unless defined $id;
467 0 0       0 $type = 'int' unless defined $type; # or is this an error?
468 0         0 push @$vars, [ $type, $post, $id ];
469 0         0 $type =~ s/[ *]*$//;
470 0         0 $id = undef;
471             } elsif ($word eq '[') {
472 0 0       0 warn "panic: expecting name before '[' in '$in'\n" unless defined $id;
473 0 0       0 $type = 'int' unless defined $type; # or is this an error?
474 0         0 my $b = pos $in;
475 0         0 matchingbrace($in);
476 0         0 $post .= $word . substr $in, $b, pos($in) - $b;
477             } elsif ($word eq '(') {
478             # simple hack for function pointers
479 0 0       0 $type = join ' ', grep defined, $type, $id if defined $id;
480 0 0       0 $type = 'int' unless defined $type;
481 0 0       0 if ($in =~ /\G\s*(\*[\s\*]*?)\s*(\w+)[\[\]\d\s]*(\)\s*\()/gc) {
482 0         0 $type .= "($1";
483 0         0 $id = $2;
484 0         0 $post = $3;
485 0         0 my $b = pos $in;
486 0         0 matchingbrace($in);
487 0         0 $post .= substr $in, $b, pos($in) - $b;
488             } else {
489 0         0 warn "panic: can't parse function pointer declaration in '$in'\n";
490 0         0 return;
491             }
492             } elsif ($word =~ /^:/) {
493             # bitfield
494 0 0       0 $type = 'int' unless defined $type;
495 0         0 $post .= $word;
496             } else {
497 0 0       0 if (defined $post) {
498 0 0       0 if ($func) {
499 0         0 $post .= $word;
500             } else {
501 0         0 warn "panic: not expecting '$word' after array bounds in '$in'\n";
502             }
503             } else {
504 0 0       0 $type = join ' ', grep defined, $type, $id if defined $id;
505 0         0 $id = $word;
506             }
507             }
508             }
509 0 0       0 unless ($vars) {
510 0         0 warn sprintf "failed on <%s> with type=<%s>, id=<%s>, post=<%s> at pos=%d\n",
511             $in, $type, $id, $post, pos($in);
512             }
513 0         0 $vars;
514             }
515            
516             sub vdecl_hash {
517 0     0 0 0 my($vdecls, $mdecls) = @_;
518 0         0 my %vdecl_hash;
519 0         0 for (@$vdecls, @$mdecls) {
520 0 0       0 next if /[()]/; # ignore functions, and function pointers
521 0         0 my $copy = $_;
522 0 0       0 next unless $copy =~ s/^\s*extern\s*//;
523 0         0 my $vars = parse_vars($copy);
524 0         0 $vdecl_hash{$_->[2]} = [ @$_[0, 1] ] for @$vars;
525             }
526 0         0 \%vdecl_hash;
527             }
528            
529             # The output is the list of list of inline chunks and list of
530             # declaration chunks.
531            
532             sub functions_in { # The arg is text without type declarations.
533 0     0 0 0 my $in = shift; # remove_type_decl(top_level(sanitize($txt)));
534             # What remains now consists of variable and function declarations,
535             # and inline functions.
536 0         0 $in =~ /(?=\S)/g;
537 0         0 my ($b, $e, $b1, $e1, @inlines, @decls, @mdecls, @fdecls, @vdecls);
538 0         0 $b = pos $in;
539 0         0 my $chunk;
540 0   0     0 while (defined($b) && $b != length $in) {
541 0 0       0 $in =~ /;/g or pos $in = $b, $in =~ /.*\S|\Z/g ; # Or last non-space
542 0         0 $e = pos $in;
543 0         0 $chunk = substr $in, $b, $e - $b;
544             # Now subdivide the chunk.
545             #
546             # What we got is one chunk, probably finished by `;'. Whoever, it
547             # may start with several inline functions.
548             #
549             # Note that inline functions contain ( ) { } in the stripped version.
550 0         0 $b1 = 0;
551 0         0 while ($chunk =~ /\(\s*\)\s*\{\s*\}/g) {
552 0         0 $e1 = pos $chunk;
553 0         0 push @inlines, $b + $b1, $b + $e1;
554 0         0 $chunk =~ /(?=\S)/g;
555 0         0 $b1 = pos $chunk;
556 0 0       0 $b1 = length $chunk, last unless defined $b1;
557             }
558 0 0       0 if ($e - $b - $b1 > 0) {
559 0         0 my($isvar, $isfunc) = (1, 1);
560 0         0 substr ($chunk, 0, $b1) = '';
561 0 0       0 if ($chunk =~ /,/) { # Contains multiple declarations.
562 0         0 push @mdecls, $b + $b1, $e;
563             } else { # Non-multiple.
564             # Since leading \s* is not optimized, this is quadratic!
565 0         0 $chunk =~ s{
566             ( ( const | __const
567             | __attribute__ \s* \( \s* \)
568             ) \s* )* ( ; \s* )? \Z # Strip from the end
569             }()x;
570 0         0 $chunk =~ s/\s*\Z//;
571 0 0       0 if ($chunk =~ /\)\Z/) { # Function declaration ends on ")"!
    0          
572 0 0 0     0 if ($chunk !~ m{
573             \( .* \( # Multiple parenths
574             }x
575             and $chunk =~ / \w \s* \( /x) { # Most probably pointer to a function?
576 0         0 $isvar = 0;
577             }
578             } elsif ($chunk =~ /
579             ^ \s* (enum|struct|union|class) \s+ \w+ \s* $
580             /x) {
581 0         0 $isvar = $isfunc = 0;
582             }
583 0 0       0 if ($isvar) { # Heuristically variable
    0          
584 0         0 push @vdecls, $b + $b1, $e;
585             } elsif ($isfunc) {
586 0         0 push @fdecls, $b + $b1, $e;
587             }
588             }
589 0 0 0     0 push @decls, $b + $b1, $e if $isvar || $isfunc;
590             }
591 0         0 $in =~ /\G\s*/g ;
592 0         0 $b = pos $in;
593             }
594 0         0 [\@inlines, \@decls, \@mdecls, \@vdecls, \@fdecls];
595             }
596            
597             # XXXX This is heuristical in many respects...
598             # Recipe: remove all struct-ish chunks. Remove all array specifiers.
599             # Remove GCC attribute specifiers.
600             # What remains may contain function's arguments, old types, and newly
601             # defined types.
602             # Remove function arguments using heuristics methods.
603             # Now out of several words in a row the last one is a newly defined type.
604            
605             sub whited_decl { # Input is sanitized.
606 0     0 0 0 my $keywords_rex = shift;
607 0         0 my $in = shift; # Text of a declaration
608            
609             #typedef ret_type*(*func) -> typedef ret_type* (*func)
610 0         0 $in =~ s/\*\(\*/* \(*/;
611            
612 0         0 my $rest = $in;
613 0         0 my $out = $in; # Whited out $in
614            
615             # Remove all the structs
616 0         0 while ($out =~ /(\b(struct|union|class|enum)(\s+\w+)?\s*\{)/g) {
617 0         0 my $pos_start = pos($out) - length $1;
618            
619 0         0 matchingbrace($out);
620 0         0 my $pos_end = pos $out;
621 0         0 substr($out, $pos_start, $pos_end - $pos_start) =
622             ' ' x ($pos_end - $pos_start);
623 0         0 pos $out = $pos_end;
624             }
625            
626             # Deal with glibc's wierd ass __attribute__ tag. Just dump it.
627             # Maaaybe this should check to see if you're using GCC, but I don't
628             # think so since glibc is nice enough to do that for you. [MGS]
629 0         0 while ( $out =~ m/(\b(__attribute__|attribute)\s*\((?=\s*\())/g ) {
630 0         0 my $att_pos_start = pos($out) - length($1);
631            
632             # Need to figure out where ((..)) ends.
633 0         0 matchingbrace($out);
634 0         0 my $att_pos_end = pos $out;
635            
636             # Remove the __attribute__ tag.
637 0         0 substr($out, $att_pos_start, $att_pos_end - $att_pos_start) =
638             ' ' x ($att_pos_end - $att_pos_start);
639 0         0 pos $out = $att_pos_end;
640             }
641            
642             # Remove arguments of functions (heuristics only).
643             # These things (start) arglist of a declared function:
644             # paren word comma
645             # paren word space non-paren
646             # paren keyword paren
647             # start a list of arguments. (May be "cdecl *myfunc"?) XXXXX ?????
648 0         0 while ( $out =~ /(\(\s*(\w+(,|\s*[^\)\s])|$keywords_rex\s*\)))/g ) {
649 0         0 my $pos_start = pos($out) - length($1);
650 0         0 pos $out = $pos_start + 1;
651 0         0 matchingbrace($out);
652 0         0 substr ($out, $pos_start + 1, pos($out) - 2 - $pos_start)
653             = ' ' x (pos($out) - 2 - $pos_start);
654             }
655             # Remove array specifiers
656 0         0 $out =~ s/(\[[\w\s\+]*\])/ ' ' x length $1 /ge;
  0         0  
657 0         0 my $tout = $out;
658             # Several words in a row cannot be new typedefs, but the last one.
659 0         0 $out =~ s/((\w+\**\s+)+(?=[^\s,;\[\{\)]))/ ' ' x length $1 /ge;
  0         0  
660 0 0       0 unless ($out =~ /\w/) {
661             # Probably a function-type declaration: typedef int f(int);
662             # Redo scan leaving the last word of the first group of words:
663 0 0       0 if ($tout =~ /(\w+\s+)*(\w+)\s*\(/g) {
664 0         0 $out = ' ' x (pos($tout) - length $2)
665             . $2 . ' ' x (length($tout) - pos($tout));
666             }
667             else {
668             # try a different approach to get the last type
669 0         0 my $len = length $tout;
670             # cut all non-words at the end of the definition
671 0 0       0 my $end = $tout =~ s/(\W*)$// ? length $1 : 0;
672             # remove everything but the last word
673 0 0       0 my $mid = $tout =~ s/.*?(\w*)$/$1/ ? length $1 : 0;
674             # restore the length
675 0         0 $out = $tout . ' ' x ($len - $mid);
676             }
677             # warn "function typedef\n\t'$in'\nwhited-out as\n\t'$out'\n";
678             }
679 0 0       0 warn "panic: length mismatch\n\t'$in'\nwhited-out as\n\t'$out'\n"
680             if length($in) != length $out;
681             # Sanity check
682 0 0       0 warn "panic: multiple types without intervening comma in\n\t'$in'\nwhited-out as\n\t'$out'\n"
683             if $out =~ /\w[^\w,]+\w/;
684 0 0       0 warn "panic: no types found in\n\t'$in'\nwhited-out as\n\t'$out'\n"
685             unless $out =~ /\w/;
686 0         0 $out
687             }
688            
689             sub matchingbrace {
690             # pos($_[0]) is after the opening brace now
691 405     405 0 454 my $n = 0;
692 405         2469 while ($_[0] =~ /([\{\[\(])|([\]\)\}])/g) {
693 1169 100       2372 $1 ? $n++ : $n-- ;
694 1169 100       12043 return 1 if $n < 0;
695             }
696             # pos($_[0]) is after the closing brace now
697 0         0 return; # false
698             }
699            
700             sub remove_Comments_no_Strings { # We expect that no strings are around
701 0     0 0 0 my $in = shift;
702 0         0 $in =~ s,/(/.*|\*[\s\S]*?\*/),,g ; # C and C++
703 0 0       0 die "Unfinished comment" if $in =~ m,/\*, ;
704 0         0 $in;
705             }
706            
707             sub sanitize { # We expect that no strings are around
708 5     5 0 9670 my $in = shift;
709             # C and C++, strings and characters
710 5         254 $in =~ s{ / (
711             / .* # C++ style
712             |
713             \* [\s\S]*? \*/ # C style
714             ) # (1)
715             | '((?:[^\\\']|\\.)+)' # (2) Character constants
716             | "((?:[^\\\"]|\\.)*)" # (3) Strings
717             | ( ^ \s* \# .* # (4) Preprocessor
718             ( \\ $ \n .* )* ) # and continuation lines
719             } {
720             # We want to preserve the length, so that one may go back
721 265 50       5689 defined $1 ? ' ' x (1 + length $1) :
    50          
    100          
    50          
722             defined $4 ? ' ' x length $4 :
723             defined $2 ? "'" . ' ' x length($2) . "'" :
724             defined $3 ? '"' . ' ' x length($3) . '"' : '???'
725             }xgem ;
726 5 50       106 die "Unfinished comment" if $in =~ m{ /\* }x;
727 5         94 $in;
728             }
729            
730             sub top_level { # We expect argument is sanitized
731             # Note that this may remove the variable in declaration: int (*func)();
732 5     5 0 85 my $in = shift;
733 5         14 my $start;
734 5         279 my $out = $in;
735 5         67 while ($in =~ /[\[\{\(]/g ) {
736 405         459 $start = pos $in;
737 405         867 matchingbrace($in);
738 405         1862 substr($out, $start, pos($in) - 1 - $start)
739             = ' ' x (pos($in) - 1 - $start);
740             }
741 5         117 $out;
742             }
743            
744             sub remove_type_decl { # We suppose that the arg is top-level only.
745 0     0 0 0 my $in = shift;
746 0         0 $in =~ s/(\b__extension__)(\s+typedef\b)/(' ' x length $1) . $2/gse;
  0         0  
747 0         0 $in =~ s/(\btypedef\b.*?;)/' ' x length $1/gse;
  0         0  
748             # The following form may appear only in the declaration of the type itself:
749 0         0 $in =~
750 0         0 s/(\b(enum|struct|union|class)\b[\s\w]*\{\s*\}\s*;)/' ' x length $1/gse;
751 0         0 $in;
752             }
753            
754             sub new {
755 5     5 0 25 my $class = shift;
756 5         74 my $out = SUPER::new $class $recipes;
757 5         89 $out->set(@_);
758 5         107 $out;
759             }
760            
761             sub do_declarations {
762 0     0 0 0 my @d = map do_declaration($_, $_[1], $_[2]), @{ $_[0] };
  0         0  
763 0         0 \@d;
764             }
765            
766             # Forth argument: if defined, there maybe no identifier. Generate one
767             # basing on this argument.
768            
769             sub do_declaration {
770 0     0 0 0 my ($decl, $typedefs, $keywords, $argnum) = @_;
771 0         0 $decl =~ s/;?\s*$//;
772            
773 0         0 my ($type, $typepre, $typepost, $ident, $args, $w, $pos, $repeater);
774 0         0 $decl =~ s/[\r\n]\s*/ /g;
775             #warn "DECLAR [$decl][$argnum]\n";
776 0         0 $decl =~ s/^\s*__extension__\b\s*//;
777 0         0 $decl =~ s/^\s*extern\b\s*//;
778 0         0 $decl =~ s/^\s*__inline\b\s*//;
779 0         0 $pos = 0;
780 0   0     0 while ($decl =~ /(\w+)/g and ($typedefs->{$1} or $keywords->{$1})) {
      0        
781 0         0 $w = $1;
782 0 0       0 if ($w =~ /^(struct|class|enum|union)$/) {
783 0 0       0 $decl =~ /\G\s+\w+/g or die "`$w' is not followed by word in `$decl'";
784             }
785 0         0 $pos = pos $decl;
786             }
787             #warn "pos: $pos\n";
788 0         0 pos $decl = $pos;
789 0 0       0 $decl =~ /\G[\s*]*\*/g or pos $decl = $pos;
790 0         0 $type = substr $decl, 0, pos $decl;
791 0 0       0 $decl =~ /\G\s*/g or pos $decl = length $type; # ????
792 0         0 $pos = pos $decl;
793             #warn "pos: $pos\n";
794 0 0       0 if (defined $argnum) {
795 0 0       0 if ($decl =~ /\G(\w+)((\s*\[[^][]*\])*)/g) { # The best we can do with [2]
796 0         0 $ident = $1;
797 0         0 $repeater = $2;
798 0         0 $pos = pos $decl;
799             } else {
800 0         0 pos $decl = $pos = length $decl;
801 0         0 $type = $decl;
802 0         0 $ident = "arg$argnum";
803             }
804             } else {
805 0 0       0 die "Cannot process declaration `$decl' without an identifier"
806             unless $decl =~ /\G(\w+)/g;
807 0         0 $ident = $1;
808 0         0 $pos = pos $decl;
809             }
810             #warn "pos: $pos\n";
811 0 0       0 $decl =~ /\G\s*/g or pos $decl = $pos;
812 0         0 $pos = pos $decl;
813             #my $st = length $decl;
814             #warn substr($decl, 0, $pos), "\n";
815             #warn "pos: $pos $st\n";
816             #warn "DECLAR [$decl][$argnum]\n";
817 0 0       0 if (pos $decl != length $decl) {
818 0         0 pos $decl = $pos;
819 0 0       0 die "Expecting parenth after identifier in `$decl'\nafter `",
820             substr($decl, 0, $pos), "'"
821             unless $decl =~ /\G\(/g;
822 0         0 my $argstring = substr($decl, pos($decl) - length $decl);
823 0 0       0 matchingbrace($argstring) or die "Cannot find matching parenth in `$decl'";
824 0         0 $argstring = substr($argstring, 0, pos($argstring) - 1);
825 0         0 $argstring =~ s/ ^ ( \s* void )? \s* $ //x;
826 0         0 $args = [];
827 0         0 my @args;
828 0 0       0 if ($argstring ne '') {
829 0         0 my $top = top_level $argstring;
830 0         0 my $p = 0;
831 0         0 my $arg;
832 0         0 while ($top =~ /,/g) {
833 0         0 $arg = substr($argstring, $p, pos($top) - 1 - $p);
834 0         0 $arg =~ s/^\s+|\s+$//gs;
835 0         0 push @args, $arg;
836 0         0 $p = pos $top;
837             }
838 0         0 $arg = substr $argstring, $p;
839 0         0 $arg =~ s/^\s+|\s+$//gs;
840 0         0 push @args, $arg;
841             }
842 0         0 my $i = 0;
843 0         0 for (@args) {
844 0         0 push @$args, do_declaration1($_, $typedefs, $keywords, $i++);
845             }
846             }
847 0         0 [$type, $ident, $args, $decl, $repeater];
848             }
849            
850             sub do_declaration1 {
851 0     0 0 0 my ($decl, $typedefs, $keywords, $argnum) = @_;
852 0         0 $decl =~ s/;?\s*$//;
853             #warn "DECLARO [$decl][$argnum]\n";
854 0         0 my ($type, $typepre, $typepost, $ident, $args, $w, $pos, $repeater);
855 0         0 $pos = 0;
856 0   0     0 while ($decl =~ /(\w+)/g and ($typedefs->{$1} or $keywords->{$1})) {
      0        
857 0         0 $w = $1;
858 0 0       0 if ($w =~ /^(struct|class|enum|union)$/) {
859 0 0       0 $decl =~ /\G\s+\w+/g or die "`$w' is not followed by word in `$decl'";
860             }
861 0         0 $pos = pos $decl;
862             }
863             #warn "POS: $pos\n";
864 0         0 pos $decl = $pos;
865 0 0       0 $decl =~ /\G[\s*]*\*/g or pos $decl = $pos;
866 0         0 $type = substr $decl, 0, pos $decl;
867 0 0       0 $decl =~ /\G\s*/g or pos $decl = length $type; # ????
868 0         0 $pos = pos $decl;
869 0 0       0 if (defined $argnum) {
870 0 0       0 if ($decl =~ /\G(\w+)((\s*\[[^][]*\])*)/g) { # The best we can do with [2]
871 0         0 $ident = $1;
872 0         0 $repeater = $2;
873 0         0 $pos = pos $decl;
874             } else {
875 0         0 pos $decl = $pos = length $decl;
876 0         0 $type = $decl;
877 0         0 $ident = "arg$argnum";
878             }
879             } else {
880 0 0       0 die "Cannot process declaration `$decl' without an identifier"
881             unless $decl =~ /\G(\w+)/g;
882 0         0 $ident = $1;
883 0         0 $pos = pos $decl;
884             }
885 0 0       0 $decl =~ /\G\s*/g or pos $decl = $pos;
886 0         0 $pos = pos $decl;
887             #warn "DECLAR1 [$decl][$argnum]\n";
888             #my $st = length $decl;
889             #warn substr($decl, 0, $pos), "\n";
890             #warn "pos: $pos $st\n";
891 0 0       0 if (pos $decl != length $decl) {
892 0         0 pos $decl = $pos;
893 0 0       0 die "Expecting parenth after identifier in `$decl'\nafter `",
894             substr($decl, 0, $pos), "'"
895             unless $decl =~ /\G\(/g;
896 0         0 my $argstring = substr($decl, pos($decl) - length $decl);
897 0 0       0 matchingbrace($argstring) or die "Cannot find matching parenth in `$decl'";
898 0         0 $argstring = substr($argstring, 0, pos($argstring) - 1);
899 0         0 $argstring =~ s/ ^ ( \s* void )? \s* $ //x;
900 0         0 $args = [];
901 0         0 my @args;
902 0 0       0 if ($argstring ne '') {
903 0         0 my $top = top_level $argstring;
904 0         0 my $p = 0;
905 0         0 my $arg;
906 0         0 while ($top =~ /,/g) {
907 0         0 $arg = substr($argstring, $p, pos($top) - 1 - $p);
908 0         0 $arg =~ s/^\s+|\s+$//gs;
909 0         0 push @args, $arg;
910 0         0 $p = pos $top;
911             }
912 0         0 $arg = substr $argstring, $p;
913 0         0 $arg =~ s/^\s+|\s+$//gs;
914 0         0 push @args, $arg;
915             }
916 0         0 my $i = 0;
917 0         0 for (@args) {
918 0         0 push @$args, do_declaration1($_, $typedefs, $keywords, $i++);
919             }
920             }
921 0         0 [$type, $ident, $args, $decl, $repeater];
922             }
923            
924             ############################################################
925            
926             package C::Preprocessed;
927 4     4   6694 use Symbol;
  4         6090  
  4         389  
928 4     4   34 use File::Basename;
  4         9  
  4         457  
929 4     4   253 use Config;
  4         10  
  4         483  
930 4     4   26 use constant WIN32 => $^O eq 'MSWin32';
  4         7  
  4         4095  
931            
932             sub new {
933 5 50 33 5   56 die "usage: C::Preprocessed->new(filename[, defines[, includes[, cpp]]])"
934             if @_ < 2 or @_ > 5;
935 5         21 my ($class, $filename, $Defines, $Includes, $Cpp)
936             = (shift, shift, shift, shift, shift);
937 5   50     15 $Cpp ||= \%Config::Config;
938 5   50     181 my $filedir = dirname $filename || '.';
939 5   50     42 $Includes ||= [$filedir, '/usr/local/include', '.'];
940 5         13 my $addincludes = "";
941 5 50 33     49 $addincludes = "-I" . join(" -I", @$Includes)
942             if defined $Includes and @$Includes;
943 5         26 my($sym) = gensym;
944 5         165 my $cmd = WIN32 ?
945             "$Cpp->{cppstdin} $Defines $addincludes $Cpp->{cppflags} $filename |" :
946             "echo '\#include \"$filename\"' | $Cpp->{cppstdin} $Defines $addincludes $Cpp->{cppflags} $Cpp->{cppminus} |";
947             #my $cmd = "echo '\#include <$filename>' | $Cpp->{cppstdin} $Defines $addincludes $Cpp->{cppflags} $Cpp->{cppminus} |";
948            
949 5 50 50     54062 (open($sym, $cmd) or die "Cannot open pipe from `$cmd': $!")
950             and bless $sym => $class;
951             }
952            
953             sub text {
954 5     5   124 my $class = shift;
955 5         10 my $filter = shift;
956 5 50       18 if (defined $filter) {
957 0         0 return text_only_from($class, $filter, @_);
958             }
959 5         26 my $stream = $class->new(@_);
960 5         97 my $oh = select $stream;
961 5         637 local $/;
962 5         56 select $oh;
963 5         338631 <$stream>;
964             }
965            
966             sub text_only_from {
967 0     0   0 my $class = shift;
968 0   0     0 my $from = shift || die "Expecting argument in `text_only_from'";
969 0         0 my $stream = $class->new(@_);
970 0         0 my $on = $from eq $_[0];
971 0 0       0 my $eqregexp = $on ? '\"\"|' : '';
972 0         0 my @out;
973 0         0 while (<$stream>) {
974             #print;
975            
976 0 0       0 $on = /$eqregexp[\"\/]\Q$from\"/ if /^\#/;
977 0 0       0 push @out, $_ if $on;
978             }
979 0         0 join '', @out;
980             }
981            
982             sub DESTROY {
983 5 50   5   1763 close($_[0])
984             or die "Cannot close pipe from `$Config::Config{cppstdin}': err $?, $!\n";
985             }
986            
987             # Autoload methods go after __END__, and are processed by the autosplit program.
988             # Return to the principal package.
989             package ModPerl::CScan;
990            
991             1;
992             __END__