Add GDS doc
[bpt/guile.git] / emacs / gds-client.scm
CommitLineData
32ac6ed1
NJ
1;;;; Guile Debugger UI client
2
3;;; Copyright (C) 2003 Free Software Foundation, Inc.
4;;;
5;; This library is free software; you can redistribute it and/or
6;; modify it under the terms of the GNU Lesser General Public
7;; License as published by the Free Software Foundation; either
8;; version 2.1 of the License, or (at your option) any later version.
9;;
10;; This library is distributed in the hope that it will be useful,
11;; but WITHOUT ANY WARRANTY; without even the implied warranty of
12;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13;; Lesser General Public License for more details.
14;;
15;; You should have received a copy of the GNU Lesser General Public
16;; License along with this library; if not, write to the Free Software
17;; Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18
19(define-module (emacs gds-client)
20 #:use-module (ice-9 debugger)
21 #:use-module (ice-9 debugger behaviour)
22 #:use-module (ice-9 debugger breakpoints)
23 #:use-module (ice-9 debugger breakpoints procedural)
7dd3f110 24 #:use-module (ice-9 debugger breakpoints source)
32ac6ed1
NJ
25 #:use-module (ice-9 debugger state)
26 #:use-module (ice-9 debugger utils)
27 #:use-module (ice-9 optargs)
28 #:use-module (ice-9 regex)
29 #:use-module (ice-9 session)
30 #:use-module (ice-9 string-fun)
31 #:use-module (ice-9 threads)
32 #:export (gds-port-number
33 gds-connected?
34 gds-connect
35 gds-command-loop
36 gds-server-died-hook)
37 #:no-backtrace)
38
39;; The TCP port number that the UI server listens for application
40;; connections on.
41(define gds-port-number 8333)
42
43;; Once connected, the TCP socket port to the UI server.
44(define gds-port #f)
45
46(define* (gds-connect name debug #:optional host)
47 "Connect to the debug UI server as @var{name}, a string that should
48be sufficient to describe the calling application to the debug UI
49user. The optional @var{host} arg specifies the hostname or dotted
50decimal IP address where the UI server is running; default is
51127.0.0.1."
52 (if (gds-connected?)
53 (error "Already connected to UI server!"))
54 ;; Connect to debug server.
55 (set! gds-port
56 (let ((s (socket PF_INET SOCK_STREAM 0))
57 (SOL_TCP 6)
58 (TCP_NODELAY 1))
59 (setsockopt s SOL_TCP TCP_NODELAY 1)
60 (connect s AF_INET (inet-aton (or host "127.0.0.1")) gds-port-number)
61 s))
62 ;; Set debugger-output-port so that stuff written to it is
63 ;; accumulated for sending to the debug server.
64 (set! (debugger-output-port)
65 (make-soft-port (vector accumulate-output
66 accumulate-output
67 #f #f #f #f)
68 "w"))
69 ;; Write initial context to debug server.
70 (write-form (list 'name name (getpid)))
0f8b558c 71 ;(write-form (cons 'modules (map module-name (loaded-modules))))
32ac6ed1
NJ
72 ;; Start the asynchronous UI thread.
73 (start-async-gds-thread)
74 ;; If `debug' is true, debug immediately.
75 (if debug
76 (debug-stack (make-stack #t gds-connect) #:continuable))
77; (gds-command-loop #f)
78 )
79
80(define gds-disable-async-thread noop)
81(define gds-continue-async-thread noop)
82(define async-gds-thread #f)
83
84(define (start-async-gds-thread)
85 (let ((mutex (make-mutex))
86 (condition (make-condition-variable))
87 (admin (pipe)))
88 ;; Start the asynchronous UI thread.
89 (begin-thread
90 (set! async-gds-thread (current-thread))
32ac6ed1
NJ
91 ;;(write (cons admin gds-port))
92 ;;(newline)
0f8b558c 93 (lock-mutex mutex)
32ac6ed1
NJ
94 (catch 'server-died
95 (lambda ()
96 (let loop ((avail '()))
0f8b558c 97 (write-note 'startloop)
32ac6ed1
NJ
98 ;;(write avail)
99 ;;(newline)
100 (cond ((not gds-port)) ; exit loop
101 ((null? avail)
102 (write-status 'ready-for-input)
0f8b558c
NJ
103 (unlock-mutex mutex)
104 (let ((ports (car (select (list gds-port (car admin))
105 '() '()))))
106 (lock-mutex mutex)
107 (loop ports)))
32ac6ed1 108 (else
0f8b558c 109 (write-note 'sthg-to-read)
32ac6ed1
NJ
110 (let ((port (car avail)))
111 (if (eq? port gds-port)
112 (handle-instruction #f (read gds-port))
113 (begin
0f8b558c 114 (write-note 'debugger-takeover)
32ac6ed1
NJ
115 ;; Notification from debugger that it
116 ;; wants to take over. Read the
117 ;; notification char.
118 (read-char (car admin))
119 ;; Wait on condition variable - this allows the
120 ;; debugger thread to grab the mutex.
0f8b558c
NJ
121 (write-note 'cond-wait)
122 (signal-condition-variable condition)
123 (wait-condition-variable condition mutex)
124 ))
32ac6ed1 125 ;; Loop.
0f8b558c
NJ
126 (loop '()))))
127 (write-note 'loopexited)))
32ac6ed1
NJ
128 (lambda args #f))
129 (set! gds-disable-async-thread noop)
130 (set! gds-continue-async-thread noop)
131 (set! async-gds-thread #f)
132 (unlock-mutex mutex))
133 ;; Redefine procs used by debugger thread to take control.
134 (set! gds-disable-async-thread
135 (lambda ()
0f8b558c 136 (lock-mutex mutex)
32ac6ed1
NJ
137 (write-char #\x (cdr admin))
138 (force-output (cdr admin))
0f8b558c
NJ
139 (write-note 'char-written)
140 (wait-condition-variable condition mutex)
32ac6ed1
NJ
141 ;;(display "gds-disable-async-thread: locking mutex...\n"
142 ;; (current-error-port))
0f8b558c 143 ))
32ac6ed1
NJ
144 (set! gds-continue-async-thread
145 (lambda ()
0f8b558c
NJ
146 (write-note 'cond-signal)
147 (signal-condition-variable condition)
148 ;; Make sure that the async thread has got the message
149 ;; before we could possibly try to grab the main mutex
150 ;; again.
151 (unlock-mutex mutex)))))
32ac6ed1
NJ
152
153(define accumulated-output '())
154
155(define (accumulate-output obj)
156 (set! accumulated-output
157 (cons (if (string? obj) obj (make-string 1 obj))
158 accumulated-output)))
159
160(define (get-accumulated-output)
161 (let ((s (apply string-append (reverse! accumulated-output))))
162 (set! accumulated-output '())
163 s))
164
165(define (gds-connected?)
166 "Return @code{#t} if a UI server connected has been made; else @code{#f}."
167 (not (not gds-port)))
168
169(define (gds-command-loop state)
170 "Interact with the UI frontend."
171 (or (gds-connected?)
172 (error "Not connected to UI server."))
173 (gds-disable-async-thread)
174 (catch #t ; Only expect here 'exit-debugger or 'server-died.
175 (lambda ()
176 (let loop ((state state))
177 ;; Write accumulated debugger output.
178 (write-form (list 'output
179 (sans-surrounding-whitespace
180 (get-accumulated-output))))
181 ;; Write current state to the frontend.
182 (if state (write-stack state))
183 ;; Tell the frontend that we're waiting for input.
184 (write-status 'waiting-for-input)
185 ;; Read next instruction, act on it, and loop with
186 ;; updated state.
187 (loop (handle-instruction state (read gds-port)))))
188 (lambda args *unspecified*))
189 (gds-continue-async-thread))
190
191(define (write-stack state)
192 ;; Write Emacs-readable representation of current state to UI
193 ;; frontend.
194 (let ((frames (stack->emacs-readable (state-stack state)))
195 (index (index->emacs-readable (state-index state)))
196 (flags (flags->emacs-readable (state-flags state))))
197 (if (memq 'backwards (debug-options))
198 (write-form (list 'stack
199 frames
200 index
201 flags))
202 ;; Calculate (length frames) here because `reverse!' will make
203 ;; the original `frames' invalid.
204 (let ((nframes (length frames)))
205 (write-form (list 'stack
206 (reverse! frames)
207 (- nframes index 1)
208 flags))))))
209
210(define (write-form form)
211 ;; Write any form FORM to UI frontend.
212 (write form gds-port)
213 (newline gds-port)
214 (force-output gds-port))
215
0f8b558c
NJ
216(define (write-note note)
217 ;; Write a note (for debugging this code) to UI frontend.
218 (false-if-exception (write-form `(note ,note))))
219
32ac6ed1
NJ
220(define (stack->emacs-readable stack)
221 ;; Return Emacs-readable representation of STACK.
222 (map (lambda (index)
223 (frame->emacs-readable (stack-ref stack index)))
224 (iota (stack-length stack))))
225
226(define (frame->emacs-readable frame)
227 ;; Return Emacs-readable representation of FRAME.
228 (if (frame-procedure? frame)
229 (list 'application
230 (with-output-to-string
231 (lambda ()
232 (display (if (frame-real? frame) " " "t "))
233 (write-frame-short/application frame)))
234 (source->emacs-readable (or (frame-source frame)
235 (let ((proc (frame-procedure frame)))
236 (and proc
237 (procedure-source proc))))))
238 (list 'evaluation
239 (with-output-to-string
240 (lambda ()
241 (display (if (frame-real? frame) " " "t "))
242 (write-frame-short/expression frame)))
243 (source->emacs-readable (frame-source frame)))))
244
245(define (source->emacs-readable source)
246 ;; Return Emacs-readable representation of the filename, line and
247 ;; column source properties of SOURCE.
248 (if (and source
249 (string? (source-property source 'filename)))
250 (list (source-property source 'filename)
251 (source-property source 'line)
252 (source-property source 'column))
253 'nil))
254
255(define (index->emacs-readable index)
256 ;; Return Emacs-readable representation of INDEX (the current stack
257 ;; index).
258 index)
259
260(define (flags->emacs-readable flags)
261 ;; Return Emacs-readable representation of FLAGS passed to
262 ;; debug-stack.
263 (map (lambda (flag)
264 (if (keyword? flag)
265 (keyword->symbol flag)
266 (format #f "~S" flag)))
267 flags))
268
269(define the-ice-9-debugger-commands-module
270 (resolve-module '(ice-9 debugger commands)))
271
272(define internal-error-stack #f)
273
274(define (handle-instruction state ins)
275 (if (eof-object? ins)
276 (server-died)
277 (catch #t
278 (lambda ()
279 (lazy-catch #t
280 (lambda ()
281 (handle-instruction-1 state ins))
282 (lambda (key . args)
283 (set! internal-error-stack (make-stack #t))
284 (apply throw key args))))
285 (lambda (key . args)
286 (case key
287 ((exit-debugger)
288 (apply throw key args))
289 (else
290 (write-form
291 `(eval-results "GDS Internal Error\n"
292 ,(list (with-output-to-string
293 (lambda ()
294 (write key)
295 (display ": ")
296 (write args)
297 (newline)
298 (display-backtrace internal-error-stack
299 (current-output-port)))))))))
300 state))))
301
302(define (server-died)
303 (get-accumulated-output)
304 (close-port gds-port)
305 (set! gds-port #f)
306 (run-hook gds-server-died-hook)
307 (throw 'server-died))
308
309(define gds-server-died-hook (make-hook))
310
311(define (handle-instruction-1 state ins)
312 ;; Read the newline that always follows an instruction.
313 (read-char gds-port)
314 ;; Handle instruction from the UI frontend, and return updated state.
315 (case (car ins)
316 ((query-modules)
317 (write-form (cons 'modules (map module-name (loaded-modules))))
318 state)
319 ((query-module)
320 (let ((name (cadr ins)))
321 (write-form `(module ,name
322 ,(or (loaded-module-source name) "(no source file)")
323 ,@(sort (module-map (lambda (key value)
324 (symbol->string key))
325 (resolve-module name))
326 string<?))))
327 state)
328 ((debugger-command)
329 (write-status 'running)
330 (let ((name (cadr ins))
331 (args (cddr ins)))
332 (let ((proc (module-ref the-ice-9-debugger-commands-module name)))
333 (if proc
334 (apply proc state args)
335 (throw 'internal-error proc name args))))
336 state)
337 ((set-breakpoint)
338 (set-breakpoint! (case (cadddr ins)
339 ((debug-here) debug-here)
340 ((trace-here) trace-here)
341 ((trace-subtree) trace-subtree)
342 (else
343 (lambda ()
344 (display "Don't know `")
345 (display (cadddr ins))
346 (display "' behaviour; doing `debug-here' instead.\n")
347 (debug-here))))
348 (module-ref (resolve-module (cadr ins)) (caddr ins)))
349 state)
350 ((eval)
7dd3f110 351 (apply (lambda (module port-name line column bpinfo code)
32ac6ed1
NJ
352 (with-input-from-string code
353 (lambda ()
354 (set-port-filename! (current-input-port) port-name)
355 (set-port-line! (current-input-port) line)
356 (set-port-column! (current-input-port) column)
357 (let ((m (and module (resolve-module module))))
358 (let loop ((results '()) (x (read)))
359 (if (eof-object? x)
360 (write-form `(eval-results ,@results))
7dd3f110 361 (loop (append results (gds-eval x bpinfo m))
32ac6ed1
NJ
362 (read))))))))
363 (cdr ins))
364 state)
365 ((complete)
366 (let ((matches (apropos-internal
367 (string-append "^" (regexp-quote (cadr ins))))))
368 (cond ((null? matches)
369 (write-form '(completion-result nil)))
370 (else
371 ;;(write matches (current-error-port))
372 ;;(newline (current-error-port))
373 (let ((match
374 (let loop ((match (symbol->string (car matches)))
375 (matches (cdr matches)))
376 ;;(write match (current-error-port))
377 ;;(newline (current-error-port))
378 ;;(write matches (current-error-port))
379 ;;(newline (current-error-port))
380 (if (null? matches)
381 match
382 (if (string-prefix=? match
383 (symbol->string (car matches)))
384 (loop match (cdr matches))
385 (loop (substring match 0
386 (- (string-length match) 1))
387 matches))))))
388 (if (string=? match (cadr ins))
389 (write-form `(completion-result
390 ,(map symbol->string matches)))
391 (write-form `(completion-result
392 ,match)))))))
393 state)
394 ((async-break)
395 (let ((thread (car (delq async-gds-thread (all-threads)))))
396 (write (cons 'target-thread thread))
397 (newline)
398 (write (cons 'async-thread async-gds-thread))
399 (newline)
400 (system-async-mark (lambda ()
401 (debug-stack (make-stack #t 3) #:continuable))
402 thread))
403 state)
404 (else state)))
405
7dd3f110
NJ
406(define (install-breakpoints x bpinfo)
407 (define (install-recursive x)
408 (if (list? x)
409 (begin
410 ;; Check source properties of x itself.
411 (let* ((infokey (cons (source-property x 'line)
412 (source-property x 'column)))
413 (bpentry (assoc infokey bpinfo)))
414 (if bpentry
415 (let ((bp (set-breakpoint! debug-here x x)))
416 ;; FIXME: Here should transfer properties from the
417 ;; old breakpoint with index (cdr bpentry) to the
418 ;; new breakpoint. (Or else provide an alternative
419 ;; to set-breakpoint! that reuses the same
420 ;; breakpoint.)
421 (write-form (list 'breakpoint-set
422 (source-property x 'filename)
423 (car infokey)
424 (cdr infokey)
425 (bp-number bp))))))
426 ;; Check each of x's elements.
427 (for-each install-recursive x))))
428 (install-recursive x))
429
430(define (gds-eval x bpinfo m)
32ac6ed1
NJ
431 ;; Consumer to accept possibly multiple values and present them for
432 ;; Emacs as a list of strings.
433 (define (value-consumer . values)
434 (if (unspecified? (car values))
435 '()
436 (map (lambda (value)
437 (with-output-to-string (lambda () (write value))))
438 values)))
7dd3f110
NJ
439 ;; Before evaluation, set breakpoints in the read code as specified
440 ;; by bpinfo.
441 (install-breakpoints x bpinfo)
442 ;; Now do evaluation.
32ac6ed1
NJ
443 (let ((value #f))
444 (let* ((do-eval (if m
445 (lambda ()
446 (display "Evaluating in module ")
447 (write (module-name m))
448 (newline)
449 (set! value
450 (call-with-values (lambda ()
451 (eval x m))
452 value-consumer)))
453 (lambda ()
454 (display "Evaluating in current module ")
455 (write (module-name (current-module)))
456 (newline)
457 (set! value
458 (call-with-values (lambda ()
459 (primitive-eval x))
460 value-consumer)))))
461 (output
462 (with-output-to-string
463 (lambda ()
464 (catch #t
465 do-eval
466 (lambda (key . args)
467 (case key
468 ((misc-error signal unbound-variable
469 numerical-overflow)
470 (apply display-error #f
471 (current-output-port) args)
472 (set! value '("error-in-evaluation")))
473 (else
474 (display "EXCEPTION: ")
475 (display key)
476 (display " ")
477 (write args)
478 (newline)
479 (set! value
480 '("unhandled-exception-in-evaluation"))))))))))
481 (list output value))))
482
483(define (write-status status)
484 (write-form (list 'current-module
485 (format #f "~S" (module-name (current-module)))))
486 (write-form (list 'status status)))
487
488(define (loaded-module-source module-name)
489 ;; Return the file name that (ice-9 boot-9) probably loaded the
490 ;; named module from. (The `probably' is because `%load-path' might
491 ;; have changed since the module was loaded.)
492 (let* ((reverse-name (reverse module-name))
493 (name (symbol->string (car reverse-name)))
494 (dir-hint-module-name (reverse (cdr reverse-name)))
495 (dir-hint (apply string-append
496 (map (lambda (elt)
497 (string-append (symbol->string elt) "/"))
498 dir-hint-module-name))))
499 (%search-load-path (in-vicinity dir-hint name))))
500
501(define (loaded-modules)
502 ;; Return list of all loaded modules sorted by name.
503 (sort (apropos-fold-all (lambda (module acc) (cons module acc)) '())
504 (lambda (m1 m2)
505 (symlist<? (module-name m1) (module-name m2)))))
506
507(define (symlist<? l1 l2)
508 ;; Return #t if symbol list L1 is alphabetically less than L2.
509 (cond ((null? l1) #t)
510 ((null? l2) #f)
511 ((eq? (car l1) (car l2)) (symlist<? (cdr l1) (cdr l2)))
512 (else (string<? (symbol->string (car l1)) (symbol->string (car l2))))))
513
514;;; (emacs gds-client) ends here.