File Coverage

inc/Pegex/Parser.pm
Criterion Covered Total %
statement 97 150 64.6
branch 49 86 56.9
condition 19 31 61.2
subroutine 12 18 66.6
pod 0 13 0.0
total 177 298 59.4


line stmt bran cond sub pod time code
1             package Pegex::Parser;
2 1     1   5 use Pegex::Base;
  1         3  
  1         9  
3              
4 1     1   3360 use Pegex::Input;
  1         2  
  1         29  
5 1     1   414 use Pegex::Optimizer;
  1         3  
  1         33  
6 1     1   8 use Scalar::Util;
  1         3  
  1         1488  
7              
8             has grammar => (required => 1);
9             has receiver => ();
10             has input => ();
11             has debug => (
12             exists($ENV{PERL_PEGEX_DEBUG}) ? $ENV{PERL_PEGEX_DEBUG} :
13             defined($Pegex::Parser::Debug) ? $Pegex::Parser::Debug :
14             0
15             );
16             sub BUILD {
17 274     274 0 34603 my ($self) = @_;
18 274   50     1895 $self->{throw_on_error} ||= 1;
19             # $self->{rule} = undef;
20             # $self->{parent} = undef;
21             # $self->{error} = undef;
22             # $self->{position} = undef;
23             # $self->{farthest} = undef;
24             }
25              
26             # XXX Add an optional $position argument. Default to 0. This is the position
27             # to start parsing. Set position and farthest below to this value. Allows for
28             # sub-parsing. Need to somehow return the finishing position of a subparse.
29             # Maybe this all goes in a subparse() method.
30             sub parse {
31 275     275 0 2608 my ($self, $input, $start) = @_;
32              
33 275 100       1506 $start =~ s/-/_/g if $start;
34              
35 275         944 $self->{position} = 0;
36 275         685 $self->{farthest} = 0;
37              
38 275 50       1675 $self->{input} = (not ref $input)
39             ? Pegex::Input->new(string => $input)
40             : $input;
41              
42 275 50       25978 $self->{input}->open
43             unless $self->{input}{_is_open};
44 275         1181 $self->{buffer} = $self->{input}->read;
45              
46 275 50       835 die "No 'grammar'. Can't parse"
47             unless $self->{grammar};
48              
49 275   66     2479 $self->{grammar}{tree} ||= $self->{grammar}->make_tree;
50              
51 275 50 33     3055 my $start_rule_ref = $start ||
52             $self->{grammar}{tree}{'+toprule'} ||
53             $self->{grammar}{tree}{'TOP'} & 'TOP' or
54             die "No starting rule for Pegex::Parser::parse";
55              
56 275 50       1236 die "No 'receiver'. Can't parse"
57             unless $self->{receiver};
58              
59 275         1498 my $optimizer = Pegex::Optimizer->new(
60             parser => $self,
61             grammar => $self->{grammar},
62             receiver => $self->{receiver},
63             );
64              
65 275         19079 $optimizer->optimize_grammar($start_rule_ref);
66              
67             # Add circular ref and weaken it.
68 275         1028 $self->{receiver}{parser} = $self;
69 275         1862 Scalar::Util::weaken($self->{receiver}{parser});
70              
71 275 50       1453 if ($self->{receiver}->can("initial")) {
72 0         0 $self->{rule} = $start_rule_ref;
73 0         0 $self->{parent} = {};
74 0         0 $self->{receiver}->initial();
75             }
76              
77 275 50       1252 my $match = $self->debug ? do {
78 0         0 my $method = $optimizer->make_trace_wrapper(\&match_ref);
79 0         0 $self->$method($start_rule_ref, {'+asr' => 0});
80             } : $self->match_ref($start_rule_ref, {});
81              
82 275         4375 $self->{input}->close;
83              
84 275 50 33     1016 if (not $match or $self->{position} < length ${$self->{buffer}}) {
  275         1374  
85 0         0 $self->throw_error("Parse document failed for some reason");
86 0         0 return; # In case $self->throw_on_error is off
87             }
88              
89 275 50       1648 if ($self->{receiver}->can("final")) {
90 275         559 $self->{rule} = $start_rule_ref;
91 275         556 $self->{parent} = {};
92 275         1473 $match = [ $self->{receiver}->final(@$match) ];
93             }
94              
95 275         25213 $match->[0];
96             }
97              
98             sub match_next {
99 20714     20714 0 29160 my ($self, $next) = @_;
100              
101 20714         70563 my ($rule, $method, $kind, $min, $max, $assertion) =
102 20714         27412 @{$next}{'rule', 'method', 'kind', '+min', '+max', '+asr'};
103              
104 20714         48965 my ($position, $match, $count) =
105             ($self->{position}, [], 0);
106              
107 20714         45654 while (my $return = $method->($self, $rule, $next)) {
108 5578 100       33084 $position = $self->{position} unless $assertion;
109 5578         7722 $count++;
110 5578         14102 push @$match, @$return;
111 5578 100       15067 last if $max == 1;
112             }
113 20714 100 100     85187 if (not $count and $min == 0 and $kind eq 'all') {
      100        
114 4         82 $match = [[]];
115             }
116 20714 100       49967 if ($max != 1) {
117 432 100       1682 if ($next->{-flat}) {
118 4 50       7 $match = [ map { (ref($_) eq 'ARRAY') ? (@$_) : ($_) } @$match ];
  4         17  
119             }
120             else {
121 428         1205 $match = [$match]
122             }
123 432 50       1918 $self->{farthest} = $position
124             if ($self->{position} = $position) > $self->{farthest};
125             }
126 20714   66     62739 my $result = ($count >= $min and (not $max or $count <= $max))
127             ^ ($assertion == -1);
128 20714 100 100     57832 if (not($result) or $assertion) {
129 15439 50       45080 $self->{farthest} = $position
130             if ($self->{position} = $position) > $self->{farthest};
131             }
132              
133 20714 100       112913 ($result ? $next->{'-skip'} ? [] : $match : 0);
    100          
134             }
135              
136             sub match_rule {
137 0     0 0 0 my ($self, $position, $match) = (@_, []);
138 0         0 $self->{position} = $position;
139 0 0       0 $self->{farthest} = $position
140             if $position > $self->{farthest};
141 0 0       0 $match = [ $match ] if @$match > 1;
142 0         0 my ($ref, $parent) = @{$self}{'rule', 'parent'};
  0         0  
143 0 0       0 my $rule = $self->{grammar}{tree}{$ref}
144             or die "No rule defined for '$ref'";
145              
146 0         0 [ $rule->{action}->($self->{receiver}, @$match) ];
147             }
148              
149             sub match_ref {
150 10666     10666 0 18698 my ($self, $ref, $parent) = @_;
151 10666 50       36386 my $rule = $self->{grammar}{tree}{$ref}
152             or die "No rule defined for '$ref'";
153 10666 100       29361 my $match = $self->match_next($rule) or return;
154 3009 50       8494 return $Pegex::Constant::Dummy unless $rule->{action};
155 3009         4193 @{$self}{'rule', 'parent'} = ($ref, $parent);
  3009         9042  
156              
157             # XXX Possible API mismatch.
158             # Not sure if we should "splat" the $match.
159 3009         12282 [ $rule->{action}->($self->{receiver}, @$match) ];
160             }
161              
162             sub match_rgx {
163 7957     7957 0 11208 my ($self, $regexp) = @_;
164 7957         13884 my $buffer = $self->{buffer};
165              
166 7957         20459 pos($$buffer) = $self->{position};
167 7957 100       61617 $$buffer =~ /$regexp/g or return;
168              
169 1663         3310 $self->{position} = pos($$buffer);
170              
171 1663 100       5512 $self->{farthest} = $self->{position}
172             if $self->{position} > $self->{farthest};
173              
174 1     1   7 no strict 'refs';
  1         3  
  1         1142  
175 1663         10435 my $captures = [ map $$_, 1..$#+ ];
176 1663 100       5562 $captures = [ $captures ] if $#+ > 1;
177              
178 1663         22251 return $captures;
179             }
180              
181             sub match_all {
182 1373     1373 0 2015 my ($self, $list) = @_;
183 1373         1948 my $position = $self->{position};
184 1373         2120 my $set = [];
185 1373         1916 my $len = 0;
186 1373         2311 for my $elem (@$list) {
187 2511 100       5953 if (my $match = $self->match_next($elem)) {
188 1575 100 100     7539 if (not ($elem->{'+asr'} or $elem->{'-skip'})) {
189 1468         2750 push @$set, @$match;
190 1468         3832 $len++;
191             }
192             }
193             else {
194 936 50       2785 $self->{farthest} = $position
195             if ($self->{position} = $position) > $self->{farthest};
196 936         3079 return;
197             }
198             }
199 437 100       1646 $set = [ $set ] if $len > 1;
200 437         1475 return $set;
201             }
202              
203             sub match_any {
204 1720     1720 0 2941 my ($self, $list) = @_;
205 1720         3264 for my $elem (@$list) {
206 7537 100       17539 if (my $match = $self->match_next($elem)) {
207 744         2433 return $match;
208             }
209             }
210 976         3359 return;
211             }
212              
213             sub match_err {
214 0     0 0   my ($self, $error) = @_;
215 0           $self->throw_error($error);
216             }
217              
218             sub trace {
219 0     0 0   my ($self, $action) = @_;
220 0 0         my $indent = ($action =~ /^try_/) ? 1 : 0;
221 0   0       $self->{indent} ||= 0;
222 0 0         $self->{indent}-- unless $indent;
223 0           print STDERR ' ' x $self->{indent};
224 0 0         $self->{indent}++ if $indent;
225 0           my $snippet = substr(${$self->{buffer}}, $self->{position});
  0            
226 0 0         $snippet = substr($snippet, 0, 30) . "..."
227             if length $snippet > 30;
228 0           $snippet =~ s/\n/\\n/g;
229 0 0         print STDERR sprintf("%-30s", $action) .
230             ($indent ? " >$snippet<\n" : "\n");
231             }
232              
233             sub throw_error {
234 0     0 0   my ($self, $msg) = @_;
235 0           $@ = $self->{error} = $self->format_error($msg);
236 0 0         return undef unless $self->{throw_on_error};
237 0           require Carp;
238 0           Carp::croak($self->{error});
239             }
240              
241             sub format_error {
242 0     0 0   my ($self, $msg) = @_;
243 0           my $buffer = $self->{buffer};
244 0           my $position = $self->{farthest};
245 0           my $real_pos = $self->{position};
246              
247 0           my $line = @{[substr($$buffer, 0, $position) =~ /(\n)/g]} + 1;
  0            
248 0           my $column = $position - rindex($$buffer, "\n", $position);
249              
250 0 0         my $pretext = substr(
    0          
251             $$buffer,
252             $position < 50 ? 0 : $position - 50,
253             $position < 50 ? $position : 50
254             );
255 0           my $context = substr($$buffer, $position, 50);
256 0           $pretext =~ s/.*\n//gs;
257 0           $context =~ s/\n/\\n/g;
258              
259 0           return <<"...";
260             Error parsing Pegex document:
261 0           msg: $msg
262             line: $line
263             column: $column
264             context: $pretext$context
265             ${\ (' ' x (length($pretext) + 10) . '^')}
266             position: $position ($real_pos pre-lookahead)
267             ...
268             }
269              
270             # TODO Move this to a Parser helper role/subclass
271             sub line_column {
272 0     0 0   my ($self, $position) = @_;
273 0   0       $position ||= $self->{position};
274 0           my $buffer = $self->{buffer};
275 0           my $line = @{[substr($$buffer, 0, $position) =~ /(\n)/g]} + 1;
  0            
276 0           my $column = $position - rindex($$buffer, "\n", $position);
277 0           return [$line, $position];
278             }
279              
280             # XXX Need to figure out what uses this. (sample.t)
281             {
282             package Pegex::Constant;
283             our $Null = [];
284             our $Dummy = [];
285             }
286              
287             1;