File Coverage

blib/lib/Inline/CPP/Parser/RecDescent.pm
Criterion Covered Total %
statement 47 54 87.0
branch 5 8 62.5
condition 4 6 66.6
subroutine 10 12 83.3
pod 0 6 0.0
total 66 86 76.7


line stmt bran cond sub pod time code
1 43     43   350 use strict; use warnings;
  43     43   321  
  43         1501  
  43         266  
  43         98  
  43         2700  
2             package Inline::CPP::Parser::RecDescent;
3              
4             # Dev versions will have a _0xx suffix.
5             # We eval the $VERSION to accommodate dev version numbering as described in
6             # perldoc perlmodstyle
7             our $VERSION = '0.75';
8             #$VERSION = eval $VERSION; ## no critic (eval)
9              
10 43     43   267 use Carp;
  43         101  
  43         6645  
11              
12             sub register {
13             {
14 0     0 0 0 extends => [qw(CPP)],
15             overrides => [qw(get_parser)],
16             }
17             }
18              
19             sub get_parser {
20 0     0 0 0 my $o = shift;
21 0         0 return Inline::CPP::Parser::RecDescent::get_parser_recdescent($o);
22             }
23              
24             sub get_parser_recdescent {
25 44     44 0 115 my $o = shift;
26 44         120 eval { require Parse::RecDescent };
  44         454  
27 44 50       215 croak <
28             This invocation of Inline requires the Parse::RecDescent module.
29             $@
30             END
31 43     43   292 no warnings qw/ once /; ## no critic (warnings)
  43         101  
  43         4484  
32 44         114 $::RD_HINT = 1; # Turns on Parse::RecDescent's warnings/diagnostics.
33 44         221 my $parser = Parse::RecDescent->new(grammar());
34 44         19192534 $parser->{data}{typeconv} = $o->{ILSM}{typeconv};
35 44         192 $parser->{ILSM} = $o->{ILSM}; # give parser access to config options
36 44         523 return $parser;
37             }
38              
39 43     43   295 use vars qw($TYPEMAP_KIND $class_part $class_decl $star);
  43         94  
  43         6006  
40              
41             # Parse::RecDescent 1.90 and later have an incompatible change
42             # 'The key of an %item entry for a repeated subrule now includes
43             # the repetition specifier.'
44             # Hence various hash keys may or may not need trailing '(s?)' depending on
45             # the version of Parse::RecDescent we are using.
46              
47             require Parse::RecDescent;
48              
49             # Deal with Parse::RecDescent's version numbers for development
50             # releases (eg, '1.96_000') resulting in a warning about non-numeric in >
51             # comparison.
52             { # Lexical scope.
53             # Eval away the underscore. "1.96_000" => "1.96000".
54             # Use that "stable release" version number as the basis for our numeric
55             # comparison.
56             my $stable_version = eval $Parse::RecDescent::VERSION; ## no critic (eval)
57             ($class_part, $class_decl, $star)
58             = map { ($stable_version > 1.89) ? "$_(s?)" : $_ }
59             qw ( class_part class_decl star );
60             } # End lexical scope.
61              
62              
63             #============================================================================
64             # Regular expressions to match code blocks, numbers, strings, parenthesized
65             # expressions, function calls, and macros. The more complex regexes are only
66             # implemented in 5.6.0 and above, so they're in eval-blocks.
67             #
68             # These are all adapted from the output of Damian Conway's excellent
69             # Regexp::Common module. In future, Inline::CPP may depend directly on it,
70             # but for now I'll just duplicate the code.
71 43     43   296 use vars qw( $code_block $string $number $parens $funccall );
  43         134  
  43         39016  
