Monthly Archives: October 2014

The HP67 emulator, converting from the domain-specific language

OK, so now we’ve defined our domain-specific language (DSL), and we’ve written a function to retrieve symbols in a tree.  Now, we need a few more functions.

First, we will need something that tells us which variables are used in the forms, so that we know how to pop the stack.  So, we locate the deepest stack variable used, and declare that all variables up to and including that one are used.  That function is here:
calc1.lisp

(defun get-vars-used (rules-list varnames)
  (let ((symbols-used (get-symbols-in-list rules-list))
        (vlen (length varnames)))
    (dotimes (i vlen)
      (let ((check (nth (- vlen i 1) varnames)))
        (when (member check symbols-used)
          (return-from get-vars-used 
            (subseq varnames 0 (- vlen i))))))))

with sample output:
*slime-repl sbcl*
CL-USER> (get-vars-used '(X <- (sqrt (+ (* X X) (* Y Y)))
                          Y <- (atan Y X))
                        '(X Y Z W))
(X Y)

Next, we’ll need a list of variables assigned.  We look for the special ‘<- symbol in all depths of the tree and collect the variables assigned.  If there are no <- symbols, it’s an implicit assignment to X, so we return that variable name alone.  This function is here:
calc1.lisp
(defun get-vars-assigned (rules-list varnames)
  (let ((rv '()))
    (labels
        ((worker (rl)
           (do ((v rl (cdr v)))
               ((not v) rv)
             (cond
               ((listp (first v))
                (setf rv (append rv (worker (first v)))))
               ((and (symbolp (first v))
                     (eq (second v) '<-))
                (push (first v) rv))))))

      (setf rv (worker rules-list))

      (remove-if #'(lambda (x)
                     (not (member x varnames))) rv)
      (if (not rv)
          (list (first varnames))
          (delete-duplicates rv)))))

with sample output:
*slime-repl sbcl*
CL-USER> (get-vars-assigned '(X <- (sqrt (+ (* X X) (* Y Y)))
                              Y <- (atan Y X))
                            '(X Y Z W))
(Y X)

Our third helpful function will convert <- assignments to proper setf forms, and in so doing will convert the syntax back to proper Lisp.  The following function will do that, but it also changes the names of the targets of the setf.  That is, assignment is made to a different symbol, so that the values of X, Y, Z, W are not overwritten.  This is necessary if you look at one of the forms we want to be able to process:

X <- (sqrt (+ (* X X) (* Y Y)))   Y <- (atan Y X)

If these assignments were done serially, the changed value of X would be used to compute Y, and the wrong answer would be produced.  If we try to do it with a psetf, we will be severely limiting our permitted structures, as we will require exactly one assignment execution, happening simultaneously, and the programmer may not want to be bound by that.  So, by assigning the new values to new symbols, and pushing those back onto the stack, we can ensure that we have full flexibility of syntax.  Here, then, is the function that converts to a list of setf forms:
calc1.lisp

(defun convert-to-setf-forms (rules-list 
                              vars-used 
                              output-varnames)
  (let (rv)
    (do ((pos rules-list (cdr pos)))
        ((not pos) rv)
      (cond
        ((and (member (first pos) vars-used)
              (eq (second pos) '<-)
              (third pos))
         (setf rv
               (append rv 
                       `((setf ,(nth (position (first pos)
                                               vars-used)
                                     output-varnames)
                               ,(third pos)))))
         (setf pos (cddr pos)))
        ((listp (first pos))
         (setf rv 
               (append 
                rv 
                (list 
                 (convert-to-setf-forms (first pos)
                                        vars-used
                                        output-varnames)))))
        (t
         (setf rv (append rv (list (first pos)))))))))

with sample output:
*slime-repl sbcl*
CL-USER> (convert-to-setf-forms '(X <- (sqrt (+ (* X X) (* Y Y)))
                                  Y <- (atan Y X))
                                '(X Y)
                                '(X-OUT Y-OUT))
((SETF X-OUT (SQRT (+ (* X X) (* Y Y)))) (SETF Y-OUT (ATAN Y X)))

You’ll notice the use of a backtick in the convert-to-setf-forms function.  If you are new to Lisp, you can be forgiven for thinking that backticks are “the things that make macros”, but that’s not what they are.  The backtick is the intermediate construct between single-quoted lists and lists built with the list function.  Single-quoted lists are literal collections of symbols, no interpretation of the internal structure beyond the list layout itself is performed: no functions are called and no variables are replaced.  The list function is a function, and as such it evaluates all of its arguments.  Bare symbols are converted to their value, or called as functions, depending on the syntax in which they appear.  The backtick allows the programmer to build lists where some elements are evaluated, and some are not.  It’s possible to do the same with the list function, but it’s much less convenient, and much less readable.  Consider, for instance, these two forms, which do the same thing:
*slime-repl sbcl*
CL-USER> (let ((aval 20) (bval 10)) 
           `(let ((a ,aval)
                  (b ,bval))
              (when (> a b)
                (format t "A is greater than B~%"))))
(LET ((A 20) (B 10))
  (WHEN (> A B) (FORMAT T "A is greater than B~%")))

*slime-repl sbcl*

CL-USER> (let ((aval 20) (bval 10))
           (list 'let (list (list 'a aval)
                            (list 'b bval))
                 (list 'when (list '> 'a 'b)
                       (list 'format t "A is greater than B~%"))))
(LET ((A 20) (B 10))
  (WHEN (> A B) (FORMAT T "A is greater than B~%")))

The form built from the backticks is much easier to interpret and modify.  There is nothing about backticks that declares that they must appear in macros, it’s merely that they are almost always necessary for readable macros, but the function contexts where backticks are helpful are less common.  The programmer will find the backtick of considerable use when building Lisp code in a function, as we are doing here.

The HP67 emulator, continuing the domain-specific language

So, we’ve described our domain-specific language (DSL).  We want something Lisp-like, but with a few additional syntaxes.  First, we have a new assignment construct with the ‘<- symbol when appearing to the right of X, Y, Z, or W.  Also, we have an implicit assignment to X if that new assignment symbol does not appear anywhere in the forms.

This assignment syntax is not lisp-like.  The symbol does not appear in a function context, and the standard Lisp reader would not handle it well.  This is where the use of macros enters.  One important advantage of Lisp macros when designing DSLs is that the body of a macro is not fully parsed.  It has to enter through the standard reader, so parentheses must be balanced, single-quotes for literal lists and double-quotes for strings still behave the same way, and so on, but the symbols themselves are not interpreted.  A macro can manipulate the body forms in many ways before passing them to the eval stage.

We need to characterize the forms we’ve received before we can manipulate them.  To decide whether there is an implicit assignment, we’ll need to walk the expression forms we’ve been given and identify all of the symbols used in the tree (the tree is the list of body forms).  We’ll say that if the ‘<- symbol appears, then there’s at least one of our special assignment functions, so no implicit assignment will be used.  So, our first requirement is a function that returns a list of all symbols in the tree it is passed.  That will be this function, get-symbols-in-list:
calc1.lisp

(defun get-symbols-in-list (rlist)
  (let ((rval '()))
    (dolist (element rlist)
      (cond
        ((listp element)
         (setf rval (append rval (get-symbols-in-list element))))
        ((symbolp element)
         (push element rval))))
    (delete-duplicates rval)))

This function parses the passed list and its sublists, and collects a list of distinct symbols.  Each appears only once.  Here’s the output acting on the factorial form from the previous post:
*slime-repl sbcl*
CL-USER> (get-symbols-in-list
          '((let ((n X)
                  (rv X))
              (assert (and (integerp X) (> X 0)))
              (dotimes (i (1- n))
                (setf rv (* rv (- n i 1))))
              X <- rv)))
(<- LET ASSERT AND INTEGERP X > DOTIMES 1- SETF RV * I N -)

The HP67 emulator and a tiny domain-specific language

Now, we’ll talk a bit about domain-specific languages.  The Lisp language, with its macros and natural ability to parse its own syntax, is well-suited to creating a domain-specific language.  This is a novel programming language designed to simplify certain aspects of the problem.  It is for the convenience of the programmer, and usually not for the user, so it’s not likely that the aim is to do away entirely with Lisp syntax, it’s to make the Lisp programmer’s job easier and to simplify the reading of the code.

So, this HP67 emulator is going to need to have data structures to represent keys, and associated operations.  Let’s consider first some basic mathematical operations, we’re not going to talk about things like memory registers or programming mode.

The HP67 has a four-number stack.  The most-recently-added value to the stack is called the X value, and is the value that is usually displayed on the screen of the calculator.  Beyond that is the Y register, then two more values that are not usually given a name, but which I will call Z and W. EDIT #1 2014-10-09: In fact, the manual for the HP-67 calculator calls these X, Y, Z, and T. We will use W instead of T because of the practical difficulties of using T in Lisp forms.

Let’s look at the sort of operations we will want to implement.  There’s the addition operator, which consumes X and Y and pushes the sum X+Y onto the stack.  There’s the sine function, which consumes X and replaces it with its sine, in the appropriate angular units.  There’s the rectangular-to-polar function, which consumes X and Y and pushes the angle as Y and the radius as X.  A simple notation for this might be:

X <- (+ Y X)

X <- (sin X)

X <- (sqrt (* X X) (* Y Y))    Y <- (atan Y X)

These representations are easily read, and we’d like to make use of them in the definitions of our keys, but we’d also like to allow the possibility that the programmer wants to use a full Lisp form, such as this, for factorials of positive integers:
calc1.lisp

(let ((n X)
      (rv X))
  (assert (and (integerp X) (> X 0)))
  (dotimes (i (1- n))
    (setf rv (* rv (- n i 1))))
  X <- rv)

So, we’re going to write some code that permits these to appear, as written above.  While X, Y, Z, W will be considered reserved names for purposes of Lisp forms written in the key operation, anything else will be allowed.  Also, for functions that only push a single X value onto the stack, we will allow the programmer to omit the “X <-” construct, so the key’s definition can simply say:

(+ Y X)

if the programmer prefers that.  I’ll show how we do that in upcoming posts.

A Lisp project: HP67 emulator

When I was in CÉGEP, my parents gave me a calculator as a birthday present.  An HP-67, it is a programmable RPN calculator.  I used it constantly, though it eventually had to retire because I could no longer get replacement battery packs for it, and it couldn’t run off the mains without a working battery pack.  Still, I got about 15 years of use out of it.  I’ve always preferred RPN calculators, and when I use infix calculators I have to be very careful not to lose my partial results.

I also tend to write HP-67 emulators.  I started with a CDA (classic desk accessory) version for my Apple ][GS.  I probably still have the sources on my old Apple ][GS, but it’s boxed up in the basement.  I did, though, find them still on comp.sources.apple2, more than 20 years after I posted them, so I’ve pulled them out and put them into an archive here.  It was compiled with ORCA/C and ORCA/M, the C compiler and assembler commonly used for development on the Apple ][GS.

A few years later, I decided to learn C++, and used the HP-67 emulator as my practice problem.  The resulting program, hp67, is one I still use frequently today.  Its sources and screenshots are available here.  It used to ship with redhat, but I don’t think it’s packaged anymore.

The emulators I wrote are programmable, though it’s now many years since I’ve invoked the programmable mode in hp67.  The programming mode in the HP-67 is mostly a keypress recorder, but with simple flow control.  The mode supports goto, gosub, and conditional operators that skip over the next program step when the condition is not met.  Together, these operations allow the calculator to be programmed to perform more complex, repetitive operations.

Now, I’m going to start doing some development of an hp-67 emulator in Lisp, just to provide some real examples of some of the Lisp features I’ve been describing in these pages.  I’ll probably put the sources on github later, for easier access.

We’ll be making use of handlers, macros, a tiny domain-specific language, and other features.  I’m an engine writer, I don’t like doing user interface stuff, and have little experience with it, so I’ll be writing my code from the engine outward, but with an eye to plugging in different front-ends.  The C++ version of the calculator is an interactive, ncurses-based program, but I may aim for a stream-processing calculator and for a proper GUI version, we’ll see what happens when we get to the front-end.