File Coverage

blib/lib/DDC/PP/CQueryCompiler.pm
Criterion Covered Total %
statement 92 103 89.3
branch 19 36 52.7
condition 6 11 54.5
subroutine 27 32 84.3
pod 0 18 0.0
total 144 200 72.0


line stmt bran cond sub pod time code
1             ##-*- Mode: CPerl -*-
2             ##
3             ## File: DDC::PP::CQueryCompiler.pm
4             ## Author: Bryan Jurish
5             ## Description: pure-perl DDC query parser, top-level
6             ##======================================================================
7              
8             package DDC::PP::CQueryCompiler;
9 20     20   149 use DDC::Utils qw(:escape);
  20         57  
  20         2829  
10 20     20   141 use DDC::PP::Constants;
  20         41  
  20         373  
11 20     20   101 use DDC::PP::Object;
  20         51  
  20         524  
12 20     20   120 use DDC::PP::CQuery;
  20         40  
  20         476  
13 20     20   105 use DDC::PP::CQCount;
  20         51  
  20         479  
14 20     20   106 use DDC::PP::CQFilter;
  20         42  
  20         489  
15 20     20   113 use DDC::PP::CQueryOptions;
  20         95  
  20         588  
16              
17 20     20   10607 use DDC::PP::yyqlexer;
  20         54  
  20         803  
18 20     20   16262 use DDC::PP::yyqparser;
  20         83  
  20         723  
19              
20 20     20   151 use strict;
  20         52  
  20         37554  
