changeset 23137:334119c390b3

move bp_table class to separate file * libinterp/parse-tree/bp-table.h: Rename from libinterp/corefcn/debug.h. Change all uses. * libinterp/parse-tree/bp-table.cc: New file, extracted from debug.h. * bp-table.h, bp-table.cc (bp_table::parse_dbfunction_params): Now static member function instead of friend of bp_table class. Change all uses. * libinterp/corefcn/module.mk, libinterp/parse-tree/module.mk: Update.
author John W. Eaton <jwe@octave.org>
date Thu, 02 Feb 2017 16:16:26 -0500
parents 2165993aed7d
children e66b3bfea380
files libgui/src/m-editor/file-editor-tab.cc libinterp/corefcn/debug.cc libinterp/corefcn/debug.h libinterp/corefcn/error.cc libinterp/corefcn/input.cc libinterp/corefcn/module.mk libinterp/corefcn/sighandlers.cc libinterp/corefcn/symtab.cc libinterp/parse-tree/bp-table.cc libinterp/parse-tree/bp-table.h libinterp/parse-tree/module.mk libinterp/parse-tree/pt-eval.cc libinterp/parse-tree/pt-jit.cc libinterp/parse-tree/pt-stmt.cc libinterp/parse-tree/pt-stmt.h
diffstat 15 files changed, 1311 insertions(+), 1261 deletions(-) [+]
line wrap: on
line diff
--- a/libgui/src/m-editor/file-editor-tab.cc	Wed Feb 01 22:22:19 2017 +0100
+++ b/libgui/src/m-editor/file-editor-tab.cc	Thu Feb 02 16:16:26 2017 -0500
@@ -69,8 +69,8 @@
 
 #include "file-ops.h"
 
+#include "bp-table.h"
 #include "call-stack.h"
-#include "debug.h"
 #include "octave-qt-link.h"
 #include "version.h"
 #include "utils.h"
--- a/libinterp/corefcn/debug.cc	Wed Feb 01 22:22:19 2017 +0100
+++ b/libinterp/corefcn/debug.cc	Thu Feb 02 16:16:26 2017 -0500
@@ -20,6 +20,7 @@
 <http://www.gnu.org/licenses/>.
 
 */
+
 #if defined (HAVE_CONFIG_H)
 #  include "config.h"
 #endif
@@ -32,957 +33,28 @@
 #include <set>
 #include <string>
 
-#include "file-stat.h"
-#include "singleton-cleanup.h"
+#include "dNDArray.h"
 
+#include "bp-table.h"
 #include "call-stack.h"
 #include "defun.h"
 #include "error.h"
+#include "errwarn.h"
 #include "file-ops.h"
 #include "help.h"
 #include "input.h"
-#include "pager.h"
-#include "octave-link.h"
-#include "ovl.h"
-#include "utils.h"
-#include "parse.h"
-#include "symtab.h"
-#include "errwarn.h"
 #include "octave-preserve-stream-state.h"
+#include "ov-usr-fcn.h"
 #include "ov.h"
-#include "ov-usr-fcn.h"
-#include "ov-fcn.h"
-#include "ov-struct.h"
+#include "ovl.h"
+#include "pager.h"
+#include "parse.h"
 #include "pt-eval.h"
-#include "pt-exp.h"
-#include "pt-stmt.h"
-#include "sighandlers.h"
-#include "interpreter.h"
 #include "unwind-prot.h"
 #include "utils.h"
+#include "utils.h"
 #include "variables.h"
 
