changeset 27040:8408acb7ca4f

make dbup/dbdown work again (bug #56020) Store call stack index in stack frame object to make iteration in call stack possible given only a stack frame. Store current debug frame in call stack and use that info to track stack location when at a debug prompt. New class for managing debug info and the debug repl. It should now be possible to enter the debugger recursively. * stack-frame.h, stack-frame.cc (stack_frame::m_index): New data member. (stack_frame::m_prev): Delete data member and all uses. Update constructors for base and derived classes and change all uses. (stack_frame::index): New function. (display_stopped_in_message): New function. * bp-table.cc: Eliminate use of Vdebugger. Don't set debug_mode directly. * error.cc (maybe_enter_debugger, warning_1): Use tree_evaluator::enter_debugger instead of input_system::keyboard to enter debugger. * call-stack.h, call-stack.cc (call_stack::goto_frame_relative): Delete. (call_stack::find_current_user_frame, call_stack::find_current_user_frame, call_stack::find_current_user_frame, call_stack::find_caller_frame): New functions. (call_stack::goto_caller_frame): Simplify by calling find_caller_frame and dbupdown. (class stack_trace_generator): Delete class and all uses. (call_stack::current_user_code): Rename from caller_user_code and eliminate nskip argument. (call_stack::current_user_code_line): Rename from caller_user_code_line. (call_stack::current_user_code_column): Rename from caller_user_code_column. (call_stack::goto_frame, call_stack::dbupdown): Use stack_frame::display_in_stopped_message. (call_stack::backtrace_frames, call_stack::backtrace): Make these functions work again. Eliminate nskip argument. * debug.cc (Fdbstop, Fdbclear, Fdbstep): Call tree_evaluator::reset_debug_state. (Fdbstop, Fdbclear, Fdbstep, Fdbcont, Fdbquit, Fisdebugmode): Move real work to evaluator, debugger, and call_stack classes. (Fdbwhere): Use stack_frame::display_stopped_in_message. * input.h, input.cc (Vdebugging): Delete variable and all uses. (input_system::keyboard, execute_in_debugger_handler, input_system::get_debutg_input): Delete. Move functionality to new debugger class. (Fkeyboard): Simplify. (do_keyboard): Delete. * pt-eval.h, pt-eval.cc (class debugger): New class for managing debugger. (tree_evaluator::m_debug_frame): Rename from m_current_frame. Use this variable to manage debugger location when making function calls during debugging (for example, to dbup, dbdown, etc.). (tree_evaluator::m_debugger_stack): New variable. (tree_evaluator::reset_debug_state): Set m_debug_mode if we have breakpoints, are stepping in the debugger, or executing in the debugger repl. (tree_evaluator::reset_debug_state): Don't set m_dbstep_flag here. (tree_evaluator::enter_debugger): New function. (tree_evaluator::do_breakpoint): Handle exiting from the debugger object here. Maybe rejoin existing debugger instance. (tree_evaluator::do_keyboard): Delete. (bool tree_evaluator::in_debug_repl, bool tree_evaluator::exit_debug_repl, bool tree_evaluator::exit_debug_repl, bool tree_evaluator::abort_debug_repl, bool tree_evaluator::abort_debug_repl): New functions.
author John W. Eaton <jwe@octave.org>
date Mon, 08 Apr 2019 00:36:33 +0000
parents ca0230d3efbf
children 43f6f02dd91c
files libgui/src/m-editor/file-editor-tab.cc libinterp/corefcn/call-stack.cc libinterp/corefcn/call-stack.h libinterp/corefcn/debug.cc libinterp/corefcn/error.cc libinterp/corefcn/help.cc libinterp/corefcn/input.cc libinterp/corefcn/input.h libinterp/corefcn/stack-frame.cc libinterp/corefcn/stack-frame.h libinterp/parse-tree/bp-table.cc libinterp/parse-tree/pt-eval.cc libinterp/parse-tree/pt-eval.h
diffstat 13 files changed, 767 insertions(+), 698 deletions(-) [+]
line wrap: on
line diff
--- a/libgui/src/m-editor/file-editor-tab.cc	Mon Apr 08 09:22:39 2019 -0700
+++ b/libgui/src/m-editor/file-editor-tab.cc	Mon Apr 08 00:36:33 2019 +0000
@@ -2085,10 +2085,9 @@
     // If this file is loaded, check that we aren't currently running it
     bool retval = true;
     octave_idx_type curr_frame = -1;
-    size_t nskip = 0;
     call_stack& cs
       = __get_call_stack__ ("file_editor_tab::exit_debug_and_clear");
-    octave_map stk = cs.backtrace (nskip, curr_frame, false);
+    octave_map stk = cs.backtrace (curr_frame, false);
     Cell names = stk.contents ("name");
     for (octave_idx_type i = names.numel () - 1; i >= 0; i--)
       {
@@ -2109,7 +2108,7 @@
                 while (names.numel () > i)
                   {
                     octave::sleep (0.01);
-                    stk = cs.backtrace (nskip, curr_frame, false);
+                    stk = cs.backtrace (curr_frame, false);
                     names = stk.contents ("name");
                   }
               }
--- a/libinterp/corefcn/call-stack.cc	Mon Apr 08 09:22:39 2019 -0700
+++ b/libinterp/corefcn/call-stack.cc	Mon Apr 08 00:36:33 2019 +0000
@@ -24,6 +24,8 @@
 #  include "config.h"
 #endif
 
+#include <iostream>
+
 #include "lo-regexp.h"
 #include "str-vec.h"
 
@@ -54,86 +56,6 @@
 
 namespace octave
 {
-  class stack_trace_generator : public stack_frame_walker
-  {
-  public:
-
-    stack_trace_generator (size_t nskip = 0)
-      : stack_frame_walker (), m_frames (), m_nskip (nskip),
-        m_curr_frame (0)
-    { }
-
-    stack_trace_generator (const stack_trace_generator&) = delete;
-
-    stack_trace_generator& operator = (const stack_trace_generator&) = delete;
-
-    ~stack_trace_generator (void) = default;
-
-    std::list<stack_frame *> frames (void) const { return m_frames; }
-
-    size_t current_frame (void) const { return m_curr_frame; }
-
-    void visit_compiled_fcn_stack_frame (compiled_fcn_stack_frame& frame)
-    {
-      stack_frame *slink = frame.static_link ();
-
-      if (slink)
-        slink->accept (*this);
-    }
-
-    void visit_script_stack_frame (script_stack_frame& frame)
-    {
-      maybe_add_frame (frame);
-
-      stack_frame *slink = frame.static_link ();
-
-      if (slink)
-        slink->accept (*this);
-    }
-
-    void visit_user_fcn_stack_frame (user_fcn_stack_frame& frame)
-    {
-      maybe_add_frame (frame);
-
-      symbol_scope scope = frame.get_scope ();
-
-      stack_frame *slink = frame.static_link ();
-
-      if (slink)
-        slink->accept (*this);
-    }
-
-    void visit_scope_stack_frame (scope_stack_frame& frame)
-    {
-      symbol_scope scope = frame.get_scope ();
-
-      stack_frame *slink = frame.static_link ();
-
-      if (slink)
-        slink->accept (*this);
-    }
-
-  private:
-
-    void maybe_add_frame (stack_frame& frame)
-    {
-      if (m_nskip > 0)
-        {
-          m_nskip--;
-          return;
-        }
-
-      m_frames.push_back (&frame);
-    }
-
-    std::list<stack_frame *> m_frames;
-
-    // Number of user code frames to skip.
-    size_t m_nskip;
-
-    size_t m_curr_frame;
-  };
-
   call_stack::call_stack (tree_evaluator& evaluator)
     : m_evaluator (evaluator), m_cs (), m_curr_frame (0),
       m_max_stack_depth (1024), m_global_values ()
@@ -167,42 +89,32 @@
     return retval;
   }
 
-  octave_user_code * call_stack::caller_user_code (size_t nskip) const
+  octave_user_code * call_stack::current_user_code (void) const
   {
-    octave_user_code *retval = nullptr;
+    // Start at current frame.
 
-    size_t xframe = m_curr_frame;
+    size_t xframe = find_current_user_frame ();
 
-    while (xframe != 0)
+    if (xframe > 0)
       {
         const stack_frame *elt = m_cs[xframe];
 
         octave_function *f = elt->function ();
 
         if (f && f->is_user_code ())
-          {
-            if (nskip > 0)
-              nskip--;
-            else
-              {
-                retval = dynamic_cast<octave_user_code *> (f);
-                break;
-              }
-          }
-
-        xframe = m_cs[xframe]->previous ();
+          return dynamic_cast<octave_user_code *> (f);
       }
 
-    return retval;
+    return nullptr;
   }
 
-  int call_stack::caller_user_code_line (void) const
+  int call_stack::current_user_code_line (void) const
   {
-    int retval = -1;
+    // Start at current frame.
 
-    size_t xframe = m_curr_frame;
+    size_t xframe = find_current_user_frame ();
 
-    while (xframe != 0)
+    if (xframe > 0)
       {
         const stack_frame *elt = m_cs[xframe];
 
@@ -210,24 +122,47 @@
 
         if (f && f->is_user_code ())
           {
-            if (elt->line () > 0)
-              {
-                retval = elt->line ();
-                break;
-              }
+            int line = elt->line ();
+
+            if (line > 0)
+              return line;
           }
-
-        xframe = m_cs[xframe]->previous ();
       }
 
-    return retval;
+    return -1;
+  }
+
+  int call_stack::current_user_code_column (void) const
+  {
+    // Start at current frame.
+
+    size_t xframe = find_current_user_frame ();
+
+    if (xframe > 0)
+      {
+        const stack_frame *elt = m_cs[xframe];
+
+        octave_function *f = elt->function ();
+
+        if (f && f->is_user_code ())
+          {
+            int column = elt->column ();
+
+            if (column > 0)
+              return column;
+          }
+      }
+
+    return -1;
   }
 
   unwind_protect * call_stack::curr_fcn_unwind_protect_frame (void) const
   {
-    size_t xframe = m_curr_frame;
+    // Start at current frame.
 
-    while (xframe != 0)
+    size_t xframe = find_current_user_frame ();
+
+    if (xframe > 0)
       {
         const stack_frame *elt = m_cs[xframe];
 
@@ -235,40 +170,11 @@
 
         if (f && f->is_user_code ())
           return elt->unwind_protect_frame ();
-
-        xframe = m_cs[xframe]->previous ();
       }
 
     return nullptr;
   }
 
-  int call_stack::caller_user_code_column (void) const
-  {
-    int retval = -1;
-
-    size_t xframe = m_curr_frame;
-
-    while (xframe != 0)
-      {
-        const stack_frame *elt = m_cs[xframe];
-
-        octave_function *f = elt->function ();
-
-        if (f && f->is_user_code ())
-          {
-            if (elt->column ())
-              {
-                retval = elt->column ();
-                break;
-              }
-          }
-
-        xframe = m_cs[xframe]->previous ();
-      }
-
-    return retval;
-  }
-
   octave_user_code * call_stack::debug_user_code (void) const
   {
     octave_user_code *retval = nullptr;
@@ -444,7 +350,10 @@
 
     stack_frame *slink = get_static_link (prev_frame);
 
-    m_cs.push_back (new scope_stack_frame (*this, prev_frame, scope, slink));
+    stack_frame *new_frame
+      = new scope_stack_frame (*this, scope, m_curr_frame, slink);
+
+    m_cs.push_back (new_frame);
   }
 
   void call_stack::push (octave_user_function *fcn, unwind_protect *up_frame,
@@ -459,9 +368,11 @@
 
     stack_frame *slink = get_static_link (prev_frame);
 
-    m_cs.push_back (new user_fcn_stack_frame (*this, fcn, up_frame,
-                                              prev_frame, slink,
-                                              closure_frames));
+    stack_frame *new_frame
+      = new user_fcn_stack_frame (*this, fcn, up_frame, m_curr_frame,
+                                  slink, closure_frames);
+
+    m_cs.push_back (new_frame);
   }
 
   void call_stack::push (octave_user_script *script, unwind_protect *up_frame)
@@ -475,8 +386,10 @@
 
     stack_frame *slink = get_static_link (prev_frame);
 
-    m_cs.push_back (new script_stack_frame (*this, script, up_frame,
-                                            prev_frame, slink));
+    stack_frame *new_frame
+      = new script_stack_frame (*this, script, up_frame, m_curr_frame, slink);
+
+    m_cs.push_back (new_frame);
   }
 
   void call_stack::push (octave_function *fcn)
