File Coverage

blib/lib/App/YAML/Filter/RecDescentTree.pm
Criterion Covered Total %
statement 15 96 15.6
branch 0 68 0.0
condition 0 21 0.0
subroutine 5 18 27.7
pod 0 13 0.0
total 20 216 9.2


line stmt bran cond sub pod time code
1             package App::YAML::Filter::RecDescentTree;
2             # ABSTRACT: A Parse::RecDescent-based parser using a parse tree
3             $App::YAML::Filter::RecDescentTree::VERSION = '0.013';
4 1     1   1052859 use App::YAML::Filter::Base;
  1         3  
  1         7  
5 1     1   744 use boolean qw( :all );
  1         1241  
  1         5  
6 1     1   1558 use Parse::RecDescent;
  1         38903  
  1         7  
7              
8             $|++;
9             $::RD_ERRORS = 1;
10             #$::RD_WARN = 1;
11             #$::RD_HINT = 1;
12             #$::RD_TRACE = 1;
13              
14 1     1   735 use Data::Dumper;
  1         5557  
  1         72  
15 1     1   6 use boolean qw( true false );
  1         2  
  1         7  
16 0 0   0 0   sub one { return is_list( $_[0] ) ? $_[0]->[0] : $_[0] }
17 0     0 0   sub is_list { return ref $_[0] eq 'list' }
18 0 0   0 0   sub flatten { map { is_list( $_ ) ? @$_ : $_ } @_ }
  0            
