]> git.donarmstrong.com Git - lilypond.git/blobdiff - guile18/ice-9/gds-client.scm
New upstream version 2.19.65
[lilypond.git] / guile18 / ice-9 / gds-client.scm
diff --git a/guile18/ice-9/gds-client.scm b/guile18/ice-9/gds-client.scm
new file mode 100755 (executable)
index 0000000..960015a
--- /dev/null
@@ -0,0 +1,592 @@
+(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.