File Coverage

lib/OPTIMADE/Filter/Parser.yp
Criterion Covered Total %
statement 190 206 92.2
branch 27 36 75.0
condition 6 9 66.6
subroutine 63 66 95.4
pod 0 4 0.0
total 286 321 89.1


line stmt bran cond sub pod time code
1             # Header section
2              
3             %{
4              
5 5     5   58 use warnings;
  5         11  
  5         197  
6              
7 5     5   29 use Scalar::Util qw(blessed);
  5         15  
  5         236  
8              
9 5     5   2166 use OPTIMADE::Filter::AndOr;
  5         19  
  5         155  
10 5     5   2162 use OPTIMADE::Filter::Comparison;
  5         12  
  5         152  
11 5     5   2072 use OPTIMADE::Filter::Known;
  5         12  
  5         146  
12 5     5   2050 use OPTIMADE::Filter::ListComparison;
  5         11  
  5         147  
13 5     5   2041 use OPTIMADE::Filter::Negation;
  5         13  
  5         141  
14 5     5   2056 use OPTIMADE::Filter::Property;
  5         12  
  5         148  
15 5     5   2083 use OPTIMADE::Filter::Zip;
  5         10  
  5         19295  
16              
17             our $allow_LIKE_operator = 0;
18              
19             %}
20              
21             %%
22 78     78 0 22149  
23 78 50       189 # Rules section
24              
25             # The top-level 'filter' rule
26              
27             filter: expression ;
28              
29             # Values
30              
31             constant: string | number ;
32              
33             value: string | number | property ;
34              
35             value_list: value
36             {
37 6     6   399 return [ [ '=', $_[1] ] ];
38             }
39             | operator value
40             {
41 6     6   543 return [ [ @_[1..$#_] ] ];
42             }
43             | value_list comma value
44             {
45 17     17   1112 push @{$_[1]}, [ '=', $_[3] ];
  17         65  
46 17         34 return $_[1];
47             }
48             | value_list comma operator value
49             {
50 17     17   1123 push @{$_[1]}, [ $_[3], $_[4] ];
  17         46  
51 17         33 return $_[1];
52             }
53             ;
54              
55             value_zip: value value_zip_part
56             {
57 9     9   303 return [ [ '=', $_[1] ], $_[2] ];
58             }
59             | operator value value_zip_part
60             {
61 13     13   454 return [ [ $_[1], $_[2] ], $_[3] ];
62             }
63             | value_zip value_zip_part
64             {
65 6     6   197 push @{$_[1]}, $_[2];
  6         15  
66 6         17 return $_[1];
67             }
68             ;
69              
70             value_zip_part: colon value
71             {
72 10     10   678 return [ '=', $_[2] ];
73             }
74             | colon operator value
75             {
76 18     18   1529 return [ $_[2], $_[3] ];
77             }
78             ;
79              
80             value_zip_list: value_zip
81             {
82 10     10   205 return [ $_[1] ];
83             }
84             | value_zip_list comma value_zip
85             {
86 10     10   224 push @{$_[1]}, $_[3];
  10         25  
87 10         19 return $_[1];
88             }
89             ;
90              
91             # Expressions
92              
93             expression: expression_clause
94             | expression_clause OR expression
95             {
96 17     17   1255 return OPTIMADE::Filter::AndOr->new( @_[1..$#_] );
97             }
98             ;
99              
100             expression_clause: expression_phrase
101             | expression_phrase AND expression_clause
102             {
103 113     113   5864 return OPTIMADE::Filter::AndOr->new( @_[1..$#_] );
104             }
105             ;
106              
107             expression_phrase: comparison
108             | openingbrace expression closingbrace
109             {
110 174     174   22322 return $_[2];
111             }
112             | NOT comparison
113             {
114 5     5   347 return OPTIMADE::Filter::Negation->new( $_[2] );
115             }
116             | NOT openingbrace expression closingbrace
117             {
118 16     16   2538 return OPTIMADE::Filter::Negation->new( $_[3] );
119             }
120             ;
121              
122             comparison: constant_first_comparison | property_first_comparison ;
123              
124             constant_first_comparison: constant value_op_rhs
125             {
126 4     4   144 $_[2]->unshift_operand( $_[1] );
127 4         8 return $_[2];
128             }
129             ;
130              
131             property_first_comparison: property value_op_rhs
132             {
133 147     147   5161 $_[2]->unshift_operand( $_[1] );
134 147         258 return $_[2];
135             }
136             | property known_op_rhs
137             {
138 4     4   145 $_[2]->property( $_[1] );
139 4         8 return $_[2];
140             }
141             | property fuzzy_string_op_rhs
142             {
143 18     18   705 $_[2]->unshift_operand( $_[1] );
144 18         31 return $_[2];
145             }
146             | property set_op_rhs
147             {
148 16     16   592 $_[2]->property( $_[1] );
149 16         28 return $_[2];
150             }
151             | property set_zip_op_rhs
152             {
153 12     12   471 $_[2]->unshift_property( $_[1] );
154 12         21 return $_[2];
155             }
156             | property length_op_rhs
157             {
158 6     6   211 $_[2]->property( $_[1] );
159 6         10 return $_[2];
160             }
161             ;
162              
163             value_op_rhs: operator value
164             {
165 151     151   10711 my $cmp = OPTIMADE::Filter::Comparison->new( $_[1] );
166 151         396 $cmp->push_operand( $_[2] );
167 151         273 return $cmp;
168             }
169             ;
170              
171             known_op_rhs: IS KNOWN
172             {
173 2     2   85 return OPTIMADE::Filter::Known->new( 1 );
174             }
175             | IS UNKNOWN
176             {
177 2     2   73 return OPTIMADE::Filter::Known->new( 0 );
178             }
179             ;
180              
181             fuzzy_string_op_rhs: CONTAINS value
182             {
183 6     6   428 my $cmp = OPTIMADE::Filter::Comparison->new( $_[1] );
184 6         18 $cmp->push_operand( $_[2] );
185 6         10 return $cmp;
186             }
187             | STARTS value
188             {
189 2     2   142 my $cmp = OPTIMADE::Filter::Comparison->new( $_[1] );
190 2         7 $cmp->push_operand( $_[2] );
191 2         4 return $cmp;
192             }
193             | STARTS WITH value
194             {
195 6     6   403 my $cmp = OPTIMADE::Filter::Comparison->new( "$_[1] $_[2]" );
196 6         19 $cmp->push_operand( $_[3] );
197 6         11 return $cmp;
198             }
199             | ENDS value
200             {
201 0     0   0 my $cmp = OPTIMADE::Filter::Comparison->new( $_[1] );
202 0         0 $cmp->push_operand( $_[2] );
203 0         0 return $cmp;
204             }
205             | ENDS WITH value
206             {
207 2     2   140 my $cmp = OPTIMADE::Filter::Comparison->new( "$_[1] $_[2]" );
208 2         7 $cmp->push_operand( $_[3] );
209 2         53 return $cmp;
210             }
211             | LIKE value
212             {
213 2     2   142 my $cmp = OPTIMADE::Filter::Comparison->new( $_[1] );
214 2         6 $cmp->push_operand( $_[2] );
215 2         4 return $cmp;
216             }
217             ;
218              
219             set_op_rhs: HAS value
220             {
221 1     1   79 my $lc = OPTIMADE::Filter::ListComparison->new( $_[1] );
222 1         7 $lc->values( [ [ '=', $_[2] ] ] );
223 1         3 return $lc;
224             }
225             | HAS operator value
226             {
227 3     3   285 my $lc = OPTIMADE::Filter::ListComparison->new( $_[1] );
228 3         14 $lc->values( [ [ $_[2], $_[3] ] ] );
229 3         7 return $lc;
230             }
231             | HAS ALL value_list
232             {
233 4     4   116 my $lc = OPTIMADE::Filter::ListComparison->new( "$_[1] $_[2]" );
234 4         17 $lc->values( $_[3] );
235 4         11 return $lc;
236             }
237             | HAS ANY value_list
238             {
239 4     4   99 my $lc = OPTIMADE::Filter::ListComparison->new( "$_[1] $_[2]" );
240 4         13 $lc->values( $_[3] );
241 4         6 return $lc;
242             }
243             | HAS ONLY value_list
244             {
245 4     4   105 my $lc = OPTIMADE::Filter::ListComparison->new( "$_[1] $_[2]" );
246 4         13 $lc->values( $_[3] );
247 4         7 return $lc;
248             }
249             ;
250              
251             set_zip_op_rhs: property_zip_addon HAS value_zip
252             {
253 2     2   51 $_[1]->operator( $_[2] );
254 2         8 $_[1]->values( [ $_[3] ] );
255 2         4 return $_[1];
256             }
257             | property_zip_addon HAS ONLY value_zip_list
258             {
259 4     4   165 $_[1]->operator( "$_[2] $_[3]" );
260 4         11 $_[1]->values( $_[4] );
261 4         6 return $_[1];
262             }
263             | property_zip_addon HAS ALL value_zip_list
264             {
265 2     2   83 $_[1]->operator( "$_[2] $_[3]" );
266 2         5 $_[1]->values( $_[4] );
267 2         3 return $_[1];
268             }
269             | property_zip_addon HAS ANY value_zip_list
270             {
271 4     4   161 $_[1]->operator( "$_[2] $_[3]" );
272 4         13 $_[1]->values( $_[4] );
273 4         9 return $_[1];
274             }
275             ;
276              
277             property_zip_addon: colon property
278             {
279 12     12   297 my $zip = OPTIMADE::Filter::Zip->new;
280 12         37 $zip->push_property( $_[2] );
281 12         22 return $zip;
282             }
283             | property_zip_addon colon property
284             {
285 2     2   51 $_[1]->push_property( $_[3] );
286 2         4 return $_[1];
287             }
288             ;
289              
290             length_op_rhs: LENGTH value
291             {
292 0     0   0 my $cmp = OPTIMADE::Filter::ListComparison->new( $_[1] );
293 0         0 $cmp->values( [ [ '=', $_[2] ] ] );
294 0         0 return $cmp;
295             }
296             | LENGTH operator value
297             {
298 6     6   452 my $cmp = OPTIMADE::Filter::ListComparison->new( $_[1] );
299 6         23 $cmp->values( [ [ $_[2], $_[3] ] ] );
300 6         12 return $cmp;
301             }
302             ;
303              
304             # Property
305              
306             property: identifier
307             {
308 237     237   9168 return OPTIMADE::Filter::Property->new( $_[1] );
309             }
310             | property dot identifier
311             {
312 4     4   151 push @{$_[1]}, $_[3];
  4         83  
313 4         11 return $_[1];
314             }
315             ;
316              
317             # Separators
318              
319             openingbrace: '(' ;
320              
321             closingbrace: ')' ;
322              
323             dot: '.' ;
324              
325             comma: ',' ;
326              
327             colon: ':' ;
328              
329             # OperatorComparison operator tokens
330              
331             operator: '<'
332             | '<' '='
333             {
334 3     3   115 return join( '', @_[1..$#_] );
335             }
336             | '>'
337             | '>' '='
338             {
339 5     5   198 return join( '', @_[1..$#_] );
340             }
341             | '='
342             | '!' '='
343             {
344 4     4   143 return join( '', @_[1..$#_] );
345             }
346 78         17022 ;
347              
348             %%
349 78         9522  
350             # Footer section
351              
352             sub _Error
353             {
354 3     3   195 my( $self ) = @_;
355 3 50       53 close $self->{USER}{FILEIN} if $self->{USER}{FILEIN};
356 3         34 my $msg = "$0: syntax error at line $self->{USER}{LINENO}, " .
357             "position $self->{USER}{CHARNO}";
358 3 50       11 if( $self->YYData->{INPUT} ) {
359 3         28 $self->YYData->{INPUT} =~ s/\n$//;
360 3         37 die "$msg: '" . $self->YYData->{INPUT} . "'.\n";
361             } else {
362 0         0 die "$msg.\n";
363             }
364             }
365              
366             sub _Lexer
367             {
368 1518     1518   58328 my( $self ) = @_;
369              
370             # If the line is empty and the input is originating from the file,
371             # another line is read.
372 1518 100 100     2674 if( !$self->YYData->{INPUT} && $self->{USER}{FILEIN} ) {
373 39         423 my $filein = $self->{USER}{FILEIN};
374 39         70 $self->YYData->{INPUT} = <$filein>;
375 39 50       1609 $self->{USER}{LINENO} = -1 unless exists $self->{USER}{LINENO};
376 39         71 $self->{USER}{LINENO}++;
377 39         76 $self->{USER}{CHARNO} = 0;
378             }
379              
380 1518         9462 $self->YYData->{INPUT} =~ s/^(\s+)//;
381 1518 100       12638 $self->{USER}{CHARNO} += length( $1 ) if defined $1;
382              
383             # Escaped double quote or backslash are detected here and returned
384             # as is to the caller in order to be detected as syntax errors.
385 1518 50       2847 if( $self->YYData->{INPUT} =~ s/^(\\"|\\\\)// ) {
386 0         0 $self->{USER}{CHARNO} += length( $1 );
387 0         0 return( $1, $1 );
388             }
389              
390             # Handling strings
391 1518 100       9273 if( $self->YYData->{INPUT} =~ s/^"// ) {
392 180         1311 $self->{USER}{CHARNO} ++;
393 180         230 my $string = '';
394 180         201 while( 1 ) {
395 368 100       589 if( $self->YYData->{INPUT} =~
    100          
    100          
396 5     5   3266 s/^([A-Za-z_0-9 \t!#\$\%&\'\(\)\*\+,\-\.\/\:;<=>\?@\[\]\^`\{\|\}\~\P{ASCII}]+)// ) {
  5         74  
  5         85  
397 184         1431 $self->{USER}{CHARNO} += length( $1 );
398 184         357 $string .= $1;
399             } elsif( $self->YYData->{INPUT} =~ s/^\\([\\"])// ) {
400 4         52 $self->{USER}{CHARNO} ++;
401 4         6 $string .= $1;
402 4         7 next;
403             } elsif( $self->YYData->{INPUT} =~ s/^"// ) {
404 179         3073 $self->{USER}{CHARNO} ++;
405 179         580 return( 'string', $string );
406             } else {
407 1         19 return( undef, undef );
408             }
409             }
410             }
411              
412             # Handling identifiers
413 1338 100       7644 if( $self->YYData->{INPUT} =~ s/^([a-z_][a-z0-9_]*)// ) {
414 241         2004 $self->{USER}{CHARNO} += length( $1 );
415 241         887 return( 'identifier', $1 );
416             }
417              
418             # Handling boolean relations
419 1097 100       6864 if( $self->YYData->{INPUT} =~ s/^(AND|NOT|OR|
420             IS|UNKNOWN|KNOWN|
421             CONTAINS|STARTS|ENDS|WITH|
422             LENGTH|HAS|ALL|ONLY|ANY)//x ) {
423 240         1982 $self->{USER}{CHARNO} += length( $1 );
424 240         1153 return( $1, $1 );
425             }
426              
427             # Handling LIKE operator if allowed
428 857 100 100     5305 if( $allow_LIKE_operator && $self->YYData->{INPUT} =~ s/^(LIKE)// ) {
429 2         22 $self->{USER}{CHARNO} += length( $1 );
430 2         8 return( $1, $1 );
431             }
432              
433             # Handling numbers
434 855 100       1387 if( $self->YYData->{INPUT} =~ s/^([+-]?
435             (\d+\.?\d*|\.\d+)
436             ([eE][+-]?\d+)?)//x ) {
437 82         767 $self->{USER}{CHARNO} += length( $1 );
438 82         287 return( 'number', $1 );
439             }
440              
441 773         5239 my $char = substr( $self->YYData->{INPUT}, 0, 1 );
442 773 100       4599 if( $char ne '' ) {
443 698         1172 $self->YYData->{INPUT} = substr( $self->YYData->{INPUT}, 1 );
444             }
445 773         6439 $self->{USER}{CHARNO}++;
446 773         1914 return( $char, $char );
447             }
448              
449             sub Run
450             {
451 39     39 0 225 my( $self, $filename ) = @_;
452 39         1721 open $self->{USER}{FILEIN}, $filename;
453 39         309 my $result = $self->YYParse( yylex => \&_Lexer, yyerror => \&_Error );
454 36         5621 close $self->{USER}{FILEIN};
455 36         196 return $result;
456             }
457              
458             sub parse_string
459             {
460 39     39 0 230 my( $self, $string ) = @_;
461 39         131 $self->YYData->{INPUT} = $string;
462 39         345 $self->{USER}{LINENO} = 0;
463 39         63 $self->{USER}{CHARNO} = 0;
464 39         126 return $self->YYParse( yylex => \&_Lexer, yyerror => \&_Error );
465             }
466              
467             sub modify
468             {
469 0     0 0   my $node = shift;
470 0           my $code = shift;
471              
472 0 0 0       if( blessed $node && $node->can( 'modify' ) ) {
    0          
473 0           return $node->modify( $code, @_ );
474             } elsif( ref $node eq 'ARRAY' ) {
475 0           return [ map { modify( $_, $code, @_ ) } @$node ];
  0            
476             } else {
477 0           return $code->( $node, @_ );
478             }
479             }
480              
481             1;