@@ -490,8 +403,10 @@
 
     stack_frame *slink = get_static_link (prev_frame);
 
-    m_cs.push_back (new compiled_fcn_stack_frame (*this, fcn, prev_frame,
-                                                  slink));
+    stack_frame *new_frame
+      = new compiled_fcn_stack_frame (*this, fcn, m_curr_frame, slink);
+
+    m_cs.push_back (new_frame);
   }
 
   bool call_stack::goto_frame (size_t n, bool verbose)
@@ -504,111 +419,156 @@
 
         m_curr_frame = n;
 
-        const stack_frame *elt = m_cs[n];
+        if (verbose)
+          {
+            const stack_frame *elt = m_cs[n];
 
-        if (verbose)
-          octave_stdout << "stopped in " << elt->fcn_name ()
-                        << " at line " << elt->line ()
-                        << " column " << elt->column ()
-                        << " [" << elt->fcn_file_name () << "] "
-                        << std::endl;
+            elt->display_stopped_in_message (octave_stdout);
+          }
       }
 
     return retval;
   }
 
-  bool call_stack::goto_frame_relative (int nskip, bool verbose)
+  size_t call_stack::find_current_user_frame (void) const
+  {
+    size_t user_frame = m_curr_frame;
+
+    stack_frame *frm = m_cs[user_frame];
+
+    if (! (frm->is_user_fcn_frame () || frm->is_user_script_frame ()
+           || frm->is_scope_frame ()))
+      {
+        frm = frm->static_link ();
+
+        user_frame = frm->index ();
+      }
+
+    return user_frame;
+  }
+
+  stack_frame *call_stack::current_user_frame (void) const
+  {
+    size_t frame = find_current_user_frame ();
+
+    return m_cs[frame];
+  }
+
+  // Go to the Nth frame (up if N is negative or down if positive) in
+  // the call stack that corresponds to a script, function, or scope
+  // beginning with the frame indexed by START.
+
+  size_t call_stack::dbupdown (size_t start, int n, bool verbose)
   {
-    bool retval = false;
+    if (start >= m_cs.size ())
+      error ("invalid stack frame");
+
+    // Can't go up from here.
+
+    if (start == 0 && n < 0)
+      {
+        if (verbose)
+          m_cs[start]->display_stopped_in_message (octave_stdout);
+
+        return start;
+      }
+
+    stack_frame *frm = m_cs[start];
+
+    if (! (frm && (frm->is_user_fcn_frame ()
+                   || frm->is_user_script_frame ()
+                   || frm->is_scope_frame ())))
+      error ("call_stack::dbupdown: invalid initial frame in call stack!");
+
+    // Use index into the call stack to begin the search.  At this point
+    // we iterate up or down using indexing instead of static links
+    // because ... FIXME: it's a bit complicated, but deserves
+    // explanation.  May be easiest with some pictures of the call stack
+    // for an example or two.
+
+    size_t xframe = frm->index ();
+
+    if (n == 0)
+      {
+        if (verbose)
+          frm->display_stopped_in_message (octave_stdout);
+
+        return xframe;
+      }
 
     int incr = 0;
 
-    if (nskip < 0)
-      incr = -1;
-    else if (nskip > 0)
+    if (n < 0)
+      {
+        incr = -1;
+        n = -n;
+      }
+    else if (n > 0)
       incr = 1;
 
-    size_t xframe = m_curr_frame;
+    size_t last_good_frame = 0;
 
     while (true)
       {
-        if ((incr < 0 && xframe == 0) || (incr > 0 && xframe == m_cs.size () - 1))
-          break;
+        frm = m_cs[xframe];
+
+        if (frm->is_user_fcn_frame () || frm->is_user_script_frame ()
+            || frm->is_scope_frame ())
+          {
+            last_good_frame = xframe;
+
+            if (n == 0)
+              break;
+
+            n--;
+          }
 
         xframe += incr;
 
-        const stack_frame *elt = m_cs[xframe];
-
-        octave_function *f = elt->function ();
-
-        if (xframe == 0 || (f && f->is_user_code ()))
+        if (xframe == 0)
           {
-            if (nskip > 0)
-              nskip--;
-            else if (nskip < 0)
-              nskip++;
-
-            if (nskip == 0)
-              {
-                m_curr_frame = xframe;
+            last_good_frame = 0;
+            break;
+          }
 
-                if (verbose)
-                  {
-                    std::ostringstream buf;
-
-                    if (f)
-                      buf << "stopped in " << elt->fcn_name ()
-                          << " at line " << elt->line ()
-                          << " [" << elt->fcn_file_name () << "] "
-                          << std::endl;
-                    else
-                      buf << "at top level" << std::endl;
-
-                    octave_stdout << buf.str ();
-                  }
-
-                retval = true;
-                break;
-              }
-          }
-        else if (incr == 0)  // Break out of infinite loop by choosing an incr.
-          incr = -1;
+        if (xframe == m_cs.size ())
+          break;
       }
 
-    return retval;
+    if (verbose)
+      m_cs[last_good_frame]->display_stopped_in_message (octave_stdout);
+
+    return last_good_frame;
   }
 
-  size_t call_stack::find_caller_frame (void)
-  {
-    // Find the preceeding frame that corresponds to a script or
-    // function.  Expected to be called from a stack frame corresponding
-    // to a compiled function.
-
-    size_t xframe = m_curr_frame;
-
-    bool skipped = false;
-
-    while (xframe != 0)
-      {
-        xframe = m_cs[xframe]->previous ();
+  // Like dbupdown above but find the starting frame automatically from
+  // the current frame.  If the current frame is already a user
+  // function, script, or scope frame, use that.  Otherwise, follow
+  // the static link for the current frame.  If that is not a user
+  // function, script or scope frame then there is an error in the
+  // implementation.
 
-        stack_frame *frm = m_cs[xframe];
-        if (frm->is_user_fcn_frame () || frm->is_user_script_frame ())
-          {
-            if (! skipped)
-              // We found the current user code frame, so skip it.
-              skipped = true;
-            else
-              return xframe;
-          }
-      }
+  size_t call_stack::dbupdown (int n, bool verbose)
+  {
+    size_t start = find_current_user_frame ();
 
-    return 0;
+    return dbupdown (start, n, verbose);
   }
 
+  // May be used to temporarily change the value ov m_curr_frame inside
+  // a function like evalin.  If used in a function like dbup, the new
+  // value of m_curr_frame would be wiped out when dbup returns and the
+  // stack frame for dbup is popped.
+
   void call_stack::goto_caller_frame (void)
   {
-    m_curr_frame = find_caller_frame ();
+    size_t start = find_current_user_frame ();
+
+    // FIXME: is this supposed to be an error?
+    if (start == 0)
+      error ("already at top level");
+
+    m_curr_frame = dbupdown (start, -1, false);
   }
 
   void call_stack::goto_base_frame (void)
@@ -618,58 +578,51 @@
   }
 
   std::list<stack_frame *>
