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