21              
22             ##======================================================================
23             ## Globals etc.
24             our @ISA = qw(DDC::PP::Object);
25              
26              
27             ##======================================================================
28             ## $qc = $CLASS_OR_OBJ->new(%args)
29             ## + abstract constructor
30             ## + object structure, %args:
31             ## {
32             ## ##-- DDC::XS::CQueryCompiler emulation
33             ## Query => $query, ##-- last query parsed
34             ##
35             ## ##-- guts: status flags
36             ## error => $current_errstr, ##-- false indicates no error
37             ##
38             ## ##-- guts: underlying lexer/parser pair
39             ## lexer => $yylexer, ##-- a DDC::PP::yyqlexer object
40             ## parser => $yyparser, ##-- a DDC::PP::yyqparser object
41             ## yydebug => $mask, ##-- yydebug value
42             ##
43             ## ##-- guts: closures
44             ## yylex => \&yylex, ##-- yapp-friendly lexer sub
45             ## yyerror => \&yyerror, ##-- yapp-friendly parser sub
46             ## }
47             sub new {
48 15     15 0 2274 my $that = shift;
49 15   33     132 my $qc = bless({
50             ##-- DDC::XS emulation
51             Query => undef,
52             KeepLexerComments => 0,
53              
54             ##-- guts: status flags
55             error => undef,
56              
57             ##-- guts: underlying lexer/parser pair
58             lexer => DDC::PP::yyqlexer->new(),
59             parser => DDC::PP::yyqparser->new(),
60              
61             ##-- guts: runtime data
62             qopts => undef,
63              
64             ##-- parser debugging
65             yydebug => 0, # no debug
66             #yydebug => 0x01, # lexer debug
67             #yydebug => 0x02, # state info
68             #yydebug => 0x04, # driver actions (shift/reduce/etc.)
69             #yydebug => 0x08, # stack dump
70             #yydebug => 0x10, # Error recovery trace
71             #yydebug => 0x01 | 0x02 | 0x04 | 0x08, # almost everything
72             #yydebug => 0xffffffff, ##-- pretty much everything
73              
74             ##-- User args
75             @_
76             },
77             ref($that)||$that);
78 15         91 $qc->getClosures();
79 15         68 return $qc;
80             }
81              
82             ## undef = $qc->free()
83             ## + clears $qc itself, as well as $qc->{parser}{USER}
84             ## + makes $qc subsequently useless, but destroyable
85             sub free {
86 0     0 0 0 my $qc = shift;
87 0 0       0 delete($qc->{parser}{USER}) if ($qc->{parser});
88 0         0 %$qc = qw();
89             }
90              
91             ## $qc = $qc->getClosures()
92             ## + compiles lexer & parser closures
93             sub getClosures {
94 15     15 0 41 my $qc = shift;
95 15         172 delete(@$qc{qw(yylex yyerror)});
96 15         84 $qc->{yylex} = $qc->_yylex_sub();
97 15         68 $qc->{yyerror} = $qc->_yyerror_sub();
98 15         49 return $qc;
99             }
100              
101             ##======================================================================
102             ## DDC::XS emulation
103              
104             __PACKAGE__->defprop('Query');
105             __PACKAGE__->defprop('KeepLexerComments');
106              
107             ## undef = $qc->CleanParser()
108             ## + reset all parse-relevant data structures
109 0     0 0 0 sub CleanParser { $_[0]->reset; }
110              
111             ## $CQuery = $qc->ParseQuery($qstr)
112             sub ParseQuery {
113 211     211 0 3418 my ($qc,$qstr) = @_;
114 211         361 $qc->{Query} = eval { $qc->parse(string=>\$qstr); };
  211         578  
115 211 100       449 die(__PACKAGE__."::ParseQuery() failed: could not parse query: $@") if ($@);
116 209         696 return $qc->{Query};
117             }
118              
119             ## $s = $qc->QueryToString()
120 2     2 0 46 sub QueryToString { return $_[0]->getQuery->toStringFull(); }
121              
122             ## $s = $qc->QueryToJson()
123             sub QueryToJson {
124 2     2 0 15 return "{\"Query\":".$_[0]->getQuery->toJson().",\"Options\":".$_[0]->getQuery->getOptions->toJson()."}";
125             }
126              
127              
128             ##======================================================================
129             ## Local API: Input selection
130              
131             ## undef = $qc->reset()
132             ## + reset all parse-relevant data structures
133             sub reset {
134 211     211 0 301 my $qc = shift;
135              
136             ##-- runtime data
137 211         1252 delete(@$qc{qw(Query qopts)});
138              
139             ##-- lexer & parser state
140 211         874 $qc->{lexer}->reset();
141              
142 211         483 delete($qc->{parser}{USER}{hint});
143 211         456 $qc->{parser}{USER}{qc} = $qc;
144 211         438 $qc->{parser}{USER}{lex} = $qc->{lexer};
145              
146             }
147              
148             ## $qc = $qc->from($which,$src, %opts)
149             ## + wraps $qc->{lexer}->from()
150             ## + $which is one of qw(fh file string)
151             ## + $src is the actual source (default: 'string')
152             ## + %opts may contain (src=>$name)
153             sub from {
154 211 50   211 0 801 return $_[0]{lexer}->from(@_[1..$#_]) ? $_[0] : undef;
155             }
156              
157             ## $qc = $qc->fromFile($filename_or_handle,%opts)
158             ## + wraps $qc->{lexer}->fromFile()
159             sub fromFile {
160 0 0   0 0 0 return $_[0]{lexer}->fromFile(@_[1..$#_]) ? $_[0] : undef;
161             }
162              
163             ## $qc = $qc->fromFh($fh,%opts)
164             ## + wraps $qc->{lexer}->fromFh()
165             sub fromFh {
166 0 0   0 0 0 return $_[0]{lexer}->fromFh(@_[1..$#_]) ? $_[0] : undef;
167             }
168              
169             ## $qc = $qc->fromString($str,%opts)
170             ## $qc = $qc->fromString(\$str,%opts)
171             ## + wraps $qc->{lexer}->fromString()
172             sub fromString {
173 0 0   0 0 0 return $_[0]{lexer}->fromString(@_[1..$#_]) ? $_[0] : undef;
174             }
175              
176              
177             ##======================================================================
178             ## Local API: High-level Parsing
179              
180             ## $query_or_undef = $qc->parse(string=>$str)
181             ## $query_or_undef = $qc->parse(string=>\$str)
182             ## $query_or_undef = $qc->parse(file=>$filename)
183             ## $query_or_undef = $qc->parse(fh=>$handle)
184             sub parse {
185 211     211 0 334 my $qc = shift;
186 211         595 $qc->reset();
187 211         612 $qc->from(@_);
188 211         387 my $result = eval { $qc->yyparse(); };
  211         485  
189 211         13931 my $err = $@;
190 211         442 delete($qc->{parser}{qc}); ##-- chop circular reference we know how to get at...
191 211         489 delete($qc->{parser}{USER}{qc}); ##-- chop circular reference we know how to get at...
192              
193             ##-- adopt lexer comments
194             $result->{Options}{LexerComments} = $qc->{lexer}{comments}
195 211 50 66     631 if ($qc->{KeepLexerComments} && $result && $result->{Options});
      66        
196              
197             ##-- how'd it go?
198 211 100       464 die($err) if ($err);
199 209         515 return $result;
200             }
201              
202             ## $query_or_undef = $qc->yyparse()
203             ## + parses from currently selected input source; no reset or error catching
204             sub yyparse {
205 211     211 0 388 my $qc = shift;
206             return $qc->{parser}->YYParse(
207             yylex => $qc->{yylex},
208             yyerror => $qc->{yyerror},
209             yydebug => $qc->{yydebug},
210 211         816 );
211             }
212              
213             ##======================================================================
214             ## Local API: Mid-level: Query Generation
215              
216             ## $q = $qc->newq($class,@args)
217             ## + wrapper for "DDC::PP::$class"->new(@args); called by yapp parser
218             sub newq {
219 391     391 0 2113 return "DDC::PP::$_[1]"->new(@_[2..$#_]);
220             }
221              
222             ## $qf = $qc->newf(@args)
223             ## + wrapper for DDC::Query::Filter->new(@args); called by yapp parser
224             sub newf {
225 20     20 0 152 return "DDC::PP::$_[1]"->new(@_[2..$#_]);
226             }
227              
228             ## $re = $qc->newre($re,$modifiers)
229             sub newre {
230 11     11 0 30 my ($qc,$re,$mods) = @_;
231 11 50 50     60 if (($mods||'') =~ /g/) {
232 0         0 $re = "^(?:${re})\$";
233 0         0 $mods =~ s/g//g;
234             }
235 11 50       43 return $re if (!$mods);
236 0         0 return "(?:${mods})$re";
237             }
238              
239             ## $qo = $qc->qopts()
240             ## $qo = $qc->qopts($opts)
241             ## + get/set current query options
242             sub qopts {
243 606 100   606 0 1438 $_[0]{qopts} = $_[1] if ($_[1]);
244 606 100       1803 $_[0]{qopts} = DDC::PP::CQueryOptions->new if (!defined($_[0]{qopts}));
245 606         2022 return $_[0]{qopts};
246             }
247              
248              
249             ##======================================================================
250             ## API: Low-LEVEL: Parse::Lex <-> Parse::Yapp interface
251             ##
252             ## - REQUIREMENTS on yylex() sub:
253             ## + Yapp-compatible lexing routine
254             ## + reads input and returns token values to the parser
255             ## + our only argument ($MyParser) is the YYParser object itself
256             ## + We return a list ($TOKENTYPE, $TOKENVAL) of the next tokens to the parser
257             ## + on end-of-input, we should return the list ('', undef)
258             ##
259              
260             ## \&yylex_sub = $qc->_yylex_sub()
261             ## + returns a Parse::Yapp-friendly lexer subroutine
262             sub _yylex_sub {
263 15     15   38 my $qc = shift;
264 15         40 my ($type,$text,@expect);
265              
266             return sub {
267 1125     1125   36388 $qc->{yyexpect} = [$qc->{parser}->YYExpect];
268 1125         14422 ($type,$text) = $qc->{lexer}->yylex();
269 1125 100       2961 return ('',undef) if ($type eq '__EOF__');
270              
271             ##-- un-escape single-quoted symbols (this happens in the parser)
272             # if ($type =~ /^SQ_(.*)$/) {
273             # $type = $1;
274             # $text = unescapeq($text);
275             # }
276             # elsif ($type eq 'SYMBOL') {
277             # $text = unescape($text);
278             # }
279              
280 916 50       2140 if ($qc->{yydebug} & 0x01) {
281 0 0       0 print STDERR ": yylex(): type=($type) ; text=(".(defined($text) ? $text : '-undef-')." ; state=(".($qc->{lexer}{state}).")\n";
282             }
283              
284 916         2676 return ($type,$text);
285 15         140 };
286             }
287              
288              
289             ## \&yyerror_sub = $qc->_yyerror_sub()
290             ## + returns error subroutine for the underlying Yapp parser
291             sub _yyerror_sub {
292 15     15   39 my $qc = shift;
293 15         37 my (%expect,@expecting);
294             return sub {
295 2 50   2   62 @expect{@{$qc->{yyexpect}||[]}}=qw();
  2         10  
296 2 50       6 @expect{@{$qc->{yyexpect}||[]}, $qc->{parser}->YYExpect}=qw();
  2         25  
297 2 100       26 @expecting = sort map {$_ eq '' ? '$end' : $_} keys %expect;
  4         21  
298             die("syntax error, unexpected ".$qc->{lexer}->yytype
299             .", expecting ".join(' or ', @expecting)
300             ." at line ".$qc->{lexer}->yylineno
301 2         12 .", near token \`".$qc->{lexer}->yytext."'");
302             # $qc->{error} = ("syntax error in ".$qc->{lexer}->yywhere().":\n"
303             # #." > Expected one of (here): ".join(', ', map {$_ eq '' ? '__EOF__' : $_} $qc->{parser}->YYExpect)."\n"
304             # #." > Expected one of (prev): ".join(', ', map {$_ eq '' ? '__EOF__' : $_} @{$qc->{yyexpect}||['???']})."\n"
305             # ." > Expected one of: ".join(', ', sort map {$_ eq '' ? '__EOF__' : $_} keys %expect)."\n"
306             # ." > Got: ".$qc->{lexer}->yytype.' "'.$qc->{lexer}->yytext."\"\n"
307             # );
308 15         108 };
309             }
310              
311              
312             1; ##-- be happy
313              
314             __END__