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