Mercurial > emacs
diff lisp/emacs-lisp/edebug.el @ 661:36fbc3f71803
Initial revision
author | Eric S. Raymond <esr@snark.thyrsus.com> |
---|---|
date | Sat, 30 May 1992 23:52:26 +0000 |
parents | |
children | 540b047ece4d |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lisp/emacs-lisp/edebug.el Sat May 30 23:52:26 1992 +0000 @@ -0,0 +1,2521 @@ +;;; edebug.el --- a source-level debugger for emacs lisp. + +;; Copyright (C) 1988, 1989, 1990, 1991 Free Software Foundation, Inc + +;; This file is part of GNU Emacs. + +;; GNU Emacs is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY. No author or distributor +;; accepts responsibility to anyone for the consequences of using it +;; or for whether it serves any particular purpose or works at all, +;; unless he says so in writing. Refer to the GNU Emacs General Public +;; License for full details. + +;; Everyone is granted permission to copy, modify and redistribute +;; GNU Emacs, but only under the conditions described in the +;; GNU Emacs General Public License. A copy of this license is +;; supposed to have been given to you along with GNU Emacs so you +;; can know your rights and responsibilities. It should be in a +;; file named COPYING. Among other things, the copyright notice +;; and this notice must be preserved on all copies. + +;;;================================================================ +;;; This minor mode allows programmers to step through elisp source +;;; code while executing, set breakpoints, etc. See the texinfo +;;; document (being constructed...) for more detailed instructions +;;; than contained here. Send me your enhancement, ideas, bugs, or +;;; fixes. + +;;; Daniel LaLiberte 217-244-0785 +;;; University of Illinois, Urbana-Champaign +;;; Department of Computer Science +;;; 1304 W Springfield +;;; Urbana, IL 61801 + +;;; uiucdcs!liberte +;;; liberte@cs.uiuc.edu + +;;; Contents: +;;; ========= +;;; Change list +;;; Installation +;;; Todo list +;;; Utilities +;;; Parser +;;; Debugger + + +;;;================================================================ +;;; Change list +;;; ----------- + +;;; $Header: /import/kaplan/kaplan/liberte/Edebug/RCS/edebug.el,v 2.5 91/07/25 13:32:53 liberte Exp Locker: liberte $ +;;; $Log: edebug.el,v $ +;;; Revision 2.5 91/07/25 13:32:53 liberte +;;; Doc string cleanup. +;;; If edebug-form-hook is t, evaluate all arguments. +;;; If edebug-form-hook is 0, evaluate no arguments. +;;; If edebug-form-hook is nil, evaluate macro args according +;;; to edebug-eval-macro-args. +;;; Save the outside value of executing macro. +;;; Save and restore the outside restriction. +;;; Dont force update for go and Go-nonstop. +;;; Save and restore last-command-char, last-command, +;;; this-command, last-input-char. +;;; For epoch, do epoch::dispatch-events before sit-for +;;; and input-pending-p since X events could interfere. +;;; Warn about unsetting non-existent breakpoint. +;;; Fix edebug-forward-sexp with prefix arg. +;;; Add edebug-step-out to exit from current sexp. +;;; +;;; Revision 2.4 91/03/18 12:35:44 liberte +;;; Force update after go or Go-nonstop modes, so overlay arrow is correct. +;;; Support debug-on-quit. Remove edebug-on-error. +;;; Fix edebug-anonymous. Bug found by jackr@wpd.sgi.com (Jack Repenning). +;;; Don't discard-input anymore. Easier to change modes this way. +;;; Fix max-lisp-eval-depth and max-specpdl-size incrementing. +;;; Save and restore points in all buffers, if +;;; edebug-save-buffer-points is non-nil. Expensive! +;;; Bug caught by wolfgang@wsrcc.com (Wolfgang S. Rupprecht) +;;; Save standard-output and standard-input in edebug-recursive-edit +;;; so that edebug-outside-excursion can restore them. +;;; Call set-buffer in edebug-pop-to-buffer since +;;; select-window does not do that. +;;; Fix edebug's eval-defun to remember current buffer inside evaluations +;;; and to evaluate top-level forms. Found by Jamie Zawinski. +;;; Add edebug-interactive-entry to support interactive forms with +;;; non-string arg. Bug found by Jack Repenning. +;;; Simplify edebug-restore-match-data to just store-match-data. +;;; Motivated by linus@lysator.liu.se. +;;; Move the match-data call to before the outside +;;; buffer is changed, since it assumes that. +;;; +;;; Revision 2.3 91/01/17 20:55:14 liberte +;;; Fix bug found by hollen@megatek.uucp. +;;; Current buffer was not being restored. +;;; Call edebug with (edebug begin end 'exp) +;;; and add additional wrapper around body of functions: +;;; (edebug-enter function body). +;;; Make &optional only apply to immediate next arg +;;; in edebug-form-parser (was edebug-macro-parser). +;;; Catch debug errors with edebug. Yeah! +;;; Reset edebug-mode on first function entry. Yeah! +;;; Motivated by Dion Hollenbeck. +;;; Add the missing bindings to the global-edebug-map. +;;; eval-current-buffer now uses eval-region. +;;; eval-region now does not narrow region. +;;; Narrowing was the cause of the window-start being set wrong. +;;; Reset edebug-mode only on +;;; first entry of any function at each recursive-edit level. +;;; Add edebug-backtrace, to generate cleaned up +;;; backtrace. It doesnt "work" like the debug backtrace, however. +;;; Require reselecting outside window even if +;;; quit occurs, otherwise save-excursions may restore +;;; buffer to the wrong window. +;;; +;;; Revision 2.2 90/11/26 21:14:22 liberte +;;; Shadow eval-defun and eval-region. Toggle +;;; edebugging with edebug-all-defuns. +;;; Call edebug with (edebug 'function begin end 'exp) +;;; Suggested by Jamie Zawinski <jwz@lucid.com>. +;;; Add edebug-form-parser to process macro args. +;;; Motivated by Darryl Okahata darrylo@hpnmxx.hp.com. +;;; Fix by Roland McGrath <roland@ai.mit.edu> +;;; to wrap body of edebug-save-restriction in progn. +;;; Fix by Darryl Okahata <darrylo%hpnmd@hpcea.hp.com> +;;; to add (set-window-hscroll (selected-window) 0) to +;;; edebug-pop-to-buffer. +;;; +;;; Revision 2.1 90/11/16 21:55:35 liberte +;;; Clean up. +;;; Add edebug-form-hook to edebug macro calls. Thanks to Joe Wells. +;;; edebug-forward-sexp uses step mode if no forward-sexp. +;;; +;;; Revision 2.0 90/11/14 22:30:54 liberte +;;; Handle lambda forms, function, interactive evals, defmacro. +;;; Clean up display for Epoch - save and restore screen configurations. +;;; Note: epoch 3.2 broke set-window-configuration. +;;; Also, sit-for pauses do not always work in epoch. +;;; Display evaluations window. +;;; Display result after expression evaluation. +;;; Thanks to discussions with Shinichirou Sugou. +;;; Conditional and temporary breakpoints. +;;; Change "continue" to "go" mode and add different "continue" mode. +;;; Option to stop before symbols. +;;; +;;; Fix by: Glen Ditchfield gjditchfield@violet.uwaterloo.ca +;;; to handle ?# type chars. +;;; +;;; Revision 1.5 89/05/10 02:39:27 liberte +;;; Fix condition-case expression lists. +;;; Reorganize edebug. +;;; +;;; Revision 1.4 89/02/14 22:58:34 liberte +;;; Fix broken breakpointing. +;;; Temporarily widen elisp buffer during edebug. +;;; +;;; Revision 1.3 89/01/30 00:26:09 liberte +;;; More bug fixes for cond and let. +;;; Another parsing fix backquote. +;;; Fix for lambda forms inside defuns. +;;; Leave point at syntax error, mark at starting position. +;;; +;;; Revision 1.2 88/11/28 12:14:15 liberte +;;; Bug fixes: cond construct didnt execute. +;;; () in sexp list didnt parse +;;; () as variable in condition-case didnt parse. +;;; +;;; Revision 1.1 88/11/28 12:11:27 liberte +;;; Initial revision +;;; + + +;;; Installation +;;; ------------ +;; Put edebug.el in some directory in your load-path and byte-compile it. + +;; Put the following forms in your .emacs file. +;; (setq edebug-global-prefix "...whatever you want") ; default is C-xX +;; (define-key emacs-lisp-mode-map "\^Xx" 'edebug-defun) +;; (autoload 'edebug-defun "edebug") +;; (autoload 'edebug-debug "edebug") +;; (setq debugger 'edebug-debug) +;; ... other options, described in the next section. + +;; Evaluate a defun for edebug with edebug-defun. +;; Evaluate your function normally. +;; Use the "?" command in edebug to describe other commands. +;; See edebug.texinfo for more instructions. + + +;;; Options +;;; ------- + +(defvar edebug-all-defuns nil + "*If non-nil, all defuns and defmacros evaluated will use edebug. +eval-defun without prefix arg and eval-region will use edebug-defun. + +If nil, eval-region evaluates normally, but eval-defun with prefix arg +uses edebug-defun. eval-region is called by eval-defun, eval-last-sexp, +and eval-print-last-sexp. + +You may wish to make this variable local to each elisp buffer by calling +(make-local-variable 'edebug-all-defuns) in your emacs-lisp-mode-hook. +You can use the function edebug-all-defuns to toggle its value.") + + +(defvar edebug-eval-macro-args nil + "*If non-nil, edebug will assume that all macro call arguments for +macros that have no edebug-form-hook may be evaluated, otherwise it +will not. To specify exceptions for macros that have some arguments +evaluated and some not, you should specify an edebug-form-hook") + +(defvar edebug-stop-before-symbols nil + "*Non-nil causes edebug to stop before symbols as well as after. +In any case, it is possible to stop before a symbol with a breakpoint or +interrupt.") + +(defvar edebug-save-windows t + "*If non-nil, save and restore window configuration on edebug calls. +It takes some time to save and restore, so if your program does not care +what happens to the window configurations, it is better to set this +variable to nil.") + +(defvar edebug-save-point t + "*If non-nil, save and restore the point and mark in source code buffers.") + +(defvar edebug-save-buffer-points nil + "*If non-nil, save and restore the points of all buffers, displayed or not. + +Saving and restoring buffer points is necessary if you are debugging +code that changes the point of a buffer which is displayed in a +non-selected window. If edebug or the user then selects the +window, the buffer's point will be changed to the window's point. + +Saving and restoring all the points is an expensive operation since it +visits each buffer twice for each edebug call, so it is best to avoid +it if you can.") + +(defvar edebug-initial-mode 'step + "*Global initial mode for edebug, if non-nil. +This is used when edebug is first entered for each recursive-edit level. +Possible values are nil (meaning keep using edebug-mode), step, go, +Go-nonstop, trace, Trace-fast, continue, and Continue-fast.") + +(defvar edebug-trace nil + "*Non-nil if edebug should show a trace of function entry and exit. +Tracing output is displayed in a buffer named *edebug-trace*, one +function entry or exit per line, indented by the recursion level. You +can customize by replacing functions edebug-print-trace-entry and +edebug-print-trace-exit.") + + + +;;;======================================================================== +;;; Utilities +;;; --------- + +(defun edebug-which-function () + "Return the symbol of the function we are in" + (save-excursion + (end-of-defun) + (beginning-of-defun) + (down-list 1) + (if (not (memq (read (current-buffer)) '(defun defmacro))) + (error "Not in defun or defmacro.")) + (read (current-buffer)))) + +(defun edebug-last-sexp () + "Return the last sexp before point in current buffer. +Assumes elisp syntax is active." + (car + (read-from-string + (buffer-substring + (save-excursion + (forward-sexp -1) + (point)) + (point))))) + +(defun edebug-window-list () + "Return a list of windows, in order of next-window." + ;; This doesnt work for epoch. + (let* ((first-window (selected-window)) + (window-list (list first-window)) + (next (next-window first-window))) + (while (not (eq next first-window)) + (setq window-list (cons next window-list)) + (setq next (next-window next))) + (nreverse window-list))) + +(defun edebug-get-buffer-points () + "Return a list of buffer point pairs, for all buffers." + (save-excursion + (mapcar (function (lambda (buf) + (set-buffer buf) + (cons buf (point)))) + (buffer-list)))) + +(defun edebug-set-buffer-points () + "Restore the buffer-points given by edebug-get-buffer-points." + (mapcar (function (lambda (buf-point) + (if (buffer-name (car buf-point)) ; still exists + (progn + (set-buffer (car buf-point)) + (goto-char (cdr buf-point)))))) + edebug-buffer-points)) + +(defun edebug-two-window-p () + "Return t if there are two windows." + (and (not (one-window-p)) + (eq (selected-window) + (next-window (next-window (selected-window)))))) + +(defun edebug-macrop (object) + "Return the macro named by OBJECT, or nil if it is not a macro." + (while (and (symbolp object) (fboundp object)) + (setq object (symbol-function object))) + (if (and (listp object) + (eq 'macro (car object)) + (edebug-functionp (cdr object))) + object)) + +(defun edebug-functionp (object) + "Returns the function named by OBJECT, or nil if it is not a function." + (while (and (symbolp object) (fboundp object)) + (setq object (symbol-function object))) + (if (or (subrp object) + (and (listp object) + (eq (car object) 'lambda) + (listp (car (cdr object))))) + object)) + +(defun edebug-sort-alist (alist function) + "Return the ALIST sorted with comparison function FUNCTION. +This uses 'sort so the sorting is destructive." + (sort alist (function + (lambda (e1 e2) + (funcall function (car e1) (car e2)))))) + +(put 'edebug-save-restriction 'edebug-form-hook + '(&rest form)) + +(defmacro edebug-save-restriction (&rest body) + "Evaluate BODY while saving the current buffers restriction. +BODY may change buffer outside of current restriction, unlike +save-restriction. BODY may change the current buffer, +and the restriction will be restored to the original buffer, +and the current buffer remains current. +Return the result of the last expression in BODY." + (` (let ((edebug:s-r-beg (point-min-marker)) + (edebug:s-r-end (point-max-marker))) + (unwind-protect + (progn (,@ body)) + (save-excursion + (set-buffer (marker-buffer edebug:s-r-beg)) + (narrow-to-region edebug:s-r-beg edebug:s-r-end)))))) + + +;;;============================================================= +;;; Redefine eval-defun, eval-region, and eval-current-buffer. +;;; ----------------------------------------------------------- + +(defun edebug-all-defuns () + "Toggle edebugging of all defuns and defmacros, +not including those evaluated in the minibuffer, or during load." + (interactive) + (setq edebug-all-defuns (not edebug-all-defuns)) + (message "Edebugging is %s." (if edebug-all-defuns "on" "off"))) + + +(if (not (fboundp 'edebug-emacs-eval-defun)) + (fset 'edebug-emacs-eval-defun (symbol-function 'eval-defun))) +;;(fset 'eval-defun (symbol-function 'edebug-emacs-eval-defun)) + +(defun eval-defun (edebug-debug) + "Edebug replacement for eval-defun. Print value in the minibuffer. +Evaluate the top-level form that point is in or before. Note: +eval-defun normally evaluates any top-level form, not just defuns. + +Here are the differences from the standard eval-defun. If the prefix +argument is the same as edebug-all-defuns (nil or non-nil), evaluate +normally; otherwise edebug-defun is called to wrap edebug calls around +evaluatable expressions in the defun or defmacro body. Also, the +value printed by edebug-defun is not just the function name." + (interactive "P") + (let ((edebug-all-defuns + (not (eq (not edebug-debug) (not edebug-all-defuns))))) + (edebug-emacs-eval-defun nil) + )) + + +(if (not (fboundp 'edebug-emacs-eval-region)) + (fset 'edebug-emacs-eval-region (symbol-function 'eval-region))) +;; (fset 'eval-region (symbol-function 'edebug-emacs-eval-region)) + +(defun eval-region (edebug-e-r-start edebug-e-r-end + &optional edebug-e-r-output) + "Edebug replacement for eval-defun. +Like eval-region, but call edebug-defun for defuns or defmacros. +Also, this eval-region does not narrow to the region and +if an error occurs, point is left at the error." + ;; One other piddling difference concerns whitespace after the expression. + (interactive "r") + (let ((standard-output (or edebug-e-r-output 'symbolp)) + (edebug-e-r-pnt (point)) + (edebug-e-r-buf (current-buffer)) + (edebug-e-r-inside-buf (current-buffer)) + ;; Mark the end because it may move. + (edebug-e-r-end-marker (set-marker (make-marker) edebug-e-r-end)) + edebug-e-r-val + ) + (goto-char edebug-e-r-start) + (edebug-skip-whitespace) + (while (< (point) edebug-e-r-end-marker) + (if (and edebug-all-defuns + (eq 'lparen (edebug-next-token-class)) + (save-excursion + (forward-char 1) ; skip \( + (memq (edebug-read-sexp) '(defun defmacro)))) + (progn + (edebug-defun) + ;; Potential problem: edebug-defun always prints name. + (forward-sexp 1) ; skip the defun + ) + (if (and (eq 'lparen (edebug-next-token-class)) + (save-excursion + (forward-char 1) ; skip \( + (memq (edebug-read-sexp) '(defun defmacro)))) + ;; If it's a defun or defmacro, but not edebug-all-defuns + ;; reset the symbols edebug property to be just a marker at + ;; the definitions source code. + (put (edebug-which-function) 'edebug (point-marker))) + + ;; Evaluate normally - after restoring the current-buffer. + (setq edebug-e-r-val (edebug-read-sexp)) + (save-excursion + (set-buffer edebug-e-r-inside-buf) + (setq edebug-e-r-val (eval edebug-e-r-val)) + ;; Remember current buffer for next time. + (setq edebug-e-r-inside-buf (current-buffer))) + + (if edebug-e-r-output + (progn + (setq values (cons edebug-e-r-val values)) + (if (eq standard-output t) + (prin1 edebug-e-r-val) + (print edebug-e-r-val)))) + ) + (goto-char + (min (max edebug-e-r-end-marker (point)) + (progn (edebug-skip-whitespace) (point)))) + ) ; while + (if (null edebug-e-r-output) + ;; do the save-excursion recovery + (progn + ;; but mark is not restored + (set-buffer edebug-e-r-buf) + (goto-char edebug-e-r-pnt))) + nil + )) + + +;; The standard eval-current-buffer doesn't use eval-region. +(if (not (fboundp 'edebug-emacs-eval-current-buffer)) + (fset 'edebug-emacs-eval-current-buffer + (symbol-function 'eval-current-buffer))) +;; (fset 'eval-current-buffer (symbol-function 'edebug-emacs-eval-current-buffer)) + +(defun eval-current-buffer (&optional edebug-e-c-b-output) + "Call eval-region on the whole buffer." + (interactive) + (eval-region (point-min) (point-max) edebug-e-c-b-output)) + + + +;;;====================================================================== +;;; The Parser +;;; ---------- + +;;; The top level function for parsing defuns is edebug-defun; it +;;; calls all the rest. It checks the syntax a bit and leaves point +;;; at any error it finds, but otherwise should appear to work like +;;; eval-defun. + +;;; The basic plan is to surround each expression with a call to the +;;; function edebug together with indexes into a table of positions of +;;; all expressions. Thus an expression "exp" in function foo +;;; becomes: + +;;; (edebug 1 2 'exp) + +;;; First point moved to to the beginning of exp (offset 1 of the +;;; current function). Then the expression is evaluated and point is +;;; moved to offset 2, at the end of exp. + +;;; The top level expressions of the function are wrapped in a call to +;;; edebug-enter, which supplies the function name and the actual +;;; arguments to the function. See functions edebug and edebug-enter +;;; for more details. + + +(defun edebug-defun () + "Evaluate defun or defmacro, like eval-defun, but with edebug calls. +Print its name in the minibuffer and leave point after any error it finds, +with mark at the original point." + (interactive) + (let (def-kind ; whether defmacro or defun + def-name + def-args + def-docstring + defun-interactive + (edebug-offset-index 0) + edebug-offset-list + edebug-func-mark + (starting-point (point)) + tmp-point + (parse-sexp-ignore-comments t)) + + (condition-case err + (progn + (end-of-defun) + (beginning-of-defun) + (down-list 1) + + (setq edebug-func-mark (point-marker)) + (if (not (eq 'defun (setq def-kind (edebug-read-sexp)))) + (if (not (eq 'defmacro def-kind)) + (edebug-syntax-error "%s is not a defun or defmacro." + def-kind))) + (setq def-name (edebug-read-sexp)) + (if (not (symbolp def-name)) + (edebug-syntax-error "Bad defun name: %s" def-name)) + (setq def-args (edebug-read-sexp)) + (if (not (listp def-args)) + (edebug-syntax-error "Bad defun arg list: %s" def-args)) + + ;; look for doc string + (setq tmp-point (point)) + (if (eq 'string (edebug-next-token-class)) + (progn + (setq def-docstring (edebug-read-sexp)) + (setq tmp-point (point)))) + + ;; look for interactive form + (if (eq 'lparen (edebug-next-token-class)) + (progn + (forward-char 1) ; skip \( + (if (eq 'interactive (edebug-read-sexp)) + (progn + (setq defun-interactive + (cons 'interactive (edebug-interactive))) + (forward-char 1) ; skip \) + (setq tmp-point (point)) + )))) + + (goto-char tmp-point) + + ;; build the new definition + (fset def-name (` (lambda + (, def-args) + (, def-docstring) + (, defun-interactive) + ;; the remainder is a list of sexps + (edebug-enter + (quote (, def-name)) + (quote (, def-args)) + (quote (progn + (,@ (edebug-sexp-list t))))) + ))) + ;; if it is a defmacro, prepend 'macro + (if (eq 'defmacro def-kind) + (fset def-name (cons 'macro (symbol-function def-name)))) + + ;; recover point, like save-excursion but only if no error occurs + (goto-char starting-point) + + ;; store the offset list in functions property list + (put def-name 'edebug + (list edebug-func-mark + nil ; clear breakpoints + (vconcat (nreverse edebug-offset-list)))) + (message "edebug: %s" def-name) + ) ; progn + + (invalid-read-syntax + ;; Set mark at starting-point so user can return. + ;; Leave point at error. + (save-excursion + (goto-char starting-point) + (set-mark-command nil)) + (message "Syntax error: %s" (cdr err)) +;; (signal 'invalid-read-syntax (cdr err)) ; pass it on, to who? + ) + ) ; condition-case + def-name + )) + + +(defun edebug-sexp-list (debuggable) + "Return an edebug form built from the sexp list following point in the +current buffer. If DEBUGGABLE then wrap edebug calls around each sexp. +The sexp list does not start with a left paren; we are already in the list. +Leave point at (before) the trailing right paren." + (let (sexp-list) + (while (not (eq 'rparen (edebug-next-token-class))) + (setq sexp-list (cons (if debuggable + (edebug-form) + (edebug-read-sexp)) + sexp-list))) + (nreverse sexp-list))) + + +(defun edebug-increment-offset () + ;; accesses edebug-offset-index and edebug-offset-list + (setq edebug-offset-index (1+ edebug-offset-index)) + (setq edebug-offset-list (cons (- (point) edebug-func-mark) + edebug-offset-list))) + + +(defun edebug-make-edebug-form (index form) + "Return the edebug form for the current function at offset INDEX given FORM. +Looks like: (edebug def-name INDEX edebug-offset-index 'FORM). +Also increment the offset index." + (prog1 + (list 'edebug + index + edebug-offset-index + (list 'quote form)) + (edebug-increment-offset) + )) + + +(defun edebug-form () + "Return the debug form for the following form. Add the point offset +to the edebug-offset-list for the function and move point to +immediately after the form." + (let* ((index edebug-offset-index) + form class) + ;; The point must be added to the offset list now + ;; because edebug-list will add more offsets indirectly. + (edebug-skip-whitespace) + (edebug-increment-offset) + (setq class (edebug-next-token-class)) + (cond + ((eq 'lparen class) + (edebug-make-edebug-form index (edebug-list))) + + ((eq 'symbol class) + (if (and (not (memq (setq form (edebug-read-sexp)) '(nil t))) + ;; note: symbol includes numbers, see parsing utilities + (not (numberp form))) + (edebug-make-edebug-form index form) + form)) + (t (edebug-read-sexp))))) + + +(defun edebug-list () + "Return an edebug form built from the list form that follows point. +Insert debug calls as appropriate to the form. Start with point at +the left paren. Leave point after the right paren." + (let ((beginning (point)) + class + head) + + (forward-char 1) ; skip \( + (setq class (edebug-next-token-class)) + (cond + ((eq 'symbol class) + (setq head (edebug-read-sexp))) + ((eq 'lparen class) + (setq head (edebug-anonymous))) + ((eq 'rparen class) + (setq head nil)) + (t (edebug-syntax-error + "Head of list must be a symbol or lambda expression."))) + + (prog1 + (if head + (cons head + (cond + +;; None of the edebug-form-hooks defined below are used, for speed. +;; They are included for documentation, though the hook would not +;; necessarily behave the same as the function it is replacing. + +;;; Using the edebug-form-hooks should work, but would take more time. +;;; ((symbolp head) +;;; (let ((form (get head 'edebug-form-hook))) +;;; (if form +;;; (edebug-form-parser form) +;;; (if (edebug-macrop head) +;;; (if edebug-eval-macro-args +;;; (edebug-sexp-list t) +;;; (edebug-sexp-list nil)) +;;; ;; assume it is a function +;;; (edebug-sexp-list t))))) + + ;; handle all special-forms with unevaluated arguments + ((memq head '(let let*)) (edebug-let)) + ((memq head '(setq setq-default)) (edebug-setq)) + ((eq head 'cond) (edebug-cond)) + ((eq head 'condition-case) (edebug-condition-case)) + + ((memq head '(quote ; permits more than one arg + defun defvar defconst defmacro)) + (edebug-sexp-list nil)) + ((eq head 'function) + (list + (if (eq 'lparen (edebug-next-token-class)) + (edebug-anonymous) + (edebug-read-sexp) ; should be just a symbol + ))) + + ;; is it a lisp macro? + ((edebug-macrop head) + (or (and (symbolp head) + (let ((form (get head 'edebug-form-hook))) + (if form + (if (eq form t) + (edebug-sexp-list t) + (if (eq form 0) + (edebug-sexp-list nil) + (edebug-form-parser form)))))) + (edebug-sexp-list edebug-eval-macro-args))) + + ((eq head 'interactive) + (edebug-syntax-error "interactive not expected here.")) + + ;; otherwise it is a function call + (t (edebug-sexp-list t)) + ))) + + (if (eq 'rparen (edebug-next-token-class)) + (forward-char 1) ; skip \) + (edebug-syntax-error "Too many arguments.")) + ))) + + +(defun edebug-form-parser (args) + "Parse the macro arguments that follow based on ARGS. +ARGS describes the types of the arguments of a list form. Each of the ARGS +is processed left to right, in the same order as the arguments of the +list form. See the edebug documentation for more details. The ARGS +may be one of the following: + + symbolp - an unevaluated symbol + integerp - an unevaluated number + stringp - an unevaluated string + vectorp - an unevaluated vector + atom - an unevaluated number, string, symbol, or vector + + sexp - an unevaluated sexp (atom or list); may not be empty + form - an evaluated sexp; may not be empty + + foo - any other symbol should be the name of a function; this + function is called on the argument as a predicate and an error + is signaled if the predicate fails. + + &optional - one following arg in the list may or may not appear. + &rest - all following args are repeated zero or more times as a group. + This is an extension of the normal meaning of &rest. + &or - each of the following args are alternatives, processed left to + right until one succeeds. There is no way to group + more than one list element as one alternative. + + (...) - a sublist, of the same format as the top level, processed recursively. + Special case: if the car of the list is quote, the argument must match + the quoted sexp (see example below of 'for macro). +" + + (let ((arglist args) + arg form form-list class + &optional &rest &or) + (while (and arglist + (not (eq 'rparen (setq class (edebug-next-token-class))))) + (catch 'no-match + (setq arg (car arglist)) + (setq arglist (cdr arglist)) + (if (and &rest (null arglist)) + (setq arglist &rest)) + + (cond + ((memq arg '(&optional &rest &or)) + ;; remember arglist at this point + (set arg arglist) + (throw 'no-match nil)) + + ((eq arg 'form) + (setq form (edebug-form))) + + ((eq arg 'sexp) + (setq form (edebug-read-sexp))) + + ((listp arg) + (if (eq 'quote (car arg)) + ;; special case, match the quoted symbol + (let ((pnt (point))) + (setq arg (car (cdr arg))) + (if (not (eq arg (setq form (edebug-read-sexp)))) + (edebug-form-parser-error) + )) + (if (eq class 'lparen) + (progn + (forward-char 1) ; skip \( + (setq form (edebug-form-parser arg)) + (forward-char 1) ; skip \) + )))) + ((symbolp arg) + (let ((pnt (point)) + (pred (if (fboundp arg) (symbol-function arg)))) + (and pred + (not (funcall pred (setq form (edebug-read-sexp)))) + (edebug-form-parser-error) + ))) + (t (throw 'no-match nil)) + ) ; cond + (setq &optional nil) ; only lasts for one match + (setq form-list (cons form form-list)) ; skipped by no-match throw + )) ; while + + (if (and arglist (not (or &optional &rest + (memq (car arglist) '(&optional &rest))))) + (edebug-syntax-error "Not enough arguments.")) + (if (not (eq 'rparen (edebug-next-token-class))) + (if &or + (edebug-syntax-error "Unrecognized argument.") + (edebug-syntax-error "Too many arguments."))) + (nreverse form-list))) + + +(defun edebug-form-parser-error () + (goto-char pnt) + (if &or + (throw 'no-match nil) + (if &optional + (progn + (setq &optional nil) ; only lasts for one failed match not in &or + (throw 'no-match nil)) + (edebug-syntax-error "%s is not %s" form arg)))) + +;; for loop defined in elisp manual +(put 'for 'edebug-form-hook + '(symbolp 'from form 'to form 'do &rest form)) + +;; case and do defined in cl.el +(put 'case 'edebug-form-hook + '(form &rest (sexp form))) + +(put 'do 'edebug-form-hook + '((&rest + &or symbolp + (symbolp &optional form + &optional form)) + (form &rest form) + &rest body)) + +(put 'defvar 'edebug-form-hook + (put 'defconst 'edebug-form-hook + '(symbolp &optional form &optional stringp))) + +(put 'defun 'edebug-form-hook + (put 'defmacro 'edebug-form-hook + '(symbolp (&rest symbolp) + &optional stringp + &optional ('interactive &or stringp form) + &rest form))) + +(put 'anonymous 'edebug-form-hook + '(&optional 'macro 'lambda (&rest symbolp) &rest form)) + +(defun edebug-anonymous () + "Return the edebug form for an anonymous lambda or macro. +Point starts before the left paren and ends after it." + (forward-char 1) ; skip \( + (prog1 + (let ((head (edebug-read-sexp))) + (cond + ((eq head 'lambda) + (edebug-lambda)) + ((eq head 'macro) + (if (not (eq 'lambda (edebug-read-sexp))) + (edebug-syntax-error "lambda expected.")) + (cons 'macro (edebug-lambda))) + (t (edebug-syntax-error "Anonymous lambda or macro expected.")))) + (forward-char 1) ; skip \) + )) + + +(defun edebug-lambda () + "Return the edebug form for the lambda form that follows. +Point starts after the lambda symbol and is moved to before the right paren." + (append + (list 'lambda (edebug-read-sexp)) ; the args + (edebug-sexp-list t))) ; the body + + + +(put 'let 'edebug-form-hook + (put 'let* 'edebug-form-hook + '((&rest + &or (symbolp &optional form) + symbolp) + &rest form))) + +(defun edebug-let () + "Return the edebug form of the let or let* form. +Leave point before the right paren." + (let (var-value-list + token + class) + (cons + ;; first process the var/value list + (if (not (eq 'lparen (edebug-next-token-class))) + (if (setq token (edebug-read-sexp)) + (edebug-syntax-error "Bad var list in let.") ; should be nil + token ; == nil + ) + + (forward-char 1) ; lparen + (while (not (eq 'rparen (setq class (edebug-next-token-class)))) + (setq var-value-list + (cons + (if (not (eq 'lparen class)) + (edebug-read-sexp) + (forward-char 1) ; lparen + (prog1 + (edebug-var-value) + (if (not (eq 'rparen (edebug-next-token-class))) + (edebug-syntax-error "Right paren expected in let.") + (forward-char 1) ; rparen + ))) + var-value-list))) + (forward-char 1) ; rparen + (nreverse var-value-list)) + + ;; now process the expression list + (edebug-sexp-list t)))) + + +(defun edebug-var-value () + "Return the edebug form of the var and optional value that follow point. +Leave point after the value, if there is one." + (list + (edebug-read-sexp) ; the variable + (and (not (eq 'rparen (edebug-next-token-class))) + (edebug-form)))) + + +(put 'setq 'edebug-form-hook + (put 'setq-default 'edebug-form-hook + '(&rest symbolp form))) + +(defun edebug-setq () + "Return the edebug form of the setq or setq-default var-value list." + (let (var-value-list) + (while (not (eq 'rparen (edebug-next-token-class))) + (setq var-value-list + (append var-value-list + (edebug-var-value)))) + var-value-list)) + + +(put 'interactive 'edebug-form-hook + '(&optional &or stringp form)) + +(defun edebug-interactive () + "Return the edebug form of the interactive form." + (list + (if (not (eq 'rparen (edebug-next-token-class))) + (if (eq 'string (edebug-next-token-class)) + (edebug-read-sexp) + (prog1 + (` (edebug-interactive-entry + (quote (, def-name)) + (quote ((,@ (edebug-form)))))) + (if (not (eq 'rparen (edebug-next-token-class))) + (edebug-syntax-error + "Only first expression used in interactive form."))))))) + + +(put 'cond 'edebug-form-hook + '(&rest (form &rest form))) + +(defun edebug-cond () + "Return the edebug form of the cond form." + (let (value-value-list + class) + (while (not (eq 'rparen (setq class (edebug-next-token-class)))) + (setq value-value-list + (cons + (if (not (eq 'lparen class)) + (let ((thing (edebug-read-sexp))) + (if thing + (edebug-syntax-error "Condition expected in cond") + nil)) + (forward-char 1) ; \( + (prog1 + (cons + (edebug-form) + (if (eq 'rparen (edebug-next-token-class)) + nil + (edebug-sexp-list t))) + (if (not (eq 'rparen (edebug-next-token-class))) + (edebug-syntax-error "Right paren expected in cond")) + (forward-char 1) ; \) + )) + value-value-list))) + (nreverse value-value-list))) + + +;; Bug: this doesnt support condition name lists +(put 'condition-case 'edebug-form-hook + '(symbolp + form + &rest (symbolp &optional form))) + +(defun edebug-condition-case () + "Return the edebug form of the condition-case form." + (cons + (let (token) + ;; read the variable or nil + (setq token (edebug-read-sexp)) + (if (not (symbolp token)) + (edebug-syntax-error + "Variable or nil required for condition-case; found: %s" token)) + token) + + (cons + (edebug-form) ; the form + + ;; process handlers + (let (symb-sexp-list + class) + (while (not (eq 'rparen (setq class (edebug-next-token-class)))) + (setq symb-sexp-list + (cons + (if (not (eq 'lparen class)) + (edebug-syntax-error "Bad handler in condition-case.") + (forward-char 1) ; \( + (prog1 + (cons + (edebug-read-sexp) ; the error-condition + (and (not (eq 'rparen (edebug-next-token-class))) + (edebug-sexp-list t))) + (forward-char 1) ; \) + )) + symb-sexp-list))) + (nreverse symb-sexp-list))))) + + + +;;------------------------------------------------ +;; Parser utilities + +(defun edebug-syntax-error (msg &rest args) + "Signal an invalid-read-syntax with MSG and ARGS. + This is caught by edebug-defun." + (signal 'invalid-read-syntax (apply 'format msg args))) + + +(defun edebug-skip-whitespace () + "Leave point before the next token, skipping white space and comments." + (skip-chars-forward " \t\r\n\f") + (while (= (following-char) ?\;) + (skip-chars-forward "^\n\r") ; skip the comment + (skip-chars-forward " \t\r\n\f"))) + +(defun edebug-read-sexp () + "Read one sexp from the current buffer starting at point. +Leave point immediately after it. A sexp can be a list or atom. +An atom is a symbol (or number), character, string, or vector." + ;; This is gummed up by parser inconsistencies (bugs?) + (let (token) + (edebug-skip-whitespace) + (if (or (= (following-char) ?\[) (= (following-char) ??)) + ;; scan-sexps doesn't read vectors or character literals correctly, + ;; but read does. + (setq token (read (current-buffer))) + (goto-char + (min ; use the lesser of the read and scan-sexps motion + ;; read goes one too far if (quoted) string or symbol + ;; is immediately followed by non-whitespace + (save-excursion + (setq token (read (current-buffer))) + (point)) + ;; scan-sexps reads too far if a quoting character is read + (scan-sexps (point) 1)))) + token)) + +(defconst edebug-syntax-table + (let ((table (make-vector 256 'symbol))) + ;; Treat numbers as symbols, because of confusion with -, -1, and 1-. + (aset table ?\( 'lparen) + (aset table ?\) 'rparen) + (aset table ?\' 'quote) + (aset table ?\" 'string) + (aset table ?\? 'char) + (aset table ?\[ 'vector) + (aset table ?\. 'dot) + ;; We dont care about any other chars since they wont be seen. + table) + "Lookup table for the token class of each character.") + +(defun edebug-next-token-class () + "Move to the next token and return its class. We only care about +lparen, rparen, dot, quote, string, char, vector, or symbol." + (edebug-skip-whitespace) + (aref edebug-syntax-table (following-char))) + + +;;;================================================================= +;;; The debugger itself +;;; ------------------- + + +(defvar edebug-active nil + "Non-nil when edebug is active") + + +;;; add minor-mode-alist entry +(or (assq 'edebug-active minor-mode-alist) + (setq minor-mode-alist (cons (list 'edebug-active " *Debugging*") + minor-mode-alist))) + +(defvar edebug-backtrace nil + "Stack of active functions evaluated via edebug. +Should be nil at the top level.") + +(defvar edebug-offset-indices nil ; not used yet. + "Stack of offset indices of visited edebug sexps. +Should be nil at the top level.") + +(defvar edebug-entered nil + "Non-nil if edebug has already been entered at this recursive edit level.") + + +(defun edebug-enter (edebug-func edebug-args edebug-body) + "Entering FUNC. The arguments are ARGS, and the body is BODY. +Setup edebug variables and evaluate BODY. This function is called +when a function evaluated with edebug-defun is entered. Return the +result of BODY." + + ;; Is this the first time we are entering edebug since + ;; lower-level recursive-edit command? + (if (and (not edebug-entered) + edebug-initial-mode) + ;; Reset edebug-mode to the initial mode. + (setq edebug-mode edebug-initial-mode)) + (let* ((edebug-entered t) + (edebug-data (get edebug-func 'edebug)) + ;; pull out parts of the edebug-data + (edebug-func-mark (car edebug-data)) ; mark at function start + + (edebug-buffer (marker-buffer edebug-func-mark)) + (edebug-backtrace (cons edebug-func edebug-backtrace)) + (max-lisp-eval-depth (+ 6 max-lisp-eval-depth)) ; too much?? + (max-specpdl-size (+ 10 max-specpdl-size)) ; the args and these vars + ) + (if edebug-trace + (let ((edebug-stack-depth (1- (length edebug-backtrace))) + edebug-result) + (edebug-print-trace-entry + "*edebug-trace*" edebug-func edebug-args edebug-stack-depth) + (setq edebug-result (eval edebug-body)) + (edebug-print-trace-exit + "*edebug-trace*" edebug-func edebug-result edebug-stack-depth) + edebug-result) + (eval edebug-body) + ))) + +(defun edebug-interactive-entry (edebug-func edebug-args) + "Evaluating FUNCs non-string argument of interactive form ARGS." + (if (and (not edebug-entered) + edebug-initial-mode) + ;; Reset edebug-mode to the initial mode. + (setq edebug-mode edebug-initial-mode)) + (let* ((edebug-entered t) + (edebug-data (get edebug-func 'edebug)) + ;; pull out parts of the edebug-data + (edebug-func-mark (car edebug-data)) ; mark at function start + + (edebug-buffer (marker-buffer edebug-func-mark)) +;; (edebug-backtrace (cons edebug-func edebug-backtrace)) + ) + (eval edebug-args))) + + +(defun edebug-print-trace-entry + (edebug-stream edebug-function edebug-args edebug-stack-depth) + (edebug-trace-display + edebug-stream + "%sEnter: %s\n" (make-string edebug-stack-depth ?\ ) edebug-function) + ) + +(defun edebug-print-trace-exit + (edebug-stream edebug-function edebug-result edebug-stack-depth) + (edebug-trace-display + edebug-stream + "%sExit: %s\n" (make-string edebug-stack-depth ?\ ) edebug-function) + ) + + +(defun edebug (edebug-before-index edebug-after-index edebug-exp) + "Debug current function given BEFORE and AFTER positions around EXP. +BEFORE and AFTER are indexes into the position offset vector in the +functions 'edebug property. edebug is called from functions compiled +with edebug-defun." + (let ((max-lisp-eval-depth (+ 5 max-lisp-eval-depth)) ; enough?? + (max-specpdl-size (+ 7 max-specpdl-size)) ; the args and these vars + (edebug-offset-indices + (cons edebug-before-index edebug-offset-indices)) + ;; Save the outside value of executing macro. + (edebug-outside-executing-macro executing-macro) + ;; Don't keep reading from an executing kbd macro within edebug! + (executing-macro nil) + ) + (if (and (eq edebug-mode 'Go-nonstop) + (not (edebug-input-pending-p))) + ;; Just return evalled expression. + (eval edebug-exp) + (edebug-debugger edebug-before-index 'enter edebug-exp) + (edebug-debugger edebug-after-index 'exit (eval edebug-exp)) + ))) + + +(defun edebug-debugger (edebug-offset-index edebug-arg-mode edebug-exp) + "Determine if edebug display should be updated." + (let* ( + ;; This needs to be here since breakpoints may be changed. + (edebug-breakpoints (car (cdr edebug-data))) ; list of breakpoints + (edebug-break-data (assq edebug-offset-index edebug-breakpoints)) + (edebug-break + (if edebug-break-data + (let ((edebug-break-condition + (car (cdr edebug-break-data)))) + (or (not edebug-break-condition) + (eval edebug-break-condition))))) + ) + (if (and edebug-break + (car (cdr (cdr edebug-break-data)))) ; is it temporary? + ;; Delete the breakpoint. + (setcdr edebug-data + (cons (delq edebug-break-data edebug-breakpoints) + (cdr (cdr edebug-data))))) + + ;; Dont do anything if mode is go, continue, or Continue-fast + ;; and no break, and no input. + (if (or (and (not (memq edebug-mode '(go continue Continue-fast))) + (or edebug-stop-before-symbols + (not (and (eq edebug-arg-mode 'enter) + (symbolp edebug-exp))))) + (edebug-input-pending-p) + edebug-break) + (edebug-display)) + + edebug-exp + )) + + +(defvar edebug-window-start 0 + "Remember where each buffers' window starts between edebug calls. +This is to avoid spurious recentering.") + +(setq-default edebug-window-start 0) +(make-variable-buffer-local 'edebug-window-start) + +(defun edebug-display () + "Setup windows for edebug, determine mode, maybe enter recursive-edit." + ;; uses local variables of edebug-enter, edebug, and edebug-debugger. + (let ((edebug-active t) ; for minor mode alist + edebug-stop ; should we enter recursive-edit + (edebug-point (+ edebug-func-mark + (aref (car (cdr (cdr edebug-data))) + edebug-offset-index))) + (edebug-buffer-points + (if edebug-save-buffer-points (edebug-get-buffer-points))) + edebug-window ; window displaying edebug-buffer + edebug-inside-window ; window displayed after recursive edit + (edebug-outside-window (selected-window)) + (edebug-outside-buffer (current-buffer)) + (edebug-outside-point (point)) + (edebug-outside-mark (mark)) + edebug-outside-windows ; window or screen configuration + edebug-outside-edebug-point ; old point in edebug buffer + edebug-outside-edebug-mark + + edebug-eval-buffer ; declared here so we can kill it below + (edebug-eval-result-list (and edebug-eval-list + (edebug-eval-result-list))) + (edebug-outside-o-a-p overlay-arrow-position) + (edebug-outside-o-a-s overlay-arrow-string) + (edebug-outside-c-i-e-a cursor-in-echo-area) + + edebug-outside-point-min + edebug-outside-point-max + + overlay-arrow-position + overlay-arrow-string + (cursor-in-echo-area nil) + ;; any others?? + ) + (if (not (buffer-name edebug-buffer)) + (let (debug-on-error nil) + (error "Buffer defining %s not found." edebug-func))) + + ;; Save windows now before we modify them. + (if edebug-save-windows + (setq edebug-outside-windows + (edebug-current-window-configuration))) + + ;; If edebug-buffer is not currently displayed, + ;; first find a window for it. + (edebug-pop-to-buffer edebug-buffer) + (setq edebug-window (selected-window)) + + ;; Now display eval list, if any. + ;; This is done after the pop to edebug-buffer + ;; so that buffer-window correspondence is correct after quit. + (edebug-eval-display edebug-eval-result-list) + (select-window edebug-window) + + (if edebug-save-point + (progn + (setq edebug-outside-edebug-point (point)) + (setq edebug-outside-edebug-mark (mark)))) + + (edebug-save-restriction + (setq edebug-outside-point-min (point-min)) + (setq edebug-outside-point-max (point-max)) + (widen) + (goto-char edebug-point) + + (setq edebug-window-start + (edebug-adjust-window edebug-window-start)) + + (if (edebug-input-pending-p) ; not including keyboard macros + (progn + (setq edebug-mode 'step) + (setq edebug-stop t) + (edebug-stop) + ;; (discard-input) ; is this unfriendly?? + )) + (edebug-overlay-arrow) + + (cond + ((eq 'exit edebug-arg-mode) + ;; Display result of previous evaluation. + (setq edebug-previous-result edebug-exp) + (edebug-previous-result)) + + ((eq 'error edebug-arg-mode) + ;; Display error message + (beep) + (if (eq 'quit (car edebug-exp)) + (message "Quit") + (message "%s: %s" + (get (car edebug-exp) 'error-message) + (car (cdr edebug-exp))))) + + (edebug-break + (message "Break")) + (t (message ""))) + + (if edebug-break + (if (not (memq edebug-mode '(continue Continue-fast))) + (setq edebug-stop t) + (if (eq edebug-mode 'continue) + (edebug-sit-for 1) + (edebug-sit-for 0))) + ;; not edebug-break + (if (eq edebug-mode 'trace) + (edebug-sit-for 1) ; Force update and pause. + (if (eq edebug-mode 'Trace-fast) + (edebug-sit-for 0) ; Force update and continue. + ))) + + (unwind-protect + (if (or edebug-stop + (eq edebug-mode 'step) + (eq edebug-arg-mode 'error)) + (progn + (setq edebug-mode 'step) + (edebug-overlay-arrow) ; this doesnt always show up. + (edebug-recursive-edit));; <<<<<< Recursive edit + ) + + (if edebug-save-buffer-points + (edebug-set-buffer-points)) + ;; Since we may be in a save-excursion, in case of quit + ;; restore the outside window only. + (select-window edebug-outside-window) + ) ; unwind-protect + + ;; None of the following is done if quit or signal occurs. + (if edebug-save-point + ;; Restore point and mark in edebug-buffer. + ;; This does the save-excursion recovery only if no quit. + ;; If edebug-buffer == edebug-outside-buffer, + ;; then this is redundant with outside save-excursion. + (progn + (set-buffer edebug-buffer) + (goto-char edebug-outside-edebug-point) + (if (mark-marker) + (set-marker (mark-marker) edebug-outside-edebug-mark)) + )) + ) ; edebug-save-restriction + + ;; Restore windows, buffer, point, and mark. + (if edebug-save-windows + ;; Restore windows before continuing. + (edebug-set-window-configuration edebug-outside-windows)) + (set-buffer edebug-outside-buffer) + (goto-char edebug-outside-point) + (if (mark-marker) + (set-marker (mark-marker) edebug-outside-mark)) + ;; The following is not sufficient, and sometimes annoying. + ;; (if (memq edebug-mode '(go Go-nonstop)) + ;; (edebug-sit-for 0)) + )) + + +(defvar edebug-depth 0 + "Number of recursive edits started by edebug. +Should be 0 at the top level.") + +(defvar edebug-recursion-depth 0 + "Value of recursion-depth when edebug was called.") + + +(defun edebug-recursive-edit () + "Start up a recursive edit inside of edebug." + ;; The current buffer is the edebug-buffer, which is put into edebug-mode. + (let ((edebug-buffer-read-only buffer-read-only) + ;; match-data must be done in the outside buffer + (edebug-outside-match-data + (save-excursion + (set-buffer edebug-outside-buffer) + (match-data))) + + (edebug-depth (1+ edebug-depth)) + (edebug-recursion-depth (recursion-depth)) + edebug-entered ; bind locally to nil + edebug-backtrace-buffer ; each recursive edit gets its own + ;; The window configuration may be saved and restored + ;; during a recursive-edit + edebug-inside-windows + + (edebug-outside-map (current-local-map)) + (edebug-outside-standard-output standard-output) + (edebug-outside-standard-input standard-input) + + (edebug-outside-last-command-char last-command-char) + (edebug-outside-last-command last-command) + (edebug-outside-this-command this-command) + (edebug-outside-last-input-char last-input-char) +;; (edebug-outside-unread-command-char unread-command-char) + + ;; Declare the following local variables to protect global values. + ;; We could set these to the values for previous edebug call. + ;; But instead make it local, but use global value. + (last-command-char last-command-char) + (last-command last-command) + (this-command this-command) + (last-input-char last-input-char) + ;; Assume no edebug command sets unread-command-char. +;; (unread-command-char -1) + + (debug-on-error debug-on-error) + + ;; others?? + ) + + (if (and (eq edebug-mode 'go) + (not (memq edebug-arg-mode '(exit error)))) + (message "Break")) + (edebug-mode) + (if (boundp 'edebug-outside-debug-on-error) + (setq debug-on-error edebug-outside-debug-on-error)) + + (setq buffer-read-only t) + (unwind-protect + (recursive-edit) ; <<<<<<<<<< Recursive edit + + ;; Do the following, even if quit occurs. + (if edebug-backtrace-buffer + (kill-buffer edebug-backtrace-buffer)) + ;; Could be an option to keep eval display up. + (if edebug-eval-buffer (kill-buffer edebug-eval-buffer)) + + ;; Remember selected-window after recursive-edit. + (setq edebug-inside-window (selected-window)) + + (store-match-data edebug-outside-match-data) + + ;; Recursive edit may have changed buffers, + ;; so set it back before exiting let. + (if (buffer-name edebug-buffer) ; if it still exists + (progn + (set-buffer edebug-buffer) + (if (memq edebug-mode '(go Go-nonstop)) + (edebug-overlay-arrow)) + (setq buffer-read-only edebug-buffer-read-only) + (use-local-map edebug-outside-map) + ;; Remember current window-start for next visit. + (select-window edebug-window) + (if (eq edebug-buffer (window-buffer edebug-window)) + (setq edebug-window-start (window-start))) + (select-window edebug-inside-window) + )) + ))) + + +;;-------------------------- +;; Display related functions + +(defun edebug-adjust-window (old-start) + "Adjust window to fit as much as possible following point. +The display should prefer to start at OLD-START if point is not visible. +Return the new window-start." + (if (not (pos-visible-in-window-p)) + (progn + (set-window-start (selected-window) old-start) + (if (not (pos-visible-in-window-p)) + (let ((start (window-start)) + (pnt (point))) + (set-window-start + (selected-window) + (save-excursion + (forward-line + (if (< pnt start) -1 ; one line before + (- (/ (window-height) 2)) ; center the line + )) + (beginning-of-line) + (point))))))) + (window-start)) + + +(defconst edebug-arrow-alist + '((Continue-fast . ">") + (Trace-fast . ">") + (continue . ">") + (trace . "->") + (step . "=>") + (go . "<>") + (Go-nonstop . "..") ; not used + ) + "Association list of arrows for each edebug mode. +If you come up with arrows that make more sense, let me know.") + +(defun edebug-overlay-arrow () + "Set up the overlay arrow at beginning-of-line in current buffer. +The arrow string is derived from edebug-arrow-alist and edebug-mode." + (let* ((pos)) + (save-excursion + (beginning-of-line) + (setq pos (point))) + (setq overlay-arrow-string + (cdr (assq edebug-mode edebug-arrow-alist))) + (setq overlay-arrow-position (make-marker)) + (set-marker overlay-arrow-position pos (current-buffer)))) + + +(put 'edebug-outside-excursion 'edebug-form-hook + '(&rest form)) + +(defmacro edebug-outside-excursion (&rest body) + "Evaluate an expression list in the outside context. +Return the result of the last expression." + (` (save-excursion ; of current-buffer + (if edebug-save-windows + (progn + ;; After excursion, we will + ;; restore to current window configuration. + (setq edebug-inside-windows + (edebug-current-window-configuration)) + ;; Restore outside windows. + (edebug-set-window-configuration edebug-outside-windows))) + + (set-buffer edebug-buffer) + ;; Restore outside context. + (let ((edebug-inside-map (current-local-map)) + (last-command-char edebug-outside-last-command-char) + (last-command edebug-outside-last-command) + (this-command edebug-outside-this-command) +;; (unread-command-char edebug-outside-unread-command-char) + (last-input-char edebug-outside-last-input-char) + (overlay-arrow-position edebug-outside-o-a-p) + (overlay-arrow-string edebug-outside-o-a-s) + (cursor-in-echo-area edebug-outside-c-i-e-a) + (standard-output edebug-outside-standard-output) + (standard-input edebug-outside-standard-input) + (executing-macro edebug-outside-executing-macro) + ) + (unwind-protect + (save-restriction + (narrow-to-region edebug-outside-point-min + edebug-outside-point-max) + (save-excursion ; of edebug-buffer + (if edebug-save-point + (progn + (goto-char edebug-outside-edebug-point) + (if (mark-marker) + (set-marker (mark-marker) + edebug-outside-edebug-mark)) + )) + (use-local-map edebug-outside-map) + (store-match-data edebug-outside-match-data) + (select-window edebug-outside-window) + (set-buffer edebug-outside-buffer) + (goto-char edebug-outside-point) + (,@ body) + ) ; save-excursion + ) ; save-restriction + ;; Back to edebug-buffer. Restore rest of inside context. + (use-local-map edebug-inside-map) + (if edebug-save-windows + ;; Restore inside windows. + (edebug-set-window-configuration edebug-inside-windows)) + )) ; let + ))) + + +(defun edebug-toggle-save-windows () + "Toggle the edebug-save-windows variable. +Each time you toggle it, the inside and outside window configurations +become the same as the current configuration." + (interactive) + (if (setq edebug-save-windows (not edebug-save-windows)) + (setq edebug-inside-windows + (setq edebug-outside-windows + (edebug-current-window-configuration)))) + (message "Window saving is %s." + (if edebug-save-windows "on" "off"))) + + +(defun edebug-where () + "Show the debug windows and where we stopped in the program." + (interactive) + (if (not edebug-active) + (error "edebug is not active.")) + (edebug-pop-to-buffer edebug-buffer) + (goto-char edebug-point) ; from edebug + ) + +(defun edebug-view-outside () + "Change to the outside window configuration." + (interactive) + (if (not edebug-active) + (error "edebug is not active.")) + (setq edebug-inside-windows (edebug-current-window-configuration)) + (edebug-set-window-configuration edebug-outside-windows) + (goto-char edebug-outside-point) + (message "Window configuration outside of edebug. Return with %s" + (substitute-command-keys "\\<global-map>\\[edebug-where]"))) + + +(defun edebug-bounce-point () + "Bounce the point in the outside current buffer." + (interactive) + (if (not edebug-active) + (error "edebug is not active.")) + (save-excursion + ;; If the buffer's currently displayed, avoid the set-window-configuration. + (save-window-excursion + (edebug-pop-to-buffer edebug-outside-buffer) + ;; (edebug-sit-for 1) ; this shouldnt be necessary + (goto-char edebug-outside-point) + ;; (message "current buffer: %s" (current-buffer)) + (edebug-sit-for 1) + (edebug-pop-to-buffer edebug-buffer)))) + + + +;;-------------------------- +;; epoch related things + +(defvar edebug-epoch-running (and (boundp 'epoch::version) epoch::version) + "non-nil if epoch is running. +Windows are handled a little differently under epoch.") + + +(defun edebug-current-window-configuration () + "Return the current window or screen configuration." + (if edebug-epoch-running + (edebug-current-screen-configuration) + (current-window-configuration))) + + +(defun edebug-set-window-configuration (conf) + "Set the window or screen configuration to CONF." + (if edebug-epoch-running + (edebug-set-screen-configuration conf) + (set-window-configuration conf))) + + +(defun edebug-get-buffer-window (buffer) + (if edebug-epoch-running + (epoch::get-buffer-window buffer) + (get-buffer-window buffer))) + + +(defun edebug-pop-to-buffer (buffer) + "Like pop-to-buffer, but select a screen that buffer was shown in." + (let ((edebug-window (edebug-get-buffer-window buffer))) + (if edebug-window + (select-window edebug-window) + ;; It is not currently displayed, so find some place to display it. + (if edebug-epoch-running + ;; Select a screen that the buffer has been displayed in before + ;; or the current screen otherwise. + (select-screen + ;; allowed-screens in epoch 3.2, was called screens before that + (or (car (symbol-buffer-value 'allowed-screens buffer)) + (epoch::current-screen)))) + (if (one-window-p) + (split-window)) + (select-window (next-window)) + (set-window-buffer (selected-window) buffer) + (set-window-hscroll (selected-window) 0) + )) + ;; Selecting the window does not set the buffer. + (set-buffer buffer) + ) + + +(defun edebug-current-screen-configuration () + "Return an object recording the current configuration of Epoch screen-list. +The object is a list of pairs of the form (SCREEN . CONFIGURATION) +where SCREEN has window-configuration CONFIGURATION. The current +screen is the head of the list." + (let ((screen-list (epoch::screen-list 'unmapped)) + (current-screen (epoch::get-screen)) + (current-buffer (current-buffer)) + ) + ;; put current screen first + (setq screen-list (cons current-screen (delq current-screen screen-list))) + (prog1 + (mapcar (function + (lambda (screen) + (cons screen + (progn + (epoch::select-screen screen) + (current-window-configuration))))) + screen-list) + (epoch::select-screen current-screen) + (set-buffer current-buffer) + ))) + +(defun edebug-set-screen-configuration (sc) + "Set the window-configuration for all the screens in SC. +Set the current screen to be the head of SC." + (mapcar (function + (lambda (screen-conf) + (if (epoch::screen-p (car screen-conf)) ; still exist? + (progn + (epoch::select-screen (car screen-conf)) + (set-window-configuration (cdr screen-conf)))))) + sc) + (if (epoch::screen-p (car (car sc))) + (epoch::select-screen (car (car sc)))) + ) + + +(defun edebug-sit-for (arg) + (if edebug-epoch-running + (epoch::dispatch-events)) + (sit-for arg) +) + +(defun edebug-input-pending-p () + (if edebug-epoch-running + (epoch::dispatch-events)) + (input-pending-p) +) + + + +;;-------------------------- +;; breakpoint related functions + +(defun edebug-find-stop-point () + "Return (function . index) of the nearest edebug stop point." + (let* ((def-name (edebug-which-function)) + (edebug-data + (or (get def-name 'edebug) + (error + "%s must first be evaluated with edebug-defun." def-name))) + ;; pull out parts of edebug-data. + (edebug-func-mark (car edebug-data)) + (edebug-breakpoints (car (cdr edebug-data))) + + (offset-vector (car (cdr (cdr edebug-data)))) + (offset (- (save-excursion + (if (looking-at "[ \t]") + ;; skip backwards until non-whitespace, or bol + (skip-chars-backward " \t")) + (point)) + edebug-func-mark)) + len i) + ;; the offsets are in order so we can do a linear search + (setq len (length offset-vector)) + (setq i 0) + (while (and (< i len) (> offset (aref offset-vector i))) + (setq i (1+ i))) + (if (and (< i len) + (<= offset (aref offset-vector i))) + ;; return the relevant info + (cons def-name i) + (message "Point is not on an expression in %s." + def-name) + ))) + + +(defun edebug-next-breakpoint () + "Move point to the next breakpoint, or first if none past point." + (interactive) + (let ((edebug-stop-point (edebug-find-stop-point))) + (if edebug-stop-point + (let* ((def-name (car edebug-stop-point)) + (index (cdr edebug-stop-point)) + (edebug-data (get def-name 'edebug)) + + ;; pull out parts of edebug-data + (edebug-func-mark (car edebug-data)) + (edebug-breakpoints (car (cdr edebug-data))) + (offset-vector (car (cdr (cdr edebug-data)))) + breakpoint) + (if (not edebug-breakpoints) + (message "No breakpoints in this function.") + (let ((breaks edebug-breakpoints)) + (while (and breaks + (<= (car (car breaks)) index)) + (setq breaks (cdr breaks))) + (setq breakpoint + (if breaks + (car breaks) + ;; goto the first breakpoint + (car edebug-breakpoints))) + (goto-char (+ edebug-func-mark + (aref offset-vector (car breakpoint)))) + + (message (concat (if (car (cdr (cdr breakpoint))) + "Temporary " "") + (if (car (cdr breakpoint)) + (format "Condition: %s" + (prin1-to-string + (car (cdr breakpoint)))) + ""))) + )))))) + + +(defun edebug-modify-breakpoint (flag &optional condition temporary) + "Modify the breakpoint for the form at point or after it according +to FLAG: set if t, clear if nil. Then move to that point. +If CONDITION or TEMPORARY are non-nil, add those attributes to +the breakpoint. " + (let ((edebug-stop-point (edebug-find-stop-point))) + (if edebug-stop-point + (let* ((def-name (car edebug-stop-point)) + (index (cdr edebug-stop-point)) + (edebug-data (get def-name 'edebug)) + + ;; pull out parts of edebug-data + (edebug-func-mark (car edebug-data)) + (edebug-breakpoints (car (cdr edebug-data))) + (offset-vector (car (cdr (cdr edebug-data)))) + present) + ;; delete it either way + (setq present (assq index edebug-breakpoints)) + (setq edebug-breakpoints (delq present edebug-breakpoints)) + (if flag + (progn + ;; add it to the list and resort + (setq edebug-breakpoints + (edebug-sort-alist + (cons + (list index condition temporary) + edebug-breakpoints) '<)) + (message "Breakpoint set in %s." def-name)) + (if present + (message "Breakpoint unset in %s." def-name) + (message "No breakpoint here."))) + + (setcdr edebug-data + (cons edebug-breakpoints (cdr (cdr edebug-data)))) + (goto-char (+ edebug-func-mark (aref offset-vector index))) + )))) + +(defun edebug-set-breakpoint (arg) + "Set the breakpoint of nearest sexp. +With prefix argument, make it a temporary breakpoint." + (interactive "P") + (edebug-modify-breakpoint t nil arg)) + +(defun edebug-unset-breakpoint () + "Clear the breakpoint of nearest sexp." + (interactive) + (edebug-modify-breakpoint nil)) + +(defun edebug-set-conditional-breakpoint (arg condition) + "Set a conditional breakpoint at nearest sexp. +The condition is evaluated in the outside context. +With prefix argument, make it a temporary breakpoint." + (interactive "P\nxCondition: ") + (edebug-modify-breakpoint t condition arg)) + + +;;-------------------------- +;; Mode switching functions + +(defun edebug-set-mode (mode shortmsg msg) + "Set the edebug mode to MODE. +Display SHORTMSG, or MSG if not within edebug." + (interactive) + (setq edebug-mode mode) + (if (< 0 edebug-depth) + (if (eq (current-buffer) edebug-buffer) + (progn + (message shortmsg) + (exit-recursive-edit))) + (message msg))) + + +(defun edebug-step-through () + "Proceed to next debug step." + (interactive) + (edebug-set-mode 'step "" "edebug will stop before next eval.")) + +(defun edebug-go (arg) + "Go, evaluating until break. +With ARG set temporary break at stop point and go." + (interactive "P") + (if arg + (edebug-set-breakpoint t)) + (edebug-set-mode 'go "Go..." "edebug will go until break.")) + +(defun edebug-Go-nonstop () + "Go, evaluating without debugging." + (interactive) + (edebug-set-mode 'Go-nonstop "Go-Nonstop..." + "edebug will not stop at breaks.")) + +(defun edebug-forward-sexp (arg) + "Proceed from the current point to the end of the ARGth sexp ahead. +If there are not ARG sexps ahead, then do edebug-step-out." + (interactive "p") + (condition-case err + (let ((parse-sexp-ignore-comments t)) + ;; Call forward-sexp repeatedly until done or failure. + (forward-sexp arg) + (edebug-go t)) + (error + (edebug-step-out) + ))) + +(defun edebug-step-out () + "Proceed from the current point to the end of the containing sexp. +If there is no containing sexp that is not the top level defun, +go to the end of the last sexp, or if that is the same point, then step." + (interactive) + (condition-case err + (let ((parse-sexp-ignore-comments t)) + (up-list 1) + (save-excursion + ;; Is there still a containing expression? + (up-list 1)) + (edebug-go t)) + (error + ;; At top level - 1, so first check if there are more sexps at this level. + (let ((start-point (point))) +;; (up-list 1) + (down-list -1) + (if (= (point) start-point) + (edebug-step-through) ; No more at this level, so step. + (edebug-go t) + ))))) + + +(defun edebug-goto-here () + "Proceed to this stop point." + (interactive) + (edebug-go t) + ) + +(defun edebug-trace () + "Begin trace mode." + (interactive) + (edebug-set-mode 'trace "Tracing..." "edebug will trace with pause.")) + +(defun edebug-Trace-fast () + "Trace with no wait at each step." + (interactive) + (edebug-set-mode 'Trace-fast + "Trace fast..." "edebug will trace without pause.")) + +(defun edebug-continue () + "Begin continue mode." + (interactive) + (edebug-set-mode 'continue "Continue..." + "edebug will pause at breakpoints.")) + +(defun edebug-Continue-fast () + "Trace with no wait at each step." + (interactive) + (edebug-set-mode 'Continue-fast "Continue fast..." + "edebug will stop and go at breakpoints.")) + + +(defun edebug-step-in () + "Step into the function about to be called. +Do this before the arguments are evaluated since otherwise it will be +too late. One side effect of using edebug-step-in is that the next +time the function is called, edebug will be called there as well." + (interactive) + (if (not (eq 'enter edebug-arg-mode)) + (error "You must be in front of a function or macro call.")) + (let* ((func (car edebug-exp)) + (func-marker (get func 'edebug))) + (cond + ((markerp func-marker) + (save-excursion + (set-buffer (marker-buffer func-marker)) + (goto-char func-marker) + (edebug-defun))) + ((listp func-marker) + ;; its already been evaluated for edebug + nil) + (t (error "You must first evaluate %s in a buffer." func)))) + (exit-recursive-edit)) + + +;;(defun edebug-exit-out () +;; "Go until the current function exits." +;; (interactive) +;; (edebug-set-mode 'exiting "Exit...")) + + +(defun edebug-stop () + "Useful for exiting from trace loop." + (interactive) + (message "Stop")) + + +;;; The following initial mode setting definitions are not used yet. + +(defconst edebug-initial-mode-alist + '((edebug-Continue-fast . Continue-fast) + (edebug-Trace-fast . Trace-fast) + (edebug-continue . continue) + (edebug-trace . trace) + (edebug-go . go) + (edebug-step-through . step) + (edebug-Go-nonstop . Go-nonstop) + ) + "Association list between commands and the modes they set.") + + +(defun edebug-set-initial-mode () + "Ask for the initial mode of the enclosing function. +The mode is requested via the key that would be used to set the mode in +edebug-mode." + (interactive) + (let* ((this-function (edebug-which-function)) + (keymap (if (eq edebug-mode-map (current-local-map)) + edebug-mode-map)) + (old-mode (or (get this-function 'edebug-initial-mode) + edebug-initial-mode)) + (key (read-key-sequence + (format + "Change initial edebug mode for %s from %s (%s) to (enter key): " + this-function + old-mode + (where-is-internal + (car (rassq old-mode edebug-initial-mode-alist)) + keymap 'firstonly + )))) + (mode (cdr (assq (key-binding key) edebug-initial-mode-alist))) + ) + (if (and mode + (or (get this-function 'edebug-initial-mode) + (not (eq mode edebug-initial-mode)))) + (progn + (put this-function 'edebug-initial-mode mode) + (message "Initial mode for %s is now: %s" + this-function mode)) + (error "Key must map to one of the mode changing commands.") + ))) + + + +;;-------------------------- +;; Evaluation of expressions + +(defvar edebug-previous-result nil + "Last result returned from an expression.") + +(defun edebug-previous-result () + "Return the previous result." + (interactive) + (let ((print-escape-newlines t) + (print-length 20)) + (message "Result: %s" (prin1-to-string edebug-previous-result)))) + + +(defun edebug-eval (expr) + "Evaluate EXPR in the outside environment." + (if (not edebug-active) + (error "edebug is not active.")) + (edebug-outside-excursion + (eval expr))) + +(defun edebug-eval-expression (expr) + "Prompt and evaluate an expression in the outside environment. +Print result in minibuffer." + (interactive "xEval: ") + (prin1 (edebug-eval expr))) + +(defun edebug-eval-last-sexp () + "Evaluate sexp before point in the outside environment; +print value in minibuffer." + (interactive) + (prin1 (edebug-eval (edebug-last-sexp)))) + +(defun edebug-eval-print-last-sexp () + "Evaluate sexp before point in the outside environment; +print value into current buffer." + (interactive) + (let ((standard-output (current-buffer))) + (print + (condition-case err + (edebug-eval (edebug-last-sexp)) + (error (format "%s: %s" + (get (car err) 'error-message) + (car (cdr err)))))))) + +;;;--------------------------------- +;;; edebug minor mode initialization + +(defvar edebug-mode 'step + "Current edebug mode set by user.") + +(defvar edebug-mode-map nil) +(if edebug-mode-map + nil + (progn + (setq edebug-mode-map (copy-keymap emacs-lisp-mode-map)) + ;; control + (define-key edebug-mode-map " " 'edebug-step-through) + (define-key edebug-mode-map "g" 'edebug-go) + (define-key edebug-mode-map "G" 'edebug-Go-nonstop) + (define-key edebug-mode-map "t" 'edebug-trace) + (define-key edebug-mode-map "T" 'edebug-Trace-fast) + (define-key edebug-mode-map "c" 'edebug-continue) + (define-key edebug-mode-map "C" 'edebug-Continue-fast) + + (define-key edebug-mode-map "f" 'edebug-forward-sexp) + (define-key edebug-mode-map "h" 'edebug-goto-here) + + (define-key edebug-mode-map "r" 'edebug-previous-result) + + (define-key edebug-mode-map "i" 'edebug-step-in) + (define-key edebug-mode-map "o" 'edebug-step-out) + +;; (define-key edebug-mode-map "m" 'edebug-set-initial-mode) + + (define-key edebug-mode-map "q" 'top-level) + (define-key edebug-mode-map "a" 'abort-recursive-edit) + (define-key edebug-mode-map "S" 'edebug-stop) + + ;; breakpoints + (define-key edebug-mode-map "b" 'edebug-set-breakpoint) + (define-key edebug-mode-map "u" 'edebug-unset-breakpoint) + (define-key edebug-mode-map "B" 'edebug-next-breakpoint) + (define-key edebug-mode-map "x" 'edebug-set-conditional-breakpoint) + + ;; evaluation + (define-key edebug-mode-map "e" 'edebug-eval-expression) + (define-key edebug-mode-map "\C-x\C-e" 'edebug-eval-last-sexp) + (define-key edebug-mode-map "E" 'edebug-visit-eval-list) + + ;; views + (define-key edebug-mode-map "w" 'edebug-where) + (define-key edebug-mode-map "v" 'edebug-view-outside) + (define-key edebug-mode-map "p" 'edebug-bounce-point) + (define-key edebug-mode-map "W" 'edebug-toggle-save-windows) + + ;; misc + (define-key edebug-mode-map "?" 'edebug-help) + (define-key edebug-mode-map "d" 'edebug-backtrace) + + (define-key edebug-mode-map "-" 'negative-argument) + )) + + +(defvar global-edebug-prefix "\^XX" + "Prefix key for global edebug commands, available from any buffer.") + +(defvar global-edebug-map nil + "Global map of edebug commands, available from any buffer.") + +(if global-edebug-map + nil + (setq global-edebug-map (make-sparse-keymap)) + + (global-unset-key global-edebug-prefix) + (global-set-key global-edebug-prefix global-edebug-map) + +;; (define-key global-edebug-map "X" 'edebug-step-through) + (define-key global-edebug-map " " 'edebug-step-through) + (define-key global-edebug-map "g" 'edebug-go) + (define-key global-edebug-map "G" 'edebug-Go-nonstop) + (define-key global-edebug-map "t" 'edebug-trace) + (define-key global-edebug-map "T" 'edebug-Trace-fast) + (define-key global-edebug-map "c" 'edebug-continue) + (define-key global-edebug-map "C" 'edebug-Continue-fast) + +;; (define-key global-edebug-map "m" 'edebug-set-initial-mode) + (define-key global-edebug-map "b" 'edebug-set-breakpoint) + (define-key global-edebug-map "x" 'edebug-set-conditional-breakpoint) + (define-key global-edebug-map "u" 'edebug-unset-breakpoint) + (define-key global-edebug-map "w" 'edebug-where) + (define-key global-edebug-map "q" 'top-level) + ) + + +(defun edebug-help () + (interactive) + (describe-function 'edebug-mode)) + + +(defun edebug-mode () + "Mode for elisp buffers while in edebug. Under construction. + +There are both buffer local and global key bindings to several +functions. E.g. edebug-step-through is bound to +\\[edebug-step-through] in the debug buffer and +\\<global-map>\\[edebug-step-through] in any buffer. + +edebug buffer commands: +\\{edebug-mode-map} + +Global commands prefixed by global-edbug-prefix: +\\{global-edebug-map} + +Options: +edebug-all-defuns +edebug-eval-macro-args +edebug-stop-before-symbols +edebug-save-windows +edebug-save-point +edebug-save-buffer-points +edebug-initial-mode +edebug-trace +" + (use-local-map edebug-mode-map)) + + + +;;=============================================== +;; edebug eval list mode +;; A list of expressions and their evaluations is displayed +;; in edebug-eval-buffer + +(defvar edebug-eval-list nil + "List of expressions to evaluate.") + +;;(defvar edebug-eval-buffer "*edebug*" +;; "*Declared globally so edebug-eval-display can be called independent +;;of edebug (not implemented yet).") + + +(defun edebug-eval-result-list () + "Return a list of evaluations of edebug-eval-list" + ;; Assumes in outside environment. + (mapcar (function + (lambda (expr) + (condition-case err + (eval expr) + (error (format "%s: %s" + (get (car err) 'error-message) + (car (cdr err)))) + ))) + edebug-eval-list)) + +(defun edebug-eval-display-list (edebug-eval-result-list) + ;; Assumes edebug-eval-buffer exists. + (let ((edebug-eval-list-temp edebug-eval-list) + (standard-output edebug-eval-buffer) + (edebug-display-line + (format ";%s\n" (make-string (- (window-width) 2) ?-)))) + (edebug-pop-to-buffer edebug-eval-buffer) + (erase-buffer) + (while edebug-eval-list-temp + (prin1 (car edebug-eval-list-temp)) (terpri) + (prin1 (car edebug-eval-result-list)) (terpri) + (princ edebug-display-line) + (setq edebug-eval-list-temp (cdr edebug-eval-list-temp)) + (setq edebug-eval-result-list (cdr edebug-eval-result-list))) + )) + +(defun edebug-create-eval-buffer () + (if (not (and edebug-eval-buffer (buffer-name edebug-eval-buffer))) + (progn + (set-buffer (setq edebug-eval-buffer (get-buffer-create "*edebug*"))) + (edebug-eval-mode)))) + +;; Should generalize this to be callable outside of edebug +;; with calls in user functions, e.g. (edebug-eval-display) + +(defun edebug-eval-display (edebug-eval-result-list) + "Display expressions and evaluations in EVAL-LIST. +It modifies the context by popping up the eval display." + (if edebug-eval-result-list + (progn + (edebug-create-eval-buffer) + (edebug-pop-to-buffer edebug-eval-buffer) + (edebug-eval-display-list edebug-eval-result-list) + ))) + +(defun edebug-eval-redisplay () + "Redisplay eval list in outside environment. +May only be called from within edebug-recursive-edit." + (edebug-create-eval-buffer) + (edebug-pop-to-buffer edebug-eval-buffer) + (edebug-outside-excursion + (edebug-eval-display-list (edebug-eval-result-list)) + )) + +(defun edebug-visit-eval-list () + (interactive) + (edebug-eval-redisplay) + (edebug-pop-to-buffer edebug-eval-buffer)) + + +(defun edebug-update-eval-list () + "Replace the evaluation list with the sexps now in the eval buffer." + (interactive) + (let ((starting-point (point)) + new-list) + (goto-char (point-min)) + ;; get the first expression + (edebug-skip-whitespace) + (if (not (eobp)) + (progn + (forward-sexp 1) + (setq new-list (cons (edebug-last-sexp) new-list)))) + + (while (re-search-forward "^;" nil t) + (forward-line 1) + (skip-chars-forward " \t\n\r") + (if (and (/= ?\; (following-char)) + (not (eobp))) + (progn + (forward-sexp 1) + (setq new-list (cons (edebug-last-sexp) new-list))))) + + (setq edebug-eval-list (nreverse new-list)) + (edebug-eval-redisplay) + (goto-char starting-point))) + + +(defun edebug-delete-eval-item () + "Delete the item under point and redisplay." + ;; could add arg to do repeatedly + (interactive) + (if (re-search-backward "^;" nil 'nofail) + (forward-line 1)) + (delete-region + (point) (progn (re-search-forward "^;" nil 'nofail) + (beginning-of-line) + (point))) + (edebug-update-eval-list)) + + + +(defvar edebug-eval-mode-map nil + "Keymap for edebug-eval-mode. Superset of lisp-interaction-mode.") + +(if edebug-eval-mode-map + nil + (setq edebug-eval-mode-map (copy-keymap lisp-interaction-mode-map)) + + (define-key edebug-eval-mode-map "\C-c\C-w" 'edebug-where) + (define-key edebug-eval-mode-map "\C-c\C-d" 'edebug-delete-eval-item) + (define-key edebug-eval-mode-map "\C-c\C-u" 'edebug-update-eval-list) + (define-key edebug-eval-mode-map "\C-x\C-e" 'edebug-eval-last-sexp) + (define-key edebug-eval-mode-map "\C-j" 'edebug-eval-print-last-sexp) + ) + + +(defun edebug-eval-mode () + "Mode for data display buffer while in edebug. Under construction. +... ignore the following... +There are both buffer local and global key bindings to several +functions. E.g. edebug-step-through is bound to +\\[edebug-step-through] in the debug buffer and +\\<global-map>\\[edebug-step-through] in any buffer. + +Eval list buffer commands: +\\{edebug-eval-mode-map} + +Global commands prefixed by global-edbug-prefix: +\\{global-edebug-map} +" + (lisp-interaction-mode) + (setq major-mode 'edebug-eval-mode) + (setq mode-name "Edebug-Eval") + (use-local-map edebug-eval-mode-map)) + + +;;======================================== +;; Interface with standard debugger. + +(setq debugger 'edebug-debug) +;; (setq debugger 'debug) ; use the default + +;; Note that debug and its utilities must be byte-compiled to work, since +;; they depend on the backtrace looking a certain way. + +(defun edebug-debug (&rest debugger-args) + "Replacement for debug. +If an error or quit occurred and we are running an edebugged function, +show where we last were. Otherwise call debug normally." + (if (and edebug-backtrace ; anything active? + (eq (recursion-depth) edebug-recursion-depth) + ) + + ;; Where were we before the error occurred? + (let ((edebug-offset-index (car edebug-offset-indices)) + (edebug-arg-mode (car debugger-args)) + (edebug-exp (car (cdr debugger-args))) + edebug-break-data + edebug-break + (edebug-outside-debug-on-eror debug-on-error) + (debug-on-error nil)) + (edebug-display) + ) + + ;; Otherwise call debug normally. + ;; Still need to remove extraneous edebug calls from stack. + (apply 'debug debugger-args) + )) + + +(defun edebug-backtrace () + "Display a non-working backtrace. Better than nothing..." + (interactive) + (let ((old-buf (current-buffer))) + (if (not edebug-backtrace-buffer) + (setq edebug-backtrace-buffer + (let ((default-major-mode 'fundamental-mode)) + (generate-new-buffer "*Backtrace*")))) + (edebug-pop-to-buffer edebug-backtrace-buffer) + (erase-buffer) + (let ((standard-output (current-buffer)) + (print-escape-newlines t) + (print-length 50) + last-ok-point + ) + (setq truncate-lines t) + (backtrace) + + ;; Clean up the backtrace. + (goto-char (point-min)) + (delete-region + (point) + (progn + ;; Everything up to the first edebug is internal. + (re-search-forward "^ edebug(") + (forward-line 1) + (point))) + (forward-line 1) + (setq last-ok-point (point)) + + ;; Delete interspersed edebug internals. + (while (re-search-forward "^ edebug" nil t) + (if (looking-at "-enter") + ;; delete extraneous progn at top level of function body + (save-excursion + (goto-char last-ok-point) + (forward-line -1) + (setq last-ok-point (point)))) + (forward-line 1) + (delete-region last-ok-point (point)) + (forward-line 1) ; skip past the good line + (setq last-ok-point (point)) + ) + ) + (edebug-pop-to-buffer old-buf) + )) + + +;;======================================================================== +;; Trace display - append text to a buffer, and update display. +;;; e.g. +;;; (edebug-trace-display +;;; "*trace-point*" +;;; "saving: point = %s window-start = %s\n" +;;; (point) (window-start)) + +(defun edebug-trace-display (buf-name fmt &rest args) + "In buffer BUF-NAME, display FMT and ARGS at the end and make it visible. +The buffer is created if it does not exist. +You must include newlines in FMT to break lines." + (let* ((selected-window (selected-window)) + (buffer (get-buffer-create buf-name)) + (buf-window)) + (edebug-pop-to-buffer buffer) + (save-excursion + (setq buf-window (selected-window)) + (set-buffer buffer) + (goto-char (point-max)) + (insert (apply 'format fmt args)) + (set-window-point buf-window (point)) + (forward-line (- 1 (window-height buf-window))) + (set-window-start buf-window (point)) +;; (edebug-sit-for 1) + (bury-buffer buffer) + ) + (select-window selected-window))) + +;;; edebug.el ends here