Perhaps the simplest way is to just evaluate expressions, e.g.,
if you were experimenting with the small machine and issued the
following commands:
(include-book "small-machine")
(in-package "SMALL-MACHINE")
(defun demo-state nil
(st :pc '(time . 0)
:stk nil
:mem '(7 11 3 4 5)
:halt nil
:code (list (times-program))))
(sm (demo-state) 1)
It seems like sm did nothing. Why didn't it increment the
program counter? Here is what I mean by evaluating expressions.
We just take the definition of sm:
(defun sm (s n)
(if (zp n)
s
(sm (step s) (+ n -1))))
and replace the first line with a let* to get:
(let* ((s (demo-state))
(n 1))
(if (zp n)
s
(sm (step s) (+ n -1))))
This is equivalent to the original call, but now by cutting and
pasting we can easily check subexpressions, e.g., we can check
(zp n)
(let* ((s (demo-state))
(n 1))
(zp n))
(after typing (set-ignore-ok t) to bypass the ACL2
warning generated) we get
nil for the above, which is what we expect, so now we decide to
check (step s)
(let* ((s (demo-state))
(n 1))
(step s))
which is just the original state, so the problem is in step.
We open the definition of step.
(let* ((s (demo-state))
(n 1))
(if (halt s)
s
(execute (current-instruction s) s)))
We now check halt
(let* ((s (demo-state))
(n 1))
(halt s))
This returns nil, as expected so we check
(let* ((s (demo-state))
(n 1))
(current-instruction s))
which is nil. That is a surprise, since this should return the
first instruction in times, so we open up current-instruction:
(let* ((s (demo-state))
(n 1))
(fetch (pc s) (code s)))
We check pc first.
(let* ((s (demo-state))
(n 1))
(pc s))
Seems ok. So let's check code.
(let* ((s (demo-state))
(n 1))
(code s))
Seems ok, so let's open up code's definition. Here is why we
were using let*, so that we can use earlier bindings.
(let* ((s (demo-state))
(n 1)
(pc (pc s))
(code (code s)))
(nth (cdr pc)
(cdr (assoc (car pc) code))))
Let's look at the first argument to nth.
(let* ((s (demo-state))
(n 1)
(pc (pc s))
(code (code s)))
(cdr pc))
Looks good. What about the 2nd argument? It should be the code
for times.
(let* ((s (demo-state))
(n 1)
(pc (pc s))
(code (code s)))
(cdr (assoc (car pc) code)))
We get nil, so this is the problem. Let's look at the first
argument to assoc:
(let* ((s (demo-state))
(n 1)
(pc (pc s))
(code (code s)))
(car pc))
Aha! We wrote "time" instead of "times".
If you want more powerful mechanisms, then you have to use the debugging facilities provided by the underlying lisp.
For example, if you are using GCL on Linux, then in then in the directory ~manolios/public/gcl-2.4.1/info you will find info files for GCL. The node user-interfaces describes (briefly) how to use step, trace, etc. to debug.
First, note that you do not want to debug compiled functions, so
you would not use include-book, but would rather submit each of
the function definitions to ACL2. Then when you are ready, get
into the underlying lisp by typing :q and then
you can call GCL's step function (note that you have to give the
full names of the SMALL-MACHINE functions)
(step (small-machine::sm (small-machine::demo-state) 1))
Alternatively, you can enter the SMALL-MACHINE package:
(in-package "SMALL-MACHINE")
and then issue the command
(lisp::step (sm (demo-state) 1))
Note, above you have to tell GCL where the step function is
(otherwise, you would call small-machine::step!). A this point,
follow the online instructions and experiment.