File: | blib/lib/CSS/Parse/Op.pm |
Coverage: | 99.1% |
line | stmt | bran | cond | sub | time | code |
---|---|---|---|---|---|---|
1 | package CSS::Parse::Op; | |||||
2 | ||||||
3 | 14 14 14 | 51 20 63 | use strict; | |||
4 | 14 14 14 | 63 70 57 | use warnings; | |||
5 | ||||||
6 | 14 14 14 | 88 128 161 | use CSS::Parse::Match; | |||
7 | 14 14 14 | 123 26 134 | use Data::Dumper; | |||
8 | ||||||
9 | sub new { | |||||
10 | 9956 | 33208 | my ($class, $rule, $type, $content) = @_; | |||
11 | ||||||
12 | 9956 | 30961 | my $self = bless {}, $class; | |||
13 | ||||||
14 | 9956 | 28490 | $self->{rule} = $rule; | |||
15 | 9956 | 25107 | $self->{error} = 0; | |||
16 | 9956 | 26564 | $self->{type} = $type; | |||
17 | 9956 | 25868 | $self->{content} = $content; | |||
18 | 9956 | 27763 | $self->{ops} = []; | |||
19 | ||||||
20 | 9956 | 28162 | return $self; | |||
21 | } | |||||
22 | ||||||
23 | sub reduce_alternations { | |||||
24 | 6463 | 15572 | my ($self) = @_; | |||
25 | ||||||
26 | 6463 | 30642 | return 1 unless $self->{type} eq 'list'; | |||
27 | ||||||
28 | ||||||
29 | # | |||||
30 | # reduce our own children first | |||||
31 | # | |||||
32 | ||||||
33 | 948 948 | 1527 3295 | for my $op (@{$self->{ops}}){ | |||
34 | ||||||
35 | # | |||||
36 | # if this could actually fail, we'd need to check the result here | |||||
37 | # | |||||
38 | ||||||
39 | 5941 | 15357 | $op->reduce_alternations(); | |||
40 | } | |||||
41 | ||||||
42 | ||||||
43 | # | |||||
44 | # now check if we have any alts | |||||
45 | # | |||||
46 | ||||||
47 | 948 | 2074 | my $alts = 0; | |||
48 | 948 948 | 1617 3218 | for my $op (@{$self->{ops}}){ | |||
49 | 5941 | 26108 | $alts++ if $op->{type} eq 'alt'; | |||
50 | } | |||||
51 | ||||||
52 | 948 | 3757 | return 1 unless $alts; | |||
53 | ||||||
54 | ||||||
55 | # | |||||
56 | # we have alts - change our base type and create new alt children | |||||
57 | # | |||||
58 | ||||||
59 | 351 | 908 | my $our_ops = $self->{ops}; | |||
60 | 351 | 995 | $self->{ops} = []; | |||
61 | 351 | 919 | $self->{type} = 'alternation'; | |||
62 | ||||||
63 | 351 | 1373 | my $current = CSS::Parse::Op->new($self->{rule}, 'list'); | |||
64 | 351 | 1074 | $current->{ops} = []; | |||
65 | ||||||
66 | 351 351 | 600 1033 | for my $op (@{$our_ops}){ | |||
67 | ||||||
68 | 2998 | 10118 | if ($op->{type} eq 'alt'){ | |||
69 | ||||||
70 | 929 929 | 1562 2864 | push @{$self->{ops}}, $current; | |||
71 | ||||||
72 | 929 | 3419 | $current = CSS::Parse::Op->new($self->{rule}, 'list'); | |||
73 | 929 | 3842 | $current->{ops} = []; | |||
74 | ||||||
75 | }else{ | |||||
76 | 2069 2069 | 3439 8723 | push @{$current->{ops}}, $op; | |||
77 | } | |||||
78 | } | |||||
79 | ||||||
80 | 351 351 | 683 1102 | push @{$self->{ops}}, $current; | |||
81 | ||||||
82 | 351 | 1968 | return 1; | |||
83 | } | |||||
84 | ||||||
85 | sub reduce_repetition { | |||||
86 | 6814 | 16831 | my ($self) = @_; | |||
87 | ||||||
88 | 6814 | 59801 | return 1 unless (($self->{type} eq 'list') || ($self->{type} eq 'alternation')); | |||
89 | ||||||
90 | # | |||||
91 | # reduce our own children first | |||||
92 | # | |||||
93 | ||||||
94 | 2228 2228 | 3666 7419 | for my $op (@{$self->{ops}}){ | |||
95 | 6292 | 16119 | unless ($op->reduce_repetition()){ | |||
96 | 1 | 4 | $self->{error} = $op->{error}; | |||
97 | 1 | 5 | return 0; | |||
98 | } | |||||
99 | } | |||||
100 | ||||||
101 | ||||||
102 | # | |||||
103 | # do it | |||||
104 | # | |||||
105 | ||||||
106 | 2227 | 6131 | my $old_ops = $self->{ops}; | |||
107 | 2227 | 6215 | $self->{ops} = []; | |||
108 | ||||||
109 | 2227 2227 | 3872 6382 | for my $op (@{$old_ops}){ | |||
110 | ||||||
111 | 6290 | 22320 | if ($op->{type} =~ m!^rep (.*)$!){ | |||
112 | ||||||
113 | 1348 | 6621 | my $new = CSS::Parse::Op->new($self->{rule}, 'repeat '.$1); | |||
114 | 1348 | 4036 | $new->{ops} = []; | |||
115 | ||||||
116 | 1348 1348 | 2199 4430 | my $subject = pop @{$self->{ops}}; | |||
117 | ||||||
118 | 1348 | 4069 | unless (defined $subject){ | |||
119 | 1 | 3 | $self->{error} = "Repetition operator without subject"; | |||
120 | 1 | 5 | return 0; | |||
121 | } | |||||
122 | ||||||
123 | 1347 1347 | 2267 4213 | push @{$new->{ops}}, $subject; | |||
124 | ||||||
125 | 1347 1347 | 2289 5826 | push @{$self->{ops}}, $new; | |||
126 | }else{ | |||||
127 | ||||||
128 | 4942 4942 | 8185 20478 | push @{$self->{ops}}, $op; | |||
129 | } | |||||
130 | } | |||||
131 | ||||||
132 | 2226 | 10233 | return 1; | |||
133 | } | |||||
134 | ||||||
135 | sub reduce_empty { | |||||
136 | 6807 | 16529 | my ($self) = @_; | |||
137 | ||||||
138 | ||||||
139 | # | |||||
140 | # reduce our own children first | |||||
141 | # | |||||
142 | ||||||
143 | 6807 | 24433 | if (defined($self->{ops})){ | |||
144 | 6806 6806 | 11517 24125 | for my $op (@{$self->{ops}}){ | |||
145 | 6286 | 16138 | $op->reduce_empty(); | |||
146 | } | |||||
147 | } | |||||
148 | ||||||
149 | ||||||
150 | # | |||||
151 | # reduce self? | |||||
152 | # | |||||
153 | ||||||
154 | 6807 | 25601 | if ($self->{type} eq 'list'){ | |||
155 | 1874 1874 | 2741 8118 | if (scalar(@{$self->{ops}}) == 1){ | |||
156 | 987 | 2932 | my $child = $self->{ops}->[0]; | |||
157 | ||||||
158 | 987 987 4939 | 1656 3678 14729 | for my $key(keys %{$self}){ delete $self->{$key}; } | |||
159 | 987 987 4935 | 2186 3377 20093 | for my $key(keys %{$child}){ $self->{$key} = $child->{$key}; } | |||
160 | } | |||||
161 | } | |||||
162 | ||||||
163 | 6807 | 21750 | return 1; | |||
164 | } | |||||
165 | ||||||
166 | sub match { | |||||
167 | 8942 | 25967 | my ($self, $tokens, $token_pc) = @_; | |||
168 | ||||||
169 | # | |||||
170 | # given a list of input tokens ($tokens) we | |||||
171 | # try to create a tree of match objects to | |||||
172 | # return, else we return undef | |||||
173 | # | |||||
174 | ||||||
175 | 8942 | 23428 | if ($CSS::TraceParser){ | |||
176 | 0 | 0 | print "\tMatching op $self->{type} against token stack ".$self->stack_peek(5, $tokens, $token_pc)."\n"; | |||
177 | } | |||||
178 | ||||||
179 | ||||||
180 | # | |||||
181 | # prepare a match object | |||||
182 | # | |||||
183 | ||||||
184 | 8942 | 37786 | my $match = CSS::Parse::Match->new($self->{type}, $tokens, $token_pc); | |||
185 | ||||||
186 | ||||||
187 | # | |||||
188 | # for list types we need to match against each subrule | |||||
189 | # and return a single match object | |||||
190 | # | |||||
191 | ||||||
192 | 8942 | 33448 | if ($self->{type} eq 'list'){ | |||
193 | ||||||
194 | 1491 1491 | 2426 5066 | for my $op(@{$self->{ops}}){ | |||
195 | ||||||
196 | 2564 | 11177 | my $submatch = $op->match($match->{tokens}, $match->{token_pc}); | |||
197 | ||||||
198 | 2564 | 7095 | if (defined $submatch){ | |||
199 | ||||||
200 | 1655 | 5244 | $match->add_submatch($submatch); | |||
201 | }else{ | |||||
202 | 909 | 3000 | return undef; | |||
203 | } | |||||
204 | } | |||||
205 | ||||||
206 | 582 | 1852 | return $match; | |||
207 | } | |||||
208 | ||||||
209 | ||||||
210 | # | |||||
211 | # for subrule ops we just need to match against | |||||
212 | # the literal token | |||||
213 | # | |||||
214 | ||||||
215 | 7451 | 27514 | if ($self->{type} eq 'subrule'){ | |||
216 | ||||||
217 | 5694 | 16286 | my $token = $match->shift_token; | |||
218 | ||||||
219 | 5694 | 16490 | return undef unless defined $token; | |||
220 | #return undef unless scalar keys %{$token}; | |||||
221 | ||||||
222 | 5571 | 23669 | if ($token->{type} eq $self->{content}){ | |||
223 | ||||||
224 | 1022 | 3315 | $match->add_matched_token($token); | |||
225 | ||||||
226 | 1022 | 2652 | return $match; | |||
227 | } | |||||
228 | ||||||
229 | # | |||||
230 | # failed to match the next token - is this | |||||
231 | # a rule we can lex by itself? | |||||
232 | # | |||||
233 | ||||||
234 | 4549 | 13653 | $match->unshift_token($token); | |||
235 | ||||||
236 | 4549 | 19999 | my $rule = $self->{rule}->find_lex_rule($self->{content}); | |||
237 | ||||||
238 | 4549 | 13056 | if (defined $rule){ | |||
239 | ||||||
240 | #print "Descending into subrule $self->{content} (".$self->stack_peek(5, $match->{tokens}, $match->{token_pc}).")...\n"; | |||||
241 | ||||||
242 | 1042 | 4777 | my $ret = $rule->match($match->{tokens}, $match->{token_pc}); | |||
243 | ||||||
244 | #print "Returning from subrule $self->{content} (".(defined $ret ? 'MATCHED ('.$self->stack_peek(5, $ret->{tokens}, $ret->{token_pc}).')' : 'no match').")...\n"; | |||||
245 | ||||||
246 | 1042 | 3648 | return $ret; | |||
247 | } | |||||
248 | ||||||
249 | ||||||
250 | 3507 | 12021 | return undef; | |||
251 | } | |||||
252 | ||||||
253 | ||||||
254 | # | |||||
255 | # optional repeats can only ever match - it's just a case of whether the | |||||
256 | # match alters the token list | |||||
257 | # | |||||
258 | ||||||
259 | 1757 | 6262 | if ($self->{type} eq 'repeat quest'){ | |||
260 | ||||||
261 | 188 | 625 | my $subop = $self->{ops}->[0]; | |||
262 | ||||||
263 | 188 | 786 | my $submatch = $subop->match($match->{tokens}, $match->{token_pc}); | |||
264 | ||||||
265 | 188 | 648 | if (defined $submatch){ | |||
266 | ||||||
267 | 105 | 323 | $match->add_submatch($submatch); | |||
268 | } | |||||
269 | ||||||
270 | 188 | 486 | return $match; | |||
271 | } | |||||
272 | ||||||
273 | ||||||
274 | # | |||||
275 | # for alternation ops we try each sub-op in turn until one matches | |||||
276 | # | |||||
277 | ||||||
278 | 1569 | 5793 | if ($self->{type} eq 'alternation'){ | |||
279 | ||||||
280 | 755 755 | 1244 2626 | for my $subop (@{$self->{ops}}){ | |||
281 | ||||||
282 | 3501 | 15215 | my $submatch = $subop->match($match->{tokens}, $match->{token_pc}); | |||
283 | ||||||
284 | 3501 | 12700 | if (defined $submatch){ | |||
285 | ||||||
286 | 471 | 1594 | $match->add_submatch($submatch); | |||
287 | ||||||
288 | 471 | 1339 | return $match; | |||
289 | } | |||||
290 | } | |||||
291 | 284 | 1030 | return undef; | |||
292 | } | |||||
293 | ||||||
294 | ||||||
295 | # | |||||
296 | # for repeat ops we loop matching until we can't | |||||
297 | # match any more, then we check the match count | |||||
298 | # | |||||
299 | ||||||
300 | 814 | 6816 | if ($self->{type} eq 'repeat plus' || $self->{type} eq 'repeat star'){ | |||
301 | ||||||
302 | 813 | 2743 | my $subop = $self->{ops}->[0]; | |||
303 | ||||||
304 | 813 | 1587 | my $match_count = 0; | |||
305 | 813 | 1625 | my $loop = 1; | |||
306 | 813 | 2041 | my $last_pc = $match->{token_pc}; | |||
307 | ||||||
308 | 813 | 2319 | while ($loop){ | |||
309 | ||||||
310 | 1621 | 2971 | $loop = 0; | |||
311 | ||||||
312 | 1621 | 7001 | my $submatch = $subop->match($match->{tokens}, $match->{token_pc}); | |||
313 | ||||||
314 | 1621 | 6217 | if (defined $submatch){ | |||
315 | ||||||
316 | 813 | 2455 | $match->add_submatch($submatch); | |||
317 | ||||||
318 | 813 | 2004 | my $this_pc = $match->{token_pc}; | |||
319 | ||||||
320 | 813 | 2628 | if ($this_pc > $last_pc){ | |||
321 | ||||||
322 | 808 | 1663 | $loop = 1; | |||
323 | } | |||||
324 | ||||||
325 | 813 | 1525 | $last_pc = $this_pc; | |||
326 | ||||||
327 | 813 | 2797 | $match_count++; | |||
328 | } | |||||
329 | } | |||||
330 | ||||||
331 | 813 | 4226 | if ($self->{type} eq 'repeat plus' && !$match_count){ | |||
332 | ||||||
333 | 8 | 32 | return undef; | |||
334 | } | |||||
335 | ||||||
336 | 805 | 2293 | return $match; | |||
337 | } | |||||
338 | ||||||
339 | ||||||
340 | ||||||
341 | # | |||||
342 | # fall through | |||||
343 | # | |||||
344 | ||||||
345 | 1 | 2 | die "Don't know how to match against a '$self->{type}' op."; | |||
346 | } | |||||
347 | ||||||
348 | sub stack_peek { | |||||
349 | 2 | 8 | my ($self, $count, $tokens, $token_pc) = @_; | |||
350 | ||||||
351 | 2 | 3 | my @sample; | |||
352 | 2 2 10 2 | 7 11 45 6 | push @sample, $_->{type} for grep{ defined } @{$tokens}[$token_pc..$token_pc+$count-1]; | |||
353 | 2 | 13 | return "@sample"; | |||
354 | } | |||||
355 | ||||||
356 | 1; |