Tag Archives: lisp

The HP-67 emulator, completion of the transition to the new API

At this point, all that remains is to write an engine that understands the new API.  It should push preformatted strings to the UI, tell it to repaint itself, and ask it for input, then pass the returned input back to the keypress-handling function.

The code for this is fairly straight-forward.  It does have to keep track of old values of some state variables, so that it can know when to update state in the UI and when not to.  The code looks like this:
engine.lisp

(defun run-engine (ui &key (stacksize 4))
  (let* ((stack (get-new-stack-object stacksize))
         (mode (get-new-mode-object))
         (prev-active-mode nil)
         (prev-complex-mode nil)
         (prev-display-mode nil)
         (prev-display-digits -1))

    (do (quit-requested)
        (quit-requested)

      (let ((current-active-mode (modes-run/prog mode))
            (current-complex-mode (modes-complex mode))
            (current-error-text (stack-error-state stack))
            (current-display-mode (modes-display-mode mode))
            (current-display-digits (modes-digits mode)))

        (unless (eq prev-active-mode current-active-mode)
          (hp67-ui:ui-set-active-mode ui current-active-mode)
          (hp67-ui:ui-set-active-keys ui (get-key-structs current-active-mode
                                                          :limit-to-mode t))
          (setf prev-active-mode current-active-mode))

        (unless (and (eq current-display-mode prev-display-mode)
                     (= current-display-digits prev-display-digits))
          (hp67-ui:ui-set-display-mode ui current-display-mode current-display-digits)
          (setf prev-display-mode current-display-mode
                prev-display-digits current-display-digits))

        (unless (eq prev-complex-mode current-complex-mode)
          (hp67-ui:ui-set-complex-mode ui current-complex-mode)
          (setf prev-complex-mode current-complex-mode))

        (when current-error-text
          (hp67-ui:ui-set-error-text ui current-error-text))

        (let ((num-stack-to-pass (if (> stacksize 0)
                                     stacksize
                                     (length (stack-registers stack)))))

          (hp67-ui:ui-clear-stack-contents ui num-stack-to-pass)
          (dotimes (i num-stack-to-pass)
            (let ((entry (nth i (stack-registers stack))))
              (unless entry
                (setf entry 0))
              (hp67-ui:ui-add-stack-value ui i
                                          (format-for-printing mode entry)))))

        (let ((mem (stack-memory stack)))
          (hp67-ui:ui-clear-memory-contents ui)
          (dotimes (i (length mem))
            (hp67-ui:ui-add-memory-value ui i
                                         (car (nth i mem))
                                         (format-for-printing mode (cdr (nth i mem))))))

        (hp67-ui:ui-paint ui)
        (let ((response (hp67-ui:ui-get-input ui)))
          (setf quit-requested (hp67-ui:get-quit-requested ui))
          (unless quit-requested
            (etypecase response
              (string
               (when (string= response "")
                 (setf response "enter"))
               (handle-one-keypress response nil nil stack mode
                                    :arg-is-num t))
              (key-struct
               (handle-one-keypress (key-struct-abbrev response)
                                    nil nil stack mode
                                    :arg-is-num nil)))))))))

This is checked into the git repository under the tag v2014-12-04.  Also, I just realized that my tags hadn’t been going to the repository.  They have to be explicitly pushed, by supplying the –tags switch to the git push command.

The HP-67 emulator, moving the ncurses interface to a new API model

We had a basic calculator running, but it had too much understanding of the interior functioning of the calculator.  We’ve changed this now, there is a UI base class from which user interfaces might derive.  This base class is given preformatted strings for things like stack contents and memory values, leaving the user interface code to concentrate solely on rendering those things it is told are there.  The user interface doesn’t have to know how the stack is laid out, or how to convert stack values to strings, that’s all handled in the internals in this new model.

