File Coverage

blib/lib/ExtUtils/XSpp/Lexer.pm
Criterion Covered Total %
statement 247 261 94.6
branch 63 80 78.7
condition 15 17 88.2
subroutine 51 53 96.2
pod 0 27 0.0
total 376 438 85.8


line stmt bran cond sub pod time code
1             # this module will be loaded by ExtUtils/XSpp/Grammar.pm and needs to
2             # define subroutines in the ExtUtils::XSpp::Grammar namespace
3             package ExtUtils::XSpp::Lexer;
4             # for the indexer and friends
5 21     21   109 use strict;
  21         37  
  21         606  
6 21     21   62 use warnings;
  21         28  
  21         879  
7              
8             package ExtUtils::XSpp::Grammar;
9              
10 21     21   804 use ExtUtils::XSpp::Node;
  21         30  
  21         456  
11 21     21   7596 use ExtUtils::XSpp::Node::Access;
  21         49  
  21         547  
12 21     21   7517 use ExtUtils::XSpp::Node::Argument;
  21         74  
  21         501  
13 21     21   8167 use ExtUtils::XSpp::Node::Class;
  21         48  
  21         543  
14 21     21   7507 use ExtUtils::XSpp::Node::Comment;
  21         59  
  21         486  
15 21     21   7608 use ExtUtils::XSpp::Node::Constructor;
  21         61  
  21         532  
16 21     21   7955 use ExtUtils::XSpp::Node::Destructor;
  21         62  
  21         549  
17 21     21   7480 use ExtUtils::XSpp::Node::File;
  21         54  
  21         474  
18 21     21   105 use ExtUtils::XSpp::Node::Function;
  21         29  
  21         324  
19 21     21   8210 use ExtUtils::XSpp::Node::Member;
  21         67  
  21         565  
20 21     21   106 use ExtUtils::XSpp::Node::Method;
  21         29  
  21         383  
21 21     21   7356 use ExtUtils::XSpp::Node::Module;
  21         61  
  21         472  
22 21     21   116 use ExtUtils::XSpp::Node::Package;
  21         30  
  21         346  
23 21     21   62 use ExtUtils::XSpp::Node::Raw;
  21         26  
  21         414  
24 21     21   797 use ExtUtils::XSpp::Node::Type;
  21         28  
  21         921  
25 21     21   7657 use ExtUtils::XSpp::Node::PercAny;
  21         55  
  21         489  
26 21     21   7846 use ExtUtils::XSpp::Node::Enum;
  21         54  
  21         466  
27 21     21   7293 use ExtUtils::XSpp::Node::EnumValue;
  21         78  
  21         514  
28 21     21   7205 use ExtUtils::XSpp::Node::Preprocessor;
  21         54  
  21         530  
29              
30 21     21   901 use ExtUtils::XSpp::Typemap;
  21         34  
  21         497  
31 21     21   8017 use ExtUtils::XSpp::Exception;
  21         52  
  21         639  
32              
33 21     21   106 use Digest::MD5 qw(md5_hex);
  21         35  
  21         61302  
