File Coverage

blib/lib/Babble/Plugin/SubstituteAndReturn.pm
Criterion Covered Total %
statement 65 68 95.5
branch 12 16 75.0
condition 1 3 33.3
subroutine 7 8 87.5
pod 0 2 0.0
total 85 97 87.6


line stmt bran cond sub pod time code
1             package Babble::Plugin::SubstituteAndReturn;
2              
3 1     1   68374 use Moo;
  1         7137  
  1         5  
4              
5             our ($REGMARK, $REGERROR);
6              
7             my %OP_TYPE_DATA = (
8             s => {
9             rule => 'QuotelikeS',
10             flags => qr/([msixpodualgcern]*+)$/,
11             },
12             y => {
13             rule => 'QuotelikeTR',
14             flags => qr/([cdsr]*+)$/,
15             },
16             );
17              
18             my $OP_TYPE_RE = qr{
19             \A
20             (?:
21             s (*MARK:s)
22             | (?: y|tr ) (*MARK:y)
23             )
24             }x;
25              
26             sub _get_flags {
27 40     40   101 my ($text) = @_;
28              
29 40 50       335 if ( $text =~ $OP_TYPE_RE ) {
30 40         389 return $OP_TYPE_DATA{$REGMARK}{flags};
31             }
32              
33 0         0 return '';
34             }
35              
36             sub _transform_binary {
37 8     8   63 my ($self, $top) = @_;
38              
39 8         22 my $chained_re = qr{
40             \G
41             (
42             (?>(?&PerlOWS)) =~ (?>(?&PerlOWS))
43             ((?>
44             (?&PerlSubstitution)
45             | (?&PerlTransliteration)
46             ))
47             )
48 8         169 @{[ $top->grammar_regexp ]}
49             }x;
50              
51 8         990 my $replaced;
52 8         60 do {
53 18         49 $replaced = 0;
54             $top->each_match_within(BinaryExpression => [
55             [ 'left' => '(?>(?&PerlPrefixPostfixTerm))' ],
56             '(?>(?&PerlOWS)) =~ (?>(?&PerlOWS))',
57             [ 'right' => '(?>
58             (?&PerlSubstitution)
59             | (?&PerlTransliteration)
60             )' ],
61             ] => sub {
62 29     29   72 my ($m) = @_;
63 29         53 my ($left, $right);
64 29 100       70 eval {
65 29         118 ($left, $right) = $m->subtexts(qw(left right));
66 22         91 1
67             } or return;
68 22         83 my ($flags) = $right =~ _get_flags($right);
69 22 100       144 return unless (my $newflags = $flags) =~ s/r//g;
70              
71             # find chained substitutions
72             # ... =~ s///r =~ s///r =~ s///r
73 10         37 my $top_text = $top->text;
74 10         55 pos( $top_text ) = $m->start + length $m->text;
75 10         28 my $chained_subs_length = 0;
76 10         22 my @chained_subs;
77 10         2116 while( $top_text =~ /$chained_re/g ) {
78 11         3505 $chained_subs_length += length $1;
79 11         349 push @chained_subs, $2;
80             }
81 10         34 for my $subst_c (@chained_subs) {
82 11         30 my ($f_c) = $subst_c =~ _get_flags($subst_c);
83 11 50       73 die "Chained substitution must use the /r modifier"
84             unless (my $nf_c = $f_c) =~ s/r//g;
85 11         128 $subst_c =~ s/\Q${f_c}\E$/${nf_c}/;
86             }
87              
88 10         108 $right =~ s/\Q${flags}\E$/${newflags}/;
89 10         36 $left =~ s/\s+$//;
90 10         250 my $genlex = '$'.$m->gensym;
91              
92 10 100       37 if( @chained_subs ) {
93 7         41 my $chained_for = 'for ('.$genlex.') { '
94             . join("; ", @chained_subs)
95             . ' }';
96 7         80 $top->replace_substring(
97             $m->start,
98             length($m->text) + $chained_subs_length,
99             '(map { (my '.$genlex.' = $_) =~ '.$right.'; '.$chained_for.' '.$genlex.' }'
100             .' '.$left.')[0]'
101             );
102             } else {
103 3         33 $m->replace_text(
104             '(map { (my '.$genlex.' = $_) =~ '.$right.'; '.$genlex.' }'
105             .' '.$left.')[0]'
106             );
107             }
108              
109 10         48 $replaced++;
110 18         248 });
111             } while( $replaced );
112             }
113              
114             sub _transform_contextualise {
115 8     8   23 my ($self, $top) = @_;
116              
117 8         18 do {
118 8         16 my @subst_pos; # sorted positions
119             # Look for substitution without binding operator:
120             # First look for an expression that begins with Substitution.
121             $top->each_match_of( Expression => sub {
122 20     20   46 my ($m) = @_;
123 20         54 my $expr_text = $m->text;
124 20         41 my @start_pos = do {
125 20 100       151 if( $expr_text =~ $OP_TYPE_RE ) {
126 7         28 my $rule = $OP_TYPE_DATA{$REGMARK}{rule};
127 7         35 my @pos = $m->match_positions_of($rule);
128 7 50 33     65 return unless @pos && $pos[0][0] == 0;
129 7         16 @{ $pos[0] };
  7         33  
130             } else {
131 13         43 return;
132             }
133             };
134 7         31 my $text = substr($expr_text, $start_pos[0], $start_pos[1]);
135 7         25 my ($flags) = $text =~ _get_flags($text);
136 7 50       39 return unless $flags =~ /r/;
137 7         54 push @subst_pos, $m->start;
138 8         79 });
139              
140             # Insert context variable and binding operator
141 8         86 my $diff = 0;
142 8         23 my $replace = '$_ =~ ';
143 8         36 while( my $pos = shift @subst_pos ) {
144 7         37 $top->replace_substring($pos + $diff, 0, $replace);
145 7         30 $diff += length $replace;
146             }
147             };
148             }
149              
150             sub transform_to_plain {
151 8     8 0 8058 my ($self, $top) = @_;
152              
153 8         37 $self->_transform_contextualise($top);
154              
155 8         31 $self->_transform_binary($top);
156             }
157              
158             sub check_bail_out_early {
159 0     0 0   my ($self, $top) = @_;
160 0           $top->text !~ m/ \b (?: s|y|tr ) \b /xs;
161             }
162              
163             1;
164             __END__
165              
166             =head1 NAME
167              
168             Babble::Plugin::SubstituteAndReturn - Plugin for /r flag for substitution and transliteration
169              
170             =head1 SYNOPSIS
171              
172             Converts usage of the C<s///r> and C<tr///r> syntax to substitution and
173             transliteration without the C</r> flag.
174              
175             =head1 SEE ALSO
176              
177             L<E<sol>r flag|Syntax::Construct/"/r">
178              
179             =cut