| 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 | ||||||