Christophe Rhodes csr21@cam.ac.uk writes:
My current attempt at writing a command parser (no attempt at partial parsing or unparsing yet) is
(defun esa-command-parser (command-table stream) (let ((command-name nil) (command-args nil)) (with-delimiter-gestures (*command-name-delimiters* :override t) ;; While reading the command name we want use the history of the ;; (accept 'command ...) that's calling this function. (setq command-name (accept `(command-name :command-table ,command-table) :stream stream :prompt nil :history nil)) (let ((delimiter (read-gesture :stream stream :peek-p t))) ;; Let argument parsing function see activation gestures. (when (and delimiter (delimiter-gesture-p delimiter)) (read-gesture :stream stream)))) (with-delimiter-gestures (*command-argument-delimiters* :override t) (let* ((info (gethash command-name climi::*command-parser-table*)) (required-args (climi::required-args info)) (keyword-args (climi::keyword-args info))) (let (result) ;; only required args for now. (dolist (arg required-args (cons command-name (nreverse result))) (destructuring-bind (name ptype &rest args) arg (declare (ignore name)) ;; clear the stream somehow #+nil (setf (stream-cursor-position stream) (values 0 0)) #+nil (replace-input stream "") (push (apply #'accept (eval ptype) :stream stream ;; FIXME: probably wrong evaluation for ARGS args) result))))))))
Quite apart from the apparent impossibility of writing a command parser portable across CLIM implementations, this doesn't work in several ways. It doesn't handle keyword arguments, the evaluation of the arguments in the call to ACCEPT for arguments is wrong (see the spec for DEFINE-COMMAND for the evaluation rules...), and, most importantly, I find myself unable to clear the stream. Setting the stream cursor position doesn't work, because stream is an input stream; I don't know why REPLACE-INPUT doesn't work, but it gives me an error. ERASE-INPUT-BUFFER is unimplemented.
My current version, which I've built up by trial and error, almost works (I've appended it below). In fact it basically works except when the user types SPC at a point where (I think) there is more than one possibility, but only if the user has previously pressed backspace; in those conditions, the "Extended Command: " prompt from below is deleted. That is, in my example where I have com-new-find-file and com-newline-and-indent, the sequence M-x N e SPC works as I want it to, while M-x N e DEL e SPC causes the prompt to disappear. I'm mystified by this.
(defun esa-command-parser (command-table stream) (let ((command-name nil) (command-args nil)) (with-delimiter-gestures (*command-name-delimiters* :override t) ;; While reading the command name we want use the history of the ;; (accept 'command ...) that's calling this function. (setq command-name (accept `(command-name :command-table ,command-table) :stream stream :prompt "Extended Command: " :prompt-mode :raw :history nil)) (let ((delimiter (read-gesture :stream stream :peek-p t))) ;; Let argument parsing function see activation gestures. (when (and delimiter (delimiter-gesture-p delimiter)) (read-gesture :stream stream)))) (with-delimiter-gestures (*command-argument-delimiters* :override t) (let* ((info (gethash command-name climi::*command-parser-table*)) (required-args (climi::required-args info)) (keyword-args (climi::keyword-args info))) (let (result) ;; only required args for now. (dolist (arg required-args (cons command-name (nreverse result))) (destructuring-bind (name ptype &rest args) arg (declare (ignore name)) ;; clear the stream somehow (replace-input stream "" :rescan nil) #+nil (reset-scan-pointer stream) #+nil (window-clear (encapsulating-stream-stream stream)) #+nil (setf (stream-insertion-pointer stream) 0) #+nil (setf (stream-scan-pointer stream) 0) #+nil (replace-input stream "") #+nil (reset-scan-pointer stream) (push (apply #'accept (eval ptype) :stream stream ;; FIXME: probably wrong evaluation for ARGS args) result))))))))
Cheers,
Christophe