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