-  call_stack::backtrace_frames (size_t nskip,
-                                octave_idx_type& curr_user_frame) const
+  call_stack::backtrace_frames (octave_idx_type& curr_user_frame) const
   {
-    stack_trace_generator stack_tracer (nskip);
-
-    // Start at the end of the stack, even if the current pointer is
-    // somewhere else.
+    std::list<stack_frame *> frames;
 
-    size_t n = m_cs.size () - 1;
-
-    m_cs[n]->accept (stack_tracer);
+    // curr_frame is the index to the current frame in the overall call
+    // stack, which includes any compiled function frames and scope
+    // frames.  The curr_user_frame value we set is the index into the
+    // subset of frames returned in the octave_map object.
 
-    std::list<stack_frame *> frame_list = stack_tracer.frames ();
-
-    if (frame_list.empty ())
-      return frame_list;
+    size_t curr_frame = find_current_user_frame ();
 
-    // Find the index into the list of frames where we are currently.
-    // We'll just search the list of frames for the one that matches
-    // where we are now.
+    // Don't include top-level stack frame in the list.
 
-    stack_frame *frame = m_cs[m_curr_frame];
-
-    octave_function *fcn = frame->function ();
+    for (size_t n = m_cs.size () - 1; n > 0; n--)
+      {
+        stack_frame *frm = m_cs[n];
 
-    if (! (fcn && fcn->is_user_code ()))
-      frame = frame->static_link ();
+        if (frm->is_user_script_frame () || frm->is_user_fcn_frame ()
+            || frm->is_scope_frame ())
+          {
+            if (frm->index () == curr_frame)
+              curr_user_frame = frames.size ();
 
-    curr_user_frame = 0;
-    bool found = false;
-    for (const auto *frm : frame_list)
-      {
-        if (frm == frame)
-          {
-            found = true;
-            break;
+            frames.push_back (frm);
           }
 
-        curr_user_frame++;
+        if (n == 0)
+          break;
       }
 
-    if (! found)
-      curr_user_frame = -1;
-
-    return frame_list;
+    return frames;
   }
 
-  octave_map call_stack::backtrace (size_t nskip,
-                                    octave_idx_type& curr_user_frame,
+  std::list<stack_frame *>
+  call_stack::backtrace_frames (void) const
+  {
+    octave_idx_type curr_user_frame = -1;
+
+    return backtrace_frames (curr_user_frame);
+  }
+
+  octave_map call_stack::backtrace (octave_idx_type& curr_user_frame,
                                     bool print_subfn) const
   {
-    std::list<stack_frame *> frames = backtrace_frames (nskip, curr_user_frame);
+    std::list<stack_frame *> frames = backtrace_frames (curr_user_frame);
 
     size_t nframes = frames.size ();
 
@@ -684,22 +637,26 @@
 
     for (const auto *frm : frames)
       {
-        file(k) = frm->fcn_file_name ();
-        name(k) = frm->fcn_name (print_subfn);
-        line(k) = frm->line ();
-        column(k) = frm->column ();
+        if (frm->is_user_script_frame () || frm->is_user_fcn_frame ()
+            || frm->is_scope_frame ())
+          {
+            file(k) = frm->fcn_file_name ();
+            name(k) = frm->fcn_name (print_subfn);
+            line(k) = frm->line ();
+            column(k) = frm->column ();
 
-        k++;
+            k++;
+          }
       }
 
     return retval;
   }
 
-  octave_map call_stack::backtrace (size_t nskip)
+  octave_map call_stack::backtrace (void) const
   {
     octave_idx_type curr_user_frame = -1;
 
-    return backtrace (nskip, curr_user_frame, true);
+    return backtrace (curr_user_frame, true);
   }
 
   octave_map call_stack::empty_backtrace (void) const
@@ -712,11 +669,13 @@
     // Never pop top scope.
     // FIXME: is it possible for this case to happen?
 
-    if (m_cs.size () > 0)
+    if (m_cs.size () > 1)
       {
         stack_frame *elt = m_cs.back ();
 
-        m_curr_frame = elt->previous ();
+        stack_frame *caller = elt->static_link ();
+
+        m_curr_frame = caller->index ();
 
         m_cs.pop_back ();
 
--- a/libinterp/corefcn/call-stack.h	Mon Apr 08 09:22:39 2019 -0700
+++ b/libinterp/corefcn/call-stack.h	Mon Apr 08 00:36:33 2019 +0000
@@ -135,15 +135,15 @@
     }
 
     // User code caller.
-    octave_user_code * caller_user_code (size_t nskip = 0) const;
+    octave_user_code * current_user_code (void) const;
 
     unwind_protect * curr_fcn_unwind_protect_frame (void) const;
 
     // Line in user code caller.
-    int caller_user_code_line (void) const;
+    int current_user_code_line (void) const;
 
     // Column in user code caller.
-    int caller_user_code_column (void) const;
+    int current_user_code_column (void) const;
 
     // Current function that we are debugging.
     octave_user_code * debug_user_code (void) const;
@@ -214,28 +214,25 @@
       goto_frame (n);
     }
 
-    bool goto_frame_relative (int n, bool verbose = false);
+    size_t find_current_user_frame (void) const;
+    stack_frame *current_user_frame (void) const;
 
-    size_t find_caller_frame (void);
+    size_t dbupdown (size_t start, int n, bool verbose);
+    size_t dbupdown (int n = -1, bool verbose = false);
 
     void goto_caller_frame (void);
 
     void goto_base_frame (void);
 
     std::list<stack_frame *>
-    backtrace_frames (size_t nskip, octave_idx_type& curr_user_frame) const;
+    backtrace_frames (octave_idx_type& curr_user_frame) const;
 
-    std::list<stack_frame *> backtrace_frames (size_t nskip = 0) const
-    {
-      octave_idx_type curr_user_frame = -1;
+    std::list<stack_frame *> backtrace_frames (void) const;
 
-      return backtrace_frames (nskip, curr_user_frame);
-    }
-
-    octave_map backtrace (size_t nskip, octave_idx_type& curr_user_frame,
+    octave_map backtrace (octave_idx_type& curr_user_frame,
                           bool print_subfn = true) const;
 
-    octave_map backtrace (size_t nskip = 0);
+    octave_map backtrace (void) const;
 
     octave_map empty_backtrace (void) const;
 
--- a/libinterp/corefcn/debug.cc	Mon Apr 08 09:22:39 2019 -0700
+++ b/libinterp/corefcn/debug.cc	Mon Apr 08 00:36:33 2019 +0000
@@ -257,6 +257,9 @@
         }
     }
 
+  // If we add a breakpoint, we also need to reset debug_mode.
+  tw.reset_debug_state ();
+
   return retval;
 }
 
@@ -328,6 +331,9 @@
         bptab.remove_breakpoint (symbol_name, lines);
     }
 
+  // If we remove a breakpoint, we also need to reset debug_mode.
+  tw.reset_debug_state ();
+
   return ovl ();
 }
 
@@ -401,7 +407,7 @@
   else
     {
 /*
-      if (Vdebugging)
+      if (tw.in_debug_repl ())
         {
           octave_user_code *dbg_fcn = tw.get_user_code ();
           if (dbg_fcn)
@@ -561,42 +567,11 @@
 @seealso{dbstack, dblist, dbstatus, dbcont, dbstep, dbup, dbdown}
 @end deftypefn */)
 {
-  octave::tree_evaluator& tw = interp.get_evaluator ();
-
-  octave_user_code *dbg_fcn = tw.get_user_code ();
-
-  if (! dbg_fcn)
-    {
-      octave_stdout << "stopped at top level" << std::endl;
-      return ovl ();
-    }
-
-  octave_stdout << "stopped in " << dbg_fcn->name () << " at ";
-
   octave::call_stack& cs = interp.get_call_stack ();
 
-  int l = cs.debug_user_code_line ();
-
-  if (l > 0)
-    {
-      octave_stdout << "line " << l;
-
-      std::string file_name = dbg_fcn->fcn_file_name ();
+  octave::stack_frame *frm = cs.current_user_frame ();
 
-      if (! file_name.empty ())
-        {
-          octave_stdout << " [" << file_name << ']' << std::endl;
-
-          std::string line = dbg_fcn->get_code_line (l);
-
-          if (! line.empty ())
-            octave_stdout << l << ": " << line << std::endl;
-        }
-      else
-        octave_stdout << std::endl;
-    }
-  else
-    octave_stdout << "<unknown line>" << std::endl;
+  frm->display_stopped_in_message (octave_stdout);
 
   return ovl ();
 }
