File Coverage

blib/lib/XML/Filter/Dispatcher/Compiler.pm
Criterion Covered Total %
statement 149 204 73.0
branch 42 88 47.7
condition 10 32 31.2
subroutine 17 17 100.0
pod 2 3 66.6
total 220 344 63.9


line stmt bran cond sub pod time code
1             package XML::Filter::Dispatcher::Compiler;
2              
3             $VERSION = 0.000_1;
4              
5             =head1 NAME
6              
7             XML::Filter::Dispatcher::Compiler - Compile rulesets in to code
8              
9             =head1 SYNOPSIS
10              
11             use XML::Filter::Dispatcher::Compiler qw( xinline );
12              
13             my $c = XML::Filter::Dispatcher::Compiler->new( ... )
14              
15             my $code = $c->compile(
16             Package => "My::Filter",
17             Rules => [
18             'a/b/c' => xinline q{warn "found a/b/c"},
19             ],
20             Output => "lib/My/Filter.pm", ## optional
21             );
22              
23             =head1 DESCRIPTION
24              
25             Most of the options from XML::Filter::Dispatcher are accepted.
26              
27             NOTE: you cannot pass code references to compile() if you want to write
28             the $code to disk, they will not survive. If you want to C,
29             this is ok.
30              
31             =head1 METHODS
32              
33             =over
34              
35             =cut
36              
37             @EXPORT_OK = qw( xinline );
38             %EXPORT_TAGS = ( all => \@EXPORT_OK );
39             @ISA = qw( Exporter );
40 1     1   6055 use Exporter;
  1         3  
  1         33  
41              
42 1     1   6 use strict;
  1         1  
  1         25  
43              
44 1     1   4 use Carp;
  1         1  
  1         87  
45 1     1   791 use XML::Filter::Dispatcher::Parser;
  1         3  
  1         12  
