File Coverage

File:blib/lib/CSS/Parse/Op.pm
Coverage:99.1%

linestmtbrancondsubtimecode
1package 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
9sub 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
23sub 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
85sub 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
135sub 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
166sub 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
348sub 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
3561;