@@ -909,10 +884,10 @@
 
   if (nargout == 0)
     {
-      octave_map stk = cs.backtrace (nskip, curr_frame);
-      octave_idx_type nframes_to_display = stk.numel ();
+      octave_map stk = cs.backtrace (curr_frame);
+      octave_idx_type nframes = stk.numel ();
 
-      if (nframes_to_display > 0)
+      if (nframes > 0)
         {
           octave::preserve_stream_state stream_state (os);
 
@@ -926,14 +901,14 @@
 
           size_t max_name_len = 0;
 
-          for (octave_idx_type i = 0; i < nframes_to_display; i++)
+          for (octave_idx_type i = nskip; i < nframes; i++)
             {
               std::string name = names(i).string_value ();
 
               max_name_len = std::max (name.length (), max_name_len);
             }
 
-          for (octave_idx_type i = 0; i < nframes_to_display; i++)
+          for (octave_idx_type i = nskip; i < nframes; i++)
             {
               std::string name = names(i).string_value ();
               std::string file = files(i).string_value ();
@@ -955,7 +930,7 @@
     }
   else
     {
-      octave_map stk = cs.backtrace (nskip, curr_frame, false);
+      octave_map stk = cs.backtrace (curr_frame, false);
 
       // If current stack frame is not in the list curr_frame will be
       // -1 and either nskip caused us to skip it or we are at the top
@@ -1050,10 +1025,9 @@
   if (who == "dbup")
     n = -n;
 
-  octave::call_stack& cs = interp.get_call_stack ();
+  octave::tree_evaluator& tw = interp.get_evaluator ();
 
-  if (! cs.goto_frame_relative (n, true))
-    error ("%s: invalid stack frame", who.c_str ());
+  tw.dbupdown (n, true);
 }
 
 DEFMETHOD (dbup, interp, args, ,
@@ -1109,7 +1083,9 @@
 @seealso{dbcont, dbquit}
 @end deftypefn */)
 {
-  if (! Vdebugging)
+  octave::tree_evaluator& tw = interp.get_evaluator ();
+
+  if (! tw.in_debug_repl ())
     error ("dbstep: can only be called in debug mode");
 
   int nargin = args.length ();
@@ -1117,45 +1093,37 @@
   if (nargin > 1)
     print_usage ();
 
-  octave::tree_evaluator& tw = interp.get_evaluator ();
+  int n = 0;
 
   if (nargin == 1)
     {
-      std::string arg = args(0).xstring_value ("dbstep: input argument must be a string");
+      std::string arg
+        = args(0).xstring_value ("dbstep: input argument must be a string");
 
       if (arg == "in")
-        {
-          Vdebugging = false;
-          Vtrack_line_num = true;
-
-          tw.set_dbstep_flag (-1);
-        }
+        n = -1;
       else if (arg == "out")
-        {
-          Vdebugging = false;
-          Vtrack_line_num = true;
-
-          tw.set_dbstep_flag (-2);
-        }
+        n = -2;
       else
         {
-          int n = atoi (arg.c_str ());
+          n = atoi (arg.c_str ());
 
           if (n < 1)
             error ("dbstep: invalid argument");
-
-          Vdebugging = false;
-          Vtrack_line_num = true;
-
-          tw.set_dbstep_flag (n);
         }
     }
   else
+    n = 1;
+
+  if (n != 0)
     {
-      Vdebugging = false;
       Vtrack_line_num = true;
 
-      tw.set_dbstep_flag (1);
+      tw.set_dbstep_flag (n);
+
+      // If we set the dbstep flag, we also need to reset debug_mode.
+      tw.reset_debug_state ();
+
     }
 
   return ovl ();
@@ -1170,18 +1138,17 @@
 @seealso{dbstep, dbquit}
 @end deftypefn */)
 {
-  if (! Vdebugging)
+  octave::tree_evaluator& tw = interp.get_evaluator ();
+
+  if (! tw.in_debug_repl ())
     error ("dbcont: can only be called in debug mode");
 
   if (args.length () != 0)
     print_usage ();
 
-  Vdebugging = false;
   Vtrack_line_num = true;
 
-  octave::tree_evaluator& tw = interp.get_evaluator ();
-
-  tw.reset_debug_state ();
+  tw.exit_debug_repl (true);
 
   return ovl ();
 }
@@ -1194,21 +1161,15 @@
 @seealso{dbcont, dbstep}
 @end deftypefn */)
 {
-  if (! Vdebugging)
+  octave::tree_evaluator& tw = interp.get_evaluator ();
+
+  if (! tw.in_debug_repl ())
     error ("dbquit: can only be called in debug mode");
 
   if (args.length () != 0)
     print_usage ();
 
-  // FIXME: there are too many debug mode flags!
-
-  Vdebugging = false;
-
-  octave::tree_evaluator& tw = interp.get_evaluator ();
-
-  tw.reset_debug_state (false);
-
-  throw octave::interrupt_exception ();
+  tw.abort_debug_repl (true);
 
   return ovl ();
 }
@@ -1223,7 +1184,9 @@
   if (args.length () != 0)
     print_usage ();
 
-  return ovl (Vdebugging);
+  octave::tree_evaluator& tw = octave::__get_evaluator__ ("Fisdebugmode");
+
+  return ovl (tw.in_debug_repl ());
 }
 
 DEFMETHOD (__db_next_breakpoint_quiet__, interp, args, ,
--- a/libinterp/corefcn/error.cc	Mon Apr 08 09:22:39 2019 -0700
+++ b/libinterp/corefcn/error.cc	Mon Apr 08 00:36:33 2019 +0000
@@ -218,7 +218,7 @@
       Vlast_error_id = id;
       Vlast_error_message = base_msg;
 
-      octave_user_code *fcn = cs.caller_user_code ();
+      octave_user_code *fcn = cs.current_user_code ();
 
       if (fcn)
         Vlast_error_stack = cs.backtrace ();
@@ -357,7 +357,7 @@
        || octave::application::forced_interactive ())
       && ((Vdebug_on_error && bptab.debug_on_err (last_error_id ()))
           || (Vdebug_on_caught && bptab.debug_on_caught (last_error_id ())))
-      && cs.caller_user_code ())
+      && cs.current_user_code ())
     {
       octave::unwind_protect frame;
       frame.protect_var (Vdebug_on_error);
@@ -366,9 +366,6 @@
       octave::tree_evaluator& tw
         = octave::__get_evaluator__ ("maybe_enter_debugger");
 
-      tw.debug_mode (true);
-      tw.current_frame (cs.current_frame ());
-
       if (show_stack_trace)
         {
           std::string stack_trace = e.info ();
@@ -381,10 +378,7 @@
             }
         }
 
-      octave::input_system& input_sys
-        = octave::__get_input_system__ ("maybe_enter_debugger");
-
-      input_sys.keyboard ();
+      tw.enter_debugger ();
     }
 }
 
@@ -527,7 +521,7 @@
                   octave::call_stack& cs
                     = octave::__get_call_stack__ ("error_1");
 
-                  bool in_user_code = cs.caller_user_code () != nullptr;
+                  bool in_user_code = cs.current_user_code () != nullptr;
 
                   if (in_user_code && ! discard_error_messages)
                     show_stack_trace = true;
@@ -749,7 +743,7 @@
 
       octave::call_stack& cs = octave::__get_call_stack__ ("warning_1");
 
-      bool in_user_code = cs.caller_user_code () != nullptr;
+      bool in_user_code = cs.current_user_code () != nullptr;
 
       if (! fmt_suppresses_backtrace && in_user_code
           && Vbacktrace_on_warning
@@ -770,13 +764,7 @@
           octave::tree_evaluator& tw
             = octave::__get_evaluator__ ("warning_1");
 
-          tw.debug_mode (true);
-          tw.current_frame (cs.current_frame ());
-
-          octave::input_system& input_sys
-            = octave::__get_input_system__ ("warning_1");
-
-          input_sys.keyboard ();
+          tw.enter_debugger ();
         }
     }
 }
--- a/libinterp/corefcn/help.cc	Mon Apr 08 09:22:39 2019 -0700
+++ b/libinterp/corefcn/help.cc	Mon Apr 08 00:36:33 2019 +0000
@@ -489,7 +489,7 @@
 
     call_stack& cs = m_interpreter.get_call_stack ();
 
-    octave_user_code *curr_fcn = cs.caller_user_code ();
+    octave_user_code *curr_fcn = cs.current_user_code ();
 
     if (! curr_fcn)
       return retval;
--- a/libinterp/corefcn/input.cc	Mon Apr 08 09:22:39 2019 -0700
+++ b/libinterp/corefcn/input.cc	Mon Apr 08 00:36:33 2019 +0000
@@ -86,9 +86,6 @@
 // the next user prompt.
 bool Vdrawnow_requested = false;
 
-// TRUE if we are in debugging mode.
-bool Vdebugging = false;
-
 // TRUE if we are recording line numbers in a source file.
 // Always true except when debugging and taking input directly from
 // the terminal.
@@ -635,53 +632,15 @@
         retval
           = m_interpreter.eval_string (input_buf, true, parse_status, nargout);
 
-        if (! Vdebugging && retval.empty ())
+        tree_evaluator& tw = m_interpreter.get_evaluator ();
+
+        if (! tw.in_debug_repl () && retval.empty ())
           retval(0) = Matrix ();
       }
 
     return retval;
   }
 
