view libinterp/parse-tree/bp-table.cc @ 31605:e88a07dec498 stable

maint: Use macros to begin/end C++ namespaces. * oct-conf-post-public.in.h: Define two macros (OCTAVE_BEGIN_NAMESPACE, OCTAVE_END_NAMESPACE) that can be used to start/end a namespace. * mk-opts.pl, build-env.h, build-env.in.cc, __betainc__.cc, __contourc__.cc, __dsearchn__.cc, __eigs__.cc, __expint__.cc, __ftp__.cc, __gammainc__.cc, __ichol__.cc, __ilu__.cc, __isprimelarge__.cc, __lin_interpn__.cc, __magick_read__.cc, __pchip_deriv__.cc, __qp__.cc, amd.cc, auto-shlib.cc, auto-shlib.h, balance.cc, base-text-renderer.cc, base-text-renderer.h, besselj.cc, bitfcns.cc, bsxfun.cc, c-file-ptr-stream.cc, c-file-ptr-stream.h, call-stack.cc, call-stack.h, ccolamd.cc, cellfun.cc, chol.cc, colamd.cc, colloc.cc, conv2.cc, daspk.cc, dasrt.cc, dassl.cc, data.cc, data.h, debug.cc, defaults.cc, defaults.h, defun-int.h, defun.cc, det.cc, dirfns.cc, display.cc, display.h, dlmread.cc, dmperm.cc, dot.cc, dynamic-ld.cc, dynamic-ld.h, eig.cc, ellipj.cc, environment.cc, environment.h, error.cc, error.h, errwarn.h, event-manager.cc, event-manager.h, event-queue.cc, event-queue.h, fcn-info.cc, fcn-info.h, fft.cc, fft2.cc, fftn.cc, file-io.cc, filter.cc, find.cc, ft-text-renderer.cc, ft-text-renderer.h, gcd.cc, getgrent.cc, getpwent.cc, getrusage.cc, givens.cc, gl-render.cc, gl-render.h, gl2ps-print.cc, gl2ps-print.h, graphics-toolkit.cc, graphics-toolkit.h, graphics.cc, graphics.in.h, gsvd.cc, gtk-manager.cc, gtk-manager.h, hash.cc, help.cc, help.h, hess.cc, hex2num.cc, hook-fcn.cc, hook-fcn.h, input.cc, input.h, interpreter-private.cc, interpreter-private.h, interpreter.cc, interpreter.h, inv.cc, jsondecode.cc, jsonencode.cc, kron.cc, latex-text-renderer.cc, latex-text-renderer.h, load-path.cc, load-path.h, load-save.cc, load-save.h, lookup.cc, ls-ascii-helper.cc, ls-ascii-helper.h, ls-oct-text.cc, ls-utils.cc, ls-utils.h, lsode.cc, lu.cc, mappers.cc, matrix_type.cc, max.cc, mex-private.h, mex.cc, mgorth.cc, nproc.cc, oct-fstrm.cc, oct-fstrm.h, oct-hdf5-types.cc, oct-hdf5-types.h, oct-hist.cc, oct-hist.h, oct-iostrm.cc, oct-iostrm.h, oct-opengl.h, oct-prcstrm.cc, oct-prcstrm.h, oct-procbuf.cc, oct-procbuf.h, oct-process.cc, oct-process.h, oct-stdstrm.h, oct-stream.cc, oct-stream.h, oct-strstrm.cc, oct-strstrm.h, oct-tex-lexer.in.ll, oct-tex-parser.yy, ordqz.cc, ordschur.cc, pager.cc, pager.h, pinv.cc, pow2.cc, pr-flt-fmt.cc, pr-output.cc, procstream.cc, procstream.h, psi.cc, qr.cc, quad.cc, quadcc.cc, qz.cc, rand.cc, rcond.cc, regexp.cc, schur.cc, settings.cc, settings.h, sighandlers.cc, sighandlers.h, sparse-xdiv.cc, sparse-xdiv.h, sparse-xpow.cc, sparse-xpow.h, sparse.cc, spparms.cc, sqrtm.cc, stack-frame.cc, stack-frame.h, stream-euler.cc, strfind.cc, strfns.cc, sub2ind.cc, svd.cc, sylvester.cc, symbfact.cc, syminfo.cc, syminfo.h, symrcm.cc, symrec.cc, symrec.h, symscope.cc, symscope.h, symtab.cc, symtab.h, syscalls.cc, sysdep.cc, sysdep.h, text-engine.cc, text-engine.h, text-renderer.cc, text-renderer.h, time.cc, toplev.cc, tril.cc, tsearch.cc, typecast.cc, url-handle-manager.cc, url-handle-manager.h, urlwrite.cc, utils.cc, utils.h, variables.cc, variables.h, xdiv.cc, xdiv.h, xnorm.cc, xnorm.h, xpow.cc, xpow.h, __delaunayn__.cc, __fltk_uigetfile__.cc, __glpk__.cc, __init_fltk__.cc, __init_gnuplot__.cc, __ode15__.cc, __voronoi__.cc, audiodevinfo.cc, audioread.cc, convhulln.cc, fftw.cc, gzip.cc, mk-build-env-features.sh, mk-builtins.pl, cdef-class.cc, cdef-class.h, cdef-fwd.h, cdef-manager.cc, cdef-manager.h, cdef-method.cc, cdef-method.h, cdef-object.cc, cdef-object.h, cdef-package.cc, cdef-package.h, cdef-property.cc, cdef-property.h, cdef-utils.cc, cdef-utils.h, ov-base.cc, ov-base.h, ov-bool-mat.cc, ov-builtin.h, ov-cell.cc, ov-class.cc, ov-class.h, ov-classdef.cc, ov-classdef.h, ov-complex.cc, ov-fcn-handle.cc, ov-fcn-handle.h, ov-fcn.h, ov-java.cc, ov-java.h, ov-mex-fcn.h, ov-null-mat.cc, ov-oncleanup.cc, ov-struct.cc, ov-typeinfo.cc, ov-typeinfo.h, ov-usr-fcn.cc, ov-usr-fcn.h, ov.cc, ov.h, octave.cc, octave.h, mk-ops.sh, op-b-b.cc, op-b-bm.cc, op-b-sbm.cc, op-bm-b.cc, op-bm-bm.cc, op-bm-sbm.cc, op-cdm-cdm.cc, op-cell.cc, op-chm.cc, op-class.cc, op-cm-cm.cc, op-cm-cs.cc, op-cm-m.cc, op-cm-s.cc, op-cm-scm.cc, op-cm-sm.cc, op-cs-cm.cc, op-cs-cs.cc, op-cs-m.cc, op-cs-s.cc, op-cs-scm.cc, op-cs-sm.cc, op-dm-dm.cc, op-dm-scm.cc, op-dm-sm.cc, op-dm-template.cc, op-dms-template.cc, op-fcdm-fcdm.cc, op-fcm-fcm.cc, op-fcm-fcs.cc, op-fcm-fm.cc, op-fcm-fs.cc, op-fcn.cc, op-fcs-fcm.cc, op-fcs-fcs.cc, op-fcs-fm.cc, op-fcs-fs.cc, op-fdm-fdm.cc, op-fm-fcm.cc, op-fm-fcs.cc, op-fm-fm.cc, op-fm-fs.cc, op-fs-fcm.cc, op-fs-fcs.cc, op-fs-fm.cc, op-fs-fs.cc, op-i16-i16.cc, op-i32-i32.cc, op-i64-i64.cc, op-i8-i8.cc, op-int-concat.cc, op-m-cm.cc, op-m-cs.cc, op-m-m.cc, op-m-s.cc, op-m-scm.cc, op-m-sm.cc, op-mi.cc, op-pm-pm.cc, op-pm-scm.cc, op-pm-sm.cc, op-pm-template.cc, op-range.cc, op-s-cm.cc, op-s-cs.cc, op-s-m.cc, op-s-s.cc, op-s-scm.cc, op-s-sm.cc, op-sbm-b.cc, op-sbm-bm.cc, op-sbm-sbm.cc, op-scm-cm.cc, op-scm-cs.cc, op-scm-m.cc, op-scm-s.cc, op-scm-scm.cc, op-scm-sm.cc, op-sm-cm.cc, op-sm-cs.cc, op-sm-m.cc, op-sm-s.cc, op-sm-scm.cc, op-sm-sm.cc, op-str-m.cc, op-str-s.cc, op-str-str.cc, op-struct.cc, op-ui16-ui16.cc, op-ui32-ui32.cc, op-ui64-ui64.cc, op-ui8-ui8.cc, ops.h, anon-fcn-validator.cc, anon-fcn-validator.h, bp-table.cc, bp-table.h, comment-list.cc, comment-list.h, filepos.h, lex.h, lex.ll, oct-lvalue.cc, oct-lvalue.h, oct-parse.yy, parse.h, profiler.cc, profiler.h, pt-anon-scopes.cc, pt-anon-scopes.h, pt-arg-list.cc, pt-arg-list.h, pt-args-block.cc, pt-args-block.h, pt-array-list.cc, pt-array-list.h, pt-assign.cc, pt-assign.h, pt-binop.cc, pt-binop.h, pt-bp.cc, pt-bp.h, pt-cbinop.cc, pt-cbinop.h, pt-cell.cc, pt-cell.h, pt-check.cc, pt-check.h, pt-classdef.cc, pt-classdef.h, pt-cmd.h, pt-colon.cc, pt-colon.h, pt-const.cc, pt-const.h, pt-decl.cc, pt-decl.h, pt-eval.cc, pt-eval.h, pt-except.cc, pt-except.h, pt-exp.cc, pt-exp.h, pt-fcn-handle.cc, pt-fcn-handle.h, pt-id.cc, pt-id.h, pt-idx.cc, pt-idx.h, pt-jump.h, pt-loop.cc, pt-loop.h, pt-mat.cc, pt-mat.h, pt-misc.cc, pt-misc.h, pt-pr-code.cc, pt-pr-code.h, pt-select.cc, pt-select.h, pt-spmd.cc, pt-spmd.h, pt-stmt.cc, pt-stmt.h, pt-tm-const.cc, pt-tm-const.h, pt-unop.cc, pt-unop.h, pt-vm-eval.cc, pt-walk.cc, pt-walk.h, pt.cc, pt.h, token.cc, token.h, Range.cc, Range.h, idx-vector.cc, idx-vector.h, range-fwd.h, CollocWt.cc, CollocWt.h, aepbalance.cc, aepbalance.h, chol.cc, chol.h, gepbalance.cc, gepbalance.h, gsvd.cc, gsvd.h, hess.cc, hess.h, lo-mappers.cc, lo-mappers.h, lo-specfun.cc, lo-specfun.h, lu.cc, lu.h, oct-convn.cc, oct-convn.h, oct-fftw.cc, oct-fftw.h, oct-norm.cc, oct-norm.h, oct-rand.cc, oct-rand.h, oct-spparms.cc, oct-spparms.h, qr.cc, qr.h, qrp.cc, qrp.h, randgamma.cc, randgamma.h, randmtzig.cc, randmtzig.h, randpoisson.cc, randpoisson.h, schur.cc, schur.h, sparse-chol.cc, sparse-chol.h, sparse-lu.cc, sparse-lu.h, sparse-qr.cc, sparse-qr.h, svd.cc, svd.h, child-list.cc, child-list.h, dir-ops.cc, dir-ops.h, file-ops.cc, file-ops.h, file-stat.cc, file-stat.h, lo-sysdep.cc, lo-sysdep.h, lo-sysinfo.cc, lo-sysinfo.h, mach-info.cc, mach-info.h, oct-env.cc, oct-env.h, oct-group.cc, oct-group.h, oct-password.cc, oct-password.h, oct-syscalls.cc, oct-syscalls.h, oct-time.cc, oct-time.h, oct-uname.cc, oct-uname.h, action-container.cc, action-container.h, base-list.h, cmd-edit.cc, cmd-edit.h, cmd-hist.cc, cmd-hist.h, f77-fcn.h, file-info.cc, file-info.h, lo-array-errwarn.cc, lo-array-errwarn.h, lo-hash.cc, lo-hash.h, lo-ieee.h, lo-regexp.cc, lo-regexp.h, lo-utils.cc, lo-utils.h, oct-base64.cc, oct-base64.h, oct-glob.cc, oct-glob.h, oct-inttypes.h, oct-mutex.cc, oct-mutex.h, oct-refcount.h, oct-shlib.cc, oct-shlib.h, oct-sparse.cc, oct-sparse.h, oct-string.h, octave-preserve-stream-state.h, pathsearch.cc, pathsearch.h, quit.cc, quit.h, unwind-prot.cc, unwind-prot.h, url-transfer.cc, url-transfer.h : Use new macros to begin/end C++ namespaces.
author Rik <rik@octave.org>
date Thu, 01 Dec 2022 14:23:45 -0800
parents abca9eac1ec9
children aac27ad79be6
line wrap: on
line source

