Skip to content

Commit c65b360

Browse files
committed
proof-of-concept for feature-tracking and perl sub signatures (see #273)
1 parent 58f257c commit c65b360

File tree

8 files changed

+324
-19
lines changed

8 files changed

+324
-19
lines changed

lib/PPI/Document.pm

Lines changed: 31 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -128,17 +128,22 @@ In all cases, the document is considered to be "anonymous" and not tied back
128128
to where it was created from. Specifically, if you create a PPI::Document from
129129
a filename, the document will B<not> remember where it was created from.
130130
131+
Returns a C<PPI::Document> object, or C<undef> if parsing fails.
132+
L<PPI::Exception> objects can also be thrown if there are parsing problems.
133+
131134
The constructor also takes attribute flags.
132135
133-
At this time, the only available attribute is the C<readonly> flag.
136+
=head3 readonly
134137
135-
Setting C<readonly> to true will allow various systems to provide
136-
additional optimisations and caching. Note that because C<readonly> is an
137-
optimisation flag, it is off by default and you will need to explicitly
138-
enable it.
138+
Setting C<readonly> to true will allow various systems to provide additional
139+
optimisations and caching. Note that because C<readonly> is an optimisation
140+
flag, it is off by default and you will need to explicitly enable it.
139141
140-
Returns a C<PPI::Document> object, or C<undef> if parsing fails.
141-
L<PPI::Exception> objects can also be thrown if there are parsing problems.
142+
=head3 feature_mods
143+
144+
Setting feature_mods with a hashref allows defining perl parsing features to be
145+
enabled for the whole document. (e.g. when the code is assumed to be run as a
146+
oneliner)
142147
143148
=cut
144149

@@ -181,25 +186,25 @@ sub new {
181186
my $document = $CACHE->get_document($file_contents);
182187
return $class->_setattr( $document, %attr ) if $document;
183188

184-
$document = PPI::Lexer->lex_source( $$file_contents );
189+
$document = PPI::Lexer->lex_source( $$file_contents, %attr );
185190
if ( $document ) {
186191
# Save in the cache
187192
$CACHE->store_document( $document );
188-
return $class->_setattr( $document, %attr );
193+
return $document;
189194
}
190195
} else {
191-
my $document = PPI::Lexer->lex_file( $source );
192-
return $class->_setattr( $document, %attr ) if $document;
196+
my $document = PPI::Lexer->lex_file( $source, %attr );
197+
return $document if $document;
193198
}
194199

195200
} elsif ( _SCALAR0($source) ) {
196-
my $document = PPI::Lexer->lex_source( $$source );
197-
return $class->_setattr( $document, %attr ) if $document;
201+
my $document = PPI::Lexer->lex_source( $$source, %attr );
202+
return $document if $document;
198203

199204
} elsif ( _ARRAY0($source) ) {
200205
$source = join '', map { "$_\n" } @$source;
201-
my $document = PPI::Lexer->lex_source( $source );
202-
return $class->_setattr( $document, %attr ) if $document;
206+
my $document = PPI::Lexer->lex_source( $source, %attr );
207+
return $document if $document;
203208

204209
} else {
205210
$class->_error("Unknown object or reference was passed to PPI::Document::new");
@@ -229,6 +234,7 @@ sub _setattr {
229234
my ($class, $document, %attr) = @_;
230235
$document->{readonly} = !! $attr{readonly};
231236
$document->{filename} = $attr{filename};
237+
$document->{feature_mods} = $attr{feature_mods};
232238
return $document;
233239
}
234240

@@ -344,6 +350,16 @@ sub tab_width {
344350
$self->{tab_width} = shift;
345351
}
346352

353+
=head2 feature_mods { feature_name => $enabled }
354+
355+
=cut
356+
357+
sub feature_mods {
358+
my $self = shift;
359+
return $self->{feature_mods} unless @_;
360+
$self->{feature_mods} = shift;
361+
}
362+
347363
=pod
348364
349365
=head2 save

lib/PPI/Element.pm

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -467,9 +467,32 @@ sub previous_token {
467467
}
468468
}
469469

