PPI::Node - Abstract PPI Node class, an Element that can contain other Elements


PPI documentation Contained in the PPI distribution.

Index


Code Index:

NAME

Top

PPI::Node - Abstract PPI Node class, an Element that can contain other Elements

INHERITANCE

Top

  PPI::Node
  isa PPI::Element

SYNOPSIS

Top

  # Create a typical node (a Document in this case)
  my $Node = PPI::Document->new;

  # Add an element to the node( in this case, a token )
  my $Token = PPI::Token::Word->new('my');
  $Node->add_element( $Token );

  # Get the elements for the Node
  my @elements = $Node->children;

  # Find all the barewords within a Node
  my $barewords = $Node->find( 'PPI::Token::Word' );

  # Find by more complex criteria
  my $my_tokens = $Node->find( sub { $_[1]->content eq 'my' } );

  # Remove all the whitespace
  $Node->prune( 'PPI::Token::Whitespace' );

  # Remove by more complex criteria
  $Node->prune( sub { $_[1]->content eq 'my' } );

DESCRIPTION

Top

The PPI::Node class provides an abstract base class for the Element classes that are able to contain other elements PPI::Document, PPI::Statement, and PPI::Structure.

As well as those listed below, all of the methods that apply to PPI::Element objects also apply to PPI::Node objects.

METHODS

Top

scope

The scope method returns true if the node represents a lexical scope boundary, or false if it does not.

add_element $Element

The add_element method adds a PPI::Element object to the end of a PPI::Node. Because Elements maintain links to their parent, an Element can only be added to a single Node.

Returns true if the PPI::Element was added. Returns undef if the Element was already within another Node, or the method is not passed a PPI::Element object.

elements

The elements method accesses all child elements structurally within the PPI::Node object. Note that in the base of the PPI::Structure classes, this DOES include the brace tokens at either end of the structure.

Returns a list of zero or more PPI::Element objects.

Alternatively, if called in the scalar context, the elements method returns a count of the number of elements.

first_element

The first_element method accesses the first element structurally within the PPI::Node object. As for the elements method, this does include the brace tokens for PPI::Structure objects.

Returns a PPI::Element object, or undef if for some reason the PPI::Node object does not contain any elements.

last_element

The last_element method accesses the last element structurally within the PPI::Node object. As for the elements method, this does include the brace tokens for PPI::Structure objects.

Returns a PPI::Element object, or undef if for some reason the PPI::Node object does not contain any elements.

children

The children method accesses all child elements lexically within the PPI::Node object. Note that in the case of the PPI::Structure classes, this does NOT include the brace tokens at either end of the structure.

Returns a list of zero of more PPI::Element objects.

Alternatively, if called in the scalar context, the children method returns a count of the number of lexical children.

schildren

The schildren method is really just a convenience, the significant-only variation of the normal children method.

In list context, returns a list of significant children. In scalar context, returns the number of significant children.

child $index

The child method accesses a child PPI::Element object by its position within the Node.

Returns a PPI::Element object, or undef if there is no child element at that node.

schild $index

The lexical structure of the Perl language ignores 'insignificant' items, such as whitespace and comments, while PPI treats these items as valid tokens so that it can reassemble the file at any time. Because of this, in many situations there is a need to find an Element within a Node by index, only counting lexically significant Elements.

The schild method returns a child Element by index, ignoring insignificant Elements. The index of a child Element is specified in the same way as for a normal array, with the first Element at index 0, and negative indexes used to identify a "from the end" position.

contains $Element

The contains method is used to determine if another PPI::Element object is logically "within" a PPI::Node. For the special case of the brace tokens at either side of a PPI::Structure object, they are generally considered "within" a PPI::Structure object, even if they are not actually in the elements for the PPI::Structure.

Returns true if the PPI::Element is within us, false if not, or undef on error.

find $class | \&wanted

The find method is used to search within a code tree for PPI::Element objects that meet a particular condition.

To specify the condition, the method can be provided with either a simple class name (full or shortened), or a CODE/function reference.

  # Find all single quotes in a Document (which is a Node)
  $Document->find('PPI::Quote::Single');

  # The same thing with a shortened class name
  $Document->find('Quote::Single');

  # Anything more elaborate, we so with the sub
  $Document->find( sub {
  	# At the top level of the file...
  	$_[1]->parent == $_[0]
  	and (
  		# ...find all comments and POD
  		$_[1]->isa('PPI::Token::Pod')
  		or
  		$_[1]->isa('PPI::Token::Comment')
  	)
  } );

The function will be passed two arguments, the top-level PPI::Node you are searching in and the current PPI::Element that the condition is testing.

The anonymous function should return one of three values. Returning true indicates a condition match, defined-false (0 or '') indicates no-match, and undef indicates no-match and no-descend.

