Implement eval threads.
[bpt/guile.git] / emacs / gds-client.scm
CommitLineData
32ac6ed1
NJ
1;;;; Guile Debugger UI client
2
a6ab1deb 3;;; Copyright (C) 2003, 2004 Free Software Foundation, Inc.
32ac6ed1
NJ
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
a6ab1deb
NJ
39
40;;;; {Internal Tracing and Debugging}
41
42;; Some of this module's thread and mutex code is quite tricky and
43;; includes `trc' statements to trace out useful information if the
44;; environment variable GDS_TRC is defined.
45(define trc
46 (if (getenv "GDS_TRC")
47 (let ((port (open-output-file "/home/neil/gds-client.log"))
48 (trc-mutex (make-mutex)))
49 (lambda args
50 (with-mutex trc-mutex
51 (write args port)
52 (newline port)
53 (force-output port))))
54 noop))
55
56(define-macro (assert expr)
57 `(or ,expr
58 (error "Assertion failed" expr)))
59
60
61;;;; {TCP Connection}
62
63;; Communication between this module (running in the application being
64;; debugged) and the GDS server and UI code (running in/under Emacs)
65;; is through a TCP connection. `gds-port-number' is the TCP port
66;; number where the server listens for application connections.
32ac6ed1
NJ
67(define gds-port-number 8333)
68
a6ab1deb 69;; Once connected, the TCP socket port to the server.
32ac6ed1
NJ
70(define gds-port #f)
71
a6ab1deb
NJ
72;; Public procedure to discover whether there is a GDS connection yet.
73(define (gds-connected?)
74 "Return @code{#t} if a UI server connected has been made; else @code{#f}."
75 (not (not gds-port)))
76
77;; Public procedure to create the connection to the GDS server.
78(define* (gds-connect name #:optional host)
79 "Connect to the GDS server as @var{name}, a string that should be
80sufficient to describe the calling application to the GDS frontend
32ac6ed1
NJ
81user. The optional @var{host} arg specifies the hostname or dotted
82decimal IP address where the UI server is running; default is
83127.0.0.1."
84 (if (gds-connected?)
85 (error "Already connected to UI server!"))
86 ;; Connect to debug server.
87 (set! gds-port
88 (let ((s (socket PF_INET SOCK_STREAM 0))
89 (SOL_TCP 6)
90 (TCP_NODELAY 1))
91 (setsockopt s SOL_TCP TCP_NODELAY 1)
92 (connect s AF_INET (inet-aton (or host "127.0.0.1")) gds-port-number)
93 s))
a6ab1deb
NJ
94 ;; Set debugger-output-port so that messages written to it are not
95 ;; displayed on the application's stdout, but instead accumulated
96 ;; for sending to the GDS frontend.
32ac6ed1
NJ
97 (set! (debugger-output-port)
98 (make-soft-port (vector accumulate-output
99 accumulate-output
100 #f #f #f #f)
101 "w"))
a6ab1deb 102 ;; Announce ourselves to the server.
32ac6ed1 103 (write-form (list 'name name (getpid)))
a6ab1deb
NJ
104 ;; Start the UI read thread.
105 (set! ui-read-thread (make-thread ui-read-thread-proc)))
32ac6ed1
NJ
106
107(define accumulated-output '())
108
109(define (accumulate-output obj)
110 (set! accumulated-output
111 (cons (if (string? obj) obj (make-string 1 obj))
112 accumulated-output)))
113
114(define (get-accumulated-output)
115 (let ((s (apply string-append (reverse! accumulated-output))))
116 (set! accumulated-output '())
117 s))
118
32ac6ed1 119
a6ab1deb
NJ
120;;;; {UI Read Thread}
121
122;; Except when the application enters the debugger, communication with
123;; the GDS server and frontend is managed by a dedicated thread for
124;; this purpose. This design avoids having to modify application code
125;; at the expense of requiring a Guile with threads support.
126(define (ui-read-thread-proc)
127 (let ((eval-thread-needed? #t))
128 ;; Start up the default eval thread.
129 (make-thread eval-thread 1 (lambda () (not eval-thread-needed?)))
130 (with-mutex ui-read-mutex
131 (catch 'server-died
132 ;; Protected thunk: loop reading either protocol input from
133 ;; the server, or an indication (through ui-read-switch-pipe)
134 ;; that a thread in the debugger wants to take over the
135 ;; interaction with the server.
136 (lambda ()
137 (let loop ((avail '()))
138 (write-note 'startloop)
139 (cond ((not gds-port)) ; exit loop
140 ((null? avail)
141 (write-status 'ready-for-input)
142 (loop (without-mutex ui-read-mutex
143 (car (select (list gds-port
144 (car ui-read-switch-pipe))
145 '() '())))))
146 (else
147 (write-note 'sthg-to-read)
148 (let ((port (car avail)))
149 (if (eq? port gds-port)
150 (handle-instruction #f (read gds-port))
151 (begin
152 (write-note 'debugger-takeover)
153 ;; Notification from debugger that it wants
154 ;; to take over. Read the notification
155 ;; char.
156 (read-char (car ui-read-switch-pipe))
157 ;; Wait on ui-read-switch variable - this
158 ;; allows the debugger thread to grab the
159 ;; mutex.
160 (write-note 'cond-wait)
161 (signal-condition-variable ui-read-switch)
162 (wait-condition-variable ui-read-switch
163 ui-read-mutex)))
164 ;; Loop.
165 (loop '()))))
166 (write-note 'loopexited)))
167 ;; Catch handler.
168 (lambda args #f)))
169 ;; Tell the eval thread that it can exit.
170 (with-mutex eval-work-mutex
171 (set! eval-thread-needed? #f)
172 (broadcast-condition-variable eval-work-changed))))
173
174;; It's useful to keep a note of the UI thread's id.
175(define ui-read-thread #f)
176
177;; Mutex used to control which thread is currently reading the TCP
178;; connection to the server/UI.
179(define ui-read-mutex (make-mutex))
180
181;; Condition variable used by threads interested in reading the TCP
182;; connection to signal changes in their state.
183(define ui-read-switch (make-condition-variable))
184
185;; Pipe used by application threads that enter the debugger to tell
186;; the UI read thread that they'd like to take over reading the TCP
187;; connection.
188(define ui-read-switch-pipe (pipe))
189
190
191;;;; {Debugger Integration}
192
193;; When a thread enters the Guile debugger and a GDS connection is
194;; present, the debugger calls `gds-command-loop' instead of entering
195;; its usual command loop.
32ac6ed1
NJ
196(define (gds-command-loop state)
197 "Interact with the UI frontend."
198 (or (gds-connected?)
199 (error "Not connected to UI server."))
a6ab1deb
NJ
200 ;; Take over server/UI interaction from the normal UI read thread.
201 (with-mutex ui-read-mutex)
202 (write-char #\x (cdr ui-read-switch-pipe))
203 (force-output (cdr ui-read-switch-pipe))
204 (write-note 'char-written)
205 (wait-condition-variable ui-read-switch ui-read-mutex)
206 ;; We now "have the com", as they say on Star Trek.
207 (catch #t ; Only expect here 'exit-debugger or 'server-died.
208 (lambda ()
209 (let loop ((state state))
210 ;; Write accumulated debugger output.
211 (write-form (list 'output (sans-surrounding-whitespace
212 (get-accumulated-output))))
213 ;; Write current state to the frontend.
214 (if state (write-stack state))
215 ;; Tell the frontend that we're waiting for input.
216 (write-status 'waiting-for-input)
217 ;; Read next instruction, act on it, and loop with updated
218 ;; state.
219 (loop (handle-instruction state (read gds-port)))))
220 (lambda args *unspecified*))
221 (write-note 'cond-signal)
222 ;; Tell the UI read thread that it can take control again.
223 (signal-condition-variable ui-read-switch))
224
225
226;;;; {General Output to Server/UI}
227
228(define write-form
229 (let ((protocol-mutex (make-mutex)))
230 (lambda (form)
231 ;; Write any form FORM to UI frontend.
232 (with-mutex protocol-mutex
233 (write form gds-port)
234 (newline gds-port)
235 (force-output gds-port)))))
236
237(define (write-note note)
238 ;; Write a note (for debugging this code) to UI frontend.
239 (false-if-exception (write-form `(note ,note))))
240
241(define (write-status status)
242 (write-form (list 'current-module
243 (format #f "~S" (module-name (current-module)))))
244 (write-form (list 'status status)))
245
246
247;;;; {Stack Output to Server/UI}
32ac6ed1
NJ
248
249(define (write-stack state)
250 ;; Write Emacs-readable representation of current state to UI
251 ;; frontend.
252 (let ((frames (stack->emacs-readable (state-stack state)))
253 (index (index->emacs-readable (state-index state)))
254 (flags (flags->emacs-readable (state-flags state))))
255 (if (memq 'backwards (debug-options))
256 (write-form (list 'stack
257 frames
258 index
259 flags))
260 ;; Calculate (length frames) here because `reverse!' will make
261 ;; the original `frames' invalid.
262 (let ((nframes (length frames)))
263 (write-form (list 'stack
264 (reverse! frames)
265 (- nframes index 1)
266 flags))))))
267
32ac6ed1
NJ
268(define (stack->emacs-readable stack)
269 ;; Return Emacs-readable representation of STACK.
270 (map (lambda (index)
271 (frame->emacs-readable (stack-ref stack index)))
272 (iota (stack-length stack))))
273
274(define (frame->emacs-readable frame)
275 ;; Return Emacs-readable representation of FRAME.
276 (if (frame-procedure? frame)
277 (list 'application
278 (with-output-to-string
279 (lambda ()
280 (display (if (frame-real? frame) " " "t "))
281 (write-frame-short/application frame)))
282 (source->emacs-readable (or (frame-source frame)
283 (let ((proc (frame-procedure frame)))
284 (and proc
285 (procedure-source proc))))))
286 (list 'evaluation
287 (with-output-to-string
288 (lambda ()
289 (display (if (frame-real? frame) " " "t "))
290 (write-frame-short/expression frame)))
291 (source->emacs-readable (frame-source frame)))))
292
293(define (source->emacs-readable source)
294 ;; Return Emacs-readable representation of the filename, line and
295 ;; column source properties of SOURCE.
296 (if (and source
297 (string? (source-property source 'filename)))
298 (list (source-property source 'filename)
299 (source-property source 'line)
300 (source-property source 'column))
301 'nil))
302
303(define (index->emacs-readable index)
304 ;; Return Emacs-readable representation of INDEX (the current stack
305 ;; index).
306 index)
307
308(define (flags->emacs-readable flags)
309 ;; Return Emacs-readable representation of FLAGS passed to
310 ;; debug-stack.
311 (map (lambda (flag)
312 (if (keyword? flag)
313 (keyword->symbol flag)
314 (format #f "~S" flag)))
315 flags))
316
32ac6ed1 317
a6ab1deb 318;;;; {Handling GDS Protocol Instructions}
32ac6ed1 319
a6ab1deb
NJ
320;; Instructions from the server/UI always come through here. If
321;; `state' is non-#f, we are in the debugger; otherwise, not.
32ac6ed1
NJ
322(define (handle-instruction state ins)
323 (if (eof-object? ins)
324 (server-died)
325 (catch #t
326 (lambda ()
327 (lazy-catch #t
328 (lambda ()
329 (handle-instruction-1 state ins))
330 (lambda (key . args)
331 (set! internal-error-stack (make-stack #t))
332 (apply throw key args))))
333 (lambda (key . args)
334 (case key
335 ((exit-debugger)
336 (apply throw key args))
337 (else
338 (write-form
a6ab1deb
NJ
339 `(eval-results error
340 "GDS Internal Error\n"
32ac6ed1
NJ
341 ,(list (with-output-to-string
342 (lambda ()
343 (write key)
344 (display ": ")
345 (write args)
346 (newline)
347 (display-backtrace internal-error-stack
348 (current-output-port)))))))))
349 state))))
350
351(define (server-died)
352 (get-accumulated-output)
353 (close-port gds-port)
354 (set! gds-port #f)
355 (run-hook gds-server-died-hook)
356 (throw 'server-died))
357
a6ab1deb
NJ
358(define internal-error-stack #f)
359
32ac6ed1
NJ
360(define gds-server-died-hook (make-hook))
361
362(define (handle-instruction-1 state ins)
363 ;; Read the newline that always follows an instruction.
364 (read-char gds-port)
365 ;; Handle instruction from the UI frontend, and return updated state.
366 (case (car ins)
367 ((query-modules)
368 (write-form (cons 'modules (map module-name (loaded-modules))))
369 state)
370 ((query-module)
371 (let ((name (cadr ins)))
372 (write-form `(module ,name
373 ,(or (loaded-module-source name) "(no source file)")
374 ,@(sort (module-map (lambda (key value)
375 (symbol->string key))
376 (resolve-module name))
377 string<?))))
378 state)
379 ((debugger-command)
a6ab1deb 380 (or state (error "Not currently in debugger!"))
32ac6ed1
NJ
381 (write-status 'running)
382 (let ((name (cadr ins))
383 (args (cddr ins)))
384 (let ((proc (module-ref the-ice-9-debugger-commands-module name)))
385 (if proc
386 (apply proc state args)
387 (throw 'internal-error proc name args))))
388 state)
389 ((set-breakpoint)
390 (set-breakpoint! (case (cadddr ins)
391 ((debug-here) debug-here)
392 ((trace-here) trace-here)
393 ((trace-subtree) trace-subtree)
394 (else
395 (lambda ()
396 (display "Don't know `")
397 (display (cadddr ins))
398 (display "' behaviour; doing `debug-here' instead.\n")
399 (debug-here))))
400 (module-ref (resolve-module (cadr ins)) (caddr ins)))
401 state)
402 ((eval)
a6ab1deb 403 (apply (lambda (correlator module port-name line column bpinfo code)
32ac6ed1
NJ
404 (with-input-from-string code
405 (lambda ()
406 (set-port-filename! (current-input-port) port-name)
407 (set-port-line! (current-input-port) line)
408 (set-port-column! (current-input-port) column)
409 (let ((m (and module (resolve-module module))))
a6ab1deb 410 (let loop ((exprs '()) (x (read)))
32ac6ed1 411 (if (eof-object? x)
a6ab1deb
NJ
412 ;; Expressions to be evaluated have all been
413 ;; read. Now hand them off to an
414 ;; eval-thread for the actual evaluation.
415 (with-mutex eval-work-mutex
416 (trc 'protocol-thread "evaluation work available")
417 (set! eval-work (cons* correlator m (reverse! exprs)))
418 (set! eval-work-available #t)
419 (broadcast-condition-variable eval-work-changed)
420 (wait-condition-variable eval-work-taken
421 eval-work-mutex)
422 (assert (not eval-work-available))
423 (trc 'protocol-thread "evaluation work underway"))
424 ;; Another complete expression read. Set
425 ;; breakpoints in the read code as specified
426 ;; by bpinfo, and add it to the list.
427 (begin
428 (install-breakpoints x bpinfo)
429 (loop (cons x exprs) (read)))))))))
32ac6ed1
NJ
430 (cdr ins))
431 state)
432 ((complete)
433 (let ((matches (apropos-internal
434 (string-append "^" (regexp-quote (cadr ins))))))
435 (cond ((null? matches)
436 (write-form '(completion-result nil)))
437 (else
438 ;;(write matches (current-error-port))
439 ;;(newline (current-error-port))
440 (let ((match
441 (let loop ((match (symbol->string (car matches)))
442 (matches (cdr matches)))
443 ;;(write match (current-error-port))
444 ;;(newline (current-error-port))
445 ;;(write matches (current-error-port))
446 ;;(newline (current-error-port))
447 (if (null? matches)
448 match
449 (if (string-prefix=? match
450 (symbol->string (car matches)))
451 (loop match (cdr matches))
452 (loop (substring match 0
453 (- (string-length match) 1))
454 matches))))))
455 (if (string=? match (cadr ins))
456 (write-form `(completion-result
457 ,(map symbol->string matches)))
458 (write-form `(completion-result
459 ,match)))))))
460 state)
461 ((async-break)
a6ab1deb 462 (let ((thread (car (delq ui-read-thread (all-threads)))))
32ac6ed1
NJ
463 (write (cons 'target-thread thread))
464 (newline)
a6ab1deb 465 (write (cons 'ui-read-thread ui-read-thread))
32ac6ed1
NJ
466 (newline)
467 (system-async-mark (lambda ()
468 (debug-stack (make-stack #t 3) #:continuable))
469 thread))
470 state)
471 (else state)))
472
a6ab1deb
NJ
473(define the-ice-9-debugger-commands-module
474 (resolve-module '(ice-9 debugger commands)))
475
476
477;;;; {Module Browsing}
478
479(define (loaded-module-source module-name)
480 ;; Return the file name that (ice-9 boot-9) probably loaded the
481 ;; named module from. (The `probably' is because `%load-path' might
482 ;; have changed since the module was loaded.)
483 (let* ((reverse-name (reverse module-name))
484 (name (symbol->string (car reverse-name)))
485 (dir-hint-module-name (reverse (cdr reverse-name)))
486 (dir-hint (apply string-append
487 (map (lambda (elt)
488 (string-append (symbol->string elt) "/"))
489 dir-hint-module-name))))
490 (%search-load-path (in-vicinity dir-hint name))))
491
492(define (loaded-modules)
493 ;; Return list of all loaded modules sorted by name.
494 (sort (apropos-fold-all (lambda (module acc) (cons module acc)) '())
495 (lambda (m1 m2)
496 (symlist<? (module-name m1) (module-name m2)))))
497
498(define (symlist<? l1 l2)
499 ;; Return #t if symbol list L1 is alphabetically less than L2.
500 (cond ((null? l1) #t)
501 ((null? l2) #f)
502 ((eq? (car l1) (car l2)) (symlist<? (cdr l1) (cdr l2)))
503 (else (string<? (symbol->string (car l1)) (symbol->string (car l2))))))
504
505
506;;;; {Source Breakpoint Installation}
507
7dd3f110
NJ
508(define (install-breakpoints x bpinfo)
509 (define (install-recursive x)
510 (if (list? x)
511 (begin
512 ;; Check source properties of x itself.
513 (let* ((infokey (cons (source-property x 'line)
514 (source-property x 'column)))
515 (bpentry (assoc infokey bpinfo)))
516 (if bpentry
517 (let ((bp (set-breakpoint! debug-here x x)))
518 ;; FIXME: Here should transfer properties from the
519 ;; old breakpoint with index (cdr bpentry) to the
520 ;; new breakpoint. (Or else provide an alternative
521 ;; to set-breakpoint! that reuses the same
522 ;; breakpoint.)
523 (write-form (list 'breakpoint-set
524 (source-property x 'filename)
525 (car infokey)
526 (cdr infokey)
527 (bp-number bp))))))
528 ;; Check each of x's elements.
529 (for-each install-recursive x))))
530 (install-recursive x))
531
a6ab1deb
NJ
532
533;;;; {Evaluation}
534
535;; Evaluation threads are unleashed by two possible triggers. One is
536;; a boolean variable, specific to each thread, that tells the thread
537;; to exit when set to #t. The other is another boolean variable, but
538;; global, indicating that there is an evaluation to perform:
539(define eval-work-available #f)
540
541;; This variable, which is only valid when `eval-work-available' is
542;; #t, holds the evaluation to perform:
543(define eval-work #f)
544
545;; A mutex protects against concurrent access to these variables.
546(define eval-work-mutex (make-mutex))
547
548;; Changes in these variables are signaled by broadcasting the
549;; following condition variable.
550(define eval-work-changed (make-condition-variable))
551
552;; When an evaluation thread takes some work, it tells the main GDS
553;; thread by signaling this condition variable.
554(define eval-work-taken (make-condition-variable))
555
556(define-macro (without-mutex m . body)
557 `(dynamic-wind
558 (lambda () (unlock-mutex ,m))
559 (lambda () (begin ,@body))
560 (lambda () (lock-mutex ,m))))
561
562(define next-thread-number
563 (let ((count 0))
564 (lambda ()
565 (set! count (+ count 1))
566 count)))
567
568(define (eval-thread depth thread-should-exit-thunk)
569 ;; Acquire mutex to check trigger variables.
570 (with-mutex eval-work-mutex
571 (let ((thread-number (next-thread-number)))
572 (trc 'eval-thread depth thread-number "entering loop")
573 (let loop ()
574 (cond ((thread-should-exit-thunk)
575 ;; Allow thread to exit.
576 )
577
578 (eval-work-available
579 ;; Take a local copy of the work, reset global
580 ;; variables, then do the work with mutex released.
581 (trc 'eval-thread depth thread-number "starting work")
582 (let ((work eval-work)
583 (subthread-needed? #t))
584 (set! eval-work-available #f)
585 (signal-condition-variable eval-work-taken)
586 (without-mutex eval-work-mutex
587 ;; Before starting evaluation, create another eval
588 ;; thread like this one, so that it can take over
589 ;; if another evaluation is requested before this
590 ;; one is finished.
591 (make-thread eval-thread (+ depth 1)
592 (lambda () (not subthread-needed?)))
593 ;; Do the evaluation(s).
594 (let loop2 ((correlator (car work))
595 (m (cadr work))
596 (exprs (cddr work))
597 (results '()))
598 (if (null? exprs)
599 (write-form `(eval-results ,correlator ,@results))
600 (loop2 correlator
601 m
602 (cdr exprs)
603 (append results (gds-eval (car exprs) m))))))
604 (trc 'eval-thread depth thread-number "work done")
605 ;; Tell the subthread that it should now exit.
606 (set! subthread-needed? #f)
607 (broadcast-condition-variable eval-work-changed)
608 ;; Loop for more work for this thread.
609 (loop)))
610
611 (else
612 ;; Wait for something to change, then loop to check
613 ;; trigger variables again.
614 (trc 'eval-thread depth thread-number "wait")
615 (wait-condition-variable eval-work-changed eval-work-mutex)
616 (trc 'eval-thread depth thread-number "wait done")
617 (loop))))
618 (trc 'eval-thread depth thread-number "exiting"))))
619
620(define (gds-eval x m)
32ac6ed1
NJ
621 ;; Consumer to accept possibly multiple values and present them for
622 ;; Emacs as a list of strings.
623 (define (value-consumer . values)
624 (if (unspecified? (car values))
625 '()
626 (map (lambda (value)
627 (with-output-to-string (lambda () (write value))))
628 values)))
7dd3f110 629 ;; Now do evaluation.
32ac6ed1
NJ
630 (let ((value #f))
631 (let* ((do-eval (if m
632 (lambda ()
633 (display "Evaluating in module ")
634 (write (module-name m))
635 (newline)
636 (set! value
637 (call-with-values (lambda ()
638 (eval x m))
639 value-consumer)))
640 (lambda ()
641 (display "Evaluating in current module ")
642 (write (module-name (current-module)))
643 (newline)
644 (set! value
645 (call-with-values (lambda ()
646 (primitive-eval x))
647 value-consumer)))))
648 (output
649 (with-output-to-string
650 (lambda ()
651 (catch #t
652 do-eval
653 (lambda (key . args)
654 (case key
655 ((misc-error signal unbound-variable
656 numerical-overflow)
657 (apply display-error #f
658 (current-output-port) args)
659 (set! value '("error-in-evaluation")))
660 (else
661 (display "EXCEPTION: ")
662 (display key)
663 (display " ")
664 (write args)
665 (newline)
666 (set! value
667 '("unhandled-exception-in-evaluation"))))))))))
668 (list output value))))
669
32ac6ed1
NJ
670
671;;; (emacs gds-client) ends here.