File Coverage

blib/lib/ExtUtils/XSpp/Parser.pm
Criterion Covered Total %
statement 103 113 91.1
branch 18 26 69.2
condition 7 15 46.6
subroutine 26 29 89.6
pod 11 20 55.0
total 165 203 81.2


line stmt bran cond sub pod time code
1             package ExtUtils::XSpp::Parser;
2              
3 21     21   125 use strict;
  21         40  
  21         693  
4 21     21   103 use warnings;
  21         31  
  21         477  
5              
6 21     21   27573 use IO::Handle;
  21         151198  
  21         1097  
7 21     21   24288 use ExtUtils::XSpp::Grammar;
  21         73  
  21         38149  
8              
9             =head1 NAME
10              
11             ExtUtils::XSpp::Parser - an XS++ parser
12              
13             =cut
14              
15             sub _my_open {
16 4     4   8 my $file = shift;
17              
18 4 50       289 open my $in, "<", $file
19             or die "Failed to open '$file' for reading: $!";
20              
21 4         31 return $in;
22             }
23              
24             =head2 ExtUtils::XSpp::Parser::new( file => path )
25              
26             Create a new XS++ parser.
27              
28             =cut
29              
30             sub new {
31 86     86 1 196 my $class = shift;
32 86         290 my $this = bless {}, $class;
33 86         355 my %args = @_;
34              
35 86         381 $this->{FILE} = $args{file};
36 86         264 $this->{STRING} = $args{string};
37 86         1071 $this->{PARSER} = ExtUtils::XSpp::Grammar->new;
38 86         309 $this->{PLUGINS} = {};
39              
40 86         407 return $this;
41             }
42              
43             =head2 ExtUtils::XSpp::Parser::parse
44              
45             Parse the file data; returns true on success, false otherwise,
46             on failure C will return the list of errors.
47              
48             =cut
49              
50             sub parse {
51 86     86 1 175 my $this = shift;
52 86         144 my $fh;
53 86 100       334 if( $this->{FILE} ) {
54 2         8 $fh = _my_open( $this->{FILE} );
55             } else {
56 84 50   19   2654 open $fh, '<', \$this->{STRING}
  19         216  
  19         36  
  19         196  
57             or die "Failed to create file handle from in-memory string";
58             }
59 86         31159 my $buf = '';
60              
61 86         309 my $parser = $this->{PARSER};
62 86         526 $parser->YYData->{LEX}{FH} = $fh;
63 86         328 $parser->YYData->{LEX}{BUFFER} = \$buf;
64 86         357 $parser->YYData->{LEX}{FILE} = $this->{FILE};
65 86         281 local $parser->YYData->{PARSER} = $this;
66              
67 86         639 $this->{DATA} = $parser->YYParse( yylex => \&ExtUtils::XSpp::Grammar::yylex,
68             yyerror => \&ExtUtils::XSpp::Grammar::yyerror,
69             yydebug => 0x00,
70             );
71             }
72              
73             sub parse_type {
74 0     0 0 0 my( $class, $type ) = @_;
75 0         0 my $this = $class->new( string => "%_type{$type}" );
76              
77 0         0 $this->parse;
78              
79 0         0 return $this->{DATA};
80             }
81              
82             sub include_file {
83 2     2 0 4 my $this = shift;
84 2         4 my( $file ) = @_;
85 2         3 my $buf = '';
86 2         10 my $new_lex = { FH => _my_open( $file ),
87             FILE => $file,
88             BUFFER => \$buf,
89             NEXT => $this->{PARSER}->YYData->{LEX},
90             };
91              
92 2         11 $this->{PARSER}->YYData->{LEX} = $new_lex;
93             }
94              
95             =head2 ExtUtils::XSpp::Parser::get_data
96              
97             Returns a list containing the parsed data. Each item of the list is
98             a subclass of C
99              
100             =cut
101              
102             sub get_data {
103 85     85 1 158 my $this = shift;
104 85 50       300 die "'parse' must be called before calling 'get_data'"
105             unless exists $this->{DATA};
106              
107 85         303 return $this->{DATA};
108             }
109              
110             =head2 ExtUtils::XSpp::Parser::get_errors
111              
112             Returns the parsing errors as an array.
113              
114             =cut
115              
116             sub get_errors {
117 0     0 1 0 my $this = shift;
118              
119 0         0 return @{$this->{ERRORS}};
  0         0  
120             }
121              
122             =head2 ExtUtils::XSpp::Parser::load_plugin
123              
124             Loads the specified plugin and calls its C method.
125              
126             =cut
127              
128             sub load_plugin {
129 9     9 1 27 my( $this, $package ) = @_;
130              
131 9 100       698 if (eval "require ExtUtils::XSpp::Plugin::$package;") {
    50          
132 7         1571 $package = "ExtUtils::XSpp::Plugin::$package";
133             }
134             elsif (!eval "require $package;") {
135 0         0 die "Could not load XS++ plugin '$package' (neither via the namespace "
136             ."'ExtUtils::XSpp::Plugin::$package' nor via '$package'). Reason: $@";
137             }
138              
139             # only call register_plugin once
140 9 100       338 if (!$this->{PLUGINS}{$package}) {
141 8         57 $package->register_plugin( $this );
142 8         30 $this->{PLUGINS}{$package} = 1;
143             }
144              
145             # TODO handle %load_plugin parameters
146              
147 9         24 return 1;
148             }
149              
150             =head2 ExtUtils::XSpp::Parser::add_post_process_plugin
151              
152             Adds the specified plugin to be called after parsing is complete to
153             modify the parse tree before it is emitted.
154              
155             =cut
156              
157             sub add_post_process_plugin {
158 4     4 1 94 my( $this, %args ) = @_;
159              
160 4         18 _add_plugin( $this, 'POST_PROCESS', \%args, 'post_process' );
161             }
162              
163 85 100   85 0 713 sub post_process_plugins { $_[0]->{PLUGINS}{POST_PROCESS} || [] }
164              
165             =head2 ExtUtils::XSpp::Parser::add_class_tag_plugin
166              
167             Adds the specified plugin to the list of plugins that can handle custom
168             %foo annotations for a class.
169              
170             =cut
171              
172             sub add_class_tag_plugin {
173 4     4 1 26 my( $this, %args ) = @_;
174 4   50     12 my $tag = $args{tag} || '_any_';
175              
176 4         10 _add_plugin( $this, 'CLASS_TAG', \%args, 'handle_class_tag' );
177             }
178              
179             sub handle_class_tag_plugins {
180 2     2 0 7 my( $this, $class, @args ) = @_;
181              
182 2         8 _handle_plugin( $this, $this->{PLUGINS}{CLASS_TAG}, 'class',
183             [ $class, @args ] );
184             }
185              
186             =head2 ExtUtils::XSpp::Parser::add_function_tag_plugin
187              
188             Adds the specified plugin to the list of plugins that can handle custom
189             %foo annotations for a function.
190              
191             =cut
192              
193             sub add_function_tag_plugin {
194 4     4 1 54 my( $this, %args ) = @_;
195 4   50     14 my $tag = $args{tag} || '_any_';
196              
197 4         13 _add_plugin( $this, 'FUNCTION_TAG', \%args, 'handle_function_tag' );
198             }
199              
200             sub handle_function_tags_plugins {
201 44     44 0 81 my( $this, $function, $tags ) = @_;
202              
203 44         211 _handle_plugins( $this, $this->{PLUGINS}{FUNCTION_TAG}, 'function',
204             $tags, $function )
205             }
206              
207             =head2 ExtUtils::XSpp::Parser::add_method_tag_plugin
208              
209             Adds the specified plugin to the list of plugins that can handle custom
210             %foo annotations for a function.
211              
212             =cut
213              
214             sub add_method_tag_plugin {
215 5     5 1 47 my( $this, %args ) = @_;
216 5   50     30 my $tag = $args{tag} || '_any_';
217              
218 5         15 _add_plugin( $this, 'METHOD_TAG', \%args, 'handle_method_tag' );
219             }
220              
221             sub handle_method_tags_plugins {
222 51     51 0 122 my( $this, $method, $tags ) = @_;
223              
224 51         380 _handle_plugins( $this, $this->{PLUGINS}{METHOD_TAG}, 'method',
225             $tags, $method );
226             }
227              
228             =head2 ExtUtils::XSpp::Parser::add_argument_tag_plugin
229              
230             Adds the specified plugin to the list of plugins that can handle custom
231             %foo annotations for an arguments.
232              
233             =cut
234              
235             sub add_argument_tag_plugin {
236 1     1 1 9 my( $this, %args ) = @_;
237 1   50     5 my $tag = $args{tag} || '_any_';
238              
239 1         4 _add_plugin( $this, 'ARGUMENT_TAG', \%args, 'handle_argument_tag' );
240             }
241              
242             sub handle_argument_tags_plugins {
243 114     114 0 216 my( $this, $argument, $tags ) = @_;
244              
245 114         587 _handle_plugins( $this, $this->{PLUGINS}{ARGUMENT_TAG}, 'argument',
246             $tags, $argument );
247             }
248              
249             =head2 ExtUtils::XSpp::Parser::add_toplevel_tag_plugin
250              
251             Adds the specified plugin to the list of plugins that can handle custom
252             %foo top level directives.
253              
254             =cut
255              
256             sub add_toplevel_tag_plugin {
257 4     4 1 24 my( $this, %args ) = @_;
258 4   50     13 my $tag = $args{tag} || '_any_';
259              
260 4         10 _add_plugin( $this, 'TOPLEVEL_TAG', \%args, 'handle_toplevel_tag' );
261             }
262              
263             sub handle_toplevel_tag_plugins {
264 2     2 0 9 my( $this, @args ) = @_;
265              
266 2         11 _handle_plugin( $this, $this->{PLUGINS}{TOPLEVEL_TAG}, 'top-level',
267             [ undef, @args ] );
268             }
269              
270             sub _add_plugin {
271 22     22   42 my( $this, $kind, $args, $default_method ) = @_;
272 22   33     142 my $entry = { plugin => $args->{plugin},
273             method => $args->{method} || $default_method,
274             };
275              
276 22 100       51 if( $kind eq 'POST_PROCESS' ) {
277 4         6 push @{$this->{PLUGINS}{$kind}}, $entry;
  4         22  
278             } else {
279 18   50     23 push @{$this->{PLUGINS}{$kind}{$args->{tag} || '_any_'}}, $entry;
  18         121  
280             }
281             }
282              
283             sub _handle_plugins {
284 209     209   501 my( $this, $plugins, $plugin_type, $tags, $arg ) = @_;
285 209         252 my @nodes;
286              
287 209 50       274 foreach my $tag ( @{$tags || []} ) {
  209         698  
288 8         45 my $nodes = _handle_plugin( $this, $plugins, $plugin_type,
289             [ $arg, $tag->{any},
290             named => $tag->{named},
291             positional => $tag->{positional},
292             any_named_arguments => $tag->{named},
293             any_positional_arguments => $tag->{positional},
294             ] );
295              
296 8         30 push @nodes, @$nodes;
297             }
298              
299 209         969 return \@nodes;
300             }
301              
302             sub _handle_plugin {
303 12     12   22 my( $this, $plugins, $plugin_type, $plugin_args ) = @_;
304 12         16 my $tag = $plugin_args->[1];
305              
306 12 50       25 foreach my $plugin ( @{$plugins->{$tag} || []}, @{$plugins->{_any_} || []} ) {
  12 50       37  
  12         58  
307 12         21 my $method = $plugin->{method};
308              
309 12         56 my( $handled, @nodes ) = $plugin->{plugin}->$method( @$plugin_args );
310 12 50       81 return \@nodes if $handled;
311             }
312              
313 0         0 die "Unhandled $plugin_type annotation '$tag'";
314             }
315              
316 0     0 0 0 sub current_file { $_[0]->{PARSER}->YYData->{LEX}{FILE} }
317              
318             1;