In the last case, the tree walker will skip over anything below the undef-returning element and move on to the next element at the same level.

To halt the entire search and return undef immediately, a condition function should throw an exception (i.e. die).

Note that this same wanted logic is used for all methods documented to have a \&wanted parameter, as this one does.

The find method returns a reference to an array of PPI::Element objects that match the condition, false (but defined) if no Elements match the condition, or undef if you provide a bad condition, or an error occurs during the search process.

In the case of a bad condition, a warning will be emitted as well.

find_first $class | \&wanted

If the normal find method is like a grep, then find_first is equivalent to the Scalar::Util first function.

Given an element class or a wanted function, it will search depth-first through a tree until it finds something that matches the condition, returning the first Element that it encounters.

See the find method for details on the format of the search condition.

Returns the first PPI::Element object that matches the condition, false if nothing matches the condition, or undef if given an invalid condition, or an error occurs.

find_any $class | \&wanted

The find_any method is a short-circuiting true/false method that behaves like the normal find method, but returns true as soon as it finds any Elements that match the search condition.

See the find method for details on the format of the search condition.

Returns true if any Elements that match the condition can be found, false if not, or undef if given an invalid condition, or an error occurs.

remove_child $Element

If passed a PPI::Element object that is a direct child of the Node, the remove_element method will remove the Element intact, along with any of its children. As such, this method acts essentially as a 'cut' function.

If successful, returns the removed element. Otherwise, returns undef.

prune $class | \&wanted

The prune method is used to strip PPI::Element objects out of a code tree. The argument is the same as for the find method, either a class name, or an anonymous subroutine which returns true/false. Any Element that matches the class|wanted will be deleted from the code tree, along with any of its children.

The prune method returns the number of Element objects that matched and were removed, non-recursively. This might also be zero, so avoid a simple true/false test on the return false of the prune method. It returns undef on error, which you probably should test for.

TO DO

Top

- Move as much as possible to PPI::XS

SUPPORT

Top

See the support section in the main module.

AUTHOR

Top

Adam Kennedy <adamk@cpan.org>

COPYRIGHT

Top


PPI documentation Contained in the PPI distribution.
package PPI::Node;

use strict;
use Carp            ();
use Scalar::Util    qw{refaddr};
use List::MoreUtils ();
use Params::Util    qw{_INSTANCE _CLASS _CODELIKE};
use PPI::Element    ();

use vars qw{$VERSION @ISA *_PARENT};
BEGIN {
	$VERSION = '1.215';
	@ISA     = 'PPI::Element';
	*_PARENT = *PPI::Element::_PARENT;
}





#####################################################################
# The basic constructor

sub new {
	my $class = ref $_[0] || $_[0];
	bless { children => [] }, $class;
}





#####################################################################
# PDOM Methods

### XS -> PPI/XS.xs:_PPI_Node__scope 0.903+
sub scope { '' }

sub add_element {
	my $self = shift;

	# Check the element
	my $Element = _INSTANCE(shift, 'PPI::Element') or return undef;
	$_PARENT{refaddr $Element} and return undef;

	# Add the argument to the elements
	push @{$self->{children}}, $Element;
	Scalar::Util::weaken(
		$_PARENT{refaddr $Element} = $self
	);

	1;
}

# In a typical run profile, add_element is the number 1 resource drain.
# This is a highly optimised unsafe version, for internal use only.
sub __add_element {
	Scalar::Util::weaken(
		$_PARENT{refaddr $_[1]} = $_[0]
	);
	push @{$_[0]->{children}}, $_[1];
}

sub elements {
	if ( wantarray ) {
		return @{$_[0]->{children}};
	} else {
		return scalar @{$_[0]->{children}};
	}
}

# Normally the first element is also the first child
sub first_element {
	$_[0]->{children}->[0];
}

# Normally the last element is also the last child
sub last_element {
	$_[0]->{children}->[-1];
}

# In the default case, this is the same as for the elements method
sub children {
	wantarray ? @{$_[0]->{children}} : scalar @{$_[0]->{children}};
}

sub schildren {
	return grep { $_->significant } @{$_[0]->{children}} if wantarray;
	my $count = 0;
	foreach ( @{$_[0]->{children}} ) {
		$count++ if $_->significant;
	}
	return $count;
}

sub child {
	$_[0]->{children}->[$_[1]];
}

sub schild {
	my $self = shift;
	my $idx  = 0 + shift;
	my $el   = $self->{children};
	if ( $idx < 0 ) {
		my $cursor = 0;
		while ( exists $el->[--$cursor] ) {
			return $el->[$cursor] if $el->[$cursor]->significant and ++$idx >= 0;
		}
	} else {
		my $cursor = -1;
		while ( exists $el->[++$cursor] ) {
			return $el->[$cursor] if $el->[$cursor]->significant and --$idx < 0;
		}
	}
	undef;
}

