The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#  You may distribute under the terms of either the GNU General Public License
#  or the Artistic License (the same terms as Perl itself)
#
#  (C) Paul Evans, 2017 -- leonerd@leonerd.org.uk

package Devel::MAT::Tool::Symbols;

use strict;
use warnings;
use base qw( Devel::MAT::Tool );

our $VERSION = '0.32';

use constant CMD => "symbols";
use constant CMD_DESC => "Display a list of the symbol table";

=head1 NAME

C<Devel::MAT::Tool::Symbols> - display a list of the symbol table

=head1 DESCRIPTION

This C<Devel::MAT> tool displays a list names from the symbol table.

=cut

=head1 COMMANDS

=head2 symbols

   pmat> symbols strict
   $strict::VERSION
   &strict::all_bits
   &strict::all_explicit_bits
   &strict::bits
   &strict::import
   &strict::unimport

Prints a list of every name inside a symbol table hash ("stash"), starting
from the one given by name, or the toplevel default stash if none is provided.

Takes the following named options:

=over 4

=item --recurse, -R

Recursively show the inner symbols inside stashes.

=back

=cut

sub extract_symbols
{
   my ( $stash, $prefix ) = @_;

   my @ret;
   foreach my $key ( sort $stash->keys ) {
      my $gv = $stash->value( $key );

      my $name;
      if( $key =~ m/^([\0-\x1f])/ ) {
         $name = "{^" . chr(ord($1)+0x40) . substr( $key, 1 ) . "}";
      }
      else {
         $name = $prefix . $key;
      }

      push @ret, [ $gv, $name ];
   }

   return @ret;
}

sub _show_symbol
{
   my ( $name, $sv ) = @_;

   Devel::MAT::Cmd->printf( "%s at %s\n",
      $name,
      Devel::MAT::Cmd->format_sv( $sv ),
   );
}

use constant CMD_OPTS => (
   recurse => { help => "recursively show inner symbols",
                alias => "R" },
);

use constant CMD_ARGS => (
   { name => "start", help => "show symbols within this symbol, rather than %main::" },
);

sub run
{
   my $self = shift;
   my %opts = %{ +shift };

   my $df = $self->df;

   my @queue;

   if( @_ ) {
      my $name = shift @_;
      @queue = extract_symbols( $self->pmat->find_stash( $name ), $name . "::" );
   }
   else {
      # Don't recurse into self-referential 'main::' symbol
      @queue = grep { $_->[1] ne "main::" }
         extract_symbols( $df->defstash, "" );

      # Also skip the "debug location" symbols, whatever those are
      @queue = grep { $_->[1] !~ m/^_</ } @queue;
   }

   while( @queue ) {
      $_ = shift @queue;
      if( $_->[0]->isa( "Devel::MAT::SV::GLOB" ) ) {
         my ( $gv, $name ) = @$_;
         _show_symbol( '$' . $name, $gv->scalar ) if $gv->scalar;
         _show_symbol( '@' . $name, $gv->array  ) if $gv->array;
         _show_symbol( '%' . $name, $gv->hash   ) if $gv->hash;
         _show_symbol( '&' . $name, $gv->code   ) if $gv->code;

         unshift @queue, [ $gv->hash, $name ] if $gv->hash;
      }
      elsif( $opts{recurse} and $_->[0]->isa( "Devel::MAT::SV::STASH" ) ) {
         my ( $stash, $prefix ) = @$_;
         unshift @queue, extract_symbols( $stash, $prefix );
      }
   }
}

=head1 AUTHOR

Paul Evans <leonerd@leonerd.org.uk>

=cut

0x55AA;