-  octave_value input_system::keyboard (const octave_value_list& args)
-  {
-    octave_value retval;
-
-    int nargin = args.length ();
-
-    assert (nargin == 0 || nargin == 1);
-
-    octave::unwind_protect frame;
-
-    frame.add_fcn (octave::command_history::ignore_entries,
-                   octave::command_history::ignoring_entries ());
-
-    octave::command_history::ignore_entries (false);
-
-    frame.protect_var (Vdebugging);
-
-    octave::call_stack& cs = m_interpreter.get_call_stack ();
-
-    frame.add_method (cs, &octave::call_stack::restore_frame,
-                      cs.current_frame ());
-
-    // FIXME: probably we just want to print one line, not the
-    // entire statement, which might span many lines...
-    //
-    // tree_print_code tpc (octave_stdout);
-    // stmt.accept (tpc);
-
-    Vdebugging = true;
-    Vtrack_line_num = false;
-
-    std::string prompt = "debug> ";
-    if (nargin > 0)
-      prompt = args(0).string_value ();
-
-    get_debug_input (prompt);
-
-    return retval;
-  }
-
   bool input_system::have_input_event_hooks (void) const
   {
     return ! m_input_event_hook_functions.empty ();
@@ -729,153 +688,6 @@
     return retval;
   }
 
-  static void
-  execute_in_debugger_handler (const std::pair<std::string, int>& arg)
-  {
-    octave_link::execute_in_debugger_event (arg.first, arg.second);
-  }
-
-  void input_system::get_debug_input (const std::string& prompt)
-  {
-    octave::unwind_protect frame;
-
-    octave::tree_evaluator& tw = m_interpreter.get_evaluator ();
-
-    bool silent = tw.quiet_breakpoint_flag (false);
-
-    octave::call_stack& cs = m_interpreter.get_call_stack ();
-
-    octave_user_code *caller = cs.caller_user_code ();
-    std::string nm;
-    int curr_debug_line;
-
-    if (caller)
-      {
-        nm = caller->fcn_file_name ();
-
-        if (nm.empty ())
-          nm = caller->name ();
-
-        curr_debug_line = cs.caller_user_code_line ();
-      }
-    else
-      curr_debug_line = cs.current_line ();
-
-    std::ostringstream buf;
-
-    if (! nm.empty ())
-      {
-        if (m_gud_mode)
-          {
-            static char ctrl_z = 'Z' & 0x1f;
-
-            buf << ctrl_z << ctrl_z << nm << ':' << curr_debug_line;
-          }
-        else
-          {
-            // FIXME: we should come up with a clean way to detect
-            // that we are stopped on the no-op command that marks the
-            // end of a function or script.
-
-            if (! silent)
-              {
-                buf << "stopped in " << nm;
-
-                if (curr_debug_line > 0)
-                  buf << " at line " << curr_debug_line;
-              }
-
-            octave_link::enter_debugger_event (nm, curr_debug_line);
-
-            octave_link::set_workspace ();
-
-            frame.add_fcn (execute_in_debugger_handler,
-                           std::pair<std::string, int> (nm, curr_debug_line));
-
-            if (! silent)
-              {
-                std::string line_buf;
-
-                if (caller)
-                  line_buf = caller->get_code_line (curr_debug_line);
-
-                if (! line_buf.empty ())
-                  buf << "\n" << curr_debug_line << ": " << line_buf;
-              }
-          }
-      }
-
-    if (silent)
-      octave::command_editor::erase_empty_line (true);
-
-    std::string msg = buf.str ();
-
-    if (! msg.empty ())
-      std::cerr << msg << std::endl;
-
-    frame.add_method (*this, &octave::input_system::set_PS1, m_PS1);
-    m_PS1 = prompt;
-
-    // FIXME: should debugging be possible in an embedded interpreter?
-
-    octave::application *app = octave::application::app ();
-
-    if (! app->interactive ())
-      {
-
-        frame.add_method (app, &octave::application::interactive,
-                          app->interactive ());
-
-        frame.add_method (app, &octave::application::forced_interactive,
-                          app->forced_interactive ());
-
-        app->interactive (true);
-
-        app->forced_interactive (true);
-      }
-
-    octave::parser curr_parser (m_interpreter);
-
-    while (Vdebugging)
-      {
-        try
-          {
-            Vtrack_line_num = false;
-
-            reset_error_handler ();
-
-            curr_parser.reset ();
-
-            int retval = curr_parser.run ();
-
-            if (octave::command_editor::interrupt (false))
-              break;
-            else
-              {
-                if (retval == 0 && curr_parser.m_stmt_list)
-                  {
-                    curr_parser.m_stmt_list->accept (tw);
-
-                    if (octave_completion_matches_called)
-                      octave_completion_matches_called = false;
-                  }
-
-                octave_quit ();
-              }
-          }
-        catch (const octave::execution_exception& e)
-          {
-            std::string stack_trace = e.info ();
-
-            if (! stack_trace.empty ())
-              std::cerr << stack_trace;
-
-            // Ignore errors when in debugging mode;
-            octave::interpreter::recover_from_exception ();
-          }
-      }
-  }
-
   std::string base_reader::octave_gets (bool& eof)
   {
     octave_quit ();
@@ -887,9 +699,13 @@
     // Process pre input event hook function prior to flushing output and
     // printing the prompt.
 
+    interpreter& interp = __get_interpreter__ ("base_reader::octave_gets");
+
+    tree_evaluator& tw = interp.get_evaluator ();
+
     if (application::interactive ())
       {
-        if (! Vdebugging)
+        if (! tw.in_debug_repl ())
           octave_link::exit_debugger_event ();
 
         octave_link::pre_input_event ();
@@ -899,7 +715,7 @@
 
     bool history_skip_auto_repeated_debugging_command = false;
 
-    input_system& input_sys = __get_input_system__ ("base_reader::octave_gets");
+    input_system& input_sys = interp.get_input_system ();
 
     std::string ps = (m_pflag > 0) ? input_sys.PS1 () : input_sys.PS2 ();
 
@@ -907,7 +723,7 @@
 
     pipe_handler_error_count = 0;
 
-    output_system& output_sys = __get_output_system__ ("do_sync");
+    output_system& output_sys = interp.get_output_system ();
 
     output_sys.reset ();
 
@@ -920,16 +736,16 @@
     if (retval != "\n"
         && retval.find_first_not_of (" \t\n\r") != std::string::npos)
       {
-        load_path& lp = __get_load_path__ ("base_reader::octave_gets");
+        load_path& lp = interp.get_load_path ();
 
         lp.update ();
 
-        if (Vdebugging)
+        if (tw.in_debug_repl ())
           input_sys.last_debugging_command (retval);
         else
           input_sys.last_debugging_command ("\n");
       }
-    else if (Vdebugging)
+    else if (tw.in_debug_repl ())
       {
         retval = input_sys.last_debugging_command ();
         history_skip_auto_repeated_debugging_command = true;
@@ -1235,28 +1051,22 @@
 @seealso{dbstop, dbcont, dbquit}
 @end deftypefn */)
 {
-  if (args.length () > 1)
-    print_usage ();
-
-  octave::unwind_protect frame;
+  int nargin = args.length ();
 
-  octave::call_stack& cs = interp.get_call_stack ();
-
-  frame.add_method (cs, &octave::call_stack::restore_frame,
-                    cs.current_frame ());
-
-  // Go up to the nearest user code frame.
-  cs.goto_frame_relative (-1);
+  if (nargin > 1)
+    print_usage ();
 
   octave::tree_evaluator& tw = interp.get_evaluator ();
 
-  tw.debug_mode (true);
-  tw.quiet_breakpoint_flag (false);
-  tw.current_frame (cs.current_frame ());
+  if (nargin == 1)
+    {
+      std::string prompt
+        = args(0).xstring_value ("keyboard: PROMPT must be a string");
 
-  octave::input_system& input_sys = interp.get_input_system ();
-
-  input_sys.keyboard (args);
+      tw.keyboard (prompt);
+    }
+  else
+    tw.keyboard ();
 
   return ovl ();
 }
@@ -1625,15 +1435,6 @@
   return input_sys.yes_or_no (prompt);
 }
 
-octave_value
-do_keyboard (const octave_value_list& args)
-{
-  octave::input_system& input_sys
-    = octave::__get_input_system__ ("do_keyboard");
-
-  return input_sys.keyboard (args);
-}
-
 void
 remove_input_event_hook_functions (void)
 {
--- a/libinterp/corefcn/input.h	Mon Apr 08 09:22:39 2019 -0700
+++ b/libinterp/corefcn/input.h	Mon Apr 08 00:36:33 2019 +0000
@@ -44,9 +44,6 @@
 // the next user prompt.
 extern OCTINTERP_API bool Vdrawnow_requested;
 
-// TRUE if we are in debugging mode.
-extern OCTINTERP_API bool Vdebugging;
-
 // TRUE if we are not executing a command direct from debug> prompt.
 extern OCTINTERP_API bool Vtrack_line_num;
 
@@ -155,9 +152,6 @@
     octave_value_list
     get_user_input (const octave_value_list& args, int nargout);
 
-    octave_value
-    keyboard (const octave_value_list& args = octave_value_list ());
-
     bool have_input_event_hooks (void) const;
 
     void add_input_event_hook (const hook_function& hook_fcn);
@@ -196,8 +190,6 @@
     hook_function_list m_input_event_hook_functions;
 
     std::string gnu_readline (const std::string& s, bool& eof) const;
-
-    void get_debug_input (const std::string& prompt);
   };
 
   class base_reader
@@ -339,10 +331,6 @@
 OCTAVE_DEPRECATED (5, "use 'octave::input_system::yes_or_no' instead")
 extern bool octave_yes_or_no (const std::string& prompt);
 
-OCTAVE_DEPRECATED (5, "use 'octave::input_system::keyboard' instead")
-extern octave_value do_keyboard (const octave_value_list& args
-                                 = octave_value_list ());
-
 OCTAVE_DEPRECATED (5, "use 'octave::input_system::clear_input_event_hooks' instead")
 extern void remove_input_event_hook_functions (void);
 
--- a/libinterp/corefcn/stack-frame.cc	Mon Apr 08 09:22:39 2019 -0700
+++ b/libinterp/corefcn/stack-frame.cc	Mon Apr 08 00:36:33 2019 +0000
@@ -478,6 +478,22 @@
     accept (sc);
   }
 