sub contains {
	my $self    = shift;
	my $Element = _INSTANCE(shift, 'PPI::Element') or return undef;

	# Iterate up the Element's parent chain until we either run out
	# of parents, or get to ourself.
	while ( $Element = $Element->parent ) {
		return 1 if refaddr($self) == refaddr($Element);
	}

	'';
}

sub find {
	my $self   = shift;
	my $wanted = $self->_wanted(shift) or return undef;

	# Use a queue based search, rather than a recursive one
	my @found = ();
	my @queue = @{$self->{children}};
	eval {
		while ( @queue ) {
			my $Element = shift @queue;
			my $rv      = &$wanted( $self, $Element );
			push @found, $Element if $rv;

			# Support "don't descend on undef return"
			next unless defined $rv;

			# Skip if the Element doesn't have any children
			next unless $Element->isa('PPI::Node');

			# Depth-first keeps the queue size down and provides a
			# better logical order.
			if ( $Element->isa('PPI::Structure') ) {
				unshift @queue, $Element->finish if $Element->finish;
				unshift @queue, @{$Element->{children}};
				unshift @queue, $Element->start if $Element->start;
			} else {
				unshift @queue, @{$Element->{children}};
			}
		}
	};
	if ( $@ ) {
		# Caught exception thrown from the wanted function
		return undef;
	}

	@found ? \@found : '';
}

sub find_first {
	my $self   = shift;
	my $wanted = $self->_wanted(shift) or return undef;

	# Use the same queue-based search as for ->find
	my @queue = @{$self->{children}};
	my $rv    = eval {
		# The defined() here prevents a ton of calls to PPI::Util::TRUE
		while ( @queue ) {
			my $Element = shift @queue;
			my $rv      = &$wanted( $self, $Element );
			return $Element if $rv;

			# Support "don't descend on undef return"
			next unless defined $rv;

			# Skip if the Element doesn't have any children
			next unless $Element->isa('PPI::Node');

			# Depth-first keeps the queue size down and provides a
			# better logical order.
			if ( $Element->isa('PPI::Structure') ) {
				unshift @queue, $Element->finish if defined($Element->finish);
				unshift @queue, @{$Element->{children}};
				unshift @queue, $Element->start  if defined($Element->start);
			} else {
				unshift @queue, @{$Element->{children}};
			}
		}
	};
	if ( $@ ) {
		# Caught exception thrown from the wanted function
		return undef;
	}

	$rv or '';
}

sub find_any {
	my $self = shift;
	my $rv   = $self->find_first(@_);
	$rv ? 1 : $rv; # false or undef
}

sub remove_child {
	my $self  = shift;
	my $child = _INSTANCE(shift, 'PPI::Element') or return undef;

	# Find the position of the child
	my $key = refaddr $child;
	my $p   = List::MoreUtils::firstidx {
		refaddr $_ == $key
	} @{$self->{children}};
	return undef unless defined $p;

	# Splice it out, and remove the child's parent entry
	splice( @{$self->{children}}, $p, 1 );
	delete $_PARENT{refaddr $child};

	$child;
}

sub prune {
	my $self   = shift;
	my $wanted = $self->_wanted(shift) or return undef;

	# Use a depth-first queue search
	my $pruned = 0;
	my @queue  = $self->children;
	eval {
		while ( my $element = shift @queue ) {
			my $rv = &$wanted( $self, $element );
			if ( $rv ) {
				# Delete the child
				$element->delete or return undef;
				$pruned++;
				next;
			}

			# Support the undef == "don't descend"
			next unless defined $rv;

			if ( _INSTANCE($element, 'PPI::Node') ) {
				# Depth-first keeps the queue size down
				unshift @queue, $element->children;
			}
		}
	};
	if ( $@ ) {
		# Caught exception thrown from the wanted function
		return undef;		
	}

	$pruned;
}