72              
73             #============================================================================
74              
75             # $RE{balanced}{-parens=>q|{}()[]"'|}
76             eval <<'END'; ## no critic (eval)
77             $code_block = qr'(?-xism:(?-xism:(?:[{](?:(?>[^][)(}{]+)|(??{$Inline::CPP::Parser::RecDescent::code_block}))*[}]))|(?-xism:(?-xism:(?:[(](?:(?>[^][)(}{]+)|(??{$Inline::CPP::Parser::RecDescent::code_block}))*[)]))|(?-xism:(?-xism:(?:[[](?:(?>[^][)(}{]+)|(??{$Inline::CPP::Parser::RecDescent::code_block}))*[]]))|(?-xism:(?!)))))';
78             END
79             $code_block = qr'{[^}]*}' if $@; # For the stragglers: here's a lame regexp.
80              
81             # $RE{balanced}{-parens=>q|()"'|}
82             eval <<'END'; ## no critic (eval)
83             $parens = qr'(?-xism:(?-xism:(?:[(](?:(?>[^)(]+)|(??{$Inline::CPP::Parser::RecDescent::parens}))*[)]))|(?-xism:(?!)))';
84             END
85             $parens = qr'\([^)]*\)' if $@; # For the stragglers: here's another
86              
87             # $RE{quoted}
88             $string
89             = qr'(?:(?:\")(?:[^\\\"]*(?:\\.[^\\\"]*)*)(?:\")|(?:\')(?:[^\\\']*(?:\\.[^\\\']*)*)(?:\')|(?:\`)(?:[^\\\`]*(?:\\.[^\\\`]*)*)(?:\`))';
90              
91             # $RE{num}{real}|$RE{num}{real}{-base=>16}|$RE{num}{int}
92             $number
93             = qr'(?:(?i)(?:[+-]?)(?:(?=[0123456789]|[.])(?:[0123456789]*)(?:(?:[.])(?:[0123456789]{0,}))?)(?:(?:[E])(?:(?:[+-]?)(?:[0123456789]+))|))|(?:(?i)(?:[+-]?)(?:(?=[0123456789ABCDEF]|[.])(?:[0123456789ABCDEF]*)(?:(?:[.])(?:[0123456789ABCDEF]{0,}))?)(?:(?:[G])(?:(?:[+-]?)(?:[0123456789ABCDEF]+))|))|(?:(?:[+-]?)(?:\d+))';
94             $funccall
95             = qr/(?:[_a-zA-Z][_a-zA-Z0-9]*::)*[_a-zA-Z][_a-zA-Z0-9]*(?:$Inline::CPP::Parser::RecDescent::parens)?/;
96              
97             #============================================================================
98             # Inline::CPP's grammar
99             #============================================================================
100             sub grammar {
101 44     44 0 487 return <<'END';
102              
103             { use Data::Dumper; }
104              
105             {
106             sub handle_class_def {
107             my ($thisparser, $def) = @_;
108             # print "Found a class: $def->[0]\n";
109             my $class = $def->[0];
110             my @parts;
111             for my $part (@{$def->[1]}) { push @parts, @$_ for @$part }
112             push @{$thisparser->{data}{classes}}, $class
113             unless defined $thisparser->{data}{class}{$class};
114             $thisparser->{data}{class}{$class} = \@parts;
115             # print "Class $class:\n", Dumper \@parts;
116             Inline::CPP::Parser::RecDescent::typemap($thisparser, $class);
117             [$class, \@parts];
118             }
119             sub handle_typedef {
120             my ($thisparser, $t) = @_;
121             my ($name, $type) = @{$t}{qw(name type)};
122             # print "found a typedef: $name => $type\n";
123              
124             # XXX: this doesn't handle non-class typedefs that we could handle,
125             # e.g. "typedef int my_int_t"
126              
127             if ($thisparser->{data}{class}{$type}
128             && !exists($thisparser->{data}{class}{$name})) {
129             push @{$thisparser->{data}{classes}}, $name;
130             $thisparser->{data}{class}{$name} = $thisparser->{data}{class}{$type};
131             Inline::CPP::Parser::RecDescent::typemap($thisparser, $name);
132             }
133             $t;
134             }
135             sub handle_enum {
136             my ($thisparser, $t) = @_;
137             $t;
138             }
139             }
140              
141             code: part(s) {1}
142              
143             part: comment
144             | typedef
145             {
146             handle_typedef($thisparser, $item[1]);
147             1;
148             }
149             | enum
150             {
151             my $t = handle_enum($thisparser, $item[1]);
152             push @{$thisparser->{data}{enums}}, $t;
153             1;
154             }
155             | class_def
156             {
157             handle_class_def($thisparser, $item[1]);
158             1;
159             }
160             | function_def
161             {
162             # print "found a function: $item[1]->{name}\n";
163             my $name = $item[1]->{name};
164             my $i=0;
165             for my $arg (@{$item[1]->{args}}) {
166             $arg->{name} = 'dummy' . ++$i unless defined $arg->{name};
167             }
168             Inline::CPP::Parser::RecDescent::strip_ellipsis($thisparser,
169             $item[1]->{args});
170             push @{$thisparser->{data}{functions}}, $name
171             unless defined $thisparser->{data}{function}{$name};
172             $thisparser->{data}{function}{$name} = $item[1];
173             # print Dumper $item[1];
174             1;
175             }
176             | all
177              
178             typedef: 'typedef' class IDENTIFIER(?) '{' class_part(s?) '}' IDENTIFIER ';'
179             {
180             my ($class, $parts);
181             $class = $item[3][0] || 'anon_class'.($thisparser->{data}{anonclass}++);
182             ($class, $parts)= handle_class_def($thisparser, [$class, $item{$Inline::CPP::Parser::RecDescent::class_part}]);
183             { thing => 'typedef', name => $item[8], type => $class, body => $parts }
184             }
185             | 'typedef' IDENTIFIER IDENTIFIER ';'
186             { { thing => 'typedef', name => $item[3], type => $item[2] } }
187             | 'typedef' /[^;]*/ ';'
188             {
189             # dprint "Typedef $item{__DIRECTIVE1__} is too heinous\n";
190             { thing => 'comment'}
191             }
192              
193             enum: 'enum' IDENTIFIER(?) '{' '}' ';'
194             {
195             { thing => 'enum', name => $item{IDENTIFIER}[0],
196             body => $item{__DIRECTIVE1__} }
197             }
198              
199             enum_item: IDENTIFIER '=' /[0-9]+/
200             { [$item{IDENTIFIER}, $item{__PATTERN1__}] }
201             | IDENTIFIER
202             { [$item{IDENTIFIER}, undef] }
203              
204             class_def: class IDENTIFIER '{' class_part(s?) '}' ';'
205             {
206             [@item{'IDENTIFIER',$Inline::CPP::Parser::RecDescent::class_part}]
207             }
208             | class IDENTIFIER ':'
209             '{' class_part(s?) '}' ';'
210             {
211             push @{$item{$Inline::CPP::Parser::RecDescent::class_part}}, [$item{__DIRECTIVE2__}];
212             [@item{'IDENTIFIER',$Inline::CPP::Parser::RecDescent::class_part}]
213             }
214              
215             inherit: scope IDENTIFIER
216             { {thing => 'inherits', name => $item[2], scope => $item[1]} }
217              
218             class_part: comment { [ {thing => 'comment'} ] }
219             | scope ':' class_decl(s?)
220             {
221             for my $part (@{$item{$Inline::CPP::Parser::RecDescent::class_decl}}) {
222             $_->{scope} = $item[1] for @$part;
223             }
224             $item{$Inline::CPP::Parser::RecDescent::class_decl}
225             }
226             | class_decl(s)
227             {
228             for my $part (@{$item[1]}) {
229             $_->{scope} = $thisparser->{data}{defaultscope}
230             for @$part;
231             }
232             $item[1]
233             }
234              
235             class_decl: comment { [{thing => 'comment'}] }
236             | typedef { [ handle_typedef($thisparser, $item[1]) ] }
237             | enum { [ handle_enum($thisparser, $item[1]) ] }
238             | class_def
239             {
240             my ($class, $parts) = handle_class_def($thisparser, $item[1]);
241             [{ thing => 'class', name => $class, body => $parts }];
242             }
243             | method_def
244             {
245             $item[1]->{thing} = 'method';
246             # print "class_decl found a method: $item[1]->{name}\n";
247             my $i=0;
248             for my $arg (@{$item[1]->{args}}) {
249             $arg->{name} = 'dummy' . ++$i unless defined $arg->{name};
250             }
251             Inline::CPP::Parser::RecDescent::strip_ellipsis($thisparser,
252             $item[1]->{args});
253             [$item[1]];
254             }
255             | member_def
256             {
257             # print "class_decl found one or more members:\n", Dumper(\@item);
258             $_->{thing} = 'member' for @{$item[1]};
259             $item[1];
260             }
261              
262             function_def: operator ';'
263             {
264             $item[1]
265             }
266             | operator smod(?) code_block
267             {
268             $item[1]
269             }
270             | IDENTIFIER '(' (s?) ')' smod(?) code_block
271             {
272             {name => $item{IDENTIFIER}, args => $item{__DIRECTIVE2__}, rtype => '' }
273             }
274             | rtype IDENTIFIER '(' (s?) ')' ';'
275             {
276             {rtype => $item[1], name => $item[2], args => $item{__DIRECTIVE1__} }
277             }
278             | rtype IDENTIFIER '(' (s?) ')' smod(?) code_block
279             {
280             {rtype => $item{rtype}, name => $item[2], args => $item{__DIRECTIVE1__} }
281             }
282              
283             method_def: operator method_imp
284             {
285             # print "method operator:\n", Dumper $item[1];
286             $item[1];
287             }
288              
289             | IDENTIFIER '(' (s?) ')' method_imp
290             {
291             # print "con-/de-structor found: $item[1]\n";
292             {name => $item[1], args => $item{__DIRECTIVE2__}, abstract => ${$item{method_imp}} };
293             }
294             | rtype IDENTIFIER '(' (s?) ')' method_imp
295             {
296             # print "method found: $item[2]\n";
297             $return =
298             {name => $item[2], rtype => $item[1], args => $item[4],
299             abstract => ${$item[6]},
300             rconst => $thisparser->{data}{smod}{const},
301             };
302             $thisparser->{data}{smod}{const} = 0;
303             }
304              
305             operator: rtype(?) 'operator' /\(\)|[^()]+/ '(' (s?) ')'
306             {
307             # print "Found operator: $item[1][0] operator $item[3]\n";
308             {name=> "operator $item[3]", args => $item[5], ret => $item[1][0]}
309             }
310              
311             # By adding smod, we allow 'const' member functions. This would also bind to
312             # incorrect C++ with the word 'static' after the argument list, but we don't
313             # care at all because such code would never be compiled successfully.
314              
315             # By adding init, we allow constructors to initialize references. Again, we'll
316             # allow them anywhere, but our goal is not to enforce c++ standards -- that's
317             # the compiler's job.
318             method_imp: smod(?) ';' { \0 }
319             | smod(?) '=' '0' ';' { \1 }
320             | smod(?) initlist(?) code_block { \0 }
321             | smod(?) '=' '0' code_block { \0 }
322              
323             initlist: ':'
324              
325             member_def: anytype ';'
326             {
327             my @retval;
328             for my $def (@{$item[2]}) {
329             my $type = join '', $item[1], @{$def->[0]};
330             my $name = $def->[1];
331             # print "member found: type=$type, name=$name\n";
332             push @retval, { name => $name, type => $type };
333             }
334             \@retval;
335             }
336              
337             var: star(s?) IDENTIFIER '=' expr { [@item[1,2]] }
338             | star(s?) IDENTIFIER '[' expr ']' { [@item[1,2]] }
339             | star(s?) IDENTIFIER { [@item[1,2]] }
340              
341             arg: type IDENTIFIER '=' expr
342             {
343             # print "argument $item{IDENTIFIER} found\n";
344             # print "expression: $item{expr}\n";
345             {type => $item[1], name => $item{IDENTIFIER}, optional => 1,
346             offset => $thisoffset}
347             }
348             | type IDENTIFIER
349             {
350             # print "argument $item{IDENTIFIER} found\n";
351             {type => $item[1], name => $item{IDENTIFIER}, offset => $thisoffset}
352             }
353             | type { {type => $item[1]} }
354             | '...'
355             { {name => '...', type => '...', offset => $thisoffset} }
356              
357             ident_part: /[~_a-z]\w*/i '<' (s?) '>'
358             {
359             $item[1].'<'.join('', @{$item[4]}).'>'
360             }
361              
362             | /[~_a-z]\w*/i
363             {
364             $item[1]
365             }
366              
367             IDENTIFIER:
368             {
369             my $x = join '::', @{$item[1]};
370             # print "IDENTIFIER: $x\n";
371             $x
372             }
373              
374             # Parse::RecDescent is retarded in this one case: if a subrule fails, it
375             # gives up the entire rule. This is a stupid way to get around that.
376             rtype: rtype2 | rtype1
377             rtype1: TYPE star(s?)
378             {
379             $return = $item[1];
380             $return .= join '',' ',@{$item[2]} if @{$item[2]};
381             # print "rtype1: $return\n";
382             # return undef
383             # unless(defined$thisparser->{data}{typeconv}{valid_rtypes}{$return});
384             }
385             rtype2: modifier(s) TYPE star(s?)
386             {
387             $return = $item[2];
388             $return = join ' ',grep{$_}@{$item[1]},$return
389             if @{$item[1]};
390             $return .= join '',' ',@{$item[3]} if @{$item[3]};
391             # print "rtype2: $return\n";
392             # return undef
393             # unless(defined$thisparser->{data}{typeconv}{valid_rtypes}{$return});
394             $return = 'static ' . $return
395             if $thisparser->{data}{smod}{static};
396             $thisparser->{data}{smod}{static} = 0;
397             }
398              
399             type: type2 | type1
400             type1: TYPE star(s?)
401             {
402             $return = $item[1];
403             $return .= join '',' ',@{$item{$Inline::CPP::Parser::RecDescent::star}} if @{$item{$Inline::CPP::Parser::RecDescent::star}};
404             # print "type1: $return\n";
405             # return undef
406             # unless(defined$thisparser->{data}{typeconv}{valid_types}{$return});
407             }
408             type2: modifier(s) TYPE star(s?)
409             {
410             $return = $item{TYPE};
411             $return = join ' ',grep{$_}@{$item[1]},$return if @{$item[1]};
412             $return .= join '',' ',@{$item{$Inline::CPP::Parser::RecDescent::star}} if @{$item{$Inline::CPP::Parser::RecDescent::star}};
413             # print "type2: $return\n";
414             # return undef
415             # unless(defined$thisparser->{data}{typeconv}{valid_types}{$return});
416             }
417              
418             anytype: anytype2 | anytype1
419             anytype1: TYPE star(s?)
420             {
421             $return = $item[1];
422             $return .= join '',' ',@{$item[2]} if @{$item[2]};
423             }
424             anytype2: modifier(s) TYPE star(s?)
425             {
426             $return = $item[2];
427             $return = join ' ',grep{$_}@{$item[1]},$return if @{$item[1]};
428             $return .= join '',' ',@{$item[3]} if @{$item[3]};
429             }
430              
431             comment: m{\s* // [^\n]* \n }x
432             | m{\s* /\* (?:[^*]+|\*(?!/))* \*/ ([ \t]*)? }x
433              
434             # long and short aren't recognized as modifiers because they break when used
435             # as regular types. Another Parse::RecDescent problem is greedy matching; I
436             # need tmodifier to "give back" long or short in cases where keeping them would
437             # cause the modifier rule to fail. One side-effect is 'long long' can never
438             # be parsed correctly here.
439             modifier: tmod
440             | smod { ++$thisparser->{data}{smod}{$item[1]}; ''}
441             | nmod { '' }
442             tmod: 'unsigned' # | 'long' | 'short'
443             smod: 'const' | 'static'
444             nmod: 'extern' | 'virtual' | 'mutable' | 'volatile' | 'inline'
445              
446             scope: 'public' | 'private' | 'protected'
447              
448             class: 'class' { $thisparser->{data}{defaultscope} = 'private'; $item[1] }
449             | 'struct' { $thisparser->{data}{defaultscope} = 'public'; $item[1] }
450              
451             star: '*' | '&'
452              
453             code_block: /$Inline::CPP::Parser::RecDescent::code_block/
454              
455             # Consume expressions
456             expr: {
457             my $o = join '', @{$item[1]};
458             # print "expr: $o\n";
459             $o;
460             }
461             subexpr: /$Inline::CPP::Parser::RecDescent::funccall/ # Matches a macro, too
462             | /$Inline::CPP::Parser::RecDescent::string/
463             | /$Inline::CPP::Parser::RecDescent::number/
464             | UOP subexpr
465             OP: '+' | '-' | '*' | '/' | '^' | '&' | '|' | '%' | '||' | '&&'
466             UOP: '~' | '!' | '-' | '*' | '&'
467              
468             TYPE: IDENTIFIER
469              
470             all: /.*/
471              
472             END
473             }
474              
475             #============================================================================
476             # Generate typemap code for the classes and structs we bind to. This allows
477             # functions declared after a class to return or accept class objects as
478             # parameters.
479             #============================================================================
480             $TYPEMAP_KIND = 'O_Inline_CPP_Class';
481              
482             sub typemap {
483 49     49 0 1406889 my ($parser, $typename) = @_;
484              
485             # print "Inline::CPP::Parser::RecDescent::typemap(): typename=$typename\n";
486              
487 49         193 my ($TYPEMAP, $INPUT, $OUTPUT);
488 49         287 $TYPEMAP = "$typename *\t\t$TYPEMAP_KIND\n";
489 49         164 $INPUT = <<"END";
490             if (sv_isobject(\$arg) && (SvTYPE(SvRV(\$arg)) == SVt_PVMG)) {
491             \$var = (\$type)SvIV((SV*)SvRV( \$arg ));
492             }
493             else {
494             warn ( \\"\${Package}::\$func_name() -- \$var is not a blessed reference\\" );
495             XSRETURN_UNDEF;
496             }
497             END
498 49         129 $OUTPUT = <<"END";
499             sv_setref_pv( \$arg, CLASS, (void*)\$var );
500             END
501              
502 49         175 my $ctypename = $typename . ' *';
503 49   66     590 $parser->{data}{typeconv}{input_expr}{$TYPEMAP_KIND} ||= $INPUT;
504 49   66     427 $parser->{data}{typeconv}{output_expr}{$TYPEMAP_KIND} ||= $OUTPUT;
505 49         235 $parser->{data}{typeconv}{type_kind}{$ctypename} = $TYPEMAP_KIND;
506 49         206 $parser->{data}{typeconv}{valid_types}{$ctypename}++;
507 49         173 $parser->{data}{typeconv}{valid_rtypes}{$ctypename}++;
508 49         1397 return;
509             }
510              
511             #============================================================================
512             # Default action is to strip ellipses from the C++ code. This allows having
513             # _only_ a '...' in the code, just like XS. It is the default.
514             #============================================================================
515             sub strip_ellipsis {
516 173     173 0 425881 my ($parser, $args) = @_;
517 173 50       1022 return if $parser->{ILSM}{PRESERVE_ELLIPSIS};
518 173         1109 for (my $i = 0; $i < @$args; $i++) {
519 57 100       275 next unless $args->[$i]{name} eq '...';
520              
521             # if it's the first one, just strip it
522 1 50       4 if ($i == 0) {
523 1         7 substr($parser->{ILSM}{code}, $args->[$i]{offset} - 3, 3, ' ');
524             }
525             else {
526 0         0 my $prev = $i - 1;
527 0         0 my $prev_offset = $args->[$prev]{offset};
528 0         0 my $length = $args->[$i]{offset} - $prev_offset;
529 0         0 substr($parser->{ILSM}{code}, $prev_offset, $length) =~ s/\S/ /g;
530             }
531             }
532 173         3895 return;
533             }
534              
535             my $hack = sub { # Appease -w using Inline::Files
536             print Parse::RecDescent::IN '';
537             print Parse::RecDescent::IN '';
538             print Parse::RecDescent::TRACE_FILE '';
539             print Parse::RecDescent::TRACE_FILE '';
540             };
541              
542             1;
543              
544             =head1 Inline::CPP::Parser::RecDescent
545              
546             All functions are internal. No documentation necessary.
547              
548             =cut