+  void stack_frame::display_stopped_in_message (std::ostream& os) const
+  {
+    if (index () == 0)
+      os << "at top level" << std::endl;
+    else
+      {
+        os << "stopped in " << fcn_name ();
+
+        int l = line ();
+        if (l > 0)
+          os << " at line " << line ();
+
+        os << " [" << fcn_file_name () << "] " << std::endl;
+      }
+  }
+
   void stack_frame::display (bool follow) const
   {
     std::ostream& os = octave_stdout;
@@ -500,7 +516,7 @@
 
     os << "line: " << m_line << std::endl;
     os << "column: " << m_column << std::endl;
-    os << "prev: " << m_prev << std::endl;
+    os << "index: " << m_index << std::endl;
 
     os << std::endl;
 
@@ -543,9 +559,9 @@
   script_stack_frame::script_stack_frame (call_stack& cs,
                                           octave_user_script *script,
                                           unwind_protect *up_frame,
-                                          size_t prev,
+                                          size_t index,
                                           stack_frame *static_link)
-    : stack_frame (cs, prev, static_link, get_access_link (static_link)),
+    : stack_frame (cs, index, static_link, get_access_link (static_link)),
       m_script (script), m_unwind_protect_frame (up_frame),
       m_lexical_frame_offsets (get_num_symbols (script), 1),
       m_value_offsets (get_num_symbols (script), 0)
--- a/libinterp/corefcn/stack-frame.h	Mon Apr 08 09:22:39 2019 -0700
+++ b/libinterp/corefcn/stack-frame.h	Mon Apr 08 00:36:33 2019 +0000
@@ -140,9 +140,9 @@
 
     stack_frame (void) = delete;
 
-    stack_frame (call_stack& cs, size_t prev, stack_frame *static_link,
-                 stack_frame *access_link)
-      : m_call_stack (cs), m_line (-1), m_column (-1), m_prev (prev),
+    stack_frame (call_stack& cs, size_t index,
+                 stack_frame *static_link, stack_frame *access_link)
+      : m_call_stack (cs), m_line (-1), m_column (-1), m_index (index),
         m_static_link (static_link), m_access_link (access_link),
         m_dispatch_class ()
     { }
@@ -166,7 +166,7 @@
 
     virtual void clear_values (void);
 
-    size_t previous (void) const { return m_prev; }
+    size_t index (void) const { return m_index; }
 
     void line (int l) { m_line = l; }
     int line (void) const { return m_line; }
@@ -544,6 +544,8 @@
       m_dispatch_class = class_name;
     }
 
+    void display_stopped_in_message (std::ostream& os) const;
+
     virtual void mark_scope (const symbol_record&, scope_flags) = 0;
 
     virtual void display (bool follow = true) const;
@@ -562,10 +564,8 @@
     int m_line;
     int m_column;
 
-    // FIXME: We could probably eliminate this variable.  Now that we
-    // maintain the static and access links to previous frames, this
-    // index should not be necessary.
-    size_t m_prev;
+    // Index in call stack.
+    size_t m_index;
 
     // Pointer to the nearest parent frame that contains variable
     // information (script, function, or scope).
@@ -588,8 +588,8 @@
     compiled_fcn_stack_frame (void) = delete;
 
     compiled_fcn_stack_frame (call_stack& cs, octave_function *fcn,
-                              size_t prev, stack_frame *static_link)
-      : stack_frame (cs, prev, static_link, static_link->access_link ()),
+                              size_t index, stack_frame *static_link)
+      : stack_frame (cs, index, static_link, static_link->access_link ()),
         m_fcn (fcn)
     { }
 
@@ -709,7 +709,7 @@
     script_stack_frame (void) = delete;
 
     script_stack_frame (call_stack& cs, octave_user_script *script,
-                        unwind_protect *up_frame, size_t prev,
+                        unwind_protect *up_frame, size_t index,
                         stack_frame *static_link);
 
     script_stack_frame (const script_stack_frame& elt) = default;
@@ -819,9 +819,9 @@
     base_value_stack_frame (void) = delete;
 
     base_value_stack_frame (call_stack& cs, size_t num_symbols,
-                            size_t prev, stack_frame *static_link,
+                            size_t index, stack_frame *static_link,
                             stack_frame *access_link)
-      : stack_frame (cs, prev, static_link, access_link),
+      : stack_frame (cs, index, static_link, access_link),
         m_values (num_symbols, octave_value ()),
         m_flags (num_symbols, LOCAL),
         m_auto_vars (NUM_AUTO_VARS, octave_value ())
@@ -923,10 +923,10 @@
     user_fcn_stack_frame (void) = delete;
 
     user_fcn_stack_frame (call_stack& cs, octave_user_function *fcn,
-                          unwind_protect *up_frame, size_t prev,
+                          unwind_protect *up_frame, size_t index,
                           stack_frame *static_link,
                           stack_frame *access_link = nullptr)
-      : base_value_stack_frame (cs, get_num_symbols (fcn), prev, static_link,
+      : base_value_stack_frame (cs, get_num_symbols (fcn), index, static_link,
                                 (access_link
                                  ? access_link
                                  : get_access_link (fcn, static_link))),
@@ -1009,9 +1009,9 @@
 
     scope_stack_frame (void) = delete;
 
-    scope_stack_frame (call_stack& cs, size_t prev, const symbol_scope& scope,
-                       stack_frame *static_link)
-      : base_value_stack_frame (cs, scope.num_symbols (), prev,
+    scope_stack_frame (call_stack& cs, const symbol_scope& scope,
+                        size_t index, stack_frame *static_link)
+      : base_value_stack_frame (cs, scope.num_symbols (), index,
                                 static_link, nullptr),
         m_scope (scope)
     { }
--- a/libinterp/parse-tree/bp-table.cc	Mon Apr 08 09:22:39 2019 -0700
+++ b/libinterp/parse-tree/bp-table.cc	Mon Apr 08 00:36:33 2019 +0000
@@ -365,7 +365,7 @@
             else
               {
                 // It was a line number.  Get function name from debugger.
-                if (Vdebugging)
+                if (m_evaluator.in_debug_repl ())
                   func_name = m_evaluator.get_user_code ()->profiler_name ();
                 else
                   error ("%s: function name must come before line number "
@@ -615,7 +615,7 @@
           }
       }
 
-    m_evaluator.debug_mode (bp_table::have_breakpoints () || Vdebugging);
+    m_evaluator.reset_debug_state ();
 
     return retval;
   }
@@ -711,7 +711,7 @@
           }
       }
 
-    m_evaluator.debug_mode (bp_table::have_breakpoints () || Vdebugging);
+    m_evaluator.reset_debug_state ();
 
     return retval;
   }
@@ -745,7 +745,7 @@
       error ("remove_all_breakpoint_in_file: "
              "unable to find function %s\n", fname.c_str ());
 
-    m_evaluator.debug_mode (bp_table::have_breakpoints () || Vdebugging);
+    m_evaluator.reset_debug_state ();
 
     return retval;
   }
@@ -762,7 +762,7 @@
         remove_all_breakpoints_in_file (*it);
       }
 
-    m_evaluator.debug_mode (bp_table::have_breakpoints () || Vdebugging);
+    m_evaluator.reset_debug_state ();
   }
 
   std::string find_bkpt_list (octave_value_list slist, std::string match)
--- a/libinterp/parse-tree/pt-eval.cc	Mon Apr 08 09:22:39 2019 -0700
+++ b/libinterp/parse-tree/pt-eval.cc	Mon Apr 08 00:36:33 2019 +0000
@@ -44,6 +44,8 @@
 #include "input.h"
 #include "interpreter-private.h"
 #include "interpreter.h"
+#include "octave-link.h"
+#include "octave.h"
 #include "ov-classdef.h"
 #include "ov-fcn-handle.h"
 #include "ov-usr-fcn.h"
