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