# This method is likely to be very heavily used, to take
# it slowly and carefuly.
### NOTE: Renaming this function or changing either to self will probably
###       break File::Find::Rule::PPI
sub _wanted {
	my $either = shift;
	my $it     = defined($_[0]) ? shift : do {
		Carp::carp('Undefined value passed as search condition') if $^W;
		return undef;
	};

	# Has the caller provided a wanted function directly
	return $it if _CODELIKE($it);
	if ( ref $it ) {
		# No other ref types are supported
		Carp::carp('Illegal non-CODE reference passed as search condition') if $^W;
		return undef;
	}

	# The first argument should be an Element class, possibly in shorthand
	$it = "PPI::$it" unless substr($it, 0, 5) eq 'PPI::';
	unless ( _CLASS($it) and $it->isa('PPI::Element') ) {
		# We got something, but it isn't an element
		Carp::carp("Cannot create search condition for '$it': Not a PPI::Element") if $^W;
		return undef;
	}

	# Create the class part of the wanted function
	my $wanted_class = "\n\treturn '' unless \$_[1]->isa('$it');";

	# Have we been given a second argument to check the content
	my $wanted_content = '';
	if ( defined $_[0] ) {
		my $content = shift;
		if ( ref $content eq 'Regexp' ) {
			$content = "$content";
		} elsif ( ref $content ) {
			# No other ref types are supported
			Carp::carp("Cannot create search condition for '$it': Not a PPI::Element") if $^W;
			return undef;
		} else {
			$content = quotemeta $content;
		}

		# Complete the content part of the wanted function
		$wanted_content .= "\n\treturn '' unless defined \$_[1]->{content};";
		$wanted_content .= "\n\treturn '' unless \$_[1]->{content} =~ /$content/;";
	}

	# Create the complete wanted function
	my $code = "sub {"
		. $wanted_class
		. $wanted_content
		. "\n\t1;"
		. "\n}";

	# Compile the wanted function
	$code = eval $code;
	(ref $code eq 'CODE') ? $code : undef;
}





####################################################################
# PPI::Element overloaded methods

sub tokens {
	map { $_->tokens } @{$_[0]->{children}};
}

### XS -> PPI/XS.xs:_PPI_Element__content 0.900+
sub content {
	join '', map { $_->content } @{$_[0]->{children}};
}

# Clone as normal, but then go down and relink all the _PARENT entries
sub clone {
	my $self  = shift;
	my $clone = $self->SUPER::clone;
	$clone->__link_children;
	$clone;
}

sub location {
	my $self  = shift;
	my $first = $self->{children}->[0] or return undef;
	$first->location;
}





#####################################################################
# Internal Methods

sub DESTROY {
	local $_;
	if ( $_[0]->{children} ) {
		my @queue = $_[0];
		while ( defined($_ = shift @queue) ) {
			unshift @queue, @{delete $_->{children}} if $_->{children};

			# Remove all internal/private weird crosslinking so that
			# the cascading DESTROY calls will get called properly.
			%$_ = ();
		}
	}

	# Remove us from our parent node as normal
	delete $_PARENT{refaddr $_[0]};
}

# Find the position of a child
sub __position {
	my $key = refaddr $_[1];
	List::MoreUtils::firstidx { refaddr $_ == $key } @{$_[0]->{children}};
}

# Insert one or more elements before a child
sub __insert_before_child {
	my $self = shift;
	my $key  = refaddr shift;
	my $p    = List::MoreUtils::firstidx {
	         refaddr $_ == $key
	         } @{$self->{children}};
	foreach ( @_ ) {
		Scalar::Util::weaken(
			$_PARENT{refaddr $_} = $self
			);
	}
	splice( @{$self->{children}}, $p, 0, @_ );
	1;
}

# Insert one or more elements after a child
sub __insert_after_child {
	my $self = shift;
	my $key  = refaddr shift;
	my $p    = List::MoreUtils::firstidx {
	         refaddr $_ == $key
	         } @{$self->{children}};
	foreach ( @_ ) {
		Scalar::Util::weaken(
			$_PARENT{refaddr $_} = $self
			);
	}
	splice( @{$self->{children}}, $p + 1, 0, @_ );
	1;
}

# Replace a child
sub __replace_child {
	my $self = shift;
	my $key  = refaddr shift;
	my $p    = List::MoreUtils::firstidx {
	         refaddr $_ == $key
	         } @{$self->{children}};
	foreach ( @_ ) {
		Scalar::Util::weaken(
			$_PARENT{refaddr $_} = $self
			);
	}
	splice( @{$self->{children}}, $p, 1, @_ );
	1;
}

# Create PARENT links for an entire tree.
# Used when cloning or thawing.
sub __link_children {
	my $self = shift;

	# Relink all our children ( depth first )
	my @queue = ( $self );
	while ( my $Node = shift @queue ) {
		# Link our immediate children
		foreach my $Element ( @{$Node->{children}} ) {
			Scalar::Util::weaken(
				$_PARENT{refaddr($Element)} = $Node
				);
			unshift @queue, $Element if $Element->isa('PPI::Node');
		}

		# If it's a structure, relink the open/close braces
		next unless $Node->isa('PPI::Structure');
		Scalar::Util::weaken(
			$_PARENT{refaddr($Node->start)}  = $Node
			) if $Node->start;
		Scalar::Util::weaken(
			$_PARENT{refaddr($Node->finish)} = $Node
			) if $Node->finish;
	}

	1;
}

1;