@@ -67,6 +69,230 @@
 {
   // Normal evaluator.
 
+  class debugger
+  {
+  public:
+
+    debugger (interpreter& interp, size_t level)
+      : m_interpreter (interp), m_level (level), m_in_debug_repl (false),
+        m_exit_debug_repl (false), m_abort_debug_repl (false)
+    { }
+
+    void repl (const std::string& prompt = "debug> ");
+
+    bool in_debug_repl (void) const { return m_in_debug_repl; }
+
+    bool in_debug_repl (bool flag)
+    {
+      bool val = m_in_debug_repl;
+      m_in_debug_repl = flag;
+      return val;
+    }
+
+    bool exit_debug_repl (void) const { return m_exit_debug_repl; }
+
+    bool exit_debug_repl (bool flag)
+    {
+      bool val = m_exit_debug_repl;
+      m_exit_debug_repl = flag;
+      return val;
+    }
+
+    bool abort_debug_repl (void) const { return m_abort_debug_repl; }
+
+    bool abort_debug_repl (bool flag)
+    {
+      bool val = m_abort_debug_repl;
+      m_abort_debug_repl = flag;
+      return val;
+    }
+
+  private:
+
+    interpreter& m_interpreter;
+
+    size_t m_level;
+    size_t m_debug_frame;
+    bool m_in_debug_repl;
+    bool m_exit_debug_repl;
+    bool m_abort_debug_repl;
+  };
+
+  static void
+  execute_in_debugger_handler (const std::pair<std::string, int>& arg)
+  {
+    octave_link::execute_in_debugger_event (arg.first, arg.second);
+  }
+
+  void debugger::repl (const std::string& prompt)
+  {
+    unwind_protect frame;
+
+    frame.protect_var (m_in_debug_repl);
+
+    m_in_debug_repl = true;
+
+    tree_evaluator& tw = m_interpreter.get_evaluator ();
+
+    bool silent = tw.quiet_breakpoint_flag (false);
+
+    call_stack& cs = m_interpreter.get_call_stack ();
+
+    frame.add_method (cs, &call_stack::restore_frame,
+                      cs.current_frame ());
+
+    cs.goto_frame (tw.debug_frame ());
+
+    octave_user_code *caller = cs.current_user_code ();
+    std::string nm;
+    int curr_debug_line;
+
+    if (caller)
+      {
+        nm = caller->fcn_file_name ();
+
+        if (nm.empty ())
+          nm = caller->name ();
+
+        curr_debug_line = cs.current_user_code_line ();
+      }
+    else
+      curr_debug_line = cs.current_line ();
+
+    std::ostringstream buf;
+
+    input_system& input_sys = m_interpreter.get_input_system ();
+
+    if (! nm.empty ())
+      {
+        if (input_sys.gud_mode ())
+          {
+            static char ctrl_z = 'Z' & 0x1f;
+
+            buf << ctrl_z << ctrl_z << nm << ':' << curr_debug_line;
+          }
+        else
+          {
+            // FIXME: we should come up with a clean way to detect
+            // that we are stopped on the no-op command that marks the
+            // end of a function or script.
+
+            if (! silent)
+              {
+                stack_frame *frm = cs.current_user_frame ();
+
+                frm->display_stopped_in_message (buf);
+              }
+
+            octave_link::enter_debugger_event (nm, curr_debug_line);
+
+            octave_link::set_workspace ();
+
+            frame.add_fcn (execute_in_debugger_handler,
+                           std::pair<std::string, int> (nm, curr_debug_line));
+
+            if (! silent)
+              {
+                std::string line_buf;
+
+                if (caller)
+                  line_buf = caller->get_code_line (curr_debug_line);
+
+                if (! line_buf.empty ())
+                  buf << curr_debug_line << ": " << line_buf;
+              }
+          }
+      }
+
+    if (silent)
+      command_editor::erase_empty_line (true);
+
+    std::string msg = buf.str ();
+
+    if (! msg.empty ())
+      std::cerr << msg << std::endl;
+
+    std::string tmp_prompt = prompt;
+    if (m_level > 0)
+      tmp_prompt = "[" + std::to_string (m_level) + "]" + prompt;
+
+    frame.add_method (input_sys, &input_system::set_PS1, input_sys.PS1 ());
+    input_sys.PS1 (tmp_prompt);
+
+    // FIXME: should debugging be possible in an embedded interpreter?
+
+    application *app = application::app ();
+
+    if (! app->interactive ())
+      {
+
+        frame.add_method (app, &application::interactive,
+                          app->interactive ());
+
+        frame.add_method (app, &application::forced_interactive,
+                          app->forced_interactive ());
+
+        app->interactive (true);
+
+        app->forced_interactive (true);
+      }
+
+    parser curr_parser (m_interpreter);
+
+    while (m_in_debug_repl)
+      {
+        if (m_exit_debug_repl || m_abort_debug_repl || tw.dbstep_flag ())
+          break;
+
+        try
+          {
+            Vtrack_line_num = false;
+
+            reset_error_handler ();
+
+            curr_parser.reset ();
+
+            int retval = curr_parser.run ();
+
+            if (command_editor::interrupt (false))
+              break;
+            else
+              {
+                if (retval == 0 && curr_parser.m_stmt_list)
+                  {
+                    curr_parser.m_stmt_list->accept (tw);
+
+                    if (octave_completion_matches_called)
+                      octave_completion_matches_called = false;
+
+                    // FIXME: the following statement is here because
+                    // the last command may have been a dbup, dbdown, or
+                    // dbstep command that changed the current debug
+                    // frame.  If so, we need to reset the current frame
+                    // for the call stack.  But is this right way to do
+                    // this job?  What if the statement list was
+                    // something like "dbup; dbstack"?  Will the call to
+                    // dbstack use the right frame?  If not, how can we
+                    // fix this problem?
+                    cs.goto_frame (tw.debug_frame ());
+                  }
+
+                octave_quit ();
+              }
+          }
+        catch (const execution_exception& e)
+          {
+            std::string stack_trace = e.info ();
+
+            if (! stack_trace.empty ())
+              std::cerr << stack_trace;
+
+            // Ignore errors when in debugging mode;
+            interpreter::recover_from_exception ();
+          }
+      }
+  }
+
   bool tree_evaluator::at_top_level (void) const
   {
     return m_call_stack.at_top_level ();
@@ -81,6 +307,12 @@
     m_expr_result_value_list = octave_value_list ();
     m_lvalue_list_stack.clear ();
     m_nargout_stack.clear ();
+
+    while (! m_debugger_stack.empty ())
+      {
+        delete m_debugger_stack.top ();
+        m_debugger_stack.pop ();
+      }
   }
 
   int tree_evaluator::repl (bool interactive)
@@ -207,7 +439,7 @@
   {
     std::string fname;
 
-    octave_user_code *fcn = m_call_stack.caller_user_code ();
+    octave_user_code *fcn = m_call_stack.current_user_code ();
 
     if (fcn)
       {
@@ -844,17 +1076,59 @@
   void
   tree_evaluator::reset_debug_state (void)
   {
-    m_debug_mode = m_bp_table.have_breakpoints () || Vdebugging;
-
-    m_dbstep_flag = 0;
+    m_debug_mode = (m_bp_table.have_breakpoints () ||
+                    m_dbstep_flag != 0 || in_debug_repl ());
   }
 
   void
   tree_evaluator::reset_debug_state (bool mode)
   {
     m_debug_mode = mode;
-
-    m_dbstep_flag = 0;
+  }
+
+  void
+  tree_evaluator::enter_debugger (const std::string& prompt)
+  {
+    octave::unwind_protect frame;
+
+    frame.add_fcn (octave::command_history::ignore_entries,
+                   octave::command_history::ignoring_entries ());
+
+    octave::command_history::ignore_entries (false);
+
+    frame.add_method (m_call_stack, &octave::call_stack::restore_frame,
+                      m_call_stack.current_frame ());
+
+    // Go up to the nearest user code frame.
+    m_call_stack.dbupdown (0);
+
+    // FIXME: probably we just want to print one line, not the
+    // entire statement, which might span many lines...
+    //
+    // tree_print_code tpc (octave_stdout);
+    // stmt.accept (tpc);
+
+    Vtrack_line_num = false;
+
+    debugger *dbgr = new debugger (m_interpreter, m_debugger_stack.size ());
+
+    m_debug_frame = m_call_stack.current_frame ();
+
+    m_debugger_stack.push (dbgr);
+
+    dbgr->repl (prompt);
+  }
+
+  void
+  tree_evaluator::keyboard (const std::string& prompt)
+  {
+    enter_debugger (prompt);
+  }
+
+  void
+  tree_evaluator::dbupdown (int n, bool verbose)
+  {
+    m_debug_frame = m_call_stack.dbupdown (n, verbose);
   }
 
   Matrix
@@ -3244,11 +3518,11 @@
 
     // Act like dbcont.
 
-    if (Vdebugging && m_call_stack.current_frame () == m_current_frame)
+    if (in_debug_repl () && m_call_stack.current_frame () == m_debug_frame)
       {
-        Vdebugging = false;
-
-        reset_debug_state ();
+        m_dbstep_flag = 0;
+
+        exit_debug_repl (true);
       }
     else if (m_statement_context == SC_FUNCTION
              || m_statement_context == SC_SCRIPT
@@ -3913,17 +4187,42 @@
   {
     bool break_on_this_statement = false;
 
+    debugger *curr_debugger
+      = (m_debugger_stack.empty () ? nullptr : m_debugger_stack.top ());
+
+    if (curr_debugger)
+      {
+        if (curr_debugger->exit_debug_repl ())
+          {
+            // This action corresponds to dbcont.
+
+            m_debugger_stack.pop ();
+            delete curr_debugger;
+
+            reset_debug_state ();
+          }
+        else if (curr_debugger->abort_debug_repl ())
+          {
+            // This action corresponds to dbquit.
+
+            m_debugger_stack.pop ();
+            delete curr_debugger;
+
+            debug_mode (false);
+
+            throw interrupt_exception ();
+          }
+      }
+
     if (is_breakpoint)
       {
-        break_on_this_statement = true;
-
         m_dbstep_flag = 0;
 
-        m_current_frame = m_call_stack.current_frame ();
+        enter_debugger ();
       }
     else if (m_dbstep_flag > 0)
       {
-        if (m_call_stack.current_frame () == m_current_frame)
+        if (m_call_stack.current_frame () == m_debug_frame)
           {
             if (m_dbstep_flag == 1 || is_end_of_fcn_or_script)
               {
@@ -3934,8 +4233,6 @@
                 // return to prompt.
 
                 break_on_this_statement = true;
-
-                m_dbstep_flag = 0;
               }
             else
               {
@@ -3946,15 +4243,13 @@
 
           }
         else if (m_dbstep_flag == 1
-                 && m_call_stack.current_frame () < m_current_frame)
+                 && m_call_stack.current_frame () < m_debug_frame)
           {
             // We stepped out from the end of a function.
 
-            m_current_frame = m_call_stack.current_frame ();
+            m_debug_frame = m_call_stack.current_frame ();
 
             break_on_this_statement = true;
-
-            m_dbstep_flag = 0;
           }
       }
     else if (m_dbstep_flag == -1)
@@ -3963,9 +4258,7 @@
 
         break_on_this_statement = true;
 
-        m_dbstep_flag = 0;
-
-        m_current_frame = m_call_stack.current_frame ();
+        m_debug_frame = m_call_stack.current_frame ();
       }
     else if (m_dbstep_flag == -2)
       {
@@ -3976,30 +4269,23 @@
         // that frame.
 
         if (is_end_of_fcn_or_script
-            && m_call_stack.current_frame () == m_current_frame)
+            && m_call_stack.current_frame () == m_debug_frame)
           m_dbstep_flag = -1;
       }
 
     if (break_on_this_statement)
       {
-        input_system& input_sys = m_interpreter.get_input_system ();
-
-        input_sys.keyboard ();
+        m_dbstep_flag = 0;
+
+        // We are stepping so the debugger should already exist.  If
+        // not, something went wrong.
+        if (m_debugger_stack.empty ())
+          error ("internal error: dbstep without an active debugger!");
+
+        m_debugger_stack.top()->repl ();
       }
   }
 
-  // ARGS is currently unused, but since the do_keyboard function in
-  // input.cc accepts an argument list, we preserve it here so that the
-  // interface won't have to change if we decide to use it in the future.
-
-  octave_value
-  tree_evaluator::do_keyboard (const octave_value_list& args) const
-  {
-    input_system& input_sys = m_interpreter.get_input_system ();
-
-    return input_sys.keyboard (args);
-  }
-
   bool
   tree_evaluator::is_logically_true (tree_expression *expr,
                                      const char *warn_for)
@@ -4415,6 +4701,48 @@
     return octave_value ();
   }
 
+  bool tree_evaluator::in_debug_repl (void) const
+  {
+    return (m_debugger_stack.empty ()
+            ? false : m_debugger_stack.top()->in_debug_repl ());
+  }
+
+  bool tree_evaluator::in_debug_repl (bool flag)
+  {
+    if (! m_debugger_stack.empty ())
+      error ("attempt to set in_debug_repl without debugger object");
+
+    return m_debugger_stack.top()->in_debug_repl (flag);
+  }
+
+  bool tree_evaluator::exit_debug_repl (void) const
+  {
+    return (m_debugger_stack.empty ()
+            ? false : m_debugger_stack.top()->exit_debug_repl (true));
+  }
+
+  bool tree_evaluator::exit_debug_repl (bool flag)
+  {
+    if (m_debugger_stack.empty ())
+      error ("attempt to set exit_debug_repl without debugger object");
+
+    return m_debugger_stack.top()->exit_debug_repl (flag);
+  }
+
+  bool tree_evaluator::abort_debug_repl (void) const
+  {
+    return (m_debugger_stack.empty ()
+            ? false : m_debugger_stack.top()->abort_debug_repl ());
+  }
+
+  bool tree_evaluator::abort_debug_repl (bool flag)
+  {
+    if (m_debugger_stack.empty ())
+      error ("attempt to set abort_debug_repl without debugger object");
+
+    return m_debugger_stack.top()->abort_debug_repl (flag);
+  }
+
   octave_value
   tree_evaluator::PS4 (const octave_value_list& args, int nargout)
   {
@@ -4526,7 +4854,7 @@
 
     std::string full_name = nm;
 
-    octave_user_code *fcn = m_call_stack.caller_user_code ();
+    octave_user_code *fcn = m_call_stack.current_user_code ();
 
     bool found = false;
 
--- a/libinterp/parse-tree/pt-eval.h	Mon Apr 08 09:22:39 2019 -0700
+++ b/libinterp/parse-tree/pt-eval.h	Mon Apr 08 00:36:33 2019 +0000
@@ -46,6 +46,7 @@
   class tree_decl_elt;
   class tree_expression;
 
+  class debugger;
   class interpreter;
   class unwind_protect;
 
@@ -130,9 +131,9 @@
         m_result_type (RT_UNDEFINED), m_expr_result_value (),
         m_expr_result_value_list (), m_lvalue_list_stack (),
         m_nargout_stack (), m_autoload_map (), m_bp_table (*this),
-        m_call_stack (*this), m_profiler (), m_current_frame (0),
+        m_call_stack (*this), m_profiler (), m_debug_frame (0),
         m_debug_mode (false), m_quiet_breakpoint_flag (false),
-        m_max_recursion_depth (256),
+        m_debugger_stack (), m_max_recursion_depth (256),
         m_whos_line_format ("  %a:4; %ln:6; %cs:16:6:1;  %rb:12;  %lc:-1;\n"),
         m_silent_functions (false), m_string_fill_char (' '),
         m_PS4 ("+ "), m_dbstep_flag (0), m_echo (ECHO_OFF),
@@ -289,7 +290,11 @@
 
     void reset_debug_state (bool mode);
 
-    void set_dbstep_flag (int step) { m_dbstep_flag = step; }
+    void enter_debugger (const std::string& prompt = "debug> ");
+
+    void keyboard (const std::string& prompt = "keyboard> ");
+
+    void dbupdown (int n, bool verbose = false);
 
     // Possible types of evaluation contexts.
     enum stmt_list_type
@@ -557,12 +562,12 @@
     octave_value
     silent_functions (const octave_value_list& args, int nargout);
 
-    size_t current_frame (void) const { return m_current_frame; }
+    size_t debug_frame (void) const { return m_debug_frame; }
 
-    size_t current_frame (size_t n)
+    size_t debug_frame (size_t n)
     {
-      size_t val = m_current_frame;
-      m_current_frame = n;
+      size_t val = m_debug_frame;
+      m_debug_frame = n;
       return val;
     }
 
@@ -593,6 +598,16 @@
       return val;
     }
 
+    // The following functions are provided for convenience and forward
+    // to the corresponding functions in the debugger class for the
+    // current debugger (if any).
+    bool in_debug_repl (void) const;
+    bool in_debug_repl (bool flag);
+    bool exit_debug_repl (void) const;
+    bool exit_debug_repl (bool flag);
+    bool abort_debug_repl (void) const;
+    bool abort_debug_repl (bool flag);
+
     octave_value PS4 (const octave_value_list& args, int nargout);
 
     std::string PS4 (void) const { return m_PS4; }
@@ -640,6 +655,17 @@
       return val;
     }
 