////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2001-2022 The Octave Project Developers
//
// See the file COPYRIGHT.md in the top-level directory of this
// distribution or <https://octave.org/copyright/>.
//
// 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
// <https://www.gnu.org/licenses/>.
//
////////////////////////////////////////////////////////////////////////

#if defined (HAVE_CONFIG_H)
#  include "config.h"
#endif

#include <algorithm>
#include <limits>
#include <list>
#include <map>
#include <set>
#include <string>

#include "file-ops.h"
#include "oct-env.h"

#include "bp-table.h"
#include "defun-int.h"
#include "error.h"
#include "event-manager.h"
#include "interpreter.h"
#include "interpreter-private.h"
#include "oct-map.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"

OCTAVE_BEGIN_NAMESPACE(octave)

  class bp_file_info
  {
  public:

    bp_file_info (tree_evaluator& tw, const std::string& file)
      : m_ok (false), m_file (file), m_dir (), m_fcn (), m_class_name ()
    {
      std::string abs_file = sys::env::make_absolute (file);

      std::string dir = sys::file_ops::dirname (abs_file);
      std::string fcn = sys::file_ops::tail (abs_file);
      std::size_t len = fcn.length ();
      if (len >= 2 && fcn[len-2] == '.' && fcn[len-1] == 'm')
        fcn = fcn.substr (0, len-2);

      std::size_t pos = dir.rfind (sys::file_ops::dir_sep_chars ());

      if (pos != std::string::npos && pos < dir.length () - 1)
        {
          if (dir[pos+1] == '@')
            {
              m_class_name = dir.substr (pos+1);

              fcn = sys::file_ops::concat (m_class_name, fcn);

              dir = dir.substr (0, pos);
            }
        }

      m_dir = dir;
      m_fcn = fcn;

      interpreter& interp = tw.get_interpreter ();

      load_path& lp = interp.get_load_path ();

      if (lp.contains_file_in_dir (m_file, m_dir))
        m_ok = true;
    }

    std::string file (void) const { return m_file; }
    std::string dir (void) const { return m_fcn; }
    std::string fcn (void) const { return m_fcn; }
    std::string class_name (void) const { return m_class_name; }

    bool ok (void) const { return m_ok; }

  private:

    bool m_ok;
    std::string m_file;
    std::string m_dir;
    std::string m_fcn;
    std::string m_class_name;
  };

  // Clear all reasons to stop, other than breakpoints.

  void bp_table::dbclear_all_signals (void)
  {
    interpreter& interp = m_evaluator.get_interpreter ();
    error_system& es = interp.get_error_system ();

    es.debug_on_error (false);
    bp_table::m_errors_that_stop.clear ();

    es.debug_on_caught (false);
    bp_table::m_caught_that_stop.clear ();

    es.debug_on_warning (false);
    bp_table::m_warnings_that_stop.clear ();

    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)
  {
    interpreter& interp = m_evaluator.get_interpreter ();
    error_system& es = interp.get_error_system ();

    // 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 (0);
        if (W.isempty () || W(0).isempty ())
          es.debug_on_error (true);    // 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++)
              {
                m_errors_that_stop.insert (V(i).string_value ());
                es.debug_on_error (true);
              }
          }
      }

    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 (0);
        if (W.isempty () || W(0).isempty ())
          es.debug_on_caught (true);  // 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++)
              {
                m_caught_that_stop.insert (V(i).string_value ());
                es.debug_on_caught (true);
              }
          }
      }

    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 (0);
        if (W.isempty () || W(0).isempty ())
          es.debug_on_warning (true);    // 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++)
              {
                m_warnings_that_stop.insert (V(i).string_value ());
                es.debug_on_warning (true);
              }
          }
      }

    if (fail)
      error ("dbstop: invalid 'warn' field");

    // process interrupt
    if (mv.isfield ("intr"))
      Vdebug_on_interrupt = true;
  }

  // Insert a breakpoint in function fcn at line within file fname,
  // to stop only when condition is true.
  // Record in m_bp_set that fname contains a breakpoint.

  bool bp_table::add_breakpoint_1 (octave_user_code *fcn,
                                   const std::string& fname,
                                   const bp_table::bp_lines& line,
                                   const std::string& condition,
                                   bp_table::bp_lines& retval)
  {
    bool found = false;

    tree_statement_list *cmds = fcn->body ();

    std::string file = fcn->fcn_file_name ();

    if (cmds)
      {
        interpreter& interp = m_evaluator.get_interpreter ();

        event_manager& evmgr = interp.get_event_manager ();

        retval = cmds->add_breakpoint (evmgr, file, line, condition);

        for (auto& lineno : retval)
          {
            if (lineno != 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 (), '>');
                if (s)
                  m_bp_set.insert (fname.substr (0, s - fname.c_str ()));
                else
                  m_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)
      {
        // ; to reject partial expr like "y=="
        parser parser (cond + " ;", m_evaluator.get_interpreter ());
        parser.reset ();
        int parse_status = parser.run ();
        if (parse_status)
          error ("dbstop: Cannot parse condition '%s'", cond.c_str ());
        else
          {
            tree_statement *stmt = nullptr;

            std::shared_ptr<tree_statement_list> stmt_list
              = parser.statement_list ();

            if (! stmt_list)
              error ("dbstop: "
                     "condition is not empty, but has nothing to evaluate");
            else
              {
                if (stmt_list->length () == 1
                    && (stmt = stmt_list->front ())
                    && stmt->is_expression ())
                  {
                    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
  };

  // FIXME: This function probably needs to be completely overhauled to
  // correctly parse the full syntax of the dbstop command and properly
  // reject incorrect forms.

  // Parse parameters (args) of dbstop and dbclear commands.
  // For dbstop, who=="dbstop"; for dbclear, who=="dbclear".
  // The syntax is: dbstop [[in] symbol] [[at] [method | line [line [...]]]] [if condition]
  // where the form of condition depends on whether or not a file or line has
  // been seen.  IF symbol and method are specified, then symbol should
  // be a class name.  Otherwise it should be a function name.
  // Also execute "if [error|warning|interrupt|naninf]" clauses.

  void bp_table::parse_dbfunction_params (const char *who,
                                          const octave_value_list& args,
                                          std::string& fcn_name,
                                          std::string& class_name,
                                          bp_table::bp_lines& lines,
                                          std::string& cond)
  {
    int nargin = args.length ();
    fcn_name = "";
    class_name = "";
    lines = bp_table::bp_lines ();

    if (nargin == 0 || ! args(0).is_string ())
      print_usage (who);

    // elements already processed
    bool seen_in = false;
    bool seen_at = false;
    bool 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:
            fcn_name = args(pos).string_value ();
            if (seen_in)
              error ("%s: Too many function names specified -- %s",
                     who, fcn_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", who);
            seen_at = true;

            if (seen_if)
              error ("%s: line number must come before 'if' clause\n", who);
            else if (seen_in)
              {
                std::string arg = args(pos).string_value ();

                // FIXME: we really want to distinguish number
                // vs. method name here.

                if (atoi (arg.c_str ()) == 0)
                  {
                    // We have class and function names but already
                    // stored the class name in fcn_name.
                    class_name = fcn_name;
                    fcn_name = arg;
                    pos++;
                    break;
                  }

              }
            else
              {
                // It was a line number.  Get function name from debugger.
                if (m_evaluator.in_debug_repl ())
                  fcn_name = m_evaluator.get_user_code ()->profiler_name ();
                else
                  error ("%s: function name must come before line number "
                         "and 'if'", who);
                seen_in = true;
              }

            // 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.insert (line);
                    else
                      break;        // may be "if" or a method name
                  }
                else if (args(pos).isnumeric ())
                  {
                    const NDArray arg = args(pos).array_value ();

                    for (octave_idx_type j = 0; j < arg.numel (); j++)
                      lines.insert (static_cast<int> (arg.elem (j)));
                  }
                else
                  error ("%s: Invalid argument type %s",
                         who, 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 += ' ' + 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 ();
                bool on_off = ! strcmp (who, "dbstop");

                // FIXME: the following seems a bit messy in the way it
                // duplicates checks on CONDITION.

                if (condition == "error")
                  process_id_list (who, condition, args, nargin, pos, on_off,
                                   m_errors_that_stop);
                else if (condition == "warning")
                  process_id_list (who, condition, args, nargin, pos, on_off,
                                   m_warnings_that_stop);
                else if (condition == "caught" && nargin > pos+1
                         && args(pos+1).string_value () == "error")
                  {
                    pos++;
                    process_id_list (who, condition, args, nargin, pos, on_off,
                                     m_caught_that_stop);
                  }
                else if (condition == "interrupt")
                  {
                    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 ());

                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 105;  # 105 is a comment; code line is at 106
%! dbstop in ls 123;    # 123 is a comment; code line is at 126
%! dbstop help 204 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], [56, 106, 208, 63, 126]);
%! assert (s.errs, {"Octave:undefined-function"});
*/

  void bp_table::set_stop_flag (const char *who, const std::string& condition,
                                bool on_off)
  {
    interpreter& interp = m_evaluator.get_interpreter ();
    error_system& es = interp.get_error_system ();

    if (condition == "error")
      es.debug_on_error (on_off);
    else if (condition == "warning")
      es.debug_on_warning (on_off);
    else if (condition == "caught")
      es.debug_on_caught (on_off);
    else
      error ("%s: internal error in set_stop_flag", who);
  }

  void bp_table::process_id_list (const char *who,
                                  const std::string& condition,
                                  const octave_value_list& args,
                                  int nargin, int& pos, bool on_off,
                                  std::set<std::string>& id_list)
  {
    pos++;

    if (nargin > pos)       // 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)
          {
            id_list.insert (args(pos).string_value ());
            set_stop_flag (who, condition, true);
          }
        else
          {
            id_list.erase (args(pos).string_value ());
            if (id_list.empty ())
              set_stop_flag (who, condition, false);
          }
      }
    else   // unqualified.  Turn all on or off
      {
        id_list.clear ();
        set_stop_flag (who, condition, on_off);

        if (condition == "error")
          {
            // Matlab stops on both.
            Vdebug_on_interrupt = on_off;
          }
      }
  }

  // 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 && 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.
  int bp_table::add_breakpoint_in_function (const std::string& fname,
                                            const std::string& class_name,
                                            int line,
                                            const std::string& condition)
  {
    bp_lines line_info;
    line_info.insert (line);

    bp_lines result
      = add_breakpoints_in_function (fname, class_name, line_info, condition);

    return result.empty () ? 0 : *(result.begin ());
  }

  // Given file name fname, find the subfunction at line and create
  // a breakpoint there.  Put the system into debug_mode.
  bp_table::bp_lines
  bp_table::add_breakpoints_in_function (const std::string& fname,
                                         const std::string& class_name,
                                         const bp_table::bp_lines& lines,
                                         const std::string& condition)
  {
    octave_user_code *main_fcn = m_evaluator.get_user_code (fname, class_name);

    if (! main_fcn)
      error ("add_breakpoints_in_function: unable to find function '%s'\n",
             fname.c_str ());

    condition_valid (condition);  // Throw error if condition not valid.

    bp_lines retval;

    for (const auto& lineno : lines)
      {
        octave_user_code *dbg_fcn = find_fcn_by_line (main_fcn, lineno);

        // We've found the right (sub)function.  Now insert the breakpoint.
        bp_lines line_info;
        line_info.insert (lineno);

        bp_lines ret_one;
        if (dbg_fcn && add_breakpoint_1 (dbg_fcn, fname, line_info,
                                         condition, ret_one))
          {
            if (! ret_one.empty ())
              {
                int line = *(ret_one.begin ());

                if (line)
                  retval.insert (line);
              }
          }
      }

    m_evaluator.reset_debug_state ();

    return retval;
  }

  int bp_table::add_breakpoint_in_file (const std::string& file,
                                        int line,
                                        const std::string& condition)
  {
    // Duplicates what the GUI was doing previously, but this action
    // should not be specific to the GUI.

    bp_file_info info (m_evaluator, file);

    if (! info.ok ())
      return 0;

    return add_breakpoint_in_function (info.fcn (), info.class_name (),
                                       line, condition);
  }

  bp_table::bp_lines
  bp_table::add_breakpoints_in_file (const std::string& file,
                                     const bp_lines& lines,
                                     const std::string& condition)
  {
    // Duplicates what the GUI was doing previously, but this action
    // should not be specific to the GUI.

    bp_file_info info (m_evaluator, file);

    if (! info.ok ())
      return bp_lines ();

    return add_breakpoints_in_function (info.fcn (), info.class_name (),
                                        lines, condition);
  }

  int bp_table::remove_breakpoint_1 (octave_user_code *fcn,
                                     const std::string& fname,
                                     const bp_table::bp_lines& lines)
  {
    int retval = 0;

    std::string file = fcn->fcn_file_name ();

    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)
          {
            interpreter& interp = m_evaluator.get_interpreter ();

            event_manager& evmgr = interp.get_event_manager ();

            for (const auto& lineno : lines)
              {
                cmds->delete_breakpoint (lineno);

                if (! file.empty ())
                  evmgr.update_breakpoint (false, file, lineno);
              }

            results = cmds->list_breakpoints ();

            auto it = m_bp_set.find (fname);
            if (results.empty () && it != m_bp_set.end ())
              m_bp_set.erase (it);
          }

        retval = results.length ();
      }

    return retval;
  }

  int
  bp_table::remove_breakpoint_from_function (const std::string& fname, int line)
  {
    bp_lines line_info;
    line_info.insert (line);

    return remove_breakpoints_from_function (fname, line_info);
  }

  int
  bp_table::remove_breakpoints_from_function (const std::string& fname,
                                              const bp_table::bp_lines& lines)
  {
    int retval = 0;

    if (lines.empty ())
      {
        bp_lines results = remove_all_breakpoints_from_function (fname);
        retval = results.size ();
      }
    else
      {
        octave_user_code *dbg_fcn = m_evaluator.get_user_code (fname);

        if (! dbg_fcn)
          error ("remove_breakpoints_from_function: unable to find function %s\n",
                 fname.c_str ());

        retval = remove_breakpoint_1 (dbg_fcn, fname, lines);

        // 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 += remove_breakpoint_1 (dbg_subfcn, fname, lines);
              }
          }
      }

    m_evaluator.reset_debug_state ();

    return retval;
  }

  // Remove all breakpoints from a file, including those in subfunctions.

  bp_table::bp_lines
  bp_table::remove_all_breakpoints_from_function (const std::string& fname,
                                                  bool silent)
  {
    bp_lines retval;

    octave_user_code *dbg_fcn = m_evaluator.get_user_code (fname);

    if (dbg_fcn)
      {
        std::string file = dbg_fcn->fcn_file_name ();

        tree_statement_list *cmds = dbg_fcn->body ();

        if (cmds)
          {
            interpreter& interp = m_evaluator.get_interpreter ();

            event_manager& evmgr = interp.get_event_manager ();

            retval = cmds->remove_all_breakpoints (evmgr, file);

            auto it = m_bp_set.find (fname);
            if (it != m_bp_set.end ())
              m_bp_set.erase (it);
          }
      }
    else if (! silent)
      error ("remove_all_breakpoints_from_function: "
             "unable to find function %s\n", fname.c_str ());

    m_evaluator.reset_debug_state ();

    return retval;
  }

  int
  bp_table::remove_breakpoint_from_file (const std::string& file, int line)
  {
    // Duplicates what the GUI was doing previously, but this action
    // should not be specific to the GUI.

    bp_file_info info (m_evaluator, file);

    if (! info.ok ())
      return 0;

    return remove_breakpoint_from_function (info.fcn (), line);
  }

  int
  bp_table::remove_breakpoints_from_file (const std::string& file,
                                          const bp_lines& lines)
  {
    // Duplicates what the GUI was doing previously, but this action
    // should not be specific to the GUI.

    bp_file_info info (m_evaluator, file);

    if (! info.ok ())
      return 0;

    return remove_breakpoints_from_function (info.fcn (), lines);
  }

  bp_table::bp_lines
  bp_table::remove_all_breakpoints_from_file (const std::string& file,
                                              bool silent)
  {
    // Duplicates what the GUI was doing previously, but this action
    // should not be specific to the GUI.

    bp_file_info info (m_evaluator, file);

    if (! info.ok ())
      return bp_lines ();

    return remove_all_breakpoints_from_function (info.fcn (), silent);
  }

  void bp_table::remove_all_breakpoints (void)
  {
    // Odd loop structure required because delete will invalidate
    // m_bp_set iterators.
    for (auto it = m_bp_set.cbegin (), it_next = it;
         it != m_bp_set.cend ();
         it = it_next)
      {
        ++it_next;
        remove_all_breakpoints_from_function (*it);
      }

    m_evaluator.reset_debug_state ();
  }

  std::string 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::get_breakpoint_list (const octave_value_list& fname_list)
  {
    fname_bp_map retval;

    // make copy since changes may invalidate iters of m_bp_set.
    std::set<std::string> tmp_bp_set = m_bp_set;

    for (auto& bp_fname : tmp_bp_set)
      {
        if (fname_list.empty ()
            || find_bkpt_list (fname_list, bp_fname) != "")
          {
            octave_user_code *dbg_fcn = m_evaluator.get_user_code (bp_fname);

            if (dbg_fcn)
              {
                tree_statement_list *cmds = dbg_fcn->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
                  = dbg_fcn->subfunction_names ();

                std::map<std::string, octave_value> subfcns
                  = dbg_fcn->subfunctions ();

                for (const auto& subfcn_nm : subf_nm)
                  {
                    const auto q = subfcns.find (subfcn_nm);

                    if (q != subfcns.end ())
                      {
                        octave_user_code *dbg_subfcn
                          = q->second.user_code_value ();

                        cmds = dbg_subfcn->body ();
                        if (cmds)
                          {
                            std::list<bp_type> bkpts
                              = cmds->breakpoints_and_conds ();

                            if (! bkpts.empty ())
                              {
                                std::string key
                                  = bp_fname + '>' + dbg_subfcn->name ();

                                retval[key] = 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;

    interpreter& interp = m_evaluator.get_interpreter ();
    error_system& es = interp.get_error_system ();

    // print dbstop if error information
    if (es.debug_on_error ())
      {
        if (m_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::m_errors_that_stop.size (), 1));
            int i = 0;

            for (const auto& e : m_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 (es.debug_on_caught ())
      {
        if (m_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 (m_caught_that_stop.size (), 1));
            int i = 0;

            for (const auto& e : m_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 (es.debug_on_warning ())
      {
        if (m_warnings_that_stop.empty ())
          {
            if (to_screen)
              octave_stdout << "stop if warning\n";
            else
              retval.assign ("warn", octave_value (""));
          }
        else
          {
            Cell warn (dim_vector (m_warnings_that_stop.size (), 1));
            int i = 0;

            for (const auto& w : m_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 (Vdebug_on_interrupt)
      {
        if (to_screen)
          octave_stdout << "stop if interrupt\n";
        else
          retval.assign ("intr", octave_value (""));
      }

    return retval;
  }

OCTAVE_END_NAMESPACE(octave)