PPIx::U...aversal(3)

πŸ’– NAME πŸ’–

so like, hi bestie 🫢 you just opened the manual page for PPIx::Utils::Traversal(3), and ngl it's kind of a vibe. This is a whole collection of utility functions for traversing PPI documents β€” think of it as your lil tour guide through the syntax tree, holding your hand and being like "this way queen πŸ‘‘".


✨ SYNOPSIS ✨

use PPIx::Utils::Traversal ':all';

that's it. that's the whole thing. load it up and you get everything πŸ’…


🌸 DESCRIPTION 🌸

This package is literally one component of PPIx::Utils, and what it brings to the group chat is functions for traversal of PPI documents. walking the tree, finding the next thing, splitting stuff up. main character behavior fr fr.


πŸ¦‹ FUNCTIONS πŸ¦‹

okay so every single one of these can be imported by name, or you can just grab the whole set with the tag :all. no gatekeeping here ✨

first_arg

my $first_arg = first_arg($element);

You hand it a PPI::Element that you're presuming is a function call (which, let's be real, is usually a PPI::Token::Word), and it gives you back the first argument. It's similar to parse_arg_list and follows the same logic, but listen up because there are caveats.

So for this code:

int($x + 0.5)

this function returns just the $x, not the whole entire expression. That is different from how parse_arg_list behaves, okay? Don't mix them up. πŸ™ˆ

And another caveat, bestie:

int(($x + $y) + 0.5)

this one returns ($x + $y) as a PPI::Structure::List instance. iconic, honestly.

parse_arg_list

my @args = parse_arg_list($element);

Give it a PPI::Element that's presumed to be a function call (again, usually a PPI::Token::Word), and it splits the argument expressions into arrays of tokens. What you get back is a list containing references to each of those arrays. πŸ’«

Why is this useful? Because parentheses are optional when you're calling a function, and PPI parses those two situations very differently. So this method is basically a poor-man's parse tree of PPI nodes. It's not bullet-proof because it doesn't respect precedence.

and like, real talk: in general I don't like the way this function works, so do not count on it to be stable (or even present). πŸ’€

split_nodes_on_comma

my @args = split_nodes_on_comma(@nodes);

Same return type as parse_arg_list, but this one expects you to pass in the nodes representing the interior of a list, like:

'foo', 1, 2, 'bar'

get_next_element_in_same_simple_statement

my $element = get_next_element_in_same_simple_statement($element);

Given a PPI::Element, this subroutine returns the next element in the same simple statement, as defined by is_ppi_simple_statement in PPIx::Utils::Classification. If there's no next element to be found, it just returns undef. no drama, no fuss πŸŽ€

Here's the full rundown of the lore:

  • If the $element is undefined or unblessed, we simply return undef.
  • If the $element satisfies is_ppi_simple_statement in PPIx::Utils::Classification, we return undef β€” unless it has a parent which is a PPI::Structure::List.
  • If the $element is the last significant element in its PPI::Node, we replace it with its parent and iterate again. πŸ”
  • Otherwise, we return $element->snext_sibling().

get_previous_module_used_on_same_line

my $element = get_previous_module_used_on_same_line($element);

Given a PPI::Element, this returns the PPI::Element representing the name of the module that was included by the previous use or require on the same line as the $element. If none is found, it simply returns undef. sorry bestie 😭

For example, with the line

use version; our $VERSION = ...;

given the PPI::Token::Symbol instance for $VERSION, this will return "version".

And if the given element is in a use or require, the return comes from the previous use or require on the line, if any. 🧚

get_constant_name_elements_from_declaring_statement

my @constants = get_constant_name_elements_from_declaring_statement($statement);

Given a PPI::Statement, if that statement is a Readonly, ReadonlyX, or Const::Fast declaration statement, or a use constant, this returns the names of the things being defined. slay. ✨

Given

use constant 1.16 FOO => 'bar';

this will return the PPI::Token::Word containing 'FOO'.

Given

use constant 1.16 { FOO => 'bar', 'BAZ' => 'burfle' };

this will return a list of the PPI::Tokens containing 'FOO' and 'BAZ'.

And similarly, given

Readonly::Hash my %FOO => ( bar => 'baz' );

or

const my %FOO => ( bar => 'baz' );

this will return the PPI::Token::Symbol containing '%FOO'. πŸ’…

split_ppi_node_by_namespace

my $subtrees = split_ppi_node_by_namespace($node);

Returns the sub-trees for each namespace in the node, as a reference to a hash of references to arrays of PPI::Nodes. Say we've got the following code:

#!perl

my $x = blah();

package Foo;

my $y = blah_blah();

{
    say 'Whee!';

    package Bar;

    something();
}

thingy();

package Baz;

da_da_da();

package Foo;

foreach ( blrfl() ) {
    ...
}

Calling this function on a PPI::Document for the above returns a value that looks like this β€” using multi-line string literals for the actual code parts instead of PPI trees, to make it easier to read:

{
    main    => [
        q<
            #!perl

            my $x = blah();
        >,
    ],
    Foo     => [
        q<
            package Foo;

            my $y = blah_blah();

            {
                say 'Whee!';

            }

            thingy();
        >,
        q<
            package Foo;

            foreach ( blrfl() ) {
                ...
            }
        >,
    ],
    Bar     => [
        q<
            package Bar;

            something();
        >,
    ],
    Baz     => [
        q<
            package Baz;

            da_da_da();
        >,
    ],
}

Important note bestie: the return value contains copies of the original nodes, not the original nodes themselves β€” that's because it needs to handle namespaces that aren't file-scoped. (Notice how the first element for "Foo" above differs from the original code. πŸ‘€)


πŸ’€ BUGS πŸ’€

Report any issues on the public bugtracker. pls and thank u πŸ™


πŸ‘‘ AUTHOR πŸ‘‘

Dan Book <dbook@cpan.org>

Code originally from Perl::Critic::Utils by Jeffrey Ryan Thalhammer <jeff@imaginative-software.com>, Perl::Critic::Utils::PPI and PPIx::Utilities::Node by Elliot Shank <perl@galumph.com>, and PPIx::Utilities::Statement by Thomas R. Wyant, III <wyant@cpan.org>

the way this page has a whole lineage, we love a collaborative queen πŸŒˆπŸ‘‘


This software is copyright (c) 2005-2011 Imaginative Software Systems, 2007-2011 Elliot Shank, 2009-2010 Thomas R. Wyant, III, 2017 Dan Book.

This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself. 🫢


🌈 SEE ALSO 🌈

  • Perl::Critic::Utils
  • Perl::Critic::Utils::PPI
  • PPIx::Utilities

and that's the whole page bestie, we ate and left no crumbs πŸ“βœ¨ this was perl v5.42.0, dated 2025-08-05, from the PPIx::Utils::Traversal(3) manual β€” go forth and traverse those documents, you absolute icon πŸ’–πŸŽ€πŸ¦‹

πŸ“¦ where this page comes from