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   71 use strict;
  9         22  
  9         261  
45 9     9   55 use warnings;
  9         22  
  9         238  
46              
47 9     9   50 use base qw{ PPIx::Regexp::Token };
  9         19  
  9         741  
48              
49 9     9   2906 use PPI::Document;
  9         570428  
  9         392  
50 9         1186 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   79 };
  9         23  
57 9     9   71 use PPIx::Regexp::Util qw{ __instance };
  9         25  
  9         577  
58              
59             our $VERSION = '0.088';
60              
61 9     9   64 use constant TOKENIZER_ARGUMENT_REQUIRED => 1;
  9         599  
  9         549  
62 9     9   490 use constant VERSION_WHEN_IN_REGEX_SET => undef;
  9         43  
  9         6254  
63              
64             sub __new {
65 146     146   6782 my ( $class, $content, %arg ) = @_;
66              
67             defined $arg{perl_version_introduced}
68 146 100       487 or $arg{perl_version_introduced} = '5.005';
69              
70 146         700 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       659 if ( $arg{tokenizer}->cookie( COOKIE_REGEX_SET ) ) {
75 1 50       13 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 146 100 66     652 and $self->{perl_version_introduced} = '5.019005';
84              
85 146         822 return $self;
86             }
87              
88             sub content {
89 271     271 1 606 my ( $self ) = @_;
90 271 50       708 if ( exists $self->{content} ) {
    0          
91 271         956 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 4 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 372 my ( $self ) = @_;
127 149 100       637 if ( exists $self->{ppi} ) {
    50          
128 5         19 return $self->{ppi};
129             } elsif ( exists $self->{content} ) {
130 144         242 my $content;
131 144         332 my $location = $self->{location};
132 144 100       338 if ( $location ) {
133 2         4 my $fn;
134 2 50       7 if( defined( $fn = $location->[LOCATION_LOGICAL_FILE] ) ) {
135 2         7 $fn =~ s/ (?= [\\"] ) /\\/smxg;
136 2         17 $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         8 $content .= ' ' x ( $location->[LOCATION_COLUMN] - 1 );
141             }
142              
143 144         589 $content .= $self->__ppi_normalize_content();
144              
145 144         759 $self->{ppi} = PPI::Document->new( \$content );
146              
147 144 100       148371 if ( $location ) {
148             # Generate locations now.
149 2         16 $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         897 my $elem;
155             # Remove the '#line' directive if we find it
156 2 50 33     14 $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         164 my $wid = $location->[LOCATION_COLUMN] - 1;
164             $wid
165 2 100 33     15 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         629 return $self->{ppi};
172              
173             } else {
174 0         0 return;
175             }
176             }
177              
178             sub width {
179 21     21 1 58 return ( undef, undef );
180             }
181              
182             sub __ppi_normalize_content {
183 51     51   121 my ( $self ) = @_;
184 51         153 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   80 no warnings qw{ qw }; ## no critic (ProhibitNoWarnings)
  9         28  
  9         1680  
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   123 return \%accept;
199             }
200             }
201              
202             sub __PPIX_TOKENIZER__regexp {
203 14     14   52 my ( undef, $tokenizer, $character ) = @_;
204              
205 14 50       49 $character eq '{' or return;
206              
207 14 50       49 my $offset = $tokenizer->find_matching_delimiter()
208             or return;
209              
210 14         53 return $offset + 1; # to include the closing delimiter.
211             }
212              
213             1;
214              
215             __END__