File Coverage

lib/OPTIMADE/Filter/Parser.yp
Criterion Covered Total %
statement 204 220 92.7
branch 27 36 75.0
condition 6 9 66.6
subroutine 69 72 95.8
pod 0 4 0.0
total 306 341 89.7


line stmt bran cond sub pod time code
1             # Header section
2              
3             %{
4              
5 5     5   43 use warnings;
  5         16  
  5         455  
6              
7 5     5   30 use Scalar::Util qw(blessed);
  5         9  
  5         334  
8              
9 5     5   2486 use OPTIMADE::Filter::AndOr;
  5         16  
  5         237  
10 5     5   2119 use OPTIMADE::Filter::Boolean;
  5         14  
  5         157  
11 5     5   3785 use OPTIMADE::Filter::Comparison;
  5         14  
  5         176  
12 5     5   2314 use OPTIMADE::Filter::Known;
  5         17  
  5         4554  
13 5     5   2456 use OPTIMADE::Filter::ListComparison;
  5         15  
  5         195  
14 5     5   2386 use OPTIMADE::Filter::Negation;
  5         15  
  5         176  
15 5     5   2313 use OPTIMADE::Filter::Property;
  5         13  
  5         178  
16 5     5   2490 use OPTIMADE::Filter::Zip;
  5         17  
  5         28763  
17              
18             our $allow_LIKE_operator = 0;
19              
20             %}
21              
22             %%
23 97     97 0 1294809  
24 97 50       335 # Rules section
25              
26             # The top-level 'filter' rule
27              
28             filter: expression ;
29              
30             # Values
31              
32             ordered_constant: string | number ;
33              
34             unordered_constant: TRUE
35             {
36 18     18   911 return OPTIMADE::Filter::Boolean->new( 1 );
37             }
38             | FALSE
39             {
40 6     6   270 return OPTIMADE::Filter::Boolean->new( '' );
41             }
42             ;
43              
44             value: unordered_constant | ordered_value ;
45              
46             ordered_value: ordered_constant | property ;
47              
48             value_list_entry: value
49             {
50 44     44   5694 return [ '=', $_[1] ];
51             }
52             | value_eq_rhs
53             {
54 46     46   1577 return [ $_[1]->operator, $_[1]->left ];
55             }
56             | value_rel_comp_rhs
57             {
58 14     14   492 return [ $_[1]->operator, $_[1]->left ];
59             }
60             | fuzzy_string_op_rhs
61             {
62 6     6   252 return [ $_[1]->operator, $_[1]->left ];
63             }
64             ;
65              
66             value_list: value_list_entry
67             {
68 16     16   653 return [ $_[1] ];
69             }
70             | value_list comma value_list_entry
71             {
72 36     36   1554 push @{$_[1]}, $_[3];
  36         132  
73 36         85 return $_[1];
74             }
75             ;
76              
77             value_zip: value_list_entry colon value_list_entry
78             {
79 26     26   871 return [ $_[1], $_[3] ];
80             }
81             | value_zip colon value_list_entry
82             {
83 6     6   244 push @{$_[1]}, $_[3];
  6         19  
84 6         14 return $_[1];
85             }
86             ;
87              
88             value_zip_list: value_zip
89             {
90 12     12   270 return [ $_[1] ];
91             }
92             | value_zip_list comma value_zip
93             {
94 10     10   288 push @{$_[1]}, $_[3];
  10         29  
95 10         18 return $_[1];
96             }
97             ;
98              
99             # Expressions
100              
101             expression: expression_clause
102             | expression_clause OR expression
103             {
104 19     19   1934 return OPTIMADE::Filter::AndOr->new( @_[1..$#_] );
105             }
106             ;
107              
108             expression_clause: expression_phrase
109             | expression_phrase AND expression_clause
110             {
111 123     123   8407 return OPTIMADE::Filter::AndOr->new( @_[1..$#_] );
112             }
113             ;
114              
115             expression_phrase: comparison
116             | openingbrace expression closingbrace
117             {
118 201     201   35031 return $_[2];
119             }
120             | NOT comparison
121             {
122 6     6   517 return OPTIMADE::Filter::Negation->new( $_[2] );
123             }
124             | NOT openingbrace expression closingbrace
125             {
126 17     17   3284 return OPTIMADE::Filter::Negation->new( $_[3] );
127             }
128             ;
129              
130             comparison: constant_first_comparison | property_first_comparison ;
131              
132             constant_first_comparison: ordered_constant value_op_rhs
133             {
134 4     4   320 $_[2]->unshift_operand( $_[1] );
135 4         11 return $_[2];
136             }
137             | unordered_constant value_eq_rhs
138             {
139 4     4   174 $_[2]->unshift_operand( $_[1] );
140 4         10 return $_[2];
141             }
142             ;
143              
144             property_first_comparison: property
145             {
146 4     4   133 my $cmp = OPTIMADE::Filter::Comparison->new( '=' );
147 4         19 $cmp->left( $_[1] );
148 4         24 $cmp->right( OPTIMADE::Filter::Boolean->new( 1 ) );
149 4         10 return $cmp;
150             }
151             | property value_op_rhs
152             {
153 159     159   13779 $_[2]->unshift_operand( $_[1] );
154 159         366 return $_[2];
155             }
156             | property known_op_rhs
157             {
158 4     4   186 $_[2]->property( $_[1] );
159 4         8 return $_[2];
160             }
161             | property fuzzy_string_op_rhs
162             {
163 18     18   849 $_[2]->unshift_operand( $_[1] );
164 18         44 return $_[2];
165             }
166             | property set_op_rhs
167             {
168 22     22   2241 $_[2]->property( $_[1] );
169 22         47 return $_[2];
170             }
171             | property set_zip_op_rhs
172             {
173 16     16   625 $_[2]->unshift_property( $_[1] );
174 16         29 return $_[2];
175             }
176             | property length_op_rhs
177             {
178 6     6   199 $_[2]->property( $_[1] );
179 6         9 return $_[2];
180             }
181             ;
182              
183             value_op_rhs: value_eq_rhs | value_rel_comp_rhs ;
184              
185             value_eq_rhs: equality_operator value
186             {
187 179     179   25390 my $cmp = OPTIMADE::Filter::Comparison->new( $_[1] );
188 179         605 $cmp->push_operand( $_[2] );
189 179         443 return $cmp;
190             }
191             ;
192              
193             value_rel_comp_rhs: relative_comparison_operator ordered_value
194             {
195 48     48   6022 my $cmp = OPTIMADE::Filter::Comparison->new( $_[1] );
196 48         162 $cmp->push_operand( $_[2] );
197 48         127 return $cmp;
198             }
199             ;
200              
201             known_op_rhs: IS KNOWN
202             {
203 2     2   174 return OPTIMADE::Filter::Known->new( 1 );
204             }
205             | IS UNKNOWN
206             {
207 2     2   131 return OPTIMADE::Filter::Known->new( 0 );
208             }
209             ;
210              
211             fuzzy_string_op_rhs: CONTAINS value
212             {
213 8     8   1129 my $cmp = OPTIMADE::Filter::Comparison->new( $_[1] );
214 8         28 $cmp->push_operand( $_[2] );
215 8         22 return $cmp;
216             }
217             | STARTS value
218             {
219 2     2   348 my $cmp = OPTIMADE::Filter::Comparison->new( $_[1] );
220 2         8 $cmp->push_operand( $_[2] );
221 2         36 return $cmp;
222             }
223             | STARTS WITH value
224             {
225 10     10   1649 my $cmp = OPTIMADE::Filter::Comparison->new( "$_[1] $_[2]" );
226 10         41 $cmp->push_operand( $_[3] );
227 10         27 return $cmp;
228             }
229             | ENDS value
230             {
231 0     0   0 my $cmp = OPTIMADE::Filter::Comparison->new( $_[1] );
232 0         0 $cmp->push_operand( $_[2] );
233 0         0 return $cmp;
234             }
235             | ENDS WITH value
236             {
237 4     4   668 my $cmp = OPTIMADE::Filter::Comparison->new( "$_[1] $_[2]" );
238 4         14 $cmp->push_operand( $_[3] );
239 4         11 return $cmp;
240             }
241             | LIKE value
242             {
243 2     2   342 my $cmp = OPTIMADE::Filter::Comparison->new( $_[1] );
244 2         8 $cmp->push_operand( $_[2] );
245 2         5 return $cmp;
246             }
247             ;
248              
249             set_op_rhs: HAS value
250             {
251 1     1   161 my $lc = OPTIMADE::Filter::ListComparison->new( $_[1] );
252 1         6 $lc->values( [ [ '=', $_[2] ] ] );
253 1         3 return $lc;
254             }
255             | HAS equality_operator value
256             {
257 1     1   160 my $lc = OPTIMADE::Filter::ListComparison->new( $_[1] );
258 1         6 $lc->values( [ [ $_[2], $_[3] ] ] );
259 1         3 return $lc;
260             }
261             | HAS relative_comparison_operator ordered_value
262             {
263 2     2   262 my $lc = OPTIMADE::Filter::ListComparison->new( $_[1] );
264 2         9 $lc->values( [ [ $_[2], $_[3] ] ] );
265 2         4 return $lc;
266             }
267             | HAS fuzzy_string_op_rhs
268             {
269 2     2   78 my $lc = OPTIMADE::Filter::ListComparison->new( $_[1] );
270 2         7 $lc->values( [ [ $_[2]->operator, $_[2]->left ] ] );
271 2         6 return $lc;
272             }
273             | HAS ALL value_list
274             {
275 8     8   319 my $lc = OPTIMADE::Filter::ListComparison->new( "$_[1] $_[2]" );
276 8         32 $lc->values( $_[3] );
277 8         19 return $lc;
278             }
279             | HAS ANY value_list
280             {
281 4     4   142 my $lc = OPTIMADE::Filter::ListComparison->new( "$_[1] $_[2]" );
282 4         17 $lc->values( $_[3] );
283 4         10 return $lc;
284             }
285             | HAS ONLY value_list
286             {
287 4     4   150 my $lc = OPTIMADE::Filter::ListComparison->new( "$_[1] $_[2]" );
288 4         15 $lc->values( $_[3] );
289 4         8 return $lc;
290             }
291             ;
292              
293             set_zip_op_rhs: property_zip_addon HAS value_zip
294             {
295 4     4   109 $_[1]->operator( $_[2] );
296 4         13 $_[1]->values( [ $_[3] ] );
297 4         7 return $_[1];
298             }
299             | property_zip_addon HAS ONLY value_zip_list
300             {
301 4     4   136 $_[1]->operator( "$_[2] $_[3]" );
302 4         8 $_[1]->values( $_[4] );
303 4         6 return $_[1];
304             }
305             | property_zip_addon HAS ALL value_zip_list
306             {
307 4     4   202 $_[1]->operator( "$_[2] $_[3]" );
308 4         14 $_[1]->values( $_[4] );
309 4         9 return $_[1];
310             }
311             | property_zip_addon HAS ANY value_zip_list
312             {
313 4     4   190 $_[1]->operator( "$_[2] $_[3]" );
314 4         12 $_[1]->values( $_[4] );
315 4         8 return $_[1];
316             }
317             ;
318              
319             property_zip_addon: colon property
320             {
321 16     16   394 my $zip = OPTIMADE::Filter::Zip->new;
322 16         49 $zip->push_property( $_[2] );
323 16         24 return $zip;
324             }
325             | property_zip_addon colon property
326             {
327 2     2   59 $_[1]->push_property( $_[3] );
328 2         3 return $_[1];
329             }
330             ;
331              
332             length_op_rhs: LENGTH value
333             {
334 0     0   0 my $cmp = OPTIMADE::Filter::ListComparison->new( $_[1] );
335 0         0 $cmp->values( [ [ '=', $_[2] ] ] );
336 0         0 return $cmp;
337             }
338             | LENGTH operator value
339             {
340 6     6   872 my $cmp = OPTIMADE::Filter::ListComparison->new( $_[1] );
341 6         26 $cmp->values( [ [ $_[2], $_[3] ] ] );
342 6         15 return $cmp;
343             }
344             ;
345              
346             # Property
347              
348             property: identifier
349             {
350 270     270   12646 return OPTIMADE::Filter::Property->new( $_[1] );
351             }
352             | property dot identifier
353             {
354 4     4   164 push @{$_[1]}, $_[3];
  4         104  
355 4         11 return $_[1];
356             }
357             ;
358              
359             # Separators
360              
361             openingbrace: '(' ;
362              
363             closingbrace: ')' ;
364              
365             dot: '.' ;
366              
367             comma: ',' ;
368              
369             colon: ':' ;
370              
371             # Comparison operator tokens
372              
373             operator: equality_operator | relative_comparison_operator ;
374              
375             equality_operator: '='
376             | '!' '='
377             {
378 8     8   395 return join( '', @_[1..$#_] );
379             }
380             ;
381              
382             relative_comparison_operator: '<'
383             | '<' '='
384             {
385 3     3   153 return join( '', @_[1..$#_] );
386             }
387             | '>'
388             | '>' '='
389             {
390 5     5   255 return join( '', @_[1..$#_] );
391             }
392 97         44221 ;
393              
394             %%
395 97         20080  
396             # Footer section
397              
398             sub _Error
399             {
400 4     4   332 my( $self ) = @_;
401 4 50       132 close $self->{USER}{FILEIN} if $self->{USER}{FILEIN};
402 4         36 my $msg = "$0: syntax error at line $self->{USER}{LINENO}, " .
403             "position $self->{USER}{CHARNO}";
404 4 50       15 if( $self->YYData->{INPUT} ) {
405 4         38 $self->YYData->{INPUT} =~ s/\n$//;
406 4         48 die "$msg: '" . $self->YYData->{INPUT} . "'.\n";
407             } else {
408 0         0 die "$msg.\n";
409             }
410             }
411              
412             sub _Lexer
413             {
414 1741     1741   80669 my( $self ) = @_;
415              
416             # If the line is empty and the input is originating from the file,
417             # another line is read.
418 1741 100 100     4215 if( !$self->YYData->{INPUT} && $self->{USER}{FILEIN} ) {
419 49         606 my $filein = $self->{USER}{FILEIN};
420 49         113 $self->YYData->{INPUT} = <$filein>;
421 49 50       4072 $self->{USER}{LINENO} = -1 unless exists $self->{USER}{LINENO};
422 49         159 $self->{USER}{LINENO}++;
423 49         154 $self->{USER}{CHARNO} = 0;
424             }
425              
426 1741         12761 $self->YYData->{INPUT} =~ s/^(\s+)//;
427 1741 100       22227 $self->{USER}{CHARNO} += length( $1 ) if defined $1;
428              
429             # Escaped double quote or backslash are detected here and returned
430             # as is to the caller in order to be detected as syntax errors.
431 1741 50       3752 if( $self->YYData->{INPUT} =~ s/^(\\"|\\\\)// ) {
432 0         0 $self->{USER}{CHARNO} += length( $1 );
433 0         0 return( $1, $1 );
434             }
435              
436             # Handling strings
437 1741 100       13897 if( $self->YYData->{INPUT} =~ s/^"// ) {
438 189         1587 $self->{USER}{CHARNO} ++;
439 189         329 my $string = '';
440 189         299 while( 1 ) {
441 386 100       722 if( $self->YYData->{INPUT} =~
    100          
    100          
442             s/^([A-Za-z_0-9 \t!#\$\%&\'\(\)\*\+,\-\.\/\:;<=>\?@\[\]\^`\{\|\}\~\P{ASCII}]+)// ) {
443 193         1759 $self->{USER}{CHARNO} += length( $1 );
444 193         522 $string .= $1;
445             } elsif( $self->YYData->{INPUT} =~ s/^\\([\\"])// ) {
446 4         33 $self->{USER}{CHARNO} ++;
447 4         6 $string .= $1;
448 4         4 next;
449             } elsif( $self->YYData->{INPUT} =~ s/^"// ) {
450 188         3742 $self->{USER}{CHARNO} ++;
451 188         821 return( 'string', $string );
452             } else {
453 1         22 return( undef, undef );
454             }
455             }
456             }
457              
458             # Handling identifiers
459 1552 100       10416 if( $self->YYData->{INPUT} =~ s/^([a-z_][a-z0-9_]*)// ) {
460 274         2743 $self->{USER}{CHARNO} += length( $1 );
461 274         1203 return( 'identifier', $1 );
462             }
463              
464             # Handling textual operators and other literals
465 1278 100       9108 if( $self->YYData->{INPUT} =~ s/^(AND|NOT|OR|
466             IS|UNKNOWN|KNOWN|
467             CONTAINS|STARTS|ENDS|WITH|
468             LENGTH|HAS|ALL|ONLY|ANY|
469             TRUE|FALSE)//x ) {
470 309         3124 $self->{USER}{CHARNO} += length( $1 );
471 309         1345 return( $1, $1 );
472             }
473              
474             # Handling LIKE operator if allowed
475 969 100 100     7726 if( $allow_LIKE_operator && $self->YYData->{INPUT} =~ s/^(LIKE)// ) {
476 2         23 $self->{USER}{CHARNO} += length( $1 );
477 2         11 return( $1, $1 );
478             }
479              
480             # Handling numbers
481 967 100       1907 if( $self->YYData->{INPUT} =~ s/^([+-]?
482             (\d+\.?\d*|\.\d+)
483             ([eE][+-]?\d+)?)//x ) {
484 83         986 $self->{USER}{CHARNO} += length( $1 );
485 83         354 return( 'number', $1 );
486             }
487              
488 884         7333 my $char = substr( $self->YYData->{INPUT}, 0, 1 );
489 884 100       21380 if( $char ne '' ) {
490 791         1619 $self->YYData->{INPUT} = substr( $self->YYData->{INPUT}, 1 );
491             }
492 884         8366 $self->{USER}{CHARNO}++;
493 884         3268 return( $char, $char );
494             }
495              
496             sub Run
497             {
498 49     49 0 412 my( $self, $filename ) = @_;
499 49         3174 open $self->{USER}{FILEIN}, $filename;
500 49         410 my $result = $self->YYParse( yylex => \&_Lexer, yyerror => \&_Error );
501 45         10479 close $self->{USER}{FILEIN};
502 45         239 return $result;
503             }
504              
505             sub parse_string
506             {
507 48     48 0 421 my( $self, $string ) = @_;
508 48         199 $self->YYData->{INPUT} = $string;
509 48         650 $self->{USER}{LINENO} = 0;
510 48         129 $self->{USER}{CHARNO} = 0;
511 48         292 return $self->YYParse( yylex => \&_Lexer, yyerror => \&_Error );
512             }
513              
514             sub modify
515             {
516 0     0 0   my $node = shift;
517 0           my $code = shift;
518              
519 0 0 0       if( blessed $node && $node->can( 'modify' ) ) {
    0          
520 0           return $node->modify( $code, @_ );
521             } elsif( ref $node eq 'ARRAY' ) {
522 0           return [ map { modify( $_, $code, @_ ) } @$node ];
  0            
523             } else {
524 0           return $code->( $node, @_ );
525             }
526             }
527              
528             1;