;;; Globals used by the command interpreter (define *current-string* "") (define *current-mod* '|Main|) (define *current-command* '()) (define *remembered-file* "Foo") (define *fragment-status* '()) (define *temp-counter* 0) (define *last-compiled* "") (define *abort-command* '()) (define *command-dispatch* '()) (define *extension-module* '()) (define *extension-file-name* "interactive") (define (prompt mod) (format '#f "~A> " mod)) (define-local-syntax (define-command name&args helpstr . body) (let* ((str (car name&args)) (args (cdr name&args)) (fname (string->symbol (string-append "CMD-" str)))) `(begin (define (,fname arguments) (verify-command-args ',args arguments ',helpstr) (apply (lambda ,args ,@body) arguments)) (setf *command-dispatch* (nconc *command-dispatch* (list (cons ',str (function ,fname))))) ',fname))) (define (heval) (initialize-haskell-system) (setf *current-string* "") (setf *fragment-status* 'Building) (say "~&Yale Haskell ~A~A ~A~%Type :? for help.~%" *haskell-compiler-version* *haskell-compiler-update* (identify-system)) (read-commands)) ;;; This loop reads commands until a quit (define (read-commands) (do ((cmd-status (read-command) (read-command))) ((eq? cmd-status 'quit-command-loop) (exit)))) ;;; This processes a single line of input. (define (read-command) (let/cc abort-command (setf *abort-command* (lambda () (funcall abort-command 'error))) (setf *abort-compilation* *abort-command*) (setf *phase* 'command-interface) (setf *in-error-handler?* '#f) (ready-for-input-line) (let ((ch (peek-char))) (cond ((eof-object? ch) 'quit-command-loop) ((char=? ch '#\:) (read-char) (execute-command)) ((and (char=? ch '#\newline) (not (eq? *fragment-status* 'Building))) (read-char) 'Ignored) (else (when (not (eq? *fragment-status* 'Building)) (setf *fragment-status* 'Building) (setf *current-string* "")) (cond ((eqv? ch '#\=) (read-char) (append-to-current-string (expand-print-abbr (read-line)))) ((eqv? ch '#\@) (read-char) (append-to-current-string (expand-exec-abbr (read-line)))) (else (append-to-current-string (read-line)))) 'OK) )))) (define (append-to-current-string string) (setf *current-string* (string-append *current-string* string (string #\newline)))) (define (expand-print-abbr string) (incf *temp-counter*) (format '#f "temp_~a = print temp1_~a where temp1_~a = ~a" *temp-counter* *temp-counter* *temp-counter* string)) (define (expand-exec-abbr string) (incf *temp-counter*) (format '#f "temp_~a :: Dialogue~%temp_~a = ~a" *temp-counter* *temp-counter* string)) (define (ready-for-input-line) (when (not *emacs-mode*) (fresh-line (current-output-port)) (write-string (prompt *current-mod*) (current-output-port)) (force-output (current-output-port))) (notify-ready)) (define (execute-command) (if (char=? (peek-char) '#\() ;this is the escape to the lisp evaluator (let ((form (read))) (eval form) 'OK) (let* ((string (read-line)) (length (string-length string)) (cmd+args (parse-command-args string 0 0 length))) (cond ((null? cmd+args) (say "~&Eh?~%") 'OK) (else (let ((fn (assoc/test (function string-starts?) (car cmd+args) *command-dispatch*))) (cond ((eq? fn '#f) (say "~&~A: unknown command. Use :? for help.~%" (car cmd+args)) 'OK) (else (funcall (cdr fn) (cdr cmd+args)))))))))) ;;; This parses the command into a list of substrings. ;;; Args are separated by spaces. (define (parse-command-args string start next end) (declare (type fixnum start next end) (type string string)) (cond ((eqv? next end) (if (eqv? start next) '() (list (substring string start next)))) ((char=? (string-ref string next) '#\space) (let ((next-next (+ next 1))) (if (eqv? start next) (parse-command-args string next-next next-next end) (cons (substring string start next) (parse-command-args string next-next next-next end))))) (else (parse-command-args string start (+ next 1) end)))) (define (verify-command-args template args help) (cond ((and (null? template) (null? args)) '#t) ((symbol? template) '#t) ((or (null? template) (null? args)) (say "~&Command error.~%~A~%" help) (funcall *abort-command*)) (else (verify-command-args (car template) (car args) help) (verify-command-args (cdr template) (cdr args) help)))) (define-command ("?") ":? Print the help file." (print-file "$HASKELL/command-interface-help")) (define-command ("eval") ":eval Evaluate current extension." (eval-fragment '#t) 'OK) (define-command ("save") ":save Save current extension" (eval-fragment '#f) (cond ((eq? *fragment-status* 'Error) (say/ne "~&Cannot save: errors encountered.~%")) ((eq? *fragment-status* 'Compiled) (extend-module *current-mod* *extension-module*) (setf *fragment-status* 'Saved))) 'OK) (define-command ("quit") ":quit Quit the Haskell evaluator." 'quit-command-loop) (define-command ("module" mod) ":module module-name Select module for incremental evaluation." (setf *current-mod* (string->symbol mod)) (when (not (cm-available?)) (say/ne "~&Warning: module ~A is not currently loaded.~%" *current-mod*)) 'OK) (define-command ("run" . file) ":run Compile, load, and run a file." (set-current-file file) (clear-extended-modules) (let ((mods (compile/load *remembered-file*))) (when (pair? mods) (dolist (m mods) (eval-module (table-entry *modules* m))))) 'OK) (define-command ("compile" . file) ":compile Compile and load a file." (set-current-file file) (clear-extended-modules) (select-current-mod (compile/compile *remembered-file*)) 'OK) (define-command ("load" . file) ":load Load a file." (set-current-file file) (clear-extended-modules) (select-current-mod (compile/load *remembered-file*)) 'OK) (define-command ("Main") ":Main Switch to an empty Main module." (make-empty-main) 'OK) (define-command ("clear") ":clear Clear saved definitions from current module." (remove-extended-modules *current-mod*) (setf *current-string* "") (setf *fragment-status* 'Building)) (define-command ("list") ":list List current extension." (say "~&Current Haskell extension:~%~a" *current-string*) (cond ((eq? *fragment-status* 'Error) (say "Extension contains errors.~%")) ((eq? *fragment-status* 'Compiled) (say "Extension is compiled and ready.~%"))) 'OK) (define-command ("kill") ":kill Clear the current fragment." (when (eq? *fragment-status* 'Building) (setf *current-string* "")) 'OK) (define-command ("p?") ":p? Show available printers." (if *emacs-mode* (notify-printers (dynamic *printers*)) (begin (print-file "$HASKELL/emacs-tools/printer-help.txt") (say "~&Active printers: ~A~%" (dynamic *printers*))) )) (define-command ("p=" . passes) ":p= pass1 pass2 ... Set printers." (setf *printers* (set-printers passes '=)) (say/ne "~&Setting printers: ~A~%" *printers*)) (define-command ("p+" . passes) ":p+ pass1 pass2 ... Add printers." (setf *printers* (set-printers passes '+)) (say/ne "~&Setting printers: ~A~%" *printers*)) (define-command ("p-" . passes) ":p- pass1 pass2 ... Turn off printers." (setf *printers* (set-printers passes '-)) (say/ne "~&Setting printers: ~A~%" *printers*)) (define-command ("o?") ":o? Show available optimizers." (if *emacs-mode* (notify-optimizers (dynamic *optimizers*)) (begin (print-file "$HASKELL/emacs-tools/optimizer-help.txt") (say "~&Active optimizers: ~A~%" (dynamic *optimizers*))) )) (define-command ("o=" . optimizers) ":o= optimizer1 optimizer2 ... Set optimizers." (setf *optimizers* (set-optimizers optimizers '=)) (say/ne "~&Setting optimizers: ~A~%" *optimizers*)) (define-command ("o+" . optimizers) ":o+ optimizer1 optimizer2 ... Add optimizers." (setf *optimizers* (set-optimizers optimizers '+)) (say/ne "~&Setting optimizers: ~A~%" *optimizers*)) (define-command ("o-" . optimizers) ":o- optimizer1 optimizer2 ... Turn off optimizers." (setf *optimizers* (set-optimizers optimizers '-)) (say/ne "~&Setting optimizers: ~A~%" *optimizers*)) (define-command ("cd" d) ":cd directory Change the current directory." (cd d) 'OK) (define-command ("Emacs" mode) ":Emacs on/off Turn on or off emacs mode." (cond ((string=? mode "on") (setf *emacs-mode* '#t)) ((string=? mode "off") (setf *emacs-mode* '#f)) (else (say "~&Use on or off.~%")))) (define-command ("file" name) ":file name" (setf *extension-file-name* name) 'OK)