Category Archives: Uncategorized

The HP-67 emulator, callbacks to obtain arguments

The new HP67-UI interface needs a way for the engine to call back to the user interface and request an additional argument when one is required.  For instance, the memory-store key, STO, requires an argument to indicate where the store is to take place.  If no such argument is supplied, it will be requested through this interface.

We used to ask the UI to build the appropriate query string, but it makes more sense to put this into the engine.  So now, all the UI has to do is to write out the string it receives, without any modification or augmentation.

The handle-one-keypress function in the engine is not aware of the new HP67-UI interface.  It doesn’t have to be, all of the method functions it might need to use are passed down to it inside closures.  This is a very nice feature of Lisp, the ability to build closures easily and pass them down to functions.  In C, you would do this with function pointers, but that requires writing the code in a separate function, which makes it less easy to see immediately what it is doing.  The Lisp code in question looks like this:
engine.lisp

(handle-one-keypress response
                     #'(lambda (x)
                         (hp67-ui:ui-get-argument ui x))
                     nil stack mode
                     :arg-is-num t))

This code is checked into the git repository under the tag v2014-12-06.

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.