1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
|
;; Run perl -d under Emacs
;; Based on gdb.el, as written by W. Schelter, and modified by rms.
;; Modified for Perl by Ray Lischner (uunet!mntgfx!lisch), Nov 1990.
;; This file is part of GNU Emacs.
;; Copyright (C) 1988,1990 Free Software Foundation, Inc.
;; 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.
;; Description of perl -d interface:
;; A facility is provided for the simultaneous display of the source code
;; in one window, while using perldb to step through a function in the
;; other. A small arrow in the source window, indicates the current
;; line.
;; Starting up:
;; In order to use this facility, invoke the command PERLDB to obtain a
;; shell window with the appropriate command bindings. You will be asked
;; for the name of a file to run and additional command line arguments.
;; Perldb will be invoked on this file, in a window named *perldb-foo*
;; if the file is foo.
;; M-s steps by one line, and redisplays the source file and line.
;; You may easily create additional commands and bindings to interact
;; with the display. For example to put the perl debugger command n on \M-n
;; (def-perldb n "\M-n")
;; This causes the emacs command perldb-next to be defined, and runs
;; perldb-display-frame after the command.
;; perldb-display-frame is the basic display function. It tries to display
;; in the other window, the file and line corresponding to the current
;; position in the perldb window. For example after a perldb-step, it would
;; display the line corresponding to the position for the last step. Or
;; if you have done a backtrace in the perldb buffer, and move the cursor
;; into one of the frames, it would display the position corresponding to
;; that frame.
;; perldb-display-frame is invoked automatically when a filename-and-line-number
;; appears in the output.
(require 'shell)
(defvar perldb-prompt-pattern "^ DB<[0-9]+> "
"A regexp to recognize the prompt for perldb.")
(defvar perldb-mode-map nil
"Keymap for perldb-mode.")
(if perldb-mode-map
nil
(setq perldb-mode-map (copy-keymap shell-mode-map))
(define-key perldb-mode-map "\C-l" 'perldb-refresh))
(define-key ctl-x-map " " 'perldb-break)
(define-key ctl-x-map "&" 'send-perldb-command)
;;Of course you may use `def-perldb' with any other perldb command, including
;;user defined ones.
(defmacro def-perldb (name key &optional doc)
(let* ((fun (intern (concat "perldb-" name))))
(` (progn
(defun (, fun) (arg)
(, (or doc ""))
(interactive "p")
(perldb-call (if (not (= 1 arg))
(concat (, name) arg)
(, name))))
(define-key perldb-mode-map (, key) (quote (, fun)))))))
(def-perldb "s" "\M-s" "Step one source line with display")
(def-perldb "n" "\M-n" "Step one source line (skip functions)")
(def-perldb "c" "\M-c" "Continue with display")
(def-perldb "r" "\C-c\C-r" "Return from current subroutine")
(def-perldb "A" "\C-c\C-a" "Delete all actions")
(defun perldb-mode ()
"Major mode for interacting with an inferior Perl debugger process.
The following commands are available:
\\{perldb-mode-map}
\\[perldb-display-frame] displays in the other window
the last line referred to in the perldb buffer.
\\[perldb-s],\\[perldb-n], and \\[perldb-n] in the perldb window,
call perldb to step, next or continue and then update the other window
with the current file and position.
If you are in a source file, you may select a point to break
at, by doing \\[perldb-break].
Commands:
Many commands are inherited from shell mode.
Additionally we have:
\\[perldb-display-frame] display frames file in other window
\\[perldb-s] advance one line in program
\\[perldb-n] advance one line in program (skip over calls).
\\[send-perldb-command] used for special printing of an arg at the current point.
C-x SPACE sets break point at current line."
(interactive)
(kill-all-local-variables)
(setq major-mode 'perldb-mode)
(setq mode-name "Inferior Perl")
(setq mode-line-process '(": %s"))
(use-local-map perldb-mode-map)
(make-local-variable 'last-input-start)
(setq last-input-start (make-marker))
(make-local-variable 'last-input-end)
(setq last-input-end (make-marker))
(make-local-variable 'perldb-last-frame)
(setq perldb-last-frame nil)
(make-local-variable 'perldb-last-frame-displayed-p)
(setq perldb-last-frame-displayed-p t)
(make-local-variable 'perldb-delete-prompt-marker)
(setq perldb-delete-prompt-marker nil)
(make-local-variable 'perldb-filter-accumulator)
(setq perldb-filter-accumulator nil)
(make-local-variable 'shell-prompt-pattern)
(setq shell-prompt-pattern perldb-prompt-pattern)
(run-hooks 'shell-mode-hook 'perldb-mode-hook))
(defvar current-perldb-buffer nil)
(defvar perldb-command-name "perl"
"Pathname for executing perl -d.")
(defun end-of-quoted-arg (argstr start end)
(let* ((chr (substring argstr start (1+ start)))
(idx (string-match (concat "[^\\]" chr) argstr (1+ start))))
(and idx (1+ idx))
)
)
(defun parse-args-helper (arglist argstr start end)
(while (and (< start end) (string-match "[ \t\n\f\r\b]"
(substring argstr start (1+ start))))
(setq start (1+ start)))
(cond
((= start end) arglist)
((string-match "[\"']" (substring argstr start (1+ start)))
(let ((next (end-of-quoted-arg argstr start end)))
(parse-args-helper (cons (substring argstr (1+ start) next) arglist)
argstr (1+ next) end)))
(t (let ((next (string-match "[ \t\n\f\b\r]" argstr start)))
(if next
(parse-args-helper (cons (substring argstr start next) arglist)
argstr (1+ next) end)
(cons (substring argstr start) arglist))))
)
)
(defun parse-args (args)
"Extract arguments from a string ARGS.
White space separates arguments, with single or double quotes
used to protect spaces. A list of strings is returned, e.g.,
(parse-args \"foo bar 'two args'\") => (\"foo\" \"bar\" \"two args\")."
(nreverse (parse-args-helper '() args 0 (length args)))
)
(defun perldb (path args)
"Run perldb on program FILE in buffer *perldb-FILE*.
The default directory for the current buffer becomes the initial
working directory, by analogy with gdb . If you wish to change this, use
the Perl command `chdir(DIR)'."
(interactive "FRun perl -d on file: \nsCommand line arguments: ")
(setq path (expand-file-name path))
(let ((file (file-name-nondirectory path))
(dir default-directory))
(switch-to-buffer (concat "*perldb-" file "*"))
(setq default-directory dir)
(or (bolp) (newline))
(insert "Current directory is " default-directory "\n")
(apply 'make-shell
(concat "perldb-" file) perldb-command-name nil "-d" path "-emacs"
(parse-args args))
(perldb-mode)
(set-process-filter (get-buffer-process (current-buffer)) 'perldb-filter)
(set-process-sentinel (get-buffer-process (current-buffer)) 'perldb-sentinel)
(perldb-set-buffer)))
(defun perldb-set-buffer ()
(cond ((eq major-mode 'perldb-mode)
(setq current-perldb-buffer (current-buffer)))))
;; This function is responsible for inserting output from Perl
;; into the buffer.
;; Aside from inserting the text, it notices and deletes
;; each filename-and-line-number;
;; that Perl prints to identify the selected frame.
;; It records the filename and line number, and maybe displays that file.
(defun perldb-filter (proc string)
(let ((inhibit-quit t))
(if perldb-filter-accumulator
(perldb-filter-accumulate-marker proc
(concat perldb-filter-accumulator string))
(perldb-filter-scan-input proc string))))
(defun perldb-filter-accumulate-marker (proc string)
(setq perldb-filter-accumulator nil)
(if (> (length string) 1)
(if (= (aref string 1) ?\032)
(let ((end (string-match "\n" string)))
(if end
(progn
(let* ((first-colon (string-match ":" string 2))
(second-colon
(string-match ":" string (1+ first-colon))))
(setq perldb-last-frame
(cons (substring string 2 first-colon)
(string-to-int
(substring string (1+ first-colon)
second-colon)))))
(setq perldb-last-frame-displayed-p nil)
(perldb-filter-scan-input proc
(substring string (1+ end))))
(setq perldb-filter-accumulator string)))
(perldb-filter-insert proc "\032")
(perldb-filter-scan-input proc (substring string 1)))
(setq perldb-filter-accumulator string)))
(defun perldb-filter-scan-input (proc string)
(if (equal string "")
(setq perldb-filter-accumulator nil)
(let ((start (string-match "\032" string)))
(if start
(progn (perldb-filter-insert proc (substring string 0 start))
(perldb-filter-accumulate-marker proc
(substring string start)))
(perldb-filter-insert proc string)))))
(defun perldb-filter-insert (proc string)
(let ((moving (= (point) (process-mark proc)))
(output-after-point (< (point) (process-mark proc)))
(old-buffer (current-buffer))
start)
(set-buffer (process-buffer proc))
(unwind-protect
(save-excursion
;; Insert the text, moving the process-marker.
(goto-char (process-mark proc))
(setq start (point))
(insert string)
(set-marker (process-mark proc) (point))
(perldb-maybe-delete-prompt)
;; Check for a filename-and-line number.
(perldb-display-frame
;; Don't display the specified file
;; unless (1) point is at or after the position where output appears
;; and (2) this buffer is on the screen.
(or output-after-point
(not (get-buffer-window (current-buffer))))
;; Display a file only when a new filename-and-line-number appears.
t))
(set-buffer old-buffer))
(if moving (goto-char (process-mark proc)))))
(defun perldb-sentinel (proc msg)
(cond ((null (buffer-name (process-buffer proc)))
;; buffer killed
;; Stop displaying an arrow in a source file.
(setq overlay-arrow-position nil)
(set-process-buffer proc nil))
((memq (process-status proc) '(signal exit))
;; Stop displaying an arrow in a source file.
(setq overlay-arrow-position nil)
;; Fix the mode line.
(setq mode-line-process
(concat ": "
(symbol-name (process-status proc))))
(let* ((obuf (current-buffer)))
;; save-excursion isn't the right thing if
;; process-buffer is current-buffer
(unwind-protect
(progn
;; Write something in *compilation* and hack its mode line,
(set-buffer (process-buffer proc))
;; Force mode line redisplay soon
(set-buffer-modified-p (buffer-modified-p))
(if (eobp)
(insert ?\n mode-name " " msg)
(save-excursion
(goto-char (point-max))
(insert ?\n mode-name " " msg)))
;; If buffer and mode line will show that the process
;; is dead, we can delete it now. Otherwise it
;; will stay around until M-x list-processes.
(delete-process proc))
;; Restore old buffer, but don't restore old point
;; if obuf is the perldb buffer.
(set-buffer obuf))))))
(defun perldb-refresh ()
"Fix up a possibly garbled display, and redraw the arrow."
(interactive)
(redraw-display)
(perldb-display-frame))
(defun perldb-display-frame (&optional nodisplay noauto)
"Find, obey and delete the last filename-and-line marker from PERLDB.
The marker looks like \\032\\032FILENAME:LINE:CHARPOS\\n.
Obeying it means displaying in another window the specified file and line."
(interactive)
(perldb-set-buffer)
(and perldb-last-frame (not nodisplay)
(or (not perldb-last-frame-displayed-p) (not noauto))
(progn (perldb-display-line (car perldb-last-frame) (cdr perldb-last-frame))
(setq perldb-last-frame-displayed-p t))))
;; Make sure the file named TRUE-FILE is in a buffer that appears on the screen
;; and that its line LINE is visible.
;; Put the overlay-arrow on the line LINE in that buffer.
(defun perldb-display-line (true-file line)
(let* ((buffer (find-file-noselect true-file))
(window (display-buffer buffer t))
(pos))
(save-excursion
(set-buffer buffer)
(save-restriction
(widen)
(goto-line line)
(setq pos (point))
(setq overlay-arrow-string "=>")
(or overlay-arrow-position
(setq overlay-arrow-position (make-marker)))
(set-marker overlay-arrow-position (point) (current-buffer)))
(cond ((or (< pos (point-min)) (> pos (point-max)))
(widen)
(goto-char pos))))
(set-window-point window overlay-arrow-position)))
(defun perldb-call (command)
"Invoke perldb COMMAND displaying source in other window."
(interactive)
(goto-char (point-max))
(setq perldb-delete-prompt-marker (point-marker))
(perldb-set-buffer)
(send-string (get-buffer-process current-perldb-buffer)
(concat command "\n")))
(defun perldb-maybe-delete-prompt ()
(if (and perldb-delete-prompt-marker
(> (point-max) (marker-position perldb-delete-prompt-marker)))
(let (start)
(goto-char perldb-delete-prompt-marker)
(setq start (point))
(beginning-of-line)
(delete-region (point) start)
(setq perldb-delete-prompt-marker nil))))
(defun perldb-break ()
"Set PERLDB breakpoint at this source line."
(interactive)
(let ((line (save-restriction
(widen)
(1+ (count-lines 1 (point))))))
(send-string (get-buffer-process current-perldb-buffer)
(concat "b " line "\n"))))
(defun perldb-read-token()
"Return a string containing the token found in the buffer at point.
A token can be a number or an identifier. If the token is a name prefaced
by `$', `@', or `%', the leading character is included in the token."
(save-excursion
(let (begin)
(or (looking-at "[$@%]")
(re-search-backward "[^a-zA-Z_0-9]" (point-min) 'move))
(setq begin (point))
(or (looking-at "[$@%]") (setq begin (+ begin 1)))
(forward-char 1)
(buffer-substring begin
(if (re-search-forward "[^a-zA-Z_0-9]"
(point-max) 'move)
(- (point) 1)
(point)))
)))
(defvar perldb-commands nil
"List of strings or functions used by send-perldb-command.
It is for customization by the user.")
(defun send-perldb-command (arg)
"Issue a Perl debugger command selected by the prefix arg. A numeric
arg selects the ARG'th member COMMAND of the list perldb-commands.
The token under the cursor is passed to the command. If COMMAND is a
string, (format COMMAND TOKEN) is inserted at the end of the perldb
buffer, otherwise (funcall COMMAND TOKEN) is inserted. If there is
no such COMMAND, then the token itself is inserted. For example,
\"p %s\" is a possible string to be a member of perldb-commands,
or \"p $ENV{%s}\"."
(interactive "P")
(let (comm token)
(if arg (setq comm (nth arg perldb-commands)))
(setq token (perldb-read-token))
(if (eq (current-buffer) current-perldb-buffer)
(set-mark (point)))
(cond (comm
(setq comm
(if (stringp comm) (format comm token) (funcall comm token))))
(t (setq comm token)))
(switch-to-buffer-other-window current-perldb-buffer)
(goto-char (dot-max))
(insert-string comm)))
|