File Coverage

blib/lib/ExtUtils/XSpp/Parser.pm
Criterion Covered Total %
statement 100 110 90.9
branch 18 26 69.2
condition 7 15 46.6
subroutine 25 28 89.2
pod 11 20 55.0
total 161 199 80.9


line stmt bran cond sub pod time code
1             package ExtUtils::XSpp::Parser;
2              
3 21     21   99 use strict;
  21         26  
  21         546  
4 21     21   63 use warnings;
  21         27  
  21         559  
5              
6 21     21   9884 use IO::Handle;
  21         87485  
  21         1008  
7 21     21   12073 use ExtUtils::XSpp::Grammar;
  21         65  
  21         25892  
8              
9             =head1 NAME
10              
11             ExtUtils::XSpp::Parser - an XS++ parser
12              
13             =cut
14              
15             sub _my_open {
16 4     4   5 my $file = shift;
17              
18 4 50       245 open my $in, "<", $file
19             or die "Failed to open '$file' for reading: $!";
20              
21 4         19 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 131 my $class = shift;
32 86         145 my $this = bless {}, $class;
33 86         222 my %args = @_;
34              
35 86         213 $this->{FILE} = $args{file};
36 86         183 $this->{STRING} = $args{string};
37 86         518 $this->{PARSER} = ExtUtils::XSpp::Grammar->new;
38 86         176 $this->{PLUGINS} = {};
39              
40 86         274 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 130 my $this = shift;
52 86         126 my $fh;
53 86 100       207 if( $this->{FILE} ) {
54 2         6 $fh = _my_open( $this->{FILE} );
55             } else {
56             open $fh, '<', \$this->{STRING}
57 84 50       1485 or die "Failed to create file handle from in-memory string";
58             }
59 86         163 my $buf = '';
60              
61 86         141 my $parser = $this->{PARSER};
62 86         323 $parser->YYData->{LEX}{FH} = $fh;
63 86         209 $parser->YYData->{LEX}{BUFFER} = \$buf;
64 86         202 $parser->YYData->{LEX}{FILE} = $this->{FILE};
65 86         192 local $parser->YYData->{PARSER} = $this;
66              
67 86         385 $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 2 my $this = shift;
84 2         4 my( $file ) = @_;
85 2         3 my $buf = '';
86             my $new_lex = { FH => _my_open( $file ),
87             FILE => $file,
88             BUFFER => \$buf,
89             NEXT => $this->{PARSER}->YYData->{LEX},
90 2         5 };
91              
92 2         63 $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 132 my $this = shift;
104             die "'parse' must be called before calling 'get_data'"
105 85 50       198 unless exists $this->{DATA};
106              
107 85         153 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 19 my( $this, $package ) = @_;
130              
131 9 100       634 if (eval "require ExtUtils::XSpp::Plugin::$package;") {
    50          
132 7         1387 $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       285 if (!$this->{PLUGINS}{$package}) {
141 8         41 $package->register_plugin( $this );
142 8         20 $this->{PLUGINS}{$package} = 1;
143             }
144              
145             # TODO handle %load_plugin parameters
146              
147 9         18 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 27 my( $this, %args ) = @_;
159              
160 4         11 _add_plugin( $this, 'POST_PROCESS', \%args, 'post_process' );
161             }
162              
163 85 100   85 0 399 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 20 my( $this, %args ) = @_;
174 4   50     9 my $tag = $args{tag} || '_any_';
175              
176 4         13 _add_plugin( $this, 'CLASS_TAG', \%args, 'handle_class_tag' );
177             }
178              
179             sub handle_class_tag_plugins {
180 2     2 0 5 my( $this, $class, @args ) = @_;
181              
182 2         13 _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 62 my( $this, %args ) = @_;
195 4   50     12 my $tag = $args{tag} || '_any_';
196              
197 4         11 _add_plugin( $this, 'FUNCTION_TAG', \%args, 'handle_function_tag' );
198             }
199              
200             sub handle_function_tags_plugins {
201 44     44 0 85 my( $this, $function, $tags ) = @_;
202              
203 44         103 _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 28 my( $this, %args ) = @_;
216 5   50     10 my $tag = $args{tag} || '_any_';
217              
218 5         8 _add_plugin( $this, 'METHOD_TAG', \%args, 'handle_method_tag' );
219             }
220              
221             sub handle_method_tags_plugins {
222 51     51 0 109 my( $this, $method, $tags ) = @_;
223              
224 51         129 _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 6 my( $this, %args ) = @_;
237 1   50     3 my $tag = $args{tag} || '_any_';
238              
239 1         3 _add_plugin( $this, 'ARGUMENT_TAG', \%args, 'handle_argument_tag' );
240             }
241              
242             sub handle_argument_tags_plugins {
243 114     114 0 198 my( $this, $argument, $tags ) = @_;
244              
245 114         338 _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 15 my( $this, %args ) = @_;
258 4   50     9 my $tag = $args{tag} || '_any_';
259              
260 4         7 _add_plugin( $this, 'TOPLEVEL_TAG', \%args, 'handle_toplevel_tag' );
261             }
262              
263             sub handle_toplevel_tag_plugins {
264 2     2 0 8 my( $this, @args ) = @_;
265              
266 2         8 _handle_plugin( $this, $this->{PLUGINS}{TOPLEVEL_TAG}, 'top-level',
267             [ undef, @args ] );
268             }
269              
270             sub _add_plugin {
271 22     22   100 my( $this, $kind, $args, $default_method ) = @_;
272             my $entry = { plugin => $args->{plugin},
273 22   33     74 method => $args->{method} || $default_method,
274             };
275              
276 22 100       96 if( $kind eq 'POST_PROCESS' ) {
277 4         7 push @{$this->{PLUGINS}{$kind}}, $entry;
  4         14  
278             } else {
279 18   50     19 push @{$this->{PLUGINS}{$kind}{$args->{tag} || '_any_'}}, $entry;
  18         63  
280             }
281             }
282              
283             sub _handle_plugins {
284 209     209   470 my( $this, $plugins, $plugin_type, $tags, $arg ) = @_;
285 209         227 my @nodes;
286              
287 209 50       213 foreach my $tag ( @{$tags || []} ) {
  209         368  
288             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 8         28 ] );
295              
296 8         20 push @nodes, @$nodes;
297             }
298              
299 209         484 return \@nodes;
300             }
301              
302             sub _handle_plugin {
303 12     12   18 my( $this, $plugins, $plugin_type, $plugin_args ) = @_;
304 12         13 my $tag = $plugin_args->[1];
305              
306 12 50       12 foreach my $plugin ( @{$plugins->{$tag} || []}, @{$plugins->{_any_} || []} ) {
  12 50       21  
  12         31  
307 12         15 my $method = $plugin->{method};
308              
309 12         45 my( $handled, @nodes ) = $plugin->{plugin}->$method( @$plugin_args );
310 12 50       73 return \@nodes if $handled;
311             }
312              
313 0           die "Unhandled $plugin_type annotation '$tag'";
314             }
315              
316 0     0 0   sub current_file { $_[0]->{PARSER}->YYData->{LEX}{FILE} }
317              
318             1;