=head1 NAME

gdb++ - GDB wrapper providing nice reflection features

=head1 SYNOPSIS

gdb++ PROGRAM

=head1 DESCRIPTION

Devel::GDB::Reflect provides a reflection API for GDB/C++, which can
be used to recursively print the contents of STL data structures
(vector, set, map, etc.) within a GDB session.  It is not limited
to STL, however; you can write your own delegates for printing custom
container types.

The module provides a script, "gdb++", which serves as a wrapper
around GDB.  Invoke it the same way you would invoke gdb, e.g.:

 $ gdb++ MYPROG

Within the gdb++ session, you can execute the same commands as within
gdb, with the following extensions:

=over

=item *

C<print_r> I<VAR>

Recursively prints the contents of VAR.  The command can be
abbreviated as "pr".  For example, if "v" is of type
vector< vector<int> >:

        (gdb) pr v
        [
            [ 11, 12, 13 ], 
            [ 21, 22, 23 ], 
            [ 31, 32, 33 ]
        ]

=item *

C<set gdb_reflect_indent> I<VALUE>

=item *

C<show gdb_reflect_indent>

Set or show the number of spaces to indent at each level of
recursion.  Defaults to 4.

=item *

C<set gdb_reflect_max_depth> I<VALUE>

=item *

C<show gdb_reflect_max_depth>

Set or show the maximum recursion depth.  Defaults to 5.  Example:

        (gdb) set gdb_reflect_max_depth 2
        (gdb) pr v
        [
            [ { ... }, { ... }, { ... } ], 
            [ { ... }, { ... }, { ... } ], 
            [ { ... }, { ... }, { ... } ]
        ]

=item *

C<set gdb_reflect_max_width> I<VALUE>

=item *

C<show gdb_reflect_max_width>

Set or show the maximum number of elements to show from a given
container.  Defaults to 10.  Example:

        (gdb) set gdb_reflect_max_width 2
        (gdb) pr v                       
        [
          [ 11, 12, ... ], 
          [ 21, 22, ... ], 
          ...
        ]

=cut

#!/usr/bin/perl

use warnings;
use strict;

use Devel::GDB::Reflect;
use Devel::GDB;
use Term::ReadLine;

#
# Map from parameter name (e.g. gdb_reflect_indent) to a variable ref
# (e.g. \$Devel::GDB::Reflect::INDENT)
#
sub get_var($)
{
	($_) = @_;

	/^gdb_reflect_indent$/
		and return \$Devel::GDB::Reflect::INDENT;

	/^gdb_reflect_max_width$/
		and return \$Devel::GDB::Reflect::MAX_WIDTH;

	/^gdb_reflect_max_depth$/
		and return \$Devel::GDB::Reflect::MAX_DEPTH;

	return undef;
}

die unless @ARGV;

my ($gdb, $bufs, $errs) = new Devel::GDB( -file => $ARGV[0] );
my $reflector = new Devel::GDB::Reflect($gdb);

my $IS_TTY      = -t STDIN;
my $VERBOSE     = 1;

print STDERR $bufs;

if($errs)
{
	print STDERR $errs;
	die "Failed to start GDB";
}

# Create a new ReadLine instance if we're reading from a TTY;
# otherwise create a stub that behaves like ReadLine

my $term;

if($IS_TTY)
{
	$term = new Term::ReadLine 'gdbwrap';
	my $rl_attribs = $term->Attribs;

	$rl_attribs->{completion_function} =
		sub {
			my ($text, $line, $start) = @_;
			my @gdb_completions = map { substr($_, $start) } $reflector->get_completions($line);
			my @gdbpp_completions = ();

			if($line =~ /^(set|show)\b/)
			{
				foreach('gdb_reflect_indent', 'gdb_reflect_max_width', 'gdb_reflect_max_depth')
				{
					push @gdbpp_completions, $_ if($_ =~ /^\Q$text\E/);
				}
			}

			if($text eq $line)
			{
				foreach('print_r', 'pr')
				{
					push @gdbpp_completions, $_ if($_ =~ /^\Q$text\E/);
				}
			}

			return (@gdb_completions, @gdbpp_completions);
		};
}
else
{
	$term = new MessageMethod sub
	{
		my $arg = shift;

		({
			readline => sub
			{
				my $prompt = shift;
				print STDERR $prompt if $VERBOSE;
				defined($_ = <STDIN>) and chomp $_ or undef;
				return $_;
			}
		}->{$arg} || sub { die "Unknown request: $arg" })->(@_);
	};
}

my $prompt = '(gdb)';
my $lastcmd = '';
my $lasterror = '';

for( ; defined($_ = $term->readline("$prompt ")) ; $lastcmd = $_ if /\S/ )
{
	print STDERR "$_\n"
		if(!$IS_TTY && $VERBOSE);

	s/^\s*//;
	$_ = $lastcmd unless /\S/;
	next unless /\S/;

	if(/^(?:print_r|pr)\s+(.*)/)
	{
		$reflector->print($1);
	}
	elsif(/^(?:set)\s+(gdb_reflect_(?:indent|max_width|max_depth))\b(.*)/)
	{
		my $param = $1;
		(my $value = $2) =~ s/\s//;

		unless ($value =~ /./)
		{
			print STDERR "Parameter requires a value!\n";
			next;
		}

		unless ($value =~ /[0-9]+/)
		{
			print STDERR "Number expected, got '$value'!\n";
			next;
		}

		${get_var($param)} = $value;
	}
	elsif(/^(?:show)\s+(gdb_reflect_(?:indent|max_width|max_depth))\b/)
	{
		my $param = $1;
		print STDERR "$param is " . ${get_var($param)} . "\n";
	}
	else
	{
		(my $result, $lasterror, $prompt) = $gdb->get($_);
		print STDERR $result;
		last if $lasterror;
	}
}

if($lasterror eq '')
{
	# We got EOF on STDIN, so let's quit gdb gracefully
	$gdb->get('quit');
	print STDERR "\n";
	exit 0;
}

die "Error: $lasterror"
	unless $lasterror eq 'EOF';
