This is a live mirror of the Perl 5 development currently hosted at https://github.com/perl/perl5
Extract _handle_cmd_wrapper_commands.
[perl5.git] / lib / perl5db.pl
index a33342b..9042ab0 100644 (file)
@@ -186,7 +186,7 @@ uses this hash to determine where breakpoints have been set. Any true value is
 considered to be a breakpoint; C<perl5db.pl> uses C<$break_condition\0$action>.
 Values are magical in numeric context: 1 if the line is breakable, 0 if not.
 
-The scalar C<${"_<$filename"}> simply contains the string C<<< _<$filename> >>>.
+The scalar C<${"_<$filename"}> simply contains the string C<$filename>.
 This is also the case for evaluated strings that contain subroutines, or
 which are currently being executed.  The $filename for C<eval>ed strings looks
 like C<(eval 34).
@@ -512,7 +512,7 @@ package DB;
 
 use strict;
 
-BEGIN {eval 'use IO::Handle'}; # Needed for flush only? breaks under miniperl
+BEGIN {eval 'use IO::Handle'}; # Needed for flush only? breaks under miniperl
 
 BEGIN {
     require feature;
@@ -627,7 +627,6 @@ context, so we can use C<my> freely.
 use vars qw(
     @args
     %break_on_load
-    @cmdfhs
     $CommandSet
     $CreateTTY
     $DBGR
@@ -635,28 +634,18 @@ use vars qw(
     $dbline
     %dbline
     $dieLevel
-    $evalarg
     $filename
-    $frame
-    $hist
     $histfile
     $histsize
-    $ImmediateStop
     $IN
     $inhibit_exit
     @ini_INC
     $ini_warn
-    $line
     $maxtrace
     $od
-    $onetimeDump
-    $onetimedumpDepth
-    %option
     @options
     $osingle
     $otrace
-    $OUT
-    $packname
     $pager
     $post
     %postponed
@@ -669,18 +658,32 @@ use vars qw(
     @res
     $rl
     @saved
-    $signal
     $signalLevel
-    $single
-    $start
     $sub
-    %sub
-    $subname
     $term
-    $trace
     $usercontext
     $warnLevel
-    $window
+);
+
+our (
+    @cmdfhs,
+    $evalarg,
+    $frame,
+    $hist,
+    $ImmediateStop,
+    $line,
+    $onetimeDump,
+    $onetimedumpDepth,
+    %option,
+    $OUT,
+    $packname,
+    $signal,
+    $single,
+    $start,
+    %sub,
+    $subname,
+    $trace,
+    $window,
 );
 
 # Used to save @ARGV and extract any debugger-related flags.
@@ -692,7 +695,7 @@ use vars qw($panic);
 
 # Used to prevent the debugger from running nonstop
 # after a restart
-use vars qw($second_time);
+our ($second_time);
 
 sub _calc_usercontext {
     my ($package) = @_;
@@ -741,7 +744,7 @@ sub eval {
     # Since we're only saving $@, we only have to localize the array element
     # that it will be stored in.
     local $saved[0];    # Preserve the old value of $@
-    eval { &DB::save };
+    eval { DB::save() };
 
     # Now see whether we need to report an error back to the user.
     if ($at) {
@@ -833,7 +836,7 @@ Each new thread will be announced and the debugger prompt will always inform
 you of each new thread created.  It will also indicate the thread id in which
 we are currently running within the prompt like this:
 
-       [tid] DB<$i>
+    [tid] DB<$i>
 
 Where C<[tid]> is an integer thread id and C<$i> is the familiar debugger
 command prompt.  The prompt will show: C<[0]> when running under threads, but
@@ -853,19 +856,18 @@ C<5.8.6> and debugger version C<1.2.8>.
 =cut
 
 BEGIN {
-  # ensure we can share our non-threaded variables or no-op
-  if ($ENV{PERL5DB_THREADED}) {
-       require threads;
-       require threads::shared;
-       import threads::shared qw(share);
-       $DBGR;
-       share(\$DBGR);
-       lock($DBGR);
-       print "Threads support enabled\n";
-  } else {
-       *lock  = sub(*) {};
-       *share = sub(*) {};
-  }
+    # ensure we can share our non-threaded variables or no-op
+    if ($ENV{PERL5DB_THREADED}) {
+        require threads;
+        require threads::shared;
+        import threads::shared qw(share);
+        $DBGR;
+        share(\$DBGR);
+        lock($DBGR);
+        print "Threads support enabled\n";
+    } else {
+        *share = sub(\[$@%]) {};
+    }
 }
 
 # These variables control the execution of 'dumpvar.pl'.
@@ -890,9 +892,7 @@ BEGIN {
 }
 
 # without threads, $filename is not defined until DB::DB is called
-foreach my $k (keys (%INC)) {
-       share(\$main::{'_<'.$filename}) if defined $filename;
-};
+share($main::{'_<'.$filename}) if defined $filename;
 
 # Command-line + PERLLIB:
 # Save the contents of @INC before they are modified elsewhere.
@@ -1211,7 +1211,7 @@ use vars qw($pidprompt);
 $pidprompt = '';
 
 # Sets up $emacs as a synonym for $slave_editor.
-use vars qw($slave_editor);
+our ($slave_editor);
 *emacs = $slave_editor if $slave_editor;    # May be used in afterinit()...
 
 =head2 READING THE RC FILE
@@ -1377,7 +1377,9 @@ back into the appropriate spots in the debugger.
 
 =cut
 
-use vars qw(@hist @truehist %postponed_file @typeahead);
+use vars qw(%postponed_file @typeahead);
+
+our (@hist, @truehist);
 
 sub _restore_shared_globals_after_restart
 {
@@ -1465,12 +1467,14 @@ to be anyone there to enter commands.
 
 =cut
 
-use vars qw($notty $runnonstop $console $tty $LINEINFO);
+use vars qw($notty $console $tty $LINEINFO);
 use vars qw($lineinfo $doccmd);
 
+our ($runnonstop);
+
 if ($notty) {
     $runnonstop = 1;
-       share($runnonstop);
+    share($runnonstop);
 }
 
 =pod
@@ -1664,8 +1668,8 @@ and if we can.
     # and a I/O description to keep track of.
     $LINEINFO = $OUT     unless defined $LINEINFO;
     $lineinfo = $console unless defined $lineinfo;
-       # share($LINEINFO); # <- unable to share globs
-       share($lineinfo);   #
+    # share($LINEINFO); # <- unable to share globs
+    share($lineinfo);   #
 
 =pod
 
@@ -1734,29 +1738,34 @@ see what's happening in any given command.
 
 =cut
 
+# $cmd cannot be an our() variable unfortunately (possible perl bug?).
+
 use vars qw(
     $action
-    %alias
     $cmd
-    $doret
-    $fall_off_end
     $file
     $filename_ini
     $finished
     %had_breakpoints
-    $incr
-    $laststep
     $level
     $max
-    @old_watch
     $package
-    $rc
-    $sh
-    @stack
-    $stack_depth
-    @to_watch
     $try
-    $end
+);
+
+our (
+    %alias,
+    $doret,
+    $end,
+    $fall_off_end,
+    $incr,
+    $laststep,
+    $rc,
+    $sh,
+    $stack_depth,
+    @stack,
+    @to_watch,
+    @old_watch,
 );
 
 sub _DB__determine_if_we_should_break
@@ -1780,7 +1789,7 @@ sub _DB__determine_if_we_should_break
         # see if we should stop. If so, remove the one-time sigil.
         elsif ($stop) {
             $evalarg = "\$DB::signal |= 1 if do {$stop}";
-            &eval;
+            DB::eval();
             # If the breakpoint is temporary, then delete its enabled status.
             if ($dbline{$line} =~ s/;9($|\0)/$1/) {
                 _cancel_breakpoint_temp_enabled_status($filename, $line);
@@ -1789,6 +1798,625 @@ sub _DB__determine_if_we_should_break
     } ## end if ($dbline{$line} && ...
 }
 
+sub _DB__is_finished {
+    if ($finished and $level <= 1) {
+        end_report();
+        return 1;
+    }
+    else {
+        return;
+    }
+}
+
+sub _DB__read_next_cmd
+{
+    my ($tid) = @_;
+
+    # We have a terminal, or can get one ...
+    if (!$term) {
+        setterm();
+    }
+
+    # ... and it belogs to this PID or we get one for this PID ...
+    if ($term_pid != $$) {
+        resetterm(1);
+    }
+
+    # ... and we got a line of command input ...
+    $cmd = DB::readline(
+        "$pidprompt $tid DB"
+        . ( '<' x $level )
+        . ( $#hist + 1 )
+        . ( '>' x $level ) . " "
+    );
+
+    return defined($cmd);
+}
+
+sub _DB__trim_command_and_return_first_component {
+    $cmd =~ s/\A\s+//s;    # trim annoying leading whitespace
+    $cmd =~ s/\s+\z//s;    # trim annoying trailing whitespace
+
+    $cmd =~ m{\A(\S*)};
+    return $1;
+}
+
+sub _DB__handle_f_command {
+    if (($file) = $cmd =~ /\Af\b\s*(.*)/) {
+        $file =~ s/\s+$//;
+
+        # help for no arguments (old-style was return from sub).
+        if ( !$file ) {
+            print $OUT
+            "The old f command is now the r command.\n";    # hint
+            print $OUT "The new f command switches filenames.\n";
+            next CMD;
+        } ## end if (!$file)
+
+        # if not in magic file list, try a close match.
+        if ( !defined $main::{ '_<' . $file } ) {
+            if ( ($try) = grep( m#^_<.*$file#, keys %main:: ) ) {
+                {
+                    $try = substr( $try, 2 );
+                    print $OUT "Choosing $try matching '$file':\n";
+                    $file = $try;
+                }
+            } ## end if (($try) = grep(m#^_<.*$file#...
+        } ## end if (!defined $main::{ ...
+
+        # If not successfully switched now, we failed.
+        if ( !defined $main::{ '_<' . $file } ) {
+            print $OUT "No file matching '$file' is loaded.\n";
+            next CMD;
+        }
+
+        # We switched, so switch the debugger internals around.
+        elsif ( $file ne $filename ) {
+            *dbline   = $main::{ '_<' . $file };
+            $max      = $#dbline;
+            $filename = $file;
+            $start    = 1;
+            $cmd      = "l";
+        } ## end elsif ($file ne $filename)
+
+        # We didn't switch; say we didn't.
+        else {
+            print $OUT "Already in $file.\n";
+            next CMD;
+        }
+    }
+
+    return;
+}
+
+sub _DB__handle_dot_command {
+    my ($obj) = @_;
+
+    # . command.
+    if ($cmd eq '.') {
+        $incr = -1;    # stay at current line
+
+        # Reset everything to the old location.
+        $start    = $line;
+        $filename = $filename_ini;
+        *dbline   = $main::{ '_<' . $filename };
+        $max      = $#dbline;
+
+        # Now where are we?
+        print_lineinfo($obj->position());
+        next CMD;
+    }
+
+    return;
+}
+
+sub _DB__handle_y_command {
+    my ($obj) = @_;
+
+    if (my ($match_level, $match_vars)
+        = $cmd =~ /^y(?:\s+(\d*)\s*(.*))?$/) {
+
+        # See if we've got the necessary support.
+        if (!eval { require PadWalker; PadWalker->VERSION(0.08) }) {
+            my $Err = $@;
+            DB::warn(
+                $Err =~ /locate/
+                ? "PadWalker module not found - please install\n"
+                : $Err
+            );
+            next CMD;
+        }
+
+        # Load up dumpvar if we don't have it. If we can, that is.
+        do 'dumpvar.pl' || die $@ unless defined &main::dumpvar;
+        defined &main::dumpvar
+            or print $OUT "dumpvar.pl not available.\n"
+            and next CMD;
+
+        # Got all the modules we need. Find them and print them.
+        my @vars = split( ' ', $match_vars || '' );
+
+        # Find the pad.
+        my $h = eval { PadWalker::peek_my( ( $match_level || 0 ) + 1 ) };
+
+        # Oops. Can't find it.
+        if (my $Err = $@) {
+            $Err =~ s/ at .*//;
+            DB::warn($Err);
+            next CMD;
+        }
+
+        # Show the desired vars with dumplex().
+        my $savout = select($OUT);
+
+        # Have dumplex dump the lexicals.
+        foreach my $key (sort keys %$h) {
+            dumpvar::dumplex( $key, $h->{$key},
+                defined $option{dumpDepth} ? $option{dumpDepth} : -1,
+                @vars );
+        }
+        select($savout);
+        next CMD;
+    }
+}
+
+sub _DB__handle_c_command {
+    my ($obj) = @_;
+
+    if (my ($new_i) = $cmd =~ m#\Ac\b\s*([\w:]*)\s*\z#) {
+
+        $obj->i_cmd($new_i);
+
+        # Hey, show's over. The debugged program finished
+        # executing already.
+        next CMD if _DB__is_finished();
+
+        # Capture the place to put a one-time break.
+        $subname = $obj->i_cmd;
+
+        #  Probably not needed, since we finish an interactive
+        #  sub-session anyway...
+        # local $filename = $filename;
+        # local *dbline = *dbline; # XXX Would this work?!
+        #
+        # The above question wonders if localizing the alias
+        # to the magic array works or not. Since it's commented
+        # out, we'll just leave that to speculation for now.
+
+        # If the "subname" isn't all digits, we'll assume it
+        # is a subroutine name, and try to find it.
+        if ( $subname =~ /\D/ ) {    # subroutine name
+            # Qualify it to the current package unless it's
+            # already qualified.
+            $subname = $package . "::" . $subname
+            unless $subname =~ /::/;
+
+            # find_sub will return "file:line_number" corresponding
+            # to where the subroutine is defined; we call find_sub,
+            # break up the return value, and assign it in one
+            # operation.
+            ( $file, $new_i ) = ( find_sub($subname) =~ /^(.*):(.*)$/ );
+
+            # Force the line number to be numeric.
+            $obj->i_cmd($new_i + 0);
+
+            # If we got a line number, we found the sub.
+            if ($obj->i_cmd) {
+
+                # Switch all the debugger's internals around so
+                # we're actually working with that file.
+                $filename = $file;
+                *dbline   = $main::{ '_<' . $filename };
+
+                # Mark that there's a breakpoint in this file.
+                $had_breakpoints{$filename} |= 1;
+
+                # Scan forward to the first executable line
+                # after the 'sub whatever' line.
+                $max = $#dbline;
+                my $ii = $obj->i_cmd;
+                ++$ii while $dbline[$ii] == 0 && $ii < $max;
+                $obj->i_cmd($ii);
+            } ## end if ($i)
+
+            # We didn't find a sub by that name.
+            else {
+                print $OUT "Subroutine $subname not found.\n";
+                next CMD;
+            }
+        } ## end if ($subname =~ /\D/)
+
+        # At this point, either the subname was all digits (an
+        # absolute line-break request) or we've scanned through
+        # the code following the definition of the sub, looking
+        # for an executable, which we may or may not have found.
+        #
+        # If $i (which we set $subname from) is non-zero, we
+        # got a request to break at some line somewhere. On
+        # one hand, if there wasn't any real subroutine name
+        # involved, this will be a request to break in the current
+        # file at the specified line, so we have to check to make
+        # sure that the line specified really is breakable.
+        #
+        # On the other hand, if there was a subname supplied, the
+        # preceding block has moved us to the proper file and
+        # location within that file, and then scanned forward
+        # looking for the next executable line. We have to make
+        # sure that one was found.
+        #
+        # On the gripping hand, we can't do anything unless the
+        # current value of $i points to a valid breakable line.
+        # Check that.
+        if ($obj->i_cmd) {
+
+            # Breakable?
+            if ( $dbline[$obj->i_cmd] == 0 ) {
+                print $OUT "Line " . $obj->i_cmd . " not breakable.\n";
+                next CMD;
+            }
+
+            # Yes. Set up the one-time-break sigil.
+            $dbline{$obj->i_cmd} =~ s/($|\0)/;9$1/;  # add one-time-only b.p.
+            _enable_breakpoint_temp_enabled_status($filename, $obj->i_cmd);
+        } ## end if ($i)
+
+        # Turn off stack tracing from here up.
+        for my $i (0 .. $stack_depth) {
+            $stack[ $i ] &= ~1;
+        }
+        last CMD;
+    }
+
+    return;
+}
+
+sub _DB__handle_forward_slash_command {
+    my ($obj) = @_;
+
+    # The pattern as a string.
+    use vars qw($inpat);
+
+    if (($inpat) = $cmd =~ m#\A/(.*)\z#) {
+
+        # Remove the final slash.
+        $inpat =~ s:([^\\])/$:$1:;
+
+        # If the pattern isn't null ...
+        if ( $inpat ne "" ) {
+
+            # Turn of warn and die procesing for a bit.
+            local $SIG{__DIE__};
+            local $SIG{__WARN__};
+
+            # Create the pattern.
+            eval 'no strict q/vars/; $inpat =~ m' . "\a$inpat\a";
+            if ( $@ ne "" ) {
+
+                # Oops. Bad pattern. No biscuit.
+                # Print the eval error and go back for more
+                # commands.
+                print $OUT "$@";
+                next CMD;
+            }
+            $obj->pat($inpat);
+        } ## end if ($inpat ne "")
+
+        # Set up to stop on wrap-around.
+        $end = $start;
+
+        # Don't move off the current line.
+        $incr = -1;
+
+        my $pat = $obj->pat;
+
+        # Done in eval so nothing breaks if the pattern
+        # does something weird.
+        eval
+        {
+            no strict q/vars/;
+            for (;;) {
+                # Move ahead one line.
+                ++$start;
+
+                # Wrap if we pass the last line.
+                $start = 1 if ($start > $max);
+
+                # Stop if we have gotten back to this line again,
+                last if ($start == $end);
+
+                # A hit! (Note, though, that we are doing
+                # case-insensitive matching. Maybe a qr//
+                # expression would be better, so the user could
+                # do case-sensitive matching if desired.
+                if ($dbline[$start] =~ m/$pat/i) {
+                    if ($slave_editor) {
+                        # Handle proper escaping in the slave.
+                        print $OUT "\032\032$filename:$start:0\n";
+                    }
+                    else {
+                        # Just print the line normally.
+                        print $OUT "$start:\t",$dbline[$start],"\n";
+                    }
+                    # And quit since we found something.
+                    last;
+                }
+            }
+        };
+
+        if ($@) {
+            warn $@;
+        }
+
+        # If we wrapped, there never was a match.
+        if ( $start == $end ) {
+            print {$OUT} "/$pat/: not found\n";
+        }
+        next CMD;
+    }
+
+    return;
+}
+
+sub _DB__handle_question_mark_command {
+    my ($obj) = @_;
+
+    # ? - backward pattern search.
+    if (my ($inpat) = $cmd =~ m#\A\?(.*)\z#) {
+
+        # Get the pattern, remove trailing question mark.
+        $inpat =~ s:([^\\])\?$:$1:;
+
+        # If we've got one ...
+        if ( $inpat ne "" ) {
+
+            # Turn off die & warn handlers.
+            local $SIG{__DIE__};
+            local $SIG{__WARN__};
+            eval '$inpat =~ m' . "\a$inpat\a";
+
+            if ( $@ ne "" ) {
+
+                # Ouch. Not good. Print the error.
+                print $OUT $@;
+                next CMD;
+            }
+            $obj->pat($inpat);
+        } ## end if ($inpat ne "")
+
+        # Where we are now is where to stop after wraparound.
+        $end = $start;
+
+        # Don't move away from this line.
+        $incr = -1;
+
+        my $pat = $obj->pat;
+        # Search inside the eval to prevent pattern badness
+        # from killing us.
+        eval {
+            no strict q/vars/;
+            for (;;) {
+                # Back up a line.
+                --$start;
+
+                # Wrap if we pass the first line.
+
+                $start = $max if ($start <= 0);
+
+                # Quit if we get back where we started,
+                last if ($start == $end);
+
+                # Match?
+                if ($dbline[$start] =~ m/$pat/i) {
+                    if ($slave_editor) {
+                        # Yep, follow slave editor requirements.
+                        print $OUT "\032\032$filename:$start:0\n";
+                    }
+                    else {
+                        # Yep, just print normally.
+                        print $OUT "$start:\t",$dbline[$start],"\n";
+                    }
+
+                    # Found, so done.
+                    last;
+                }
+            }
+        };
+
+        # Say we failed if the loop never found anything,
+        if ( $start == $end ) {
+            print {$OUT} "?$pat?: not found\n";
+        }
+        next CMD;
+    }
+
+    return;
+}
+
+sub _DB__handle_restart_and_rerun_commands {
+    my ($obj) = @_;
+
+    # R - restart execution.
+    # rerun - controlled restart execution.
+    if (my ($cmd_cmd, $cmd_params) =
+        $cmd =~ /\A((?:R)|(?:rerun\s*(.*)))\z/) {
+        my @args = ($cmd_cmd eq 'R' ? restart() : rerun($cmd_params));
+
+        # Close all non-system fds for a clean restart.  A more
+        # correct method would be to close all fds that were not
+        # open when the process started, but this seems to be
+        # hard.  See "debugger 'R'estart and open database
+        # connections" on p5p.
+
+        my $max_fd = 1024; # default if POSIX can't be loaded
+        if (eval { require POSIX }) {
+            eval { $max_fd = POSIX::sysconf(POSIX::_SC_OPEN_MAX()) };
+        }
+
+        if (defined $max_fd) {
+            foreach ($^F+1 .. $max_fd-1) {
+                next unless open FD_TO_CLOSE, "<&=$_";
+                close(FD_TO_CLOSE);
+            }
+        }
+
+        # And run Perl again.  We use exec() to keep the
+        # PID stable (and that way $ini_pids is still valid).
+        exec(@args) or print {$OUT} "exec failed: $!\n";
+
+        last CMD;
+    }
+
+    return;
+}
+
+sub _DB__handle_run_command_in_pager_command {
+    my ($obj) = @_;
+
+    if ($cmd =~ m#\A\|\|?\s*[^|]#) {
+        if ( $pager =~ /^\|/ ) {
+
+            # Default pager is into a pipe. Redirect I/O.
+            open( SAVEOUT, ">&STDOUT" )
+            || DB::warn("Can't save STDOUT");
+            open( STDOUT, ">&OUT" )
+            || DB::warn("Can't redirect STDOUT");
+        } ## end if ($pager =~ /^\|/)
+        else {
+
+            # Not into a pipe. STDOUT is safe.
+            open( SAVEOUT, ">&OUT" ) || &warn("Can't save DB::OUT");
+        }
+
+        # Fix up environment to record we have less if so.
+        fix_less();
+
+        unless ( $obj->piped(scalar ( open( OUT, $pager ) ) ) ) {
+
+            # Couldn't open pipe to pager.
+            DB::warn("Can't pipe output to '$pager'");
+            if ( $pager =~ /^\|/ ) {
+
+                # Redirect I/O back again.
+                open( OUT, ">&STDOUT" )    # XXX: lost message
+                || DB::warn("Can't restore DB::OUT");
+                open( STDOUT, ">&SAVEOUT" )
+                || DB::warn("Can't restore STDOUT");
+                close(SAVEOUT);
+            } ## end if ($pager =~ /^\|/)
+            else {
+
+                # Redirect I/O. STDOUT already safe.
+                open( OUT, ">&STDOUT" )    # XXX: lost message
+                || DB::warn("Can't restore DB::OUT");
+            }
+            next CMD;
+        } ## end unless ($piped = open(OUT,...
+
+        # Set up broken-pipe handler if necessary.
+        $SIG{PIPE} = \&DB::catch
+        if $pager =~ /^\|/
+        && ( "" eq $SIG{PIPE} || "DEFAULT" eq $SIG{PIPE} );
+
+        OUT->autoflush(1);
+        # Save current filehandle, and put it back.
+        $obj->selected(scalar( select(OUT) ));
+        # Don't put it back if pager was a pipe.
+        if ($cmd !~ /\A\|\|/)
+        {
+            select($obj->selected());
+            $obj->selected("");
+        }
+
+        # Trim off the pipe symbols and run the command now.
+        $cmd =~ s#\A\|+\s*##;
+        redo PIPE;
+    }
+
+    return;
+}
+
+sub _DB__handle_m_command {
+    my ($obj) = @_;
+
+    if ($cmd =~ s#\Am\s+([\w:]+)\s*\z# #) {
+        methods($1);
+        next CMD;
+    }
+
+    # m expr - set up DB::eval to do the work
+    if ($cmd =~ s#\Am\b# #) {    # Rest gets done by DB::eval()
+        $onetimeDump = 'methods';   #  method output gets used there
+    }
+
+    return;
+}
+
+sub _DB__at_end_of_every_command {
+    my ($obj) = @_;
+
+    # At the end of every command:
+    if ($obj->piped) {
+
+        # Unhook the pipe mechanism now.
+        if ( $pager =~ /^\|/ ) {
+
+            # No error from the child.
+            $? = 0;
+
+            # we cannot warn here: the handle is missing --tchrist
+            close(OUT) || print SAVEOUT "\nCan't close DB::OUT\n";
+
+            # most of the $? crud was coping with broken cshisms
+            # $? is explicitly set to 0, so this never runs.
+            if ($?) {
+                print SAVEOUT "Pager '$pager' failed: ";
+                if ( $? == -1 ) {
+                    print SAVEOUT "shell returned -1\n";
+                }
+                elsif ( $? >> 8 ) {
+                    print SAVEOUT ( $? & 127 )
+                    ? " (SIG#" . ( $? & 127 ) . ")"
+                    : "", ( $? & 128 ) ? " -- core dumped" : "", "\n";
+                }
+                else {
+                    print SAVEOUT "status ", ( $? >> 8 ), "\n";
+                }
+            } ## end if ($?)
+
+            # Reopen filehandle for our output (if we can) and
+            # restore STDOUT (if we can).
+            open( OUT, ">&STDOUT" ) || &warn("Can't restore DB::OUT");
+            open( STDOUT, ">&SAVEOUT" )
+            || &warn("Can't restore STDOUT");
+
+            # Turn off pipe exception handler if necessary.
+            $SIG{PIPE} = "DEFAULT" if $SIG{PIPE} eq \&DB::catch;
+
+            # Will stop ignoring SIGPIPE if done like nohup(1)
+            # does SIGINT but Perl doesn't give us a choice.
+        } ## end if ($pager =~ /^\|/)
+        else {
+
+            # Non-piped "pager". Just restore STDOUT.
+            open( OUT, ">&SAVEOUT" ) || &warn("Can't restore DB::OUT");
+        }
+
+        # Close filehandle pager was using, restore the normal one
+        # if necessary,
+        close(SAVEOUT);
+
+        if ($obj->selected() ne "") {
+            select($obj->selected);
+            $obj->selected("");
+        }
+
+        # No pipes now.
+        $obj->piped("");
+    } ## end if ($piped)
+
+    return;
+}
+
 sub DB {
 
     # lock the debugger and get the thread id for the prompt
@@ -1798,11 +2426,15 @@ sub DB {
     my ($prefix, $after, $infix);
     my $pat;
     my $explicit_stop;
+    my $piped;
+    my $selected;
 
     if ($ENV{PERL5DB_THREADED}) {
         $tid = eval { "[".threads->tid."]" };
     }
 
+    my $i;
+
     my $obj = DB::Obj->new(
         {
             position => \$position,
@@ -1810,6 +2442,10 @@ sub DB {
             after => \$after,
             explicit_stop => \$explicit_stop,
             infix => \$infix,
+            i_cmd => \$i,
+            pat => \$pat,
+            piped => \$piped,
+            selected => \$selected,
         },
     );
 
@@ -1929,7 +2565,10 @@ If there are any preprompt actions, execute those as well.
 =cut
 
     # If there's an action, do it now.
-    $evalarg = $action, DB::eval(@_) if $action;
+    if ($action) {
+        $evalarg = $action;
+        DB::eval();
+    }
 
     # Are we nested another level (e.g., did we evaluate a function
     # that had a breakpoint in it at the debugger prompt)?
@@ -1940,12 +2579,13 @@ If there are any preprompt actions, execute those as well.
 
         # Do any pre-prompt actions.
         foreach $evalarg (@$pre) {
-            DB::eval(@_);
+            DB::eval();
         }
 
         # Complain about too much recursion if we passed the limit.
-        print $OUT $stack_depth . " levels deep in subroutine calls!\n"
-          if $single & 4;
+        if ($single & 4) {
+            print $OUT $stack_depth . " levels deep in subroutine calls!\n";
+        }
 
         # The line we're currently on. Set $incr to -1 to stay here
         # until we get a command that tells us to advance.
@@ -2004,31 +2644,12 @@ the new command. This is faster, but perhaps a bit more convoluted.
         #
         # If we have a terminal for input, and we get something back
         # from readline(), keep on processing.
-        my $piped;
-        my $selected;
 
       CMD:
-        while (
-
-            # We have a terminal, or can get one ...
-            ( $term || &setterm ),
-
-            # ... and it belogs to this PID or we get one for this PID ...
-            ( $term_pid == $$ or resetterm(1) ),
-
-            # ... and we got a line of command input ...
-            defined(
-                $cmd = &readline(
-                        "$pidprompt $tid DB"
-                      . ( '<' x $level )
-                      . ( $#hist + 1 )
-                      . ( '>' x $level ) . " "
-                )
-            )
-          )
+        while (_DB__read_next_cmd($tid))
         {
 
-                       share($cmd);
+            share($cmd);
             # ... try to execute the input as debugger commands.
 
             # Don't stop running.
@@ -2039,7 +2660,7 @@ the new command. This is faster, but perhaps a bit more convoluted.
 
             # Handle continued commands (ending with \):
             if ($cmd =~ s/\\\z/\n/) {
-                $cmd .= &readline("  cont: ");
+                $cmd .= DB::readline("  cont: ");
                 redo CMD;
             }
 
@@ -2055,20 +2676,22 @@ it up.
 =cut
 
             # Empty input means repeat the last command.
-            $cmd =~ /^$/ && ( $cmd = $laststep );
+            if ($cmd eq '') {
+                $cmd = $laststep;
+            }
             chomp($cmd);    # get rid of the annoying extra newline
-            push( @hist, $cmd ) if length($cmd) > 1;
+            if (length($cmd) >= 2) {
+                push( @hist, $cmd );
+            }
             push( @truehist, $cmd );
-                       share(@hist);
-                       share(@truehist);
+            share(@hist);
+            share(@truehist);
 
             # This is a restart point for commands that didn't arrive
             # via direct user input. It allows us to 'redo PIPE' to
             # re-execute command processing without reading a new command.
           PIPE: {
-                $cmd =~ s/^\s+//s;    # trim annoying leading whitespace
-                $cmd =~ s/\s+$//s;    # trim annoying trailing whitespace
-                my ($i) = split( /\s+/, $cmd );
+                $i = _DB__trim_command_and_return_first_component();
 
 =head3 COMMAND ALIASES
 
@@ -2112,11 +2735,7 @@ environment, and executing with the last value of C<$?>.
 
 =cut
 
-                if ($cmd eq 'q') {
-                    $fall_off_end = 1;
-                    clean_ENV();
-                    exit $?;
-                }
+                $obj->_handle_q_command;
 
 =head4 C<t> - trace [n]
 
@@ -2125,16 +2744,7 @@ If level is specified, set C<$trace_to_depth>.
 
 =cut
 
-                if (my ($levels) = $cmd =~ /\At(?:\s+(\d+))?\z/) {
-                    $trace ^= 1;
-                    local $\ = '';
-                    $trace_to_depth = $levels ? $stack_depth + $levels : 1E9;
-                    print $OUT "Trace = "
-                      . ( ( $trace & 1 )
-                      ? ( $levels ? "on (to level $trace_to_depth)" : "on" )
-                      : "off" ) . "\n";
-                    next CMD;
-                }
+                $obj->_handle_t_command;
 
 =head4 C<S> - list subroutines matching/not matching a pattern
 
@@ -2142,101 +2752,20 @@ Walks through C<%sub>, checking to see whether or not to print the name.
 
 =cut
 
-                if (my ($print_all_subs, $should_reverse, $Spatt)
-                    = $cmd =~ /\AS(\s+(!)?(.+))?\z/) {
-                    # $Spatt is the pattern (if any) to use.
-                    # Reverse scan?
-                    my $Srev     = defined $should_reverse;
-                    # No args - print all subs.
-                    my $Snocheck = !defined $print_all_subs;
-
-                    # Need to make these sane here.
-                    local $\ = '';
-                    local $, = '';
-
-                    # Search through the debugger's magical hash of subs.
-                    # If $nocheck is true, just print the sub name.
-                    # Otherwise, check it against the pattern. We then use
-                    # the XOR trick to reverse the condition as required.
-                    foreach $subname ( sort( keys %sub ) ) {
-                        if ( $Snocheck or $Srev ^ ( $subname =~ /$Spatt/ ) ) {
-                            print $OUT $subname, "\n";
-                        }
-                    }
-                    next CMD;
-                }
+                $obj->_handle_S_command;
 
 =head4 C<X> - list variables in current package
 
 Since the C<V> command actually processes this, just change this to the
 appropriate C<V> command and fall through.
 
-=cut
-
-                $cmd =~ s/^X\b/V $package/;
-
 =head4 C<V> - list variables
 
 Uses C<dumpvar.pl> to dump out the current values for selected variables.
 
 =cut
 
-                # Bare V commands get the currently-being-debugged package
-                # added.
-                if ($cmd eq "V") {
-                    $cmd = "V $package";
-                }
-
-                # V - show variables in package.
-                if (my ($new_packname, $new_vars_str) =
-                    $cmd =~ /\AV\b\s*(\S+)\s*(.*)/) {
-
-                    # Save the currently selected filehandle and
-                    # force output to debugger's filehandle (dumpvar
-                    # just does "print" for output).
-                    my $savout = select($OUT);
-
-                    # Grab package name and variables to dump.
-                    $packname = $new_packname;
-                    my @vars     = split( ' ', $new_vars_str );
-
-                    # If main::dumpvar isn't here, get it.
-                    do 'dumpvar.pl' || die $@ unless defined &main::dumpvar;
-                    if ( defined &main::dumpvar ) {
-
-                        # We got it. Turn off subroutine entry/exit messages
-                        # for the moment, along with return values.
-                        local $frame = 0;
-                        local $doret = -2;
-
-                        # must detect sigpipe failures  - not catching
-                        # then will cause the debugger to die.
-                        eval {
-                            &main::dumpvar(
-                                $packname,
-                                defined $option{dumpDepth}
-                                ? $option{dumpDepth}
-                                : -1,    # assume -1 unless specified
-                                @vars
-                            );
-                        };
-
-                        # The die doesn't need to include the $@, because
-                        # it will automatically get propagated for us.
-                        if ($@) {
-                            die unless $@ =~ /dumpvar print failed/;
-                        }
-                    } ## end if (defined &main::dumpvar)
-                    else {
-
-                        # Couldn't load dumpvar.
-                        print $OUT "dumpvar.pl not available.\n";
-                    }
-
-                    # Restore the output filehandle, and go round again.
-                    select($savout);
-                    next CMD;
-                }
+                $obj->_handle_V_command_and_X_command;
 
 =head4 C<x> - evaluate and print an expression
 
@@ -2245,15 +2774,7 @@ via C<dumpvar.pl> instead of just printing it directly.
 
 =cut
 
-                if ($cmd =~ s#\Ax\b# #) {    # Remainder gets done by DB::eval()
-                    $onetimeDump = 'dump';    # main::dumpvar shows the output
-
-                    # handle special  "x 3 blah" syntax XXX propagate
-                    # doc back to special variables.
-                    if ( $cmd =~ s#\A\s*(\d+)(?=\s)# #) {
-                        $onetimedumpDepth = $1;
-                    }
-                }
+                $obj->_handle_x_command;
 
 =head4 C<m> - print methods
 
@@ -2261,85 +2782,22 @@ Just uses C<DB::methods> to determine what methods are available.
 
 =cut
 
-                if ($cmd =~ s#\Am\s+([\w:]+)\s*\z# #) {
-                    methods($1);
-                    next CMD;
-                }
-
-                # m expr - set up DB::eval to do the work
-                if ($cmd =~ s#\Am\b# #) {    # Rest gets done by DB::eval()
-                    $onetimeDump = 'methods';   #  method output gets used there
-                }
+                _DB__handle_m_command($obj);
 
 =head4 C<f> - switch files
 
 =cut
 
-                if (($file) = $cmd =~ /\Af\b\s*(.*)/) {
-                    $file =~ s/\s+$//;
-
-                    # help for no arguments (old-style was return from sub).
-                    if ( !$file ) {
-                        print $OUT
-                          "The old f command is now the r command.\n";    # hint
-                        print $OUT "The new f command switches filenames.\n";
-                        next CMD;
-                    } ## end if (!$file)
-
-                    # if not in magic file list, try a close match.
-                    if ( !defined $main::{ '_<' . $file } ) {
-                        if ( ($try) = grep( m#^_<.*$file#, keys %main:: ) ) {
-                            {
-                                $try = substr( $try, 2 );
-                                print $OUT "Choosing $try matching '$file':\n";
-                                $file = $try;
-                            }
-                        } ## end if (($try) = grep(m#^_<.*$file#...
-                    } ## end if (!defined $main::{ ...
-
-                    # If not successfully switched now, we failed.
-                    if ( !defined $main::{ '_<' . $file } ) {
-                        print $OUT "No file matching '$file' is loaded.\n";
-                        next CMD;
-                    }
+                _DB__handle_f_command();
 
-                    # We switched, so switch the debugger internals around.
-                    elsif ( $file ne $filename ) {
-                        *dbline   = $main::{ '_<' . $file };
-                        $max      = $#dbline;
-                        $filename = $file;
-                        $start    = 1;
-                        $cmd      = "l";
-                    } ## end elsif ($file ne $filename)
-
-                    # We didn't switch; say we didn't.
-                    else {
-                        print $OUT "Already in $file.\n";
-                        next CMD;
-                    }
-                }
-
-=head4 C<.> - return to last-executed line.
+=head4 C<.> - return to last-executed line.
 
 We set C<$incr> to -1 to indicate that the debugger shouldn't move ahead,
 and then we look up the line in the magical C<%dbline> hash.
 
 =cut
 
-                # . command.
-                if ($cmd eq '.') {
-                    $incr = -1;    # stay at current line
-
-                    # Reset everything to the old location.
-                    $start    = $line;
-                    $filename = $filename_ini;
-                    *dbline   = $main::{ '_<' . $filename };
-                    $max      = $#dbline;
-
-                    # Now where are we?
-                    print_lineinfo($position);
-                    next CMD;
-                }
+                _DB__handle_dot_command($obj);
 
 =head4 C<-> - back one window
 
@@ -2351,18 +2809,9 @@ C<$start>) in C<$cmd> to be executed later.
 =cut
 
                 # - - back a window.
-                if ($cmd eq '-') {
+                $obj->_handle_dash_command;
 
-                    # back up by a window; go to 1 if back too far.
-                    $start -= $incr + $window + 1;
-                    $start = 1 if $start <= 0;
-                    $incr  = $window - 1;
-
-                    # Generate and execute a "l +" command (handled below).
-                    $cmd = 'l ' . ($start) . '+';
-                }
-
-=head3 PRE-580 COMMANDS VS. NEW COMMANDS: C<a, A, b, B, h, l, L, M, o, O, P, v, w, W, E<lt>, E<lt>E<lt>, {, {{>
+=head3 PRE-580 COMMANDS VS. NEW COMMANDS: C<a, A, b, B, h, l, L, M, o, O, P, v, w, W, E<lt>, E<lt>E<lt>, E<0x7B>, E<0x7B>E<0x7B>>
 
 In Perl 5.8.0, a realignment of the commands was done to fix up a number of
 problems, most notably that the default case of several commands destroying
@@ -2375,10 +2824,7 @@ deal with them instead of processing them in-line.
 
                 # All of these commands were remapped in perl 5.8.0;
                 # we send them off to the secondary dispatcher (see below).
-                if (my ($cmd_letter, $my_arg) = $cmd =~ /\A([aAbBeEhilLMoOPvwW]\b|[<>\{]{1,2})\s*(.*)/so) {
-                    &cmd_wrapper( $cmd_letter, $my_arg, $line );
-                    next CMD;
-                }
+                $obj->_handle_cmd_wrapper_commands;
 
 =head4 C<y> - List lexicals in higher scope
 
@@ -2387,44 +2833,7 @@ above the current one and then displays then using C<dumpvar.pl>.
 
 =cut
 
-                if (my ($match_level, $match_vars)
-                    = $cmd =~ /^y(?:\s+(\d*)\s*(.*))?$/) {
-
-                    # See if we've got the necessary support.
-                    eval { require PadWalker; PadWalker->VERSION(0.08) }
-                      or &warn(
-                        $@ =~ /locate/
-                        ? "PadWalker module not found - please install\n"
-                        : $@
-                      )
-                      and next CMD;
-
-                    # Load up dumpvar if we don't have it. If we can, that is.
-                    do 'dumpvar.pl' || die $@ unless defined &main::dumpvar;
-                    defined &main::dumpvar
-                      or print $OUT "dumpvar.pl not available.\n"
-                      and next CMD;
-
-                    # Got all the modules we need. Find them and print them.
-                    my @vars = split( ' ', $match_vars || '' );
-
-                    # Find the pad.
-                    my $h = eval { PadWalker::peek_my( ( $match_level || 0 ) + 1 ) };
-
-                    # Oops. Can't find it.
-                    $@ and $@ =~ s/ at .*//, &warn($@), next CMD;
-
-                    # Show the desired vars with dumplex().
-                    my $savout = select($OUT);
-
-                    # Have dumplex dump the lexicals.
-                    dumpvar::dumplex( $_, $h->{$_},
-                        defined $option{dumpDepth} ? $option{dumpDepth} : -1,
-                        @vars )
-                      for sort keys %$h;
-                    select($savout);
-                    next CMD;
-                }
+                _DB__handle_y_command($obj);
 
 =head3 COMMANDS NOT WORKING AFTER PROGRAM ENDS
 
@@ -2443,16 +2852,7 @@ so a null command knows what to re-execute.
 =cut
 
                 # n - next
-                if ($cmd eq 'n') {
-                    end_report(), next CMD if $finished and $level <= 1;
-
-                    # Single step, but don't enter subs.
-                    $single = 2;
-
-                    # Save for empty command (repeat last).
-                    $laststep = $cmd;
-                    last CMD;
-                }
+                $obj->_handle_n_command;
 
 =head4 C<s> - single-step, entering subs
 
@@ -2461,20 +2861,7 @@ subs. Also saves C<s> as C<$lastcmd>.
 
 =cut
 
-                # s - single step.
-                if ($cmd eq 's') {
-
-                    # Get out and restart the command loop if program
-                    # has finished.
-                    end_report(), next CMD if $finished and $level <= 1;
-
-                    # Single step should enter subs.
-                    $single = 1;
-
-                    # Save for empty command (repeat last).
-                    $laststep = $cmd;
-                    last CMD;
-                }
+                $obj->_handle_s_command;
 
 =head4 C<c> - run continuously, setting an optional breakpoint
 
@@ -2486,105 +2873,7 @@ in this and all call levels above this one.
 =cut
 
                 # c - start continuous execution.
-                if (($i) = $cmd =~ m#\Ac\b\s*([\w:]*)\s*\z#) {
-
-                    # Hey, show's over. The debugged program finished
-                    # executing already.
-                    end_report(), next CMD if $finished and $level <= 1;
-
-                    # Capture the place to put a one-time break.
-                    $subname = $i;
-
-                    #  Probably not needed, since we finish an interactive
-                    #  sub-session anyway...
-                    # local $filename = $filename;
-                    # local *dbline = *dbline; # XXX Would this work?!
-                    #
-                    # The above question wonders if localizing the alias
-                    # to the magic array works or not. Since it's commented
-                    # out, we'll just leave that to speculation for now.
-
-                    # If the "subname" isn't all digits, we'll assume it
-                    # is a subroutine name, and try to find it.
-                    if ( $subname =~ /\D/ ) {    # subroutine name
-                            # Qualify it to the current package unless it's
-                            # already qualified.
-                        $subname = $package . "::" . $subname
-                          unless $subname =~ /::/;
-
-                        # find_sub will return "file:line_number" corresponding
-                        # to where the subroutine is defined; we call find_sub,
-                        # break up the return value, and assign it in one
-                        # operation.
-                        ( $file, $i ) = ( find_sub($subname) =~ /^(.*):(.*)$/ );
-
-                        # Force the line number to be numeric.
-                        $i += 0;
-
-                        # If we got a line number, we found the sub.
-                        if ($i) {
-
-                            # Switch all the debugger's internals around so
-                            # we're actually working with that file.
-                            $filename = $file;
-                            *dbline   = $main::{ '_<' . $filename };
-
-                            # Mark that there's a breakpoint in this file.
-                            $had_breakpoints{$filename} |= 1;
-
-                            # Scan forward to the first executable line
-                            # after the 'sub whatever' line.
-                            $max = $#dbline;
-                            ++$i while $dbline[$i] == 0 && $i < $max;
-                        } ## end if ($i)
-
-                        # We didn't find a sub by that name.
-                        else {
-                            print $OUT "Subroutine $subname not found.\n";
-                            next CMD;
-                        }
-                    } ## end if ($subname =~ /\D/)
-
-                    # At this point, either the subname was all digits (an
-                    # absolute line-break request) or we've scanned through
-                    # the code following the definition of the sub, looking
-                    # for an executable, which we may or may not have found.
-                    #
-                    # If $i (which we set $subname from) is non-zero, we
-                    # got a request to break at some line somewhere. On
-                    # one hand, if there wasn't any real subroutine name
-                    # involved, this will be a request to break in the current
-                    # file at the specified line, so we have to check to make
-                    # sure that the line specified really is breakable.
-                    #
-                    # On the other hand, if there was a subname supplied, the
-                    # preceding block has moved us to the proper file and
-                    # location within that file, and then scanned forward
-                    # looking for the next executable line. We have to make
-                    # sure that one was found.
-                    #
-                    # On the gripping hand, we can't do anything unless the
-                    # current value of $i points to a valid breakable line.
-                    # Check that.
-                    if ($i) {
-
-                        # Breakable?
-                        if ( $dbline[$i] == 0 ) {
-                            print $OUT "Line $i not breakable.\n";
-                            next CMD;
-                        }
-
-                        # Yes. Set up the one-time-break sigil.
-                        $dbline{$i} =~ s/($|\0)/;9$1/;  # add one-time-only b.p.
-                        _enable_breakpoint_temp_enabled_status($filename, $i);
-                    } ## end if ($i)
-
-                    # Turn off stack tracing from here up.
-                    for my $i (0 .. $stack_depth) {
-                        $stack[ $i ] &= ~1;
-                    }
-                    last CMD;
-                }
+                _DB__handle_c_command($obj);
 
 =head4 C<r> - return from a subroutine
 
@@ -2597,18 +2886,7 @@ appropriately, and force us out of the command loop.
 =cut
 
                 # r - return from the current subroutine.
-                if ($cmd eq 'r') {
-
-                    # Can't do anything if the program's over.
-                    end_report(), next CMD if $finished and $level <= 1;
-
-                    # Turn on stack trace.
-                    $stack[$stack_depth] |= 1;
-
-                    # Print return value unless the stack is empty.
-                    $doret = $option{PrintRet} ? $stack_depth - 1 : -2;
-                    last CMD;
-                }
+                $obj->_handle_r_command;
 
 =head4 C<T> - stack trace
 
@@ -2616,10 +2894,7 @@ Just calls C<DB::print_trace>.
 
 =cut
 
-                if ($cmd eq 'T') {
-                    print_trace( $OUT, 1 );    # skip DB
-                    next CMD;
-                }
+                $obj->_handle_T_command;
 
 =head4 C<w> - List window around current line.
 
@@ -2627,10 +2902,7 @@ Just calls C<DB::cmd_w>.
 
 =cut
 
-                if (my ($arg) = $cmd =~ /\Aw\b\s*(.*)/s) {
-                    &cmd_w( 'w', $arg );
-                    next CMD;
-                }
+                $obj->_handle_w_command;
 
 =head4 C<W> - watch-expression processing.
 
@@ -2638,10 +2910,7 @@ Just calls C<DB::cmd_W>.
 
 =cut
 
-                if (my ($arg) = $cmd =~ /\AW\b\s*(.*)/s) {
-                    &cmd_W( 'W', $arg );
-                    next CMD;
-                }
+                $obj->_handle_W_command;
 
 =head4 C</> - search forward for a string in the source
 
@@ -2652,76 +2921,7 @@ mess us up.
 
 =cut
 
-                # The pattern as a string.
-                use vars qw($inpat);
-
-                if (($inpat) = $cmd =~ m#\A/(.*)\z#) {
-
-                    # Remove the final slash.
-                    $inpat =~ s:([^\\])/$:$1:;
-
-                    # If the pattern isn't null ...
-                    if ( $inpat ne "" ) {
-
-                        # Turn of warn and die procesing for a bit.
-                        local $SIG{__DIE__};
-                        local $SIG{__WARN__};
-
-                        # Create the pattern.
-                        eval 'no strict q/vars/; $inpat =~ m' . "\a$inpat\a";
-                        if ( $@ ne "" ) {
-
-                            # Oops. Bad pattern. No biscuit.
-                            # Print the eval error and go back for more
-                            # commands.
-                            print $OUT "$@";
-                            next CMD;
-                        }
-                        $pat = $inpat;
-                    } ## end if ($inpat ne "")
-
-                    # Set up to stop on wrap-around.
-                    $end = $start;
-
-                    # Don't move off the current line.
-                    $incr = -1;
-
-                    # Done in eval so nothing breaks if the pattern
-                    # does something weird.
-                    eval '
-                        no strict q/vars/;
-                        for (;;) {
-                            # Move ahead one line.
-                            ++$start;
-
-                            # Wrap if we pass the last line.
-                            $start = 1 if ($start > $max);
-
-                            # Stop if we have gotten back to this line again,
-                            last if ($start == $end);
-
-                            # A hit! (Note, though, that we are doing
-                            # case-insensitive matching. Maybe a qr//
-                            # expression would be better, so the user could
-                            # do case-sensitive matching if desired.
-                            if ($dbline[$start] =~ m' . "\a$pat\a" . 'i) {
-                                if ($slave_editor) {
-                                    # Handle proper escaping in the slave.
-                                    print $OUT "\032\032$filename:$start:0\n";
-                                }
-                                else {
-                                    # Just print the line normally.
-                                    print $OUT "$start:\t",$dbline[$start],"\n";
-                                }
-                                # And quit since we found something.
-                                last;
-                            }
-                         } ';
-
-                    # If we wrapped, there never was a match.
-                    print $OUT "/$pat/: not found\n" if ( $start == $end );
-                    next CMD;
-                }
+                _DB__handle_forward_slash_command($obj);
 
 =head4 C<?> - search backward for a string in the source
 
@@ -2729,70 +2929,7 @@ Same as for C</>, except the loop runs backwards.
 
 =cut
 
-                # ? - backward pattern search.
-                if (my ($inpat) = $cmd =~ m#\A\?(.*)\z#) {
-
-                    # Get the pattern, remove trailing question mark.
-                    $inpat =~ s:([^\\])\?$:$1:;
-
-                    # If we've got one ...
-                    if ( $inpat ne "" ) {
-
-                        # Turn off die & warn handlers.
-                        local $SIG{__DIE__};
-                        local $SIG{__WARN__};
-                        eval '$inpat =~ m' . "\a$inpat\a";
-
-                        if ( $@ ne "" ) {
-
-                            # Ouch. Not good. Print the error.
-                            print $OUT $@;
-                            next CMD;
-                        }
-                        $pat = $inpat;
-                    } ## end if ($inpat ne "")
-
-                    # Where we are now is where to stop after wraparound.
-                    $end = $start;
-
-                    # Don't move away from this line.
-                    $incr = -1;
-
-                    # Search inside the eval to prevent pattern badness
-                    # from killing us.
-                    eval '
-                        no strict q/vars/;
-                        for (;;) {
-                            # Back up a line.
-                            --$start;
-
-                            # Wrap if we pass the first line.
-
-                            $start = $max if ($start <= 0);
-
-                            # Quit if we get back where we started,
-                            last if ($start == $end);
-
-                            # Match?
-                            if ($dbline[$start] =~ m' . "\a$pat\a" . 'i) {
-                                if ($slave_editor) {
-                                    # Yep, follow slave editor requirements.
-                                    print $OUT "\032\032$filename:$start:0\n";
-                                }
-                                else {
-                                    # Yep, just print normally.
-                                    print $OUT "$start:\t",$dbline[$start],"\n";
-                                }
-
-                                # Found, so done.
-                                last;
-                            }
-                        } ';
-
-                    # Say we failed if the loop never found anything,
-                    print $OUT "?$pat?: not found\n" if ( $start == $end );
-                    next CMD;
-                }
+                _DB__handle_question_mark_command($obj);
 
 =head4 C<$rc> - Recall command
 
@@ -2803,25 +2940,7 @@ into C<$cmd>, and redoes the loop to execute it.
 =cut
 
                 # $rc - recall command.
-                if (my ($minus, $arg) = $cmd =~ m#\A$rc+\s*(-)?(\d+)?\z#) {
-
-                    # No arguments, take one thing off history.
-                    pop(@hist) if length($cmd) > 1;
-
-                    # Relative (- found)?
-                    #  Y - index back from most recent (by 1 if bare minus)
-                    #  N - go to that particular command slot or the last
-                    #      thing if nothing following.
-                    $i = $minus ? ( $#hist - ( $arg || 1 ) ) : ( $arg || $#hist );
-
-                    # Pick out the command desired.
-                    $cmd = $hist[$i];
-
-                    # Print the command to be executed and restart the loop
-                    # with that command in the buffer.
-                    print $OUT $cmd, "\n";
-                    redo CMD;
-                }
+                $obj->_handle_rc_recall_command;
 
 =head4 C<$sh$sh> - C<system()> command
 
@@ -2830,14 +2949,7 @@ C<STDOUT> from getting messed up.
 
 =cut
 
-                # $sh$sh - run a shell command (if it's all ASCII).
-                # Can't run shell commands with Unicode in the debugger, hmm.
-                if (my ($arg) = $cmd =~ m#\A$sh$sh\s*(.*)#ms) {
-
-                    # System it.
-                    &system($arg);
-                    next CMD;
-                }
+                $obj->_handle_sh_command;
 
 =head4 C<$rc I<pattern> $rc> - Search command history
 
@@ -2846,33 +2958,7 @@ If a command is found, it is placed in C<$cmd> and executed via C<redo>.
 
 =cut
 
-                # $rc pattern $rc - find a command in the history.
-                if (my ($arg) = $cmd =~ /\A$rc([^$rc].*)\z/) {
-
-                    # Create the pattern to use.
-                    $pat = "^$arg";
-
-                    # Toss off last entry if length is >1 (and it always is).
-                    pop(@hist) if length($cmd) > 1;
-
-                    # Look backward through the history.
-                    for ( $i = $#hist ; $i ; --$i ) {
-                        # Stop if we find it.
-                        last if $hist[$i] =~ /$pat/;
-                    }
-
-                    if ( !$i ) {
-
-                        # Never found it.
-                        print $OUT "No such command!\n\n";
-                        next CMD;
-                    }
-
-                    # Found it. Put it in the buffer, print it, and process it.
-                    $cmd = $hist[$i];
-                    print $OUT $cmd, "\n";
-                    redo CMD;
-                }
+                $obj->_handle_rc_search_history_command;
 
 =head4 C<$sh> - Invoke a shell
 
@@ -2880,66 +2966,18 @@ Uses C<DB::system> to invoke a shell.
 
 =cut
 
-                # $sh - start a shell.
-                if ($cmd =~ /\A$sh\z/) {
-
-                    # Run the user's shell. If none defined, run Bourne.
-                    # We resume execution when the shell terminates.
-                    &system( $ENV{SHELL} || "/bin/sh" );
-                    next CMD;
-                }
-
 =head4 C<$sh I<command>> - Force execution of a command in a shell
 
 Like the above, but the command is passed to the shell. Again, we use
 C<DB::system> to avoid problems with C<STDIN> and C<STDOUT>.
 
-=cut
-
-                # $sh command - start a shell and run a command in it.
-                if (my ($arg) = $cmd =~ m#\A$sh\s*(.*)#ms) {
-
-                    # XXX: using csh or tcsh destroys sigint retvals!
-                    #&system($1);  # use this instead
-
-                    # use the user's shell, or Bourne if none defined.
-                    &system( $ENV{SHELL} || "/bin/sh", "-c", $arg );
-                    next CMD;
-                }
-
 =head4 C<H> - display commands in history
 
 Prints the contents of C<@hist> (if any).
 
 =cut
 
-                if ($cmd =~ /\AH\b\s*\*/) {
-                    @hist = @truehist = ();
-                    print $OUT "History cleansed\n";
-                    next CMD;
-                }
-
-                if (my ($num)
-                    = $cmd =~ /\AH\b\s*(?:-(\d+))?/) {
-
-                    # Anything other than negative numbers is ignored by
-                    # the (incorrect) pattern, so this test does nothing.
-                    $end = $num ? ( $#hist - $num ) : 0;
-
-                    # Set to the minimum if less than zero.
-                    $hist = 0 if $hist < 0;
-
-                    # Start at the end of the array.
-                    # Stay in while we're still above the ending value.
-                    # Tick back by one each time around the loop.
-                    for ( $i = $#hist ; $i > $end ; $i-- ) {
-
-                        # Print the command  unless it has no arguments.
-                        print $OUT "$i: ", $hist[$i], "\n"
-                          unless $hist[$i] =~ /^.?$/;
-                    }
-                    next CMD;
-                }
+                $obj->_handle_H_command;
 
 =head4 C<man, doc, perldoc> - look up documentation
 
@@ -2947,12 +2985,7 @@ Just calls C<runman()> to print the appropriate document.
 
 =cut
 
-                # man, perldoc, doc - show manual pages.
-                if (my ($man_page)
-                    = $cmd =~ /\A(?:man|(?:perl)?doc)\b(?:\s+([^(]*))?\z/) {
-                    runman($man_page);
-                    next CMD;
-                }
+                $obj->_handle_doc_command;
 
 =head4 C<p> - print
 
@@ -2961,14 +2994,7 @@ the bottom of the loop.
 
 =cut
 
-                my $print_cmd = 'print {$DB::OUT} ';
-                # p - print (no args): print $_.
-                if ($cmd eq 'p') {
-                    $cmd = $print_cmd . '$_';
-                }
-
-                # p - print the given expression.
-                $cmd =~ s/\Ap\b/$print_cmd /;
+                $obj->_handle_p_command;
 
 =head4 C<=> - define command alias
 
@@ -2977,76 +3003,7 @@ Manipulates C<%alias> to add or list command aliases.
 =cut
 
                 # = - set up a command alias.
-                if ($cmd =~ s/\A=\s*//) {
-                    my @keys;
-                    if ( length $cmd == 0 ) {
-
-                        # No args, get current aliases.
-                        @keys = sort keys %alias;
-                    }
-                    elsif ( my ( $k, $v ) = ( $cmd =~ /^(\S+)\s+(\S.*)/ ) ) {
-
-                        # Creating a new alias. $k is alias name, $v is
-                        # alias value.
-
-                        # can't use $_ or kill //g state
-                        for my $x ( $k, $v ) {
-
-                            # Escape "alarm" characters.
-                            $x =~ s/\a/\\a/g;
-                        }
-
-                        # Substitute key for value, using alarm chars
-                        # as separators (which is why we escaped them in
-                        # the command).
-                        $alias{$k} = "s\a$k\a$v\a";
-
-                        # Turn off standard warn and die behavior.
-                        local $SIG{__DIE__};
-                        local $SIG{__WARN__};
-
-                        # Is it valid Perl?
-                        unless ( eval "sub { s\a$k\a$v\a }; 1" ) {
-
-                            # Nope. Bad alias. Say so and get out.
-                            print $OUT "Can't alias $k to $v: $@\n";
-                            delete $alias{$k};
-                            next CMD;
-                        }
-
-                        # We'll only list the new one.
-                        @keys = ($k);
-                    } ## end elsif (my ($k, $v) = ($cmd...
-
-                    # The argument is the alias to list.
-                    else {
-                        @keys = ($cmd);
-                    }
-
-                    # List aliases.
-                    for my $k (@keys) {
-
-                        # Messy metaquoting: Trim the substitution code off.
-                        # We use control-G as the delimiter because it's not
-                        # likely to appear in the alias.
-                        if ( ( my $v = $alias{$k} ) =~ s\as\a$k\a(.*)\a$\a1\a ) {
-
-                            # Print the alias.
-                            print $OUT "$k\t= $1\n";
-                        }
-                        elsif ( defined $alias{$k} ) {
-
-                            # Couldn't trim it off; just print the alias code.
-                            print $OUT "$k\t$alias{$k}\n";
-                        }
-                        else {
-
-                            # No such, dude.
-                            print "No alias for $k\n";
-                        }
-                    } ## end for my $k (@keys)
-                    next CMD;
-                }
+                $obj->_handle_equal_sign_command;
 
 =head4 C<source> - read commands from a file.
 
@@ -3055,52 +3012,15 @@ pick it up.
 
 =cut
 
-                # source - read commands from a file (or pipe!) and execute.
-                if (my ($sourced_fn) = $cmd =~ /\Asource\s+(.*\S)/) {
-                    if ( open my $fh, $sourced_fn ) {
+                $obj->_handle_source_command;
 
-                        # Opened OK; stick it in the list of file handles.
-                        push @cmdfhs, $fh;
-                    }
-                    else {
+=head4 C<enable> C<disable> - enable or disable breakpoints
 
-                        # Couldn't open it.
-                        &warn("Can't execute '$sourced_fn': $!\n");
-                    }
-                    next CMD;
-                }
+This enables or disables breakpoints.
 
-                if (my ($which_cmd, $position)
-                    = $cmd =~ /^(enable|disable)\s+(\S+)\s*$/) {
-
-                    my ($fn, $line_num);
-                    if ($position =~ m{\A\d+\z})
-                    {
-                        $fn = $filename;
-                        $line_num = $position;
-                    }
-                    elsif (my ($new_fn, $new_line_num)
-                        = $position =~ m{\A(.*):(\d+)\z}) {
-                        ($fn, $line_num) = ($new_fn, $new_line_num);
-                    }
-                    else
-                    {
-                        &warn("Wrong spec for enable/disable argument.\n");
-                    }
-
-                    if (defined($fn)) {
-                        if (_has_breakpoint_data_ref($fn, $line_num)) {
-                            _set_breakpoint_enabled_status($fn, $line_num,
-                                ($which_cmd eq 'enable' ? 1 : '')
-                            );
-                        }
-                        else {
-                            &warn("No breakpoint set at ${fn}:${line_num}\n");
-                        }
-                    }
+=cut
 
-                    next CMD;
-                }
+                $obj->_handle_enable_disable_commands;
 
 =head4 C<save> - send current history to a file
 
@@ -3112,22 +3032,7 @@ Note that all C<^(save|source)>'s are commented out with a view to minimise recu
 =cut
 
                 # save source - write commands to a file for later use
-                if (my ($new_fn) = $cmd =~ /\Asave\s*(.*)\z/) {
-                    my $filename = $new_fn || '.perl5dbrc';    # default?
-                    if ( open my $fh, '>', $filename ) {
-
-                       # chomp to remove extraneous newlines from source'd files
-                        chomp( my @truelist =
-                              map { m/^\s*(save|source)/ ? "#$_" : $_ }
-                              @truehist );
-                        print $fh join( "\n", @truelist );
-                        print "commands saved in $file\n";
-                    }
-                    else {
-                        &warn("Can't save debugger commands in '$new_fn': $!\n");
-                    }
-                    next CMD;
-                }
+                $obj->_handle_save_command;
 
 =head4 C<R> - restart
 
@@ -3141,34 +3046,7 @@ Return to any given position in the B<true>-history list
 
                 # R - restart execution.
                 # rerun - controlled restart execution.
-                if (my ($cmd_cmd, $cmd_params) =
-                    $cmd =~ /\A((?:R)|(?:rerun\s*(.*)))\z/) {
-                    my @args = ($cmd_cmd eq 'R' ? restart() : rerun($cmd_params));
-
-                    # Close all non-system fds for a clean restart.  A more
-                    # correct method would be to close all fds that were not
-                    # open when the process started, but this seems to be
-                    # hard.  See "debugger 'R'estart and open database
-                    # connections" on p5p.
-
-                    my $max_fd = 1024; # default if POSIX can't be loaded
-                    if (eval { require POSIX }) {
-                        eval { $max_fd = POSIX::sysconf(POSIX::_SC_OPEN_MAX()) };
-                    }
-
-                    if (defined $max_fd) {
-                        foreach ($^F+1 .. $max_fd-1) {
-                            next unless open FD_TO_CLOSE, "<&=$_";
-                            close(FD_TO_CLOSE);
-                        }
-                    }
-
-                    # And run Perl again.  We use exec() to keep the
-                    # PID stable (and that way $ini_pids is still valid).
-                    exec(@args) || print $OUT "exec failed: $!\n";
-
-                    last CMD;
-                }
+                _DB__handle_restart_and_rerun_commands($obj);
 
 =head4 C<|, ||> - pipe output through the pager.
 
@@ -3185,61 +3063,7 @@ reading another.
 =cut
 
                 # || - run command in the pager, with output to DB::OUT.
-                if ($cmd =~ m#\A\|\|?\s*[^|]#) {
-                    if ( $pager =~ /^\|/ ) {
-
-                        # Default pager is into a pipe. Redirect I/O.
-                        open( SAVEOUT, ">&STDOUT" )
-                          || &warn("Can't save STDOUT");
-                        open( STDOUT, ">&OUT" )
-                          || &warn("Can't redirect STDOUT");
-                    } ## end if ($pager =~ /^\|/)
-                    else {
-
-                        # Not into a pipe. STDOUT is safe.
-                        open( SAVEOUT, ">&OUT" ) || &warn("Can't save DB::OUT");
-                    }
-
-                    # Fix up environment to record we have less if so.
-                    fix_less();
-
-                    unless ( $piped = open( OUT, $pager ) ) {
-
-                        # Couldn't open pipe to pager.
-                        &warn("Can't pipe output to '$pager'");
-                        if ( $pager =~ /^\|/ ) {
-
-                            # Redirect I/O back again.
-                            open( OUT, ">&STDOUT" )    # XXX: lost message
-                              || &warn("Can't restore DB::OUT");
-                            open( STDOUT, ">&SAVEOUT" )
-                              || &warn("Can't restore STDOUT");
-                            close(SAVEOUT);
-                        } ## end if ($pager =~ /^\|/)
-                        else {
-
-                            # Redirect I/O. STDOUT already safe.
-                            open( OUT, ">&STDOUT" )    # XXX: lost message
-                              || &warn("Can't restore DB::OUT");
-                        }
-                        next CMD;
-                    } ## end unless ($piped = open(OUT,...
-
-                    # Set up broken-pipe handler if necessary.
-                    $SIG{PIPE} = \&DB::catch
-                      if $pager =~ /^\|/
-                      && ( "" eq $SIG{PIPE} || "DEFAULT" eq $SIG{PIPE} );
-
-                    OUT->autoflush(1);
-                    # Save current filehandle, and put it back.
-                    $selected = select(OUT);
-                    # Don't put it back if pager was a pipe.
-                    select($selected), $selected = "" unless $cmd =~ /^\|\|/;
-
-                    # Trim off the pipe symbols and run the command now.
-                    $cmd =~ s#\A\|+\s*##;
-                    redo PIPE;
-                }
+                _DB__handle_run_command_in_pager_command($obj);
 
 =head3 END OF COMMAND PARSING
 
@@ -3249,23 +3073,6 @@ any variables we might want to address in the C<DB> package.
 
 =cut
 
-                # t - turn trace on.
-                if ($cmd =~ s#\At\s+(\d+)?#\$DB::trace |= 1;\n#) {
-                    my $trace_arg = $1;
-                    $trace_to_depth = $trace_arg ? $stack_depth||0 + $1 : 1E9;
-                }
-
-                # s - single-step. Remember the last command was 's'.
-                if ($cmd =~ s/\As\s/\$DB::single = 1;\n/) {
-                    $laststep = 's';
-                }
-
-                # n - single-step, but not into subs. Remember last command
-                # was 'n'.
-                if ($cmd =~ s#\An\s#\$DB::single = 2;\n#) {
-                    $laststep = 'n';
-                }
-
             }    # PIPE:
 
             # Make sure the flag that says "the debugger's running" is
@@ -3273,7 +3080,7 @@ any variables we might want to address in the C<DB> package.
             $evalarg = "\$^D = \$^D | \$DB::db_stop;\n$cmd";
 
             # Run *our* eval that executes in the caller's context.
-            DB::eval(@_);
+            DB::eval();
 
             # Turn off the one-time-dump stuff now.
             if ($onetimeDump) {
@@ -3281,13 +3088,13 @@ any variables we might want to address in the C<DB> package.
                 $onetimedumpDepth = undef;
             }
             elsif ( $term_pid == $$ ) {
-               eval {          # May run under miniperl, when not available...
+                eval { # May run under miniperl, when not available...
                     STDOUT->flush();
                     STDERR->flush();
-               };
+                };
 
                 # XXX If this is the master pid, print a newline.
-                print $OUT "\n";
+                print {$OUT} "\n";
             }
         } ## end while (($term || &setterm...
 
@@ -3300,62 +3107,7 @@ our standard filehandles for input and output.
 =cut
 
         continue {    # CMD:
-
-            # At the end of every command:
-            if ($piped) {
-
-                # Unhook the pipe mechanism now.
-                if ( $pager =~ /^\|/ ) {
-
-                    # No error from the child.
-                    $? = 0;
-
-                    # we cannot warn here: the handle is missing --tchrist
-                    close(OUT) || print SAVEOUT "\nCan't close DB::OUT\n";
-
-                    # most of the $? crud was coping with broken cshisms
-                    # $? is explicitly set to 0, so this never runs.
-                    if ($?) {
-                        print SAVEOUT "Pager '$pager' failed: ";
-                        if ( $? == -1 ) {
-                            print SAVEOUT "shell returned -1\n";
-                        }
-                        elsif ( $? >> 8 ) {
-                            print SAVEOUT ( $? & 127 )
-                              ? " (SIG#" . ( $? & 127 ) . ")"
-                              : "", ( $? & 128 ) ? " -- core dumped" : "", "\n";
-                        }
-                        else {
-                            print SAVEOUT "status ", ( $? >> 8 ), "\n";
-                        }
-                    } ## end if ($?)
-
-                    # Reopen filehandle for our output (if we can) and
-                    # restore STDOUT (if we can).
-                    open( OUT, ">&STDOUT" ) || &warn("Can't restore DB::OUT");
-                    open( STDOUT, ">&SAVEOUT" )
-                      || &warn("Can't restore STDOUT");
-
-                    # Turn off pipe exception handler if necessary.
-                    $SIG{PIPE} = "DEFAULT" if $SIG{PIPE} eq \&DB::catch;
-
-                    # Will stop ignoring SIGPIPE if done like nohup(1)
-                    # does SIGINT but Perl doesn't give us a choice.
-                } ## end if ($pager =~ /^\|/)
-                else {
-
-                    # Non-piped "pager". Just restore STDOUT.
-                    open( OUT, ">&SAVEOUT" ) || &warn("Can't restore DB::OUT");
-                }
-
-                # Close filehandle pager was using, restore the normal one
-                # if necessary,
-                close(SAVEOUT);
-                select($selected), $selected = "" unless $selected eq "";
-
-                # No pipes now.
-                $piped = "";
-            } ## end if ($piped)
+            _DB__at_end_of_every_command($obj);
         }    # CMD:
 
 =head3 COMMAND LOOP TERMINATION
@@ -3374,7 +3126,7 @@ again.
 
         # Evaluate post-prompt commands.
         foreach $evalarg (@$post) {
-            DB::eval(@_);
+            DB::eval();
         }
     }    # if ($single || $signal)
 
@@ -3405,7 +3157,9 @@ sub _init {
 
 {
     no strict 'refs';
-    foreach my $slot_name (qw(after explicit_stop infix position prefix)) {
+    foreach my $slot_name (qw(
+        after explicit_stop infix pat piped position prefix selected i_cmd
+        )) {
         my $slot = $slot_name;
         *{$slot} = sub {
             my $self = shift;
@@ -3432,195 +3186,774 @@ sub _DB_on_init__initialize_globals
 
     # Check for whether we should be running continuously or not.
     # _After_ the perl program is compiled, $single is set to 1:
-    if ( $DB::single and not $DB::second_time++ ) {
+    if ( $single and not $second_time++ ) {
 
         # Options say run non-stop. Run until we get an interrupt.
-        if ($DB::runnonstop) {    # Disable until signal
+        if ($runnonstop) {    # Disable until signal
                 # If there's any call stack in place, turn off single
                 # stepping into subs throughout the stack.
-            for my $i (0 .. $DB::stack_depth) {
-                $DB::stack[ $i ] &= ~1;
+            for my $i (0 .. $stack_depth) {
+                $stack[ $i ] &= ~1;
             }
 
             # And we are now no longer in single-step mode.
-            $DB::single = 0;
+            $single = 0;
 
             # If we simply returned at this point, we wouldn't get
             # the trace info. Fall on through.
             # return;
         } ## end if ($runnonstop)
 
-        elsif ($DB::ImmediateStop) {
+        elsif ($ImmediateStop) {
 
             # We are supposed to stop here; XXX probably a break.
-            $DB::ImmediateStop = 0;    # We've processed it; turn it off
-            $DB::signal        = 1;    # Simulate an interrupt to force
+            $ImmediateStop = 0;    # We've processed it; turn it off
+            $signal        = 1;    # Simulate an interrupt to force
                                    # us into the command loop
         }
     } ## end if ($single and not $second_time...
 
     # If we're in single-step mode, or an interrupt (real or fake)
     # has occurred, turn off non-stop mode.
-    $DB::runnonstop = 0 if $DB::single or $DB::signal;
+    $runnonstop = 0 if $single or $signal;
+
+    return;
+}
+
+sub _DB__handle_watch_expressions
+{
+    my $self = shift;
+
+    if ( $trace & 2 ) {
+        for my $n (0 .. $#to_watch) {
+            $evalarg = $to_watch[$n];
+            local $onetimeDump;    # Tell DB::eval() to not output results
+
+            # Fix context DB::eval() wants to return an array, but
+            # we need a scalar here.
+            my ($val) = join( "', '", DB::eval() );
+            $val = ( ( defined $val ) ? "'$val'" : 'undef' );
+
+            # Did it change?
+            if ( $val ne $old_watch[$n] ) {
+
+                # Yep! Show the difference, and fake an interrupt.
+                $signal = 1;
+                print {$OUT} <<EOP;
+Watchpoint $n:\t$to_watch[$n] changed:
+    old value:\t$old_watch[$n]
+    new value:\t$val
+EOP
+                $old_watch[$n] = $val;
+            } ## end if ($val ne $old_watch...
+        } ## end for my $n (0 ..
+    } ## end if ($trace & 2)
+
+    return;
+}
+
+sub _my_print_lineinfo
+{
+    my ($self, $i, $incr_pos) = @_;
+
+    if ($frame) {
+        # Print it indented if tracing is on.
+        DB::print_lineinfo( ' ' x $stack_depth,
+            "$i:\t$DB::dbline[$i]" . $self->after );
+    }
+    else {
+        DB::depth_print_lineinfo($self->explicit_stop, $incr_pos);
+    }
+}
+
+sub _curr_line {
+    return $DB::dbline[$line];
+}
+
+sub _DB__grab_control
+{
+    my $self = shift;
+
+    # Yes, grab control.
+    if ($slave_editor) {
+
+        # Tell the editor to update its position.
+        $self->position("\032\032${DB::filename}:$line:0\n");
+        DB::print_lineinfo($self->position());
+    }
+
+=pod
+
+Special check: if we're in package C<DB::fake>, we've gone through the
+C<END> block at least once. We set up everything so that we can continue
+to enter commands and have a valid context to be in.
+
+=cut
+
+    elsif ( $DB::package eq 'DB::fake' ) {
+
+        # Fallen off the end already.
+        if (!$DB::term) {
+            DB::setterm();
+        }
+
+        DB::print_help(<<EOP);
+Debugged program terminated.  Use B<q> to quit or B<R> to restart,
+use B<o> I<inhibit_exit> to avoid stopping after program termination,
+B<h q>, B<h R> or B<h o> to get additional info.
+EOP
+
+        # Set the DB::eval context appropriately.
+        $DB::package     = 'main';
+        $DB::usercontext = DB::_calc_usercontext($DB::package);
+    } ## end elsif ($package eq 'DB::fake')
+
+=pod
+
+If the program hasn't finished executing, we scan forward to the
+next executable line, print that out, build the prompt from the file and line
+number information, and print that.
+
+=cut
+
+    else {
+
+
+        # Still somewhere in the midst of execution. Set up the
+        #  debugger prompt.
+        $DB::sub =~ s/\'/::/;    # Swap Perl 4 package separators (') to
+                             # Perl 5 ones (sorry, we don't print Klingon
+                             #module names)
+
+        $self->prefix($DB::sub =~ /::/ ? "" : ($DB::package . '::'));
+        $self->append_to_prefix( "$DB::sub(${DB::filename}:" );
+        $self->after( $self->_curr_line =~ /\n$/ ? '' : "\n" );
+
+        # Break up the prompt if it's really long.
+        if ( length($self->prefix()) > 30 ) {
+            $self->position($self->prefix . "$line):\n$line:\t" . $self->_curr_line . $self->after);
+            $self->prefix("");
+            $self->infix(":\t");
+        }
+        else {
+            $self->infix("):\t");
+            $self->position(
+                $self->prefix . $line. $self->infix
+                . $self->_curr_line . $self->after
+            );
+        }
+
+        # Print current line info, indenting if necessary.
+        $self->_my_print_lineinfo($line, $self->position);
+
+        my $i;
+        my $line_i = sub { return $DB::dbline[$i]; };
+
+        # Scan forward, stopping at either the end or the next
+        # unbreakable line.
+        for ( $i = $line + 1 ; $i <= $DB::max && $line_i->() == 0 ; ++$i )
+        {    #{ vi
+
+            # Drop out on null statements, block closers, and comments.
+            last if $line_i->() =~ /^\s*[\;\}\#\n]/;
+
+            # Drop out if the user interrupted us.
+            last if $signal;
+
+            # Append a newline if the line doesn't have one. Can happen
+            # in eval'ed text, for instance.
+            $self->after( $line_i->() =~ /\n$/ ? '' : "\n" );
+
+            # Next executable line.
+            my $incr_pos = $self->prefix . $i . $self->infix . $line_i->()
+                . $self->after;
+            $self->append_to_position($incr_pos);
+            $self->_my_print_lineinfo($i, $incr_pos);
+        } ## end for ($i = $line + 1 ; $i...
+    } ## end else [ if ($slave_editor)
+
+    return;
+}
+
+sub _handle_t_command {
+    if (my ($levels) = $DB::cmd =~ /\At(?:\s+(\d+))?\z/) {
+        $trace ^= 1;
+        local $\ = '';
+        $DB::trace_to_depth = $levels ? $stack_depth + $levels : 1E9;
+        print {$OUT} "Trace = "
+        . ( ( $trace & 1 )
+            ? ( $levels ? "on (to level $DB::trace_to_depth)" : "on" )
+            : "off" ) . "\n";
+        next CMD;
+    }
+
+    return;
+}
+
+
+sub _handle_S_command {
+    if (my ($print_all_subs, $should_reverse, $Spatt)
+        = $DB::cmd =~ /\AS(\s+(!)?(.+))?\z/) {
+        # $Spatt is the pattern (if any) to use.
+        # Reverse scan?
+        my $Srev     = defined $should_reverse;
+        # No args - print all subs.
+        my $Snocheck = !defined $print_all_subs;
+
+        # Need to make these sane here.
+        local $\ = '';
+        local $, = '';
+
+        # Search through the debugger's magical hash of subs.
+        # If $nocheck is true, just print the sub name.
+        # Otherwise, check it against the pattern. We then use
+        # the XOR trick to reverse the condition as required.
+        foreach $subname ( sort( keys %sub ) ) {
+            if ( $Snocheck or $Srev ^ ( $subname =~ /$Spatt/ ) ) {
+                print $OUT $subname, "\n";
+            }
+        }
+        next CMD;
+    }
+
+    return;
+}
+
+sub _handle_V_command_and_X_command {
+
+    $DB::cmd =~ s/^X\b/V $DB::package/;
+
+    # Bare V commands get the currently-being-debugged package
+    # added.
+    if ($DB::cmd eq "V") {
+        $DB::cmd = "V $DB::package";
+    }
+
+    # V - show variables in package.
+    if (my ($new_packname, $new_vars_str) =
+        $DB::cmd =~ /\AV\b\s*(\S+)\s*(.*)/) {
+
+        # Save the currently selected filehandle and
+        # force output to debugger's filehandle (dumpvar
+        # just does "print" for output).
+        my $savout = select($OUT);
+
+        # Grab package name and variables to dump.
+        $packname = $new_packname;
+        my @vars     = split( ' ', $new_vars_str );
+
+        # If main::dumpvar isn't here, get it.
+        do 'dumpvar.pl' || die $@ unless defined &main::dumpvar;
+        if ( defined &main::dumpvar ) {
+
+            # We got it. Turn off subroutine entry/exit messages
+            # for the moment, along with return values.
+            local $frame = 0;
+            local $doret = -2;
+
+            # must detect sigpipe failures  - not catching
+            # then will cause the debugger to die.
+            eval {
+                &main::dumpvar(
+                    $packname,
+                    defined $option{dumpDepth}
+                    ? $option{dumpDepth}
+                    : -1,    # assume -1 unless specified
+                    @vars
+                );
+            };
+
+            # The die doesn't need to include the $@, because
+            # it will automatically get propagated for us.
+            if ($@) {
+                die unless $@ =~ /dumpvar print failed/;
+            }
+        } ## end if (defined &main::dumpvar)
+        else {
+
+            # Couldn't load dumpvar.
+            print $OUT "dumpvar.pl not available.\n";
+        }
+
+        # Restore the output filehandle, and go round again.
+        select($savout);
+        next CMD;
+    }
+
+    return;
+}
+
+sub _handle_dash_command {
+
+    if ($DB::cmd eq '-') {
+
+        # back up by a window; go to 1 if back too far.
+        $start -= $incr + $window + 1;
+        $start = 1 if $start <= 0;
+        $incr  = $window - 1;
+
+        # Generate and execute a "l +" command (handled below).
+        $DB::cmd = 'l ' . ($start) . '+';
+    }
+    return;
+}
+
+sub _n_or_s_commands_generic {
+    my ($self, $new_val) = @_;
+    # n - next
+    next CMD if DB::_DB__is_finished();
+
+    # Single step, but don't enter subs.
+    $single = $new_val;
+
+    # Save for empty command (repeat last).
+    $laststep = $DB::cmd;
+    last CMD;
+}
+
+sub _n_or_s {
+    my ($self, $letter, $new_val) = @_;
+
+    if ($DB::cmd eq $letter) {
+        $self->_n_or_s_commands_generic($new_val);
+    }
+    elsif ($DB::cmd =~ m#\A\Q$letter\E\b#) {
+        $self->_n_or_s_and_arg_commands_generic($letter, $new_val);
+    }
+
+    return;
+}
+
+sub _handle_n_command {
+    my $self = shift;
+
+    return $self->_n_or_s('n', 2);
+}
+
+sub _handle_s_command {
+    my $self = shift;
+
+    return $self->_n_or_s('s', 1);
+}
+
+sub _handle_r_command {
+    my $self = shift;
+    # r - return from the current subroutine.
+    if ($DB::cmd eq 'r') {
+
+        # Can't do anything if the program's over.
+        next CMD if DB::_DB__is_finished();
+
+        # Turn on stack trace.
+        $stack[$stack_depth] |= 1;
+
+        # Print return value unless the stack is empty.
+        $doret = $option{PrintRet} ? $stack_depth - 1 : -2;
+        last CMD;
+    }
+
+    return;
+}
+
+sub _handle_T_command {
+    if ($DB::cmd eq 'T') {
+        DB::print_trace( $OUT, 1 );    # skip DB
+        next CMD;
+    }
+
+    return;
+}
+
+sub _handle_w_command {
+    if (my ($arg) = $DB::cmd =~ /\Aw\b\s*(.*)/s) {
+        DB::cmd_w( 'w', $arg );
+        next CMD;
+    }
+
+    return;
+}
+
+sub _handle_W_command {
+    if (my ($arg) = $DB::cmd =~ /\AW\b\s*(.*)/s) {
+        DB::cmd_W( 'W', $arg );
+        next CMD;
+    }
+
+    return;
+}
+
+sub _handle_rc_recall_command {
+    my $self = shift;
+
+    # $rc - recall command.
+    if (my ($minus, $arg) = $DB::cmd =~ m#\A$rc+\s*(-)?(\d+)?\z#) {
+
+        # No arguments, take one thing off history.
+        pop(@hist) if length($DB::cmd) > 1;
+
+        # Relative (- found)?
+        #  Y - index back from most recent (by 1 if bare minus)
+        #  N - go to that particular command slot or the last
+        #      thing if nothing following.
+        my $new_i = $minus ? ( $#hist - ( $arg || 1 ) ) : ( $arg || $#hist );
+
+        $self->i_cmd($new_i);
+
+        # Pick out the command desired.
+        $DB::cmd = $hist[$self->i_cmd];
+
+        # Print the command to be executed and restart the loop
+        # with that command in the buffer.
+        print {$OUT} $DB::cmd, "\n";
+        redo CMD;
+    }
+
+    return;
+}
+
+sub _handle_rc_search_history_command {
+    my $self = shift;
+
+    # $rc pattern $rc - find a command in the history.
+    if (my ($arg) = $DB::cmd =~ /\A$rc([^$rc].*)\z/) {
+
+        # Create the pattern to use.
+        my $pat = "^$arg";
+        $self->pat($pat);
+
+        # Toss off last entry if length is >1 (and it always is).
+        pop(@hist) if length($DB::cmd) > 1;
+
+        my $i = $self->i_cmd;
+
+        # Look backward through the history.
+        SEARCH_HIST:
+        for ( $i = $#hist ; $i ; --$i ) {
+            # Stop if we find it.
+            last SEARCH_HIST if $hist[$i] =~ /$pat/;
+        }
+
+        $self->i_cmd($i);
+
+        if ( !$self->i_cmd ) {
+
+            # Never found it.
+            print $OUT "No such command!\n\n";
+            next CMD;
+        }
+
+        # Found it. Put it in the buffer, print it, and process it.
+        $DB::cmd = $hist[$self->i_cmd];
+        print $OUT $DB::cmd, "\n";
+        redo CMD;
+    }
+
+    return;
+}
+
+sub _handle_H_command {
+    my $self = shift;
+
+    if ($DB::cmd =~ /\AH\b\s*\*/) {
+        @hist = @truehist = ();
+        print $OUT "History cleansed\n";
+        next CMD;
+    }
+
+    if (my ($num)
+        = $DB::cmd =~ /\AH\b\s*(?:-(\d+))?/) {
+
+        # Anything other than negative numbers is ignored by
+        # the (incorrect) pattern, so this test does nothing.
+        $end = $num ? ( $#hist - $num ) : 0;
+
+        # Set to the minimum if less than zero.
+        $hist = 0 if $hist < 0;
+
+        # Start at the end of the array.
+        # Stay in while we're still above the ending value.
+        # Tick back by one each time around the loop.
+        my $i;
+
+        for ( $i = $#hist ; $i > $end ; $i-- ) {
+
+            # Print the command  unless it has no arguments.
+            print $OUT "$i: ", $hist[$i], "\n"
+            unless $hist[$i] =~ /^.?$/;
+        }
+
+        $self->i_cmd($i);
+
+        next CMD;
+    }
+
+    return;
+}
+
+sub _handle_doc_command {
+    my $self = shift;
+
+    # man, perldoc, doc - show manual pages.
+    if (my ($man_page)
+        = $DB::cmd =~ /\A(?:man|(?:perl)?doc)\b(?:\s+([^(]*))?\z/) {
+        runman($man_page);
+        next CMD;
+    }
+
+    return;
+}
+
+sub _handle_p_command {
+    my $self = shift;
+
+    my $print_cmd = 'print {$DB::OUT} ';
+    # p - print (no args): print $_.
+    if ($DB::cmd eq 'p') {
+        $DB::cmd = $print_cmd . '$_';
+    }
+
+    # p - print the given expression.
+    $DB::cmd =~ s/\Ap\b/$print_cmd /;
 
     return;
 }
 
-sub _DB__handle_watch_expressions
-{
+sub _handle_equal_sign_command {
     my $self = shift;
 
-    if ( $DB::trace & 2 ) {
-        for my $n (0 .. $#DB::to_watch) {
-            $DB::evalarg = $DB::to_watch[$n];
-            local $DB::onetimeDump;    # Tell DB::eval() to not output results
+    if ($DB::cmd =~ s/\A=\s*//) {
+        my @keys;
+        if ( length $DB::cmd == 0 ) {
 
-            # Fix context DB::eval() wants to return an array, but
-            # we need a scalar here.
-            my ($val) = join( "', '", DB::eval(@_) );
-            $val = ( ( defined $val ) ? "'$val'" : 'undef' );
+            # No args, get current aliases.
+            @keys = sort keys %alias;
+        }
+        elsif ( my ( $k, $v ) = ( $DB::cmd =~ /^(\S+)\s+(\S.*)/ ) ) {
 
-            # Did it change?
-            if ( $val ne $DB::old_watch[$n] ) {
+            # Creating a new alias. $k is alias name, $v is
+            # alias value.
 
-                # Yep! Show the difference, and fake an interrupt.
-                $DB::signal = 1;
-                print {$DB::OUT} <<EOP;
-Watchpoint $n:\t$DB::to_watch[$n] changed:
-    old value:\t$DB::old_watch[$n]
-    new value:\t$val
-EOP
-                $DB::old_watch[$n] = $val;
-            } ## end if ($val ne $old_watch...
-        } ## end for my $n (0 ..
-    } ## end if ($trace & 2)
+            # can't use $_ or kill //g state
+            for my $x ( $k, $v ) {
 
-    return;
-}
+                # Escape "alarm" characters.
+                $x =~ s/\a/\\a/g;
+            }
 
-sub _my_print_lineinfo
-{
-    my ($self, $i, $incr_pos) = @_;
+            # Substitute key for value, using alarm chars
+            # as separators (which is why we escaped them in
+            # the command).
+            $alias{$k} = "s\a$k\a$v\a";
 
-    if ($DB::frame) {
-        # Print it indented if tracing is on.
-        DB::print_lineinfo( ' ' x $DB::stack_depth,
-            "$i:\t$DB::dbline[$i]" . $self->after );
-    }
-    else {
-        DB::depth_print_lineinfo($self->explicit_stop, $incr_pos);
+            # Turn off standard warn and die behavior.
+            local $SIG{__DIE__};
+            local $SIG{__WARN__};
+
+            # Is it valid Perl?
+            unless ( eval "sub { s\a$k\a$v\a }; 1" ) {
+
+                # Nope. Bad alias. Say so and get out.
+                print $OUT "Can't alias $k to $v: $@\n";
+                delete $alias{$k};
+                next CMD;
+            }
+
+            # We'll only list the new one.
+            @keys = ($k);
+        } ## end elsif (my ($k, $v) = ($DB::cmd...
+
+        # The argument is the alias to list.
+        else {
+            @keys = ($DB::cmd);
+        }
+
+        # List aliases.
+        for my $k (@keys) {
+
+            # Messy metaquoting: Trim the substitution code off.
+            # We use control-G as the delimiter because it's not
+            # likely to appear in the alias.
+            if ( ( my $v = $alias{$k} ) =~ s\as\a$k\a(.*)\a$\a1\a ) {
+
+                # Print the alias.
+                print $OUT "$k\t= $1\n";
+            }
+            elsif ( defined $alias{$k} ) {
+
+                # Couldn't trim it off; just print the alias code.
+                print $OUT "$k\t$alias{$k}\n";
+            }
+            else {
+
+                # No such, dude.
+                print "No alias for $k\n";
+            }
+        } ## end for my $k (@keys)
+        next CMD;
     }
-}
 
-sub _curr_line {
-    return $DB::dbline[$DB::line];
+    return;
 }
 
-sub _DB__grab_control
-{
+sub _handle_source_command {
     my $self = shift;
 
-    # Yes, grab control.
-    if ($DB::slave_editor) {
+    # source - read commands from a file (or pipe!) and execute.
+    if (my ($sourced_fn) = $DB::cmd =~ /\Asource\s+(.*\S)/) {
+        if ( open my $fh, $sourced_fn ) {
 
-        # Tell the editor to update its position.
-        $self->position("\032\032${DB::filename}:${DB::line}:0\n");
-        DB::print_lineinfo($self->position());
+            # Opened OK; stick it in the list of file handles.
+            push @cmdfhs, $fh;
+        }
+        else {
+
+            # Couldn't open it.
+            DB::warn("Can't execute '$sourced_fn': $!\n");
+        }
+        next CMD;
     }
 
-=pod
+    return;
+}
 
-Special check: if we're in package C<DB::fake>, we've gone through the
-C<END> block at least once. We set up everything so that we can continue
-to enter commands and have a valid context to be in.
+sub _handle_enable_disable_commands {
+    my $self = shift;
 
-=cut
+    if (my ($which_cmd, $position)
+        = $DB::cmd =~ /\A(enable|disable)\s+(\S+)\s*\z/) {
 
-    elsif ( $DB::package eq 'DB::fake' ) {
+        my ($fn, $line_num);
+        if ($position =~ m{\A\d+\z})
+        {
+            $fn = $DB::filename;
+            $line_num = $position;
+        }
+        elsif (my ($new_fn, $new_line_num)
+            = $position =~ m{\A(.*):(\d+)\z}) {
+            ($fn, $line_num) = ($new_fn, $new_line_num);
+        }
+        else
+        {
+            DB::warn("Wrong spec for enable/disable argument.\n");
+        }
 
-        # Fallen off the end already.
-        if (!$DB::term) {
-            DB::setterm();
+        if (defined($fn)) {
+            if (DB::_has_breakpoint_data_ref($fn, $line_num)) {
+                DB::_set_breakpoint_enabled_status($fn, $line_num,
+                    ($which_cmd eq 'enable' ? 1 : '')
+                );
+            }
+            else {
+                DB::warn("No breakpoint set at ${fn}:${line_num}\n");
+            }
         }
 
-        DB::print_help(<<EOP);
-Debugged program terminated.  Use B<q> to quit or B<R> to restart,
-use B<o> I<inhibit_exit> to avoid stopping after program termination,
-B<h q>, B<h R> or B<h o> to get additional info.
-EOP
+        next CMD;
+    }
 
-        # Set the DB::eval context appropriately.
-        $DB::package     = 'main';
-        $DB::usercontext = DB::_calc_usercontext($DB::package);
-    } ## end elsif ($package eq 'DB::fake')
+    return;
+}
 
-=pod
+sub _handle_save_command {
+    my $self = shift;
 
-If the program hasn't finished executing, we scan forward to the
-next executable line, print that out, build the prompt from the file and line
-number information, and print that.
+    if (my ($new_fn) = $DB::cmd =~ /\Asave\s*(.*)\z/) {
+        my $filename = $new_fn || '.perl5dbrc';    # default?
+        if ( open my $fh, '>', $filename ) {
 
-=cut
+            # chomp to remove extraneous newlines from source'd files
+            chomp( my @truelist =
+                map { m/\A\s*(save|source)/ ? "#$_" : $_ }
+                @truehist );
+            print {$fh} join( "\n", @truelist );
+            print "commands saved in $filename\n";
+        }
+        else {
+            DB::warn("Can't save debugger commands in '$new_fn': $!\n");
+        }
+        next CMD;
+    }
 
-    else {
+    return;
+}
 
+sub _n_or_s_and_arg_commands_generic {
+    my ($self, $letter, $new_val) = @_;
 
-        # Still somewhere in the midst of execution. Set up the
-        #  debugger prompt.
-        $DB::sub =~ s/\'/::/;    # Swap Perl 4 package separators (') to
-                             # Perl 5 ones (sorry, we don't print Klingon
-                             #module names)
+    # s - single-step. Remember the last command was 's'.
+    if ($DB::cmd =~ s#\A\Q$letter\E\s#\$DB::single = $new_val;\n#) {
+        $laststep = $letter;
+    }
 
-        $self->prefix($DB::sub =~ /::/ ? "" : ($DB::package . '::'));
-        $self->append_to_prefix( "$DB::sub(${DB::filename}:" );
-        $self->after( $self->_curr_line =~ /\n$/ ? '' : "\n" );
+    return;
+}
 
-        # Break up the prompt if it's really long.
-        if ( length($self->prefix()) > 30 ) {
-            $self->position($self->prefix . "$DB::line):\n$DB::line:\t" . $self->_curr_line . $self->after);
-            $self->prefix("");
-            $self->infix(":\t");
+sub _handle_sh_command {
+    my $self = shift;
+
+    # $sh$sh - run a shell command (if it's all ASCII).
+    # Can't run shell commands with Unicode in the debugger, hmm.
+    my $my_cmd = $DB::cmd;
+    if ($my_cmd =~ m#\A$sh#gms) {
+
+        if ($my_cmd =~ m#\G\z#cgms) {
+            # Run the user's shell. If none defined, run Bourne.
+            # We resume execution when the shell terminates.
+            DB::system( $ENV{SHELL} || "/bin/sh" );
+            next CMD;
         }
-        else {
-            $self->infix("):\t");
-            $self->position(
-                $self->prefix . $DB::line. $self->infix
-                . $self->_curr_line . $self->after
-            );
+        elsif ($my_cmd =~ m#\G$sh\s*(.*)#cgms) {
+            # System it.
+            DB::system($1);
+            next CMD;
         }
+        elsif ($my_cmd =~ m#\G\s*(.*)#cgms) {
+            DB::system( $ENV{SHELL} || "/bin/sh", "-c", $1 );
+            next CMD;
+        }
+    }
+}
 
-        # Print current line info, indenting if necessary.
-        $self->_my_print_lineinfo($DB::line, $self->position);
-
-        my $i;
-        my $line_i = sub { return $DB::dbline[$i]; };
+sub _handle_x_command {
+    my $self = shift;
 
-        # Scan forward, stopping at either the end or the next
-        # unbreakable line.
-        for ( $i = $DB::line + 1 ; $i <= $DB::max && $line_i->() == 0 ; ++$i )
-        {    #{ vi
+    if ($DB::cmd =~ s#\Ax\b# #) {    # Remainder gets done by DB::eval()
+        $onetimeDump = 'dump';    # main::dumpvar shows the output
 
-            # Drop out on null statements, block closers, and comments.
-            last if $line_i->() =~ /^\s*[\;\}\#\n]/;
+        # handle special  "x 3 blah" syntax XXX propagate
+        # doc back to special variables.
+        if ( $DB::cmd =~ s#\A\s*(\d+)(?=\s)# #) {
+            $onetimedumpDepth = $1;
+        }
+    }
 
-            # Drop out if the user interrupted us.
-            last if $DB::signal;
+    return;
+}
 
-            # Append a newline if the line doesn't have one. Can happen
-            # in eval'ed text, for instance.
-            $self->after( $line_i->() =~ /\n$/ ? '' : "\n" );
+sub _handle_q_command {
+    my $self = shift;
 
-            # Next executable line.
-            my $incr_pos = $self->prefix . $i . $self->infix . $line_i->()
-                . $self->after;
-            $self->append_to_position($incr_pos);
-            $self->_my_print_lineinfo($i, $incr_pos);
-        } ## end for ($i = $line + 1 ; $i...
-    } ## end else [ if ($slave_editor)
+    if ($DB::cmd eq 'q') {
+        $fall_off_end = 1;
+        DB::clean_ENV();
+        exit $?;
+    }
 
     return;
 }
 
+sub _handle_cmd_wrapper_commands {
+    my $self = shift;
+
+    # All of these commands were remapped in perl 5.8.0;
+    # we send them off to the secondary dispatcher (see below).
+    if (my ($cmd_letter, $my_arg) = $DB::cmd =~ /\A([aAbBeEhilLMoOPvwW]\b|[<>\{]{1,2})\s*(.*)/so) {
+        DB::cmd_wrapper( $cmd_letter, $my_arg, $line );
+        next CMD;
+    }
+
+    return;
+}
 package DB;
 
 # The following code may be executed now:
@@ -3711,19 +4044,19 @@ use vars qw($deep);
 # We need to fully qualify the name ("DB::sub") to make "use strict;"
 # happy. -- Shlomi Fish
 sub DB::sub {
-       # Do not use a regex in this subroutine -> results in corrupted memory
-       # See: [perl #66110]
+    # Do not use a regex in this subroutine -> results in corrupted memory
+    # See: [perl #66110]
 
-       # lock ourselves under threads
-       lock($DBGR);
+    # lock ourselves under threads
+    lock($DBGR);
 
     # Whether or not the autoloader was running, a scalar to put the
     # sub's return value in (if needed), and an array to put the sub's
     # return value in (if needed).
     my ( $al, $ret, @ret ) = "";
-       if ($sub eq 'threads::new' && $ENV{PERL5DB_THREADED}) {
-               print "creating new thread\n";
-       }
+    if ($sub eq 'threads::new' && $ENV{PERL5DB_THREADED}) {
+        print "creating new thread\n";
+    }
 
     # If the last ten characters are '::AUTOLOAD', note we've traced
     # into AUTOLOAD for $sub.
@@ -3819,17 +4152,17 @@ sub DB::sub {
 
     # Scalar context.
     else {
-       if ( defined wantarray ) {
-        no strict 'refs';
-           # Save the value if it's wanted at all.
-           $ret = &$sub;
-       }
-       else {
-        no strict 'refs';
-           # Void return, explicitly.
-           &$sub;
-           undef $ret;
-       }
+        if ( defined wantarray ) {
+            no strict 'refs';
+            # Save the value if it's wanted at all.
+            $ret = &$sub;
+        }
+        else {
+            no strict 'refs';
+            # Void return, explicitly.
+            &$sub;
+            undef $ret;
+        }
 
         # Pop the single-step value off the stack.
         $single |= $stack[ $stack_depth-- ];
@@ -3870,16 +4203,16 @@ sub lsub : lvalue {
 
     no strict 'refs';
 
-       # lock ourselves under threads
-       lock($DBGR);
+    # lock ourselves under threads
+    lock($DBGR);
 
     # Whether or not the autoloader was running, a scalar to put the
     # sub's return value in (if needed), and an array to put the sub's
     # return value in (if needed).
     my ( $al, $ret, @ret ) = "";
-       if ($sub =~ /^threads::new$/ && $ENV{PERL5DB_THREADED}) {
-               print "creating new thread\n";
-       }
+    if ($sub =~ /^threads::new$/ && $ENV{PERL5DB_THREADED}) {
+        print "creating new thread\n";
+    }
 
     # If the last ten characters are C'::AUTOLOAD', note we've traced
     # into AUTOLOAD for $sub.
@@ -4089,7 +4422,7 @@ sub cmd_wrapper {
     # default to the older version of the command.
     my $call = 'cmd_'
       . ( $set{$CommandSet}{$cmd}
-          || ( $cmd =~ /^[<>{]+/o ? 'prepost' : $cmd ) );
+          || ( $cmd =~ /\A[<>{]+/o ? 'prepost' : $cmd ) );
 
     # Call the command subroutine, call it by name.
     return __PACKAGE__->can($call)->( $cmd, $line, $dblineno );
@@ -4109,11 +4442,14 @@ sub cmd_a {
     my $dbline = shift;
 
     # If it's dot (here), or not all digits,  use the current line.
-    $line =~ s/^(\.|(?:[^\d]))/$dbline/;
+    $line =~ s/\A\./$dbline/;
 
     # Should be a line number followed by an expression.
-    if ( $line =~ /^\s*(\d*)\s*(\S.+)/ ) {
-        my ( $lineno, $expr ) = ( $1, $2 );
+    if ( my ($lineno, $expr) = $line =~ /^\s*(\d*)\s*(\S.+)/ ) {
+
+        if (! length($lineno)) {
+            $lineno = $dbline;
+        }
 
         # If we have an expression ...
         if ( length $expr ) {
@@ -4167,13 +4503,19 @@ sub cmd_A {
     # if delete_action blows up for some reason, in which case
     # we print $@ and get out.
     if ( $line eq '*' ) {
-        eval { &delete_action(); 1 } or print $OUT $@ and return;
+        if (! eval { _delete_all_actions(); 1 }) {
+            print {$OUT} $@;
+            return;
+        }
     }
 
     # There's a real line  number. Pass it to delete_action.
     # Error trapping is as above.
     elsif ( $line =~ /^(\S.*)/ ) {
-        eval { &delete_action($1); 1 } or print $OUT $@ and return;
+        if (! eval { delete_action($1); 1 }) {
+            print {$OUT} $@;
+            return;
+        }
     }
 
     # Swing and a miss. Bad syntax.
@@ -4192,35 +4534,50 @@ will get any kind of an action, including breakpoints).
 
 =cut
 
+sub _remove_action_from_dbline {
+    my $i = shift;
+
+    $dbline{$i} =~ s/\0[^\0]*//;    # \^a
+    delete $dbline{$i} if $dbline{$i} eq '';
+
+    return;
+}
+
+sub _delete_all_actions {
+    print {$OUT} "Deleting all actions...\n";
+
+    for my $file ( keys %had_breakpoints ) {
+        local *dbline = $main::{ '_<' . $file };
+        $max = $#dbline;
+        my $was;
+        for my $i (1 .. $max) {
+            if ( defined $dbline{$i} ) {
+                _remove_action_from_dbline($i);
+            }
+        }
+
+        unless ( $had_breakpoints{$file} &= ~2 ) {
+            delete $had_breakpoints{$file};
+        }
+    }
+
+    return;
+}
+
 sub delete_action {
     my $i = shift;
-    if ( defined($i) ) {
 
+    if ( defined($i) ) {
         # Can there be one?
         die "Line $i has no action .\n" if $dbline[$i] == 0;
 
         # Nuke whatever's there.
-        $dbline{$i} =~ s/\0[^\0]*//;    # \^a
-        delete $dbline{$i} if $dbline{$i} eq '';
+        _remove_action_from_dbline($i);
     }
     else {
-        print $OUT "Deleting all actions...\n";
-        for my $file ( keys %had_breakpoints ) {
-            local *dbline = $main::{ '_<' . $file };
-            $max = $#dbline;
-            my $was;
-            for $i (1 .. $max) {
-                if ( defined $dbline{$i} ) {
-                    $dbline{$i} =~ s/\0[^\0]*//;
-                    delete $dbline{$i} if $dbline{$i} eq '';
-                }
-                unless ( $had_breakpoints{$file} &= ~2 ) {
-                    delete $had_breakpoints{$file};
-                }
-            } ## end for ($i = 1 .. $max)
-        } ## end for my $file (keys %had_breakpoints)
-    } ## end else [ if (defined($i))
-} ## end sub delete_action
+        _delete_all_actions();
+    }
+}
 
 =head3 C<cmd_b> (command)
 
@@ -4237,35 +4594,33 @@ sub cmd_b {
     my $line   = shift;    # [.|line] [cond]
     my $dbline = shift;
 
+    my $default_cond = sub {
+        my $cond = shift;
+        return length($cond) ? $cond : '1';
+    };
+
     # Make . the current line number if it's there..
     $line =~ s/^\.(\s|\z)/$dbline$1/;
 
     # No line number, no condition. Simple break on current line.
     if ( $line =~ /^\s*$/ ) {
-        &cmd_b_line( $dbline, 1 );
+        cmd_b_line( $dbline, 1 );
     }
 
     # Break on load for a file.
-    elsif ( $line =~ /^load\b\s*(.*)/ ) {
-        my $file = $1;
-        $file =~ s/\s+$//;
-        &cmd_b_load($file);
+    elsif ( my ($file) = $line =~ /^load\b\s*(.*)/ ) {
+        $file =~ s/\s+\z//;
+        cmd_b_load($file);
     }
 
     # b compile|postpone <some sub> [<condition>]
     # The interpreter actually traps this one for us; we just put the
     # necessary condition in the %postponed hash.
-    elsif ( $line =~ /^(postpone|compile)\b\s*([':A-Za-z_][':\w]*)\s*(.*)/ ) {
-
-        # Capture the condition if there is one. Make it true if none.
-        my $cond = length $3 ? $3 : '1';
-
-        # Save the sub name and set $break to 1 if $1 was 'postpone', 0
-        # if it was 'compile'.
-        my ( $subname, $break ) = ( $2, $1 eq 'postpone' );
+    elsif ( my ($action, $subname, $cond)
+        = $line =~ /^(postpone|compile)\b\s*([':A-Za-z_][':\w]*)\s*(.*)/ ) {
 
         # De-Perl4-ify the name - ' separators to ::.
-        $subname =~ s/\'/::/g;
+        $subname =~ s/'/::/g;
 
         # Qualify it into the current package unless it's already qualified.
         $subname = "${package}::" . $subname unless $subname =~ /::/;
@@ -4274,11 +4629,13 @@ sub cmd_b {
         $subname = "main" . $subname if substr( $subname, 0, 2 ) eq "::";
 
         # Save the break type for this sub.
-        $postponed{$subname} = $break ? "break +0 if $cond" : "compile";
+        $postponed{$subname} = (($action eq 'postpone')
+            ? ( "break +0 if " . $default_cond->($cond) )
+            : "compile");
     } ## end elsif ($line =~ ...
     # b <filename>:<line> [<condition>]
-    elsif ($line =~ /\A(\S+[^:]):(\d+)\s*(.*)/ms) {
-        my ($filename, $line_num, $cond) = ($1, $2, $3);
+    elsif (my ($filename, $line_num, $cond)
+        = $line =~ /\A(\S+[^:]):(\d+)\s*(.*)/ms) {
         cmd_b_filename_line(
             $filename,
             $line_num,
@@ -4286,31 +4643,30 @@ sub cmd_b {
         );
     }
     # b <sub name> [<condition>]
-    elsif ( $line =~ /^([':A-Za-z_][':\w]*(?:\[.*\])?)\s*(.*)/ ) {
+    elsif ( my ($new_subname, $new_cond) =
+        $line =~ /^([':A-Za-z_][':\w]*(?:\[.*\])?)\s*(.*)/ ) {
 
         #
-        $subname = $1;
-        my $cond = length $2 ? $2 : '1';
-        &cmd_b_sub( $subname, $cond );
+        $subname = $new_subname;
+        cmd_b_sub( $subname, $default_cond->($new_cond) );
     }
 
     # b <line> [<condition>].
-    elsif ( $line =~ /^(\d*)\s*(.*)/ ) {
+    elsif ( my ($line_n, $cond) = $line =~ /^(\d*)\s*(.*)/ ) {
 
         # Capture the line. If none, it's the current line.
-        $line = $1 || $dbline;
-
-        # If there's no condition, make it '1'.
-        my $cond = length $2 ? $2 : '1';
+        $line = $line_n || $dbline;
 
         # Break on line.
-        &cmd_b_line( $line, $cond );
+        cmd_b_line( $line, $default_cond->($cond) );
     }
 
     # Line didn't make sense.
     else {
         print "confused by line($line)?\n";
     }
+
+    return;
 } ## end sub cmd_b
 
 =head3 C<break_on_load> (API)
@@ -4921,7 +5277,7 @@ sub cmd_stop {    # As on ^C, but not signal-safy.
 
 Display the current thread id:
 
-       e
+    e
 
 This could be how (when implemented) to send commands to this thread id (e cmd)
 or that thread id (e tid cmd).
@@ -4931,20 +5287,20 @@ or that thread id (e tid cmd).
 sub cmd_e {
     my $cmd  = shift;
     my $line = shift;
-       unless (exists($INC{'threads.pm'})) {
-               print "threads not loaded($ENV{PERL5DB_THREADED})
-               please run the debugger with PERL5DB_THREADED=1 set in the environment\n";
-       } else {
-               my $tid = threads->tid;
-               print "thread id: $tid\n";
-       }
+    unless (exists($INC{'threads.pm'})) {
+        print "threads not loaded($ENV{PERL5DB_THREADED})
+        please run the debugger with PERL5DB_THREADED=1 set in the environment\n";
+    } else {
+        my $tid = threads->tid;
+        print "thread id: $tid\n";
+    }
 } ## end sub cmd_e
 
 =head3 C<cmd_E> - list of thread ids
 
 Display the list of available thread ids:
 
-       E
+    E
 
 This could be used (when implemented) to send commands to all threads (E cmd).
 
@@ -4953,15 +5309,15 @@ This could be used (when implemented) to send commands to all threads (E cmd).
 sub cmd_E {
     my $cmd  = shift;
     my $line = shift;
-       unless (exists($INC{'threads.pm'})) {
-               print "threads not loaded($ENV{PERL5DB_THREADED})
-               please run the debugger with PERL5DB_THREADED=1 set in the environment\n";
-       } else {
-               my $tid = threads->tid;
-               print "thread ids: ".join(', ',
-                       map { ($tid == $_->tid ? '<'.$_->tid.'>' : $_->tid) } threads->list
-               )."\n";
-       }
+    unless (exists($INC{'threads.pm'})) {
+        print "threads not loaded($ENV{PERL5DB_THREADED})
+        please run the debugger with PERL5DB_THREADED=1 set in the environment\n";
+    } else {
+        my $tid = threads->tid;
+        print "thread ids: ".join(', ',
+            map { ($tid == $_->tid ? '<'.$_->tid.'>' : $_->tid) } threads->list
+        )."\n";
+    }
 } ## end sub cmd_E
 
 =head3 C<cmd_h> - help command (command)
@@ -5055,7 +5411,7 @@ sub cmd_i {
     my $line = shift;
     foreach my $isa ( split( /\s+/, $line ) ) {
         $evalarg = $isa;
-        ($isa) = DB::eval(@_);
+        ($isa) = DB::eval();
         no strict 'refs';
         print join(
             ', ',
@@ -5100,7 +5456,7 @@ sub cmd_l {
         # Set up for DB::eval() - evaluate in *user* context.
         $evalarg = $1;
         # $evalarg = $2;
-        my ($s) = DB::eval(@_);
+        my ($s) = DB::eval();
 
         # Ooops. Bad scalar.
         if ($@) {
@@ -5493,6 +5849,28 @@ of any of the expressions changes.
 
 =cut
 
+sub _add_watch_expr {
+    my $expr = shift;
+
+    # ... save it.
+    push @to_watch, $expr;
+
+    # Parameterize DB::eval and call it to get the expression's value
+    # in the user's context. This version can handle expressions which
+    # return a list value.
+    $evalarg = $expr;
+    my ($val) = join( ' ', DB::eval() );
+    $val = ( defined $val ) ? "'$val'" : 'undef';
+
+    # Save the current value of the expression.
+    push @old_watch, $val;
+
+    # We are now watching expressions.
+    $trace |= 2;
+
+    return;
+}
+
 sub cmd_w {
     my $cmd = shift;
 
@@ -5500,30 +5878,17 @@ sub cmd_w {
     my $expr = shift || '';
 
     # If expression is not null ...
-    if ( $expr =~ /^(\S.*)/ ) {
-
-        # ... save it.
-        push @to_watch, $expr;
-
-        # Parameterize DB::eval and call it to get the expression's value
-        # in the user's context. This version can handle expressions which
-        # return a list value.
-        $evalarg = $expr;
-        my ($val) = join( ' ', DB::eval(@_) );
-        $val = ( defined $val ) ? "'$val'" : 'undef';
-
-        # Save the current value of the expression.
-        push @old_watch, $val;
-
-        # We are now watching expressions.
-        $trace |= 2;
+    if ( $expr =~ /\A\S/ ) {
+        _add_watch_expr($expr);
     } ## end if ($expr =~ /^(\S.*)/)
 
     # You have to give one to get one.
     else {
         print $OUT "Adding a watch-expression requires an expression\n";  # hint
     }
-} ## end sub cmd_w
+
+    return;
+}
 
 =head3 C<cmd_W> - delete watch expressions (command)
 
@@ -5930,7 +6295,7 @@ sub print_trace {
         $file = $file eq '-e' ? $file : "file '$file'" unless $short;
 
         # Get the actual sub's name, and shorten to $maxtrace's requirement.
-        $s = $sub[$i]{sub};
+        $s = $sub[$i]{'sub'};
         $s = ( substr $s, 0, $maxtrace - 3 ) . '...' if length $s > $maxtrace;
 
         # Short report uses trimmed file and sub names.
@@ -6190,24 +6555,24 @@ sub system {
 
     # We save, change, then restore STDIN and STDOUT to avoid fork() since
     # some non-Unix systems can do system() but have problems with fork().
-    open( SAVEIN,  "<&STDIN" )  || &warn("Can't save STDIN");
-    open( SAVEOUT, ">&STDOUT" ) || &warn("Can't save STDOUT");
-    open( STDIN,   "<&IN" )     || &warn("Can't redirect STDIN");
-    open( STDOUT,  ">&OUT" )    || &warn("Can't redirect STDOUT");
+    open( SAVEIN,  "<&STDIN" )  || DB::warn("Can't save STDIN");
+    open( SAVEOUT, ">&STDOUT" ) || DB::warn("Can't save STDOUT");
+    open( STDIN,   "<&IN" )     || DB::warn("Can't redirect STDIN");
+    open( STDOUT,  ">&OUT" )    || DB::warn("Can't redirect STDOUT");
 
     # XXX: using csh or tcsh destroys sigint retvals!
     system(@_);
-    open( STDIN,  "<&SAVEIN" )  || &warn("Can't restore STDIN");
-    open( STDOUT, ">&SAVEOUT" ) || &warn("Can't restore STDOUT");
+    open( STDIN,  "<&SAVEIN" )  || DB::warn("Can't restore STDIN");
+    open( STDOUT, ">&SAVEOUT" ) || DB::warn("Can't restore STDOUT");
     close(SAVEIN);
     close(SAVEOUT);
 
     # most of the $? crud was coping with broken cshisms
     if ( $? >> 8 ) {
-        &warn( "(Command exited ", ( $? >> 8 ), ")\n" );
+        DB::warn( "(Command exited ", ( $? >> 8 ), ")\n" );
     }
     elsif ($?) {
-        &warn(
+        DB::warn(
             "(Command died of SIG#",
             ( $? & 127 ),
             ( ( $? & 128 ) ? " -- core dumped" : "" ),
@@ -6445,8 +6810,8 @@ my $c_pipe = 0;
 sub os2_get_fork_TTY { # A simplification of the following (and works without):
     local $\  = '';
     ( my $name = $0 ) =~ s,^.*[/\\],,s;
-    my %opt = (        title => "Daughter Perl debugger $pids $name",
-               ($rl ? (read_by_key => 1) : ()) );
+    my %opt = ( title => "Daughter Perl debugger $pids $name",
+        ($rl ? (read_by_key => 1) : ()) );
     require OS2::Process;
     my ($in, $out, $pid) = eval { OS2::Process::io_term(related => 0, %opt) }
       or return;
@@ -6529,10 +6894,10 @@ sub macosx_get_fork_TTY
 
     return unless $version=$ENV{TERM_PROGRAM_VERSION};
     foreach my $entry (@script_versions) {
-       if ($version>=$entry->[0]) {
-           $script=$entry->[1];
-           last;
-       }
+        if ($version>=$entry->[0]) {
+            $script=$entry->[1];
+            last;
+        }
     }
     return unless defined($script);
     return unless open($pipe,'-|','/usr/bin/osascript','-e',$script);
@@ -8303,23 +8668,23 @@ sub methods_via {
     # Extract from all the symbols in this class.
     my $class_ref = do { no strict "refs"; \%{$class . '::'} };
     while (my ($name, $glob) = each %$class_ref) {
-       # references directly in the symbol table are Proxy Constant
-       # Subroutines, and are by their very nature defined
-       # Otherwise, check if the thing is a typeglob, and if it is, it decays
-       # to a subroutine reference, which can be tested by defined.
-       # $glob might also be the value -1  (from sub foo;)
-       # or (say) '$$' (from sub foo ($$);)
-       # \$glob will be SCALAR in both cases.
-       if ((ref $glob || ($glob && ref \$glob eq 'GLOB' && defined &$glob))
-           && !$seen{$name}++) {
-           push @to_print, "$prepend$name\n";
-       }
+        # references directly in the symbol table are Proxy Constant
+        # Subroutines, and are by their very nature defined
+        # Otherwise, check if the thing is a typeglob, and if it is, it decays
+        # to a subroutine reference, which can be tested by defined.
+        # $glob might also be the value -1  (from sub foo;)
+        # or (say) '$$' (from sub foo ($$);)
+        # \$glob will be SCALAR in both cases.
+        if ((ref $glob || ($glob && ref \$glob eq 'GLOB' && defined &$glob))
+            && !$seen{$name}++) {
+            push @to_print, "$prepend$name\n";
+        }
     }
 
     {
-       local $\ = '';
-       local $, = '';
-       print $DB::OUT $_ foreach sort @to_print;
+        local $\ = '';
+        local $, = '';
+        print $DB::OUT $_ foreach sort @to_print;
     }
 
     # If the $crawl_upward argument is false, just quit here.
@@ -9715,7 +10080,7 @@ sub cmd_pre580_W {
         # Get the current value of the expression.
         # Doesn't handle expressions returning list values!
         $evalarg = $1;
-        my ($val) = DB::eval(@_);
+        my ($val) = DB::eval();
         $val = ( defined $val ) ? "'$val'" : 'undef';
 
         # Save it.