diff -r f7e64e1baa08 libgui/src/m-editor/file-editor-interface.h --- a/libgui/src/m-editor/file-editor-interface.h Fri Jan 01 21:29:41 2016 -0800 +++ b/libgui/src/m-editor/file-editor-interface.h Sat Jan 02 20:20:02 2016 +1100 @@ -56,7 +56,7 @@ public: virtual void handle_update_breakpoint_marker_request (bool insert, const QString& file, - int line) = 0; + int line, const QString& cond) = 0; virtual void handle_edit_file_request (const QString& file) = 0; @@ -76,7 +76,8 @@ public slots: int line = -1, bool debug_pointer = false, bool breakpoint_marker = false, - bool insert = true) = 0; + bool insert = true, + const QString& cond = "") = 0; //signals: //protected: diff -r f7e64e1baa08 libgui/src/m-editor/file-editor-tab.cc --- a/libgui/src/m-editor/file-editor-tab.cc Fri Jan 01 21:29:41 2016 -0800 +++ b/libgui/src/m-editor/file-editor-tab.cc Sat Jan 02 20:20:02 2016 +1100 @@ -21,6 +21,11 @@ along with Octave; see the file COPYING. */ +/** + @file A single GUI file tab. + This interfaces QSciScintilla with the rest of Octave. + */ + #ifdef HAVE_CONFIG_H #include #endif @@ -73,6 +78,12 @@ along with Octave; see the file COPYING. bool file_editor_tab::_cancelled = false; +/** + A file_editor_tab object consists of a text area and three left margins. + The first holds breakpoints, bookmarks, and the debug program counter. + The second holds line numbers. + The third holds "fold" marks, to hide sections of text. + */ // Make parent null for the file editor tab so that warning // WindowModal messages don't affect grandparents. file_editor_tab::file_editor_tab (const QString& directory_arg) @@ -140,17 +151,25 @@ file_editor_tab::file_editor_tab (const _edit_area->setMarkerBackgroundColor (QColor (0,0,232), marker::bookmark); _edit_area->markerDefine (QsciScintilla::Circle, marker::breakpoint); _edit_area->setMarkerBackgroundColor (QColor (192,0,0), marker::breakpoint); - _edit_area->markerDefine (QsciScintilla::RightTriangle, marker::debugger_position); - _edit_area->setMarkerBackgroundColor (QColor (255,255,0), marker::debugger_position); + _edit_area->markerDefine (QsciScintilla::Circle, marker::cond_break); + _edit_area->setMarkerBackgroundColor (QColor (224,224,0), marker::cond_break); + _edit_area->markerDefine (QsciScintilla::RightTriangle, + marker::debugger_position); + _edit_area->setMarkerBackgroundColor (QColor (255,255,0), + marker::debugger_position); _edit_area->markerDefine (QsciScintilla::RightTriangle, marker::unsure_debugger_position); - _edit_area->setMarkerBackgroundColor (QColor (192,192,192), marker::unsure_debugger_position); + _edit_area->setMarkerBackgroundColor (QColor (192,192,192), + marker::unsure_debugger_position); connect (_edit_area, SIGNAL (marginClicked (int, int, Qt::KeyboardModifiers)), this, SLOT (handle_margin_clicked (int, int, Qt::KeyboardModifiers))); + connect (_edit_area, SIGNAL (context_menu_break_condition_signal (int)), + this, SLOT (handle_context_menu_break_condition (int))); + // line numbers _edit_area->setMarginsForegroundColor (QColor (96, 96, 96)); _edit_area->setMarginsBackgroundColor (QColor (232, 232, 220)); @@ -301,6 +320,64 @@ file_editor_tab::handle_context_menu_edi emit edit_mfile_request (word_at_cursor, _file_name, _ced, -1); } +// If "dbstop if ..." selected from context menu, create a conditional +// breakpoint. The default condition is (a) the existing condition if there +// is already a breakpoint (b) any selected text, or (c) empty +void +file_editor_tab::handle_context_menu_break_condition (int linenr) +{ + QString cond; + + // Search for previous condition. FIXME -- is there a more direct way? + if (_edit_area->markersAtLine (linenr) & (1 << marker::cond_break)) + { + QIntList bp_lines; + QStringList bp_conditions; + emit report_marker_linenr (bp_lines, bp_conditions); + for (int i = 0; i < bp_lines.length (); i++) + if (bp_lines.value (i) == linenr) + { + cond = bp_conditions.value (i); + break; + } + } + else + { + if ((_edit_area->markersAtLine (linenr) & (1 << marker::breakpoint)) + && _edit_area->isModified ()) + { + message_cannot_breakpoint_changed_file (); + return; + } + } + + // If text selected by the mouse, default to that instead + // If both present, use the OR of them, to avoid accidental overwriting + // FIXME If both are present, show old condition unselected and + // the selection (in edit area) selected (in the dialog). + if (_edit_area->hasSelectedText ()) + { + if (cond == "") + cond = _edit_area->selectedText (); + else + cond = "(" + cond + ") || (" + _edit_area->selectedText () + ")"; + } + + bool ok; + QString new_condition + = QInputDialog::getText (this, tr ("Breakpoint condition"), + tr ("dbstop if"), QLineEdit::Normal, cond, &ok); + if (ok) // if cancel, don't change breakpoint condition + { + if (new_condition.length () > 0) // if no condition, unconditional + new_condition = " if " + new_condition; + + bp_info info (_file_name, linenr); // function name from filename + QString str = QString ("dbstop in ") + info.function_name.c_str () + + " at " + QString::number(linenr+1) + new_condition; + emit execute_command_in_terminal_signal (str); + } +} void file_editor_tab::set_file_name (const QString& fileName) @@ -319,7 +396,7 @@ file_editor_tab::set_file_name (const QS // update the file editor with current editing directory emit editor_state_changed (_copy_available, _is_octave_file); - // add the new file to the mru list + // add the new file to the most recently used list emit mru_add_file (_file_name, _encoding); } @@ -365,21 +442,21 @@ file_editor_tab::handle_margin_clicked ( if (state & Qt::ControlModifier) { - if (markers_mask && (1 << marker::bookmark)) + if (markers_mask & (1 << marker::bookmark)) _edit_area->markerDelete (editor_linenr, marker::bookmark); else _edit_area->markerAdd (editor_linenr, marker::bookmark); } else { - if (markers_mask && (1 << marker::breakpoint)) + if (markers_mask & (1 << marker::breakpoint)) handle_request_remove_breakpoint (editor_linenr + 1); else { if (_edit_area->isModified ()) message_cannot_breakpoint_changed_file (); else - handle_request_add_breakpoint (editor_linenr + 1); + handle_request_add_breakpoint (editor_linenr + 1, ""); } } } @@ -763,7 +840,7 @@ file_editor_tab::toggle_bookmark (const int line, cur; _edit_area->getCursorPosition (&line, &cur); - if (_edit_area->markersAtLine (line) && (1 << marker::bookmark)) + if (_edit_area->markersAtLine (line) & (1 << marker::bookmark)) _edit_area->markerDelete (line, marker::bookmark); else _edit_area->markerAdd (line, marker::bookmark); @@ -778,8 +855,8 @@ file_editor_tab::next_bookmark (const QW int line, cur; _edit_area->getCursorPosition (&line, &cur); - if (_edit_area->markersAtLine (line) && (1 << marker::bookmark)) - line++; // we have a breakpoint here, so start search from next line + if (_edit_area->markersAtLine (line) & (1 << marker::bookmark)) + line++; // we have a bookmark here, so start search from next line int nextline = _edit_area->markerFindNext (line, (1 << marker::bookmark)); @@ -795,8 +872,8 @@ file_editor_tab::previous_bookmark (cons int line, cur; _edit_area->getCursorPosition (&line, &cur); - if (_edit_area->markersAtLine (line) && (1 << marker::bookmark)) - line--; // we have a breakpoint here, so start search from prev line + if (_edit_area->markersAtLine (line) & (1 << marker::bookmark)) + line--; // we have a bookmark here, so start search from prev line int prevline = _edit_area->markerFindPrevious (line, (1 << marker::bookmark)); @@ -819,7 +896,7 @@ file_editor_tab::add_breakpoint_callback line_info[0] = info.line; if (octave_qt_link::file_in_path (info.file, info.dir)) - bp_table::add_breakpoint (info.function_name, line_info); + bp_table::add_breakpoint (info.function_name, line_info, info.condition); } void @@ -839,8 +916,9 @@ file_editor_tab::remove_all_breakpoints_ bp_table::remove_all_breakpoints_in_file (info.function_name, true); } -file_editor_tab::bp_info::bp_info (const QString& fname, int l) - : line (l), file (fname.toStdString ()) +file_editor_tab::bp_info::bp_info (const QString& fname, int l, + const QString& cond) + : line (l), file (fname.toStdString ()), condition (cond.toStdString ()) { QFileInfo file_info (fname); @@ -870,9 +948,10 @@ file_editor_tab::bp_info::bp_info (const } void -file_editor_tab::handle_request_add_breakpoint (int line) +file_editor_tab::handle_request_add_breakpoint (int line, + const QString& condition) { - bp_info info (_file_name, line); + bp_info info (_file_name, line, condition); octave_link::post_event (this, &file_editor_tab::add_breakpoint_callback, info); @@ -896,17 +975,19 @@ file_editor_tab::toggle_breakpoint (cons int editor_linenr, cur; _edit_area->getCursorPosition (&editor_linenr, &cur); - if (_edit_area->markersAtLine (editor_linenr) && (1 << marker::breakpoint)) + if (_edit_area->markersAtLine (editor_linenr) & (1 << marker::breakpoint)) request_remove_breakpoint_via_editor_linenr (editor_linenr); else { if (_edit_area->isModified ()) message_cannot_breakpoint_changed_file (); else - handle_request_add_breakpoint (editor_linenr + 1); + handle_request_add_breakpoint (editor_linenr + 1, ""); } } +// Move the text cursor to the closest breakpoint (conditional or unconditional) +// after the current line. void file_editor_tab::next_breakpoint (const QWidget *ID) { @@ -916,27 +997,41 @@ file_editor_tab::next_breakpoint (const int line, cur; _edit_area->getCursorPosition (&line, &cur); - if (_edit_area->markersAtLine (line) && (1 << marker::breakpoint)) + if (_edit_area->markersAtLine (line) + & ((1 << marker::breakpoint) | (1 << marker::cond_break))) line++; // we have a breakpoint here, so start search from next line int nextline = _edit_area->markerFindNext (line, (1 << marker::breakpoint)); + int nextcond = _edit_area->markerFindNext (line, (1 << marker::cond_break)); + + // Check if the next conditional breakpoint is before next unconditional one. + if (nextcond != -1 && nextcond < nextline) + nextline = nextcond; _edit_area->setCursorPosition (nextline, 0); } +// Move the text cursor to the closest breakpoint (conditional or unconditional) +// before the current line. void file_editor_tab::previous_breakpoint (const QWidget *ID) { if (ID != this) return; - int line, cur, prevline; + int line, cur, prevline, prevcond; _edit_area->getCursorPosition (&line, &cur); - if (_edit_area->markersAtLine (line) && (1 << marker::breakpoint)) + if (_edit_area->markersAtLine (line) + & ((1 << marker::breakpoint) | 1 << marker::cond_break)) line--; // we have a breakpoint here, so start search from prev line prevline = _edit_area->markerFindPrevious (line, (1 << marker::breakpoint)); + prevcond = _edit_area->markerFindPrevious (line, (1 << marker::cond_break)); + + // Check if the prev conditional breakpoint is closer than the unconditional. + if (prevcond != -1 && prevcond > prevline) + prevline = prevcond; _edit_area->setCursorPosition (prevline, 0); } @@ -1555,9 +1650,10 @@ file_editor_tab::save_file (const QStrin QFile file (file_to_save); // Get a list of all the breakpoint line numbers. - QIntList list; - emit report_editor_linenr (list); - if (! list.isEmpty ()) + QIntList bp_lines; + QStringList bp_conditions; + emit report_marker_linenr (bp_lines, bp_conditions); + if (! bp_lines.isEmpty ()) { // At least one breakpoint is present. Get rid of breakpoints. remove_all_breakpoints (this); @@ -1633,7 +1729,7 @@ file_editor_tab::save_file (const QStrin } // Attempt to restore the breakpoints if that is desired. - if (! list.isEmpty ()) + if (! bp_lines.isEmpty ()) { bool restore_breakpoints; if (_breakpoint_filesave_behavior == "RESTORE") @@ -1689,8 +1785,9 @@ file_editor_tab::save_file (const QStrin if (restore_breakpoints) { - for (int i = 0; i < list.length (); i++) - handle_request_add_breakpoint (list.value (i) + 1); + for (int i = 0; i < bp_lines.length (); i++) + handle_request_add_breakpoint (bp_lines.value (i) + 1, + bp_conditions.value (i)); } } } @@ -2217,7 +2314,7 @@ file_editor_tab::insert_debugger_pointer if (ID != this || ID == 0) return; - emit remove_all_positions (); // remove all positions + emit remove_all_positions (); // debugger_position, unsure_debugger_position if (line > 0) { @@ -2284,7 +2381,7 @@ file_editor_tab::delete_debugger_pointer } void -file_editor_tab::do_breakpoint_marker (bool insert, const QWidget *ID, int line) +file_editor_tab::do_breakpoint_marker (bool insert, const QWidget *ID, int line, const QString& cond) { if (ID != this || ID == 0) return; @@ -2302,7 +2399,9 @@ file_editor_tab::do_breakpoint_marker (b if (editor_linenr == -1) { - marker *bp = new marker (_edit_area, line, marker::breakpoint); + marker *bp = new marker (_edit_area, line, + cond == "" ? marker::breakpoint + : marker::cond_break, cond); connect (this, SIGNAL (remove_breakpoint_via_debugger_linenr (int)), bp, SLOT (handle_remove_via_original_linenr (int))); connect (this, SIGNAL (request_remove_breakpoint_via_editor_linenr (int)), @@ -2313,8 +2412,10 @@ file_editor_tab::do_breakpoint_marker (b bp, SLOT (handle_find_translation (int, int&))); connect (this, SIGNAL (find_linenr_just_before (int, int&, int&)), bp, SLOT (handle_find_just_before (int, int&, int&))); - connect (this, SIGNAL (report_editor_linenr (QIntList&)), - bp, SLOT (handle_report_editor_linenr (QIntList&))); + connect (this, SIGNAL (report_marker_linenr (QIntList&, + QStringList&)), + bp, SLOT (handle_report_editor_linenr (QIntList&, + QStringList&))); connect (bp, SIGNAL (request_remove (int)), this, SLOT (handle_request_remove_breakpoint (int))); } diff -r f7e64e1baa08 libgui/src/m-editor/file-editor-tab.h --- a/libgui/src/m-editor/file-editor-tab.h Fri Jan 01 21:29:41 2016 -0800 +++ b/libgui/src/m-editor/file-editor-tab.h Sat Jan 02 20:20:02 2016 +1100 @@ -127,7 +127,8 @@ public slots: void insert_debugger_pointer (const QWidget *ID, int line = -1); void delete_debugger_pointer (const QWidget *ID, int line = -1); - void do_breakpoint_marker (bool insert, const QWidget *ID, int line = -1); + void do_breakpoint_marker (bool insert, const QWidget *ID, int line = -1, + const QString& cond = ""); void set_modified (bool modified = true); @@ -140,8 +141,9 @@ public slots: void file_has_changed (const QString& fileName); void handle_context_menu_edit (const QString&); + void handle_context_menu_break_condition (int linenr); - void handle_request_add_breakpoint (int line); + void handle_request_add_breakpoint (int line, const QString& cond); void handle_request_remove_breakpoint (int line); void handle_octave_result (QObject *requester, QString& command, octave_value_list &result); @@ -165,9 +167,10 @@ signals: void remove_all_breakpoints (void); void find_translated_line_number (int original_linenr, int& translated_linenr); void find_linenr_just_before (int linenr, int& original_linenr, int& editor_linenr); - void report_editor_linenr (QIntList& int_list); + void report_marker_linenr (QIntList& lines, QStringList& conditions); void remove_position_via_debugger_linenr (int debugger_linenr); void remove_all_positions (void); + void execute_command_in_terminal_signal (const QString&); // TODO: The following is similar to "process_octave_code" signal. However, // currently that signal is connected to something that simply focuses a // window and not actually communicate with Octave. @@ -213,12 +216,13 @@ private: struct bp_info { - bp_info (const QString& fname, int l = 0); + bp_info (const QString& fname, int l = 0, const QString& cond = ""); int line; std::string file; std::string dir; std::string function_name; + std::string condition; }; bool valid_file_name (const QString& file=QString ()); diff -r f7e64e1baa08 libgui/src/m-editor/file-editor.cc --- a/libgui/src/m-editor/file-editor.cc Fri Jan 01 21:29:41 2016 -0800 +++ b/libgui/src/m-editor/file-editor.cc Sat Jan 02 20:20:02 2016 +1100 @@ -388,11 +388,14 @@ file_editor::request_open_files (const Q request_open_file (open_file_names.at (i), _file_encoding); } +// Open a file, if not already open, and mark the current execution location +// and/or a breakpoint with condition cond. void file_editor::request_open_file (const QString& openFileName, const QString& encoding, int line, bool debug_pointer, - bool breakpoint_marker, bool insert) + bool breakpoint_marker, bool insert, + const QString& cond) { if (call_custom_editor (openFileName, line)) return; // custom editor called @@ -424,7 +427,7 @@ file_editor::request_open_file (const QS emit fetab_insert_debugger_pointer (tab, line); if (breakpoint_marker) - emit fetab_do_breakpoint_marker (insert, tab, line); + emit fetab_do_breakpoint_marker (insert, tab, line, cond); } if (! ((breakpoint_marker || debug_pointer) && is_editor_console_tabbed ())) @@ -460,7 +463,7 @@ file_editor::request_open_file (const QS line); if (breakpoint_marker) emit fetab_do_breakpoint_marker (insert, fileEditorTab, - line); + line, cond); } } else @@ -742,9 +745,10 @@ file_editor::handle_delete_debugger_poin void file_editor::handle_update_breakpoint_marker_request (bool insert, const QString& file, - int line) + int line, + const QString& cond) { - request_open_file (file, QString (), line, false, true, insert); + request_open_file (file, QString (), line, false, true, insert, cond); } void @@ -871,6 +875,7 @@ file_editor::request_remove_bookmark (bo emit fetab_remove_bookmark (_tab_widget->currentWidget ()); } +// FIXME What should this do with conditional breakpoints? void file_editor::request_toggle_breakpoint (bool) { @@ -1965,6 +1970,9 @@ file_editor::add_file_editor_tab (file_e connect (this, SIGNAL (fetab_check_modified_file (void)), f, SLOT (check_modified_file (void))); + connect (f, SIGNAL (execute_command_in_terminal_signal (const QString&)), + main_win (), SLOT (execute_command_in_terminal (const QString&))); + // Signals from the file_editor trivial operations connect (this, SIGNAL (fetab_set_directory (const QString&)), f, SLOT (set_current_directory (const QString&))); @@ -2061,8 +2069,9 @@ file_editor::add_file_editor_tab (file_e f, SLOT (delete_debugger_pointer (const QWidget*, int))); connect (this, SIGNAL (fetab_do_breakpoint_marker (bool, const QWidget*, - int)), - f, SLOT (do_breakpoint_marker (bool, const QWidget*, int))); + int, const QString&)), + f, SLOT (do_breakpoint_marker (bool, const QWidget*, int, + const QString&))); _tab_widget->setCurrentWidget (f); diff -r f7e64e1baa08 libgui/src/m-editor/file-editor.h --- a/libgui/src/m-editor/file-editor.h Fri Jan 01 21:29:41 2016 -0800 +++ b/libgui/src/m-editor/file-editor.h Sat Jan 02 20:20:02 2016 +1100 @@ -143,7 +143,7 @@ signals: void fetab_insert_debugger_pointer (const QWidget* ID, int line = -1); void fetab_delete_debugger_pointer (const QWidget* ID, int line = -1); void fetab_do_breakpoint_marker (bool insert, const QWidget* ID, - int line = -1); + int line = -1, const QString& = ""); void fetab_set_focus (const QWidget* ID); void fetab_scintilla_command (const QWidget* ID, unsigned int sci_msg); @@ -234,7 +234,8 @@ public slots: void handle_insert_debugger_pointer_request (const QString& file, int line); void handle_delete_debugger_pointer_request (const QString& file, int line); void handle_update_breakpoint_marker_request (bool insert, - const QString& file, int line); + const QString& file, int line, + const QString& cond); void handle_edit_mfile_request (const QString& name, const QString& file, const QString& curr_dir, int line); @@ -261,7 +262,8 @@ private slots: void request_open_file (const QString& fileName, const QString& encoding = QString (), int line = -1, bool debug_pointer = false, - bool breakpoint_marker = false, bool insert = true); + bool breakpoint_marker = false, bool insert = true, + const QString& cond = ""); void request_preferences (bool); void request_styles_preferences (bool); void restore_create_file_setting (); diff -r f7e64e1baa08 libgui/src/m-editor/marker.cc --- a/libgui/src/m-editor/marker.cc Fri Jan 01 21:29:41 2016 -0800 +++ b/libgui/src/m-editor/marker.cc Sat Jan 02 20:20:02 2016 +1100 @@ -32,16 +32,16 @@ along with Octave; see the file COPYING. marker::marker (QsciScintilla *area, int original_linenr, editor_markers type, - int editor_linenr) : QObject () + int editor_linenr, const QString& condition) : QObject () { - construct (area, original_linenr, type, editor_linenr); + construct (area, original_linenr, type, editor_linenr, condition); } marker::marker (QsciScintilla *area, int original_linenr, - editor_markers type) : QObject () + editor_markers type, const QString& condition) : QObject () { - construct (area, original_linenr, type, original_linenr - 1); + construct (area, original_linenr, type, original_linenr - 1, condition); } @@ -52,12 +52,14 @@ marker::~marker (void) void marker::construct (QsciScintilla *area, int original_linenr, - editor_markers type, int editor_linenr) + editor_markers type, int editor_linenr, + const QString& condition) { _edit_area = area; _original_linenr = original_linenr; _marker_type = type; _mhandle = _edit_area->markerAdd (editor_linenr, _marker_type); + _condition = condition; } @@ -126,9 +128,10 @@ marker::handle_find_just_after (int line void -marker::handle_report_editor_linenr (QIntList& list) +marker::handle_report_editor_linenr (QIntList& lines, QStringList& conditions) { - list << _edit_area->markerLine (_mhandle); + lines << _edit_area->markerLine (_mhandle); + conditions << _condition; } diff -r f7e64e1baa08 libgui/src/m-editor/marker.h --- a/libgui/src/m-editor/marker.h Fri Jan 01 21:29:41 2016 -0800 +++ b/libgui/src/m-editor/marker.h Sat Jan 02 20:20:02 2016 +1100 @@ -46,15 +46,17 @@ public: { bookmark, breakpoint, + cond_break, unsure_breakpoint, debugger_position, unsure_debugger_position }; marker (QsciScintilla *edit_area, int original_linenr, - editor_markers marker_type); + editor_markers marker_type, const QString& condition = ""); marker (QsciScintilla *edit_area, int original_linenr, - editor_markers marker_type, int editor_linenr); + editor_markers marker_type, int editor_linenr, + const QString& condition = ""); ~marker (void); public slots: @@ -67,19 +69,21 @@ public slots: /* void handle_lines_changed (void);*/ void handle_marker_line_deleted (int mhandle); void handle_marker_line_undeleted (int mhandle); - void handle_report_editor_linenr (QIntList& int_list); + void handle_report_editor_linenr (QIntList& lines, QStringList& conditions); signals: void request_remove (int original_linenr); private: void construct (QsciScintilla *edit_area, int original_linenr, - editor_markers marker_type, int editor_linenr); + editor_markers marker_type, int editor_linenr, + const QString& condition); QsciScintilla * _edit_area; int _original_linenr; editor_markers _marker_type; int _mhandle; + QString _condition; }; #endif // MARKER_H diff -r f7e64e1baa08 libgui/src/m-editor/octave-qscintilla.cc --- a/libgui/src/m-editor/octave-qscintilla.cc Fri Jan 01 21:29:41 2016 -0800 +++ b/libgui/src/m-editor/octave-qscintilla.cc Sat Jan 02 20:20:02 2016 +1100 @@ -194,8 +194,7 @@ octave_qscintilla::contextMenuEvent (QCo QPoint global_pos, local_pos; // the menu's position QMenu *context_menu = createStandardContextMenu (); // standard menu - // fill context menu with editor's standard actions - emit create_context_menu_signal (context_menu); + bool in_left_margin = false; // determine position depending on mouse or keyboard event if (e->reason () == QContextMenuEvent::Mouse) @@ -203,6 +202,8 @@ octave_qscintilla::contextMenuEvent (QCo // context menu by mouse global_pos = e->globalPos (); // global mouse position local_pos = e->pos (); // local mouse position + if (e->x () < marginWidth (1) + marginWidth (2)) + in_left_margin = true; } else { @@ -215,26 +216,64 @@ octave_qscintilla::contextMenuEvent (QCo global_pos = editor_rect.topLeft (); // yes, take top left corner } - // additional custom entries of the context menu - context_menu->addSeparator (); // separator before custom entries + if (! in_left_margin) + { + // fill context menu with editor's standard actions + emit create_context_menu_signal (context_menu); - // help menu: get the position of the mouse or the text cursor - // (only for octave files) - QString lexer_name = lexer ()->lexer (); - if (lexer_name == "octave" || lexer_name == "matlab") - { - _word_at_cursor = wordAtPoint (local_pos); - if (! _word_at_cursor.isEmpty ()) - { - context_menu->addAction (tr ("Help on") + " " + _word_at_cursor, - this, SLOT (contextmenu_help (bool))); - context_menu->addAction (tr ("Documentation on") - + " " + _word_at_cursor, - this, SLOT (contextmenu_doc (bool))); - context_menu->addAction (tr ("Edit") + " " + _word_at_cursor, - this, SLOT (contextmenu_edit (bool))); - } - } + // additional custom entries of the context menu + context_menu->addSeparator (); // separator before custom entries + + // help menu: get the position of the mouse or the text cursor + // (only for octave files) + QString lexer_name = lexer ()->lexer (); + if (lexer_name == "octave" || lexer_name == "matlab") + { + _word_at_cursor = wordAtPoint (local_pos); + if (! _word_at_cursor.isEmpty ()) + { + context_menu->addAction (tr ("Help on") + " " + _word_at_cursor, + this, SLOT (contextmenu_help (bool))); + context_menu->addAction (tr ("Documentation on") + + " " + _word_at_cursor, + this, SLOT (contextmenu_doc (bool))); + context_menu->addAction (tr ("Edit") + " " + _word_at_cursor, + this, SLOT (contextmenu_edit (bool))); + } + } + } + else + { + // remove all standard actions from scintilla + QList all_actions = context_menu->actions (); + QAction* a; + + foreach (a, all_actions) + context_menu->removeAction (a); + + a = context_menu->addAction (tr ("dbstop if ..."), this, + SLOT (contextmenu_break_condition (bool))); + a->setData (local_pos); + a = context_menu->addAction (tr ("Run until here"), this, + SLOT (contextmenu_break_once (local_pos))); + a->setData (local_pos); +/* + context_menu->addAction (tr ("dbstop if error"), this, + SLOT (contextmenu_break_if_error (bool))); + context_menu->addAction (tr ("dbstop if error ..."), this, + SLOT (contextmenu_break_if_error_ (bool))); + context_menu->addAction (tr ("dbstop if warning"), this, + SLOT (contextmenu_break_if_warning (bool))); + context_menu->addAction (tr ("dbstop if warning ..."), this, + SLOT (contextmenu_break_if_warning_ (bool))); + context_menu->addAction (tr ("dbstop if interrupt"), this, + SLOT (contextmenu_break_if_int (bool))); + context_menu->addAction (tr ("dbstop if caught error"), this, + SLOT (contextmenu_break_if_caught (bool))); + context_menu->addAction (tr ("dbstop if caught error ..."), this, + SLOT (contextmenu_break_if_caught_ (bool))); +*/ + } // finaly show the menu context_menu->exec (global_pos); @@ -279,6 +318,38 @@ octave_qscintilla::contextmenu_run (bool emit execute_command_in_terminal_signal (commands.at (i)); } +// wrappers for dbstop related context menu items + +#ifdef HAVE_QSCI_VERSION_2_6_0 +#include +// FIXME Why can't the data be sent as the argument to the function??? +void +octave_qscintilla::contextmenu_break_condition (bool) +{ + QAction *action = qobject_cast(sender()); + QPoint local_pos = action->data ().value (); + + int margins = marginWidth (1) + marginWidth (2) + marginWidth (3); + local_pos = QPoint (local_pos.x () + margins, local_pos.y ()); + + emit context_menu_break_condition_signal (lineAt (local_pos)); +} + +void +octave_qscintilla::contextmenu_break_once (const QPoint& local_pos) +{ + emit context_menu_break_once (lineAt (local_pos)); +} + +/* +void +octave_qscintilla::contextmenu_break_if_caught (bool) +{ + emit context_menu_break_if_caught +} +*/ +#endif // HAVE_QSCI_VERSION_2_6_0 + void octave_qscintilla::text_changed () { diff -r f7e64e1baa08 libgui/src/m-editor/octave-qscintilla.h --- a/libgui/src/m-editor/octave-qscintilla.h Fri Jan 01 21:29:41 2016 -0800 +++ b/libgui/src/m-editor/octave-qscintilla.h Sat Jan 02 20:20:02 2016 +1100 @@ -54,6 +54,8 @@ signals: void qsci_has_focus_signal (bool); void status_update (bool,bool); void show_doc_signal (const QString&); + void context_menu_break_condition_signal (int); + void context_menu_break_once (int); private slots: @@ -63,6 +65,9 @@ private slots: void contextmenu_edit (bool); void contextmenu_run (bool); + void contextmenu_break_condition (bool); + void contextmenu_break_once (const QPoint&); + void text_changed (void); protected: diff -r f7e64e1baa08 libgui/src/main-window.cc --- a/libgui/src/main-window.cc Fri Jan 01 21:29:41 2016 -0800 +++ b/libgui/src/main-window.cc Sat Jan 02 20:20:02 2016 +1100 @@ -1012,11 +1012,12 @@ main_window::handle_delete_debugger_poin void main_window::handle_update_breakpoint_marker_request (bool insert, const QString& file, - int line) + int line, + const QString& cond) { bool cmd_focus = command_window_has_focus (); - emit update_breakpoint_marker_signal (insert, file, line); + emit update_breakpoint_marker_signal (insert, file, line, cond); if (cmd_focus) focus_command_window (); @@ -1426,11 +1427,13 @@ main_window::construct (void) SLOT (handle_delete_debugger_pointer_request (const QString&, int))); connect (this, - SIGNAL (update_breakpoint_marker_signal (bool, const QString&, int)), + SIGNAL (update_breakpoint_marker_signal (bool, const QString&, + int, const QString&)), editor_window, SLOT (handle_update_breakpoint_marker_request (bool, const QString&, - int))); + int, + const QString&))); #endif octave_link::post_event (this, &main_window::resize_command_window_callback); @@ -1570,10 +1573,13 @@ main_window::construct_octave_qt_link (v SLOT (handle_delete_debugger_pointer_request (const QString&, int))); connect (_octave_qt_link, - SIGNAL (update_breakpoint_marker_signal (bool, const QString&, int)), + SIGNAL (update_breakpoint_marker_signal (bool, const QString&, + int, const QString&)), this, - SLOT (handle_update_breakpoint_marker_request (bool, const QString&, - int))); + SLOT (handle_update_breakpoint_marker_request (bool, + const QString&, + int, + const QString&))); connect (_octave_qt_link, SIGNAL (show_doc_signal (const QString &)), diff -r f7e64e1baa08 libgui/src/main-window.h --- a/libgui/src/main-window.h Fri Jan 01 21:29:41 2016 -0800 +++ b/libgui/src/main-window.h Sat Jan 02 20:20:02 2016 +1100 @@ -99,7 +99,7 @@ signals: void insert_debugger_pointer_signal (const QString& file, int line); void delete_debugger_pointer_signal (const QString& file, int line); void update_breakpoint_marker_signal (bool insert, const QString& file, - int line); + int line, const QString& cond); void copyClipboard_signal (void); void pasteClipboard_signal (void); @@ -168,7 +168,8 @@ public slots: void handle_insert_debugger_pointer_request (const QString& file, int line); void handle_delete_debugger_pointer_request (const QString& file, int line); void handle_update_breakpoint_marker_request (bool insert, - const QString& file, int line); + const QString& file, int line, + const QString& cond); void read_settings (void); void init_terminal_size (void); diff -r f7e64e1baa08 libgui/src/octave-qt-link.cc --- a/libgui/src/octave-qt-link.cc Fri Jan 01 21:29:41 2016 -0800 +++ b/libgui/src/octave-qt-link.cc Sat Jan 02 20:20:02 2016 +1100 @@ -538,12 +538,15 @@ octave_qt_link::do_exit_debugger_event ( emit exit_debugger_signal (); } +// Display (if @insert true) or remove the appropriate symbol for a breakpoint +// in @file at @line with condition @cond. void octave_qt_link::do_update_breakpoint (bool insert, - const std::string& file, int line) + const std::string& file, int line, + const std::string& cond) { emit update_breakpoint_marker_signal (insert, QString::fromStdString (file), - line); + line, QString::fromStdString (cond)); } void diff -r f7e64e1baa08 libgui/src/octave-qt-link.h --- a/libgui/src/octave-qt-link.h Fri Jan 01 21:29:41 2016 -0800 +++ b/libgui/src/octave-qt-link.h Sat Jan 02 20:20:02 2016 +1100 @@ -124,7 +124,8 @@ public: void do_execute_in_debugger_event (const std::string& file, int line); void do_exit_debugger_event (void); - void do_update_breakpoint (bool insert, const std::string& file, int line); + void do_update_breakpoint (bool insert, const std::string& file, int line, + const std::string& cond); void do_set_default_prompts (std::string& ps1, std::string& ps2, std::string& ps4); @@ -191,7 +192,7 @@ signals: void exit_debugger_signal (void); void update_breakpoint_marker_signal (bool insert, const QString& file, - int line); + int line, const QString& cond); void insert_debugger_pointer_signal (const QString&, int); void delete_debugger_pointer_signal (const QString&, int); diff -r f7e64e1baa08 libinterp/corefcn/debug.cc --- a/libinterp/corefcn/debug.cc Fri Jan 01 21:29:41 2016 -0800 +++ b/libinterp/corefcn/debug.cc Sat Jan 02 20:20:02 2016 +1100 @@ -53,7 +53,9 @@ along with Octave; see the file COPYING. #include "pt-pr-code.h" #include "pt-bp.h" #include "pt-eval.h" +#include "pt-exp.h" #include "pt-stmt.h" +#include "sighandlers.h" #include "toplev.h" #include "unwind-prot.h" #include "utils.h" @@ -64,6 +66,11 @@ along with Octave; see the file COPYING. // Initialize the singleton object bp_table *bp_table::instance = 0; +std::set bp_table::errors_that_stop; +std::set bp_table::caught_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) { @@ -191,73 +198,244 @@ get_user_code (const std::string& fname return dbg_fcn; } -static void +#ifdef DBSTOP_NANINF +#include "sigfpe.cc" +#endif + +enum +dbstop_args {dbstop_in, dbstop_at, dbstop_if, dbstop_none}; + +// Parse parameters (args) of dbstop and dbclear commands. +// For dbstop, who=="dbstop"; for dbclear, who=="dbclear". +// The syntax is dbstop [[in] symbol] [[at] line [line [...]]] [if condition] +// where the form of condition depends on whether or not a file or line has +// been seen. +// Also execute "if [error|warning|interrupt|naninf]" clauses +void parse_dbfunction_params (const char *who, const octave_value_list& args, - std::string& symbol_name, bp_table::intmap& lines) + std::string& symbol_name, bp_table::intmap& lines, + std::string& cond) { - int idx = 0; + int nargin = args.length (); int list_idx = 0; symbol_name = ""; + lines = bp_table::intmap (); - if (args.length () == 0) - return; - - if (args(0).is_string ()) + if (nargin == 0 || !args(0).is_string ()) + print_usage (who); + // elements already processed + bool seen_in = false, seen_at = false, seen_if = false; + int pos = 0; + dbstop_args token = dbstop_none; + while (pos < nargin) { - // string could be function name or line number - int isint = atoi (args(0).string_value ().c_str ()); - - if (isint == 0) + // allow "in" and "at" to be implicit + if (args(pos).is_string ()) { - // It was a function name - symbol_name = args(0).string_value (); - - idx = 1; + std::string arg = args(pos).string_value (); + if (arg == "in") + { + token = dbstop_in; + pos++; + } + else if (arg == "at") + { + token = dbstop_at; + pos++; + } + else if (arg == "if") + { + token = dbstop_if; + pos++; + } + else if (atoi (args(pos).string_value ().c_str ()) > 0) + token = dbstop_at; + else + token = dbstop_in; } else + token = dbstop_at; + + if (pos >= nargin) + error ("%s: '%s' missing argument", who, + ( token == dbstop_in ? "in" : + (token == dbstop_at ? "at" : "if"))); + + // process the actual arguments + switch (token) { - // It was a line number. Need to get function name from debugger. - if (! Vdebugging) - error ("%s: no function specified", who); + case dbstop_in: + symbol_name = args(pos).string_value (); + if (seen_in) + error ("%s: Too many function names specified -- %s", + who, symbol_name.c_str ()); + else if (seen_at || seen_if) + error ("%s: function name must come before line number and 'if'", + who); + seen_in = true; + pos++; + break; - symbol_name = get_user_code ()->name (); - idx = 0; - } - } - 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); - } - else - error ("%s: invalid parameter specified", who); + case dbstop_at: + if (seen_at) + error ("%s: Only one 'at' clause is allowed -- %s", + who, args(pos).string_value ().c_str ()); + else if (seen_if) + error ("%s: line number must come before 'if' clause\n"); + seen_at = true; - for (int i = idx; i < args.length (); i++) - { - if (args(i).is_string ()) - { - int line = atoi (args(i).string_value ().c_str ()); + if (!seen_in) + { + // It was a line number. Get function name from debugger. + if (Vdebugging) + //symbol_name = get_user_code ()->name (); + symbol_name = get_user_code ()->fcn_file_name (); + else + error ("%s: function name must come before line number " + "and 'if'", who); + seen_in = true; + } + else if (seen_if) + error ("%s: line number must come before 'if' clause\n"); - lines[list_idx++] = line; - } - else if (args(i).is_map ()) - octave_stdout << who << ": skipping struct input" << std::endl; - else - { - const NDArray arg = args(i).array_value (); + // 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 ()); - for (octave_idx_type j = 0; j < arg.numel (); j++) - { - int line = static_cast (arg.elem (j)); + if (line > 0) + lines[list_idx++] = line; + else + break; // may be "if" + } + else if (args(pos).is_numeric_type ()) + { + const NDArray arg = args(pos).array_value (); - lines[list_idx++] = line; - } + for (octave_idx_type j = 0; j < arg.numel (); j++) + lines[list_idx++] = static_cast (arg.elem (j)); + } + else + error ("%s: Invalid argument type %s", + args(pos).type_name ().c_str ()); + } + break; + + case dbstop_if: + if (seen_in) // conditional breakpoint + { + cond = ""; // remaining arguments form condition + for (; pos < nargin; pos++) + { + if (args(pos).is_string ()) + cond = cond + " " + args(pos).string_value (); + else + error ("%s: arguments to 'if' must all be strings", who); + } + + cond = cond.substr (1); // omit initial space + } + else // stop on event (error, warning, interrupt, NaN/inf) + { + std::string condition = args(pos).string_value (); + int on_off = !strcmp(who, "dbstop"); + + // list of error/warning IDs to update + std::set *id_list = NULL; + bool *stop_flag = NULL; // Vdebug_on_... flag + + if (condition == "error") + { + id_list = &bp_table::errors_that_stop; + stop_flag = &Vdebug_on_error; + } + else if (condition == "warning") + { + id_list = &bp_table::warnings_that_stop; + stop_flag = &Vdebug_on_warning; + } + else if (condition == "caught" && nargin > pos+1 + && args(pos+1).string_value () == "error") + { + id_list = &bp_table::caught_that_stop; + stop_flag = &Vdebug_on_caught; + pos++; + } + else if (condition == "interrupt") + { + Vdebug_on_interrupt = on_off; + } + else if (condition == "naninf") +#ifdef DBSTOP_NANINF + Vdebug_on_naninf = on_off; + enable_fpe (on_off); +#else + warning ("%s: condition '%s' not yet supported", + who, condition.c_str ()); +#endif + else + error ("%s: invalid condition %s", + who, condition.c_str ()); + + // process ID list for "dbstop if error " etc + if (id_list != NULL) + { + pos++; + if (pos < nargin) // only affect a single error ID + { + if (!args(pos).is_string () || nargin > pos+1) + error ("%s: ID must be a single string", who); + else if (on_off == 1) + { + id_list->insert (args(pos).string_value ()); + *stop_flag = 1; + } + else + { + id_list->erase (args(pos).string_value ()); + if (id_list->empty ()) + *stop_flag = 0; + } + } + else // unqualified. Turn all on or off + { + id_list->clear (); + *stop_flag = on_off; + if (stop_flag == &Vdebug_on_error) + Vdebug_on_interrupt = on_off; // Matlabs stops on both + } + } + + pos = nargin; + } + break; + + default: // dbstop_none should never occur + break; } } } +/* +%!test +%! dbstop help; +%! dbstop in ls; +%! dbstop help at 100; +%! dbstop in ls 100; +%! dbstop help 200 if a==5; +%! dbstop if error Octave:undefined-function +%! s = dbstatus; +%! dbclear all +%! assert ({s.bkpt(:).name}, {"help", "help", "help>do_contents", "ls", "ls"}); +%! assert ([s.bkpt(:).line], [48, 100, 200, 58, 100]); +%! assert (s.errs, {"Octave:undefined-function"}); +*/ + +// 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) { @@ -275,10 +453,113 @@ bp_table::instance_ok (void) return true; } +// Clear all reasons to stop, other than breakpoints +void +bp_table::dbclear_all_signals (void) +{ + Vdebug_on_error = false; bp_table::errors_that_stop.clear (); + Vdebug_on_caught = false; bp_table::caught_that_stop.clear (); + Vdebug_on_warning = false; bp_table::warnings_that_stop.clear (); + Vdebug_on_interrupt = false; +} + +// Process the "warn", "errs", "caught" and "intr" fields for a call of +// "dbstop (p)". +void +bp_table::dbstop_process_map_args (const octave_map& mv) +{ + // process errs + // why so many levels of indirection needed? + bool fail = false; + Cell U = mv.contents ("errs"); + if (U.numel () != 1) + fail = (U.numel () > 1); + else + { + Array 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++) + { + errors_that_stop.insert (V(i).string_value ()); + Vdebug_on_error = 1; + } + } + } + if (fail) + error ("dbstop: invalid 'errs' field"); + + // process caught + // why so many levels of indirection needed? + fail = false; + U = mv.contents ("caught"); + if (U.numel () != 1) + fail = (U.numel () > 1); + else + { + Array W = U.index (0); + if (W.numel () == 0 || W(0).length () == 0) + Vdebug_on_caught = 1; // like "dbstop if caught error" with no ID + else if (!W(0).is_cell ()) + fail = true; + else + { + Cell V = W(0).cell_value (); + for (int i = 0; i < V.numel (); i++) + { + caught_that_stop.insert (V(i).string_value ()); + Vdebug_on_caught = 1; + } + } + } + if (fail) + error ("dbstop: invalid 'caught' field"); + + // process warn + // why so many levels of indirection needed? + fail = false; + U = mv.contents ("warn"); + if (U.numel () != 1) + fail = (U.numel () > 1); + else + { + Array 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++) + { + warnings_that_stop.insert (V(i).string_value ()); + Vdebug_on_warning = 1; + } + } + } + if (fail) + error ("dbstop: invalid 'warn' field"); + + // process interrupt + if (mv.isfield ("intr")) + Vdebug_on_interrupt = 1; +} + + +// Insert a breakpoint in function fcn at line within file fname, +// to stop only when condition is true. +// Record in bp_set that fname contains a breakpoint. bool bp_table::do_add_breakpoint_1 (octave_user_code *fcn, const std::string& fname, const bp_table::intmap& line, + const std::string& condition, bp_table::intmap& retval) { bool found = false; @@ -289,13 +570,20 @@ bp_table::do_add_breakpoint_1 (octave_us 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; } @@ -305,18 +593,23 @@ bp_table::do_add_breakpoint_1 (octave_us return found; } +// Given file name fname, find the subfunction at line line and create +// a breakpoint there. Put the system into debug_mode. +// (FIXME: 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) { octave_user_code *dbg_fcn = get_user_code (fname); if (! dbg_fcn) - error ("add_breakpoint: unable to find the requested function\n"); + error ("add_breakpoint: unable to find function '%s'\n", fname.c_str ()); intmap retval; - 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. @@ -336,7 +629,8 @@ bp_table::do_add_breakpoint (const std:: { 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; } } @@ -414,7 +708,8 @@ bp_table::do_remove_breakpoint (const st octave_user_code *dbg_fcn = get_user_code (fname); if (! dbg_fcn) - error ("remove_breakpoint: unable to find the requested function\n"); + error ("remove_breakpoint: unable to find function %s\n", + fname.c_str ()); retval = do_remove_breakpoint_1 (dbg_fcn, fname, line); @@ -472,7 +767,7 @@ bp_table::do_remove_all_breakpoints_in_f } else if (! silent) error ("remove_all_breakpoint_in_file: " - "unable to find the requested function\n"); + "unable to find function %s\n", fname.c_str ()); tree_evaluator::debug_mode = bp_table::have_breakpoints () || Vdebugging; @@ -511,12 +806,14 @@ do_find_bkpt_list (octave_value_list sli 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++) + std::set tmp_bp_set = bp_set; // copy, since may change below + + for (bp_set_iterator it = tmp_bp_set.begin (); it != tmp_bp_set.end (); it++) { if (fname_list.length () == 0 || do_find_bkpt_list (fname_list, *it) != "") @@ -531,19 +828,44 @@ bp_table::do_get_breakpoint_list (const // 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; + } + } - for (int i = 0; i < len; i++) - bkpts_vec[i] = bkpts(i).double_value (); + // look for breakpoints in subfunctions + const std::list subf_nm = f->subfunction_names (); - std::string symbol_name = f->name (); + std::map subf = f->subfunctions (); - retval[symbol_name] = bkpts_vec; + for (std::list::const_iterator p = subf_nm.begin (); + p != subf_nm.end (); p++) + { + std::map::const_iterator + q = subf.find (*p); + + 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; + } + } } } } @@ -576,17 +898,118 @@ intmap_to_ov (const bp_table::intmap& li 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; +} + DEFUN (dbstop, args, , "-*- texinfo -*-\n\ -@deftypefn {} {} dbstop @var{func}\n\ -@deftypefnx {} {} dbstop @var{func} @var{line}\n\ +@deftypefn {} {} dbstop in @var{func}\n\ +@deftypefnx {} {} dbstop in @var{func} at @var{line}\n\ +@deftypefnx {} {} dbstop in @var{func} at @var{line} if @var{condition}\n\ +@deftypefnx {} {} dbstop in @var{func} if @var{condition}\n\ +@deftypefnx {} {} dbstop if @var{event}\n\ +@deftypefnx {} {} 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}. Multiple lines can be specified in a single\n\ +command.\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{caught error}\n\ +Stop whenever an error is caught by a try-catch block\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\ +Error, caught error and warning can all be followed by a string specifying\n\ +an error ID or warning ID. If that is done, only errors with the\n\ +specified ID will cause execution to stop. To stop on one of a set of\n\ +IDs, multiple @qcode{dbstop} commands must be issued.\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 {} {} dbstop @var{func} @var{line}\n\ @deftypefnx {} {} dbstop @var{func} @var{line1} @var{line2} @dots{}\n\ @deftypefnx {} {} dbstop @var{line} @dots{}\n\ -@deftypefnx {} {@var{rline} =} dbstop (\"@var{func}\")\n\ -@deftypefnx {} {@var{rline} =} dbstop (\"@var{func}\", @var{line})\n\ -@deftypefnx {} {@var{rline} =} dbstop (\"@var{func}\", @var{line1}, @var{line2}, @dots{})\n\ -@deftypefnx {} {} dbstop (\"@var{func}\", [@var{line1}, @dots{}])\n\ -@deftypefnx {} {} dbstop (@var{line}, @dots{})\n\ +@deftypefnx {Built-in Function} {@var{rline} =} dbstop (\"@var{func}\")\n\ +@deftypefnx {Built-in Function} {@var{rline} =} dbstop (\"@var{func}\", @var{line})\n\ +@deftypefnx {Built-in Function} {@var{rline} =} dbstop (\"@var{func}\", @var{line1}, @var{line2}, @dots{})\n\ +@deftypefnx {Built-in Function} {} dbstop (\"@var{func}\", [@var{line1}, @dots{}])\n\ +@deftypefnx {Built-in Function} {} dbstop (@var{line}, @dots{})\n\ Set a breakpoint at line number @var{line} in function @var{func}.\n\ \n\ Arguments are\n\ @@ -608,21 +1031,85 @@ The optional output @var{rline} is the r 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.length() >= 1 && !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; - retval = bp_table::add_breakpoint (symbol_name, lines); + if (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") + || mv.isfield ("intr")) + { + bp_table::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, , @@ -632,6 +1119,9 @@ DEFUN (dbclear, args, , @deftypefnx {} {} dbclear @var{func} @var{line1} @var{line2} @dots{}\n\ @deftypefnx {} {} dbclear @var{line} @dots{}\n\ @deftypefnx {} {} dbclear all\n\ +@deftypefnx {} {} dbclear in @var{func}\n\ +@deftypefnx {} {} dbclear in @var{func} at @var{line}\n\ +@deftypefnx {} {} dbclear if @var{event}\n\ @deftypefnx {} {} dbclear (\"@var{func}\")\n\ @deftypefnx {} {} dbclear (\"@var{func}\", @var{line})\n\ @deftypefnx {} {} dbclear (\"@var{func}\", @var{line1}, @var{line2}, @dots{})\n\ @@ -650,6 +1140,8 @@ can be omitted and the current function @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\ @@ -662,19 +1154,139 @@ files.\n\ @seealso{dbstop, dbstatus, dbwhere}\n\ @end deftypefn") { - 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 - parse_dbfunction_params ("dbclear", args, symbol_name, lines); + int nargin = args.length (); - if (args.length () == 1 && symbol_name == "all") - bp_table::remove_all_breakpoints (); + parse_dbfunction_params ("dbclear", args, symbol_name, lines, dummy); + + if (nargin == 1 && symbol_name == "all") + { + bp_table::remove_all_breakpoints (); + bp_table::dbclear_all_signals (); + } else - bp_table::remove_breakpoint (symbol_name, lines); + { + if (symbol_name != "") + bp_table::remove_breakpoint (symbol_name, lines); + } return ovl (); } +// Report the status of "dbstop if error ..." and "dbstop if warning ..." +// If to_screen is true, the output goes to octave_stdout; otherwise it is +// returned. +// If dbstop if error is true but no explicit IDs are specified, the return +// value will have an empty field called "errs". If IDs are specified, the +// "errs" field will have a row per ID. If dbstop if error is false, there +// is no "errs" field. The "warn" field is set similarly by dbstop if warning +octave_map +bp_table::stop_on_err_warn_status (bool to_screen) +{ + octave_map retval; + + // print dbstop if error information + if (Vdebug_on_error) + { + if (errors_that_stop.empty ()) + { + if (to_screen) + octave_stdout << "stop if error\n"; + else + retval.assign ("errs", octave_value("")); + } + else + { + Cell errs (dim_vector (bp_table::errors_that_stop.size (), 1)); + int i = 0; + + for (std::set::const_iterator e + = errors_that_stop.begin (); + e != 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 caught error information + if (Vdebug_on_caught) + { + if (caught_that_stop.empty ()) + { + if (to_screen) + octave_stdout << "stop if caught error\n"; + else + retval.assign ("caught", octave_value("")); + } + else + { + Cell errs (dim_vector (caught_that_stop.size (), 1)); + int i = 0; + + for (std::set::const_iterator e + = caught_that_stop.begin (); + e != caught_that_stop.end (); e++) + { + if (to_screen) + octave_stdout << "stop if caught error " << *e << "\n"; + else + errs (i++) = *e; + } + if (!to_screen) + retval.assign ("caught", octave_value (errs)); + } + } + + // print dbstop if warning information + if (Vdebug_on_warning) + { + if (warnings_that_stop.empty ()) + { + if (to_screen) + octave_stdout << "stop if warning\n"; + else + retval.assign ("warn", octave_value("")); + } + else + { + Cell warn (dim_vector (warnings_that_stop.size (), 1)); + int i = 0; + + for (std::set::const_iterator w + = warnings_that_stop.begin (); + w != 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)); + } + } + + // 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; +} + DEFUN (dbstatus, args, nargout, "-*- texinfo -*-\n\ @deftypefn {} {} dbstatus ()\n\ @@ -687,26 +1299,37 @@ functions with breakpoints and the line 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") { int nargin = args.length (); @@ -715,7 +1338,7 @@ current function.\n\ error ("dbstatus: only zero or one arguments accepted\n"); 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 == 1) @@ -731,6 +1354,7 @@ current function.\n\ } else { +/* if (Vdebugging) { octave_user_code *dbg_fcn = get_user_code (); @@ -740,6 +1364,7 @@ current function.\n\ fcn_list(0) = symbol_name; } } +*/ bp_list = bp_table::get_breakpoint_list (fcn_list); } @@ -748,51 +1373,119 @@ 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"; + } } + + bp_table::stop_on_err_warn_status (true); + return ovl (); } else { // Fill in an array for return. + int i = 0; + octave_map retmap; + octave_value retval; - 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)); - - 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++; } - octave_map retval; - 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)); - return ovl (retval); + for (bp_table::const_fname_bp_map_iterator it = bp_list.begin (); + it != bp_list.end (); it++) + { + std::string filename = it->first; + const char *sub_fun = strchr (filename.c_str (), '>'); + if (sub_fun) + filename = filename.substr(0, sub_fun - filename.c_str ()); + octave_value path_name = do_which (filename); + + 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 = bp_table::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 retval; } } @@ -801,7 +1494,7 @@ DEFUN (dbwhere, , , @deftypefn {} {} 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_user_code *dbg_fcn = get_user_code (); @@ -890,7 +1583,7 @@ specification for the last line of the f \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_user_code *dbg_fcn; @@ -1024,7 +1717,7 @@ In debugging mode, list @var{n} lines of 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") { int n = 10; @@ -1241,7 +1934,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); diff -r f7e64e1baa08 libinterp/corefcn/debug.h --- a/libinterp/corefcn/debug.h Fri Jan 01 21:29:41 2016 -0800 +++ b/libinterp/corefcn/debug.h Sat Jan 02 20:20:02 2016 +1100 @@ -30,9 +30,18 @@ along with Octave; see the file COPYING. class octave_value_list; class octave_user_code; +static std::string bp_empty_string (""); + +struct +bp_type +{ + int line; + std::string cond; + bp_type (int l, const std::string& c) : line (l), cond (c) + { } +}; // Interface to breakpoints,. - class OCTINTERP_API bp_table @@ -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,64 @@ public: return instance_ok () ? instance->do_have_breakpoints () : 0; } + // Should we enter debugging for this particular error identifier? + static bool + debug_on_err (const std::string& ID) + { + return (errors_that_stop.empty () || errors_that_stop.count (ID)); + } + + // Should we enter debugging for this particular identifier in a try/catch? + static bool + debug_on_caught (const std::string& ID) + { + return (caught_that_stop.empty () || caught_that_stop.count (ID)); + } + + // Should we enter debugging for this particular warning identifier? + static bool + debug_on_warn (const std::string& ID) + { + return (warnings_that_stop.empty () || warnings_that_stop.count (ID)); + } + + static octave_map stop_on_err_warn_status (bool toScreen); + + static void dbstop_process_map_args (const octave_map& mv); + + static void dbclear_all_signals (void); + + friend void parse_dbfunction_params (const char *, const octave_value_list&, + std::string&, bp_table::intmap&, + std::string&); + 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 / Vdebug_on_caught / Vdebug_on_warning is set. + // Empty means stop on any error / caught error / warning. + static std::set errors_that_stop; + static std::set caught_that_stop; + static std::set warnings_that_stop; + + static bp_table *instance; static void cleanup_instance (void) { delete instance; instance = 0; } bool do_add_breakpoint_1 (octave_user_code *fcn, const std::string& fname, - const intmap& line, 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 +191,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 -r f7e64e1baa08 libinterp/corefcn/error.cc --- a/libinterp/corefcn/error.cc Fri Jan 01 21:29:41 2016 -0800 +++ b/libinterp/corefcn/error.cc Sat Jan 02 20:20:02 2016 +1100 @@ -32,6 +32,7 @@ along with Octave; see the file COPYING. #include #include +#include "debug.h" #include "defun.h" #include "error.h" #include "input.h" @@ -57,6 +58,10 @@ static bool Vbeep_on_error = false; // traceback message (you will only see the top-level error message). bool Vdebug_on_error = false; +// TRUE means that Octave will try to enter the debugger when an error +// is encountered within the 'try' section of a 'try' / 'catch' block. +bool Vdebug_on_caught = false; + // TRUE means that Octave will try to enter the debugger when a warning // is encountered. bool Vdebug_on_warning = false; @@ -103,6 +108,10 @@ int error_state = 0; // the 'unwind_protect' statement. int buffer_error_messages = 0; +// The number of layers of try / catch blocks we're in. Used to print +// "caught error" instead of error when "dbstop if caught error" is on. +int in_try_catch = 0; + // TRUE means error messages are turned off. bool discard_error_messages = false; @@ -113,6 +122,7 @@ void reset_error_handler (void) { buffer_error_messages = 0; + in_try_catch = 0; discard_error_messages = false; } @@ -138,10 +148,10 @@ verror (bool save_last_error, std::ostre const char *name, const char *id, const char *fmt, va_list args, bool with_cfn = false) { - if (discard_error_messages) + if (discard_error_messages && ! Vdebug_on_caught) return; - if (! buffer_error_messages) + if (! buffer_error_messages || Vdebug_on_caught) flush_octave_stdout (); // FIXME: we really want to capture the message before it has all the @@ -162,7 +172,10 @@ verror (bool save_last_error, std::ostre msg_string = "\a"; if (name) - msg_string += std::string (name) + ": "; + if (in_try_catch && ! strcmp (name, "error")) + msg_string += "caught error: "; + else + msg_string += std::string (name) + ": "; // If with_fcn is specified, we'll attempt to prefix the message with the name // of the current executing function. But we'll do so only if: @@ -207,7 +220,7 @@ verror (bool save_last_error, std::ostre Vlast_error_stack = initialize_last_error_stack (); } - if (! buffer_error_messages) + if (! buffer_error_messages || Vdebug_on_caught) { octave_diary << msg_string; os << msg_string; @@ -299,7 +312,9 @@ maybe_enter_debugger (octave_execution_e bool show_stack_trace = false) { if ((interactive || forced_interactive) - && Vdebug_on_error && octave_call_stack::caller_user_code ()) + && ((Vdebug_on_error && bp_table::debug_on_err (last_error_id ())) + || (Vdebug_on_caught && bp_table::debug_on_caught (last_error_id ()))) + && octave_call_stack::caller_user_code ()) { unwind_protect frame; frame.protect_var (Vdebug_on_error); @@ -717,7 +732,7 @@ warning_1 (const char *id, const char *f pr_where (std::cerr, "warning"); 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); @@ -2165,6 +2180,7 @@ interpreter_try (unwind_protect& frame) buffer_error_messages++; Vdebug_on_error = false; Vdebug_on_warning = false; + // leave Vdebug_on_caught as it was, so errors in try/catch are still caught } diff -r f7e64e1baa08 libinterp/corefcn/error.h --- a/libinterp/corefcn/error.h Fri Jan 01 21:29:41 2016 -0800 +++ b/libinterp/corefcn/error.h Sat Jan 02 20:20:02 2016 +1100 @@ -119,6 +119,10 @@ extern OCTINTERP_API void initialize_def // traceback message (you will only see the top-level error message). extern OCTINTERP_API bool Vdebug_on_error; +// TRUE means that Octave will try to enter the debugger when an error +// is encountered within the 'try' section of a 'try' / 'catch' block. +extern OCTINTERP_API bool Vdebug_on_caught; + // TRUE means that Octave will try to enter the debugger when a warning // is encountered. extern OCTINTERP_API bool Vdebug_on_warning; @@ -134,6 +138,10 @@ extern OCTINTERP_API int warning_state; // the 'unwind_protect' statement. extern OCTINTERP_API int buffer_error_messages; +// The number of layers of try / catch blocks we're in. Used to print +// "caught error" instead of "error" when "dbstop if caught error" is on. +extern OCTINTERP_API int in_try_catch; + // TRUE means error messages are turned off. extern OCTINTERP_API bool discard_error_messages; diff -r f7e64e1baa08 libinterp/corefcn/octave-link.h --- a/libinterp/corefcn/octave-link.h Fri Jan 01 21:29:41 2016 -0800 +++ b/libinterp/corefcn/octave-link.h Sat Jan 02 20:20:02 2016 +1100 @@ -303,10 +303,11 @@ public: } static void - update_breakpoint (bool insert, const std::string& file, int line) + update_breakpoint (bool insert, const std::string& file, int line, + const std::string& cond = "") { if (enabled ()) - instance->do_update_breakpoint (insert, file, line); + instance->do_update_breakpoint (insert, file, line, cond); } static void connect_link (octave_link *); @@ -466,7 +467,8 @@ protected: virtual void do_exit_debugger_event (void) = 0; virtual void do_update_breakpoint (bool insert, - const std::string& file, int line) = 0; + const std::string& file, int line, + const std::string& cond) = 0; virtual void do_set_default_prompts (std::string& ps1, std::string& ps2, std::string& ps4) = 0; diff -r f7e64e1baa08 libinterp/corefcn/symtab.cc --- a/libinterp/corefcn/symtab.cc Fri Jan 01 21:29:41 2016 -0800 +++ b/libinterp/corefcn/symtab.cc Sat Jan 02 20:20:02 2016 +1100 @@ -434,6 +434,7 @@ symbol_table::fcn_info::fcn_info_rep::lo retval = octave_value (fcn); class_constructors[name] = retval; + class_methods[name] = retval; } } else @@ -456,6 +457,7 @@ symbol_table::fcn_info::fcn_info_rep::lo retval = maybe_cdef_ctor; class_constructors[name] = retval; + class_methods[name] = retval; function_on_path = old_function_on_path; } diff -r f7e64e1baa08 libinterp/corefcn/toplev.cc --- a/libinterp/corefcn/toplev.cc Fri Jan 01 21:29:41 2016 -0800 +++ b/libinterp/corefcn/toplev.cc Sat Jan 02 20:20:02 2016 +1100 @@ -679,6 +679,14 @@ main_loop (void) std::cerr << "error: out of memory -- trying to return to prompt" << std::endl; } + +#ifdef DBSTOP_NANINF + if (Vdebug_on_naninf) + { + if (setjump (naninf_jump) != 0) + debug_or_throw_exception (true); // true = stack trace + } +#endif } while (retval == 0); diff -r f7e64e1baa08 libinterp/parse-tree/pt-bp.cc --- a/libinterp/parse-tree/pt-bp.cc Fri Jan 01 21:29:41 2016 -0800 +++ b/libinterp/parse-tree/pt-bp.cc Sat Jan 02 20:20:02 2016 +1100 @@ -351,6 +351,9 @@ tree_breakpoint::visit_statement (tree_s } } +// 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_state if (act == set) { - stmt.set_breakpoint (); + stmt.set_breakpoint (condition); line = lineno; found = true; } @@ -494,7 +500,10 @@ tree_breakpoint::take_action (tree_state 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 -r f7e64e1baa08 libinterp/parse-tree/pt-bp.h --- a/libinterp/parse-tree/pt-bp.h Fri Jan 01 21:29:41 2016 -0800 +++ b/libinterp/parse-tree/pt-bp.h Sat Jan 02 20:20:02 2016 +1100 @@ -32,6 +32,7 @@ along with Octave; see the file COPYING. 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 -r f7e64e1baa08 libinterp/parse-tree/pt-eval.cc --- a/libinterp/parse-tree/pt-eval.cc Fri Jan 01 21:29:41 2016 -0800 +++ b/libinterp/parse-tree/pt-eval.cc Sat Jan 02 20:20:02 2016 +1100 @@ -97,7 +97,7 @@ void tree_evaluator::visit_break_command (tree_break_command& cmd) { if (debug_mode) - do_breakpoint (cmd.is_breakpoint ()); + do_breakpoint (cmd.is_breakpoint (true)); if (statement_context == function || statement_context == script || in_loop_command) @@ -114,7 +114,7 @@ void tree_evaluator::visit_continue_command (tree_continue_command& cmd) { if (debug_mode) - do_breakpoint (cmd.is_breakpoint ()); + do_breakpoint (cmd.is_breakpoint (true)); if (statement_context == function || statement_context == script || in_loop_command) @@ -210,7 +210,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 ()); } @@ -219,7 +219,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 ()); } @@ -282,7 +282,7 @@ void tree_evaluator::visit_simple_for_command (tree_simple_for_command& cmd) { 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 (); @@ -397,7 +397,7 @@ void tree_evaluator::visit_complex_for_command (tree_complex_for_command& cmd) { if (debug_mode) - do_breakpoint (cmd.is_breakpoint ()); + do_breakpoint (cmd.is_breakpoint (true)); unwind_protect frame; @@ -538,7 +538,7 @@ tree_evaluator::visit_if_command_list (t 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")) { @@ -580,7 +580,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 @@ -623,7 +623,7 @@ void tree_evaluator::visit_return_command (tree_return_command& cmd) { if (debug_mode) - do_breakpoint (cmd.is_breakpoint ()); + do_breakpoint (cmd.is_breakpoint (true)); // Act like dbcont. @@ -683,7 +683,7 @@ tree_evaluator::visit_statement (tree_st 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 @@ -794,7 +794,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 (); @@ -852,12 +852,15 @@ tree_evaluator::visit_try_catch_command { try { + in_try_catch++; try_code->accept (*this); + in_try_catch--; } catch (const octave_execution_exception&) { recover_from_exception (); + in_try_catch--; // must be restored before "catch" block execution_error = true; } } @@ -885,8 +888,10 @@ tree_evaluator::visit_try_catch_command err.assign ("stack", last_error_stack ()); ult.assign (octave_value::op_asn_eq, err); + } + // perform actual "catch" block if (catch_code) catch_code->accept (*this); } @@ -1029,7 +1034,7 @@ tree_evaluator::visit_while_command (tre for (;;) { if (debug_mode) - do_breakpoint (cmd.is_breakpoint ()); + do_breakpoint (cmd.is_breakpoint (true)); if (expr->is_logically_true ("while")) { @@ -1076,7 +1081,7 @@ tree_evaluator::visit_do_until_command ( break; if (debug_mode) - do_breakpoint (cmd.is_breakpoint ()); + do_breakpoint (cmd.is_breakpoint (true)); if (expr->is_logically_true ("do-until")) break; @@ -1086,7 +1091,7 @@ tree_evaluator::visit_do_until_command ( 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 -r f7e64e1baa08 libinterp/parse-tree/pt-stmt.cc --- a/libinterp/parse-tree/pt-stmt.cc Fri Jan 01 21:29:41 2016 -0800 +++ b/libinterp/parse-tree/pt-stmt.cc Sat Jan 02 20:20:02 2016 +1100 @@ -48,6 +48,8 @@ along with Octave; see the file COPYING. #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 () : (expr ? expr->is_breakpoint () : false); + return cmd ? cmd->is_breakpoint (check_active) + : (expr ? expr->is_breakpoint (check_active) : false); +} + +std::string +tree_statement::bp_cond () const +{ + 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 (v 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. +// Updates GUI via 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,10 +270,10 @@ tree_statement_list::add_breakpoint (con { 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]); + octave_link::update_breakpoint (true, file, retval[i], condition); } } diff -r f7e64e1baa08 libinterp/parse-tree/pt-stmt.h --- a/libinterp/parse-tree/pt-stmt.h Fri Jan 01 21:29:41 2016 -0800 +++ b/libinterp/parse-tree/pt-stmt.h Sat Jan 02 20:20:02 2016 +1100 @@ -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 -r f7e64e1baa08 libinterp/parse-tree/pt.cc --- a/libinterp/parse-tree/pt.cc Fri Jan 01 21:29:41 2016 -0800 +++ b/libinterp/parse-tree/pt.cc Sat Jan 02 20:20:02 2016 +1100 @@ -31,6 +31,7 @@ along with Octave; see the file COPYING. #include "ov-fcn.h" #include "pt.h" #include "pt-pr-code.h" +#include "unwind-prot.h" // Hide the details of the string buffer so that we are less likely to // create a memory leak. @@ -48,3 +49,49 @@ 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; + + unwind_protect frame; + frame.protect_var (buffer_error_messages); + frame.protect_var (Vdebug_on_error); + frame.protect_var (Vdebug_on_warning); + + buffer_error_messages++; + Vdebug_on_error = false; + Vdebug_on_warning = false; + + try + { + octave_value_list val = eval_string (*bp, 1, parse_status, 1); + if (parse_status == 0 && !error_state && val(0).bool_value ()) + retval = true; + else + { + buffer_error_messages--; + error_state = 0; + retval = false; + } + } + catch (const octave_execution_exception& e) + { + retval = true; // break if condition couldn't be evaluated + warning ("Error evaluating breakpoint condition:\n %s", + last_error_message ().c_str ()); + } + } + return retval; +} diff -r f7e64e1baa08 libinterp/parse-tree/pt.h --- a/libinterp/parse-tree/pt.h Fri Jan 01 21:29:41 2016 -0800 +++ b/libinterp/parse-tree/pt.h Sat Jan 02 20:20:02 2016 +1100 @@ -29,6 +29,8 @@ along with Octave; see the file COPYING. 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!