File Coverage

blib/lib/Babble/Match.pm
Criterion Covered Total %
statement 127 127 100.0
branch 37 40 92.5
condition 4 6 66.6
subroutine 25 25 100.0
pod 0 8 0.0
total 193 206 93.6


line stmt bran cond sub pod time code
1             package Babble::Match;
2              
3 12     12   149325 use Babble::Grammar;
  12         60  
  12         442  
4 12     12   5478 use Babble::SymbolGenerator;
  12         46  
  12         363  
5 12     12   79 use Mu;
  12         70  
  12         58  
6 12     12   10604 use List::Util 1.45;
  12         303  
  12         5703  
7              
8             ro 'top_rule';
9             rwp 'text';
10              
11             lazy 'grammar' => sub {
12 180 100   180   7637 $_[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   1032 $_[0]->can('parent')
19             ? $_[0]->parent->symbol_generator
20             : Babble::SymbolGenerator->new
21             } => handles => [ 'gensym' ];
22              
23             lazy top_re => sub {
24 98     98   1288 my ($self) = @_;
25 98         442 my $top = $self->_rule_to_re($self->top_rule);
26 98         5874 return "\\A${top}\\Z";
27             };
28              
29             my %SUBMATCHES_COMPILE_CACHE;
30             lazy submatches => sub {
31 327     327   3200 my ($self) = @_;
32 327 100       1676 return {} unless ref(my $top = $self->top_rule);
33 242         428 my @subrules;
34             my $re = join '', map {
35 242         588 ref($_)
36 653 100       1754 ? do {
37 337         584 push @subrules, $_;
38 337         700 my ($name, $rule) = @$_;
39 337         1514 "(${rule})"
40             }
41             : $_
42             } @$top;
43 242 100       1119 return {} unless @subrules;
44 99         389 my $submatch_re = qq[ \\A${re}\\Z ${\$self->grammar_regexp} ];
  99         2043  
45 99         3546 my $_re;
46             my @values = $self->text =~ (
47             Babble::Config::CACHE_RE ? $SUBMATCHES_COMPILE_CACHE{$submatch_re} : $_re = ( Babble::Config::CACHE_RE ? $SUBMATCHES_COMPILE_CACHE{$submatch_re} : 0 )
48 99   66     76617 || do {
49             warn "Cache miss submatches\n" if Babble::Config::CACHE_RE && Babble::Config::DEBUG_CACHE_MISS;
50 12     12   97 use re 'eval';
  12         27  
  12         823  
51             my $re = qr/$submatch_re/x;
52 12     12   123 no re 'eval';
  12         39  
  12         7527  
53             $re;
54             });
55 99 100       690 die "Match failed" unless @values;
56 92         229 my %submatches;
57 92         655 require Babble::SubMatch;
58 92         471 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 323 100       5673 next unless defined $values[$idx];
63 263         410 my ($name, $rule) = @{$subrules[$idx]};
  263         716  
64 263         5754 $submatches{$name} = Babble::SubMatch->new(
65             top_rule => [ $rule ],
66             start => $-[$idx+1],
67             text => $values[$idx],
68             parent => $self,
69             );
70             }
71 92         3571 return \%submatches;
72             };
73              
74             sub subtexts {
75 61     61 0 200 my ($self, @names) = @_;
76 61 100       204 unless (@names) {
77 6         9 my %s = %{$self->submatches};
  6         121  
78 6         78 return +{ map +( $_ => $s{$_}->text ), keys %s };
79             }
80 55 100       163 map +($_ ? $_->text : undef), @{$self->submatches}{@names};
  55         1124  
81             }
82              
83             sub _rule_to_re {
84 341     341   712 my $re = $_[1];
85 341 100       1238 return "(?&Perl${re})" unless ref($re);
86 243 100       2535 return join '', map +(ref($_) ? $_->[1] : $_), @$re;
87             }
88              
89             sub is_valid {
90 2     2 0 2599 my ($self) = @_;
91 2         12 return !!$self->text =~ /${\$self->top_re} ${\$self->grammar_regexp}/x;
  2         45  
  2         91  
92             }
93              
94             my %MATCH_POS_COMPILE_CACHE;
95             sub match_positions_of {
96 281     281 0 591 my ($self, $of) = @_;
97 281         402 our @F;
98             my $wrapped = $self->grammar->clone->extend_rule(
99 281     281   5360 $of => sub { '('.$_[0].')'.'(?{ push @Babble::Match::F, [ pos() - length($^N), length($^N) ] })' }
100 281         4771 )->grammar_regexp;
101 281         4501 my @found = do {
102 281         835 local @F;
103 281         1670 local $_ = $self->text;
104 281         450 my $mp_re = qq/${\$self->top_re} ${wrapped}/;
  281         7056  
105 281         10469 my $_re;
106             $_ =~ ( Babble::Config::CACHE_RE ? $MATCH_POS_COMPILE_CACHE{$mp_re} : $_re = ( Babble::Config::CACHE_RE ? $MATCH_POS_COMPILE_CACHE{$mp_re} : 0 )
107 281   66     377420 || do {
108             warn "Cache miss match_positions_of(): @{[ $self->top_re ]}\n" if Babble::Config::CACHE_RE && Babble::Config::DEBUG_CACHE_MISS;
109 12     12   103 use re 'eval';
  12         29  
  12         685  
110             my $re = qr/$mp_re/x;
111 12     12   88 no re 'eval';
  12         102  
  12         10307  
112             $re;
113             }
114             );
115 281         1449 @F;
116             };
117 170         1294 return map { [ split ',', $_ ] }
118             List::Util::uniqstr
119 281         1164 map { join ",", @$_ } @found;
  258         1741  
120             }
121              
122             sub each_match_of {
123 274     274 0 1428 my ($self, $of, $call) = @_;
124 274         719 my @found = $self->match_positions_of($of);
125 274 100       1016 return unless @found;
126 112         8527 require Babble::SubMatch;
127 112         523 while (my $f = shift @found) {
128 154         1048 my $match = substr($self->text, $f->[0], $f->[1]);
129 154         3542 my $obj = Babble::SubMatch->new(
130             top_rule => $of,
131             start => $f->[0],
132             text => $match,
133             parent => $self,
134             );
135 154         26687 $call->($obj);
136 154 100       1141 if (my $len_diff = length($obj->text) - $f->[1]) {
137 81         514 foreach my $later (@found) {
138 9 100       51 if ($later->[0] <= $f->[0]) {
139 1         21 $later->[1] += $len_diff;
140             } else {
141 8         56 $later->[0] += $len_diff;
142             }
143             }
144             }
145             }
146 112         326 return $self;
147             }
148              
149             sub each_match_within {
150 243     243 0 4001 my ($self, $within, $rule, $call) = @_;
151 243         720 my $match_re = $self->_rule_to_re($rule);
152 243         5416 my $extend_grammar = $self->grammar->clone;
153 243         22603 $extend_grammar->add_rule(
154             BabbleInnerMatch => $match_re,
155             )->augment_rule($within => '(?&PerlBabbleInnerMatch)');
156 243         751 local $self->{grammar} = $extend_grammar;
157             $self->each_match_of(BabbleInnerMatch => sub {
158 110     110   293 $_[0]->{top_rule} = $rule; # intentionally hacky, should go away (or rwp) later
159 110         450 $call->($_[0]);
160 243         1277 });
161 243         4506 return $self;
162             }
163              
164             sub replace_substring {
165 532     532 0 2186 my ($self, $start, $length, $replace) = @_;
166 532         1107 my $text = $self->text;
167 532         1175 substr($text, $start, $length, $replace);
168 532         1671 $self->_set_text($text);
169 532         810 foreach my $submatch (values %{$self->submatches}) {
  532         8689  
170 601 100       2317 next unless defined $submatch;
171 599 100       1378 if ($submatch->start > $start) {
172 224         499 $submatch->{start} += length($replace) - $length;
173             }
174             }
175 532         9317 return $self;
176             }
177              
178             sub remove_use_argument {
179 108     108 0 369 my ($self, $use, $argument, $keep_empty) = @_;
180             $self->each_match_within(
181             UseStatement =>
182             [ "use\\s+${use}\\s+", [ explist => '.*?' ], ';' ],
183             sub {
184 3     3   10 my ($m) = @_;
185 3         61 my $explist = $m->submatches->{explist};
186 3 50       233 return unless my @explist_names = eval $explist->text;
187 3         22 my @remain = grep $_ ne $argument, @explist_names;
188 3 50       13 return unless @remain < @explist_names;
189 3 100       12 unless (@remain) {
190 1 50       10 ($keep_empty ? $explist : $m)->replace_text('');
191 1         3 return;
192             }
193 2         26 $explist->replace_text('qw('.join(' ', @remain).')');
194             }
195 108         918 );
196             }
197              
198             sub remove_use_statement {
199 3     3 0 8 my ($self, $use) = @_;
200             $self->each_match_within(
201             UseStatement =>
202             [ "use\\s+${use}.*?;" ],
203 1     1   5 sub { shift->replace_text('') },
204 3         26 );
205             }
206              
207             1;