File Coverage

blib/lib/Babble/Match.pm
Criterion Covered Total %
statement 120 120 100.0
branch 37 40 92.5
condition 2 3 66.6
subroutine 23 23 100.0
pod 0 8 0.0
total 182 194 93.8


line stmt bran cond sub pod time code
1             package Babble::Match;
2              
3 12     12   149914 use Babble::Grammar;
  12         52  
  12         443  
4 12     12   5665 use Babble::SymbolGenerator;
  12         36  
  12         361  
5 12     12   83 use Mu;
  12         34  
  12         55  
6 12     12   10324 use List::Util 1.45;
  12         296  
  12         5435  
7              
8             ro 'top_rule';
9             rwp 'text';
10              
11             lazy 'grammar' => sub {
12 186 100   186   9024 $_[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   1013 $_[0]->can('parent')
19             ? $_[0]->parent->symbol_generator
20             : Babble::SymbolGenerator->new
21             } => handles => [ 'gensym' ];
22              
23             lazy top_re => sub {
24 98     98   1450 my ($self) = @_;
25 98         452 my $top = $self->_rule_to_re($self->top_rule);
26 98         4038553 return "\\A${top}\\Z";
27             };
28              
29             my %SUBMATCHES_COMPILE_CACHE;
30             lazy submatches => sub {
31 349     349   3917 my ($self) = @_;
32 349 100       2056 return {} unless ref(my $top = $self->top_rule);
33 260         527 my @subrules;
34             my $re = join '', map {
35 260         836 ref($_)
36 677 100       2023 ? do {
37 349         687 push @subrules, $_;
38 349         866 my ($name, $rule) = @$_;
39 349         1810 "(${rule})"
40             }
41             : $_
42             } @$top;
43 260 100       1423 return {} unless @subrules;
44 105         834 my $submatch_re = qq[ \\A${re}\\Z ${\$self->grammar_regexp} ];
  105         2419  
45             my @values = $self->text =~ (
46 105   66     68914 $SUBMATCHES_COMPILE_CACHE{$submatch_re} ||= do {
47 12     12   98 use re 'eval';
  12         27  
  12         706  
48 18         788062 my $re = qr/$submatch_re/x;
49 12     12   97 no re 'eval';
  12         33  
  12         16165  
50 18         10303 $re;
51             });
52 105 100       718 die "Match failed" unless @values;
53 98         252 my %submatches;
54 98         745 require Babble::SubMatch;
55 98         529 foreach my $idx (0 .. $#subrules) {
56             # there may be more than one capture with the same name if there's an
57             # alternation in the rule, or one may be optional, so we skip if that
58             # part of the pattern failed to capture
59 335 100       6019 next unless defined $values[$idx];
60 275         470 my ($name, $rule) = @{$subrules[$idx]};
  275         760  
61 275         5898 $submatches{$name} = Babble::SubMatch->new(
62             top_rule => [ $rule ],
63             start => $-[$idx+1],
64             text => $values[$idx],
65             parent => $self,
66             );
67             }
68 98         3899 return \%submatches;
69             };
70              
71             sub subtexts {
72 67     67 0 377 my ($self, @names) = @_;
73 67 100       253 unless (@names) {
74 6         14 my %s = %{$self->submatches};
  6         173  
75 6         106 return +{ map +( $_ => $s{$_}->text ), keys %s };
76             }
77 61 100       160 map +($_ ? $_->text : undef), @{$self->submatches}{@names};
  61         1439  
78             }
79              
80             sub _rule_to_re {
81 341     341   877 my $re = $_[1];
82 341 100       1470 return "(?&Perl${re})" unless ref($re);
83 243 100       4109 return join '', map +(ref($_) ? $_->[1] : $_), @$re;
84             }
85              
86             sub is_valid {
87 2     2 0 2998 my ($self) = @_;
88 2         13 return !!$self->text =~ /${\$self->top_re} ${\$self->grammar_regexp}/x;
  2         46  
  2         49  
89             }
90              
91             sub match_positions_of {
92 281     281 0 711 my ($self, $of) = @_;
93 281         526 our @F;
94             my $wrapped = $self->grammar->clone->extend_rule(
95 281     281   5478 $of => sub { '('.$_[0].')'.'(?{ push @Babble::Match::F, [ pos() - length($^N), length($^N) ] })' }
96 281         5228 )->grammar_regexp;
97 281         4779 my @found = do {
98 281         818 local @F;
99 281         1429 local $_ = $self->text;
100 281         552 /${\$self->top_re} ${wrapped}/x;
  281         7517  
101 281         121818 @F;
102             };
103 170         1527 return map { [ split ',', $_ ] }
104             List::Util::uniqstr
105 281         2250 map { join ",", @$_ } @found;
  258         2059  
106             }
107              
108             sub each_match_of {
109 274     274 0 2345 my ($self, $of, $call) = @_;
110 274         877 my @found = $self->match_positions_of($of);
111 274 100       1422 return unless @found;
112 112         9355 require Babble::SubMatch;
113 112         664 while (my $f = shift @found) {
114 154         1499 my $match = substr($self->text, $f->[0], $f->[1]);
115 154         4428 my $obj = Babble::SubMatch->new(
116             top_rule => $of,
117             start => $f->[0],
118             text => $match,
119             parent => $self,
120             );
121 154         28780 $call->($obj);
122 154 100       1322 if (my $len_diff = length($obj->text) - $f->[1]) {
123 81         540 foreach my $later (@found) {
124 9 100       65 if ($later->[0] <= $f->[0]) {
125 1         39 $later->[1] += $len_diff;
126             } else {
127 8         62 $later->[0] += $len_diff;
128             }
129             }
130             }
131             }
132 112         354 return $self;
133             }
134              
135             sub each_match_within {
136 243     243 0 5292 my ($self, $within, $rule, $call) = @_;
137 243         1002 my $match_re = $self->_rule_to_re($rule);
138 243         6237 my $extend_grammar = $self->grammar->clone;
139 243         30779 $extend_grammar->add_rule(
140             BabbleInnerMatch => $match_re,
141             )->augment_rule($within => '(?&PerlBabbleInnerMatch)');
142 243         943 local $self->{grammar} = $extend_grammar;
143             $self->each_match_of(BabbleInnerMatch => sub {
144 110     110   390 $_[0]->{top_rule} = $rule; # intentionally hacky, should go away (or rwp) later
145 110         576 $call->($_[0]);
146 243         1551 });
147 243         6642 return $self;
148             }
149              
150             sub replace_substring {
151 568     568 0 2384 my ($self, $start, $length, $replace) = @_;
152 568         1244 my $text = $self->text;
153 568         1212 substr($text, $start, $length, $replace);
154 568         1725 $self->_set_text($text);
155 568         866 foreach my $submatch (values %{$self->submatches}) {
  568         9185  
156 625 100       2424 next unless defined $submatch;
157 623 100       1560 if ($submatch->start > $start) {
158 230         554 $submatch->{start} += length($replace) - $length;
159             }
160             }
161 568         10519 return $self;
162             }
163              
164             sub remove_use_argument {
165 108     108 0 521 my ($self, $use, $argument, $keep_empty) = @_;
166             $self->each_match_within(
167             UseStatement =>
168             [ "use\\s+${use}\\s+", [ explist => '.*?' ], ';' ],
169             sub {
170 3     3   12 my ($m) = @_;
171 3         87 my $explist = $m->submatches->{explist};
172 3 50       283 return unless my @explist_names = eval $explist->text;
173 3         23 my @remain = grep $_ ne $argument, @explist_names;
174 3 50       14 return unless @remain < @explist_names;
175 3 100       12 unless (@remain) {
176 1 50       11 ($keep_empty ? $explist : $m)->replace_text('');
177 1         4 return;
178             }
179 2         24 $explist->replace_text('qw('.join(' ', @remain).')');
180             }
181 108         1231 );
182             }
183              
184             sub remove_use_statement {
185 3     3 0 12 my ($self, $use) = @_;
186             $self->each_match_within(
187             UseStatement =>
188             [ "use\\s+${use}.*?;" ],
189 1     1   7 sub { shift->replace_text('') },
190 3         33 );
191             }
192              
193             1;