We are now ready to put together an actual useful calculator. With an event loop and input handler, we can now perform interactive operations commanded by the user. By displaying the stack, we can show the user the results of those operations.
One problem we will have is keyboards. They keyboard has the ability to send control keys, most of which are handled in an implementation-dependent manner by Lisp. We would like to filter out control characters unless they have some particular meaning to the program. This is done with two new functions, quit-character and allowed-character, both defined under the reader macro #+sbcl, so only compiled when running on SBCL. In SBCL, the character codes for the common keyboard characters are just their ASCII values, so we can filter keypresses by their ASCII code.
We also want the arithmetic operation keys to be hot keys. That means that if they are the first character on a line, they will immediately take effect, as if the enter key had been pressed.
Main now looks like this:
(defun main() (charms:with-curses () (let ((w charms:*standard-window*) n-rows n-cols) (multiple-value-setq (n-cols n-rows) (charms:window-dimensions w)) (macrolet ((wsc (ostring) `(charms:write-string-at-cursor w ,ostring))) (charms:enable-echoing) (charms:disable-extra-keys w) (charms:disable-non-blocking-mode w) (charms:enable-raw-input :interpret-control-characters t) (let* ((stack (get-new-stack-object 4)) (mode (get-new-mode-object)) (all-keys (get-key-abbrevs mode :sort-fcn #'comp< :veto-list *excluded-keys*)) (maxlen (apply 'max (mapcar 'length all-keys))) (keys-per-row (floor (/ n-cols (1+ maxlen))))) (do (exit-requested) (exit-requested) (charms:clear-window w) (let ((active-keys (get-key-abbrevs mode :sort-fcn #'comp< :veto-list *excluded-keys* :limit-to-mode t)) (i 0) (accumulator (make-string-output-stream))) (dolist (candidate all-keys) (when (member candidate active-keys :test 'string=) (multiple-value-bind (r c) (floor i keys-per-row) (charms:move-cursor w (* c (1+ maxlen)) r)) (wsc candidate)) (incf i)) (dotimes (j 4) (let ((entry (nth j (stack-registers stack)))) (when entry (charms:move-cursor w 0 (- n-rows j 4)) (wsc (format-for-printing mode entry))))) (charms:move-cursor w 0 (- n-rows 2)) (charms:refresh-window w) (do ((pos 0) (c (charms:get-char w) (charms:get-char w))) ((char= c #\Newline)) (cond ((quit-character c) (return-from main)) ((allowed-character c) (format accumulator "~C" c) (incf pos))) (when (and (= pos 1) (member c *hot-keys* :test 'char=)) (return))) (let ((result (get-output-stream-string accumulator))) (handle-one-keypress result nil nil stack mode :arg-is-num t)))) )))))
It can be loaded into SBCL (but not SLIME) with the command:
(asdf:oos 'asdf:load-op 'curses-cli)
Here’s what this first version of the calculator looks like:
This code is in the git repository under the tag v2014-11-24.