File Coverage

blib/lib/Babble/Match.pm
Criterion Covered Total %
statement 133 133 100.0
branch 37 40 92.5
condition 4 6 66.6
subroutine 25 25 100.0
pod 0 8 0.0
total 199 212 93.8


line stmt bran cond sub pod time code
1             package Babble::Match;
2              
3 12     12   143530 use Babble::Grammar;
  12         50  
  12         416  
4 12     12   5184 use Babble::SymbolGenerator;
  12         45  
  12         372  
5 12     12   91 use Mu;
  12         49  
  12         49  
6 12     12   10269 use List::Util 1.45;
  12         303  
  12         5311  
7              
8             ro 'top_rule';
9             rwp 'text';
10              
11             lazy 'grammar' => sub {
12 192 100   192   8117 $_[0]->can('parent')
13             ? $_[0]->parent->grammar
14             : Babble::Grammar->new
15             } => handles => [ 'grammar_regexp' ];
16              
17             lazy 'symbol_generator' => sub {
18 24 100   24   1128 $_[0]->can('parent')
19             ? $_[0]->parent->symbol_generator
20             : Babble::SymbolGenerator->new
21             } => handles => [ 'gensym' ];
22              
23             lazy top_re => sub {
24 104     104   1374 my ($self) = @_;
25 104         423 my $top = $self->_rule_to_re($self->top_rule);
26 104         5690 return "\\A${top}\\Z";
27             };
28              
29             my %SUBMATCHES_COMPILE_CACHE;
30             lazy submatches => sub {
31 351     351   3289 my ($self) = @_;
32 351 100       1840 return {} unless ref(my $top = $self->top_rule);
33 260         474 my @subrules;
34             my $re = join '', map {
35 260         625 ref($_)
36 677 100       1813 ? do {
37 349         579 push @subrules, $_;
38 349         702 my ($name, $rule) = @$_;
39 349         1215 "(${rule})"
40             }
41             : $_
42             } @$top;
43 260 100       1207 return {} unless @subrules;
44 105         442 my $submatch_re = qq[ \\A${re}\\Z ${\$self->grammar_regexp} ];
  105         2054  
45 105         3751 my $_re;
46             my @values = $self->text =~ (
47             Babble::Config::CACHE_RE ? $SUBMATCHES_COMPILE_CACHE{$submatch_re} : $_re ||=
48 105   66     66265 do {
49 18         68 warn "Cache miss submatches\n" if Babble::Config::CACHE_RE && Babble::Config::DEBUG_CACHE_MISS;
50 12     12   125 use re 'eval';
  12         26  
  12         746  
51 18         787523 my $re = qr/$submatch_re/x;
52 12     12   77 no re 'eval';
  12         33  
  12         7245  
53 18         10101 $re;
54             });
55 105 100       691 die "Match failed" unless @values;
56 98         239 my %submatches;
57 98         701 require Babble::SubMatch;
58 98         495 foreach my $idx (0 .. $#subrules) {
59             # there may be more than one capture with the same name if there's an
60             # alternation in the rule, or one may be optional, so we skip if that
61             # part of the pattern failed to capture
62 335 100       6317 next unless defined $values[$idx];
63 275         417 my ($name, $rule) = @{$subrules[$idx]};
  275         762  
64 275         5910 $submatches{$name} = Babble::SubMatch->new(
65             top_rule => [ $rule ],
66             start => $-[$idx+1],
67             text => $values[$idx],
68             parent => $self,
69             );
70             }
71 98         4031 return \%submatches;
72             };
73              
74             sub subtexts {
75 67     67 0 246 my ($self, @names) = @_;
76 67 100       231 unless (@names) {
77 6         12 my %s = %{$self->submatches};
  6         122  
78 6         92 return +{ map +( $_ => $s{$_}->text ), keys %s };
79             }
80 61 100       147 map +($_ ? $_->text : undef), @{$self->submatches}{@names};
  61         1233  
81             }
82              
83             sub _rule_to_re {
84 383     383   738 my $re = $_[1];
85 383 100       1381 return "(?&Perl${re})" unless ref($re);
86 279 100       2831 return join '', map +(ref($_) ? $_->[1] : $_), @$re;
87             }
88              
89             sub is_valid {
90 2     2 0 2583 my ($self) = @_;
91 2         12 return !!$self->text =~ /${\$self->top_re} ${\$self->grammar_regexp}/x;
  2         49  
  2         44  
92             }
93              
94             my %MATCH_POS_COMPILE_CACHE;
95             sub match_positions_of {
96 317     317 0 648 my ($self, $of) = @_;
97 317         452 our @F;
98             my $wrapped = $self->grammar->clone->extend_rule(
99 317     317   6286 $of => sub { '('.$_[0].')'.'(?{ push @Babble::Match::F, [ pos() - length($^N), length($^N) ] })' }
100 317         5348 )->grammar_regexp;
101 317         4736 my @found = do {
102 317         784 local @F;
103 317         1323 local $_ = $self->text;
104 317         516 my $mp_re = qq/${\$self->top_re} ${wrapped}/;
  317         8208  
105 317         11351 my $_re;
106             $_ =~ ( Babble::Config::CACHE_RE ? $MATCH_POS_COMPILE_CACHE{$mp_re} : $_re ||=
107 317   66     360083 do {
108 32         105 warn "Cache miss match_positions_of(): @{[ $self->top_re ]}\n" if Babble::Config::CACHE_RE && Babble::Config::DEBUG_CACHE_MISS;
109 12     12   95 use re 'eval';
  12         25  
  12         637  
110 32         1558625 my $re = qr/$mp_re/x;
111 12     12   79 no re 'eval';
  12         111  
  12         9697  
112 32         43976 $re;
113             }
114             );
115 317         1501 @F;
116             };
117 176         1246 return map { [ split ',', $_ ] }
118             List::Util::uniqstr
119 317         1313 map { join ",", @$_ } @found;
  264         1654  
120             }
121              
122             sub each_match_of {
123 310     310 0 1602 my ($self, $of, $call) = @_;
124 310         807 my @found = $self->match_positions_of($of);
125 310 100       1122 return unless @found;
126 118         8315 require Babble::SubMatch;
127 118         566 while (my $f = shift @found) {
128 160         1053 my $match = substr($self->text, $f->[0], $f->[1]);
129 160         3730 my $obj = Babble::SubMatch->new(
130             top_rule => $of,
131             start => $f->[0],
132             text => $match,
133             parent => $self,
134             );
135 160         27192 $call->($obj);
136 160 100       1208 if (my $len_diff = length($obj->text) - $f->[1]) {
137 87         551 foreach my $later (@found) {
138 9 100       61 if ($later->[0] <= $f->[0]) {
139 1         25 $later->[1] += $len_diff;
140             } else {
141 8         62 $later->[0] += $len_diff;
142             }
143             }
144             }
145             }
146 118         318 return $self;
147             }
148              
149             sub each_match_within {
150 279     279 0 4137 my ($self, $within, $rule, $call) = @_;
151 279         785 my $match_re = $self->_rule_to_re($rule);
152 279         6067 my $extend_grammar = $self->grammar->clone;
153 279         26314 $extend_grammar->add_rule(
154             BabbleInnerMatch => $match_re,
155             )->augment_rule($within => '(?&PerlBabbleInnerMatch)');
156 279         819 local $self->{grammar} = $extend_grammar;
157             $self->each_match_of(BabbleInnerMatch => sub {
158 116     116   293 $_[0]->{top_rule} = $rule; # intentionally hacky, should go away (or rwp) later
159 116         474 $call->($_[0]);
160 279         1413 });
161 279         4970 return $self;
162             }
163              
164             sub replace_substring {
165 568     568 0 2332 my ($self, $start, $length, $replace) = @_;
166 568         1288 my $text = $self->text;
167 568         1174 substr($text, $start, $length, $replace);
168 568         1561 $self->_set_text($text);
169 568         797 foreach my $submatch (values %{$self->submatches}) {
  568         8988  
170 625 100       2447 next unless defined $submatch;
171 623 100       1479 if ($submatch->start > $start) {
172 230         495 $submatch->{start} += length($replace) - $length;
173             }
174             }
175 568         10434 return $self;
176             }
177              
178             sub remove_use_argument {
179 132     132 0 432 my ($self, $use, $argument, $keep_empty) = @_;
180             $self->each_match_within(
181             UseStatement =>
182             [ "use\\s+${use}\\s+", [ explist => '.*?' ], ';' ],
183             sub {
184 3     3   8 my ($m) = @_;
185 3         56 my $explist = $m->submatches->{explist};
186 3 50       228 return unless my @explist_names = eval $explist->text;
187 3         20 my @remain = grep $_ ne $argument, @explist_names;
188 3 50       14 return unless @remain < @explist_names;
189 3 100       11 unless (@remain) {
190 1 50       11 ($keep_empty ? $explist : $m)->replace_text('');
191 1         7 return;
192             }
193 2         25 $explist->replace_text('qw('.join(' ', @remain).')');
194             }
195 132         1055 );
196             }
197              
198             sub remove_use_statement {
199 3     3 0 10 my ($self, $use) = @_;
200             $self->each_match_within(
201             UseStatement =>
202             [ "use\\s+${use}.*?;" ],
203 1     1   5 sub { shift->replace_text('') },
204 3         24 );
205             }
206              
207             1;