-#include "debug.h"
-
-// Initialize the singleton object
-bp_table *bp_table::instance = 0;
-
-std::set<std::string> bp_table::errors_that_stop;
-std::set<std::string> bp_table::caught_that_stop;
-std::set<std::string> bp_table::warnings_that_stop;
-
-// Read entire file called fname and return the contents as a string
-static std::string
-snarf_file (const std::string& fname)
-{
-  std::string retval;
-
-  octave::sys::file_stat fs (fname);
-
-  if (fs)
-    {
-      size_t sz = fs.size ();
-
-      std::ifstream file (fname.c_str (), std::ios::in | std::ios::binary);
-
-      if (file)
-        {
-          std::string buf (sz+1, 0);
-
-          file.read (&buf[0], sz+1);
-
-          if (! file.eof ())
-            error ("error reading file %s", fname.c_str ());
-
-          // Expected to read the entire file.
-          retval = buf;
-        }
-    }
-
-  return retval;
-}
-
-static std::deque<size_t>
-get_line_offsets (const std::string& buf)
-{
-  // FIXME: This could maybe be smarter.  Is deque the right thing to use here?
-
-  std::deque<size_t> offsets;
-
-  offsets.push_back (0);
-
-  size_t len = buf.length ();
-
-  for (size_t i = 0; i < len; i++)
-    {
-      char c = buf[i];
-
-      if (c == '\r' && ++i < len)
-        {
-          c = buf[i];
-
-          if (c == '\n')
-            offsets.push_back (i+1);
-          else
-            offsets.push_back (i);
-        }
-      else if (c == '\n')
-        offsets.push_back (i+1);
-    }
-
-  offsets.push_back (len);
-
-  return offsets;
-}
-
-std::string
-get_file_line (const std::string& fname, size_t line)
-{
-  std::string retval;
-
-  static std::string last_fname;
-
-  static std::string buf;
-
-  static std::deque<size_t> offsets;
-
-  if (fname != last_fname)
-    {
-      buf = snarf_file (fname);
-
-      offsets = get_line_offsets (buf);
-    }
-
-  if (line > 0)
-    line--;
-
-  if (line < offsets.size () - 1)
-    {
-      size_t bol = offsets[line];
-      size_t eol = offsets[line+1];
-
-      while (eol > 0 && eol > bol && (buf[eol-1] == '\n' || buf[eol-1] == '\r'))
-        eol--;
-
-      retval = buf.substr (bol, eol - bol);
-    }
-
-  return retval;
-}
-
-// Return a pointer to the user-defined function FNAME.  If FNAME is
-// empty, search backward for the first user-defined function in the
-// current call stack.
-
-static octave_user_code *
-get_user_code (const std::string& fname = "")
-{
-  octave_user_code *dbg_fcn = 0;
-
-  if (fname.empty ())
-    dbg_fcn = octave::call_stack::debug_user_code ();
-  else
-    {
-      std::string name = fname;
-
-      if (octave::sys::file_ops::dir_sep_char () != '/' && name[0] == '@')
-        {
-          int len = name.length () - 1;         // -1: can't have trailing '/'
-          for (int i = 2; i < len; i++)         //  2: can't have @/method
-            if (name[i] == '/')
-              name[i] = octave::sys::file_ops::dir_sep_char ();
-        }
-
-      size_t name_len = name.length ();
-
-      if (! name.empty () && name_len > 2 && name.substr (name_len-2) == ".m")
-        name = name.substr (0, name_len-2);
-
-      octave_value fcn = symbol_table::find_function (name);
-
-      if (fcn.is_defined () && fcn.is_user_code ())
-        dbg_fcn = fcn.user_code_value ();
-    }
-
-  return dbg_fcn;
-}
-
-#if defined (DBSTOP_NANINF)
-#  include "sigfpe.cc"
-#endif
-
-enum
-dbstop_args {dbstop_in, dbstop_at, dbstop_if, dbstop_none};
-
-// Parse parameters (args) of dbstop and dbclear commands.
-// For dbstop, who=="dbstop"; for dbclear, who=="dbclear".
-// The syntax is: dbstop [[in] symbol] [[at] line [line [...]]] [if condition]
-// where the form of condition depends on whether or not a file or line has
-// been seen.
-// Also execute "if [error|warning|interrupt|naninf]" clauses.
-void
-parse_dbfunction_params (const char *who, const octave_value_list& args,
-                         std::string& symbol_name, bp_table::intmap& lines,
-                         std::string& cond)
-{
-  int nargin = args.length ();
-  int list_idx = 0;
-  symbol_name = "";
-  lines = bp_table::intmap ();
-
-  if (nargin == 0 || ! args(0).is_string ())
-    print_usage (who);
-
-  // elements already processed
-  bool seen_in = false, seen_at = false, seen_if = false;
-  int pos = 0;
-  dbstop_args tok = dbstop_none;
-  while (pos < nargin)
-    {
-      // allow "in" and "at" to be implicit
-      if (args(pos).is_string ())
-        {
-          std::string arg = args(pos).string_value ();
-          if (arg == "in")
-            {
-              tok = dbstop_in;
-              pos++;
-            }
-          else if (arg == "at")
-            {
-              tok = dbstop_at;
-              pos++;
-            }
-          else if (arg == "if")
-            {
-              tok = dbstop_if;
-              pos++;
-            }
-          else if (atoi (args(pos).string_value ().c_str ()) > 0)
-            tok = dbstop_at;
-          else
-            tok = dbstop_in;
-        }
-      else
-        tok = dbstop_at;
-
-      if (pos >= nargin)
-        error ("%s: '%s' missing argument", who,
-               (tok == dbstop_in
-                ? "in" : (tok == dbstop_at ? "at" : "if")));
-
-      // process the actual arguments
-      switch (tok)
-        {
-        case dbstop_in:
-          symbol_name = args(pos).string_value ();
-          if (seen_in)
-            error ("%s: Too many function names specified -- %s",
-                   who, symbol_name.c_str ());
-          else if (seen_at || seen_if)
-            error ("%s: function name must come before line number and 'if'",
-                   who);
-          seen_in = true;
-          pos++;
-          break;
-
-        case dbstop_at:
-          if (seen_at)
-            error ("%s: Only one 'at' clause is allowed -- %s",
-                   who, args(pos).string_value ().c_str ());
-          else if (seen_if)
-            error ("%s: line number must come before 'if' clause\n");
-          seen_at = true;
-
-          if (! seen_in)
-            {
-              // It was a line number.  Get function name from debugger.
-              if (Vdebugging)
-                symbol_name = get_user_code ()->profiler_name ();
-              else
-                error ("%s: function name must come before line number "
-                       "and 'if'", who);
-              seen_in = true;
-            }
-          else if (seen_if)
-            error ("%s: line number must come before 'if' clause\n");
-
-          // Read a list of line numbers (or arrays thereof)
-          for ( ; pos < nargin; pos++)
-            {
-              if (args(pos).is_string ())
-                {
-                  int line = atoi (args(pos).string_value ().c_str ());
-
-                  if (line > 0)
-                    lines[list_idx++] = line;
-                  else
-                    break;        // may be "if"
-                }
-              else if (args(pos).is_numeric_type ())
-                {
-                  const NDArray arg = args(pos).array_value ();
-
-                  for (octave_idx_type j = 0; j < arg.numel (); j++)
-                    lines[list_idx++] = static_cast<int> (arg.elem (j));
-                }
-              else
-                error ("%s: Invalid argument type %s",
-                       args(pos).type_name ().c_str ());
-            }
-          break;
-
-        case dbstop_if:
-          if (seen_in)    // conditional breakpoint
-            {
-              cond = "";  // remaining arguments form condition
-              for (; pos < nargin; pos++)
-                {
-                  if (args(pos).is_string ())
-                    cond = cond + " " + args(pos).string_value ();
-                  else
-                    error ("%s: arguments to 'if' must all be strings", who);
-                }
-
-              cond = cond.substr (1);   // omit initial space
-            }
-          else    // stop on event (error, warning, interrupt, NaN/inf)
-            {
-              std::string condition = args(pos).string_value ();
-              int on_off = ! strcmp(who, "dbstop");
-
-              // list of error/warning IDs to update
-              std::set<std::string> *id_list = NULL;
-              bool *stop_flag = NULL;         // Vdebug_on_... flag
-
-              if (condition == "error")
-                {
-                  id_list = &bp_table::errors_that_stop;
-                  stop_flag = &Vdebug_on_error;
-                }
-              else if (condition == "warning")
-                {
-                  id_list = &bp_table::warnings_that_stop;
-                  stop_flag = &Vdebug_on_warning;
-                }
-              else if (condition == "caught" && nargin > pos+1
-                       && args(pos+1).string_value () == "error")
-                {
-                  id_list = &bp_table::caught_that_stop;
-                  stop_flag = &Vdebug_on_caught;
-                  pos++;
-                }
-              else if (condition == "interrupt")
-                {
-                  octave::Vdebug_on_interrupt = on_off;
-                }
-              else if (condition == "naninf")
-#if defined (DBSTOP_NANINF)
-                {
-                  Vdebug_on_naninf = on_off;
-                  enable_fpe (on_off);
-                }
-#else
-                warning ("%s: condition '%s' not yet supported",
-                         who, condition.c_str ());
-#endif
-              else
-                error ("%s: invalid condition %s",
-                       who, condition.c_str ());
-
-              // process ID list for "dbstop if error <error_ID>" etc
-              if (id_list != NULL)
-                {
-                  pos++;
-                  if (pos < nargin)       // only affect a single error ID
-                    {
-                      if (! args(pos).is_string () || nargin > pos+1)
-                        error ("%s: ID must be a single string", who);
-                      else if (on_off == 1)
-                        {
-                          id_list->insert (args(pos).string_value ());
-                          *stop_flag = 1;
-                        }
-                      else
-                        {
-                          id_list->erase (args(pos).string_value ());
-                          if (id_list->empty ())
-                            *stop_flag = 0;
-                        }
-                    }
-                  else   // unqualified.  Turn all on or off
-                    {
-                      id_list->clear ();
-                      *stop_flag = on_off;
-                      if (stop_flag == &Vdebug_on_error)
-                        {
-                          // Matlab stops on both.
-                          octave::Vdebug_on_interrupt = on_off;
-                        }
-                    }
-                }
-
-              pos = nargin;
-            }
-          break;
-
-        default:      // dbstop_none should never occur
-          break;
-        }
-    }
-}
-
-/*
-%!test
-%! dbclear all;   # Clear out breakpoints before test
-%! dbstop help;
-%! dbstop in ls;
-%! dbstop help at 100;
-%! dbstop in ls 100;
-%! dbstop help 201 if a==5;
-%! dbstop if error Octave:undefined-function;
-%! s = dbstatus;
-%! dbclear all;
-%! assert ({s.bkpt(:).name}, {"help", "help", "help>do_contents", "ls", "ls"});
-%! assert ([s.bkpt(:).line], [48, 100, 201, 58, 100]);
-%! assert (s.errs, {"Octave:undefined-function"});
-*/
-
-// Return true if there is a valid breakpoint table, false otherwise.
-// If no table exists, one is created; false is only returned if this fails.
-bool
-bp_table::instance_ok (void)
-{
-  if (! instance)
-    {
-      instance = new bp_table ();
-
-      if (instance)
-        singleton_cleanup_list::add (cleanup_instance);
-    }
-
-  if (! instance)
-    error ("unable to create breakpoint table!");
-
-  return true;
-}
-
-// Clear all reasons to stop, other than breakpoints
-void
-bp_table::dbclear_all_signals (void)
-{
-  Vdebug_on_error = false;
-  bp_table::errors_that_stop.clear ();
-
-  Vdebug_on_caught = false;
-  bp_table::caught_that_stop.clear ();
-
-  Vdebug_on_warning = false;
-  bp_table::warnings_that_stop.clear ();
-
-  octave::Vdebug_on_interrupt = false;
-}
-
-// Process the "warn", "errs", "caught" and "intr" fields for a call of
-// "dbstop (p)".
-void
-bp_table::dbstop_process_map_args (const octave_map& mv)
-{
-  // process errs
-  // why so many levels of indirection needed?
-  bool fail = false;
-  Cell U = mv.contents ("errs");
-  if (U.numel () != 1)
-    fail = (U.numel () > 1);
-  else
-    {
-      Array<octave_value> W = U.index (static_cast<octave_idx_type> (0));
-      if (W.is_empty () || W(0).is_empty () == 0)
-        Vdebug_on_error = 1;    // like "dbstop if error" with no identifier
-      else if (! W(0).is_cell ())
-        fail = true;
-      else
-        {
-          Cell V = W(0).cell_value ();
-          for (int i = 0; i < V.numel (); i++)
-            {
-              errors_that_stop.insert (V(i).string_value ());
-              Vdebug_on_error = 1;
-            }
-        }
-    }
-  if (fail)
-    error ("dbstop: invalid 'errs' field");
-
-  // process caught
-  // why so many levels of indirection needed?
-  fail = false;
-  U = mv.contents ("caught");
-  if (U.numel () != 1)
-    fail = (U.numel () > 1);
-  else
-    {
-      Array<octave_value> W = U.index (static_cast<octave_idx_type> (0));
-      if (W.is_empty () || W(0).is_empty ())
-        Vdebug_on_caught = 1;    // like "dbstop if caught error" with no ID
-      else if (! W(0).is_cell ())
-        fail = true;
-      else
-        {
-          Cell V = W(0).cell_value ();
-          for (int i = 0; i < V.numel (); i++)
-            {
-              caught_that_stop.insert (V(i).string_value ());
-              Vdebug_on_caught = 1;
-            }
-        }
-    }
-  if (fail)
-    error ("dbstop: invalid 'caught' field");
-
-  // process warn
-  // why so many levels of indirection needed?
-  fail = false;
-  U = mv.contents ("warn");
-  if (U.numel () != 1)
-    fail = (U.numel () > 1);
-  else
-    {
-      Array<octave_value> W = U.index (static_cast<octave_idx_type> (0));
-      if (W.is_empty () || W(0).is_empty ())
-        Vdebug_on_warning = 1;    // like "dbstop if warning" with no identifier
-      else if (! W(0).is_cell ())
-        fail = true;
-      else
-        {
-          Cell V = W(0).cell_value ();
-          for (int i = 0; i < V.numel (); i++)
-            {
-              warnings_that_stop.insert (V(i).string_value ());
-              Vdebug_on_warning = 1;
-            }
-        }
-    }
-  if (fail)
-    error ("dbstop: invalid 'warn' field");
-
-  // process interrupt
-  if (mv.isfield ("intr"))
-    octave::Vdebug_on_interrupt = 1;
-}
-
-// Insert a breakpoint in function fcn at line within file fname,
-// to stop only when condition is true.
-// Record in bp_set that fname contains a breakpoint.
-bool
-bp_table::do_add_breakpoint_1 (octave_user_code *fcn,
-                               const std::string& fname,
-                               const bp_table::intmap& line,
-                               const std::string& condition,
-                               bp_table::intmap& retval)
-{
-  bool found = false;
-
-  octave::tree_statement_list *cmds = fcn->body ();
-
-  std::string file = fcn->fcn_file_name ();
-
-  if (cmds)
-    {
-      retval = cmds->add_breakpoint (file, line, condition);
-
-      for (auto& idx_line_p : retval)
-        {
-          if (idx_line_p.second != 0)
-            {
-              // Normalize to store only the file name.
-              // Otherwise, there can be an entry for both file>subfunction and
-              // file, which causes a crash on dbclear all
-              const char *s = strchr (fname.c_str (), Vfilemarker);
-              if (s)
-                bp_set.insert (fname.substr (0, s - fname.c_str ()));
-              else
-                bp_set.insert (fname);
-              found = true;
-              break;
-            }
-        }
-    }
-
-  return found;
-}
-
-// Cursory check that cond is a valid condition to use for a breakpoint.
-// Currently allows conditions with side-effects, like 'y+=10' and 'y++';
-// it is odd that the former is not flagged by "is_assignment_expression".
-// Throws an exception if not valid.
-bool
-bp_table::condition_valid (const std::string& cond)
-{
-  if (cond.length () > 0)
-    {
-      octave::parser parser (cond + " ;"); // ; to reject partial expr like "y=="
-      parser.reset ();
-      int parse_status = parser.run ();
-      if (parse_status)
-        error ("dbstop: Cannot parse condition '%s'", cond.c_str ());
-      else
-        {
-          octave::tree_statement *stmt = 0;
-          if (! parser.stmt_list)
-            error ("dbstop: "
-                   "condition is not empty, but has nothing to evaluate");
-          else
-            {
-              if (parser.stmt_list->length () == 1
-                  && (stmt = parser.stmt_list->front ())
-                  && stmt->is_expression ())
-                {
-                  octave::tree_expression *expr = stmt->expression ();
-                  if (expr->is_assignment_expression ())
-                    error ("dbstop: condition cannot be an assignment.  "
-                           "Did you mean '=='?");
-                }
-              else
-                error ("dbstop: condition must be an expression");
-            }
-        }
-    }
-  return true;
-}
-
-// Return the sub/nested/main function of MAIN_FCN that contains
-// line number LINENO of the source file.
-// If END_LINE != 0, *END_LINE is set to last line of the returned function.
-static octave_user_code*
-find_fcn_by_line (octave_user_code *main_fcn, int lineno, int *end_line = 0)
-{
-  octave_user_code *retval = 0;
-  octave_user_code *next_fcn = 0;  // 1st function starting after lineno
-
-  // Find innermost nested (or parent) function containing lineno.
-  int earliest_end = std::numeric_limits<int>::max ();
-
-  std::map<std::string, octave_value> subfcns = main_fcn->subfunctions ();
-  for (const auto& str_val_p : subfcns)
-    {
-      if (str_val_p.second.is_user_function ())
-        {
-          auto *dbg_subfcn = str_val_p.second.user_function_value ();
-
-          // Check if lineno is within dbg_subfcn.
-          // FIXME: we could break when beginning_line() > lineno,
-          // but that makes the code "fragile"
-          // if the order of walking subfcns changes,
-          // for a minor speed improvement in non-critical code.
-          if (dbg_subfcn->ending_line () < earliest_end
-              && dbg_subfcn->ending_line () >= lineno
-              && dbg_subfcn->beginning_line () <= lineno)
-            {
-              earliest_end = dbg_subfcn->ending_line ();
-              retval = find_fcn_by_line (dbg_subfcn, lineno, &earliest_end);
-            }
-
-          // Find the first fcn starting after lineno.
-          // This is used if line is not inside any function.
-          if (dbg_subfcn->beginning_line () >= lineno && ! next_fcn)
-            next_fcn = dbg_subfcn;
-        }
-    }
-
-  // The breakpoint is either in the subfunction found above,
-  // or in the main function, which we check now.
-  if (main_fcn->is_user_function ())
-    {
-      int e = dynamic_cast<octave_user_function*> (main_fcn)->ending_line ();
-      if (e >= lineno && e < earliest_end)
-        retval = main_fcn;
-
-      if (! retval)
-        retval = next_fcn;
-    }
-  else  // main_fcn is a script.
-    {
-      if (! retval)
-        retval = main_fcn;
-    }
-
-  if (end_line != 0 && earliest_end < *end_line)
-    *end_line = earliest_end;
-
-  return retval;
-}
-
-// Given file name fname, find the subfunction at line and create
-// a breakpoint there.  Put the system into debug_mode.
-bp_table::intmap
-bp_table::do_add_breakpoint (const std::string& fname,
-                             const bp_table::intmap& line,
-                             const std::string& condition)
-{
-  octave_user_code *main_fcn = get_user_code (fname);
-
-  if (! main_fcn)
-    error ("add_breakpoint: unable to find function '%s'\n", fname.c_str ());
-
-  condition_valid (condition);  // Throw error if condition not valid.
-
-  intmap retval;
-
-  octave_idx_type len = line.size ();
-
-  for (int i = 0; i < len; i++)
-    {
-      const_intmap_iterator m = line.find (i);
-
-      if (m != line.end ())
-        {
-          int lineno = m->second;
-
-          octave_user_code *dbg_fcn = find_fcn_by_line (main_fcn, lineno);
-
-          // We've found the right (sub)function.  Now insert the breakpoint.
-          // We insert all breakpoints.
-          // If multiple are in the same function, we insert multiple times.
-          intmap ret_one;
-          if (dbg_fcn
-              && do_add_breakpoint_1 (dbg_fcn, fname, line, condition, ret_one))
-            retval.insert (std::pair<int,int> (i, ret_one.find (i)->second));
-        }
-    }
-
-  octave::tree_evaluator::debug_mode = bp_table::have_breakpoints ()
-                                       || Vdebugging;
-
-  return retval;
-}
-
-int
-bp_table::do_remove_breakpoint_1 (octave_user_code *fcn,
-                                  const std::string& fname,
-                                  const bp_table::intmap& line)
-{
-  int retval = 0;
-
-  std::string file = fcn->fcn_file_name ();
-
-  octave::tree_statement_list *cmds = fcn->body ();
-
-  // FIXME: move the operation on cmds to the tree_statement_list class?
-
-  if (cmds)
-    {
-      octave_value_list results = cmds->list_breakpoints ();
-
-      if (results.length () > 0)
-        {
-          octave_idx_type len = line.size ();
-
-          for (int i = 0; i < len; i++)
-            {
-              const_intmap_iterator p = line.find (i);
-
-              if (p != line.end ())
-                {
-                  int lineno = p->second;
-
-                  cmds->delete_breakpoint (lineno);
-
-                  if (! file.empty ())
-                    octave_link::update_breakpoint (false, file, lineno);
-                }
-            }
-
-          results = cmds->list_breakpoints ();
-
-          bp_set_iterator it = bp_set.find (fname);
-          if (results.empty () && it != bp_set.end ())
-            bp_set.erase (it);
-        }
-
-      retval = results.length ();
-    }
-
-  return retval;
-}
-
-int
-bp_table::do_remove_breakpoint (const std::string& fname,
-                                const bp_table::intmap& line)
-{
-  int retval = 0;
-
-  octave_idx_type len = line.size ();
-
-  if (len == 0)
-    {
-      intmap results = remove_all_breakpoints_in_file (fname);
-      retval = results.size ();
-    }
-  else
-    {
-      octave_user_code *dbg_fcn = get_user_code (fname);
-
-      if (! dbg_fcn)
-        error ("remove_breakpoint: unable to find function %s\n",
-               fname.c_str ());
-
-      retval = do_remove_breakpoint_1 (dbg_fcn, fname, line);
-
-      // Search subfunctions in the order they appear in the file.
-
-      const std::list<std::string> subfcn_names
-        = dbg_fcn->subfunction_names ();
-
-      std::map<std::string, octave_value> subfcns
-        = dbg_fcn->subfunctions ();
-
-      for (const auto& subf_nm : subfcn_names)
-        {
-          const auto q = subfcns.find (subf_nm);
-
-          if (q != subfcns.end ())
-            {
-              octave_user_code *dbg_subfcn = q->second.user_code_value ();
-
-              retval += do_remove_breakpoint_1 (dbg_subfcn, fname, line);
-            }
-        }
-    }
-
-  octave::tree_evaluator::debug_mode = bp_table::have_breakpoints ()
-                                       || Vdebugging;
-
-  return retval;
-}
-
-// Remove all breakpoints from a file, including those in subfunctions
-bp_table::intmap
-bp_table::do_remove_all_breakpoints_in_file (const std::string& fname,
-                                             bool silent)
-{
-  intmap retval;
-
-  octave_user_code *dbg_fcn = get_user_code (fname);
-
-  if (dbg_fcn)
-    {
-      std::string file = dbg_fcn->fcn_file_name ();
-
-      octave::tree_statement_list *cmds = dbg_fcn->body ();
-
-      if (cmds)
-        {
-          retval = cmds->remove_all_breakpoints (file);
-
-          bp_set_iterator it = bp_set.find (fname);
-          if (it != bp_set.end ())
-            bp_set.erase (it);
-        }
-    }
-  else if (! silent)
-    error ("remove_all_breakpoint_in_file: "
-           "unable to find function %s\n", fname.c_str ());
-
-  octave::tree_evaluator::debug_mode = bp_table::have_breakpoints ()
-                                       || Vdebugging;
-
-  return retval;
-}
-
-void
-bp_table::do_remove_all_breakpoints (void)
-{
-  // Odd loop structure required because delete will invalidate bp_set iterators
-  for (const_bp_set_iterator it=bp_set.begin (), it_next=it;
-       it != bp_set.end ();
-       it=it_next)
-    {
-      ++it_next;
-      remove_all_breakpoints_in_file (*it);
-    }
-
-  octave::tree_evaluator::debug_mode = bp_table::have_breakpoints ()
-                                       || Vdebugging;
-}
-
-std::string
-do_find_bkpt_list (octave_value_list slist, std::string match)
-{
-  std::string retval;
-
-  for (int i = 0; i < slist.length (); i++)
-    {
-      if (slist(i).string_value () == match)
-        {
-          retval = slist(i).string_value ();
-          break;
-        }
-    }
-
-  return retval;
-}
-
-bp_table::fname_bp_map
-bp_table::do_get_breakpoint_list (const octave_value_list& fname_list)
-{
-  fname_bp_map retval;
-
-  // make copy since changes may invalidate iters of bp_set.
-  std::set<std::string> tmp_bp_set = bp_set;
-
-  for (auto& bp_fname : tmp_bp_set)
-    {
-      if (fname_list.empty ()
-          || do_find_bkpt_list (fname_list, bp_fname) != "")
-        {
-          octave_user_code *f = get_user_code (bp_fname);
-
-          if (f)
-            {
-              octave::tree_statement_list *cmds = f->body ();
-
-              // FIXME: move the operation on cmds to the
-              //        tree_statement_list class?
-              if (cmds)
-                {
-                  std::list<bp_type> bkpts = cmds->breakpoints_and_conds ();
-
-                  if (! bkpts.empty ())
-                    retval[bp_fname] = bkpts;
-                }
-
-              // look for breakpoints in subfunctions
-              const std::list<std::string> subf_nm = f->subfunction_names ();
-
-              std::map<std::string, octave_value> subf = f->subfunctions ();
-
-              for (const auto& subfcn_nm : subf_nm)
-                {
-                  const auto q = subf.find (subfcn_nm);
-
-                  if (q != subf.end ())
-                    {
-                      octave_user_code *ff = q->second.user_code_value ();
-
-                      cmds = ff->body ();
-                      if (cmds)
-                        {
-                          std::list<bp_type> bkpts
-                            = cmds->breakpoints_and_conds ();
-
-                          if (! bkpts.empty ())
-                            retval[bp_fname + Vfilemarker + ff->name ()] = bkpts;
-                        }
-                    }
-                }
-            }
-        }
-    }
-
-  return retval;
-}
-
 static octave_value
 intmap_to_ov (const bp_table::intmap& line)
 {
@@ -1105,7 +177,8 @@
   if (args.length() >= 1 && ! args(0).is_map ())
     {
       // explicit function / line / condition
-      parse_dbfunction_params ("dbstop", args, symbol_name, lines, condition);
+      bp_table::parse_dbfunction_params ("dbstop", args, symbol_name,
+                                         lines, condition);
 
       if (lines.size () == 0)
         lines[0] = 1;
@@ -1224,7 +297,8 @@
 
   int nargin = args.length ();
 
-  parse_dbfunction_params ("dbclear", args, symbol_name, lines, dummy);
+  bp_table::parse_dbfunction_params ("dbclear", args, symbol_name,
+                                     lines, dummy);
 
   if (nargin == 1 && symbol_name == "all")
     {
@@ -1240,111 +314,6 @@
   return ovl ();
 }
 
-// Report the status of "dbstop if error ..." and "dbstop if warning ..."
-// If to_screen is true, the output goes to octave_stdout; otherwise it is
-// returned.
-// If dbstop if error is true but no explicit IDs are specified, the return
-// value will have an empty field called "errs".  If IDs are specified, the
-// "errs" field will have a row per ID.  If dbstop if error is false, there
-// is no "errs" field.  The "warn" field is set similarly by dbstop if warning
-octave_map
-bp_table::stop_on_err_warn_status (bool to_screen)
-{
-  octave_map retval;
-
-  // print dbstop if error information
-  if (Vdebug_on_error)
-    {
-      if (errors_that_stop.empty ())
-        {
-          if (to_screen)
-            octave_stdout << "stop if error\n";
-          else
-            retval.assign ("errs", octave_value(""));
-        }
-      else
-        {
-          Cell errs (dim_vector (bp_table::errors_that_stop.size (), 1));
-          int i = 0;
-
-          for (const auto& e : errors_that_stop)
-            {
-              if (to_screen)
-                octave_stdout << "stop if error " << e << "\n";
-              else
-                errs(i++) = e;
-            }
-          if (! to_screen)
-            retval.assign ("errs", octave_value (errs));
-        }
-    }
-
-  // print dbstop if caught error information
-  if (Vdebug_on_caught)
-    {
-      if (caught_that_stop.empty ())
-        {
-          if (to_screen)
-            octave_stdout << "stop if caught error\n";
-          else
-            retval.assign ("caught", octave_value(""));
-        }
-      else
-        {
-          Cell errs (dim_vector (caught_that_stop.size (), 1));
-          int i = 0;
-
-          for (const auto& e : caught_that_stop)
-            {
-              if (to_screen)
-                octave_stdout << "stop if caught error " << e << "\n";
-              else
-                errs(i++) = e;
-            }
-          if (! to_screen)
-            retval.assign ("caught", octave_value (errs));
-        }
-    }
-
-  // print dbstop if warning information
-  if (Vdebug_on_warning)
-    {
-      if (warnings_that_stop.empty ())
-        {
-          if (to_screen)
-            octave_stdout << "stop if warning\n";
-          else
-            retval.assign ("warn", octave_value(""));
-        }
-      else
-        {
-          Cell warn (dim_vector (warnings_that_stop.size (), 1));
-          int i = 0;
-
-          for (const auto& w : warnings_that_stop)
-            {
-              if (to_screen)
-                octave_stdout << "stop if warning " << w << "\n";
-              else
-                warn(i++) = w;
-            }
-          if (! to_screen)
-            retval.assign ("warn", octave_value (warn));
-        }
-    }
-
-  // print dbstop if interrupt information
-  if (octave::Vdebug_on_interrupt)
-    {
-      if (to_screen)
-        octave_stdout << "stop if interrupt\n";
-      else
-        retval.assign ("intr", octave_value ());
-    }
-
-  return retval;
-}
-
 DEFUN (dbstatus, args, nargout,
        doc: /* -*- texinfo -*-
 @deftypefn  {} {} dbstatus
--- a/libinterp/corefcn/debug.h	Wed Feb 01 22:22:19 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,203 +0,0 @@
-/*
-
-Copyright (C) 2001-2016 Ben Sapp
-
-This file is part of Octave.
-
-Octave is free software; you can redistribute it and/or modify it
-under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 3 of the License, or
-(at your option) any later version.
-
-Octave is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with Octave; see the file COPYING.  If not, see
-<http://www.gnu.org/licenses/>.
-
-*/
-
-#if ! defined (octave_debug_h)
-#define octave_debug_h 1
-
-#include "octave-config.h"
-
-#include <map>
-#include <set>
-#include "ov.h"
-#include "dRowVector.h"
-
-class octave_value_list;
-class octave_user_code;
-static std::string bp_empty_string ("");
-
-struct
-bp_type
-{
-  int line;
-  std::string cond;
-
-  bp_type (int l, const std::string& c) : line (l), cond (c) { }
-};
-
-// Interface to breakpoints.
-class
-OCTINTERP_API
-bp_table
-{
-private:
-
-  bp_table (void) : bp_set () { }
-
-  ~bp_table (void) = default;
-
-public:
-
-  // mapping from (FIXME: arbitrary index??) to line number of breakpoint
-  typedef std::map<int, int> intmap;
-
-  typedef intmap::const_iterator const_intmap_iterator;
-  typedef intmap::iterator intmap_iterator;
-
-  typedef std::map <std::string, intmap> fname_line_map;
-
-  typedef fname_line_map::const_iterator const_fname_line_map_iterator;
-  typedef fname_line_map::iterator fname_line_map_iterator;
-
-  typedef std::map <std::string, std::list<bp_type> > fname_bp_map;
-  typedef fname_bp_map::const_iterator const_fname_bp_map_iterator;
-  typedef fname_bp_map::iterator fname_bp_map_iterator;
-
-  static bool instance_ok (void);
-
-  // Add a breakpoint at the nearest executable line.
-  static intmap add_breakpoint (const std::string& fname = "",
-                                const intmap& lines = intmap (),
-                                const std::string& condition = bp_empty_string)
-  {
-    return instance_ok ()
-           ? instance->do_add_breakpoint (fname, lines, condition) : intmap ();
-  }
-
-  // Remove a breakpoint from a line in file.
-  static int remove_breakpoint (const std::string& fname = "",
-                                const intmap& lines = intmap ())
-  {
-    return instance_ok ()
-           ? instance->do_remove_breakpoint (fname, lines) : 0;
-  }
-
-  // Remove all the breakpoints in a specified file.
-  static intmap remove_all_breakpoints_in_file (const std::string& fname,
-                                                bool silent = false)
-  {
-    return instance_ok ()
-           ? instance->do_remove_all_breakpoints_in_file (fname, silent)
-           : intmap ();
-  }
-
-  // Remove all the breakpoints registered with octave.
-  static void remove_all_breakpoints (void)
-  {
-    if (instance_ok ())
-      instance->do_remove_all_breakpoints ();
-  }
-
-  // Return all breakpoints.  Each element of the map is a vector
-  // containing the breakpoints corresponding to a given function name.
-  static fname_bp_map
-  get_breakpoint_list (const octave_value_list& fname_list)
-  {
-    return instance_ok ()
-           ? instance->do_get_breakpoint_list (fname_list) : fname_bp_map ();
-  }
-
-  static bool
-  have_breakpoints (void)
-  {
-    return instance_ok () ? instance->do_have_breakpoints () : 0;
-  }
-
-  // Should we enter debugging for this particular error identifier?
-  static bool
-  debug_on_err (const std::string& ID)
-  {
-    return (errors_that_stop.empty () || errors_that_stop.count (ID));
-  }
-
-  // Should we enter debugging for this particular identifier in a try/catch?
-  static bool
-  debug_on_caught (const std::string& ID)
-  {
-    return (caught_that_stop.empty () || caught_that_stop.count (ID));
-  }
-
-  // Should we enter debugging for this particular warning identifier?
-  static bool
-  debug_on_warn (const std::string& ID)
-  {
-    return (warnings_that_stop.empty () || warnings_that_stop.count (ID));
-  }
-
-  static octave_map stop_on_err_warn_status (bool toScreen);
-
-  static void dbstop_process_map_args (const octave_map& mv);
-
-  static void dbclear_all_signals (void);
-
-  static bool condition_valid (const std::string& cond);
-
-  friend void parse_dbfunction_params (const char *, const octave_value_list&,
-                                       std::string&, bp_table::intmap&,
-                                       std::string&);
-
-private:
-
-  typedef std::set<std::string>::const_iterator const_bp_set_iterator;
-  typedef std::set<std::string>::iterator bp_set_iterator;
-
-  // Set of function (.m file) names containing at least one breakpoint.
-  std::set<std::string> bp_set;
-
-  // Set of error and warning message IDs that cause us to stop
-  // *if* Vdebug_on_error / Vdebug_on_caught / Vdebug_on_warning is set.
-  // Empty means stop on any error / caught error / warning.
-  static std::set<std::string> errors_that_stop;
-  static std::set<std::string> caught_that_stop;
-  static std::set<std::string> warnings_that_stop;
-
-  static bp_table *instance;
-
-  static void cleanup_instance (void) { delete instance; instance = 0; }
-
-  bool do_add_breakpoint_1 (octave_user_code *fcn, const std::string& fname,
-                            const intmap& line, const std::string& condition,
-                            intmap& retval);
-
-  intmap do_add_breakpoint (const std::string& fname, const intmap& lines,
-                            const std::string& condition);
-
-  int do_remove_breakpoint_1 (octave_user_code *fcn, const std::string&,
-                              const intmap& lines);
-
-  int do_remove_breakpoint (const std::string&, const intmap& lines);
-
-  intmap do_remove_all_breakpoints_in_file_1 (octave_user_code *fcn,
-                                              const std::string& fname);
-
-  intmap do_remove_all_breakpoints_in_file (const std::string& fname,
-                                            bool silent);
-
-  void do_remove_all_breakpoints (void);
-
-  fname_bp_map do_get_breakpoint_list (const octave_value_list& fname_list);
-
-  bool do_have_breakpoints (void) { return (! bp_set.empty ()); }
-};
-
-extern std::string get_file_line (const std::string& fname, size_t line);
-
-#endif
--- a/libinterp/corefcn/error.cc	Wed Feb 01 22:22:19 2017 +0100
+++ b/libinterp/corefcn/error.cc	Thu Feb 02 16:16:26 2017 -0500
@@ -32,8 +32,8 @@
 #include <sstream>
 #include <string>
 
+#include "bp-table.h"
 #include "call-stack.h"
-#include "debug.h"
 #include "defun.h"
 #include "error.h"
 #include "input.h"
--- a/libinterp/corefcn/input.cc	Wed Feb 01 22:22:19 2017 +0100
+++ b/libinterp/corefcn/input.cc	Thu Feb 02 16:16:26 2017 -0500
@@ -40,9 +40,9 @@
 #include "quit.h"
 #include "str-vec.h"
 
+#include "bp-table.h"
 #include "builtin-defun-decls.h"
 #include "call-stack.h"
-#include "debug.h"
 #include "defun.h"
 #include "dirfns.h"
 #include "error.h"
--- a/libinterp/corefcn/module.mk	Wed Feb 01 22:22:19 2017 +0100
+++ b/libinterp/corefcn/module.mk	Thu Feb 02 16:16:26 2017 -0500
@@ -24,7 +24,6 @@
   libinterp/corefcn/cdisplay.h \
   libinterp/corefcn/comment-list.h \
   libinterp/corefcn/data.h \
-  libinterp/corefcn/debug.h \
   libinterp/corefcn/defun-dld.h \
   libinterp/corefcn/defun-int.h \
   libinterp/corefcn/defun.h \
--- a/libinterp/corefcn/sighandlers.cc	Wed Feb 01 22:22:19 2017 +0100
+++ b/libinterp/corefcn/sighandlers.cc	Thu Feb 02 16:16:26 2017 -0500
@@ -41,7 +41,7 @@
 #include "singleton-cleanup.h"
 #include "signal-wrappers.h"
 
-#include "debug.h"
+#include "bp-table.h"
 #include "defun.h"
 #include "error.h"
 #include "input.h"
--- a/libinterp/corefcn/symtab.cc	Wed Feb 01 22:22:19 2017 +0100
+++ b/libinterp/corefcn/symtab.cc	Thu Feb 02 16:16:26 2017 -0500
@@ -33,7 +33,7 @@
 #include "oct-time.h"
 #include "singleton-cleanup.h"
 
-#include "debug.h"
+#include "bp-table.h"
 #include "defun.h"
 #include "dirfns.h"
 #include "input.h"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libinterp/parse-tree/bp-table.cc	Thu Feb 02 16:16:26 2017 -0500
@@ -0,0 +1,1078 @@
+/*
+
+Copyright (C) 2001-2016 Ben Sapp
+Copyright (C) 2007-2009 John Swensen
+
+This file is part of Octave.
+
+Octave is free software; you can redistribute it and/or modify it
+under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 3 of the License, or
+(at your option) any later version.
+
+Octave is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Octave; see the file COPYING.  If not, see
+<http://www.gnu.org/licenses/>.
+
+*/
+
+#if defined (HAVE_CONFIG_H)
+#  include "config.h"
+#endif
+
+#include <fstream>
+#include <limits>
+#include <list>
+#include <map>
+#include <set>
+#include <string>
+
+#include "file-ops.h"
+#include "file-stat.h"
+#include "singleton-cleanup.h"
+
+#include "bp-table.h"
+#include "defun-int.h"
+#include "call-stack.h"
+#include "error.h"
+#include "input.h"
+#include "oct-map.h"
+#include "octave-link.h"
+#include "ov-usr-fcn.h"
+#include "ov.h"
+#include "ovl.h"
+#include "pager.h"
+#include "parse.h"
+#include "pt-eval.h"
+#include "pt-exp.h"
+#include "pt-stmt.h"
+#include "sighandlers.h"
+#include "symtab.h"
+
+// Initialize the singleton object
+bp_table *bp_table::instance = 0;
+
+std::set<std::string> bp_table::errors_that_stop;
+std::set<std::string> bp_table::caught_that_stop;
+std::set<std::string> bp_table::warnings_that_stop;
+
+// Read entire file called fname and return the contents as a string
+static std::string
+snarf_file (const std::string& fname)
+{
+  std::string retval;
+
+  octave::sys::file_stat fs (fname);
+
+  if (fs)
+    {
+      size_t sz = fs.size ();
+
+      std::ifstream file (fname.c_str (), std::ios::in | std::ios::binary);
+
+      if (file)
+        {
+          std::string buf (sz+1, 0);
+
+          file.read (&buf[0], sz+1);
+
+          if (! file.eof ())
+            error ("error reading file %s", fname.c_str ());
+
+          // Expected to read the entire file.
+          retval = buf;
+        }
+    }
+
+  return retval;
+}
+
+static std::deque<size_t>
+get_line_offsets (const std::string& buf)
+{
+  // FIXME: This could maybe be smarter.  Is deque the right thing to use here?
+
+  std::deque<size_t> offsets;
+
+  offsets.push_back (0);
+
+  size_t len = buf.length ();
+
+  for (size_t i = 0; i < len; i++)
+    {
+      char c = buf[i];
+
+      if (c == '\r' && ++i < len)
+        {
+          c = buf[i];
+
+          if (c == '\n')
+            offsets.push_back (i+1);
+          else
+            offsets.push_back (i);
+        }
+      else if (c == '\n')
+        offsets.push_back (i+1);
+    }
+
+  offsets.push_back (len);
+
+  return offsets;
+}
+
+std::string
+get_file_line (const std::string& fname, size_t line)
+{
+  std::string retval;
+
+  static std::string last_fname;
+
+  static std::string buf;
+
+  static std::deque<size_t> offsets;
+
+  if (fname != last_fname)
+    {
+      buf = snarf_file (fname);
+
+      offsets = get_line_offsets (buf);
+    }
+
+  if (line > 0)
+    line--;
+
+  if (line < offsets.size () - 1)
+    {
+      size_t bol = offsets[line];
+      size_t eol = offsets[line+1];
+
+      while (eol > 0 && eol > bol && (buf[eol-1] == '\n' || buf[eol-1] == '\r'))
+        eol--;
+
+      retval = buf.substr (bol, eol - bol);
+    }
+
+  return retval;
+}
+
+// Return a pointer to the user-defined function FNAME.  If FNAME is
+// empty, search backward for the first user-defined function in the
+// current call stack.
+
+octave_user_code *
+get_user_code (const std::string& fname)
+{
+  octave_user_code *dbg_fcn = 0;
+
+  if (fname.empty ())
+    dbg_fcn = octave::call_stack::debug_user_code ();
+  else
+    {
+      std::string name = fname;
+
+      if (octave::sys::file_ops::dir_sep_char () != '/' && name[0] == '@')
+        {
+          int len = name.length () - 1;         // -1: can't have trailing '/'
+          for (int i = 2; i < len; i++)         //  2: can't have @/method
+            if (name[i] == '/')
+              name[i] = octave::sys::file_ops::dir_sep_char ();
+        }
+
+      size_t name_len = name.length ();
+
+      if (! name.empty () && name_len > 2 && name.substr (name_len-2) == ".m")
+        name = name.substr (0, name_len-2);
+
+      octave_value fcn = symbol_table::find_function (name);
+
+      if (fcn.is_defined () && fcn.is_user_code ())
+        dbg_fcn = fcn.user_code_value ();
+    }
+
+  return dbg_fcn;
+}
+
+// Return true if there is a valid breakpoint table, false otherwise.
+// If no table exists, one is created; false is only returned if this fails.
+bool
+bp_table::instance_ok (void)
+{
+  if (! instance)
+    {
+      instance = new bp_table ();
+
+      if (instance)
+        singleton_cleanup_list::add (cleanup_instance);
+    }
+
+  if (! instance)
+    error ("unable to create breakpoint table!");
+
+  return true;
+}
+
+// Clear all reasons to stop, other than breakpoints
+void
+bp_table::dbclear_all_signals (void)
+{
+  Vdebug_on_error = false;
+  bp_table::errors_that_stop.clear ();
+
+  Vdebug_on_caught = false;
+  bp_table::caught_that_stop.clear ();
+
+  Vdebug_on_warning = false;
+  bp_table::warnings_that_stop.clear ();
+
+  octave::Vdebug_on_interrupt = false;
+}
+
+// Process the "warn", "errs", "caught" and "intr" fields for a call of
+// "dbstop (p)".
+void
+bp_table::dbstop_process_map_args (const octave_map& mv)
+{
+  // process errs
+  // why so many levels of indirection needed?
+  bool fail = false;
+  Cell U = mv.contents ("errs");
+  if (U.numel () != 1)
+    fail = (U.numel () > 1);
+  else
+    {
+      Array<octave_value> W = U.index (static_cast<octave_idx_type> (0));
+      if (W.is_empty () || W(0).is_empty () == 0)
+        Vdebug_on_error = 1;    // like "dbstop if error" with no identifier
+      else if (! W(0).is_cell ())
+        fail = true;
+      else
+        {
+          Cell V = W(0).cell_value ();
+          for (int i = 0; i < V.numel (); i++)
+            {
+              errors_that_stop.insert (V(i).string_value ());
+              Vdebug_on_error = 1;
+            }
+        }
+    }
+  if (fail)
+    error ("dbstop: invalid 'errs' field");
+
+  // process caught
+  // why so many levels of indirection needed?
+  fail = false;
+  U = mv.contents ("caught");
+  if (U.numel () != 1)
+    fail = (U.numel () > 1);
+  else
+    {
+      Array<octave_value> W = U.index (static_cast<octave_idx_type> (0));
+      if (W.is_empty () || W(0).is_empty ())
+        Vdebug_on_caught = 1;    // like "dbstop if caught error" with no ID
+      else if (! W(0).is_cell ())
+        fail = true;
+      else
+        {
+          Cell V = W(0).cell_value ();
+          for (int i = 0; i < V.numel (); i++)
+            {
+              caught_that_stop.insert (V(i).string_value ());
+              Vdebug_on_caught = 1;
+            }
+        }
+    }
+  if (fail)
+    error ("dbstop: invalid 'caught' field");
+
+  // process warn
+  // why so many levels of indirection needed?
+  fail = false;
+  U = mv.contents ("warn");
+  if (U.numel () != 1)
+    fail = (U.numel () > 1);
+  else
+    {
+      Array<octave_value> W = U.index (static_cast<octave_idx_type> (0));
+      if (W.is_empty () || W(0).is_empty ())
+        Vdebug_on_warning = 1;    // like "dbstop if warning" with no identifier
+      else if (! W(0).is_cell ())
+        fail = true;
+      else
+        {
+          Cell V = W(0).cell_value ();
+          for (int i = 0; i < V.numel (); i++)
+            {
+              warnings_that_stop.insert (V(i).string_value ());
+              Vdebug_on_warning = 1;
+            }
+        }
+    }
+  if (fail)
+    error ("dbstop: invalid 'warn' field");
+
+  // process interrupt
+  if (mv.isfield ("intr"))
+    octave::Vdebug_on_interrupt = 1;
+}
+
+// Insert a breakpoint in function fcn at line within file fname,
+// to stop only when condition is true.
+// Record in bp_set that fname contains a breakpoint.
+bool
+bp_table::do_add_breakpoint_1 (octave_user_code *fcn,
+                               const std::string& fname,
+                               const bp_table::intmap& line,
+                               const std::string& condition,
+                               bp_table::intmap& retval)
+{
+  bool found = false;
+
+  octave::tree_statement_list *cmds = fcn->body ();
+
+  std::string file = fcn->fcn_file_name ();
+
+  if (cmds)
+    {
+      retval = cmds->add_breakpoint (file, line, condition);
+
+      for (auto& idx_line_p : retval)
+        {
+          if (idx_line_p.second != 0)
+            {
+              // Normalize to store only the file name.
+              // Otherwise, there can be an entry for both file>subfunction and
+              // file, which causes a crash on dbclear all
+              const char *s = strchr (fname.c_str (), Vfilemarker);
+              if (s)
+                bp_set.insert (fname.substr (0, s - fname.c_str ()));
+              else
+                bp_set.insert (fname);
+              found = true;
+              break;
+            }
+        }
+    }
+
+  return found;
+}
+
+// Cursory check that cond is a valid condition to use for a breakpoint.
+// Currently allows conditions with side-effects, like 'y+=10' and 'y++';
+// it is odd that the former is not flagged by "is_assignment_expression".
+// Throws an exception if not valid.
+bool
+bp_table::condition_valid (const std::string& cond)
+{
+  if (cond.length () > 0)
+    {
+      octave::parser parser (cond + " ;"); // ; to reject partial expr like "y=="
+      parser.reset ();
+      int parse_status = parser.run ();
+      if (parse_status)
+        error ("dbstop: Cannot parse condition '%s'", cond.c_str ());
+      else
+        {
+          octave::tree_statement *stmt = 0;
+          if (! parser.stmt_list)
+            error ("dbstop: "
+                   "condition is not empty, but has nothing to evaluate");
+          else
+            {
+              if (parser.stmt_list->length () == 1
+                  && (stmt = parser.stmt_list->front ())
+                  && stmt->is_expression ())
+                {
+                  octave::tree_expression *expr = stmt->expression ();
+                  if (expr->is_assignment_expression ())
+                    error ("dbstop: condition cannot be an assignment.  "
+                           "Did you mean '=='?");
+                }
+              else
+                error ("dbstop: condition must be an expression");
+            }
+        }
+    }
+  return true;
+}
+
+enum
+dbstop_args {dbstop_in, dbstop_at, dbstop_if, dbstop_none};
+
+// Parse parameters (args) of dbstop and dbclear commands.
+// For dbstop, who=="dbstop"; for dbclear, who=="dbclear".
+// The syntax is: dbstop [[in] symbol] [[at] line [line [...]]] [if condition]
+// where the form of condition depends on whether or not a file or line has
+// been seen.
+// Also execute "if [error|warning|interrupt|naninf]" clauses.
+void
+bp_table::parse_dbfunction_params (const char *who,
+                                   const octave_value_list& args,
+                                   std::string& symbol_name,
+                                   bp_table::intmap& lines,
+                                   std::string& cond)
+{
+  int nargin = args.length ();
+  int list_idx = 0;
+  symbol_name = "";
+  lines = bp_table::intmap ();
+
+  if (nargin == 0 || ! args(0).is_string ())
+    print_usage (who);
+
+  // elements already processed
+  bool seen_in = false, seen_at = false, seen_if = false;
+  int pos = 0;
+  dbstop_args tok = dbstop_none;
+  while (pos < nargin)
+    {
+      // allow "in" and "at" to be implicit
+      if (args(pos).is_string ())
+        {
+          std::string arg = args(pos).string_value ();
+          if (arg == "in")
+            {
+              tok = dbstop_in;
+              pos++;
+            }
+          else if (arg == "at")
+            {
+              tok = dbstop_at;
+              pos++;
+            }
+          else if (arg == "if")
+            {
+              tok = dbstop_if;
+              pos++;
+            }
+          else if (atoi (args(pos).string_value ().c_str ()) > 0)
+            tok = dbstop_at;
+          else
+            tok = dbstop_in;
+        }
+      else
+        tok = dbstop_at;
+
+      if (pos >= nargin)
+        error ("%s: '%s' missing argument", who,
+               (tok == dbstop_in
+                ? "in" : (tok == dbstop_at ? "at" : "if")));
+
+      // process the actual arguments
+      switch (tok)
+        {
+        case dbstop_in:
+          symbol_name = args(pos).string_value ();
+          if (seen_in)
+            error ("%s: Too many function names specified -- %s",
+                   who, symbol_name.c_str ());
+          else if (seen_at || seen_if)
+            error ("%s: function name must come before line number and 'if'",
+                   who);
+          seen_in = true;
+          pos++;
+          break;
+
+        case dbstop_at:
+          if (seen_at)
+            error ("%s: Only one 'at' clause is allowed -- %s",
+                   who, args(pos).string_value ().c_str ());
+          else if (seen_if)
+            error ("%s: line number must come before 'if' clause\n");
+          seen_at = true;
+
+          if (! seen_in)
+            {
+              // It was a line number.  Get function name from debugger.
+              if (Vdebugging)
+                symbol_name = get_user_code ()->profiler_name ();
+              else
+                error ("%s: function name must come before line number "
+                       "and 'if'", who);
+              seen_in = true;
+            }
+          else if (seen_if)
+            error ("%s: line number must come before 'if' clause\n");
+
+          // Read a list of line numbers (or arrays thereof)
+          for ( ; pos < nargin; pos++)
+            {
+              if (args(pos).is_string ())
+                {
+                  int line = atoi (args(pos).string_value ().c_str ());
+
+                  if (line > 0)
+                    lines[list_idx++] = line;
+                  else
+                    break;        // may be "if"
+                }
+              else if (args(pos).is_numeric_type ())
+                {
+                  const NDArray arg = args(pos).array_value ();
+
+                  for (octave_idx_type j = 0; j < arg.numel (); j++)
+                    lines[list_idx++] = static_cast<int> (arg.elem (j));
+                }
+              else
+                error ("%s: Invalid argument type %s",
+                       args(pos).type_name ().c_str ());
+            }
+          break;
+
+        case dbstop_if:
+          if (seen_in)    // conditional breakpoint
+            {
+              cond = "";  // remaining arguments form condition
+              for (; pos < nargin; pos++)
+                {
+                  if (args(pos).is_string ())
+                    cond = cond + " " + args(pos).string_value ();
+                  else
+                    error ("%s: arguments to 'if' must all be strings", who);
+                }
+
+              cond = cond.substr (1);   // omit initial space
+            }
+          else    // stop on event (error, warning, interrupt, NaN/inf)
+            {
+              std::string condition = args(pos).string_value ();
+              int on_off = ! strcmp(who, "dbstop");
+
+              // list of error/warning IDs to update
+              std::set<std::string> *id_list = NULL;
+              bool *stop_flag = NULL;         // Vdebug_on_... flag
+
+              if (condition == "error")
+                {
+                  id_list = &bp_table::errors_that_stop;
+                  stop_flag = &Vdebug_on_error;
+                }
+              else if (condition == "warning")
+                {
+                  id_list = &bp_table::warnings_that_stop;
+                  stop_flag = &Vdebug_on_warning;
+                }
+              else if (condition == "caught" && nargin > pos+1
+                       && args(pos+1).string_value () == "error")
+                {
+                  id_list = &bp_table::caught_that_stop;
+                  stop_flag = &Vdebug_on_caught;
+                  pos++;
+                }
+              else if (condition == "interrupt")
+                {
+                  octave::Vdebug_on_interrupt = on_off;
+                }
+              else if (condition == "naninf")
+                {
+#if defined (DBSTOP_NANINF)
+                  Vdebug_on_naninf = on_off;
+                  enable_fpe (on_off);
+#else
+                  warning ("%s: condition '%s' not yet supported",
+                           who, condition.c_str ());
+#endif
+                }
+              else
+                error ("%s: invalid condition %s",
+                       who, condition.c_str ());
+
+              // process ID list for "dbstop if error <error_ID>" etc
+              if (id_list != NULL)
+                {
+                  pos++;
+                  if (pos < nargin)       // only affect a single error ID
+                    {
+                      if (! args(pos).is_string () || nargin > pos+1)
+                        error ("%s: ID must be a single string", who);
+                      else if (on_off == 1)
+                        {
+                          id_list->insert (args(pos).string_value ());
+                          *stop_flag = 1;
+                        }
+                      else
+                        {
+                          id_list->erase (args(pos).string_value ());
+                          if (id_list->empty ())
+                            *stop_flag = 0;
+                        }
+                    }
+                  else   // unqualified.  Turn all on or off
+                    {
+                      id_list->clear ();
+                      *stop_flag = on_off;
+                      if (stop_flag == &Vdebug_on_error)
+                        {
+                          // Matlab stops on both.
+                          octave::Vdebug_on_interrupt = on_off;
+                        }
+                    }
+                }
+
+              pos = nargin;
+            }
+          break;
+
+        default:      // dbstop_none should never occur
+          break;
+        }
+    }
+}
+
+/*
+%!test
+%! dbclear all;   # Clear out breakpoints before test
+%! dbstop help;
+%! dbstop in ls;
+%! dbstop help at 100;
+%! dbstop in ls 100;
+%! dbstop help 201 if a==5;
+%! dbstop if error Octave:undefined-function;
+%! s = dbstatus;
+%! dbclear all;
+%! assert ({s.bkpt(:).name}, {"help", "help", "help>do_contents", "ls", "ls"});
+%! assert ([s.bkpt(:).line], [48, 100, 201, 58, 100]);
+%! assert (s.errs, {"Octave:undefined-function"});
+*/
+
+// Return the sub/nested/main function of MAIN_FCN that contains
+// line number LINENO of the source file.
+// If END_LINE != 0, *END_LINE is set to last line of the returned function.
+static octave_user_code*
+find_fcn_by_line (octave_user_code *main_fcn, int lineno, int *end_line = 0)
+{
+  octave_user_code *retval = 0;
+  octave_user_code *next_fcn = 0;  // 1st function starting after lineno
+
+  // Find innermost nested (or parent) function containing lineno.
+  int earliest_end = std::numeric_limits<int>::max ();
+
+  std::map<std::string, octave_value> subfcns = main_fcn->subfunctions ();
+  for (const auto& str_val_p : subfcns)
+    {
+      if (str_val_p.second.is_user_function ())
+        {
+          auto *dbg_subfcn = str_val_p.second.user_function_value ();
+
+          // Check if lineno is within dbg_subfcn.
+          // FIXME: we could break when beginning_line() > lineno,
+          // but that makes the code "fragile"
+          // if the order of walking subfcns changes,
+          // for a minor speed improvement in non-critical code.
+          if (dbg_subfcn->ending_line () < earliest_end
+              && dbg_subfcn->ending_line () >= lineno
+              && dbg_subfcn->beginning_line () <= lineno)
+            {
+              earliest_end = dbg_subfcn->ending_line ();
+              retval = find_fcn_by_line (dbg_subfcn, lineno, &earliest_end);
+            }
+
+          // Find the first fcn starting after lineno.
+          // This is used if line is not inside any function.
+          if (dbg_subfcn->beginning_line () >= lineno && ! next_fcn)
+            next_fcn = dbg_subfcn;
+        }
+    }
+
+  // The breakpoint is either in the subfunction found above,
+  // or in the main function, which we check now.
+  if (main_fcn->is_user_function ())
+    {
+      int e = dynamic_cast<octave_user_function*> (main_fcn)->ending_line ();
+      if (e >= lineno && e < earliest_end)
+        retval = main_fcn;
+
+      if (! retval)
+        retval = next_fcn;
+    }
+  else  // main_fcn is a script.
+    {
+      if (! retval)
+        retval = main_fcn;
+    }
+
+  if (end_line != 0 && earliest_end < *end_line)
+    *end_line = earliest_end;
+
+  return retval;
+}
+
+// Given file name fname, find the subfunction at line and create
+// a breakpoint there.  Put the system into debug_mode.
+bp_table::intmap
+bp_table::do_add_breakpoint (const std::string& fname,
+                             const bp_table::intmap& line,
+                             const std::string& condition)
+{
+  octave_user_code *main_fcn = get_user_code (fname);
+
+  if (! main_fcn)
+    error ("add_breakpoint: unable to find function '%s'\n", fname.c_str ());
+
+  condition_valid (condition);  // Throw error if condition not valid.
+
+  intmap retval;
+
+  octave_idx_type len = line.size ();
+
+  for (int i = 0; i < len; i++)
+    {
+      const_intmap_iterator m = line.find (i);
+
+      if (m != line.end ())
+        {
+          int lineno = m->second;
+
+          octave_user_code *dbg_fcn = find_fcn_by_line (main_fcn, lineno);
+
+          // We've found the right (sub)function.  Now insert the breakpoint.
+          // We insert all breakpoints.
+          // If multiple are in the same function, we insert multiple times.
+          intmap ret_one;
+          if (dbg_fcn
+              && do_add_breakpoint_1 (dbg_fcn, fname, line, condition, ret_one))
+            retval.insert (std::pair<int,int> (i, ret_one.find (i)->second));
+        }
+    }
+
+  octave::tree_evaluator::debug_mode = bp_table::have_breakpoints ()
+                                       || Vdebugging;
+
+  return retval;
+}
+
+int
+bp_table::do_remove_breakpoint_1 (octave_user_code *fcn,
+                                  const std::string& fname,
+                                  const bp_table::intmap& line)
+{
+  int retval = 0;
+
+  std::string file = fcn->fcn_file_name ();
+
+  octave::tree_statement_list *cmds = fcn->body ();
+
+  // FIXME: move the operation on cmds to the tree_statement_list class?
+
+  if (cmds)
+    {
+      octave_value_list results = cmds->list_breakpoints ();
+
+      if (results.length () > 0)
+        {
+          octave_idx_type len = line.size ();
+
+          for (int i = 0; i < len; i++)
+            {
+              const_intmap_iterator p = line.find (i);
+
+              if (p != line.end ())
+                {
+                  int lineno = p->second;
+
+                  cmds->delete_breakpoint (lineno);
+
+                  if (! file.empty ())
+                    octave_link::update_breakpoint (false, file, lineno);
+                }
+            }
+
+          results = cmds->list_breakpoints ();
+
+          bp_set_iterator it = bp_set.find (fname);
+          if (results.empty () && it != bp_set.end ())
+            bp_set.erase (it);
+        }
+
+      retval = results.length ();
+    }
+
+  return retval;
+}
+
+int
+bp_table::do_remove_breakpoint (const std::string& fname,
+                                const bp_table::intmap& line)
+{
+  int retval = 0;
+
+  octave_idx_type len = line.size ();
+
+  if (len == 0)
+    {
+      intmap results = remove_all_breakpoints_in_file (fname);
+      retval = results.size ();
+    }
+  else
+    {
+      octave_user_code *dbg_fcn = get_user_code (fname);
+
+      if (! dbg_fcn)
+        error ("remove_breakpoint: unable to find function %s\n",
+               fname.c_str ());
+
+      retval = do_remove_breakpoint_1 (dbg_fcn, fname, line);
+
+      // Search subfunctions in the order they appear in the file.
+
+      const std::list<std::string> subfcn_names
+        = dbg_fcn->subfunction_names ();
+
+      std::map<std::string, octave_value> subfcns
+        = dbg_fcn->subfunctions ();
+
+      for (const auto& subf_nm : subfcn_names)
+        {
+          const auto q = subfcns.find (subf_nm);
+
+          if (q != subfcns.end ())
+            {
+              octave_user_code *dbg_subfcn = q->second.user_code_value ();
+
+              retval += do_remove_breakpoint_1 (dbg_subfcn, fname, line);
+            }
+        }
+    }
+
+  octave::tree_evaluator::debug_mode = bp_table::have_breakpoints ()
+                                       || Vdebugging;
+
+  return retval;
+}
+
+// Remove all breakpoints from a file, including those in subfunctions
+bp_table::intmap
+bp_table::do_remove_all_breakpoints_in_file (const std::string& fname,
+                                             bool silent)
+{
+  intmap retval;
+
+  octave_user_code *dbg_fcn = get_user_code (fname);
+
+  if (dbg_fcn)
+    {
+      std::string file = dbg_fcn->fcn_file_name ();
+
+      octave::tree_statement_list *cmds = dbg_fcn->body ();
+
+      if (cmds)
+        {
+          retval = cmds->remove_all_breakpoints (file);
+
+          bp_set_iterator it = bp_set.find (fname);
+          if (it != bp_set.end ())
+            bp_set.erase (it);
+        }
+    }
+  else if (! silent)
+    error ("remove_all_breakpoint_in_file: "
+           "unable to find function %s\n", fname.c_str ());
+
+  octave::tree_evaluator::debug_mode = bp_table::have_breakpoints ()
+                                       || Vdebugging;
+
+  return retval;
+}
+
+void
+bp_table::do_remove_all_breakpoints (void)
+{
+  // Odd loop structure required because delete will invalidate bp_set iterators
+  for (const_bp_set_iterator it=bp_set.begin (), it_next=it;
+       it != bp_set.end ();
+       it=it_next)
+    {
+      ++it_next;
+      remove_all_breakpoints_in_file (*it);
+    }
+
+  octave::tree_evaluator::debug_mode = bp_table::have_breakpoints ()
+                                       || Vdebugging;
+}
+
+std::string
+do_find_bkpt_list (octave_value_list slist, std::string match)
+{
+  std::string retval;
+
+  for (int i = 0; i < slist.length (); i++)
+    {
+      if (slist(i).string_value () == match)
+        {
+          retval = slist(i).string_value ();
+          break;
+        }
+    }
+
+  return retval;
+}
+
+bp_table::fname_bp_map
+bp_table::do_get_breakpoint_list (const octave_value_list& fname_list)
+{
+  fname_bp_map retval;
+
+  // make copy since changes may invalidate iters of bp_set.
+  std::set<std::string> tmp_bp_set = bp_set;
+
+  for (auto& bp_fname : tmp_bp_set)
+    {
+      if (fname_list.empty ()
+          || do_find_bkpt_list (fname_list, bp_fname) != "")
+        {
+          octave_user_code *f = get_user_code (bp_fname);
+
+          if (f)
+            {
+              octave::tree_statement_list *cmds = f->body ();
+
+              // FIXME: move the operation on cmds to the
+              //        tree_statement_list class?
+              if (cmds)
+                {
+                  std::list<bp_type> bkpts = cmds->breakpoints_and_conds ();
+
+                  if (! bkpts.empty ())
+                    retval[bp_fname] = bkpts;
+                }
+
+              // look for breakpoints in subfunctions
+              const std::list<std::string> subf_nm = f->subfunction_names ();
+
+              std::map<std::string, octave_value> subf = f->subfunctions ();
+
+              for (const auto& subfcn_nm : subf_nm)
+                {
+                  const auto q = subf.find (subfcn_nm);
+
+                  if (q != subf.end ())
+                    {
+                      octave_user_code *ff = q->second.user_code_value ();
+
+                      cmds = ff->body ();
+                      if (cmds)
+                        {
+                          std::list<bp_type> bkpts
+                            = cmds->breakpoints_and_conds ();
+
+                          if (! bkpts.empty ())
+                            retval[bp_fname + Vfilemarker + ff->name ()] = bkpts;
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+  return retval;
+}
+
+// Report the status of "dbstop if error ..." and "dbstop if warning ..."
+// If to_screen is true, the output goes to octave_stdout; otherwise it is
+// returned.
+// If dbstop if error is true but no explicit IDs are specified, the return
+// value will have an empty field called "errs".  If IDs are specified, the
+// "errs" field will have a row per ID.  If dbstop if error is false, there
+// is no "errs" field.  The "warn" field is set similarly by dbstop if warning
+octave_map
+bp_table::stop_on_err_warn_status (bool to_screen)
+{
+  octave_map retval;
+
+  // print dbstop if error information
+  if (Vdebug_on_error)
+    {
+      if (errors_that_stop.empty ())
+        {
+          if (to_screen)
+            octave_stdout << "stop if error\n";
+          else
+            retval.assign ("errs", octave_value(""));
+        }
+      else
+        {
+          Cell errs (dim_vector (bp_table::errors_that_stop.size (), 1));
+          int i = 0;
+
+          for (const auto& e : errors_that_stop)
+            {
+              if (to_screen)
+                octave_stdout << "stop if error " << e << "\n";
+              else
+                errs(i++) = e;
+            }
+          if (! to_screen)
+            retval.assign ("errs", octave_value (errs));
+        }
+    }
+
+  // print dbstop if caught error information
+  if (Vdebug_on_caught)
+    {
+      if (caught_that_stop.empty ())
+        {
+          if (to_screen)
+            octave_stdout << "stop if caught error\n";
+          else
+            retval.assign ("caught", octave_value(""));
+        }
+      else
+        {
+          Cell errs (dim_vector (caught_that_stop.size (), 1));
+          int i = 0;
+
+          for (const auto& e : caught_that_stop)
+            {
+              if (to_screen)
+                octave_stdout << "stop if caught error " << e << "\n";
+              else
+                errs(i++) = e;
+            }
+          if (! to_screen)
+            retval.assign ("caught", octave_value (errs));
+        }
+    }
+
+  // print dbstop if warning information
+  if (Vdebug_on_warning)
+    {
+      if (warnings_that_stop.empty ())
+        {
+          if (to_screen)
+            octave_stdout << "stop if warning\n";
+          else
+            retval.assign ("warn", octave_value(""));
+        }
+      else
+        {
+          Cell warn (dim_vector (warnings_that_stop.size (), 1));
+          int i = 0;
+
+          for (const auto& w : warnings_that_stop)
+            {
+              if (to_screen)
+                octave_stdout << "stop if warning " << w << "\n";
+              else
+                warn(i++) = w;
+            }
+          if (! to_screen)
+            retval.assign ("warn", octave_value (warn));
+        }
+    }
+
+  // print dbstop if interrupt information
+  if (octave::Vdebug_on_interrupt)
+    {
+      if (to_screen)
+        octave_stdout << "stop if interrupt\n";
+      else
+        retval.assign ("intr", octave_value ());
+    }
+
+  return retval;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libinterp/parse-tree/bp-table.h	Thu Feb 02 16:16:26 2017 -0500
@@ -0,0 +1,206 @@
+/*
+
+Copyright (C) 2001-2016 Ben Sapp
+
+This file is part of Octave.
+
+Octave is free software; you can redistribute it and/or modify it
+under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 3 of the License, or
+(at your option) any later version.
+
+Octave is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Octave; see the file COPYING.  If not, see
+<http://www.gnu.org/licenses/>.
+
+*/
+
+#if ! defined (octave_bp_table_h)
+#define octave_bp_table_h 1
+
+#include "octave-config.h"
+
+#include <list>
+#include <map>
+#include <set>
+
+class octave_map;
+class octave_user_code;
+class octave_value_list;
+
+static std::string bp_empty_string ("");
+
+struct
+bp_type
+{
+  int line;
+  std::string cond;
+
+  bp_type (int l, const std::string& c) : line (l), cond (c) { }
+};
+
+// Interface to breakpoints.
+class
+OCTINTERP_API
+bp_table
+{
+private:
+
+  bp_table (void) : bp_set () { }
+
+  ~bp_table (void) = default;
+
+public:
+
+  // mapping from (FIXME: arbitrary index??) to line number of breakpoint
+  typedef std::map<int, int> intmap;
+
+  typedef intmap::const_iterator const_intmap_iterator;
+  typedef intmap::iterator intmap_iterator;
+
+  typedef std::map <std::string, intmap> fname_line_map;
+
+  typedef fname_line_map::const_iterator const_fname_line_map_iterator;
+  typedef fname_line_map::iterator fname_line_map_iterator;
+
+  typedef std::map <std::string, std::list<bp_type> > fname_bp_map;
+  typedef fname_bp_map::const_iterator const_fname_bp_map_iterator;
+  typedef fname_bp_map::iterator fname_bp_map_iterator;
+
+  static bool instance_ok (void);
+
+  // Add a breakpoint at the nearest executable line.
+  static intmap add_breakpoint (const std::string& fname = "",
+                                const intmap& lines = intmap (),
+                                const std::string& condition = bp_empty_string)
+  {
+    return instance_ok ()
+           ? instance->do_add_breakpoint (fname, lines, condition) : intmap ();
+  }
+
+  // Remove a breakpoint from a line in file.
+  static int remove_breakpoint (const std::string& fname = "",
+                                const intmap& lines = intmap ())
+  {
+    return instance_ok ()
+           ? instance->do_remove_breakpoint (fname, lines) : 0;
+  }
+
+  // Remove all the breakpoints in a specified file.
+  static intmap remove_all_breakpoints_in_file (const std::string& fname,
+                                                bool silent = false)
+  {
+    return instance_ok ()
+           ? instance->do_remove_all_breakpoints_in_file (fname, silent)
+           : intmap ();
+  }
+
+  // Remove all the breakpoints registered with octave.
+  static void remove_all_breakpoints (void)
+  {
+    if (instance_ok ())
+      instance->do_remove_all_breakpoints ();
+  }
+
+  // Return all breakpoints.  Each element of the map is a vector
+  // containing the breakpoints corresponding to a given function name.
+  static fname_bp_map
+  get_breakpoint_list (const octave_value_list& fname_list)
+  {
+    return instance_ok ()
+           ? instance->do_get_breakpoint_list (fname_list) : fname_bp_map ();
+  }
+
+  static bool
+  have_breakpoints (void)
+  {
+    return instance_ok () ? instance->do_have_breakpoints () : 0;
+  }
+
+  // Should we enter debugging for this particular error identifier?
+  static bool
+  debug_on_err (const std::string& ID)
+  {
+    return (errors_that_stop.empty () || errors_that_stop.count (ID));
+  }
+
+  // Should we enter debugging for this particular identifier in a try/catch?
+  static bool
+  debug_on_caught (const std::string& ID)
+  {
+    return (caught_that_stop.empty () || caught_that_stop.count (ID));
+  }
+
+  // Should we enter debugging for this particular warning identifier?
+  static bool
+  debug_on_warn (const std::string& ID)
+  {
+    return (warnings_that_stop.empty () || warnings_that_stop.count (ID));
+  }
+
+  static octave_map stop_on_err_warn_status (bool toScreen);
+
+  static void dbstop_process_map_args (const octave_map& mv);
+
+  static void dbclear_all_signals (void);
+
+  static bool condition_valid (const std::string& cond);
+
+  static void parse_dbfunction_params (const char *, const octave_value_list&,
+                                       std::string&, bp_table::intmap&,
+                                       std::string&);
+
+private:
+
+  typedef std::set<std::string>::const_iterator const_bp_set_iterator;
+  typedef std::set<std::string>::iterator bp_set_iterator;
+
+  // Set of function (.m file) names containing at least one breakpoint.
+  std::set<std::string> bp_set;
+
+  // Set of error and warning message IDs that cause us to stop
+  // *if* Vdebug_on_error / Vdebug_on_caught / Vdebug_on_warning is set.
+  // Empty means stop on any error / caught error / warning.
+  static std::set<std::string> errors_that_stop;
+  static std::set<std::string> caught_that_stop;
+  static std::set<std::string> warnings_that_stop;
+
+  static bp_table *instance;
+
+  static void cleanup_instance (void) { delete instance; instance = 0; }
+
+  bool do_add_breakpoint_1 (octave_user_code *fcn, const std::string& fname,
+                            const intmap& line, const std::string& condition,
+                            intmap& retval);
+
+  intmap do_add_breakpoint (const std::string& fname, const intmap& lines,
+                            const std::string& condition);
+
+  int do_remove_breakpoint_1 (octave_user_code *fcn, const std::string&,
+                              const intmap& lines);
+
+  int do_remove_breakpoint (const std::string&, const intmap& lines);
+
+  intmap do_remove_all_breakpoints_in_file_1 (octave_user_code *fcn,
+                                              const std::string& fname);
+
+  intmap do_remove_all_breakpoints_in_file (const std::string& fname,
+                                            bool silent);
+
+  void do_remove_all_breakpoints (void);
+
+  fname_bp_map do_get_breakpoint_list (const octave_value_list& fname_list);
+
+  bool do_have_breakpoints (void) { return (! bp_set.empty ()); }
+};
+
+extern std::string get_file_line (const std::string& fname, size_t line);
+
+extern octave_user_code *get_user_code (const std::string& fname = "");
+
+#endif
--- a/libinterp/parse-tree/module.mk	Wed Feb 01 22:22:19 2017 +0100
+++ b/libinterp/parse-tree/module.mk	Thu Feb 02 16:16:26 2017 -0500
@@ -1,4 +1,5 @@
 PARSE_TREE_INC = \
+  libinterp/parse-tree/bp-table.h \
   libinterp/parse-tree/jit-ir.h \
   libinterp/parse-tree/jit-typeinfo.h \
   libinterp/parse-tree/jit-util.h \
@@ -43,6 +44,7 @@
 ## be distributed but not installed.
 
 PARSE_TREE_SRC = \
+  libinterp/parse-tree/bp-table.cc \
   libinterp/parse-tree/jit-ir.cc \
   libinterp/parse-tree/jit-typeinfo.cc \
   libinterp/parse-tree/jit-util.cc \
--- a/libinterp/parse-tree/pt-eval.cc	Wed Feb 01 22:22:19 2017 +0100
+++ b/libinterp/parse-tree/pt-eval.cc	Thu Feb 02 16:16:26 2017 -0500
@@ -31,8 +31,8 @@
 #include <fstream>
 #include <typeinfo>
 
+#include "bp-table.h"
 #include "call-stack.h"
-#include "debug.h"
 #include "defun.h"
 #include "error.h"
 #include "errwarn.h"
--- a/libinterp/parse-tree/pt-jit.cc	Wed Feb 01 22:22:19 2017 +0100
+++ b/libinterp/parse-tree/pt-jit.cc	Thu Feb 02 16:16:26 2017 -0500
@@ -29,7 +29,7 @@
 #  include "config.h"
 #endif
 
-#include "debug.h"
+#include "bp-table.h"
 #include "defun.h"
 #include "errwarn.h"
 #include "ov.h"
--- a/libinterp/parse-tree/pt-stmt.cc	Wed Feb 01 22:22:19 2017 +0100
+++ b/libinterp/parse-tree/pt-stmt.cc	Thu Feb 02 16:16:26 2017 -0500
@@ -28,13 +28,14 @@
 
 #include "quit.h"
 
+#include "bp-table.h"
 #include "defun.h"
 #include "error.h"
 #include "errwarn.h"
-#include "ov.h"
+#include "input.h"
+#include "oct-lvalue.h"
 #include "octave-link.h"
-#include "oct-lvalue.h"
-#include "input.h"
+#include "ov.h"
 #include "pager.h"
 #include "pt-bp.h"
 #include "pt-cmd.h"
@@ -48,8 +49,6 @@
 #include "utils.h"
 #include "variables.h"
 
-#include "debug.h"
-
 namespace octave
 {
   // A list of commands to be executed.
--- a/libinterp/parse-tree/pt-stmt.h	Wed Feb 01 22:22:19 2017 +0100
+++ b/libinterp/parse-tree/pt-stmt.h	Thu Feb 02 16:16:26 2017 -0500
@@ -30,8 +30,8 @@
 #include <deque>
 
 #include "base-list.h"
+#include "bp-table.h"
 #include "comment-list.h"
-#include "debug.h"
 #include "symtab.h"
 #include "pt.h"