574 lines
18 KiB
EmacsLisp
574 lines
18 KiB
EmacsLisp
;;; semantic/debug.el --- Language Debugger framework -*- lexical-binding: t; -*-
|
|
|
|
;; Copyright (C) 2003-2005, 2008-2024 Free Software Foundation, Inc.
|
|
|
|
;; Author: Eric M. Ludlam <zappo@gnu.org>
|
|
|
|
;; This file is part of GNU Emacs.
|
|
|
|
;; GNU Emacs is free software: you can redistribute it and/or modify
|
|
;; it under the terms of the GNU General Public License as published by
|
|
;; the Free Software Foundation, either version 3 of the License, or
|
|
;; (at your option) any later version.
|
|
|
|
;; GNU Emacs is distributed in the hope that it will be useful,
|
|
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
;; GNU General Public License for more details.
|
|
|
|
;; You should have received a copy of the GNU General Public License
|
|
;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
;;; Commentary:
|
|
;;
|
|
;; To provide better support for debugging parsers, this framework
|
|
;; provides the interface for debugging. The work of parsing and
|
|
;; controlling and stepping through the parsing work must be implemented
|
|
;; by the parser.
|
|
;;
|
|
;; Fortunately, the nature of language support files means that the parser
|
|
;; may not need to be instrumented first.
|
|
;;
|
|
;; The debugger uses EIEIO objects. One object controls the user
|
|
;; interface, including stepping, data-view, queries. A second
|
|
;; object implemented here represents the parser itself. A third represents
|
|
;; a parser independent frame which knows how to highlight the parser buffer.
|
|
;; Each parser must implement the interface and override any methods as needed.
|
|
;;
|
|
|
|
(require 'semantic)
|
|
(require 'eieio)
|
|
(require 'cl-generic)
|
|
(eval-when-compile (require 'semantic/find))
|
|
|
|
;;; Code:
|
|
|
|
;;;###autoload
|
|
(defvar-local semantic-debug-parser-source nil
|
|
"For any buffer, the file name (no path) of the parser.
|
|
This would be a parser for a specific language, not the source
|
|
to one of the parser generators.")
|
|
|
|
;;;###autoload
|
|
(defvar-local semantic-debug-parser-class nil
|
|
"Class to create when building a debug parser object.")
|
|
|
|
;;;###autoload
|
|
(defvar-local semantic-debug-parser-debugger-source nil
|
|
"Location of the debug parser class.")
|
|
|
|
(defvar semantic-debug-enabled nil
|
|
"Non-nil when debugging a parser.")
|
|
|
|
;;; Variables used during a debug session.
|
|
(defvar semantic-debug-current-interface nil
|
|
"The debugger interface currently active for this buffer.")
|
|
|
|
(defvar semantic-debug-current-parser nil
|
|
"The parser current active for this buffer.")
|
|
|
|
;;; User Interface Portion
|
|
;;
|
|
(defclass semantic-debug-interface ()
|
|
((parser-buffer :initarg :parser-buffer
|
|
:type buffer
|
|
:documentation
|
|
"The buffer containing the parser we are debugging.")
|
|
(parser-local-map :initarg :parser-local-map
|
|
:type keymap
|
|
:documentation
|
|
"The local keymap originally in the PARSER buffer.")
|
|
(parser-location :type marker
|
|
:documentation
|
|
"A marker representing where we are in the parser buffer.")
|
|
(source-buffer :initarg :source-buffer
|
|
:type buffer
|
|
:documentation
|
|
"The buffer containing the source we are parsing.
|
|
The :parser-buffer defines a parser that can parse the text in the
|
|
:source-buffer.")
|
|
(source-local-map :initarg :source-local-map
|
|
:type keymap
|
|
:documentation
|
|
"The local keymap originally in the SOURCE buffer.")
|
|
(source-location :type marker
|
|
:documentation
|
|
"A marker representing where we are in the parser buffer.")
|
|
(data-buffer :initarg :data-buffer
|
|
:type buffer
|
|
:documentation
|
|
"Buffer being used to display some useful data.
|
|
These buffers are brought into view when layout occurs.")
|
|
(current-frame :type semantic-debug-frame
|
|
:documentation
|
|
"The currently displayed frame.")
|
|
(overlays :type list
|
|
:initarg nil
|
|
:initform nil
|
|
:documentation
|
|
"Any active overlays being used to show the debug position.")
|
|
)
|
|
"Controls action when in `semantic-debug-mode'.")
|
|
|
|
;; Methods
|
|
(cl-defmethod semantic-debug-set-frame ((iface semantic-debug-interface) frame)
|
|
"Set the current frame on IFACE to FRAME."
|
|
(if frame
|
|
(oset iface current-frame frame)
|
|
(slot-makeunbound iface 'current-frame)))
|
|
|
|
(cl-defmethod semantic-debug-set-parser-location ((iface semantic-debug-interface) point)
|
|
"Set the parser location in IFACE to POINT."
|
|
(with-current-buffer (oref iface parser-buffer)
|
|
(if (not (slot-boundp iface 'parser-location))
|
|
(oset iface parser-location (make-marker)))
|
|
(move-marker (oref iface parser-location) point))
|
|
)
|
|
|
|
(cl-defmethod semantic-debug-set-source-location ((iface semantic-debug-interface) point)
|
|
"Set the source location in IFACE to POINT."
|
|
(with-current-buffer (oref iface source-buffer)
|
|
(if (not (slot-boundp iface 'source-location))
|
|
(oset iface source-location (make-marker)))
|
|
(move-marker (oref iface source-location) point))
|
|
)
|
|
|
|
(cl-defmethod semantic-debug-interface-layout ((iface semantic-debug-interface))
|
|
"Layout windows in the current frame to facilitate debugging."
|
|
(delete-other-windows)
|
|
;; Deal with the data buffer
|
|
(when (slot-boundp iface 'data-buffer)
|
|
(let ((lines (/ (frame-height (selected-frame)) 3))
|
|
(cnt (with-current-buffer (oref iface data-buffer)
|
|
(count-lines (point-min) (point-max))))
|
|
)
|
|
;; Set the number of lines to 1/3, or the size of the data buffer.
|
|
(if (< cnt lines) (setq cnt lines))
|
|
|
|
(split-window-vertically cnt)
|
|
(switch-to-buffer (oref iface data-buffer))
|
|
)
|
|
(other-window 1))
|
|
;; Parser
|
|
(switch-to-buffer (oref iface parser-buffer))
|
|
(when (slot-boundp iface 'parser-location)
|
|
(goto-char (oref iface parser-location)))
|
|
(split-window-vertically)
|
|
(other-window 1)
|
|
;; Source
|
|
(switch-to-buffer (oref iface source-buffer))
|
|
(when (slot-boundp iface 'source-location)
|
|
(goto-char (oref iface source-location)))
|
|
)
|
|
|
|
(cl-defmethod semantic-debug-highlight-lexical-token ((iface semantic-debug-interface) token)
|
|
"For IFACE, highlight TOKEN in the source buffer .
|
|
TOKEN is a lexical token."
|
|
(set-buffer (oref iface source-buffer))
|
|
|
|
(object-add-to-list iface 'overlays
|
|
(semantic-lex-highlight-token token))
|
|
|
|
(semantic-debug-set-source-location iface (semantic-lex-token-start token))
|
|
)
|
|
|
|
(cl-defmethod semantic-debug-highlight-rule ((iface semantic-debug-interface) nonterm &optional rule match)
|
|
"For IFACE, highlight NONTERM in the parser buffer.
|
|
NONTERM is the name of the rule currently being processed that shows up
|
|
as a nonterminal (or tag) in the source buffer.
|
|
If RULE and MATCH indices are specified, highlight those also."
|
|
(set-buffer (oref iface parser-buffer))
|
|
|
|
(let* ((rules (semantic-find-tags-by-class 'nonterminal (current-buffer)))
|
|
(nt (semantic-find-first-tag-by-name nonterm rules))
|
|
(o nil)
|
|
)
|
|
(when nt
|
|
;; I know it is the first symbol appearing in the body of this token.
|
|
(goto-char (semantic-tag-start nt))
|
|
|
|
(setq o (make-overlay (point) (progn (forward-sexp 1) (point))))
|
|
(overlay-put o 'face 'highlight)
|
|
|
|
(object-add-to-list iface 'overlays o)
|
|
|
|
(semantic-debug-set-parser-location iface (overlay-start o))
|
|
|
|
(when (and rule match)
|
|
|
|
;; Rule, an int, is the rule inside the nonterminal we are following.
|
|
(re-search-forward ":\\s-*")
|
|
(while (/= 0 rule)
|
|
(re-search-forward "^\\s-*|\\s-*")
|
|
(setq rule (1- rule)))
|
|
|
|
;; Now find the match inside the rule
|
|
(while (/= 0 match)
|
|
(forward-sexp 1)
|
|
(skip-chars-forward " \t")
|
|
(setq match (1- match)))
|
|
|
|
;; Now highlight the thingy we find there.
|
|
(setq o (make-overlay (point) (progn (forward-sexp 1) (point))))
|
|
(overlay-put o 'face 'highlight)
|
|
|
|
(object-add-to-list iface 'overlays o)
|
|
|
|
;; If we have a match for a sub-rule, have the parser position
|
|
;; move so we can see it in the output window for very long rules.
|
|
(semantic-debug-set-parser-location iface (overlay-start o))
|
|
|
|
))))
|
|
|
|
(cl-defmethod semantic-debug-unhighlight ((iface semantic-debug-interface))
|
|
"Remove all debugging overlays."
|
|
(mapc #'delete-overlay (oref iface overlays))
|
|
(oset iface overlays nil))
|
|
|
|
;; Call from the parser at a breakpoint
|
|
(defvar semantic-debug-user-command nil
|
|
"The command the user is requesting.")
|
|
|
|
(defun semantic-debug-break (frame)
|
|
"Stop parsing now at FRAME.
|
|
FRAME is an object that represents the parser's view of the
|
|
current state of the world.
|
|
This function enters a recursive edit. It returns
|
|
on an `exit-recursive-edit', or if someone uses one
|
|
of the `semantic-debug-mode' commands.
|
|
It returns the command specified. Parsers need to take action
|
|
on different types of return values."
|
|
(save-window-excursion
|
|
;; Set up displaying information
|
|
(semantic-debug-mode t)
|
|
(unwind-protect
|
|
(progn
|
|
(semantic-debug-frame-highlight frame)
|
|
(semantic-debug-interface-layout semantic-debug-current-interface)
|
|
(condition-case nil
|
|
;; Enter recursive edit... wait for user command.
|
|
(recursive-edit)
|
|
(error nil)))
|
|
(semantic-debug-unhighlight semantic-debug-current-interface)
|
|
(semantic-debug-mode nil))
|
|
;; Find the requested user state. Do something.
|
|
(let ((returnstate semantic-debug-user-command))
|
|
(setq semantic-debug-user-command nil)
|
|
returnstate)
|
|
))
|
|
|
|
;;; Frame
|
|
;;
|
|
;; A frame can represent the state at a break point.
|
|
(defclass semantic-debug-frame ()
|
|
(
|
|
)
|
|
"One frame representation.")
|
|
|
|
(cl-defmethod semantic-debug-frame-highlight ((_frame semantic-debug-frame))
|
|
"Highlight one parser frame."
|
|
|
|
)
|
|
|
|
(cl-defmethod semantic-debug-frame-info ((_frame semantic-debug-frame))
|
|
"Display info about this one parser frame."
|
|
|
|
)
|
|
|
|
;;; Major Mode
|
|
;;
|
|
(defvar semantic-debug-mode-map
|
|
(let ((km (make-sparse-keymap)))
|
|
(define-key km "n" #'semantic-debug-next)
|
|
(define-key km " " #'semantic-debug-next)
|
|
(define-key km "s" #'semantic-debug-step)
|
|
(define-key km "u" #'semantic-debug-up)
|
|
(define-key km "d" #'semantic-debug-down)
|
|
(define-key km "f" #'semantic-debug-fail-match)
|
|
(define-key km "h" #'semantic-debug-print-state)
|
|
(define-key km "s" #'semantic-debug-jump-to-source)
|
|
(define-key km "p" #'semantic-debug-jump-to-parser)
|
|
(define-key km "q" #'semantic-debug-quit)
|
|
(define-key km "a" #'semantic-debug-abort)
|
|
(define-key km "g" #'semantic-debug-go)
|
|
(define-key km "b" #'semantic-debug-set-breakpoint)
|
|
;; Some boring bindings.
|
|
(define-key km "e" #'eval-expression)
|
|
|
|
km)
|
|
"Keymap used when in semantic-debug-node.")
|
|
|
|
(defun semantic-debug-mode (onoff)
|
|
"Turn `semantic-debug-mode' on and off.
|
|
Argument ONOFF is non-nil when we are entering debug mode.
|
|
\\{semantic-debug-mode-map}"
|
|
(let ((iface semantic-debug-current-interface))
|
|
(if onoff
|
|
;; Turn it on
|
|
(with-current-buffer (oref iface parser-buffer)
|
|
;; Install our map onto this buffer
|
|
(use-local-map semantic-debug-mode-map)
|
|
;; Make the buffer read only
|
|
(setq buffer-read-only t)
|
|
|
|
(set-buffer (oref iface source-buffer))
|
|
;; Use our map in the source buffer also
|
|
(use-local-map semantic-debug-mode-map)
|
|
;; Make the buffer read only
|
|
(setq buffer-read-only t)
|
|
;; Hooks
|
|
(run-hooks 'semantic-debug-mode-hook)
|
|
)
|
|
;; Restore old mode information
|
|
(with-current-buffer
|
|
(oref semantic-debug-current-interface parser-buffer)
|
|
(use-local-map
|
|
(oref semantic-debug-current-interface parser-local-map))
|
|
(setq buffer-read-only nil)
|
|
)
|
|
(with-current-buffer
|
|
(oref semantic-debug-current-interface source-buffer)
|
|
(use-local-map
|
|
(oref semantic-debug-current-interface source-local-map))
|
|
(setq buffer-read-only nil)
|
|
)
|
|
(run-hooks 'semantic-debug-exit-hook)
|
|
)))
|
|
|
|
;;;###autoload
|
|
(defun semantic-debug ()
|
|
"Parse the current buffer and run in debug mode."
|
|
(interactive)
|
|
(if semantic-debug-current-interface
|
|
(error "You are already in a debug session"))
|
|
(if (not semantic-debug-parser-class)
|
|
(error "This major mode does not support parser debugging"))
|
|
;; Clear the cache to force a full reparse.
|
|
(semantic-clear-toplevel-cache)
|
|
;; Load in the debugger for this file.
|
|
(when semantic-debug-parser-debugger-source
|
|
(require semantic-debug-parser-debugger-source))
|
|
;; Do the parse
|
|
(let ((semantic-debug-enabled t)
|
|
;; Create an interface
|
|
(semantic-debug-current-interface
|
|
(let ((parserb (semantic-debug-find-parser-source)))
|
|
(semantic-debug-interface
|
|
:parser-buffer parserb
|
|
:parser-local-map (with-current-buffer parserb
|
|
(current-local-map))
|
|
:source-buffer (current-buffer)
|
|
:source-local-map (current-local-map)
|
|
)))
|
|
;; Create a parser debug interface
|
|
(semantic-debug-current-parser
|
|
(funcall semantic-debug-parser-class "parser"))
|
|
)
|
|
;; We could recurse into a parser while debugging.
|
|
;; Is that a problem?
|
|
(semantic-fetch-tags)
|
|
;; We should turn the auto-parser back on, but don't do it for
|
|
;; now until the debugger is working well.
|
|
))
|
|
|
|
(defun semantic-debug-find-parser-source ()
|
|
"Return a buffer containing the parser source file for the current buffer.
|
|
The parser needs to be on the load path, or this routine returns nil."
|
|
(if (not semantic-debug-parser-source)
|
|
(error "No parser is associated with this buffer"))
|
|
(let ((parser (locate-library semantic-debug-parser-source t)))
|
|
(if parser
|
|
(find-file-noselect parser)
|
|
(error "Cannot find parser source. It should be on the load-path"))))
|
|
|
|
;;; Debugger commands
|
|
;;
|
|
(defun semantic-debug-next ()
|
|
"Perform one parser operation.
|
|
In the recursive parser, this steps past one match rule.
|
|
In other parsers, this may be just like `semantic-debug-step'."
|
|
(interactive)
|
|
(let ((parser semantic-debug-current-parser))
|
|
(semantic-debug-parser-next parser)
|
|
(exit-recursive-edit)
|
|
)
|
|
)
|
|
|
|
(defun semantic-debug-step ()
|
|
"Perform one parser operation."
|
|
(interactive)
|
|
(let ((parser semantic-debug-current-parser))
|
|
(semantic-debug-parser-step parser)
|
|
(exit-recursive-edit)
|
|
)
|
|
)
|
|
|
|
(defun semantic-debug-up ()
|
|
"Move highlighting representation up one level."
|
|
(interactive)
|
|
(message "Not implemented yet.")
|
|
)
|
|
|
|
(defun semantic-debug-down ()
|
|
"Move highlighting representation down one level."
|
|
(interactive)
|
|
(message "Not implemented yet.")
|
|
)
|
|
|
|
(defun semantic-debug-fail-match ()
|
|
"Artificially fail the current match."
|
|
(interactive)
|
|
(let ((parser semantic-debug-current-parser))
|
|
(semantic-debug-parser-fail parser)
|
|
(exit-recursive-edit)
|
|
)
|
|
)
|
|
|
|
(defun semantic-debug-print-state ()
|
|
"Show interesting parser state."
|
|
(interactive)
|
|
(let ((parser semantic-debug-current-parser))
|
|
(semantic-debug-parser-print-state parser)
|
|
)
|
|
)
|
|
|
|
(defun semantic-debug-jump-to-source ()
|
|
"Move cursor to the source code being parsed at the current lexical token."
|
|
(interactive)
|
|
(let* ((interface semantic-debug-current-interface)
|
|
(buf (oref interface source-buffer)))
|
|
(if (get-buffer-window buf)
|
|
(progn
|
|
(select-frame (window-frame (get-buffer-window buf)))
|
|
(select-window (get-buffer-window buf)))
|
|
;; Technically, this should do a window layout operation
|
|
(switch-to-buffer buf))
|
|
)
|
|
)
|
|
|
|
(defun semantic-debug-jump-to-parser ()
|
|
"Move cursor to the parser being debugged."
|
|
(interactive)
|
|
(let* ((interface semantic-debug-current-interface)
|
|
(buf (oref interface parser-buffer)))
|
|
(if (get-buffer-window buf)
|
|
(progn
|
|
(select-frame (window-frame (get-buffer-window buf)))
|
|
(select-window (get-buffer-window buf)))
|
|
;; Technically, this should do a window layout operation
|
|
(switch-to-buffer buf))
|
|
)
|
|
)
|
|
|
|
(defun semantic-debug-quit ()
|
|
"Exit debug mode, blowing all stack, and leaving the parse incomplete.
|
|
Do not update any tokens already parsed."
|
|
(interactive)
|
|
(let ((parser semantic-debug-current-parser))
|
|
(semantic-debug-parser-quit parser)
|
|
(exit-recursive-edit)
|
|
)
|
|
)
|
|
|
|
(defun semantic-debug-abort ()
|
|
"Abort one level of debug mode, blowing all stack."
|
|
(interactive)
|
|
(let ((parser semantic-debug-current-parser))
|
|
(semantic-debug-parser-abort parser)
|
|
(exit-recursive-edit)
|
|
)
|
|
)
|
|
|
|
(defun semantic-debug-go ()
|
|
"Continue parsing till finish or breakpoint."
|
|
(interactive)
|
|
(let ((parser semantic-debug-current-parser))
|
|
(semantic-debug-parser-go parser)
|
|
(exit-recursive-edit)
|
|
)
|
|
)
|
|
|
|
(defun semantic-debug-set-breakpoint ()
|
|
"Set a breakpoint at the current rule location."
|
|
(interactive)
|
|
(let ((parser semantic-debug-current-parser)
|
|
;; Get the location as semantic tokens.
|
|
(location (semantic-current-tag))
|
|
)
|
|
(if location
|
|
(semantic-debug-parser-break parser location)
|
|
(error "Not on a rule"))
|
|
)
|
|
)
|
|
|
|
|
|
;;; Debugger superclass
|
|
;;
|
|
(defclass semantic-debug-parser ()
|
|
(
|
|
)
|
|
"Represents a parser and its state.
|
|
When implementing the debug parser you can add extra functionality
|
|
by overriding one of the command methods. Be sure to use
|
|
`call-next-method' so that the debug command is saved, and passed
|
|
down to your parser later."
|
|
:abstract t)
|
|
|
|
(cl-defmethod semantic-debug-parser-next ((_parser semantic-debug-parser))
|
|
"Execute next for this PARSER."
|
|
(setq semantic-debug-user-command 'next)
|
|
)
|
|
|
|
(cl-defmethod semantic-debug-parser-step ((_parser semantic-debug-parser))
|
|
"Execute a step for this PARSER."
|
|
(setq semantic-debug-user-command 'step)
|
|
)
|
|
|
|
(cl-defmethod semantic-debug-parser-go ((_parser semantic-debug-parser))
|
|
"Continue execution in this PARSER until the next breakpoint."
|
|
(setq semantic-debug-user-command 'go)
|
|
)
|
|
|
|
(cl-defmethod semantic-debug-parser-fail ((_parser semantic-debug-parser))
|
|
"Continue execution in this PARSER until the next breakpoint."
|
|
(setq semantic-debug-user-command 'fail)
|
|
)
|
|
|
|
(cl-defmethod semantic-debug-parser-quit ((_parser semantic-debug-parser))
|
|
"Continue execution in this PARSER until the next breakpoint."
|
|
(setq semantic-debug-user-command 'quit)
|
|
)
|
|
|
|
(cl-defmethod semantic-debug-parser-abort ((_parser semantic-debug-parser))
|
|
"Continue execution in this PARSER until the next breakpoint."
|
|
(setq semantic-debug-user-command 'abort)
|
|
)
|
|
|
|
(cl-defmethod semantic-debug-parser-print-state ((_parser semantic-debug-parser))
|
|
"Print state for this PARSER at the current breakpoint."
|
|
(with-slots (current-frame) semantic-debug-current-interface
|
|
(when current-frame
|
|
(semantic-debug-frame-info current-frame)
|
|
)))
|
|
|
|
(cl-defmethod semantic-debug-parser-break ((_parser semantic-debug-parser))
|
|
"Set a breakpoint for this PARSER."
|
|
)
|
|
|
|
;; Stack stuff
|
|
(cl-defmethod semantic-debug-parser-frames ((_parser semantic-debug-parser))
|
|
"Return a list of frames for the current parser.
|
|
A frame is of the form:
|
|
( .. .what ? .. )"
|
|
(error "Parser has not implemented frame values"))
|
|
|
|
|
|
(provide 'semantic/debug)
|
|
|
|
;; Local variables:
|
|
;; generated-autoload-file: "loaddefs.el"
|
|
;; generated-autoload-load-name: "semantic/debug"
|
|
;; End:
|
|
|
|
;;; semantic/debug.el ends here
|