scsh-0.5/env/inspect.scm

402 lines
12 KiB
Scheme

; Copyright (c) 1993, 1994 Richard Kelsey and Jonathan Rees. See file COPYING.
; A dirty little inspector.
; This breaks abstractions left and right.
; Look and feel shamelessly plagiarized from the Lucid Lisp inspector.
; Eventually, integrate this better with the command processor.
; Inspector state:
; thing = (focus-object)
; menu = (prepare-menu thing)
; start = position within menu; modified by M (more) command
; stack = list of other things
(define *menu-limit* 15)
(define *write-depth* 3)
(define *write-length* 5)
(define (with-limited-output thunk)
(let-fluids $write-depth *write-depth*
$write-length *write-length*
thunk))
(define (make-inspector-state menu position stack)
(cons (cons position menu) stack))
(define $inspector-state (make-fluid (make-inspector-state '() 0 '())))
(define-command-syntax 'inspect "[<exp>]" "invoke the inspector"
'(&opt form))
(define-command-syntax 'debug "" "inspect the current continuation" '())
(define (debug)
(showing-focus-object
(lambda ()
(set-focus-object! (command-continuation))))
(inspect))
(define (inspect . maybe-exp)
(if (not (null? maybe-exp))
(with-limited-output
(lambda ()
(showing-focus-object
(lambda ()
(evaluate-and-select (car maybe-exp)
(environment-for-commands)))))))
(let-fluid $inspector-state
(make-inspector-state (prepare-menu (focus-object)) 0 '())
(lambda ()
(present-menu)
(let loop ()
(let ((command (read-command-carefully "inspect: "
#f ;command preferred
(command-input)
inspector-commands)))
(cond ((eof-object? command)
(newline (command-output))
(unspecific))
((not command) ; read command error
(loop))
(else
(with-limited-output
(lambda ()
(execute-inspector-command command)))
(loop))))))))
(define (present-menu)
(let ((pos+menu (car (fluid $inspector-state))))
(display-menu (cdr pos+menu)
(car pos+menu)
(command-output))))
(define (new-selection thing stack)
(set-fluid! $inspector-state
(make-inspector-state (prepare-menu thing) 0 stack)))
(define (read-selection-command port)
(let ((x (read port)))
(if (or (integer? x)
(memq x '(u d t)))
x
(read-command-error port "invalid selection command" x))))
(define selection-command-syntax (list '&rest read-selection-command))
(define (inspector-commands name)
(if (integer? name)
selection-command-syntax
(case name
((? m q) '()) ; no arguments
((u d t) selection-command-syntax)
(else #f))))
(define (execute-inspector-command command)
(let ((result-before (focus-object))
(state-before (fluid $inspector-state)))
(showing-focus-object
(lambda ()
(let ((name (car command)))
(if (integer? name)
(execute-selection-command command)
(case name
((u d t)
(execute-selection-command command))
((m) (inspect-more))
((q) (abort-to-command-level (car (fluid $command-levels))))
((?) (inspect-help))
(else (execute-command command)))))))
(let ((result-after (focus-object))
(state-after (fluid $inspector-state)))
;; Prepare & display a new menu if we're looking at
;; a new thing. Push old thing on stack only if
;; no one's been futzing with the stack.
(if (not (eq? result-after result-before))
(begin (if (eq? state-after state-before)
(new-selection result-after
(cons result-before
(cdr state-before))))
(present-menu))))))
(define (execute-selection-command command)
(if (not (null? command))
(let ((name (car command)))
(if (integer? name)
(let ((menu (cdar (fluid $inspector-state))))
(if (and (>= name 0)
(< name (length menu)))
(move-to-object! (menu-ref menu name))
(write-line "Invalid choice." (command-output))))
(case name
((u) (pop-inspector-stack))
((d) (inspect-next-continuation))
((t) (select-template))
(else (error "bad selection command" name))))
(execute-selection-command (cdr command)))))
(define (move-to-object! object)
(new-selection object
(cons (focus-object)
(cdr (fluid $inspector-state))))
(set-focus-object! object))
(define (pop-inspector-stack)
(let ((stack (cdr (fluid $inspector-state))))
(if (pair? stack)
(begin (new-selection (car stack) (cdr stack))
(set-focus-object! (car stack)))
(write-line "Can't go up from here." (command-output)))))
(define (inspect-next-continuation)
(if (continuation? (focus-object))
(move-to-object! (continuation-parent (focus-object)))
(write-line "Can't go down from a non-continuation." (command-output))))
(define (inspect-more)
(let* ((state (fluid $inspector-state))
(pos+menu (car state))
(menu (cdr pos+menu))
(position (car pos+menu)))
(if (> (length menu) (+ *menu-limit* position))
(let ((position (- (+ position *menu-limit*) 1)))
(set-car! pos+menu position)
(present-menu))
(write-line "There is no more." (command-output)))))
(define (select-template)
(move-to-object! (coerce-to-template (focus-object))))
(define (inspect-help)
(let ((o-port (command-output)))
(for-each (lambda (s) (display s o-port) (newline o-port))
'("q quit"
"u up stack (= go to previous object)"
"d down stack"
"t template"
"<form> evaluate a form (## is current object)"
"<integer> menu item"
"or any command processor command"
"multiple u d t <integer> commands can be put on one line"))))
(define (menu-ref menu n)
(cadr (list-ref menu n)))
; Menus.
(define (prepare-menu thing)
(cond ((list? thing)
(map (lambda (x) (list #f x))
thing))
((pair? thing)
`((car ,(car thing)) (cdr ,(cdr thing))))
((vector? thing)
(prepare-menu (vector->list thing)))
((closure? thing)
(prepare-environment-menu
(closure-env thing)
(get-shape (template-debug-data (closure-template thing))
0)))
((template? thing)
(prepare-menu (template->list thing)))
((continuation? thing)
(prepare-continuation-menu thing))
((record? thing)
(prepare-record-menu thing))
((location? thing)
`((id ,(location-id thing))
(contents ,(contents thing))))
((weak-pointer? thing)
`((ref ,(weak-pointer-ref thing))))
(else '())))
(define (template->list template)
(do ((i (- (template-length template) 1) (- i 1))
(r '() (cons (template-ref template i) r)))
((< i 0) r)))
(define (prepare-continuation-menu thing)
(let ((dd (continuation-debug-data thing))
(next (continuation-parent thing)))
`(,@(let recur ((c thing))
(if (eq? c next)
'()
(let ((z (continuation-arg-count c)))
(do ((i (- z 1) (- i 1))
(l (recur (continuation-cont c))
(cons (list #f (continuation-arg c i))
l)))
((< i 0) l)))))
,@(prepare-environment-menu (continuation-env thing)
(get-shape dd (continuation-pc thing))))))
(define (continuation-debug-data thing)
(template-debug-data (continuation-template thing)))
(define (prepare-record-menu thing)
(let ((rt (record-type thing))
(z (record-length thing)))
(if (record-type? rt)
(do ((i (- z 1) (- i 1))
(f (reverse (record-type-field-names rt)) (cdr f))
(l '() (cons (list (car f) (record-ref thing i)) l)))
((< i 1) l))
(do ((i (- z 1) (- i 1))
(l '() (cons (list #f (record-ref thing i)) l)))
((< i 0) l)))))
(define (prepare-environment-menu env shape)
(if (vector? env)
(let ((values (rib-values env)))
(if (pair? shape)
(append (map list (car shape) values)
(prepare-environment-menu (vector-ref env 0)
(cdr shape)))
(append (map (lambda (x) (list #f x)) values)
(prepare-environment-menu (vector-ref env 0) shape))))
'()))
(define (rib-values env)
(let ((z (vector-length env)))
(do ((i 1 (+ i 1))
(l '() (cons (if (vector-unassigned? env i)
'unassigned
(vector-ref env i))
l)))
((>= i z) l))))
; Returns a list of proper lists describing the environment in effect
; at the given pc with the given template's code vector.
;
; Entries in the environment-maps table (one per template) have the form
; #(parent-uid pc-in-parent (env-map ...))
;
; Each environment map (one per let or lambda-expression) has the form
; #(pc-before pc-after (var ...) (env-map ...))
;
; Cf. procedure (note-environment vars segment) in comp.scm.
(define (get-shape dd pc)
(if dd
(let loop ((emaps (debug-data-env-maps dd))
(shape (get-shape (get-debug-data
(debug-data-parent dd))
(debug-data-pc-in-parent dd))))
(if (null? emaps)
shape
(let ((pc-before (vector-ref (car emaps) 0))
(pc-after (vector-ref (car emaps) 1))
(vars (vector-ref (car emaps) 2))
(more-maps (vector-ref (car emaps) 3)))
(if (and (>= pc pc-before)
(< pc pc-after))
(loop more-maps
(cons (vector->list vars) shape))
(loop (cdr emaps) shape)))))
'()))
; Information display
(define (display-menu menu start port)
(newline port)
(let ((thing (focus-object)))
(if (continuation? thing)
(let ((dd (continuation-debug-data thing)))
(if dd
(let ((source (assoc (continuation-pc thing)
(debug-data-source dd))))
(if source
(display-source-info (cdr source))))))))
(let ((menu (list-tail menu start))
(limit (+ start *menu-limit*)))
(let loop ((i start) (menu menu))
(with-limited-output
(lambda ()
(cond ((null? menu))
((and (>= i limit)
(not (null? (cdr menu))))
(display " [m] more..." port) (newline port))
(else
(let ((item (car menu)))
(display " [" port)
(write i port)
(if (car item)
(begin (display ": " port)
(write-carefully (car item) port)))
(display "] " port)
(write-carefully
(value->expression (cadr item))
port)
(newline port)
(loop (+ i 1) (cdr menu))))))))))
(define (display-source-info info)
(if (pair? info)
(let ((o-port (command-output))
(i (car info))
(exp (cdr info)))
(if (and (integer? i) (list? exp))
(begin
(display "Waiting for " o-port)
(limited-write (list-ref exp i) o-port
*write-depth* *write-length*)
(newline o-port)
(display " in " o-port)
(limited-write (append (sublist exp 0 i)
(list '^^^)
(list-tail exp (+ i 1)))
o-port
*write-depth* *write-length*)
(newline o-port))))))
(define (where-defined thing)
(let loop ((dd (template-debug-data (closure-template thing))))
(if (debug-data? dd)
(if (string? (debug-data-name dd))
(debug-data-name dd)
(loop (debug-data-parent dd)))
#f)))
(define-command-syntax 'where "[<procedure>]"
"show procedure's source file name"
'(&opt expression))
(define (where . maybe-exp)
(let ((proc (if (null? maybe-exp)
(focus-object)
(evaluate (car maybe-exp) (environment-for-commands))))
(port (command-output)))
(if (procedure? proc)
(let ((probe (where-defined proc)))
(if probe
(display probe port)
(display "Source file not recorded" port)))
(display "Not a procedure" port))
(newline port)))
(define (coerce-to-template obj) ;utility for various commands
(cond ((template? obj) obj)
((closure? obj) (closure-template obj))
((continuation? obj) (continuation-template obj))
(else (error "expected a procedure or continuation" obj))))