view libinterp/parse-tree/bp-table.cc @ 23807:336f89b6208b

Use character literals 'c' rather than string literals "c" when possible. Better performance when string constructor isn't required. * Figure.cc, __init_qt__.cc, files-dock-widget.cc, file-editor-tab.cc, file-editor.cc, octave-qscintilla.cc, main-window.cc, octave-dock-widget.cc, octave-qt-link.cc, parser.cc, webinfo.cc, resource-manager.cc, settings-dialog.cc, workspace-view.cc, __magick_read__.cc, balance.cc, debug.cc, dynamic-ld.cc, ft-text-renderer.cc, gl-render.cc, gl2ps-print.cc, graphics.cc, hook-fcn.h, input.cc, load-path.cc, load-save.cc, ls-hdf5.cc, oct-hist.cc, oct-stream.cc, pager.cc, pr-output.cc, qz.cc, symtab.cc, symtab.h, tril.cc, __delaunayn__.cc, __init_fltk__.cc, __voronoi__.cc, audioread.cc, ccolamd.cc, colamd.cc, convhulln.cc, ov-base-int.cc, ov-base-mat.cc, ov-base-scalar.cc, ov-base.cc, ov-bool-mat.cc, ov-cell.cc, ov-class.cc, ov-classdef.cc, ov-colon.cc, ov-complex.cc, ov-cx-mat.cc, ov-fcn-handle.cc, ov-fcn-inline.cc, ov-fcn.h, ov-flt-cx-mat.cc, ov-flt-re-mat.cc, ov-java.cc, ov-oncleanup.cc, ov-range.cc, ov-re-mat.cc, ov-re-sparse.cc, ov-str-mat.cc, ov-struct.cc, ov-usr-fcn.cc, ov.cc, octave.cc, bp-table.cc, jit-ir.cc, jit-ir.h, jit-typeinfo.cc, pt-funcall.cc, pt-idx.cc, pt-pr-code.cc, pt.h, Array.cc, CDiagMatrix.cc, CMatrix.cc, CNDArray.cc, CRowVector.cc, CSparse.cc, Range.cc, boolSparse.cc, dDiagMatrix.cc, dMatrix.cc, dNDArray.cc, dRowVector.cc, dSparse.cc, fCDiagMatrix.cc, fCMatrix.cc, fCNDArray.cc, fCRowVector.cc, fDiagMatrix.cc, fMatrix.cc, fNDArray.cc, fRowVector.cc, idx-vector.cc, intNDArray.cc, CollocWt.cc, DASPK.cc, DASRT.cc, DASSL.cc, LSODE.cc, oct-time.cc, cmd-hist.cc, kpse.cc, lo-array-errwarn.cc, lo-regexp.cc, lo-utils.cc, str-vec.cc, url-transfer.cc, main-cli.cc, main-gui.cc, mkoctfile.in.cc: Replace 1-character string literals "c" with the character literal 'c'.
author Rik <rik@octave.org>
date Fri, 28 Jul 2017 15:40:00 -0700
parents 980f39c3ab90
children 061a343089be
line wrap: on
line source

/*

Copyright (C) 2001-2017 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 <sstream>
#include <iostream>

#include "file-ops.h"
#include "singleton-cleanup.h"

#include "bp-table.h"
#include "defun-int.h"
#include "call-stack.h"
#include "error.h"
#include "input.h"
#include "interpreter-private.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 = nullptr;

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;

// 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 = nullptr;

  if (fname.empty ())
    {
      octave::call_stack& cs = octave::__get_call_stack__ ("get_user_code");

      dbg_fcn = cs.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::symbol_table& symtab = octave::__get_symbol_table__ ("get_user_code");

      octave_value fcn = symtab.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.isempty () || W(0).isempty ())
        Vdebug_on_error = 1;    // like "dbstop if error" with no identifier
      else if (! W(0).iscell ())
        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.isempty () || W(0).isempty ())
        Vdebug_on_caught = 1;    // like "dbstop if caught error" with no ID
      else if (! W(0).iscell ())
        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.isempty () || W(0).isempty ())
        Vdebug_on_warning = 1;    // like "dbstop if warning" with no identifier
      else if (! W(0).iscell ())
        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 = nullptr;
          if (! parser.m_stmt_list)
            error ("dbstop: "
                   "condition is not empty, but has nothing to evaluate");
          else
            {
              if (parser.m_stmt_list->length () == 1
                  && (stmt = parser.m_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).isnumeric ())
                {
                  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 = nullptr;
              bool *stop_flag = nullptr;         // 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 != nullptr)
                {
                  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 = true;
                        }
                      else
                        {
                          id_list->erase (args(pos).string_value ());
                          if (id_list->empty ())
                            *stop_flag = false;
                        }
                    }
                  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 = nullptr)
{
  octave_user_code *retval = nullptr;
  octave_user_code *next_fcn = nullptr;  // 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 != nullptr && 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;
}