We’ve defined generic functions on the ncurses class for three generic function definitions.  There’s an :after method on ui-set-active-mode, because the ncurses interface will modify its screen layout depending on what mode (interactive, programming, executing) is currently active.  We’ve also defined functions for ui-paint and ui-get-input, neither of which has an implementation for the base class.

We haven’t put everything together yet, because now the engine has to be rewritten to understand the new API.  Once that’s done, we’ll reassemble the calculator and test.

The current code is checked into the git repository under the tag v2014-12-03.

The HP-67 emulator, rethinking the UI design

We have produced our basic interactive calculator with an ncurses interface, but it’s a bit unsatisfying.  The UI code has a somewhat more intimate knowledge of the internals of the engine than is really necessary, which means writing new user interfaces is difficult.  What we really want to do is to switch this around from a system where the UI does the polling to one where the event loop is located in the engine, and the UI knows only as much as it needs to display the calculator as seen by the specific interface.

What we want to produce is a system whereby the user interface sets itself up, paints its appearance, then invokes the engine.  The engine will then call back to the user interface to inform it of state changes and to tell it when and how to redraw itself.

We’ll put this in a new namespace, and create a base class from which real UIs will be derived.  The base class will contain many slots which describe those things that it might need in order to display correctly.  Generic functions will fill in these slots.  We’re defining the base class slots with readers, rather than accessors, to emphasize that the derived classes that define the user interfaces should not be modifying these fields, only reading them.  UIs that must take specialized action when one of these setter methods is called should generally use an :after method, unless there is a compelling reason to do something different.  The painting method will not have a base class definition, as every user interface will be different.

We will augment the list of generic functions as necessary.  For now, this list is a set of functions related to drawing of the user interface.  We have not yet written a definition for generic functions related to keypresses and input.

The current generic function definitions are found in a new file, ui.lisp.  They are reproduced here:
ui.lisp

;; The user interface base class and generic function definitions.

(defpackage :HP67-UI
  (:use :COMMON-LISP)
  (:export
   ))

(in-package :HP67-UI)

(defclass ui-base ()
  ((active-keys         :reader get-active-keys
                        :initform nil)
   (active-shift        :reader get-active-shift
                        :initform nil)
   (active-mode         :reader get-active-mode
                        :initform nil)
   (display-mode        :reader get-display-mode
                        :initform nil)
   (display-digits      :reader get-display-digits
                        :initform nil)
   (error-text          :reader get-error-text
                        :initform "")
   (stack-real-contents :reader get-stack-real-contents
                        :initform nil)
   (stack-imag-contents :reader get-stack-imag-contents
                        :initform nil)
   (memory-contents     :reader get-memory-contents
                        :initform nil)
   (program-contents    :reader get-program-contents
                        :initform nil)
   (program-counter     :reader get-program-counter
                        :initform nil)))