470+
=head2 presumed_features
470471
472+
Returns a hash that indicates which features appear to be active for the given
473+
element.
471474
475+
=cut
476+
477+
sub presumed_features {
478+
my ($self) = @_;
472479

480+
my @feature_mods;
481+
my $walker = $self;
482+
while ($walker) {
483+
my $sib_walk = $walker;
484+
while ($sib_walk) {
485+
push @feature_mods, $sib_walk if $sib_walk->can("feature_mods");
486+
$sib_walk = $sib_walk->sprevious_sibling;
487+
}
488+
$walker = $walker->parent;
489+
}
490+
491+
my %feature_mods = map %{$_}, reverse grep defined, map $_->feature_mods,
492+
@feature_mods;
493+
494+
return \%feature_mods;
495+
}
473496

474497
#####################################################################
475498
# Manipulation

lib/PPI/Lexer.pm

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,8 @@ creates a L<PPI::Tokenizer> for the content and lexes the token stream
133133
produced by the tokenizer. Basically, a sort of all-in-one method for
134134
getting a L<PPI::Document> object from a file name.
135135
136+
Additional arguments are passed to the tokenizer as a hash.
137+
136138
Returns a L<PPI::Document> object, or C<undef> on error.
137139
138140
=cut
@@ -143,6 +145,7 @@ sub lex_file {
143145
unless ( defined $file ) {
144146
return $self->_error("Did not pass a filename to PPI::Lexer::lex_file");
145147
}
148+
my %args = @_;
146149

147150
# Create the Tokenizer
148151
my $Tokenizer = eval {
@@ -154,7 +157,7 @@ sub lex_file {
154157
return $self->_error( $errstr );
155158
}
156159

157-
$self->lex_tokenizer( $Tokenizer );
160+
$self->lex_tokenizer( $Tokenizer, %args );
158161
}
159162

160163
=pod
@@ -165,6 +168,8 @@ The C<lex_source> method takes a normal scalar string as argument. It
165168
creates a L<PPI::Tokenizer> object for the string, and then lexes the
166169
resulting token stream.
167170
171+
Additional arguments are passed to the tokenizer as a hash.
172+
168173
Returns a L<PPI::Document> object, or C<undef> on error.
169174
170175
=cut
@@ -175,6 +180,7 @@ sub lex_source {
175180
unless ( defined $source and not ref $source ) {
176181
return $self->_error("Did not pass a string to PPI::Lexer::lex_source");
177182
}
183+
my %args = @_;
178184

179185
# Create the Tokenizer and hand off to the next method
180186
my $Tokenizer = eval {
@@ -186,7 +192,7 @@ sub lex_source {
186192
return $self->_error( $errstr );
187193
}
188194

189-
$self->lex_tokenizer( $Tokenizer );
195+
$self->lex_tokenizer( $Tokenizer, %args );
190196
}
191197

192198
=pod
@@ -196,6 +202,8 @@ sub lex_source {
196202
The C<lex_tokenizer> takes as argument a L<PPI::Tokenizer> object. It
197203
lexes the token stream from the tokenizer into a L<PPI::Document> object.
198204
205+
Additional arguments are set on the L<PPI::Document> produced.
206+
199207
Returns a L<PPI::Document> object, or C<undef> on error.
200208
201209
=cut
@@ -206,9 +214,11 @@ sub lex_tokenizer {
206214
return $self->_error(
207215
"Did not pass a PPI::Tokenizer object to PPI::Lexer::lex_tokenizer"
208216
) unless $Tokenizer;
217+
my %args = @_;
209218

210219
# Create the empty document
211220
my $Document = PPI::Document->new;
221+
ref($Document)->_setattr( $Document, %args ) if keys %args;
212222
$Tokenizer->_document($Document);
213223

214224
# Lex the token stream into the document

lib/PPI/Statement/Include.pm

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,9 @@ L<PPI::Statement>, L<PPI::Node> and L<PPI::Element> methods.
4545
=cut
4646

4747
use strict;
48+
49+
use version ();
50+
4851
use PPI::Statement ();
4952
use PPI::Statement::Include::Perl6 ();
5053

@@ -236,6 +239,46 @@ sub arguments {
236239
return @args;
237240
}
238241

242+
=head2 arguments
243+
244+
Returns a hashref of features identified as enabled by the include, or undef if
245+
the include does not enable features.
246+
247+
=cut
248+
249+
sub feature_mods {
250+
my ($self) = @_;
251+
252+
my %known = ( signatures => 1 );
253+
254+
return if $self->type eq "require";
255+
256+
if ( my $perl_version = $self->version ) {
257+
## crude proof of concept hack due to above
258+
return { signatures => 1 } if version::parse($perl_version) >= 5.035;
259+
260+
# # tried using feature.pm here, but it is impossible to install
261+
# # future versions of it, so e.g. a 5.20 install cannot know about
262+
# # 5.36 features
263+
# $perl_version = join ".", #
264+
# ( split /\./, $perl_version )[0],
265+
# 0 + ( split /\./, $perl_version )[1];
266+
# my $bundle = $feature::feature_bundle{$perl_version};
267+
# return { map +( $_ => 1 ), %{$bundle} };
268+
}
269+
270+
if ( $self->module eq "feature" ) {
271+
my @features = grep $known{$_},
272+
map +( $_->can("literal") || $_->can("string") || die "???" )->($_),
273+
map $_->isa("PPI::Structure::List") ? $_->children : $_,
274+
$self->arguments;
275+
my $on_or_off = $self->type eq "use" ? 1 : 0;
276+
return { map +( $_ => $on_or_off ), @features } if @features;
277+
}
278+
279+
return;
280+
}
281+
239282
1;
240283

241284
=pod

lib/PPI/Token.pm

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ use PPI::Token::Separator ();
7070
use PPI::Token::Data ();
7171
use PPI::Token::End ();
7272
use PPI::Token::Prototype ();
73+
use PPI::Token::Signature ();
7374
use PPI::Token::Attribute ();
7475
use PPI::Token::Unknown ();
7576

lib/PPI/Token/Signature.pm

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package PPI::Token::Signature;
2+
3+
=pod
4+
5+
=head1 NAME
6+
7+
PPI::Token::Signature - A subroutine signature descriptor
8+
9+
=head1 INHERITANCE
10+
11+
PPI::Token::Signature
12+
isa PPI::Token::Prototype
13+
isa PPI::Token
14+
isa PPI::Element
15+
16+
=head1 SYNOPSIS
17+
18+
TODO: document
19+
20+
=head1 DESCRIPTION
21+
22+
TODO: document
23+
24+
=cut
25+
26+
use strict;
27+
use PPI::Token::Prototype ();
28+
29+
our $VERSION = '1.276';
30+
31+
our @ISA = "PPI::Token::Prototype";
32+
33+
1;
34+
35+
=pod
36+
37+
=head1 SUPPORT
38+
39+
See the L<support section|PPI/SUPPORT> in the main module.
40+
41+
=head1 AUTHOR
42+
43+
Adam Kennedy E<lt>[email protected]E<gt>
44+
45+
=head1 COPYRIGHT
46+
47+
Copyright 2001 - 2011 Adam Kennedy.
48+
49+
This program is free software; you can redistribute
50+
it and/or modify it under the same terms as Perl itself.
51+
52+
The full text of the license can be found in the
53+
LICENSE file included with this module.
54+
55+
=cut

lib/PPI/Token/Whitespace.pm

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -212,8 +212,21 @@ sub __TOKENIZER__on_char {
212212
# 2. The one before that is the word 'sub'.
213213
# 3. The one before that is a 'structure'
214214

215-
# Get the three previous significant tokens
216-
my @tokens = $t->_previous_significant_tokens(3);
215+
# Get at least the three previous significant tokens, and extend the
216+
# retrieval range to include at least one token that can walk the
217+
# already generated tree. (i.e. has a parent)
218+
my ( $tokens_to_get, @tokens ) = (3);
219+
while ( !@tokens or ( $tokens[-1] and !$tokens[-1]->parent ) ) {
220+
@tokens = $t->_previous_significant_tokens($tokens_to_get);
221+
last if @tokens < $tokens_to_get;
222+
$tokens_to_get++;
223+
}
224+
225+
my ($closest_parented_token) = grep $_->parent, @tokens;
226+
die "no parented element found" unless #
227+
$closest_parented_token ||= $t->_document;
228+
return 'Signature'
229+
if $closest_parented_token->presumed_features->{signatures};
217230

218231
# A normal subroutine declaration
219232
my $p1 = $tokens[1];

0 commit comments

Comments
 (0)