diff --git a/libinterp/corefcn/debug.cc b/libinterp/corefcn/debug.cc index 9ad9ccf..0ce3f0b 100644 --- a/libinterp/corefcn/debug.cc +++ b/libinterp/corefcn/debug.cc @@ -52,17 +52,23 @@ along with Octave; see the file COPYING. If not, see #include "pt-pr-code.h" #include "pt-bp.h" #include "pt-eval.h" +#include "pt-exp.h" #include "pt-stmt.h" #include "toplev.h" #include "unwind-prot.h" #include "utils.h" #include "variables.h" +#include "sighandlers.h" #include "debug.h" // Initialize the singleton object bp_table *bp_table::instance = 0; +std::set bp_table::errors_that_stop; +std::set 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) { @@ -192,12 +198,14 @@ get_user_code (const std::string& fname = std::string ()) return dbg_fcn; } -static void +// Parse parameters of dbstop and dbclear commands +void parse_dbfunction_params (const char *who, const octave_value_list& args, - std::string& symbol_name, bp_table::intmap& lines) + std::string& symbol_name, bp_table::intmap& lines, + std::string& cond) { int nargin = args.length (); - int idx = 0; + int idx = nargin+1; // default: don't scan for line numbers int list_idx = 0; symbol_name = std::string (); lines = bp_table::intmap (); @@ -207,41 +215,154 @@ parse_dbfunction_params (const char *who, const octave_value_list& args, if (args(0).is_string ()) { - // string could be function name or line number - int isint = atoi (args(0).string_value ().c_str ()); - - if (error_state) - return; - - if (isint == 0) + // first check for Matlab-style commands, starting "in", "if" or "at" + if (args(0).string_value () == "if") + { + if (nargin < 2) + error ("%s: No condition specified", who); + else + { + std::string condition = args(1).string_value (); + int on_off = !strcmp(who, "dbstop"); + if (condition == "error") + { + if (nargin > 2) // only affect a single error ID + { + if (!args(2).is_string () || nargin > 3) + error ("%s: Error ID must be a single string", who); + else if (on_off == 1) + { + bp_table::errors_that_stop.insert + (args(2).string_value ()); + Vdebug_on_error = 1; + } + else + { + bp_table::errors_that_stop.erase + (args(2).string_value ()); + if (bp_table::errors_that_stop.empty ()) + Vdebug_on_error = 0; + } + } + else // unqualified. Turn all on or off + { + bp_table::errors_that_stop.clear (); + Vdebug_on_error = on_off; + Vdebug_on_interrupt = on_off; // Matlabs stops on both + } + } + else if (condition == "warning") + { + if (nargin > 2) + { + if (!args(2).is_string () || nargin > 3) + error ("%s: Warning ID must be a single string", who); + else if (on_off == 1) + { + bp_table::warnings_that_stop.insert + (args(2).string_value ()); + Vdebug_on_warning = 1; + } + else + { + bp_table::warnings_that_stop.erase + (args(2).string_value ()); + if (bp_table::warnings_that_stop.empty ()) + Vdebug_on_warning = 0; + } + } + else + { + bp_table::warnings_that_stop.clear (); + Vdebug_on_warning = on_off; + } + } + else if (condition == "interrupt") + { + Vdebug_on_interrupt = on_off; + } + else if (condition == "caught" || condition == "naninf") + warning ("%s: condition '%s' not yet supported", + who, condition.c_str ()); + else + error ("%s: invalid condition %s", who, condition.c_str ()); + } + } + else if (args(0).string_value () == "in") { // It was a function name - symbol_name = args(0).string_value (); - if (error_state) - return; - idx = 1; + if (nargin < 2) + error ("%s: No function specified", who); + else + { + symbol_name = args(1).string_value (); + if (error_state) + return; + if (nargin > 2) + { + int offset = 2; // offset of "if" + bool done_at = false; // allow "at" then "if" + if (args(offset).string_value () == "at") + { + done_at = true; + if (nargin > 3) + lines[0] = atoi (args(3).string_value ().c_str ()); + else + error ("%s: 'at' missing line number", who); + offset = 4; + } + if (nargin > offset) + { + if (!strcmp (who, "dbstop") + && args(offset).string_value () == "if") + { + cond = ""; + for (int i = offset+1; i < nargin; i++) + { + cond = cond + " " + args(i).string_value (); + } + if (cond.length () == 0) + error ("%s: 'if' missing condition", who); + cond = cond.substr (1); // omit initial space + } + else if (!done_at) + error ("%s: Invalid argument %s", + who, args(offset).string_value ().c_str ()); + } + } + } } - else + else // Traditional octave-style command { - // It was a line number. Need to get function name from debugger. - if (Vdebugging) + // string could be function name or line number + int isint = atoi (args(0).string_value ().c_str ()); + + if (error_state) + return; + + if (isint == 0) { - symbol_name = get_user_code ()->name (); - idx = 0; + // It was a function name + symbol_name = args(0).string_value (); + if (error_state) + return; + idx = 1; } else { - error ("%s: no function specified", who); + // It was a line number. Need to get function name from debugger. + if (Vdebugging) + { + symbol_name = get_user_code ()->name (); + idx = 0; + } + else + { + error ("%s: no function specified", who); + } } } } - else if (args(0).is_map ()) - { - // This is a problem because parse_dbfunction_params() - // can only pass out a single function. - error ("%s: struct input not implemented", who); - return; - } else error ("%s: invalid parameter specified", who); @@ -277,6 +398,8 @@ parse_dbfunction_params (const char *who, const octave_value_list& args, } } +// Return true if there is a valid breakpoint table, false otherwise. +// If not table exists, one is created; false is only returned if this fails bool bp_table::instance_ok (void) { @@ -299,10 +422,14 @@ bp_table::instance_ok (void) return retval; } +// 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; @@ -313,13 +440,20 @@ bp_table::do_add_breakpoint_1 (octave_user_code *fcn, if (cmds) { - retval = cmds->add_breakpoint (file, line); + retval = cmds->add_breakpoint (file, line, condition); for (intmap_iterator p = retval.begin (); p != retval.end (); p++) { if (p->second != 0) { - bp_set.insert (fname); + // normalise 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) + bp_set.insert (fname.substr (0, s - fname.c_str ())); + else + bp_set.insert (fname); found = true; break; } @@ -329,9 +463,14 @@ bp_table::do_add_breakpoint_1 (octave_user_code *fcn, return found; } +// Given file name fname, find the subfunction at line line and create +// a breakpoint there. Put the system into debug_mode. +// (TODO: If line is multiple lines, what happens if they are in different +// functions?) bp_table::intmap bp_table::do_add_breakpoint (const std::string& fname, - const bp_table::intmap& line) + const bp_table::intmap& line, + const std::string& condition) { intmap retval; @@ -339,7 +478,7 @@ bp_table::do_add_breakpoint (const std::string& fname, if (dbg_fcn) { - if (! do_add_breakpoint_1 (dbg_fcn, fname, line, retval)) + if (! do_add_breakpoint_1 (dbg_fcn, fname, line, condition, retval)) { // Search subfunctions in the order they appear in the file. @@ -359,14 +498,15 @@ bp_table::do_add_breakpoint (const std::string& fname, { octave_user_code *dbg_subfcn = q->second.user_code_value (); - if (do_add_breakpoint_1 (dbg_subfcn, fname, line, retval)) + if (do_add_breakpoint_1 (dbg_subfcn, fname, line, condition, + retval)) break; } } } } else - error ("add_breakpoint: unable to find the requested function\n"); + error ("add_breakpoint: unable to find function '%s'\n", fname.c_str ()); tree_evaluator::debug_mode = bp_table::have_breakpoints () || Vdebugging; @@ -568,10 +708,10 @@ do_find_bkpt_list (octave_value_list slist, return retval; } -bp_table::fname_line_map +bp_table::fname_bp_map bp_table::do_get_breakpoint_list (const octave_value_list& fname_list) { - fname_line_map retval; + fname_bp_map retval; for (bp_set_iterator it = bp_set.begin (); it != bp_set.end (); it++) { @@ -588,25 +728,49 @@ bp_table::do_get_breakpoint_list (const octave_value_list& fname_list) // tree_statement_list class? if (cmds) { - octave_value_list bkpts = cmds->list_breakpoints (); - octave_idx_type len = bkpts.length (); + std::list bkpts = cmds->breakpoints_and_conds (); - if (len > 0) + if (!bkpts.empty ()) { - bp_table::intmap bkpts_vec; + if (f->name () == *it) + retval[f->name ()] = bkpts; + else + retval[*it + ">" + f->name ()] = bkpts; + } + } + + // look for breakpoints in subfunctions + const std::list subf_nm = f->subfunction_names (); - for (int i = 0; i < len; i++) - bkpts_vec[i] = bkpts(i).double_value (); + std::map subf = f->subfunctions (); - std::string symbol_name = f->name (); + for (std::list::const_iterator p = subf_nm.begin (); + p != subf_nm.end (); p++) + { + std::map::const_iterator + q = subf.find (*p); - retval[symbol_name] = bkpts_vec; + if (q != subf.end ()) + { + octave_user_code *ff = q->second.user_code_value (); + + cmds = ff->body (); + if (cmds) + { + std::list bkpts + = cmds->breakpoints_and_conds (); + + if (!bkpts.empty ()) + { + std::string name = f->name () + ">" + ff->name (); + retval[name] = bkpts; + } + } } } } } } - return retval; } @@ -633,10 +797,162 @@ intmap_to_ov (const bp_table::intmap& line) return retval; } +// 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". +static bool +condition_valid (const std::string& cond) +{ + bool valid_expression = true; + if (cond.length () > 0) + { + octave_parser parser (cond + " ;"); // ; to reject partial expr like "y==" + parser.reset (); + int parse_status = parser.run (); + if (parse_status) + { + valid_expression = false; + error ("dbstop: Cannot parse condition '%s'", cond.c_str ()); + } + else + { + tree_statement *stmt = 0; + if (parser.stmt_list->length () == 1 + && (stmt = parser.stmt_list->front ()) + && stmt->is_expression ()) + { + tree_expression *expr = stmt->expression (); + if (expr->is_assignment_expression ()) + { + valid_expression = false; + error ("dbstop: condition cannot be an assignment. " + "Did you mean '=='?"); + } + } + else + { + valid_expression = false; + error ("dbstop: condition must be an expression"); + } + } + } + return valid_expression; +} + +// Process the "warn" and "errs" fields for a call of "dbstop (p)". +// (Separate function from dbstop so it can be a friend of bp_table.) +void +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 W = U.index (0); + if (W.numel () == 0 || W(0).length () == 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++) + { + bp_table::errors_that_stop.insert (V(i).string_value ()); + Vdebug_on_error = 1; + } + } + } + if (fail) + error ("dbstop: invalid 'errs' 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 W = U.index (0); + if (W.numel () == 0 || W(0).length () == 0) + 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++) + { + bp_table::warnings_that_stop.insert (V(i).string_value ()); + Vdebug_on_warning = 1; + } + } + } + if (fail) + error ("dbstop: invalid 'warn' field"); +} + DEFUN (dbstop, args, , "-*- texinfo -*-\n\ -@deftypefn {Command} {} dbstop @var{func}\n\ -@deftypefnx {Command} {} dbstop @var{func} @var{line}\n\ +@deftypefn {Command} {} dbstop in @var{func}\n\ +@deftypefnx {Command} {} dbstop in @var{func} at @var{line}\n\ +@deftypefnx {Command} {} dbstop in @var{func} at @var{line} if @var{condition}\n\ +@deftypefnx {Command} {} dbstop in @var{func} if @var{condition}\n\ +@deftypefnx {Command} {} dbstop if @var{event}\n\ +@deftypefnx {Build-in Function} {} dbstop (@var{state})\n\ +\n\ +The argument @var{func} is the name of a function on the current path.\n\ +The argument @var{line} is the line number at which to break.\n\ +If @code{file.m} has a sub-function @code{func2}, then a breakpoint in\n\ +@code{func2} can be specified by specifying @var{func} either as @code{file}\n\ +or @code{file>func2}. In either case, @var{line} is specified relative to\n\ +the start of @code{file.m}. If @var{line} is not specified, it defaults to\n\ +the first line in @var{func}.\n\ +\n\ +The @var{condition} is any Octave expression that will be able to be\n\ +evaluated in the context of the breakpoint. When the breakpoint is\n\ +encountered, @var{condition} will be evaluated, and execution will break if\n\ +@var{condition} is true. If it cannot be evaluated, for example because it\n\ +refers to an undefined variable, an error will be thrown. Expressions with\n\ +side effects (such as @code{y++ > 1}) actually alter the variables, and \n\ +should generally be avoided.\n\ +\n\ +The form specifying @var{event} does not cause a fixed breakpoint. Instead\n\ +it causes debug mode to be entered when specific unexpected circumstances\n\ +arise. Possible values are\n\ +\n\ +@table @asis\n\ +@item @qcode{error}\n\ +Stop whenever an error is reported. This is equivalent to specifying\n\ +both @code{debug_on_error(1)} and @code{debug_on_interrupt(1)}.\n\ +@item @qcode{interrupt}\n\ +Stop when an interrupt (ctrl-C) occurs.\n\ +@item @qcode{warning}\n\ +Stop whenever a warning is reported. This is equivalent to specifying\n\ +@code{debug_on_warning(1)}.\n\ +@end table\n\ +\n\ +Values @qcode{naninf} and @qcode{caught error} give a warning that these\n\ +are not yet implemented, and other options give an error.\n\ +\n\ +These settings can be undone using @qcode{dbclear} commands with the same\n\ +syntax.\n\ +\n\ +It is possible to save all breakpoints and restore them at once by issuing\n\ +the commands @code{state = dbstatus; ... dbstop (state)}.\n\ +\n\ +When a file is re-parsed, such as when it is modified, all breakpoints within\n\ +that file are cleared.\n\ +\n\ +For compatibility with previous versions of Octave, the following forms are\n\ +also available.\n\ +@end deftypefn\n\ +@deftypefn {Command} {} dbstop @var{func} @var{line}\n\ @deftypefnx {Command} {} dbstop @var{func} @var{line1} @var{line2} @dots{}\n\ @deftypefnx {Command} {} dbstop @var{line} @dots{}\n\ @deftypefnx {Built-in Function} {@var{rline} =} dbstop (\"@var{func}\")\n\ @@ -665,22 +981,82 @@ The optional output @var{rline} is the real line number where the breakpoint\n\ was set. This can differ from the specified line if the line is not\n\ executable. For example, if a breakpoint attempted on a blank line then\n\ Octave will set the real breakpoint at the next executable line.\n\ +\n\ @seealso{dbclear, dbstatus, dbstep, debug_on_error, debug_on_warning, debug_on_interrupt}\n\ @end deftypefn") { - bp_table::intmap retval; - std::string symbol_name; + bp_table::intmap retmap; + std::string symbol_name = ""; // stays empty for "dbstop if error" etc bp_table::intmap lines; + std::string condition = ""; + octave_value retval; - parse_dbfunction_params ("dbstop", args, symbol_name, lines); + if (!args(0).is_map ()) // explicit function / line / condition + { + parse_dbfunction_params ("dbstop", args, symbol_name, lines, condition); - if (lines.size () == 0) - lines[0] = 1; + if (lines.size () == 0) + lines[0] = 1; - if (! error_state) - retval = bp_table::add_breakpoint (symbol_name, lines); + if (! error_state && symbol_name != "" && condition_valid (condition)) + retmap = bp_table::add_breakpoint (symbol_name, lines, condition); + retval = intmap_to_ov (retmap); + } + else if (args.length () > 1) + { + print_usage (); + } + else // structure of the form output by dbstatus + { + octave_map mv = args(0).map_value (); + if (mv.isfield ("bkpt") || mv.isfield ("errs") || mv.isfield ("warn")) + { + dbstop_process_map_args (mv); - return intmap_to_ov (retval); + // Replace mv by "bkpt", to use the processing below. + octave_value bkpt = mv.getfield ("bkpt"); + if (bkpt.is_empty ()) + mv = octave_map (); + else + { + if (bkpt.is_cell () && bkpt.cell_value ().numel () > 0 + && bkpt.cell_value () (0).is_map ()) + mv = bkpt.cell_value () (0).map_value (); + else + { + error ("dbstop: invalid 'bkpt' field"); + mv = octave_map (); + } + } + } + if (mv.numel () == 0) + { + // no changes requested. Occurs if "errs" non-empty but "bkpt" empty + } + else if (!mv.isfield ("name") || !mv.isfield ("line")) + { + error ("dbstop: Cell array must contain fields 'name' and 'line'"); + retval = octave_value (0); + } + else + { + bool use_cond = mv.isfield ("cond"); + Cell name = mv.getfield ("name"); + Cell line = mv.getfield ("line"); + Cell cond = (use_cond ? mv.getfield ("cond") : Cell ()); + std::string unconditional = ""; + for (octave_idx_type i = 0; i < line.numel (); i++) + { + lines [0] = line(i).double_value (); + bp_table::add_breakpoint (name(i).string_value (), lines, + use_cond ? cond(i).string_value () + : unconditional ); + } + retval = octave_value (line.numel ()); + } + } + + return retval; } DEFUN (dbclear, args, , @@ -690,6 +1066,9 @@ DEFUN (dbclear, args, , @deftypefnx {Command} {} dbclear @var{func} @var{line1} @var{line2} @dots{}\n\ @deftypefnx {Command} {} dbclear @var{line} @dots{}\n\ @deftypefnx {Command} {} dbclear all\n\ +@deftypefnx {Command} {} dbclear in @var{func}\n\ +@deftypefnx {Command} {} dbclear in @var{func} at @var{line}\n\ +@deftypefnx {Command} {} dbclear if @var{event}\n\ @deftypefnx {Built-in Function} {} dbclear (\"@var{func}\")\n\ @deftypefnx {Built-in Function} {} dbclear (\"@var{func}\", @var{line})\n\ @deftypefnx {Built-in Function} {} dbclear (\"@var{func}\", @var{line1}, @var{line2}, @dots{})\n\ @@ -708,6 +1087,8 @@ can be omitted and the current function will be used.\n\ @item line\n\ Line number from which to remove a breakpoint. Multiple lines may be given\n\ as separate arguments or as a vector.\n\ +@item event\n\ +An even such as error, interrupt or warning; see dbstop for details.\n\ @end table\n\ \n\ When called without a line number specification all breakpoints in the named\n\ @@ -721,24 +1102,101 @@ files.\n\ @end deftypefn") { octave_value retval; - std::string symbol_name = ""; + std::string symbol_name = ""; // stays empty for "dbclear if error" etc bp_table::intmap lines; + std::string dummy; // "if" condition -- only used for dbstop int nargin = args.length (); - parse_dbfunction_params ("dbclear", args, symbol_name, lines); + parse_dbfunction_params ("dbclear", args, symbol_name, lines, dummy); + // FIXME should this also do dbclear if error etc.? + // Problem if they were set explicitly by debug_on_error(1) etc.. + // Just copy what Matlab does, once I find out. if (nargin == 1 && symbol_name == "all") bp_table::remove_all_breakpoints (); else { - if (! error_state) + if (! error_state && symbol_name != "") bp_table::remove_breakpoint (symbol_name, lines); } 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 +stop_on_err_warn_status (bool to_screen) +{ + octave_map retval; + + // print dbstop if error information + if (Vdebug_on_error) + { + if (bp_table::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 (std::set::const_iterator e + = bp_table::errors_that_stop.begin (); + e != bp_table::errors_that_stop.end (); e++) + { + 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 warning information + if (Vdebug_on_warning) + { + if (bp_table::warnings_that_stop.empty ()) + { + if (to_screen) + octave_stdout << "stop if warning\n"; + else + retval.assign ("warn", octave_value("")); + } + else + { + Cell warn (dim_vector (bp_table::warnings_that_stop.size (), 1)); + int i = 0; + + for (std::set::const_iterator w + = bp_table::warnings_that_stop.begin (); + w != bp_table::warnings_that_stop.end (); w++) + { + if (to_screen) + octave_stdout << "stop if warning " << *w << "\n"; + else + warn (i++) = *w; + } + if (!to_screen) + retval.assign ("warn", octave_value (warn)); + } + } + + return retval; +} + DEFUN (dbstatus, args, nargout, "-*- texinfo -*-\n\ @deftypefn {Built-in Function} {} dbstatus ()\n\ @@ -751,32 +1209,42 @@ functions with breakpoints and the line numbers where those breakpoints are\n\ set.\n\ \n\ If a function name @var{func} is specified then only report breakpoints\n\ -for the named function.\n\ +for the named function, and its sub-functions.\n\ \n\ The optional return argument @var{brk_list} is a struct array with the\n\ following fields.\n\ \n\ @table @asis\n\ @item name\n\ -The name of the function with a breakpoint.\n\ +The name of the function with a breakpoint. A sub-function, say @code{func2}\n\ +within a .m file, say @code{foo/file.m}, is specified as @code{file>func2}.\n\ \n\ @item file\n\ The name of the m-file where the function code is located.\n\ \n\ @item line\n\ -A line number, or vector of line numbers, with a breakpoint.\n\ +The line number with the breakpoint.\n\ +\n\ +@item cond\n\ +The condition that must be satisfied for the breakpoint to be active, or\n\ +the empty string for unconditional breakpoints.\n\ @end table\n\ \n\ -Note: When @code{dbstatus} is called from the debug prompt within a function,\n\ -the list of breakpoints is automatically trimmed to the breakpoints in the\n\ -current function.\n\ -@seealso{dbclear, dbwhere}\n\ +@c Note: When @code{dbstatus} is called from the debug prompt within a function,\n\ +@c the list of breakpoints is automatically trimmed to the breakpoints in the\n\ +@c current function.\n\ +If @code{dbstop if error} is true but no explicit IDs are specified, the\n\ +return value will have an empty field called \"errs\". If IDs are specified,\n\ +the \"errs\" field will have a row per ID. If @code{dbstop if error} is\n\ +false, there is no \"errs\" field. The \"warn\" field is set similarly by\n\ +@code{dbstop if warning}.\n\ +\n\ +@seealso{dbstop, dbclear, dbwhere, dblist, dbstack}\n\ @end deftypefn") { - octave_map retval; int nargin = args.length (); octave_value_list fcn_list; - bp_table::fname_line_map bp_list; + bp_table::fname_bp_map bp_list; std::string symbol_name; if (nargin != 0 && nargin != 1) @@ -798,6 +1266,7 @@ current function.\n\ } else { +/* if (Vdebugging) { octave_user_code *dbg_fcn = get_user_code (); @@ -807,6 +1276,7 @@ current function.\n\ fcn_list(0) = symbol_name; } } +*/ bp_list = bp_table::get_breakpoint_list (fcn_list); } @@ -815,50 +1285,114 @@ current function.\n\ { // Print out the breakpoint information. - for (bp_table::fname_line_map_iterator it = bp_list.begin (); + for (bp_table::fname_bp_map_iterator it = bp_list.begin (); it != bp_list.end (); it++) { - bp_table::intmap m = it->second; + std::list m = it->second; - size_t nel = m.size (); + // print unconditional breakpoints, if any, on a single line - octave_stdout << "breakpoint in " << it->first; - if (nel > 1) - octave_stdout << " at lines "; - else - octave_stdout << " at line "; + // first, check to see if there are any + int have_unconditional = 0; + for (std::list::const_iterator j = m.begin (); + j != m.end (); j++) + { + if (j->cond == "") + { + if (have_unconditional++) + break; // stop once we know its plural + } + } + // If we actually have some, print line numbers only + if (have_unconditional) + { + const char *_s_ = (have_unconditional > 1) ? "s" : ""; + octave_stdout << "breakpoint" << _s_ <<" in " << it->first + << " at line" << _s_ << " "; - for (size_t j = 0; j < nel; j++) - octave_stdout << m[j] << ((j < nel - 1) ? ", " : "."); + for (std::list::const_iterator j = m.begin (); + j != m.end (); j++) + { + if (j->cond == "") + octave_stdout << j->line << " "; + } + octave_stdout << std::endl; + } - if (nel > 0) - octave_stdout << std::endl; + // print conditional breakpoints, one per line, with conditions + for (std::list::const_iterator j = m.begin (); + j != m.end (); j++) + { + if (j->cond != "") + octave_stdout << "breakpoint in " << it->first + << " at line " << j->line + << " if " << j->cond << "\n"; + } } + + stop_on_err_warn_status (true); + return octave_value (); } else { // Fill in an array for return. - int i = 0; - Cell names (dim_vector (bp_list.size (), 1)); - Cell file (dim_vector (bp_list.size (), 1)); - Cell line (dim_vector (bp_list.size (), 1)); + octave_map retmap; + octave_value retval; - for (bp_table::const_fname_line_map_iterator it = bp_list.begin (); + // count how many breakpoints there are + int count = 0; + for (bp_table::const_fname_bp_map_iterator it = bp_list.begin (); it != bp_list.end (); it++) { - names(i) = it->first; - line(i) = intmap_to_ov (it->second); - file(i) = do_which (it->first); - i++; + for (std::list::const_iterator j = it->second.begin (); + j != it->second.end (); j++) + count++; } - retval.assign ("name", names); - retval.assign ("file", file); - retval.assign ("line", line); + Cell names (dim_vector (count, 1)); + Cell file (dim_vector (count, 1)); + Cell line (dim_vector (count, 1)); + Cell cond (dim_vector (count, 1)); + + for (bp_table::const_fname_bp_map_iterator it = bp_list.begin (); + it != bp_list.end (); it++) + { + octave_value path_name = do_which (it->first); + for (std::list::const_iterator j = it->second.begin (); + j != it->second.end (); j++) + { + names(i) = it->first; + file(i) = path_name; + line(i) = octave_value (j->line); + cond(i) = octave_value (j->cond); + i++; + } + } + + retmap.assign ("name", names); + retmap.assign ("file", file); + retmap.assign ("line", line); + retmap.assign ("cond", cond); + + octave_map ew = stop_on_err_warn_status (false); + if (ew.numel () == 0) + { + retval = octave_value (retmap); + } + else + { + octave_map outer (dim_vector (3,1)); + outer.assign ("bkpt", Cell (retmap)); + for (octave_map::const_iterator f = ew.begin (); f != ew.end (); f++) + { + outer.setfield (f->first, ew.contents (f)); + } + retval = octave_value (outer); + } - return octave_value (retval); + return retval; } } @@ -867,7 +1401,7 @@ DEFUN (dbwhere, , , @deftypefn {Command} {} dbwhere\n\ In debugging mode, report the current file and line number where execution\n\ is stopped.\n\ -@seealso{dbstatus, dbcont, dbstep, dbup}\n\ +@seealso{dbstack, dblist, dbstatus, dbcont, dbstep, dbup, dbdown}\n\ @end deftypefn") { octave_value retval; @@ -964,7 +1498,7 @@ specification for the last line of the file.\n\ \n\ When called with the name of a function, list that script file with line\n\ numbers.\n\ -@seealso{dbwhere, dbstatus, dbstop}\n\ +@seealso{dblist, dbwhere, dbstatus, dbstop}\n\ @end deftypefn") { octave_value retval; @@ -1114,7 +1648,7 @@ In debugging mode, list @var{n} lines of the function being debugged\n\ centered around the current line to be executed.\n\ \n\ If unspecified @var{n} defaults to 10 (+/- 5 lines)\n\ -@seealso{dbwhere, dbtype}\n\ +@seealso{dbwhere, dbtype, dbstack}\n\ @end deftypefn") { octave_value retval; @@ -1342,7 +1876,7 @@ Undocumented.\n\ \n\ The return argument @var{idx} specifies which element of the @var{stack}\n\ struct array is currently active.\n\ -@seealso{dbup, dbdown, dbwhere, dbstatus}\n\ +@seealso{dbup, dbdown, dbwhere, dblist, dbstatus}\n\ @end deftypefn") { return do_dbstack (args, nargout, octave_stdout); @@ -1449,14 +1983,14 @@ function returns.\n\ if (arg == "in") { Vdebugging = false; - Vtrack_line_num = true; + Vtrack_line_num = true; tree_evaluator::dbstep_flag = -1; } else if (arg == "out") { Vdebugging = false; - Vtrack_line_num = true; + Vtrack_line_num = true; tree_evaluator::dbstep_flag = -2; } @@ -1467,7 +2001,7 @@ function returns.\n\ if (n > 0) { Vdebugging = false; - Vtrack_line_num = true; + Vtrack_line_num = true; tree_evaluator::dbstep_flag = n; } @@ -1481,7 +2015,7 @@ function returns.\n\ else { Vdebugging = false; - Vtrack_line_num = true; + Vtrack_line_num = true; tree_evaluator::dbstep_flag = 1; } @@ -1506,7 +2040,7 @@ Leave command-line debugging mode and continue code execution normally.\n\ if (args.length () == 0) { Vdebugging = false; - Vtrack_line_num = true; + Vtrack_line_num = true; tree_evaluator::reset_debug_state (); } @@ -1532,7 +2066,7 @@ the Octave prompt.\n\ if (args.length () == 0) { Vdebugging = false; - Vtrack_line_num = true; + Vtrack_line_num = true; tree_evaluator::reset_debug_state (); diff --git a/libinterp/corefcn/debug.h b/libinterp/corefcn/debug.h index fb31b78..c6d42d1 100644 --- a/libinterp/corefcn/debug.h +++ b/libinterp/corefcn/debug.h @@ -30,9 +30,18 @@ along with Octave; see the file COPYING. If not, see class octave_value_list; class octave_user_code; +static std::string bp_empty_string (""); -// Interface to breakpoints,. +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 @@ -45,6 +54,7 @@ private: public: + // mapping from ??? to line number of breakpoint typedef std::map intmap; typedef intmap::const_iterator const_intmap_iterator; @@ -55,14 +65,19 @@ public: typedef fname_line_map::const_iterator const_fname_line_map_iterator; typedef fname_line_map::iterator fname_line_map_iterator; + typedef std::map > 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 intmap& lines = intmap (), + const std::string& condition = bp_empty_string) { return instance_ok () - ? instance->do_add_breakpoint (fname, lines) : intmap (); + ? instance->do_add_breakpoint (fname, lines, condition) : intmap (); } // Remove a breakpoint from a line in file. @@ -91,11 +106,11 @@ public: // Return all breakpoints. Each element of the map is a vector // containing the breakpoints corresponding to a given function name. - static fname_line_map + static fname_bp_map get_breakpoint_list (const octave_value_list& fname_list) { return instance_ok () - ? instance->do_get_breakpoint_list (fname_list) : fname_line_map (); + ? instance->do_get_breakpoint_list (fname_list) : fname_bp_map (); } static bool @@ -104,22 +119,53 @@ public: return instance_ok () ? instance->do_have_breakpoints () : 0; } + // Should we enter debugging for this particular 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? + static bool + debug_on_warn (const std::string& ID) + { + return (warnings_that_stop.empty () || warnings_that_stop.count (ID)); + } + private: typedef std::set::const_iterator const_bp_set_iterator; typedef std::set::iterator bp_set_iterator; - // Set of function names containing at least one breakpoint. + // Set of function (.m file) names containing at least one breakpoint. std::set bp_set; + + // Set of error and warning message IDs that cause us to stop + // *if* Vdebug_on_error or Vdebug_on_warning is set. + // Empty means stop on any error / warning. + static std::set errors_that_stop; + static std::set warnings_that_stop; + + // friends that access the above + friend void parse_dbfunction_params (const char *, const octave_value_list&, + std::string&, bp_table::intmap&, + std::string&); + friend octave_map stop_on_err_warn_status (bool toScreen); + friend void dbstop_process_map_args (const octave_map& mv); + + 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, intmap& retval); + const intmap& line, const std::string& condition, + intmap& retval); - intmap do_add_breakpoint (const std::string& fname, const intmap& lines); + 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); @@ -134,7 +180,7 @@ private: void do_remove_all_breakpoints (void); - fname_line_map do_get_breakpoint_list (const octave_value_list& fname_list); + fname_bp_map do_get_breakpoint_list (const octave_value_list& fname_list); bool do_have_breakpoints (void) { return (! bp_set.empty ()); } }; diff --git a/libinterp/corefcn/error.cc b/libinterp/corefcn/error.cc index 437b293..0023b8e 100644 --- a/libinterp/corefcn/error.cc +++ b/libinterp/corefcn/error.cc @@ -32,6 +32,7 @@ along with Octave; see the file COPYING. If not, see #include #include +#include "debug.h" #include "defun.h" #include "error.h" #include "input.h" @@ -450,7 +451,8 @@ error_2 (const char *id, const char *fmt, va_list args, bool with_cfn = false) if (error_state != -2 && in_user_code && ! discard_error_messages) pr_where ("error"); - if (interactive && Vdebug_on_error && init_state == 0 && in_user_code) + if (interactive && Vdebug_on_error && init_state == 0 && in_user_code + && bp_table::debug_on_err (id)) { unwind_protect frame; frame.protect_var (Vdebug_on_error); @@ -656,7 +658,7 @@ warning_1 (const char *id, const char *fmt, va_list args) warning_state = 1; if ((interactive || forced_interactive) - && Vdebug_on_warning && in_user_code) + && Vdebug_on_warning && in_user_code && bp_table::debug_on_warn (id)) { unwind_protect frame; frame.protect_var (Vdebug_on_warning); diff --git a/libinterp/corefcn/sighandlers.cc b/libinterp/corefcn/sighandlers.cc index 93ca615..eb8d8fd 100644 --- a/libinterp/corefcn/sighandlers.cc +++ b/libinterp/corefcn/sighandlers.cc @@ -53,6 +53,8 @@ along with Octave; see the file COPYING. If not, see #include "utils.h" #include "variables.h" +#include "stacktrace.h" + // Nonzero means we have already printed a message for this series of // SIGPIPES. We assume that the writer will eventually give up. int pipe_handler_error_count = 0; @@ -338,6 +340,8 @@ my_friendly_exit (const char *sig_name, int sig_number, std::cerr << "panic: " << sig_name << " -- stopping myself...\n"; + print_stacktrace (20); + if (save_vars) dump_octave_core (); diff --git a/libinterp/parse-tree/pt-bp.cc b/libinterp/parse-tree/pt-bp.cc index 8648b77..79b91c9 100644 --- a/libinterp/parse-tree/pt-bp.cc +++ b/libinterp/parse-tree/pt-bp.cc @@ -351,6 +351,9 @@ tree_breakpoint::visit_statement (tree_statement& stmt) } } +// Called by +// tree_statement_list::set_breakpoint (int line, std::string& condition) +// with lst consisting of a user function in which to set a breakpoint. void tree_breakpoint::visit_statement_list (tree_statement_list& lst) { @@ -451,7 +454,7 @@ tree_breakpoint::take_action (tree& tr) { if (act == set) { - tr.set_breakpoint (); + tr.set_breakpoint (condition); line = tr.line (); found = true; } @@ -466,7 +469,10 @@ tree_breakpoint::take_action (tree& tr) else if (act == list) { if (tr.is_breakpoint ()) - bp_list.append (octave_value (tr.line ())); + { + bp_list.append (octave_value (tr.line ())); + bp_cond_list.append (octave_value (tr.bp_cond ())); + } } else panic_impossible (); @@ -479,7 +485,7 @@ tree_breakpoint::take_action (tree_statement& stmt) if (act == set) { - stmt.set_breakpoint (); + stmt.set_breakpoint (condition); line = lineno; found = true; } @@ -494,7 +500,10 @@ tree_breakpoint::take_action (tree_statement& stmt) else if (act == list) { if (stmt.is_breakpoint ()) - bp_list.append (octave_value (lineno)); + { + bp_list.append (octave_value (lineno)); + bp_cond_list.append (octave_value (stmt.bp_cond ())); + } } else panic_impossible (); diff --git a/libinterp/parse-tree/pt-bp.h b/libinterp/parse-tree/pt-bp.h index bda2514..a3a43f9 100644 --- a/libinterp/parse-tree/pt-bp.h +++ b/libinterp/parse-tree/pt-bp.h @@ -32,6 +32,7 @@ along with Octave; see the file COPYING. If not, see class tree; class tree_decl_command; +static std::string pt_bp_empty_string (""); class tree_breakpoint : public tree_walker { @@ -39,8 +40,8 @@ public: enum action { set = 1, clear = 2, list = 3 }; - tree_breakpoint (int l, action a) - : line (l), act (a), found (false), bp_list () { } + tree_breakpoint (int l, action a, const std::string& c = pt_bp_empty_string) + : line (l), act (a), condition (c), found (false), bp_list () { } ~tree_breakpoint (void) { } @@ -135,6 +136,7 @@ public: void visit_unwind_protect_command (tree_unwind_protect_command&); octave_value_list get_list (void) { return bp_list; } + octave_value_list get_cond_list (void) { return bp_cond_list; } int get_line (void) { return found ? line : 0; } @@ -152,12 +154,18 @@ private: // What to do. action act; + // Expression which must be true to break + std::string condition; + // Have we already found the line? bool found; // List of breakpoint line numbers. octave_value_list bp_list; + // List of breakpoint conditions. + octave_value_list bp_cond_list; + // No copying! tree_breakpoint (const tree_breakpoint&); diff --git a/libinterp/parse-tree/pt-eval.cc b/libinterp/parse-tree/pt-eval.cc index 2a83c2b..2da5280 100644 --- a/libinterp/parse-tree/pt-eval.cc +++ b/libinterp/parse-tree/pt-eval.cc @@ -98,7 +98,7 @@ tree_evaluator::visit_break_command (tree_break_command& cmd) if (! error_state) { if (debug_mode) - do_breakpoint (cmd.is_breakpoint ()); + do_breakpoint (cmd.is_breakpoint (true)); if (statement_context == function || statement_context == script || in_loop_command) @@ -118,7 +118,7 @@ tree_evaluator::visit_continue_command (tree_continue_command& cmd) if (! error_state) { if (debug_mode) - do_breakpoint (cmd.is_breakpoint ()); + do_breakpoint (cmd.is_breakpoint (true)); if (statement_context == function || statement_context == script || in_loop_command) @@ -221,7 +221,7 @@ void tree_evaluator::visit_global_command (tree_global_command& cmd) { if (debug_mode) - do_breakpoint (cmd.is_breakpoint ()); + do_breakpoint (cmd.is_breakpoint (true)); do_decl_init_list (do_global_init, cmd.initializer_list ()); } @@ -230,7 +230,7 @@ void tree_evaluator::visit_persistent_command (tree_persistent_command& cmd) { if (debug_mode) - do_breakpoint (cmd.is_breakpoint ()); + do_breakpoint (cmd.is_breakpoint (true)); do_decl_init_list (do_static_init, cmd.initializer_list ()); } @@ -300,7 +300,7 @@ tree_evaluator::visit_simple_for_command (tree_simple_for_command& cmd) return; if (debug_mode) - do_breakpoint (cmd.is_breakpoint ()); + do_breakpoint (cmd.is_breakpoint (true)); // FIXME: need to handle PARFOR loops here using cmd.in_parallel () // and cmd.maxproc_expr (); @@ -425,7 +425,7 @@ tree_evaluator::visit_complex_for_command (tree_complex_for_command& cmd) return; if (debug_mode) - do_breakpoint (cmd.is_breakpoint ()); + do_breakpoint (cmd.is_breakpoint (true)); unwind_protect frame; @@ -568,7 +568,7 @@ tree_evaluator::visit_if_command_list (tree_if_command_list& lst) octave_call_stack::set_location (tic->line (), tic->column ()); if (debug_mode && ! tic->is_else_clause ()) - do_breakpoint (tic->is_breakpoint ()); + do_breakpoint (tic->is_breakpoint (true)); if (tic->is_else_clause () || expr->is_logically_true ("if")) { @@ -613,7 +613,7 @@ void tree_evaluator::visit_no_op_command (tree_no_op_command& cmd) { if (debug_mode && cmd.is_end_of_fcn_or_script ()) - do_breakpoint (cmd.is_breakpoint (), true); + do_breakpoint (cmd.is_breakpoint (true), true); } void @@ -658,7 +658,7 @@ tree_evaluator::visit_return_command (tree_return_command& cmd) if (! error_state) { if (debug_mode) - do_breakpoint (cmd.is_breakpoint ()); + do_breakpoint (cmd.is_breakpoint (true)); // Act like dbcont. @@ -720,7 +720,7 @@ tree_evaluator::visit_statement (tree_statement& stmt) else { if (debug_mode) - do_breakpoint (expr->is_breakpoint ()); + do_breakpoint (expr->is_breakpoint (true)); // FIXME: maybe all of this should be packaged in // one virtual function that returns a flag saying whether @@ -852,7 +852,7 @@ void tree_evaluator::visit_switch_command (tree_switch_command& cmd) { if (debug_mode) - do_breakpoint (cmd.is_breakpoint ()); + do_breakpoint (cmd.is_breakpoint (true)); tree_expression *expr = cmd.switch_value (); @@ -1091,7 +1091,7 @@ tree_evaluator::visit_while_command (tree_while_command& cmd) for (;;) { if (debug_mode) - do_breakpoint (cmd.is_breakpoint ()); + do_breakpoint (cmd.is_breakpoint (true)); if (expr->is_logically_true ("while")) { @@ -1151,7 +1151,7 @@ tree_evaluator::visit_do_until_command (tree_do_until_command& cmd) break; if (debug_mode) - do_breakpoint (cmd.is_breakpoint ()); + do_breakpoint (cmd.is_breakpoint (true)); if (expr->is_logically_true ("do-until")) break; @@ -1161,7 +1161,7 @@ tree_evaluator::visit_do_until_command (tree_do_until_command& cmd) void tree_evaluator::do_breakpoint (tree_statement& stmt) const { - do_breakpoint (stmt.is_breakpoint (), stmt.is_end_of_fcn_or_script ()); + do_breakpoint (stmt.is_breakpoint (true), stmt.is_end_of_fcn_or_script ()); } void diff --git a/libinterp/parse-tree/pt-stmt.cc b/libinterp/parse-tree/pt-stmt.cc index 2000c14..39c4ee3 100644 --- a/libinterp/parse-tree/pt-stmt.cc +++ b/libinterp/parse-tree/pt-stmt.cc @@ -48,6 +48,8 @@ along with Octave; see the file COPYING. If not, see #include "utils.h" #include "variables.h" +#include "debug.h" + // A list of commands to be executed. tree_statement::~tree_statement (void) @@ -71,12 +73,12 @@ tree_statement::print_result (void) } void -tree_statement::set_breakpoint (void) +tree_statement::set_breakpoint (const std::string& condition) { if (cmd) - cmd->set_breakpoint (); + cmd->set_breakpoint (condition); else if (expr) - expr->set_breakpoint (); + expr->set_breakpoint (condition); } void @@ -89,9 +91,16 @@ tree_statement::delete_breakpoint (void) } bool -tree_statement::is_breakpoint (void) const +tree_statement::is_breakpoint (bool check_active) const +{ + return cmd ? cmd->is_breakpoint (check_active) + : (expr ? expr->is_breakpoint (check_active) : false); +} + +std::string +tree_statement::bp_cond () const { - return cmd ? cmd->is_breakpoint () : (expr ? expr->is_breakpoint () : false); + return cmd ? cmd->bp_cond () : (expr ? expr->bp_cond () : "0"); } int @@ -178,10 +187,12 @@ tree_statement::accept (tree_walker& tw) tw.visit_statement (*this); } +// Create a "breakpoint" tree-walker, and get it to "walk" this statement list +// (TODO: What does that do???) int -tree_statement_list::set_breakpoint (int line) +tree_statement_list::set_breakpoint (int line, const std::string& condition) { - tree_breakpoint tbp (line, tree_breakpoint::set); + tree_breakpoint tbp (line, tree_breakpoint::set, condition); accept (tbp); return tbp.get_line (); @@ -218,9 +229,34 @@ tree_statement_list::list_breakpoints (void) return tbp.get_list (); } +// Get list of pairs (breakpoint line, breakpoint condition) +std::list +tree_statement_list::breakpoints_and_conds (void) +{ + tree_breakpoint tbp (0, tree_breakpoint::list); + accept (tbp); + + std::list retval; + octave_value_list lines = tbp.get_list (); + octave_value_list conds = tbp.get_cond_list (); + + for (int i = 0; i < lines.length (); i++) + { + retval.push_back (bp_type (lines(i).double_value (), + conds(i).string_value ())); + } + + return retval; +} + +// Add breakpoints to file at multiple lines (the second arguments of line), +// to stop only if condition is true. +// Does something I don't understand with octave_link::update_breakpoint. +// TODO COME BACK TO ME bp_table::intmap tree_statement_list::add_breakpoint (const std::string& file, - const bp_table::intmap& line) + const bp_table::intmap& line, + const std::string& condition) { bp_table::intmap retval; @@ -234,7 +270,7 @@ tree_statement_list::add_breakpoint (const std::string& file, { int lineno = p->second; - retval[i] = set_breakpoint (lineno); + retval[i] = set_breakpoint (lineno, condition); if (retval[i] != 0 && ! file.empty ()) octave_link::update_breakpoint (true, file, retval[i]); diff --git a/libinterp/parse-tree/pt-stmt.h b/libinterp/parse-tree/pt-stmt.h index ff4fbb7..7a52d28 100644 --- a/libinterp/parse-tree/pt-stmt.h +++ b/libinterp/parse-tree/pt-stmt.h @@ -65,11 +65,12 @@ public: bool is_expression (void) const { return expr != 0; } - void set_breakpoint (void); + void set_breakpoint (const std::string& condition); void delete_breakpoint (void); - bool is_breakpoint (void) const; + bool is_breakpoint (bool check_valid = false) const; + std::string bp_cond () const; int line (void) const; int column (void) const; @@ -159,14 +160,17 @@ public: bool is_script_body (void) const { return script_body; } - int set_breakpoint (int line); + int set_breakpoint (int line, const std::string& condition); void delete_breakpoint (int line); octave_value_list list_breakpoints (void); + std::list breakpoints_and_conds (void); + bp_table::intmap add_breakpoint (const std::string& file, - const bp_table::intmap& line); + const bp_table::intmap& line, + const std::string& condition); bp_table::intmap remove_all_breakpoints (const std::string& file); diff --git a/libinterp/parse-tree/pt.cc b/libinterp/parse-tree/pt.cc index f28b436..1282c8b 100644 --- a/libinterp/parse-tree/pt.cc +++ b/libinterp/parse-tree/pt.cc @@ -48,3 +48,29 @@ tree::str_print_code (void) return retval; } + +// function from libinterp/parse-tree/oct-parse.cc, not listed in oct-parse.h +octave_value_list eval_string (const std::string&, bool, int&, int); +// Is the current breakpoint condition met? +bool +tree::meets_bp_condition () const +{ + bool retval; + if (bp == 0) + retval = false; + else if (bp->length () == 0) // empty condition always met + retval = true; + else + { + int parse_status = 0; + octave_value_list val = eval_string (*bp, 1, parse_status, 1); + if (parse_status == 0 && !error_state && val(0).bool_value ()) + retval = true; + else + { + error_state = 0; + retval = false; + } + } + return retval; +} diff --git a/libinterp/parse-tree/pt.h b/libinterp/parse-tree/pt.h index ac6cc0d..e297074 100644 --- a/libinterp/parse-tree/pt.h +++ b/libinterp/parse-tree/pt.h @@ -29,6 +29,8 @@ along with Octave; see the file COPYING. If not, see class octave_function; class tree_walker; +class bp_table; +bool meets_condition (std::string *); // Base class for the parse tree. @@ -38,7 +40,7 @@ tree public: tree (int l = -1, int c = -1) - : line_num (l), column_num (c), bp (false) { } + : line_num (l), column_num (c), bp (NULL) { } virtual ~tree (void) { } @@ -56,11 +58,24 @@ public: column_num = c; } - virtual void set_breakpoint (void) { bp = true; } + virtual void set_breakpoint (std::string condition) + { if (bp) + *bp = condition; + else + bp = new std::string(condition); + } - virtual void delete_breakpoint (void) { bp = false; } + virtual void delete_breakpoint (void) { if (bp) delete bp; bp = NULL; } - bool is_breakpoint (void) const { return bp; } + bool meets_bp_condition (void) const; + + bool is_breakpoint (bool check_active = false) const + { return bp && (!check_active || meets_bp_condition ()); } + + // breakpoint condition, or "0" (i.e., "false") if no breakpoint. + // To distinguish "0" from a disabled breakpoint, test "is_breakpoint" too. + const std::string bp_cond (void) const + { return bp ? *bp : std::string("0"); } std::string str_print_code (void); @@ -73,8 +88,8 @@ private: int line_num; int column_num; - // Breakpoint flag. - bool bp; + // Breakpoint flag: NULL if no breakpoint, or the condition if there is one + std::string *bp; // No copying!