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 is a
14             L.
15              
16             C is the parent of
17             L.
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.
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   67 use strict;
  9         26  
  9         281  
45 9     9   72 use warnings;
  9         34  
  9         246  
46              
47 9     9   50 use base qw{ PPIx::Regexp::Token };
  9         23  
  9         778  
48              
49 9     9   2966 use PPI::Document;
  9         620633  
  9         438  
50 9         1324 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   97 };
  9         24  
57 9     9   80 use PPIx::Regexp::Util qw{ __instance };
  9         19  
  9         628  
58              
59             our $VERSION = '0.087';
60              
61 9     9   66 use constant TOKENIZER_ARGUMENT_REQUIRED => 1;
  9         718  
  9         595  
62 9     9   506 use constant VERSION_WHEN_IN_REGEX_SET => undef;
  9         61  
  9         6853  
63              
64             sub __new {
65 146     146   7284 my ( $class, $content, %arg ) = @_;
66              
67             defined $arg{perl_version_introduced}
68 146 100       535 or $arg{perl_version_introduced} = '5.005';
69              
70 146         674 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 146 100       801 if ( $arg{tokenizer}->cookie( COOKIE_REGEX_SET ) ) {
75 1 50       7 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 146 100 66     719 and $self->{perl_version_introduced} = '5.019005';
84              
85 146         841 return $self;
86             }
87              
88             sub content {
89 271     271 1 583 my ( $self ) = @_;
90 271 50       673 if ( exists $self->{content} ) {
    0          
91 271         925 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 8 return 'Perl expression';
101             }
102              
103             =head2 is_matcher
104              
105             This method returns C 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
115             representing the content. This document should be considered read only.
116              
117             B 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 149     149 1 419 my ( $self ) = @_;
127 149 100       661 if ( exists $self->{ppi} ) {
    50          
128 5         23 return $self->{ppi};
129             } elsif ( exists $self->{content} ) {
130 144         231 my $content;
131 144         332 my $location = $self->{location};
132 144 100       336 if ( $location ) {
133 2         3 my $fn;
134 2 50       17 if( defined( $fn = $location->[LOCATION_LOGICAL_FILE] ) ) {
135 2         16 $fn =~ s/ (?= [\\"] ) /\\/smxg;
136 2         11 $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         7 $content .= ' ' x ( $location->[LOCATION_COLUMN] - 1 );
141             }
142              
143 144         485 $content .= $self->__ppi_normalize_content();
144              
145 144         731 $self->{ppi} = PPI::Document->new( \$content );
146              
147 144 100       153056 if ( $location ) {
148             # Generate locations now.
149 2         13 $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         889 my $elem;
155             # Remove the '#line' directive if we find it
156 2 50 33     19 $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         199 my $wid = $location->[LOCATION_COLUMN] - 1;
164             $wid
165 2 100 33     11 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 144         632 return $self->{ppi};
172              
173             } else {
174 0         0 return;
175             }
176             }
177              
178             sub width {
179 21     21 1 54 return ( undef, undef );
180             }
181              
182             sub __ppi_normalize_content {
183 51     51   133 my ( $self ) = @_;
184 51         149 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   110 no warnings qw{ qw }; ## no critic (ProhibitNoWarnings)
  9         26  
  9         1880  
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 40     40   126 return \%accept;
199             }
200             }
201              
202             sub __PPIX_TOKENIZER__regexp {
203 14     14   56 my ( undef, $tokenizer, $character ) = @_;
204              
205 14 50       58 $character eq '{' or return;
206              
207 14 50       51 my $offset = $tokenizer->find_matching_delimiter()
208             or return;
209              
210 14         46 return $offset + 1; # to include the closing delimiter.
211             }
212              
213             1;
214              
215             __END__