File Coverage

File:blib/lib/CSS/Parse/Rule.pm
Coverage:94.4%

linestmtbrancondsubtimecode
1package CSS::Parse::Rule;
2
3
13
13
13
45
21
51
use strict;
4
13
13
13
60
23
51
use warnings;
5
6
13
13
13
98
30
147
use CSS::Parse::Op;
7
8sub new {
9
527
1800
  my ($class, $grammar, $name, $rule) = @_;
10
527
1796
        my $self = bless {}, $class;
11
12
527
1560
        $self->{error} = 0;
13
527
1368
        $self->{grammar} = $grammar;
14
527
1521
        $self->{name} = $name;
15
527
1537
        $self->{base} = $name;
16
527
1280
        $self->{rule} = undef;
17
18
527
2323
        $self->parse($rule) if defined $rule;
19
20
527
1697
        return $self;
21}
22
23sub parse {
24
526
1650
        my ($self, $rule) = @_;
25
26
526
1280
        $self->{error} = 0;
27
526
1438
        $self->{rule} = $rule;
28
29
30        # now try and tokenise the rule
31        # we first tokenise it, and *then* split it into alternations,
32        # since finding the pipes will be tricky if they occur inside
33        # literals or character classes
34
35
526
1115
        my $ops = [];
36
37
526
1409
        $rule =~ s/^\s+//;
38
39
526
1453
        while($rule){
40
6375
12011
                my $op = undef;
41
42
6375
39701
                if ($rule =~ m!^\[!){
43
44
427
1437
                        $op = CSS::Parse::Op->new($self, 'group start');
45
427
1172
                        $rule = substr $rule, 1;
46
47                }elsif ($rule =~ m!^\]!){
48
49
427
1494
                        $op = CSS::Parse::Op->new($self, 'group end');
50
427
1122
                        $rule = substr $rule, 1;
51
52                }elsif ($rule =~ m!^([a-z_][a-z_0-9-]*)!i){
53
54
3243
11858
                        $op = CSS::Parse::Op->new($self, 'subrule', $1);
55
3243
10287
                        $rule = substr $rule, length $1;
56
57                }elsif ($rule =~ m!^\*!){
58
59
1124
3829
                        $op = CSS::Parse::Op->new($self, 'rep star');
60
1124
3137
                        $rule = substr $rule, 1;
61
62                }elsif ($rule =~ m!^\+!){
63
64
47
172
                        $op = CSS::Parse::Op->new($self, 'rep plus');
65
47
142
                        $rule = substr $rule, 1;
66
67                }elsif ($rule =~ m!^\?!){
68
69
177
598
                        $op = CSS::Parse::Op->new($self, 'rep quest');
70
177
499
                        $rule = substr $rule, 1;
71
72                }elsif ($rule =~ m!^\|!){
73
74
929
3237
                        $op = CSS::Parse::Op->new($self, 'alt');
75
929
2708
                        $rule = substr $rule, 1;
76
77                }else{
78
79
1
5
                        $self->{error} = "Couldn't parse op at start of $rule";
80
1
56
                        return;
81                }
82
83
6374
6374
10706
16051
                push @{$ops}, $op;
84
85
6374
24475
                $rule =~ s/^\s+//;
86        }
87
88        #
89        # first we create a base op (of type list)
90        # which will represent a list of ops for this rule
91        #
92
93
525
1898
        my $base = CSS::Parse::Op->new($self, 'list');
94
525
1447
        $base->{ops} = $ops;
95
96
525
1385
        $self->{base} = $base;
97
98
99        #
100        # now we create a node tree from the flat list
101        #
102
103
525
1535
        unless ($self->produce_groups($base)){
104                # $self->{error} is set in $self->produce_groups()
105
2
6
                return;
106        }
107
108
109        #
110        # and perform recursive cleanups
111        #
112
113
523
1765
        unless ($base->reduce_alternations()){
114
1
16
                $self->{error} = $base->{error};
115
1
2
                return;
116        }
117
118
522
1700
        unless ($base->reduce_repetition()){
119
1
4
                $self->{error} = $base->{error};
120
1
3
                return;
121        }
122
123
521
1713
        unless ($base->reduce_empty()){
124
1
16
                $self->{error} = $base->{error};
125
1
3
                return;
126        }
127}
128
129sub produce_groups {
130
524
1586
        my ($self, $base) = @_;
131
132
524
1299
        my $ops = $base->{ops};
133
524
1493
        $base->{ops} = [];
134
524
1066
        my $current = $base;
135
136
524
6895
991
25249
        while(my $op = shift @{$ops}){
137
138
6372
32393
                if ($op->{type} eq 'group start'){
139
140
427
1457
                        my $parent = CSS::Parse::Op->new($self, 'list');
141
427
1146
                        $parent->{parent} = $current;
142
427
1418
                        $parent->{ops} = [];
143
144
427
427
867
1340
                        push @{$current->{ops}}, $parent;
145
146
427
1658
                        $current = $parent;
147
148                }elsif ($op->{type} eq 'group end'){
149
150
427
1082
                        $current = $current->{parent};
151
152
427
1842
                        if (!defined($current)){
153
1
4
                                $self->{error} = "End of group found without matching begin in rule $self->{rule}";
154
1
6
                                return 0;
155                        }
156
157                }else{
158
5518
5518
9297
20240
                        push @{$current->{ops}}, $op;
159                }
160        }
161
162
523
3093
        if ($current ne $base){
163
1
6
                $self->{error} = "Group wasn't closed in rule $self->{rule}";
164
1
5
                return 0;
165        }
166
167
522
1997
        return 1;
168}
169
170sub match {
171
1067
3239
        my ($self, $tokens, $token_pc) = @_;
172
173        #
174        # given a list of input tokens ($tokens) we
175        # try to create a tree of match objects to
176        # return, else we return undef
177        #
178
179
1067
2853
        if ($CSS::TraceParser){
180
0
0
                print "trying to match against rule $self->{name}...\n";
181        }
182
183
1067
4381
        my $ret = $self->{base}->match($tokens, $token_pc);
184
185
1067
2918
        if ($CSS::TraceParser){
186
0
0
                if (defined $ret){
187
0
0
                        print "MATCHED $self->{name}!\n";
188                }else{
189
0
0
                        print "NO MATCH on $self->{name} :(\n";
190                }
191        }
192
193
1067
4624
        $ret->{subrule} = $self->{name} if defined $ret;
194
195
1067
2955
        return $ret;
196}
197
198sub find_lex_rule {
199
4549
12382
        my ($self, $rule_name) = @_;
200
201
4549
18022
        return $self->{grammar}->find_lex_rule($rule_name);
202}
203
2041;
205