(defgeneric ui-set-active-keys (ui active-key-list)
  (:documentation "Used to inform the UI of what keys can be
pressed given the current state of the calculator.  The engine
will attempt not to call this function unless the active keys
list has changed."))

(defgeneric ui-set-active-shift (ui active-shift)
  (:documentation "Used to inform the UI that a shift key, (F, G,
or H) has been pressed."))

(defgeneric ui-set-active-mode (ui active-mode)
  (:documentation "Used to inform the UI of any change in
mode (interactive, program, running).  The engine will attempt
not to call this function unless the active mode has changed."))

(defgeneric ui-set-display-mode (ui display-mode display-digits)
  (:documentation "Used to inform the UI what the display mode
is.  The engine will attempt not to call this function unless the
display mode has changed."))

(defgeneric ui-set-complex-mode (ui how)
  (:documentation "Used to inform the UI whether or not we want
to display complext numbers.  If 'how' is non-nil, complex
display is requested.  The engine will attempt not to call this
function unless the complex mode has changed."))

(defgeneric ui-set-error-text (ui error-text)
  (:documentation "Used to inform the UI when an error has
occured.  The engine will not call this function unless an error
has just been set, or just been cleared."))

(defgeneric ui-has-error-text (ui)
  (:documentation "Used by the UI to establish whether there is
an error message to display."))

(defgeneric ui-clear-stack-contents (ui max-depth)
  (:documentation "Erase the UI's knowledge of the stack
contents, and allocate space for up values with depth up to
max-depth.  The engine will call this when setting up for a
paint."))

(defgeneric ui-add-stack-value (ui stack-depth
                                real-part-string
                                &optional imag-part-string)
  (:documentation "Inform the UI of the contents of the stack.  X
is at depth=0.  The engine will call this at least once when
setting up for a paint."))

(defgeneric ui-clear-memory-contents (ui)
  (:documentation "Erase the UI's knowledge of the memory
contents.  The engine will call this when setting up for a
paint."))

(defgeneric ui-add-memory-value (ui precedence label
                                 real-part-string
                                 &optional imag-part-string)
  (:documentation "Inform the UI of the contents of memory.  When
not enough space is available to display all memory contents,
those with higher precedence should be displayed ahead of those
with lower.  The engine may call this one or more times when
setting up for a paint."))

(defgeneric ui-clear-program-contents (ui)
  (:documentation "Erase the UI's knowledge of program memory.
The engine will normally not call this unless program steps have
been deleted."))

(defgeneric ui-add-program-step (ui step-num display-string)
  (:documentation "Inform the UI of the contents of a single step
of memory.  Step-num will be unique.  The engine will call
whenever new program steps are present."))

(defgeneric ui-get-program-step-string (ui step-num)
  (:documentation "Used by the UI to retrieve a particular step
number from the program memory."))

(defgeneric ui-set-program-counter (ui)
  (:documentation "Inform the UI of what program step would next
be executed if program execution were to begin.  The engine will
attempt to call this only when it has changed."))

(defgeneric ui-paint (ui)
  (:documentation "Ask the UI to repaint itself based on its new
settings."))

The current code is checked into the git repository under the tag v2014-11-30.

The HP-67 emulator, some more error-related cleanups

With this latest set of changes, we have the curses-based version of the emulator more or less complete for interactive use.  We’ve put in more error handling and fixed some bugs.

Soon, we’ll put in the programming mode, which will be a bit more complex than these minor patches.   After that, we’ll start extending the calculator beyond the abilities of the physical calculator.  That means allowing complex numbers, multi-character strings as labels and memory registers, perhaps hexadecimal output for integers.

This change also adds memory registers to the display.

Here’s what happens when you try to take the square root of -1 right now, before complex numbers are permitted:

cli-error-state2

Here is the display with some memory registers loaded, and display digits set to 5:

with-memory

The current code is in the git repository under the tag v2014-11-28.

The HP-67 emulator, hardening the input

While we’ve got a program that can accept commands and behave like a calculator, it is, at this point, somewhat brittle.  Unexpected input can cause it to enter the Lisp debugger, which isn’t how we’d like to have things behave.  So, it’s time to start making sure that bad input is handled gracefully.

We’ll define an error status region of the screen that will fill in with text when the calculator determines that an error has occurred.  In that case, we’ll mark the stack with an error text and force the user to type the “clx” command before continuing.  This means we have to add a new field to our keypress structure, “can clear errors”, so that we can make sure only that key can be used until the error has been cleared.

Now, in engine.lisp, we cheated a bit on the code that allows an entire number to be input at once.  We used read-from-string on unsanitized input to see if the string contained a number.  This can break in a variety of ways if the string contains, say, unbalanced double-quotes, to name only one of many bad cases.  So, to clean it up, we do a pre-test to verify that the string contains only characters that are legal in a numeric context.  That is, the digits, the dot, the minus sign, and ‘d’ or ‘e’ in either lower- or upper-case, for the exponential notation.

Here, then, is an example of what happens when the user tries to divide 1 by 0:cli-error-state

This code is checked into the git repository under the tag v2014-11-26.