scsh-0.5/env/command.scm

584 lines
18 KiB
Scheme
Raw Permalink Normal View History

1995-10-13 23:34:21 -04:00
; Copyright (c) 1993, 1994 Richard Kelsey and Jonathan Rees. See file COPYING.
; This is file command.scm.
; Command processor.
; The command processor's state is of three kinds:
; 1. User context - preserved across dump commands.
; This includes the designated user and configuration environments.
; 2. Session state - one per "login"; not preserved across dump commands.
; This includes ## and the command loop's interactive ports.
; 3. Command levels - one for each different command level.
; This includes any condition being handled, and continuations.
(define command-prefix #\,)
; User context.
(define (make-user-context thunk)
(let ((t (make-table)))
(let-fluid $user-context t
(lambda ()
(for-each (lambda (name+thunk)
(table-set! t (car name+thunk) ((cdr name+thunk))))
*user-context-initializers*)
(thunk)
t))))
(define *user-context-initializers* '())
(define $user-context (make-fluid #f))
; (set-fluid! $user-context (make-user-context unspecific)) ;Bad for GC
(define (user-context) (fluid $user-context))
(define (user-context-accessor name initializer)
(set! *user-context-initializers*
(append *user-context-initializers*
(list (cons name initializer))))
(let ((probe (fluid $user-context)))
(if probe (table-set! probe name (initializer))))
(lambda ()
(table-ref (user-context) name)))
(define (user-context-modifier name)
(lambda (new)
(table-set! (user-context) name new)))
; Session state.
(define session-type
(make-record-type 'session '(input output values batch-mode? bow?)))
(define make-session
(record-constructor session-type
'(input output values batch-mode? bow?)))
(define $session
(make-fluid (make-session (current-input-port)
(current-output-port)
'() #f #f)))
(define (session-accessor name)
(let ((a (record-accessor session-type name)))
(lambda () (a (fluid $session)))))
(define (session-modifier name)
(let ((m (record-modifier session-type name)))
(lambda (new) (m (fluid $session) new))))
(define command-input (session-accessor 'input))
(define command-output (session-accessor 'output))
(define focus-values (session-accessor 'values))
(define set-focus-values! (session-modifier 'values))
(define batch-mode? (session-accessor 'batch-mode?))
(define set-batch-mode?! (session-modifier 'batch-mode?))
(define break-on-warnings? (session-accessor 'bow?))
(define set-break-on-warnings?! (session-modifier 'bow?))
; Command levels.
(define $command-levels (make-fluid '()))
(define (command-level) (car (fluid $command-levels)))
(define :command-level
(make-record-type 'command-level
'(throw vm-cont condition interrupts
;; env
)))
(define make-command-level
(record-constructor :command-level
'(throw vm-cont condition interrupts
;; env
)))
(define command-level? (record-predicate :command-level))
(define command-level-throw
(record-accessor :command-level 'throw))
(define command-level-vm-cont
(record-accessor :command-level 'vm-cont))
(define command-level-condition
(record-accessor :command-level 'condition))
(define command-level-interrupts
(record-accessor :command-level 'interrupts))
;(define command-level-env
; (record-accessor :command-level 'env))
;(define set-command-level-env!
; (record-modifier :command-level 'env))
(define environment-for-commands interaction-environment)
; --------------------
; Main entry point.
(define (with-user-context context thunk)
(let-fluid $user-context context thunk)) ; Log in.
(define (with-new-session context iport oport resume-args batch? thunk)
(let-fluids $user-context context
$command-levels '()
$session (make-session iport oport resume-args batch? #f)
thunk))
;; The double-paren around the w-n-s is because it returns a continuation
;; which is the thing to do after the command-processor exits.
(define (start-command-processor resume-args context
;; initial-env
start-thunk)
(interrupt-before-heap-overflow!)
((with-new-session context
(current-input-port) (current-output-port)
resume-args (and (pair? resume-args)
(equal? (car resume-args) "batch"))
(lambda ()
(command-loop start-thunk #f
;; initial-env
)))))
; Entry for initialization & testing.
(define (command-processor command-env args)
(start-command-processor args
(make-user-context
(lambda ()
(set-user-command-environment! command-env)))
;; (interaction-environment)
unspecific))
; Command loop
; Uses:
; 1. startup, 2. condition handler, 3. abort-to-level, 4. breakpoint
(define (command-loop start-thunk condition
;; env
)
(call-with-command-level condition ;; env
(lambda (level)
(start-command-level start-thunk level))))
(define (call-with-command-level condition
;; env
proc)
(primitive-catch
(lambda (vm-cont)
((call-with-current-continuation
(lambda (throw)
(proc (make-command-level throw vm-cont condition
(enabled-interrupts)
;; env
))))))))
(define (start-command-level start-thunk level)
(with-handler command-loop-condition-handler
(lambda ()
(let-fluids $command-levels (cons level (fluid $command-levels))
$note-undefined #f ;useful
(lambda ()
;;(with-interaction-environment (command-level-env level)
;;(lambda ()
(start-thunk)
(let ((condition (command-level-condition level)))
(if condition
(display-condition condition (command-output)))
(if (not (= (enabled-interrupts) all-interrupts))
(begin (if (not (and (interrupt? condition)
(= (caddr condition) all-interrupts)))
(write-line "(Enabling interrupts)"
(command-output)))
(set-enabled-interrupts! all-interrupts))))
(let loop ()
(let ((command (read-command-carefully (command-prompt)
(form-preferred?)
(command-input))))
(showing-focus-object
(lambda ()
(execute-command command)))
(loop))))))));;))
(define form-preferred?
(user-context-accessor 'form-preferred? (lambda () #t)))
; Command level control
(define (pop-command-level)
(let ((levels (fluid $command-levels)))
(if (null? (cdr levels))
(if (or (batch-mode?)
(y-or-n? "Exit the Scheme Shell" #t))
(exit-command-processor (lambda () 0))
(abort-to-command-level (car levels)))
(abort-to-command-level (cadr levels)))))
(define (abort-to-command-level level)
(throw-to-command-level
level
(lambda ()
(start-command-level
(lambda ()
(cond ((command-level-condition level)
(display "Back to" (command-output)))
((null? (fluid $command-levels))
(newline (command-output))
(write-line "Top level" (command-output)))))
;; Condition will be displayed.
level))))
(define (throw-to-command-level level thunk)
((command-level-throw level) thunk))
(define (exit-command-processor thunk)
(throw-to-command-level (top-command-level)
(lambda () thunk)))
; Condition handler
(define (command-loop-condition-handler c next-handler)
(cond ((or (warning? c) (note? c))
(if (break-on-warnings?)
(deal-with-condition c)
(begin (display-condition c (command-output))
(unspecific)))) ;proceed
((or (error? c) (interrupt? c))
(if (batch-mode?)
(begin (display-condition c (command-output))
(let ((status (if (error? c) 1 2)))
(exit-command-processor (lambda () status))))
(deal-with-condition c)))
(else
(next-handler))))
(define push-command-levels?
(user-context-accessor 'push-command-levels (lambda () #t)))
;(define set-push-command-levels?!
; (user-context-modifier 'push-command-levels))
(define (deal-with-condition c)
(if (push-command-levels?)
(command-loop list c
;; (interaction-environment)
)
(call-with-command-level c ;; (interaction-environment)
(lambda (level)
(set-focus-object! level)
(display-condition c (command-output))
(abort-to-command-level (car (fluid $command-levels)))))))
(define-condition-type 'note '())
(define note? (condition-predicate 'note))
(define (command-prompt)
(let ((level (- (length (fluid $command-levels)) 1))
(id (environment-id-string (environment-for-commands))))
(string-append (if (= level 0)
""
(number->string level))
(if (or (= level 0) (= (string-length id) 0))
""
" ")
id
"> ")))
(define-generic environment-id-string &environment-id-string (env))
(define-method &environment-id-string (env) "")
; Evaluate a form
(define (evaluate-and-select form env)
(call-with-values (lambda ()
(evaluate form env))
(lambda results
(if (or (null? results)
(not (null? (cdr results)))
(not (eq? (car results) (unspecific))))
(set-focus-values! results))
(apply values results))))
(define-generic evaluate &evaluate (form env))
(define-method &evaluate (form env) (eval form env))
; Display the focus object if it changes (sort of like emacs's redisplay)
(define (showing-focus-object thunk)
(let ((focus-before (focus-values)))
(thunk)
(let ((focus-after (focus-values)))
(if (not (eq? focus-after focus-before))
(show-command-results focus-after)))))
(define (focus-object)
(let ((v (focus-values)))
(if (and (pair? v) (null? (cdr v))) (car v) v)))
(define (set-focus-object! obj)
(set-focus-values! (list obj)))
(define (show-command-results results)
(cond ((null? results))
((not (null? (cdr results)))
(let ((out (command-output)))
(display "; " out)
(write (length results) out)
(display " values" out)
(newline out))
(for-each show-command-result results))
(else ;(not (eq? (car results) (unspecific)))
(show-command-result (car results)))))
(define (show-command-result result)
(write-carefully (value->expression result)
(command-output))
(newline (command-output)))
(define $write-depth (make-fluid -1))
(define $write-length (make-fluid -1))
(define (write-carefully x port)
(if (error? (ignore-errors (lambda ()
(limited-write x port
(fluid $write-depth)
(fluid $write-length))
#f)))
(display "<Error while printing.>" port)))
; Sentinels - run after every command.
(define *sentinels* '())
(define (run-sentinels)
(for-each (lambda (sentinel) (sentinel)) *sentinels*))
(define (add-sentinel! sentinel)
(if (not (memq sentinel *sentinels*))
(set! *sentinels* (cons sentinel *sentinels*))))
; Commands.
(define command-environment
(user-context-accessor 'command-environment interaction-environment))
;(define *command-structure* (unspecific))
;
;(define (command-structure)
; *command-structure*)
;
;(define (set-command-structure! structure) ; called on initial startup
; (set! *command-structure* structure))
(define command-syntax-table (make-table))
(define *command-help* '())
(define (get-command-syntax name)
(or (table-ref (user-command-syntax-table) name)
(table-ref command-syntax-table name)))
(define (define-command-syntax name help1 help2 arg-descriptions)
(table-set! command-syntax-table name arg-descriptions)
(if help1
(set! *command-help* (add-help *command-help* name help1 help2))))
(define (add-help help name help1 help2)
(insert (list name
(string-append (symbol->string name) " " help1)
help2)
help
(lambda (z1 z2)
(string<=? (cadr z1) (cadr z2)))))
(define user-command-syntax-table
(user-context-accessor 'user-command-syntax-table (lambda () (make-table))))
(define user-command-environment
(user-context-accessor 'user-command-environment (lambda () #f)))
(define set-user-command-environment!
(user-context-modifier 'user-command-environment))
(define user-command-help
(user-context-accessor 'user-command-help (lambda () *command-help*)))
(define set-user-command-help!
(user-context-modifier 'user-command-help))
(define (define-user-command-syntax name help1 help2 arg-descriptions)
(table-set! (user-command-syntax-table) name arg-descriptions)
(if help1
(set-user-command-help!
(add-help (user-command-help) name help1 help2))))
(define (execute-command command)
(cond ((eof-object? command)
(newline (command-output))
(pop-command-level))
((not command)) ; error while reading
(else
(let* ((name (car command))
(proc (evaluate name (user-command-environment))))
(dynamic-wind (lambda () #f)
(lambda ()
(apply proc (cdr command)))
run-sentinels)))))
; help
(define (help . maybe-id)
(if (null? maybe-id)
(list-commands)
(print-command-help (car maybe-id))))
(define (print-command-help id)
(let ((o-port (command-output)))
(display #\space o-port)
(cond ((assq id (user-command-help))
=> (lambda (data)
(if (form-preferred?) (display command-prefix o-port))
(display (cadr data) o-port)
(display " " o-port)
(display (caddr data) o-port)))
(else
(display #\" o-port)
(display id o-port)
(display #\" o-port)
(display #\space o-port)
(display "is not a command.")))
(newline o-port)))
(define (list-commands)
(let ((o-port (command-output))
(widest 28)
(f? (form-preferred?)))
(for-each (lambda (s)
(write-line s o-port))
'(
"This is an alpha-test version of Scheme 48. You are interacting with"
"the command processor. A command is either a Scheme form to evaluate"
"or one of the following:"
""))
(list-command-help (user-command-help) f? o-port)
(for-each (lambda (s)
(write-line s o-port))
'(
""
"Square brackets [...] indicate optional arguments."
""
"The expression ## evaluates to the last value displayed by the command"
"processor."
))))
(define (list-command-help data prefix? o-port)
(let* ((strings (map (if prefix?
(lambda (d)
(string-append (command-prefix-string
command-prefix)
(cadr d)))
cadr)
data))
(count (length strings))
(back-half (list-tail strings (quotient (+ 1 count) 2))))
(let loop ((s1 strings) (s2 back-half))
(cond ((not (eq? s1 back-half))
(display #\space o-port)
(display (car s1) o-port)
(write-spaces (max 1 (- 32 (string-length (car s1)))) o-port)
(if (not (null? s2))
(display (car s2) o-port))
(newline o-port)
(loop (cdr s1) (if (null? s2) s2 (cdr s2))))))))
; Utilities
(define (top-command-level)
(last (fluid $command-levels)))
(define (error-form proc args)
(cons proc (map value->expression args)))
(define (value->expression obj) ;mumble
(if (or (number? obj) (char? obj) (string? obj) (boolean? obj))
obj
`',obj))
(define (write-spaces count o-port)
(do ((count count (- count 1)))
((<= count 0))
(display #\space o-port)))
(define (command-prefix-string prefix)
(cond ((string? prefix) prefix)
((char? prefix) (string prefix))
((symbol? prefix) (symbol->string prefix))))
(define (write-line string port)
(display string port)
(newline port))
(define (y-or-n? question eof-value)
(let ((i-port (command-input))
(o-port (command-output)))
(let loop ((count *y-or-n-eof-count*))
(display question o-port)
(display " (y/n)? " o-port)
(let ((line (read-line i-port)))
(cond ((eof-object? line)
(newline o-port)
(if (= count 0)
eof-value
(begin (display "I'll only ask another " o-port)
(write count o-port)
(display " times." o-port)
(newline o-port)
(loop (- count 1)))))
((< (string-length line) 1) (loop count))
((char=? (string-ref line 0) #\y) #t)
((char=? (string-ref line 0) #\n) #f)
(else (loop count)))))))
(define *y-or-n-eof-count* 100)
(define (read-line port)
(let loop ((l '()))
(let ((c (read-char port)))
(if (eof-object? c)
c
(if (char=? c #\newline)
(list->string (reverse l))
(loop (cons c l)))))))
(define (greet-user info)
(let ((port (command-output)))
(display "Welcome to Scheme 48 " port)
(display version-info port)
(if info
(begin (write-char #\space port)
(display info port)))
(display "." port)
(newline port)
(write-line "Copyright (c) 1993, 1994 by Richard Kelsey and Jonathan Rees."
port)
(write-line "Please report bugs to scheme-48-bugs@martigny.ai.mit.edu."
port)
(if (not (batch-mode?))
(write-line "Type ,? (comma question-mark) for help." port))))
(define (command-continuation) ;utility for debugger
(let ((obj (focus-object)))
(command-level-vm-cont
(if (command-level? obj)
obj
(command-level)))))