File Coverage

blib/lib/Babble/Plugin/SubstituteAndReturn.pm
Criterion Covered Total %
statement 65 66 98.4
branch 12 16 75.0
condition 1 3 33.3
subroutine 7 7 100.0
pod 0 1 0.0
total 85 93 91.4


line stmt bran cond sub pod time code
1             package Babble::Plugin::SubstituteAndReturn;
2              
3 1     1   72335 use Moo;
  1         7701  
  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   113 my ($text) = @_;
28              
29 40 50       385 if ( $text =~ $OP_TYPE_RE ) {
30 40         496 return $OP_TYPE_DATA{$REGMARK}{flags};
31             }
32              
33 0         0 return '';
34             }
35              
36             sub _transform_binary {
37 8     8   34 my ($self, $top) = @_;
38              
39 8         21 my $chained_re = qr{
40             \G
41             (
42             (?>(?&PerlOWS)) =~ (?>(?&PerlOWS))
43             ((?>
44             (?&PerlSubstitution)
45             | (?&PerlTransliteration)
46             ))
47             )
48 8         170 @{[ $top->grammar_regexp ]}
49             }x;
50              
51 8         1233 my $replaced;
52 8         39 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   85 my ($m) = @_;
63 29         66 my ($left, $right);
64 29 100       80 eval {
65 29         161 ($left, $right) = $m->subtexts(qw(left right));
66 22         121 1
67             } or return;
68 22         86 my ($flags) = $right =~ _get_flags($right);
69 22 100       162 return unless (my $newflags = $flags) =~ s/r//g;
70              
71             # find chained substitutions
72             # ... =~ s///r =~ s///r =~ s///r
73 10         49 my $top_text = $top->text;
74 10         92 pos( $top_text ) = $m->start + length $m->text;
75 10         33 my $chained_subs_length = 0;
76 10         18 my @chained_subs;
77 10         2649 while( $top_text =~ /$chained_re/g ) {
78 11         3558 $chained_subs_length += length $1;
79 11         337 push @chained_subs, $2;
80             }
81 10         39 for my $subst_c (@chained_subs) {
82 11         32 my ($f_c) = $subst_c =~ _get_flags($subst_c);
83 11 50       64 die "Chained substitution must use the /r modifier"
84             unless (my $nf_c = $f_c) =~ s/r//g;
85 11         139 $subst_c =~ s/\Q${f_c}\E$/${nf_c}/;
86             }
87              
88 10         112 $right =~ s/\Q${flags}\E$/${newflags}/;
89 10         45 $left =~ s/\s+$//;
90 10         247 my $genlex = '$'.$m->gensym;
91              
92 10 100       47 if( @chained_subs ) {
93 7         55 my $chained_for = 'for ('.$genlex.') { '
94             . join("; ", @chained_subs)
95             . ' }';
96 7         88 $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         59 $replaced++;
110 18         339 });
111             } while( $replaced );
112             }
113              
114             sub _transform_contextualise {
115 8     8   25 my ($self, $top) = @_;
116              
117 8         16 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   56 my ($m) = @_;
123 20         71 my $expr_text = $m->text;
124 20         45 my @start_pos = do {
125 20 100       220 if( $expr_text =~ $OP_TYPE_RE ) {
126 7         85 my $rule = $OP_TYPE_DATA{$REGMARK}{rule};
127 7         90 my @pos = $m->match_positions_of($rule);
128 7 50 33     106 return unless @pos && $pos[0][0] == 0;
129 7         21 @{ $pos[0] };
  7         37  
130             } else {
131 13         43 return;
132             }
133             };
134 7         39 my $text = substr($expr_text, $start_pos[0], $start_pos[1]);
135 7         35 my ($flags) = $text =~ _get_flags($text);
136 7 50       49 return unless $flags =~ /r/;
137 7         66 push @subst_pos, $m->start;
138 8         85 });
139              
140             # Insert context variable and binding operator
141 8         97 my $diff = 0;
142 8         24 my $replace = '$_ =~ ';
143 8         40 while( my $pos = shift @subst_pos ) {
144 7         40 $top->replace_substring($pos + $diff, 0, $replace);
145 7         36 $diff += length $replace;
146             }
147             };
148             }
149              
150             sub transform_to_plain {
151 8     8 0 8807 my ($self, $top) = @_;
152              
153 8         42 $self->_transform_contextualise($top);
154              
155 8         34 $self->_transform_binary($top);
156             }
157              
158             1;
159             __END__
160              
161             =head1 NAME
162              
163             Babble::Plugin::SubstituteAndReturn - Plugin for /r flag for substitution and transliteration
164              
165             =head1 SYNOPSIS
166              
167             Converts usage of the C<s///r> and C<tr///r> syntax to substitution and
168             transliteration without the C</r> flag.
169              
170             =head1 SEE ALSO
171              
172             L<E<sol>r flag|Syntax::Construct/"/r">
173              
174             =cut