File Coverage

blib/lib/PPIx/Regexp/Token/Code.pm
Criterion Covered Total %
statement 67 72 93.0
branch 22 32 68.7
condition 8 18 44.4
subroutine 17 18 94.4
pod 5 5 100.0
total 119 145 82.0


line stmt bran cond sub pod time code
1             =head1 NAME
2              
3             PPIx::Regexp::Token::Code - Represent a chunk of Perl embedded in a regular expression.
4              
5             =head1 SYNOPSIS
6              
7             use PPIx::Regexp::Dumper;
8             PPIx::Regexp::Dumper->new(
9             'qr{(?{print "hello sailor\n"})}smx')->print;
10              
11             =head1 INHERITANCE
12              
13             C<PPIx::Regexp::Token::Code> is a
14             L<PPIx::Regexp::Token|PPIx::Regexp::Token>.
15              
16             C<PPIx::Regexp::Token::Code> is the parent of
17             L<PPIx::Regexp::Token::Interpolation|PPIx::Regexp::Token::Interpolation>.
18              
19             =head1 DESCRIPTION
20              
21             This class represents a chunk of Perl code embedded in a regular
22             expression. Specifically, it results from parsing things like
23              
24             (?{ code })
25             (??{ code })
26              
27             or from the replacement side of an s///e. Technically, interpolations
28             are also code, but they parse differently and therefore end up in a
29             different token.
30              
31             This token may not appear inside a regex set (i.e. C<(?[ ... ])>. If
32             found, it will become a C<PPIx::Regexp::Token::Unknown>.
33              
34             =head1 METHODS
35              
36             This class provides the following public methods. Methods not documented
37             here are private, and unsupported in the sense that the author reserves
38             the right to change or remove them without notice.
39              
40             =cut
41              
42             package PPIx::Regexp::Token::Code;
43              
44 9     9   46 use strict;
  9         10  
  9         252  
45 9     9   29 use warnings;
  9         14  
  9         331  
46              
47 9     9   29 use base qw{ PPIx::Regexp::Token };
  9         36  
  9         623  
48              
49 9     9   59 use PPI::Document;
  9         14  
  9         212  
50 9         805 use PPIx::Regexp::Constant qw{
51             COOKIE_REGEX_SET
52             LOCATION_COLUMN
53             LOCATION_LOGICAL_LINE
54             LOCATION_LOGICAL_FILE
55             @CARP_NOT
56 9     9   29 };
  9         10  
57 9     9   37 use PPIx::Regexp::Util qw{ __instance };
  9         10  
  9         444  
58              
59             our $VERSION = '0.091';
60              
61 9     9   34 use constant TOKENIZER_ARGUMENT_REQUIRED => 1;
  9         11  
  9         445  
62 9     9   36 use constant VERSION_WHEN_IN_REGEX_SET => undef;
  9         12  
  9         5278  
63              
64             sub __new {
65 147     147   9238 my ( $class, $content, %arg ) = @_;
66              
67             defined $arg{perl_version_introduced}
68 147 100       362 or $arg{perl_version_introduced} = '5.005';
69              
70 147         518 my $self = $class->SUPER::__new( $content, %arg );
71              
72             # TODO sort this out, since Token::Interpolation is a subclass, and
73             # those are legal in regex sets
74 147 100       522 if ( $arg{tokenizer}->cookie( COOKIE_REGEX_SET ) ) {
75 1 50       5 my $ver = $self->VERSION_WHEN_IN_REGEX_SET()
76             or return $self->__error( 'Code token not valid in Regex set' );
77             $self->{perl_version_introduced} < $ver
78 1 50       9 and $self->{perl_version_introduced} = $ver;
79             }
80              
81             $arg{tokenizer}->__recognize_postderef( $self )
82             and $self->{perl_version_introduced} < 5.019005
83 147 100 66     460 and $self->{perl_version_introduced} = '5.019005';
84              
85 147         615 return $self;
86             }
87              
88             sub content {
89 271     271 1 416 my ( $self ) = @_;
90 271 50       560 if ( exists $self->{content} ) {
    0          
91 271         733 return $self->{content};
92             } elsif ( exists $self->{ppi} ) {
93 0         0 return ( $self->{content} = $self->{ppi}->content() );
94             } else {
95 0         0 return;
96             }
97             }
98              
99             sub explain {
100 1     1 1 3 return 'Perl expression';
101             }
102              
103             =head2 is_matcher
104              
105             This method returns C<undef> because a static analysis can not in
106             general tell whether an interpolated value matches anything.
107              
108             =cut
109              
110 0     0 1 0 sub is_matcher { return undef; } ## no critic (ProhibitExplicitReturnUndef)
111              
112             =head2 ppi
113              
114             This convenience method returns the L<PPI::Document|PPI::Document>
115             representing the content. This document should be considered read only.
116              
117             B<Note> that if the location of the invocant is available the PPI
118             document will have stuff prefixed to it to make the location of the
119             tokens in the new document consistent with the location. This "stuff"
120             will include at least a C<#line> directive, and maybe leading white
121             space.
122              
123             =cut
124              
125             sub ppi {
126 150     150 1 290 my ( $self ) = @_;
127 150 100       525 if ( exists $self->{ppi} ) {
    50          
128 5         14 return $self->{ppi};
129             } elsif ( exists $self->{content} ) {
130 145         180 my $content;
131 145         205 my $location = $self->{location};
132 145 100       314 if ( $location ) {
133 2         3 my $fn;
134 2 50       5 if( defined( $fn = $location->[LOCATION_LOGICAL_FILE] ) ) {
135 2         6 $fn =~ s/ (?= [\\"] ) /\\/smxg;
136 2         5 $content = qq{#line $location->[LOCATION_LOGICAL_LINE] "$fn"\n};
137             } else {
138 0         0 $content = qq{#line $location->[LOCATION_LOGICAL_LINE]\n};
139             }
140 2         6 $content .= ' ' x ( $location->[LOCATION_COLUMN] - 1 );
141             }
142              
143 145         362 $content .= $self->__ppi_normalize_content();
144              
145 145         634 $self->{ppi} = PPI::Document->new( \$content );
146              
147 145 100       142064 if ( $location ) {
148             # Generate locations now.
149 2         9 $self->{ppi}->location();
150             # Remove the stuff we originally injected. NOTE that we can
151             # only get away with doing this if the removal does not
152             # invalidate the locations of the other tokens that we just
153             # generated.
154 2         648 my $elem;
155             # Remove the '#line' directive if we find it
156 2 50 33     9 $elem = $self->{ppi}->child( 0 )
      33        
157             and $elem->isa( 'PPI::Token::Comment' )
158             and $elem->content() =~ m/ \A \#line\b /smx
159             and $elem->remove();
160             # Remove the white space if we find it, and if it in fact
161             # represents only the white space we injected to get the
162             # column numbers right.
163 2         112 my $wid = $location->[LOCATION_COLUMN] - 1;
164             $wid
165 2 100 33     8 and $elem = $self->{ppi}->child( 0 )
      33        
      66        
166             and $elem->isa( 'PPI::Token::Whitespace' )
167             and $wid == length $elem->content()
168             and $elem->remove();
169             }
170              
171 145         507 return $self->{ppi};
172              
173             } else {
174 0         0 return;
175             }
176             }
177              
178             sub width {
179 21     21 1 40 return ( undef, undef );
180             }
181              
182             sub __ppi_normalize_content {
183 52     52   92 my ( $self ) = @_;
184 52         150 return $self->{content};
185             }
186              
187             # Return true if the token can be quantified, and false otherwise
188             # sub can_be_quantified { return };
189              
190             {
191 9     9   54 no warnings qw{ qw }; ## no critic (ProhibitNoWarnings)
  9         15  
  9         1306  
192              
193             my %accept = map { $_ => 1 } qw{ $ $# @ % & * };
194              
195             # Say what casts are accepted, since not all are in an
196             # interpolation.
197             sub __postderef_accept_cast {
198 41     41   99 return \%accept;
199             }
200             }
201              
202             sub __PPIX_TOKENIZER__regexp {
203 14     14   30 my ( undef, $tokenizer, $character ) = @_;
204              
205 14 50       63 $character eq '{' or return;
206              
207 14 50       35 my $offset = $tokenizer->find_matching_delimiter()
208             or return;
209              
210 14         31 return $offset + 1; # to include the closing delimiter.
211             }
212              
213             1;
214              
215             __END__
216              
217             =head1 SUPPORT
218              
219             Support is by the author. Please file bug reports at
220             L<https://rt.cpan.org/Public/Dist/Display.html?Name=PPIx-Regexp>,
221             L<https://github.com/trwyant/perl-PPIx-Regexp/issues>, or in
222             electronic mail to the author.
223              
224             =head1 AUTHOR
225              
226             Thomas R. Wyant, III F<wyant at cpan dot org>
227              
228             =head1 COPYRIGHT AND LICENSE
229              
230             Copyright (C) 2009-2023, 2025 by Thomas R. Wyant, III
231              
232             This program is free software; you can redistribute it and/or modify it
233             under the same terms as Perl 5.10.0. For more details, see the full text
234             of the licenses in the directory LICENSES.
235              
236             This program is distributed in the hope that it will be useful, but
237             without any warranty; without even the implied warranty of
238             merchantability or fitness for a particular purpose.
239              
240             =cut
241              
242             # ex: set textwidth=72 :