File: | blib/lib/CSS/Parse/Rule.pm |
Coverage: | 94.4% |
line | stmt | bran | cond | sub | time | code |
---|---|---|---|---|---|---|
1 | package 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 | ||||||
8 | sub 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 | ||||||
23 | sub 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 | ||||||
129 | sub 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 | ||||||
170 | sub 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 | ||||||
198 | sub find_lex_rule { | |||||
199 | 4549 | 12382 | my ($self, $rule_name) = @_; | |||
200 | ||||||
201 | 4549 | 18022 | return $self->{grammar}->find_lex_rule($rule_name); | |||
202 | } | |||||
203 | ||||||
204 | 1; | |||||
205 |