34              
35             my %tokens = ( '::' => 'DCOLON',
36             ':' => 'COLON',
37             '%{' => 'OPSPECIAL',
38             '%}' => 'CLSPECIAL',
39             '{%' => 'OPSPECIAL',
40             '{' => 'OPCURLY',
41             '}' => 'CLCURLY',
42             '(' => 'OPPAR',
43             ')' => 'CLPAR',
44             ';' => 'SEMICOLON',
45             '%' => 'PERC',
46             '~' => 'TILDE',
47             '*' => 'STAR',
48             '&' => 'AMP',
49             '|' => 'PIPE',
50             ',' => 'COMMA',
51             '=' => 'EQUAL',
52             '/' => 'SLASH',
53             '.' => 'DOT',
54             '-' => 'DASH',
55             '<' => 'OPANG',
56             '>' => 'CLANG',
57             # these are here due to my lack of skill with yacc
58             '%name' => 'p_name',
59             '%typemap' => 'p_typemap',
60             '%exception' => 'p_exceptionmap',
61             '%catch' => 'p_catch',
62             '%file' => 'p_file',
63             '%module' => 'p_module',
64             '%code' => 'p_code',
65             '%cleanup' => 'p_cleanup',
66             '%postcall' => 'p_postcall',
67             '%package' => 'p_package',
68             '%length' => 'p_length',
69             '%loadplugin' => 'p_loadplugin',
70             '%include' => 'p_include',
71             '%alias' => 'p_alias',
72             '%_type' => 'p__type',
73             );
74              
75             my %keywords = ( const => 1,
76             class => 1,
77             unsigned => 1,
78             short => 1,
79             long => 1,
80             int => 1,
81             char => 1,
82             void => 1,
83             package_static => 1,
84             class_static => 1,
85             static => 1,
86             public => 1,
87             private => 1,
88             protected => 1,
89             virtual => 1,
90             enum => 1,
91             );
92              
93 3603   100 3603 0 4663 sub get_lex_mode { return $_[0]->YYData->{LEX}{MODES}[0] || '' }
94              
95             sub push_lex_mode {
96 33     33 0 77 my( $p, $mode ) = @_;
97              
98 33         52 push @{$p->YYData->{LEX}{MODES}}, $mode;
  33         113  
99             }
100              
101             sub pop_lex_mode {
102 33     33 0 160 my( $p, $mode ) = @_;
103              
104 33 50       57 die "Unexpected mode: '$mode'"
105             unless get_lex_mode( $p ) eq $mode;
106              
107 33         44 pop @{$p->YYData->{LEX}{MODES}};
  33         55  
108             }
109              
110             sub read_more {
111 841     841 0 1295 my $v = readline $_[0]->YYData->{LEX}{FH};
112 841         1238 my $buf = $_[0]->YYData->{LEX}{BUFFER};
113              
114 841 100       1172 unless( defined $v ) {
115 88 100       305 if( $_[0]->YYData->{LEX}{NEXT} ) {
116 2         13 $_[0]->YYData->{LEX} = $_[0]->YYData->{LEX}{NEXT};
117 2         4 $buf = $_[0]->YYData->{LEX}{BUFFER};
118              
119 2 50       9 return $buf if length $$buf;
120 0         0 return read_more( $_[0] );
121             } else {
122 86         268 return;
123             }
124             }
125              
126 753         1064 $$buf .= $v;
127              
128 753         1552 return $buf;
129             }
130              
131             # for tests
132 0     0   0 sub _random_digits { sprintf '%06d', rand 100000 }
133              
134             sub push_conditional {
135 12     12 0 18 my $p = $_[0];
136             my $file = $p->YYData->{LEX}{FILE} ?
137 12 50       33 substr md5_hex( $p->YYData->{LEX}{FILE} ), 0, 8 :
138             'zzzzzzzz';
139 12         26 my $rand = _random_digits;
140              
141 12         48 my $symbol = 'XSpp_' . $file . '_' . $rand;
142 12         12 push @{$p->YYData->{LEX}{CONDITIONAL}}, $symbol;
  12         22  
143              
144 12         20 return $symbol;
145             }
146              
147             sub pop_conditional {
148 12     12 0 12 pop @{$_[0]->YYData->{LEX}{CONDITIONAL}};
  12         17  
149             }
150              
151             sub get_conditional {
152 171 100   171 0 334 return undef unless $_[0]->YYData->{LEX}{CONDITIONAL};
153 8 50       8 return undef unless @{$_[0]->YYData->{LEX}{CONDITIONAL}};
  8         11  
154 8         14 return $_[0]->YYData->{LEX}{CONDITIONAL}[-1];
155             }
156              
157             sub yylex {
158 2972     2972 0 4632 my $data = $_[0]->YYData->{LEX};
159 2972         3353 my $buf = $data->{BUFFER};
160              
161 2972         2761 for(;;) {
162 3656 100 100     5356 if( !length( $$buf ) && !( $buf = read_more( $_[0] ) ) ) {
163 86         229 return ( '', undef );
164             }
165              
166 3570 100       4466 if( get_lex_mode( $_[0] ) eq 'special' ) {
167 81 100       411 if( $$buf =~ s/^%}// ) {
    50          
168 33         204 return ( 'CLSPECIAL', '%}' );
169             } elsif( $$buf =~ s/^([^\n]*)\n$// ) {
170 48         89 my $line = $1;
171              
172 48 100       153 if( $line =~ m/^(.*?)\%}(.*)$/ ) {
173 27         81 $$buf = "%}$2\n";
174 27         63 $line = $1;
175             }
176              
177 48         134 return ( 'line', $line );
178             }
179             } else {
180 3489         6470 $$buf =~ s/^[\s\n\r]+//;
181 3489 100       4840 next unless length $$buf;
182              
183 2805 100       14378 if( $$buf =~ s/^([+-]?0x[0-9a-fA-F]+)// ) {
    100          
    100          
    100          
    100          
    100          
    50          
    100          
    50          
    100          
    50          
184 1         3 return ( 'INTEGER', $1 );
185             } elsif( $$buf =~ s/^([+-]?(?=\d|\.\d)\d*(?:\.\d*)?(?:[Ee](?:[+-]?\d+))?)// ) {
186 14         26 my $v = $1;
187 14 50       66 return ( 'INTEGER', $v ) if $v =~ /^[+-]?\d+$/;
188 0         0 return ( 'FLOAT', $v );
189             } elsif( $$buf =~ s/^\/\/(.*)(?:\r\n|\r|\n)// ) {
190 4         15 return ( 'COMMENT', [ $1 ] );
191             } elsif( $$buf =~ /^\/\*/ ) {
192 1         2 my @rows;
193 1   66     5 for(; length( $$buf ) || ( $buf = read_more( $_[0] ) ); $$buf = '') {
194 4 100       9 if( $$buf =~ s/(.*?\*\/)// ) {
195 1         3 push @rows, $1;
196 1         5 return ( 'COMMENT', \@rows );
197             }
198 3         10 $$buf =~ s/(?:\r\n|\r|\n)$//;
199 3         21 push @rows, $$buf;
200             }
201             } elsif( $$buf =~ s/^(\%\w+)// ) {
202 300 100       1459 return ( $tokens{$1}, $1 ) if exists $tokens{$1};
203 59         168 return ( 'p_any', substr $1, 1 );
204             } elsif( $$buf =~ s/^( \%}
205             | \%\{ | \{\%
206             | [{}();%~*&,=\/\.\-<>|]
207             | :: | :
208             )//x ) {
209 1491         4405 return ( $tokens{$1}, $1 );
210             } elsif( $$buf =~ s/^(INCLUDE(?:_COMMAND)?:.*)(?:\r\n|\r|\n)// ) {
211 0         0 return ( 'RAW_CODE', "$1\n" );
212             } elsif( $$buf =~ s/^([a-zA-Z_]\w*)// ) {
213 954 100       2539 return ( $1, $1 ) if exists $keywords{$1};
214              
215 638         1559 return ( 'ID', $1 );
216             } elsif( $$buf =~ s/^("[^"]*")// ) {
217 0         0 return ( 'QUOTED_STRING', $1 );
218             } elsif( $$buf =~ s/^(#\s*(if|ifdef|ifndef|else|elif|endif)\b.*)(?:\r\n|\r|\n)// ) {
219 22         25 my $symbol;
220 22 100 100     90 if( $2 eq 'else' || $2 eq 'elif' || $2 eq 'endif' ) {
      100        
221 12         19 pop_conditional( $_[0] );
222             }
223 22 100       36 if( $2 ne 'endif' ) {
224 12         19 $symbol = push_conditional( $_[0] );
225             }
226              
227 22         70 return ( 'PREPROCESSOR', [ $1, $symbol ] );
228             } elsif( $$buf =~ s/^(#.*)(?:\r\n|\r|\n)// ) {
229 18         68 return ( 'RAW_CODE', $1 );
230             } else {
231 0         0 die $$buf;
232             }
233             }
234             }
235             }
236              
237             sub yyerror {
238 0     0 0 0 my $data = $_[0]->YYData->{LEX};
239 0         0 my $buf = $data->{BUFFER};
240 0         0 my $fh = $data->{FH};
241              
242 0 0       0 print STDERR "Error: line " . $fh->input_line_number . " (Current token type: '",
243             $_[0]->YYCurtok, "') (Current value: '",
244             $_[0]->YYCurval, '\') Buffer: "', ( $buf ? $$buf : '--empty buffer--' ),
245             q{"} . "\n";
246 0         0 print STDERR "Expecting: (", ( join ", ", map { "'$_'" } $_[0]->YYExpect ),
  0         0  
247             ")\n";
248             }
249              
250 16     16 0 21 sub make_const { $_[0]->{CONST} = 1; $_[0] }
  16         21  
251 13     13 0 39 sub make_ref { $_[0]->{REFERENCE} = 1; $_[0] }
  13         19  
252 9     9 0 29 sub make_ptr { $_[0]->{POINTER}++; $_[0] }
  9         27  
253 256     256 0 876 sub make_type { ExtUtils::XSpp::Node::Type->new( base => $_[0] ) }
254              
255             sub make_template {
256 9     9 0 22 ExtUtils::XSpp::Node::Type->new( base => $_[0],
257             template_args => $_[1],
258             )
259             }
260              
261             sub add_typemap {
262 28     28 0 64 my( $name, $type, @args ) = @_;
263 28         130 my $tm = ExtUtils::XSpp::Typemap::create( $name, type => $type, @args );
264              
265 28         143 ExtUtils::XSpp::Typemap::add_typemap_for_type( $type, $tm );
266             }
267              
268             sub add_data_raw {
269 24     24 0 32 my $p = shift;
270 24         29 my $rows = shift;
271              
272 24         112 ExtUtils::XSpp::Node::Raw->new( rows => $rows );
273             }
274              
275             sub add_data_comment {
276 5     5 0 7 my $p = shift;
277 5         8 my $rows = shift;
278              
279 5         37 ExtUtils::XSpp::Node::Comment->new( rows => $rows );
280             }
281              
282             sub add_top_level_directive {
283 2     2 0 5 my( $parser, %args ) = @_;
284              
285             $parser->YYData->{PARSER}->handle_toplevel_tag_plugins
286             ( $args{any},
287             named => $args{named},
288             positional => $args{positional},
289             any_named_arguments => $args{named},
290             any_positional_arguments => $args{positional},
291 2         5 condition => $parser->get_conditional,
292             );
293             }
294              
295             sub make_argument {
296 114     114 0 219 my( $p, $type, $name, $default, @args ) = @_;
297 114         195 my %args = @args;
298 114         334 _merge_keys( 'tag', \%args, \@args );
299              
300             my $arg = ExtUtils::XSpp::Node::Argument->new
301             ( type => $type,
302             name => $name,
303             default => $default,
304 114         563 tags => $args{tag} );
305              
306 114         261 return $arg;
307             }
308              
309             sub create_class {
310 54     54 0 160 my( $parser, $name, $bases, $metadata, $methods, $condition ) = @_;
311 54         113 my %args = @$metadata;
312 54         153 _merge_keys( 'catch', \%args, $metadata );
313              
314 54         403 my $class = ExtUtils::XSpp::Node::Class->new( %args, # <-- catch only for now
315             cpp_name => $name,
316             base_classes => $bases,
317             condition => $condition,
318             );
319              
320             # when adding a class C, automatically add weak typemaps for C* and C&
321 54         224 ExtUtils::XSpp::Typemap::add_class_default_typemaps( $name );
322              
323 54         482 my @any = grep $_->isa( 'ExtUtils::XSpp::Node::PercAny' ), @$methods;
324 54         205 my @rest = grep !$_->isa( 'ExtUtils::XSpp::Node::PercAny' ), @$methods;
325              
326             # finish creating the class
327 54         175 $class->add_methods( @rest );
328              
329 54         241 foreach my $meth ( grep $_->isa( 'ExtUtils::XSpp::Node::Method' ), @rest ) {
330 51         136 call_argument_tags( $parser, $meth );
331              
332 51         104 my $nodes = $parser->YYData->{PARSER}->handle_method_tags_plugins( $meth, $meth->tags );
333              
334 51         125 $class->add_methods( @$nodes );
335             }
336              
337 54         176 foreach my $any ( @any ) {
338 8 100       17 if( $any->{NAME} eq 'accessors' ) {
339             # TODO use plugin infrastructure, add decent validation
340 6         7 my %args = @{$any->{NAMED_ARGUMENTS}};
  6         23  
341 6 50       12 if( $args{get_style} ) {
342 6 50       6 if( @{$args{get_style}} ) {
  6         12  
343 6         13 $class->set_getter_style( $args{get_style}[0][0] );
344             } else {
345 0         0 die "Invalid accessor style declaration";
346             }
347             }
348 6 50       10 if( $args{set_style} ) {
349 6 50       6 if( @{$args{set_style}} ) {
  6         10  
350 6         42 $class->set_setter_style( $args{set_style}[0][0] );
351             } else {
352 0         0 die "Invalid accessor style declaration";
353             }
354             }
355 6         10 next;
356             }
357              
358             my $nodes = $parser->YYData->{PARSER}->handle_class_tag_plugins
359             ( $class, $any->{NAME},
360             named => $any->{NAMED_ARGUMENTS},
361             positional => $any->{POSITIONAL_ARGUMENTS},
362             any_named_arguments => $any->{NAMED_ARGUMENTS},
363             any_positional_arguments => $any->{POSITIONAL_ARGUMENTS},
364 2         5 );
365              
366 2         5 $class->add_methods( @$nodes );
367             }
368              
369 54         180 return $class;
370             }
371              
372             # support multiple occurrances of specific keys
373             # => transform to flattened array ref
374             sub _merge_keys {
375 458     458   550 my $key = shift;
376 458         450 my $argshash = shift;
377 458         484 my $paramlist = shift;
378 458         634 my @occurrances;
379 458         862 for (my $i = 0; $i < @$paramlist; $i += 2) {
380 1679 100 66     3749 if (defined $paramlist->[$i] and $paramlist->[$i] eq $key) {
381 52         123 push @occurrances, $paramlist->[$i+1];
382             }
383             }
384 458 100       644 @occurrances = map {ref($_) eq 'ARRAY' ? @$_ : $_} @occurrances;
  52         133  
385 458         921 $argshash->{$key} = \@occurrances;
386             }
387              
388              
389             sub create_member {
390 12     12 0 33 my( $parser, @args ) = @_;
391 12         43 my %args = @args;
392 12         47 _merge_keys( 'tag', \%args, \@args );
393              
394             return ExtUtils::XSpp::Node::Member->new
395             ( cpp_name => $args{name},
396             perl_name => $args{perl_name},
397             class => $args{class},
398             type => $args{type},
399             condition => $args{condition},
400             tags => $args{tag},
401 12         77 );
402             }
403              
404             sub add_data_function {
405 44     44 0 171 my( $parser, @args ) = @_;
406 44         194 my %args = @args;
407 44         144 _merge_keys( 'catch', \%args, \@args );
408 44         93 _merge_keys( 'alias', \%args, \@args );
409 44         88 _merge_keys( 'tag', \%args, \@args );
410 44 50       99 $args{alias} = +{@{$args{alias}}} if exists $args{alias};
  44         118  
411              
412             return ExtUtils::XSpp::Node::Function->new
413             ( cpp_name => $args{name},
414             perl_name => $args{perl_name},
415             class => $args{class},
416             ret_type => $args{ret_type},
417             arguments => $args{arguments},
418             code => $args{code},
419             cleanup => $args{cleanup},
420             postcall => $args{postcall},
421             catch => $args{catch},
422             condition => $args{condition},
423             alias => $args{alias},
424             tags => $args{tag},
425 44         441 );
426             }
427              
428             sub add_data_method {
429 44     44 0 173 my( $parser, @args ) = @_;
430 44         227 my %args = @args;
431 44         132 _merge_keys( 'catch', \%args, \@args );
432 44         157 _merge_keys( 'alias', \%args, \@args );
433 44         94 _merge_keys( 'tag', \%args, \@args );
434 44 50       127 $args{alias} = +{@{$args{alias}}} if exists $args{alias};
  44         110  
435              
436             my $m = ExtUtils::XSpp::Node::Method->new
437             ( cpp_name => $args{name},
438             ret_type => $args{ret_type},
439             arguments => $args{arguments},
440             const => $args{const},
441             code => $args{code},
442             cleanup => $args{cleanup},
443             postcall => $args{postcall},
444             perl_name => $args{perl_name},
445             catch => $args{catch},
446             condition => $args{condition},
447             alias => $args{alias},
448             tags => $args{tag},
449 44         527 );
450              
451 44         194 return $m;
452             }
453              
454             sub add_data_ctor {
455 4     4 0 15 my( $parser, @args ) = @_;
456 4         13 my %args = @args;
457 4         13 _merge_keys( 'catch', \%args, \@args );
458 4         12 _merge_keys( 'tag', \%args, \@args );
459              
460             my $m = ExtUtils::XSpp::Node::Constructor->new
461             ( cpp_name => $args{name},
462             arguments => $args{arguments},
463             code => $args{code},
464             cleanup => $args{cleanup},
465             postcall => $args{postcall},
466             catch => $args{catch},
467             condition => $args{condition},
468             tags => $args{tag},
469 4         68 );
470              
471 4         17 return $m;
472             }
473              
474             sub add_data_dtor {
475 3     3 0 10 my( $parser, @args ) = @_;
476 3         11 my %args = @args;
477 3         12 _merge_keys( 'catch', \%args, \@args );
478 3         7 _merge_keys( 'tag', \%args, \@args );
479              
480             my $m = ExtUtils::XSpp::Node::Destructor->new
481             ( cpp_name => $args{name},
482             code => $args{code},
483             cleanup => $args{cleanup},
484             postcall => $args{postcall},
485             catch => $args{catch},
486             condition => $args{condition},
487             tags => $args{tag},
488 3         48 );
489              
490 3         14 return $m;
491             }
492              
493             sub process_function {
494 44     44 0 85 my( $parser, $function ) = @_;
495              
496 44         134 $function->resolve_typemaps;
497 44         137 $function->resolve_exceptions;
498 44         149 call_argument_tags( $parser, $function );
499              
500 44         84 my $nodes = $parser->YYData->{PARSER}->handle_function_tags_plugins( $function, $function->tags );
501              
502 44         134 return [ $function, @$nodes ];
503             }
504              
505             sub call_argument_tags {
506 95     95 0 152 my( $parser, $function ) = @_;
507              
508 95         104 foreach my $arg ( @{$function->arguments} ) {
  95         188  
509 114         259 $parser->YYData->{PARSER}->handle_argument_tags_plugins( $arg, $arg->tags );
510             }
511             }
512              
513             1;