--- /dev/null
+(define-module (ice-9 gds-client)
+ #:use-module (oop goops)
+ #:use-module (oop goops describe)
+ #:use-module (ice-9 debugging trace)
+ #:use-module (ice-9 debugging traps)
+ #:use-module (ice-9 debugging trc)
+ #:use-module (ice-9 debugging steps)
+ #:use-module (ice-9 pretty-print)
+ #:use-module (ice-9 regex)
+ #:use-module (ice-9 session)
+ #:use-module (ice-9 string-fun)
+ #:export (gds-debug-trap
+ run-utility
+ gds-accept-input))
+
+(cond ((string>=? (version) "1.7")
+ (use-modules (ice-9 debugger utils)))
+ (else
+ (define the-ice-9-debugger-module (resolve-module '(ice-9 debugger)))
+ (module-export! the-ice-9-debugger-module
+ '(source-position
+ write-frame-short/application
+ write-frame-short/expression
+ write-frame-args-long
+ write-frame-long))))
+
+(use-modules (ice-9 debugger))
+
+(define gds-port #f)
+
+;; Return an integer that somehow identifies the current thread.
+(define (get-thread-id)
+ (let ((root (dynamic-root)))
+ (cond ((integer? root)
+ root)
+ ((pair? root)
+ (object-address root))
+ (else
+ (error "Unexpected dynamic root:" root)))))
+
+;; gds-debug-read is a high-priority read. The (debug-thread-id ID)
+;; form causes the frontend to dismiss any reads from threads whose id
+;; is not ID, until it receives the (thread-id ...) form with the same
+;; id as ID. Dismissing the reads of any other threads (by sending a
+;; form that is otherwise ignored) causes those threads to release the
+;; read mutex, which allows the (gds-read) here to proceed.
+(define (gds-debug-read)
+ (write-form `(debug-thread-id ,(get-thread-id)))
+ (gds-read))
+
+(define (gds-debug-trap trap-context)
+ "Invoke the GDS debugger to explore the stack at the specified trap."
+ (connect-to-gds)
+ (start-stack 'debugger
+ (let* ((stack (tc:stack trap-context))
+ (flags1 (let ((trap-type (tc:type trap-context)))
+ (case trap-type
+ ((#:return #:error)
+ (list trap-type
+ (tc:return-value trap-context)))
+ (else
+ (list trap-type)))))
+ (flags (if (tc:continuation trap-context)
+ (cons #:continuable flags1)
+ flags1))
+ (fired-traps (tc:fired-traps trap-context))
+ (special-index (and (= (length fired-traps) 1)
+ (is-a? (car fired-traps) <exit-trap>)
+ (eq? (tc:type trap-context) #:return)
+ (- (tc:depth trap-context)
+ (slot-ref (car fired-traps) 'depth)))))
+ ;; Write current stack to the frontend.
+ (write-form (list 'stack
+ (if (and special-index (> special-index 0))
+ special-index
+ 0)
+ (stack->emacs-readable stack)
+ (append (flags->emacs-readable flags)
+ (slot-ref trap-context
+ 'handler-return-syms))))
+ ;; Now wait for instruction.
+ (let loop ((protocol (gds-debug-read)))
+ ;; Act on it.
+ (case (car protocol)
+ ((tweak)
+ ;; Request to tweak the handler return value.
+ (let ((tweaking (catch #t
+ (lambda ()
+ (list (with-input-from-string
+ (cadr protocol)
+ read)))
+ (lambda ignored #f))))
+ (if tweaking
+ (slot-set! trap-context
+ 'handler-return-value
+ (cons 'instead (car tweaking)))))
+ (loop (gds-debug-read)))
+ ((continue)
+ ;; Continue (by exiting the debugger).
+ *unspecified*)
+ ((evaluate)
+ ;; Evaluate expression in specified frame.
+ (eval-in-frame stack (cadr protocol) (caddr protocol))
+ (loop (gds-debug-read)))
+ ((info-frame)
+ ;; Return frame info.
+ (let ((frame (stack-ref stack (cadr protocol))))
+ (write-form (list 'info-result
+ (with-output-to-string
+ (lambda ()
+ (write-frame-long frame))))))
+ (loop (gds-debug-read)))
+ ((info-args)
+ ;; Return frame args.
+ (let ((frame (stack-ref stack (cadr protocol))))
+ (write-form (list 'info-result
+ (with-output-to-string
+ (lambda ()
+ (write-frame-args-long frame))))))
+ (loop (gds-debug-read)))
+ ((proc-source)
+ ;; Show source of application procedure.
+ (let* ((frame (stack-ref stack (cadr protocol)))
+ (proc (frame-procedure frame))
+ (source (and proc (procedure-source proc))))
+ (write-form (list 'info-result
+ (if source
+ (sans-surrounding-whitespace
+ (with-output-to-string
+ (lambda ()
+ (pretty-print source))))
+ (if proc
+ "This procedure is coded in C"
+ "This frame has no procedure")))))
+ (loop (gds-debug-read)))
+ ((traps-here)
+ ;; Show the traps that fired here.
+ (write-form (list 'info-result
+ (with-output-to-string
+ (lambda ()
+ (for-each describe
+ (tc:fired-traps trap-context))))))
+ (loop (gds-debug-read)))
+ ((step-into)
+ ;; Set temporary breakpoint on next trap.
+ (at-step gds-debug-trap
+ 1
+ #f
+ (if (memq #:return flags)
+ #f
+ (- (stack-length stack)
+ (cadr protocol)))))
+ ((step-over)
+ ;; Set temporary breakpoint on exit from
+ ;; specified frame.
+ (at-exit (- (stack-length stack) (cadr protocol))
+ gds-debug-trap))
+ ((step-file)
+ ;; Set temporary breakpoint on next trap in same
+ ;; source file.
+ (at-step gds-debug-trap
+ 1
+ (frame-file-name (stack-ref stack
+ (cadr protocol)))
+ (if (memq #:return flags)
+ #f
+ (- (stack-length stack)
+ (cadr protocol)))))
+ (else
+ (safely-handle-nondebug-protocol protocol)
+ (loop (gds-debug-read))))))))
+
+(define (connect-to-gds . application-name)
+ (or gds-port
+ (begin
+ (set! gds-port
+ (or (let ((s (socket PF_INET SOCK_STREAM 0))
+ (SOL_TCP 6)
+ (TCP_NODELAY 1))
+ (setsockopt s SOL_TCP TCP_NODELAY 1)
+ (catch #t
+ (lambda ()
+ (connect s AF_INET (inet-aton "127.0.0.1") 8333)
+ s)
+ (lambda _ #f)))
+ (let ((s (socket PF_UNIX SOCK_STREAM 0)))
+ (catch #t
+ (lambda ()
+ (connect s AF_UNIX "/tmp/.gds_socket")
+ s)
+ (lambda _ #f)))
+ (error "Couldn't connect to GDS by TCP or Unix domain socket")))
+ (write-form (list 'name (getpid) (apply client-name application-name))))))
+
+(define (client-name . application-name)
+ (let loop ((args (append application-name (program-arguments))))
+ (if (null? args)
+ (format #f "PID ~A" (getpid))
+ (let ((arg (car args)))
+ (cond ((string-match "^(.*[/\\])?guile(\\..*)?$" arg)
+ (loop (cdr args)))
+ ((string-match "^-" arg)
+ (loop (cdr args)))
+ (else
+ (format #f "~A (PID ~A)" arg (getpid))))))))
+
+(if (not (defined? 'make-mutex))
+ (begin
+ (define (make-mutex) #f)
+ (define lock-mutex noop)
+ (define unlock-mutex noop)))
+
+(define write-mutex (make-mutex))
+
+(define (write-form form)
+ ;; Write any form FORM to GDS.
+ (lock-mutex write-mutex)
+ (write form gds-port)
+ (newline gds-port)
+ (force-output gds-port)
+ (unlock-mutex write-mutex))
+
+(define (stack->emacs-readable stack)
+ ;; Return Emacs-readable representation of STACK.
+ (map (lambda (index)
+ (frame->emacs-readable (stack-ref stack index)))
+ (iota (min (stack-length stack)
+ (cadr (memq 'depth (debug-options)))))))
+
+(define (frame->emacs-readable frame)
+ ;; Return Emacs-readable representation of FRAME.
+ (if (frame-procedure? frame)
+ (list 'application
+ (with-output-to-string
+ (lambda ()
+ (display (if (frame-real? frame) " " "t "))
+ (write-frame-short/application frame)))
+ (source->emacs-readable frame))
+ (list 'evaluation
+ (with-output-to-string
+ (lambda ()
+ (display (if (frame-real? frame) " " "t "))
+ (write-frame-short/expression frame)))
+ (source->emacs-readable frame))))
+
+(define (source->emacs-readable frame)
+ ;; Return Emacs-readable representation of the filename, line and
+ ;; column source properties of SOURCE.
+ (or (frame->source-position frame) 'nil))
+
+(define (flags->emacs-readable flags)
+ ;; Return Emacs-readable representation of trap FLAGS.
+ (let ((prev #f))
+ (map (lambda (flag)
+ (let ((erf (if (and (keyword? flag)
+ (not (eq? prev #:return)))
+ (keyword->symbol flag)
+ (format #f "~S" flag))))
+ (set! prev flag)
+ erf))
+ flags)))
+
+(define (eval-in-frame stack index expr)
+ (write-form
+ (list 'eval-result
+ (format #f "~S"
+ (catch #t
+ (lambda ()
+ (local-eval (with-input-from-string expr read)
+ (memoized-environment
+ (frame-source (stack-ref stack
+ index)))))
+ (lambda args
+ (cons 'ERROR args)))))))
+
+(set! (behaviour-ordering gds-debug-trap) 100)
+
+;;; Code below here adds support for interaction between the GDS
+;;; client program and the Emacs frontend even when not stopped in the
+;;; debugger.
+
+;; A mutex to control attempts by multiple threads to read protocol
+;; back from the frontend.
+(define gds-read-mutex (make-mutex))
+
+;; Read a protocol instruction from the frontend.
+(define (gds-read)
+ ;; Acquire the read mutex.
+ (lock-mutex gds-read-mutex)
+ ;; Tell the front end something that identifies us as a thread.
+ (write-form `(thread-id ,(get-thread-id)))
+ ;; Now read, then release the mutex and return what was read.
+ (let ((x (catch #t
+ (lambda () (read gds-port))
+ (lambda ignored the-eof-object))))
+ (unlock-mutex gds-read-mutex)
+ x))
+
+(define (gds-accept-input exit-on-continue)
+ ;; If reading from the GDS connection returns EOF, we will throw to
+ ;; this catch.
+ (catch 'server-eof
+ (lambda ()
+ (let loop ((protocol (gds-read)))
+ (if (or (eof-object? protocol)
+ (and exit-on-continue
+ (eq? (car protocol) 'continue)))
+ (throw 'server-eof))
+ (safely-handle-nondebug-protocol protocol)
+ (loop (gds-read))))
+ (lambda ignored #f)))
+
+(define (safely-handle-nondebug-protocol protocol)
+ ;; This catch covers any internal errors in the GDS code or
+ ;; protocol.
+ (catch #t
+ (lambda ()
+ (lazy-catch #t
+ (lambda ()
+ (handle-nondebug-protocol protocol))
+ save-lazy-trap-context-and-rethrow))
+ (lambda (key . args)
+ (write-form
+ `(eval-results (error . ,(format #f "~s" protocol))
+ ,(if last-lazy-trap-context 't 'nil)
+ "GDS Internal Error
+Please report this to <neil@ossau.uklinux.net>, ideally including:
+- a description of the scenario in which this error occurred
+- which versions of Guile and guile-debugging you are using
+- the error stack, which you can get by clicking on the link below,
+ and then cut and paste into your report.
+Thanks!\n\n"
+ ,(list (with-output-to-string
+ (lambda ()
+ (write key)
+ (display ": ")
+ (write args)
+ (newline)))))))))
+
+;; The key that is used to signal a read error changes from 1.6 to
+;; 1.8; here we cover all eventualities by discovering the key
+;; dynamically.
+(define read-error-key
+ (catch #t
+ (lambda ()
+ (with-input-from-string "(+ 3 4" read))
+ (lambda (key . args)
+ key)))
+
+(define (handle-nondebug-protocol protocol)
+ (case (car protocol)
+
+ ((eval)
+ (set! last-lazy-trap-context #f)
+ (apply (lambda (correlator module port-name line column code flags)
+ (with-input-from-string code
+ (lambda ()
+ (set-port-filename! (current-input-port) port-name)
+ (set-port-line! (current-input-port) line)
+ (set-port-column! (current-input-port) column)
+ (let ((m (and module (resolve-module-from-root module))))
+ (catch read-error-key
+ (lambda ()
+ (let loop ((exprs '()) (x (read)))
+ (if (eof-object? x)
+ ;; Expressions to be evaluated have all
+ ;; been read. Now evaluate them.
+ (let loop2 ((exprs (reverse! exprs))
+ (results '())
+ (n 1))
+ (if (null? exprs)
+ (write-form `(eval-results ,correlator
+ ,(if last-lazy-trap-context 't 'nil)
+ ,@results))
+ (loop2 (cdr exprs)
+ (append results (gds-eval (car exprs) m
+ (if (and (null? (cdr exprs))
+ (= n 1))
+ #f n)))
+ (+ n 1))))
+ ;; Another complete expression read; add
+ ;; it to the list.
+ (begin
+ (if (and (pair? x)
+ (memq 'debug flags))
+ (install-trap (make <source-trap>
+ #:expression x
+ #:behaviour gds-debug-trap)))
+ (loop (cons x exprs) (read))))))
+ (lambda (key . args)
+ (write-form `(eval-results
+ ,correlator
+ ,(if last-lazy-trap-context 't 'nil)
+ ,(with-output-to-string
+ (lambda ()
+ (display ";;; Reading expressions")
+ (display " to evaluate\n")
+ (apply display-error #f
+ (current-output-port) args)))
+ ("error-in-read")))))))))
+ (cdr protocol)))
+
+ ((complete)
+ (let ((matches (apropos-internal
+ (string-append "^" (regexp-quote (cadr protocol))))))
+ (cond ((null? matches)
+ (write-form '(completion-result nil)))
+ (else
+ ;;(write matches (current-error-port))
+ ;;(newline (current-error-port))
+ (let ((match
+ (let loop ((match (symbol->string (car matches)))
+ (matches (cdr matches)))
+ ;;(write match (current-error-port))
+ ;;(newline (current-error-port))
+ ;;(write matches (current-error-port))
+ ;;(newline (current-error-port))
+ (if (null? matches)
+ match
+ (if (string-prefix=? match
+ (symbol->string (car matches)))
+ (loop match (cdr matches))
+ (loop (substring match 0
+ (- (string-length match) 1))
+ matches))))))
+ (if (string=? match (cadr protocol))
+ (write-form `(completion-result
+ ,(map symbol->string matches)))
+ (write-form `(completion-result
+ ,match))))))))
+
+ ((debug-lazy-trap-context)
+ (if last-lazy-trap-context
+ (gds-debug-trap last-lazy-trap-context)
+ (error "There is no stack available to show")))
+
+ (else
+ (error "Unexpected protocol:" protocol))))
+
+(define (resolve-module-from-root name)
+ (save-module-excursion
+ (lambda ()
+ (set-current-module the-root-module)
+ (resolve-module name))))
+
+(define (gds-eval x m part)
+ ;; Consumer to accept possibly multiple values and present them for
+ ;; Emacs as a list of strings.
+ (define (value-consumer . values)
+ (if (unspecified? (car values))
+ '()
+ (map (lambda (value)
+ (with-output-to-string (lambda () (write value))))
+ values)))
+ ;; Now do evaluation.
+ (let ((intro (if part
+ (format #f ";;; Evaluating expression ~A" part)
+ ";;; Evaluating"))
+ (value #f))
+ (let* ((do-eval (if m
+ (lambda ()
+ (display intro)
+ (display " in module ")
+ (write (module-name m))
+ (newline)
+ (set! value
+ (call-with-values (lambda ()
+ (start-stack 'gds-eval-stack
+ (eval x m)))
+ value-consumer)))
+ (lambda ()
+ (display intro)
+ (display " in current module ")
+ (write (module-name (current-module)))
+ (newline)
+ (set! value
+ (call-with-values (lambda ()
+ (start-stack 'gds-eval-stack
+ (primitive-eval x)))
+ value-consumer)))))
+ (output
+ (with-output-to-string
+ (lambda ()
+ (catch #t
+ (lambda ()
+ (lazy-catch #t
+ do-eval
+ save-lazy-trap-context-and-rethrow))
+ (lambda (key . args)
+ (case key
+ ((misc-error signal unbound-variable numerical-overflow)
+ (apply display-error #f
+ (current-output-port) args)
+ (set! value '("error-in-evaluation")))
+ (else
+ (display "EXCEPTION: ")
+ (display key)
+ (display " ")
+ (write args)
+ (newline)
+ (set! value
+ '("unhandled-exception-in-evaluation"))))))))))
+ (list output value))))
+
+(define last-lazy-trap-context #f)
+
+(define (save-lazy-trap-context-and-rethrow key . args)
+ (set! last-lazy-trap-context
+ (throw->trap-context key args save-lazy-trap-context-and-rethrow))
+ (apply throw key args))
+
+(define (run-utility)
+ (connect-to-gds)
+ (write (getpid))
+ (newline)
+ (force-output)
+ (named-module-use! '(guile-user) '(ice-9 session))
+ (gds-accept-input #f))
+
+(define-method (trap-description (trap <trap>))
+ (let loop ((description (list (class-name (class-of trap))))
+ (next 'installed?))
+ (case next
+ ((installed?)
+ (loop (if (slot-ref trap 'installed)
+ (cons 'installed description)
+ description)
+ 'conditional?))
+ ((conditional?)
+ (loop (if (slot-ref trap 'condition)
+ (cons 'conditional description)
+ description)
+ 'skip-count))
+ ((skip-count)
+ (loop (let ((skip-count (slot-ref trap 'skip-count)))
+ (if (zero? skip-count)
+ description
+ (cons* skip-count 'skip-count description)))
+ 'single-shot?))
+ ((single-shot?)
+ (loop (if (slot-ref trap 'single-shot)
+ (cons 'single-shot description)
+ description)
+ 'done))
+ (else
+ (reverse! description)))))
+
+(define-method (trap-description (trap <procedure-trap>))
+ (let ((description (next-method)))
+ (set-cdr! description
+ (cons (procedure-name (slot-ref trap 'procedure))
+ (cdr description)))
+ description))
+
+(define-method (trap-description (trap <source-trap>))
+ (let ((description (next-method)))
+ (set-cdr! description
+ (cons (format #f "~s" (slot-ref trap 'expression))
+ (cdr description)))
+ description))
+
+(define-method (trap-description (trap <location-trap>))
+ (let ((description (next-method)))
+ (set-cdr! description
+ (cons* (slot-ref trap 'file-regexp)
+ (slot-ref trap 'line)
+ (slot-ref trap 'column)
+ (cdr description)))
+ description))
+
+(define (gds-trace-trap trap-context)
+ (connect-to-gds)
+ (gds-do-trace trap-context)
+ (at-exit (tc:depth trap-context) gds-do-trace))
+
+(define (gds-do-trace trap-context)
+ (write-form (list 'trace
+ (format #f
+ "~3@a: ~a"
+ (trace/stack-real-depth trap-context)
+ (trace/info trap-context)))))
+
+(define (gds-trace-subtree trap-context)
+ (connect-to-gds)
+ (gds-do-trace trap-context)
+ (let ((step-trap (make <step-trap> #:behaviour gds-do-trace)))
+ (install-trap step-trap)
+ (at-exit (tc:depth trap-context)
+ (lambda (trap-context)
+ (uninstall-trap step-trap)))))
+
+;;; (ice-9 gds-client) ends here.