1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269
|
package PPI::Token::_QuoteEngine::Full;
# Full quote engine
use strict;
use base 'PPI::Token::_QuoteEngine';
use Clone ();
use vars qw{$VERSION %quotes %sections};
BEGIN {
$VERSION = '0.903';
# Prototypes for the different braced sections
%sections = (
'(' => { type => '()', _close => ')' },
'<' => { type => '<>', _close => '>' },
'[' => { type => '[]', _close => ']' },
'{' => { type => '{}', _close => '}' },
);
# For each quote type, the extra fields that should be set.
# This should give us faster initialization.
%quotes = (
'q' => { operator => 'q', braced => undef, seperator => undef, _sections => 1 },
'qq' => { operator => 'qq', braced => undef, seperator => undef, _sections => 1 },
'qx' => { operator => 'qx', braced => undef, seperator => undef, _sections => 1 },
'qw' => { operator => 'qw', braced => undef, seperator => undef, _sections => 1 },
'qr' => { operator => 'qr', braced => undef, seperator => undef, _sections => 1, modifiers => 1 },
'm' => { operator => 'm', braced => undef, seperator => undef, _sections => 1, modifiers => 1 },
's' => { operator => 's', braced => undef, seperator => undef, _sections => 2, modifiers => 1 },
'tr' => { operator => 'tr', braced => undef, seperator => undef, _sections => 2, modifiers => 1 },
# Y is the little used varient of tr
'y' => { operator => 'y', braced => undef, seperator => undef, _sections => 2, modifiers => 1 },
'/' => { operator => undef, braced => 0, seperator => '/', _sections => 1, modifiers => 1 },
# Angle brackets quotes mean "readline(*FILEHANDLE)"
'<' => { operator => undef, braced => 1, seperator => undef, _sections => 1, },
# The final ( and kind of depreciated ) "first match only" one is not
# used yet, since I'm not sure on the context differences between
# this and the trinary operator, but its here for completeness.
'?' => { operator => undef, braced => 0, seperator => '?', _sections => 1, modifieds => 1 },
);
}
sub new {
my $class = shift;
my $init = defined $_[0] ? shift : return undef;
# Create the token
### This manual SUPER'ing ONLY works because none of
### Token::Quote, Token::QuoteLike and Token::Regexp
### implement a new function of their own.
my $self = PPI::Token::new( $class, $init ) or return undef;
# Do we have a prototype for the intializer? If so, add the extra fields
my $options = $quotes{$init} or return $self->_error( "Unknown quote type '$init'" );
$self->{$_} = $options->{$_} foreach keys %$options;
# Set up the modifiers hash if needed
$self->{modifiers} = {} if $self->{modifiers};
# Handle the special < base
if ( $init eq '<' ) {
$self->{sections}->[0] = Clone::clone( $sections{'<'} );
}
$self;
}
sub _fill {
my $class = shift;
my $t = shift;
my $self = $t->{token} or return undef;
# Load in the operator stuff if needed
if ( $self->{operator} ) {
# In an operator based quote-like, handle the gap between the
# operator and the opening seperator.
if ( substr( $t->{line}, $t->{line_cursor}, 1 ) =~ /\s/ ) {
# Go past the gap
my $gap = $self->_scan_quote_like_operator_gap( $t );
return undef unless defined $gap;
if ( ref $gap ) {
# End of file
$self->{content} .= $$gap;
return 0;
}
$self->{content} .= $gap;
}
# The character we are now on is the seperator. Capture,
# and advance into the first section.
$_ = substr( $t->{line}, $t->{line_cursor}++, 1 );
$self->{content} .= $_;
# Determine if these are normal or braced type sections
if ( my $section = $sections{$_} ) {
$self->{braced} = 1;
$self->{sections}->[0] = Clone::clone($section);
} else {
$self->{braced} = 0;
$self->{seperator} = $_;
}
}
# Parse different based on whether we are normal or braced
$_ = $self->{braced}
? $self->_fill_braced($t)
: $self->_fill_normal($t)
or return $_;
# Return now unless it has modifiers ( i.e. s/foo//eieio )
return 1 unless $self->{modifiers};
# Check for modifiers
my $char;
my $len = 0;
while ( ($char = substr( $t->{line}, $t->{line_cursor} + 1, 1 )) =~ /\w/ ) {
if ( $char eq '_' ) {
return $self->_error( "Syntax error. Cannot use underscore '_' as regex modifier" );
}
$len++;
$self->{content} .= $char;
$self->{modifiers}->{lc $char} = 1;
$t->{line_cursor}++;
}
}
# Handle the content parsing path for normally seperated
sub _fill_normal {
my $self = shift;
my $t = shift;
# Get the content up to the next seperator
my $string = $self->_scan_for_unescaped_character( $t, $self->{seperator} );
return undef unless defined $string;
if ( ref $string ) {
# End of file
$self->{content} .= $$string;
return 0;
}
# Complete the properties of the first section
$self->{sections}->[0] = {
position => length $self->{content},
size => length($string) - 1
};
$self->{content} .= $string;
# We are done if there is only one section
return 1 if $self->{_sections} == 1;
# There are two sections.
# Advance into the next section
$t->{line_cursor}++;
# Get the content up to the end seperator
$string = $self->_scan_for_unescaped_character( $t, $self->{seperator} );
return undef unless defined $string;
if ( ref $string ) {
# End of file
$self->{content} .= $$string;
return 0;
}
# Complete the properties of the second section
$self->{sections}->[1] = {
position => length $self->{content},
size => length($string) - 1
};
$self->{content} .= $string;
1;
}
# Handle content parsing for matching crace seperated
sub _fill_braced {
my $self = shift;
my $t = shift;
# Get the content up to the close character
my $section = $self->{sections}->[0];
$DB::single = 1 unless $section->{_close};
$_ = $self->_scan_for_brace_character( $t, $section->{_close} );
return undef unless defined $_;
if ( ref $_ ) {
# End of file
$self->{content} .= $$_;
return 0;
}
$self->{content} .= $_;
# Complete the properties of the first section
$section->{position} = length $self->{content};
$section->{size} = length($_) - 1;
delete $section->{_close};
# We are done if there is only one section
return 1 if $self->{_sections} == 1;
# There are two sections.
# Is there a gap between the sections.
my $char = substr( $t->{line}, ++$t->{line_cursor}, 1 );
if ( $char =~ /\s/ ) {
# Go past the gap
$_ = $self->_scan_quote_like_operator_gap( $t );
return undef unless defined $_;
if ( ref $_ ) {
# End of file
$self->{content} .= $$_;
return 0;
}
$self->{content} .= $_;
$char = substr( $t->{line}, $t->{line_cursor}, 1 );
}
# Check that the next character is an open selector
if ( $section = $sections{$char} ) {
$self->{content} .= $char;
# Initialize the second section
$section = $self->{sections}->[1] = { %$section };
} else {
# Error, it has to be a brace of some sort
return $self->_error( "Syntax error. Second section of regex does not start with an open brace" );
}
# Advance into the second region
$t->{line_cursor}++;
# Get the content up to the close character
$_ = $self->_scan_for_brace_character( $t, $section->{_close} );
return undef unless defined $_;
if ( ref $_ ) {
# End of file
$self->{content} .= $$_;
return 0;
}
$self->{content} .= $_;
# Complete the properties for the second section
$section->{position} = length $self->{content};
$section->{size} = length($_) - 1;
delete $section->{_close};
1;
}
#####################################################################
# Additional methods to find out about the quote
# In a scalar context, get the number of sections
# In an array context, get the section information
sub _sections { wantarray ? @{$_[0]->{sections}} : scalar @{$_[0]->{sections}} }
1;
|