19 0     0 0   sub list { return bless [ flatten( @_ ) ], 'list' }
20 0     0 0   sub empty { return bless {}, 'empty' }
21              
22             my $grammar = q{
23            
24             { use Data::Dumper }
25              
26             program:
27              
28             comb: '|' | ','
29              
30             statement: conditional | expr
31              
32             conditional: 'if' cond_expr 'then' true_expr ( 'else' false_expr )(?)
33              
34             cond_expr: expr
35             true_expr: expr
36             false_expr: expr
37              
38             expr: function_call | hash | array | binop | filter | quote_string | number | word
39              
40             filter: '.' filter_part(s? /[.]/)
41              
42             filter_part: word | '[' number(?) ']'
43              
44             binop: (filter|quote_string|number|word) op (filter|quote_string|number|word)
45              
46             function_call: function_name arguments(?)
47              
48             hash: '{' pair(s /,/) '}'
49              
50             array: '[' expr(s /,/) ']'
51              
52             arguments: '(' expr(s /,/) ')'
53              
54             pair: key ':' expr
55              
56             key: filter | quote_string | word
57              
58             quote_string: quote non_quote quote
59             { $return = eval join "", map { $_->{__VALUE__} } @item[1..$#item] }
60              
61             number: binnum | hexnum | octnum | float
62             { $return = $item[1] }
63              
64             binnum: /0b[01]+/
65             { $return = eval $item[1] }
66              
67             hexnum: /0x[0-9A-Fa-f]+/
68             { $return = eval $item[1] }
69              
70             octnum: /0o?\d+/
71             { $return = eval $item[1] }
72              
73             float: /-?\d+(?:[.]\d+)?(?:e\d+)?/
74             { $return = eval $item[1] }
75              
76             word: /\w+/
77              
78             quote: /(?
79              
80             non_quote: /(?:[^'"]|(?<=\\\\)['"])+/
81              
82             function_name: "empty" | "select" | "grep" | "group_by" | "keys" | "length" | "sort"
83              
84             op: "eq" | "ne" | "==" | "!=" | ">=" | ">" | "<=" | "<"
85              
86             comb: ',' | '|'
87             };
88              
89             my $parser = Parse::RecDescent->new( $grammar );
90              
91             sub filter {
92 0     0 0   my ( $class, $filter, $doc, $scope ) = @_;
93 0           $yq::VERBOSE = 1;
94              
95 0           my $tree = $parser->program( $filter );
96             #use Data::Dumper;
97             #print Dumper $tree;
98             #exit;
99              
100 0           my @parts = @{ $tree->{__DIRECTIVE1__} };
  0            
101 0           my @input = ( $doc );
102 0           my @output;
103 0           my $i = 0;
104 0           while ( $i < @parts ) {
105 0 0 0       if ( $i > 0 && $parts[$i-1]->{__VALUE__} eq '|' ) {
106 0           @output = ();
107             }
108              
109 0           for my $input ( @input ) {
110 0           push @output, run_statement( $parts[$i], $input, $scope );
111             }
112              
113 0 0         if ( $i < @parts-1 ) {
114 0 0         if ( $parts[$i+1]->{__VALUE__} eq '|' ) {
115 0           @input = @output;
116             }
117             }
118 0           $i += 2; # Always skip the odd indices
119             }
120              
121 0 0         return wantarray ? @output : $output[0];
122             }
123              
124             sub run_statement {
125 0     0 0   my $statement = shift;
126              
127 0 0         if ( $statement->{expr} ) {
    0          
128 0           return run_expr( $statement->{expr}, @_ );
129             }
130             elsif ( $statement->{conditional} ) {
131 0           return run_conditional( $statement->{conditional}, @_ );
132             }
133             }
134              
135             sub run_conditional {
136 0     0 0   my $cond = shift;
137 0 0         if ( run_expr( $cond->{cond_expr}{expr}, @_ ) ) {
    0          
138 0           return run_expr( $cond->{true_expr}{expr}, @_ );
139             }
140             elsif ( my $false_exprs = $cond->{'_alternation_1_of_production_1_of_rule_conditional(?)'} ) {
141 0           return run_expr( $false_exprs->[0]{false_expr}{expr}, @_ );
142             }
143 0           return;
144             }
145              
146             sub run_expr {
147 0     0 0   my $expr = shift;
148 0 0         if ( $expr->{filter} ) {
149 0           return run_filter( $expr->{filter}, @_ );
150             }
151 0 0         if ( $expr->{binop} ) {
152 0           return run_binop( $expr->{binop}, @_ );
153             }
154 0 0         if ( $expr->{hash} ) {
155 0           return run_hash( $expr->{hash}, @_ );
156             }
157 0 0         if ( $expr->{array} ) {
158 0           return run_array( $expr->{array}, @_ );
159             }
160 0   0       return $expr->{quote_string} // $expr->{number} // $expr->{word};
      0        
161             }
162              
163             sub run_filter {
164 0     0 0   my ( $filter, $document, $scope ) = @_;
165 0           yq::diag( 1, "Filter: " . Dumper $filter );
166 0 0         if ( !$filter->{'filter_part(s?)'} ) {
167 0           return $document;
168             }
169 0           for my $part ( @{ $filter->{'filter_part(s?)'} } ) {
  0            
170 0 0         if ( $part->{word} ) {
    0          
171 0           $document = $document->{ $part->{word}{__VALUE__} };
172             }
173             elsif ( my $indexes = $part->{'number(?)'} ) {
174 0 0         if ( !@$indexes ) {
175 0           return @{ $document };
  0            
176             }
177             else {
178 0           $document = $document->[ $indexes->[0] ];
179             }
180             }
181             }
182 0           yq::diag( 1, "Filter returns: " . Dumper $document );
183 0           return $document;
184             }
185              
186             sub run_binop {
187 0     0 0   my $binop = shift;
188 0           yq::diag( 1, 'binop: ' . Dumper $binop );
189 0           my $lhs_value = run_expr( $binop->{'_alternation_1_of_production_1_of_rule_binop'}, @_ );
190 0           my $rhs_value = run_expr( $binop->{'_alternation_2_of_production_1_of_rule_binop'}, @_ );
191 0           my $op = $binop->{op}{__VALUE__};
192             # These operators suppress undef warnings, treating undef as just
193             # another value. Undef will never be treated as '' or 0 here.
194 0 0         if ( $op eq 'eq' ) {
    0          
    0          
    0          
    0          
    0          
    0          
    0          
195 0 0 0       return defined $lhs_value == defined $rhs_value
196             && $lhs_value eq $rhs_value ? true : false;
197             }
198             elsif ( $op eq 'ne' ) {
199 0 0 0       return defined $lhs_value != defined $rhs_value
200             || $lhs_value ne $rhs_value ? true : false;
201             }
202             elsif ( $op eq '==' ) {
203 0 0 0       return defined $lhs_value == defined $rhs_value
204             && $lhs_value == $rhs_value ? true : false;
205             }
206             elsif ( $op eq '!=' ) {
207 0 0 0       return defined $lhs_value != defined $rhs_value
208             || $lhs_value != $rhs_value ? true : false;
209             }
210             # These operators allow undef warnings, since equating undef to 0 or ''
211             # can be a cause of problems.
212             elsif ( $op eq '>' ) {
213 0 0         return $lhs_value > $rhs_value ? true : false;
214             }
215             elsif ( $op eq '>=' ) {
216 0 0         return $lhs_value >= $rhs_value ? true : false;
217             }
218             elsif ( $op eq '<' ) {
219 0 0         return $lhs_value < $rhs_value ? true : false;
220             }
221             elsif ( $op eq '<=' ) {
222 0 0         return $lhs_value <= $rhs_value ? true : false;
223             }
224 0           return;
225             }
226              
227             # XXX
228              
229             sub run_hash {
230 0     0 0   my $hash = shift;
231 0           my $return = {};
232 0           yq::diag( 1, "Hash: " . Dumper $hash );
233 0           for my $pair ( @{ $hash->{'pair(s /,/)'} } ) {
  0            
234 0           $return->{ run_expr( $pair->{key}, @_ ) } = run_expr( $pair->{expr}, @_ );
235             }
236 0           return $return;
237             }
238              
239             sub run_array {
240 0     0 0   my $array = shift;
241              
242 0           return [];
243             }
244              
245             1;
246              
247             __END__