46              
47             sub new {
48 2 100   2 0 1827 my $class = ref $_[0] ? ref shift : shift;
49 2         22 my $self = bless { @_ }, $class;
50              
51 2         7 return $self;
52             }
53              
54              
55             =item xinline
56              
57             Hints to X::F::D that the string is inlinable code. This is a
58             requirement when using the compiler and is so far (v.52) ignored
59             elswhere. In xinlined code, C<$self> refers to the current dispatcher
60             and C<$e> refers to the current event's data. Or you can get that
61             yourself in C<$_[0]> and C<$_[1]> as in a normal SAX event handling
62             method.
63              
64             =cut
65              
66 2     2 1 706 sub xinline($) { return \$_[0] }
67              
68              
69             =item compile
70              
71             Accepts options that extend and override any previously set for the duration of
72             the compile(), including the ruleset to compile.
73              
74             =cut
75              
76             sub compile {
77 1     1 1 2 my $self = shift;
78              
79             ## Clone $self to locally override options
80 1         9 $self = $self->new( %$self, @_ );
81              
82             ## XFD::dispatcher is only needed for the parse & codegen phases.
83 1         3 local $XFD::dispatcher = $self;
84              
85 1         2 my $package_name = $self->{Package};
86              
87 1         4 $self->_parse;
88              
89             ## Convert actions to subs, rejecting any that can't be converted
90 1         4 my @actions_code;
91             my @actions_predecls;
92 1         2 for my $i ( 0..$#{$self->{Actions}} ) {
  1         4  
93 3         8 local $_ = $self->{Actions}->[$i];
94 3 50       9 croak "Can't compile CODE reference actions in to external modules\n"
95             if $_->{CodeRef};
96              
97 3 100       11 if ( $_->{IsInlineCode} ) {
98 2         3 my $code = ${$_->{Code}};
  2         5  
99 2         7 $_->{Code} = "\\&action_$i";
100            
101 2         7 push @actions_predecls, "sub action_$i;\n";
102              
103 2         12 push @actions_code, <
104              
105             #line 1 ${package_name}::action_$i()
106             sub action_$i { my ( \$self, \$e ) = \@_; $code
107             }
108              
109             CODE_END
110             }
111             }
112              
113 1         4 my $actions_predecls = join "", @actions_predecls;
114 1         3 my $actions_code = join "", @actions_code;
115 1         6 my $code = $self->_post_process;
116              
117             ## HACK fixup until refactor
118 1         10 $code =~ s/\$cur_self\b/\$XFD::cur_self/g;
119 1         32 $code =~ s/(?
120              
121 1 50       3 my $imports = join " ", @{$self->{Imports} || []};
  1         9  
122              
123 1         73 my $local_time = localtime;
124 1         3 my $preamble = $self->{Preamble};
125 1 50       4 $preamble = "" unless defined $preamble;
126              
127 1 50       18 $preamble = "##PREAMBLE\n$preamble\n## END PREAMBLE\n" if length $preamble;
128              
129 1         23 $code = <
130             package $package_name;
131              
132             ## This is a quick and dirty shoehorning-in; a future version will
133             ## overload start_element, etc, and perhaps not even *be* an
134             ## XML::Filter::Dispatcher.
135              
136             ## AUTOGENERATED: DO NOT HAND EDIT
137             ##
138             ## built on $local_time
139              
140             \@${package_name}::ISA = qw( XML::Filter::Dispatcher );
141              
142             use strict;
143             use XML::Filter::Dispatcher qw( $imports );
144             use XML::Filter::Dispatcher::Runtime;
145              
146             use constant is_tracing => defined \$Devel::TraceSAX::VERSION;
147              
148             ## Some more workarounds until we can refactor
149             sub _ev(\$);
150             sub _po(\$);
151             *_ev = \\&XML::Filter::Dispatcher::_ev;
152             *_ev = \\&XML::Filter::Dispatcher::_ev;
153             *_po = \\&XML::Filter::Dispatcher::_po;
154             *_po = \\&XML::Filter::Dispatcher::_po;
155              
156             BEGIN {
157             eval( is_tracing
158             ? 'use Devel::TraceSAX qw( emit_trace_SAX_message ); 1'
159             : 'sub emit_trace_SAX_message; 1'
160             ) or die \$@;
161             }
162              
163             my \$doc_sub;
164              
165             sub start_document {
166             my \$self = shift;
167             \$self->{DocSub} = \$doc_sub;
168             \$self->SUPER::start_document( \@_ );
169             }
170              
171             ## PREDECLARE ACTION SUBS
172             $actions_predecls## END ACTION SUBS PREDECLARATIONS
173              
174             ## PATTERN MATCHING
175             \$doc_sub = sub {
176             $code};
177             ## END PATTERN MATCHING
178              
179             $preamble
180              
181             ## ACTIONS
182             ## Put this at the end so the #line directives don't disturb
183             ## error reporting.
184             $actions_code
185             ## END ACTIONS
186              
187             1;
188             CODE_END
189 1 50       6 if ( $self->{Debug} ) {
190 0         0 my $c = $code;
191 0         0 my $ln = 1;
192 0         0 $c =~ s{^}{sprintf "%4d|", $ln++}gme;
  0         0  
193 0         0 warn $c;
194             }
195              
196 1         56 return $code;
197             }
198              
199              
200             my @every_names = qw(
201             attribute
202             characters
203             comment
204             start_element
205             start_prefix_mapping
206             processing_instruction
207             );
208              
209              
210             sub _parse {
211 2     2   5 my $self = shift;
212              
213 2         43 $self->{OpTree} = undef;
214 2         8 for ( @every_names ) {
215 12         24 $self->{"${_}OpTree"} = undef;
216 12         34 $self->{"${_}Sub"} = undef;
217             }
218              
219 2         8 $self->{Actions} = [];
220              
221 2         7 while ( @{$self->{Rules}} ) {
  7         29  
222 5         12 my ( $expr, $action ) = (
223 5         13 shift @{$self->{Rules}},
224 5         10 shift @{$self->{Rules}}
225             );
226              
227             eval {
228 5         66 XML::Filter::Dispatcher::Parser->parse(
229             $self,
230             $expr,
231             $action,
232             );
233 5         18 1;
234             }
235 5 50       11 or do {
236 0   0     0 $@ ||= "parse returned undef";
237 0         0 chomp $@;
238 0         0 die "$@ in EventPath expression '$expr'\n";
239             }
240             }
241             }
242              
243             sub _compile {
244 1     1   351 my $self = shift;
245              
246             ## XFD::dispatcher is only needed for the parse & codegen phases.
247 1         3 local $XFD::dispatcher = $self;
248              
249 1         6 $self->_parse;
250 1 50       5 return unless $self->{OpTree};
251              
252 1         33 my $code = $self->_post_process;
253              
254 1         5 $code = <
255             package XFD;
256              
257             use XML::Filter::Dispatcher::Runtime;
258              
259             use strict;
260              
261             use vars qw( \$cur_self \$ctx );
262              
263             sub {
264             my ( \$d, \$postponement ) = \@_;
265             $code};
266             CODE_END
267              
268 1 50       5 if ( $self->{Debug} ) {
269 0         0 my $c = $code;
270 0         0 my $ln = 1;
271 0         0 $c =~ s{^}{sprintf "%4d|", $ln++}gme;
  0         0  
272 0         0 warn $c;
273             }
274              
275 1         7 return ( $code, $self->{Actions} );
276             }
277              
278              
279             sub _post_process {
280 2     2   5 my $self = shift;
281              
282 2         14 $self->{OpTree}->fixup( {} );
283              
284 2 50 33     31 $self->_optimize
      33        
      33        
285             unless defined $ENV{XFDOPTIMIZE} && ! $ENV{XFDOPTIMIZE}
286             || defined $self->{Optimize} && ! $self->{Optimize};
287              
288 2 50       11 if ( $self->{Debug} > 1 ) {
289 0         0 my $g = $self->{OpTree}->as_graphviz;
290 0         0 for ( map "${_}OpTree", @every_names ) {
291 0 0       0 $self->{$_}->as_graphviz( $g )
292             if $self->{$_};
293             }
294              
295 0         0 open F, ">foo.png";
296 0         0 print F $g->as_png;
297 0         0 close F;
298 0         0 system( "ee foo.png" );
299             }
300              
301 2         32 my $code = $self->{OpTree}->as_incr_code( {
302             FoldConstants => $self->{FoldConstants},
303             } );
304              
305 2         7 for ( @every_names ) {
306 12         25 my $tree_name = "${_}OpTree";
307 12         15 my $sub_name = "${_}Sub";
308 12 100 66     64 next unless exists $self->{$tree_name} && $self->{$tree_name};
309 2         15 my $sub_code = $self->{$tree_name}->as_incr_code( {
310             FoldConstants => $self->{FoldConstants},
311             } );
312              
313 2         9 XFD::_indent $sub_code
314             if XFD::_indentomatic() || $self->{Debug};
315              
316 2         22 $code .= <
317             \$cur_self->{$sub_name} = sub {
318             my ( \$d, \$postponement ) = \@_;
319             $sub_code}; ## end $sub_name
320             CODE_END
321             }
322              
323 2         8 XFD::_indent $code if XFD::_indentomatic();
324              
325 2         14 return $code;
326             }
327              
328              
329             ## This is a series of subs that call from the main sub down to each
330             ## of the child subs.
331             sub _optimize {
332 2     2   4 my $self = shift;
333              
334 2         5 @{$self->{OpTree}} = map $self->_optimize_rule( $_ ), @{$self->{OpTree}};
  2         28  
  2         12  
335              
336             ## The XFD::Rule ops are only used at compile-time to label exceptions
337             ## with the text of the rules. The folding of common leading ops
338             ## foils that by combining several rules' ops in to one tree with (at
339             ## least) a common root. Also, XFD::Rule ops look like unfoldable
340             ## ops to this stage of the opimizer. Get rid of them.
341 2         3 @{$self->{OpTree}} = map $_->get_next, @{$self->{OpTree}};
  2         5  
  2         7  
342              
343 2         18 for ( map $self->{"${_}OpTree"}, "", @every_names ) {
344 14 100       37 $_ = $self->_combine_common_leading_ops( $_ )
345             if $_;
346             }
347             }
348              
349              
350             sub _optimize_rule {
351 5     5   7 my $self = shift;
352 5         7 my ( $rule ) = @_;
353              
354 5 50       22 unless ( $rule->isa( "XFD::Rule" ) ) {
355 0         0 warn "Odd: found a ",
356             $rule->op_type,
357             " and not a Rule as a top level Op code\n";
358 0         0 return $rule;
359             }
360              
361 5         24 my $n = $rule->get_next;
362              
363 5 50       36 my @kids = $n->isa( "XFD::union" )
364             ? map $self->_optimize_rule_kid( $_ ), $n->get_kids
365             : ( $self->_optimize_rule_kid( $n ) );
366              
367             ## Capture any optimized code trees in to unions to make codegen easier.
368 5         13 for ( @every_names ) {
369 30         45 my $tree_name = "${_}OpTree";
370 30 100 66     161 next unless exists $self->{$tree_name} && $self->{$tree_name};
371 5         10 $self->{$tree_name} = "XFD::Optim::$_"->new( @{$self->{$tree_name}} );
  5         38  
372             }
373              
374 5 50       25 return () unless @kids;
375 0 0       0 $rule->force_set_next(
376             @kids == 1
377             ? shift @kids
378             : XFD::union->new( @kids )
379             );
380              
381 0         0 return $rule;
382             }
383              
384              
385             sub _optimize_rule_kid {
386 5     5   7 my $self = shift;
387 5         9 my ( $op ) = @_;
388              
389 5 50       21 if ( $op->isa( "XFD::doc_node" ) ) {
390 5         17 my $kid = $op->get_next;
391              
392 5 50       29 if ( $kid->isa( "XFD::union" ) ) {
393 0         0 $kid->set_kids( map
394             $self->_optimize_doc_node_kid( $_ ),
395             $kid->get_kids
396             );
397 0 0       0 return $kid->get_kids ? $op : ();
398             }
399             else {
400 5         15 $op->force_set_next( $self->_optimize_doc_node_kid( $kid ) );
401 5 50       16 return $op->get_next ? $op: ();
402             }
403             }
404              
405 0         0 return $op;
406             }
407              
408              
409             sub _optimize_doc_node_kid {
410 5     5   8 my $self = shift;
411 5         102 my ( $op ) = @_;
412              
413 5 50       30 if ( $op->isa( "XFD::Axis::descendant_or_self" ) ) {
414 5         23 my $kid = $op->get_next;
415              
416 5 50       28 if ( $kid->isa( "XFD::union" ) ) {
417 0         0 $kid->set_kids( map
418             $self->_optimize_doc_node_desc_or_self_kid( $_ ),
419             $kid->get_kids
420             );
421 0 0       0 return $kid->get_kids ? $op : ();
422             }
423             else {
424 5         15 $op->force_set_next(
425             $self->_optimize_doc_node_desc_or_self_kid( $kid )
426             );
427 5 50       17 return $op->get_next ? $op : ();
428             }
429             }
430              
431 0         0 return $op; ## return it unchanged.
432             }
433              
434              
435             sub _optimize_doc_node_desc_or_self_kid {
436 5     5   7 my $self = shift;
437 5         7 my ( $op ) = @_;
438              
439 5 50       20 if ( $op->isa( "XFD::EventType::node" ) ) {
440 5         21 my $kid = $op->get_next;
441 5 50       28 if ( $kid->isa( "XFD::union" ) ) {
442 0         0 $kid->set_kids(
443             map
444             $self->_optimize_doc_node_desc_or_self_node_kid( $_ ),
445             $kid->get_kids
446             );
447 0 0       0 return $op->get_kids ? $op : ();
448             }
449             else {
450 5         15 $op->force_set_next(
451             $self->_optimize_doc_node_desc_or_self_node_kid( $kid )
452             );
453 5 50       15 return $op->get_next ? $op : ();
454             }
455             }
456              
457 0         0 return $op;
458             }
459              
460              
461             sub _optimize_doc_node_desc_or_self_node_kid {
462 5     5   7 my $self = shift;
463 5         8 my ( $op ) = @_;
464              
465 5 50       32 if ( $op->isa( "XFD::Axis::end_element" ) ) {
466             ## By now, the fixup phase has made end:: replaceable by child::
467             ## when there are no precursors before it. We know there are
468             ## no precursors before it at this point in the optimizer because
469             ## there are no path segments to our left. Converting it to
470             ## a child:: element will make us able to combine the end::foo tests
471             ## with child::foo later.
472            
473             ## CHEAT: we know that end:: and child:: have the same internal
474             ## structure, so reblessing is ok.
475 0         0 bless $op, "XFD::Axis::child";
476             }
477              
478 5 50       16 if ( $op->isa( "XFD::Axis::child" ) ) {
    0          
479 5         21 my $kid = $op->get_next;
480 5 0 33     25 if ( $kid->isa( "XFD::node_name" )
      33        
481             || $kid->isa( "XFD::namespace_test" )
482             || $kid->isa( "XFD::node_local_name" )
483             ) {
484             ## The path is like "A" or "//A": optimize this to
485             ## be run directly by start_element().
486              
487 5         7 push @{$self->{start_elementOpTree}}, $kid;
  5         19  
488 5         27 return ();
489             }
490              
491 0 0       0 if ( $kid->isa( "XFD::EventType::node" ) ) {
492             ## The path is like "node()" or "//node()": optimize this
493             ## to be run directly by
494             ## start_element(), comment(), processing_instruction()
495             ## and characters().
496 0         0 my $gkid = $kid->get_next;
497 0         0 push @{$self->{charactersOpTree}}, $gkid;
  0         0  
498 0         0 push @{$self->{commentOpTree}}, $gkid;
  0         0  
499 0         0 push @{$self->{processing_instructionOpTree}}, $gkid;
  0         0  
500 0         0 push @{$self->{start_elementOpTree}}, $gkid;
  0         0  
501 0         0 push @{$self->{start_prefix_mappingOpTree}}, $gkid;
  0         0  
502 0         0 return ();
503             }
504             }
505             elsif ( $op->isa( "XFD::Axis::attribute" ) ) {
506 0         0 my $kid = $op->get_next;
507 0 0 0     0 if ( $kid->isa( "XFD::node_name" )
      0        
508             || $kid->isa( "XFD::namespace_test" )
509             || $kid->isa( "XFD::node_local_name" )
510             ) {
511             ## The path is like "@A" or "//@A": optimize this to a special
512             ## composite opcode that is run directly by start_element().
513              
514 0         0 push @{$self->{attributeOpTree}}, $kid;
  0         0  
515 0         0 return ();
516             }
517             }
518              
519 0         0 return $op;
520             }
521              
522             #sub _i { my $i = 0; ++$i while caller( $i ); " |" x $i; }
523             sub _combine_common_leading_ops {
524 14     14   20 my $self = shift;
525 14         17 my ( $op ) = @_;
526              
527 14 50       29 Carp::confess unless $op;
528              
529 14 100       81 return $op
530             if $op->isa( "XFD::Action" );
531              
532             #warn _i, $op->optim_signature, "\n";;
533 9 100       38 if ( $op->isa( "XFD::union" ) ) {
534 4         6 my %kids;
535 4         22 for ( $op->get_kids ) {
536 5         7 push @{$kids{$_->optim_signature}}, $_;
  5         21  
537             }
538              
539 4         15 for ( values %kids ) {
540             ## TODO: deal with unions inside unions.
541 5 50 33     17 if ( @$_ > 1 && $_->[0]->can( "force_set_next" ) ) {
542             #warn _i, "unionizing ", $op->optim_signature, "'s kids ", join( ", ", map $_->optim_signature, @$_ ), "\n";
543 0         0 $_->[0]->force_set_next(
544             XFD::union->new( map $_->get_next, @$_ )
545             );
546 0         0 splice @$_, 1;
547             }
548             }
549              
550             $op->set_kids(
551 5         18 map $self->_combine_common_leading_ops( $_ ),
552 4         24 map @{$kids{$_}}, keys %kids
553             );
554              
555 4 50       12 return ($op->get_kids)[0] if $op->get_kids == 1;
556             }
557             else {
558             ## TODO: Find these ops and optimize them too. One is
559             ## XFD::SubRules.
560 5 50       23 return $op unless $op->can( "force_set_next" );
561              
562 5         19 $op->force_set_next(
563             $self->_combine_common_leading_ops( $op->get_next )
564             );
565             }
566              
567 9         34 return $op;
568             }
569              
570              
571             =back
572              
573             =head1 LIMITATIONS
574              
575             =head1 COPYRIGHT
576              
577             Copyright 2003, R. Barrie Slaymaker, Jr., All Rights Reserved
578              
579             =head1 LICENSE
580              
581             You may use this module under the terms of the BSD, Artistic, or GPL licenses,
582             any version.
583              
584             =head1 AUTHOR
585              
586             Barrie Slaymaker
587              
588             =cut
589              
590             1;