;;; 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 <file> 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 <file> 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 <file> 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)