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   47 use strict;
  9         13  
  9         250  
45 9     9   29 use warnings;
  9         12  
  9         342  
46              
47 9     9   36 use base qw{ PPIx::Regexp::Token };
  9         13  
  9         650  
48              
49 9     9   42 use PPI::Document;
  9         11  
  9         207  
50 9         810 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   32 };
  9         9  
57 9     9   37 use PPIx::Regexp::Util qw{ __instance };
  9         11  
  9         443  
58              
59             our $VERSION = '0.092';
60              
61 9     9   31 use constant TOKENIZER_ARGUMENT_REQUIRED => 1;
  9         10  
  9         400  
62 9     9   41 use constant VERSION_WHEN_IN_REGEX_SET => undef;
  9         19  
  9         5486  
63              
64             sub __new {
65 147     147   6890 my ( $class, $content, %arg ) = @_;
66              
67             defined $arg{perl_version_introduced}
68 147 100       342 or $arg{perl_version_introduced} = '5.005';
69              
70 147         471 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       452 if ( $arg{tokenizer}->cookie( COOKIE_REGEX_SET ) ) {
75 1 50       4 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       8 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     412 and $self->{perl_version_introduced} = '5.019005';
84              
85 147         620 return $self;
86             }
87              
88             sub content {
89 271     271 1 416 my ( $self ) = @_;
90 271 50       487 if ( exists $self->{content} ) {
    0          
91 271         657 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 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 150     150 1 232 my ( $self ) = @_;
127 150 100       440 if ( exists $self->{ppi} ) {
    50          
128 5         14 return $self->{ppi};
129             } elsif ( exists $self->{content} ) {
130 145         168 my $content;
131 145         198 my $location = $self->{location};
132 145 100       265 if ( $location ) {
133 2         3 my $fn;
134 2 50       4 if( defined( $fn = $location->[LOCATION_LOGICAL_FILE] ) ) {
135 2         5 $fn =~ s/ (?= [\\"] ) /\\/smxg;
136 2         4 $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 145         354 $content .= $self->__ppi_normalize_content();
144              
145 145         656 $self->{ppi} = PPI::Document->new( \$content );
146              
147 145 100       118225 if ( $location ) {
148             # Generate locations now.
149 2         7 $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         593 my $elem;
155             # Remove the '#line' directive if we find it
156 2 50 33     11 $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         117 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         502 return $self->{ppi};
172              
173             } else {
174 0         0 return;
175             }
176             }
177              
178             sub width {
179 21     21 1 37 return ( undef, undef );
180             }
181              
182             sub __ppi_normalize_content {
183 52     52   80 my ( $self ) = @_;
184 52         141 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   55 no warnings qw{ qw }; ## no critic (ProhibitNoWarnings)
  9         12  
  9         1409  
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   85 return \%accept;
199             }
200             }
201              
202             sub __PPIX_TOKENIZER__regexp {
203 14     14   37 my ( undef, $tokenizer, $character ) = @_;
204              
205 14 50       53 $character eq '{' or return;
206              
207 14 50       40 my $offset = $tokenizer->find_matching_delimiter()
208             or return;
209              
210 14         26 return $offset + 1; # to include the closing delimiter.
211             }
212              
213             1;
214              
215             __END__