+    int dbstep_flag (void) const { return m_dbstep_flag; }
+
+    int dbstep_flag (int val)
+    {
+      int old_val = m_dbstep_flag;
+      m_dbstep_flag = val;
+      return old_val;
+    }
+
+    void set_dbstep_flag (int step) { m_dbstep_flag = step; }
+
     octave_value echo (const octave_value_list& args, int nargout);
 
     int echo (void) const { return m_echo; }
@@ -677,9 +703,6 @@
     void do_breakpoint (bool is_breakpoint,
                         bool is_end_of_fcn_or_script = false);
 
-    virtual octave_value
-    do_keyboard (const octave_value_list& args = octave_value_list ()) const;
-
     bool is_logically_true (tree_expression *expr, const char *warn_for);
 
     octave_value_list
@@ -730,12 +753,19 @@
     profiler m_profiler;
 
     // The number of the stack frame we are currently debugging.
-    size_t m_current_frame;
+    size_t m_debug_frame;
 
     bool m_debug_mode;
 
     bool m_quiet_breakpoint_flag;
 
+    // When entering the debugger we push it on this stack.  Managing
+    // debugger invocations this way allows us to handle recursive
+    // debugger calls.  When we exit a debugger the object is popped
+    // from the stack and deleted and we resume working with the
+    // previous debugger (if any) that is now at the top of the stack.
+    std::stack<debugger *> m_debugger_stack;
+
     // Maximum nesting level for functions, scripts, or sourced files
     // called recursively.
     int m_max_recursion_depth;