Major Section: EVENTS
Make-event
is a utility for generating events. It provides a
capability not offered by Lisp macros (see defmacro), as it allows access to
the ACL2 state
and logical world. In essence, the expression
(make-event form)
replaces itself with the result of evaluating form
,
say, ev
, as though one had submitted ev
instead of the make-event
call. For example, (make-event (quote (defun f (x) x)))
is equivalent to
the event (defun f (x) x)
.
We break this documentation into the following sections.
Introduction
Detailed Documentation
Error Reporting
Restriction to Event Contexts
Examples Illustrating How to Access State
Advanced Expansion Control
We begin with an informal introduction, which focuses on examples and introduces the key notion of ``expansion phase''.
Introduction
Make-event
is particularly useful for those who program using the ACL2
state
; see programming-with-state. That is because the evaluation of
form
may read and even modify the ACL2 state
.
Suppose for example that we want to define a constant *world-length*
,
that is the length of the current ACL2 world. A make-event
form can
accomplish this task, as follows.
ACL2 !>(length (w state)) 98883 ACL2 !>(make-event (list 'defconst '*world-length* (length (w state)))) Summary Form: ( DEFCONST *WORLD-LENGTH* ...) Rules: NIL Time: 0.00 seconds (prove: 0.00, print: 0.00, other: 0.00) Summary Form: ( MAKE-EVENT (LIST ...)) Rules: NIL Time: 0.01 seconds (prove: 0.00, print: 0.00, other: 0.01) *WORLD-LENGTH* ACL2 !>*world-length* 98883 ACL2 !>(length (w state)) 98890 ACL2 !>How did this work? First, evaluation of the form
(list 'defconst '*world-length* (length (w state)))
returned the event
form (defconst *world-length* 98883)
. Then that event form was
automatically submitted to ACL2. Of course, that changed the ACL2 logical
world, which is why the final value of (length (w state))
is greater
than its initial value.The example above illustrates how the evaluation of a make-event
call
takes place in two phases. The first phase evaluates the argument of the
call, in this case (list 'defconst '*world-length* (length (w state)))
,
to compute an event form, in this case (defconst *world-length* 98883)
.
We call this evaluation the ``expansion'' phase. Then the resulting event
form is evaluated, which in this case defines the constant
*world-length*
.
Now suppose we would like to introduce such a defconst
form any time we
like. It is common practice to define macros to automate such tasks. Now we
might be tempted simply to make the following definition.
; WRONG! (defmacro define-world-length-constant (name state) (list 'defconst name (length (w state))))But ACL2 rejects such a definition, because a macro cannot take the ACL2 state as a parameter; instead, the formal parameter to this macro named
"STATE"
merely represents an ordinary object. You can try to
experiment with other such direct methods to define such a macro, but they
won't work.Instead, however, you can use the approach illustrated by the make-event
example above to define the desired macro, as follows.
(defmacro define-world-length-constant (name) `(make-event (list 'defconst ',name (length (w state)))))Here are example uses of this macro.
ACL2 !>(define-world-length-constant *foo*) Summary Form: ( DEFCONST *FOO* ...) Rules: NIL Time: 0.00 seconds (prove: 0.00, print: 0.00, other: 0.00) Summary Form: ( MAKE-EVENT (LIST ...)) Rules: NIL Time: 0.00 seconds (prove: 0.00, print: 0.00, other: 0.00) *FOO* ACL2 !>*foo* 98891 ACL2 !>:pe *foo* 2:x(DEFINE-WORLD-LENGTH-CONSTANT *FOO*) > (DEFCONST *FOO* 98891) ACL2 !>(length (w state)) 98897 ACL2 !>(define-world-length-constant *bar*) Summary Form: ( DEFCONST *BAR* ...) Rules: NIL Time: 0.00 seconds (prove: 0.00, print: 0.00, other: 0.00) Summary Form: ( MAKE-EVENT (LIST ...)) Rules: NIL Time: 0.01 seconds (prove: 0.00, print: 0.00, other: 0.01) *BAR* ACL2 !>*bar* 98897 ACL2 !>:pe *bar* 3:x(DEFINE-WORLD-LENGTH-CONSTANT *BAR*) > (DEFCONST *BAR* 98897) ACL2 !>(length (w state)) 98903 ACL2 !>
Finally, we note that the expansion phase can be used for computation that has side effects, generally by modifying state. Here is a modification of the above example that does not change the world at all, but instead saves the length of the world in a state global.
(make-event (pprogn (f-put-global 'my-world-length (length (w state)) state) (value '(value-triple nil))))Notice that this time, the value returned by the expansion phase is not an event form, but rather, is an error triple (see error-triples) whose value component is an event form, namely, the event form
(value-triple nil)
.
Evaluation of that event form does not change the ACL2 world
(see value-triple). Thus, the sole purpose of the make-event
call above
is to change the state by associating the length of the current logical
world with the state global named 'my-world-length
. After evaluating
this form, (@ my-world-length)
provides the length of the ACL2 world, as
illustrated by the following transcript.
ACL2 !>:pbt 0 0:x(EXIT-BOOT-STRAP-MODE) ACL2 !>(length (w state)) 98883 ACL2 !>(make-event (pprogn (f-put-global 'my-world-length (length (w state)) state) (value '(value-triple nil)))) Summary Form: ( MAKE-EVENT (PPROGN ...)) Rules: NIL Time: 0.01 seconds (prove: 0.00, print: 0.00, other: 0.01) NIL ACL2 !>(length (w state)) 98883 ACL2 !>:pbt 0 0:x(EXIT-BOOT-STRAP-MODE) ACL2 !>
When make-event
is invoked by a book, it is expanded during book
certification but not, by default, when the book is included. So for the
example (define-world-length-constant *foo*)
given above, if that form is
in a book, then the value of *foo*
will be the length of the world at the
time this form was invoked during book certification, regardless of world
length at include-book
time. (The expansion is recorded in the book's
certificate, and re-used.) To overcome this default, you can specified
keyword value :CHECK-EXPANSION t
. This will cause an error if the
expansion is different, but it can be useful for side effects. For example,
if you insert the following form in a book, then the length of the world will
be printed when the form is encountered, whether during certify-book
or
during include-book
.
(make-event (pprogn (fms "Length of current world: ~x0~|" (list (cons #\0 (length (w state)))) *standard-co* state nil) (value '(value-triple nil))) :check-expansion t)
make-event
expansion
Detailed Documentation
Examples: ; Trivial example: evaluate (quote (defun foo (x) x)) to obtain ; (defun foo (x) x), which is then evaluated. (make-event (quote (defun foo (x) x))) ; Evaluate (generate-form state) to obtain (mv nil val state), and ; then evaluate val. (Generate-form is not specified here, but ; imagine for example that it explores the state and then generates ; some desired definition or theorem.) (make-event (generate-form state)) ; As above, but make sure that if this form is in a book, then when ; we include the book, the evaluation of (generate-form state) ; should return the same value as it did when the book was ; certified. (make-event (generate-form state) :CHECK-EXPANSION t) ; As above (where the :CHECK-EXPANSION value can be included or ; not), where if there is an error during expansion, then the error ; message will explain that expansion was on behalf of the indicated ; object, typically specified as the first argument. (make-event (generate-form state) :ON-BEHALF-OF (generate-form state)) General Form: (make-event form :CHECK-EXPANSION chk :ON-BEHALF-OF obj :EXPANSION? form)where
chk
is nil
(the default), t
, or the intended ``expansion
result'' from the evaluation of form
(as explained below); and if
supplied, obj
is an arbitrary ACL2 object, used only in reporting errors
in expansion, i.e., in the evaluation of form. The :EXPANSION?
keyword
is discussed in the final section, on Advanced Expansion Control.We strongly recommend that you browse some .lisp
files in the community
books directory books/make-event/
. You may even find it helpful, in
order to understand make-event
, to do so before continuing to read this
documentation. You may also find it useful to browse community book
books/misc/eval.lisp
, which contains definitions of macros
must-succeed
and must-fail
that are useful for testing and are used
in many books in the books/make-event/
directory, especially
eval-tests.lisp
. Another example, books/make-event/defrule.lisp
,
shows how to use macros whose calls expand to make-event
forms, which in
turn can generate events. For more examples, see file
books/make-event/Readme.lsp
. Other than the examples, the explanations
here should suffice for most users. If you want explanations of subtler
details, see make-event-details.
Note that make-event
may only be used at the ``top level'' or where an
event is expected. See the section ``Restriction to Event Contexts'', below.
Make-event
is related to Lisp macroexpansion in the sense that its
argument is evaluated to obtain an expansion result, which is evaluated
again. Let us elaborate on each of these notions in turn: ``is evaluated,''
``expansion result'', and ``evaluated again.'' The final section, on
Advanced Expansion Control, will generalize these processes in a way that we
ignore for now.
``is evaluated'' -- The argument can be any expression, which is evaluated as would be any expression submitted to ACL2's top level loop. Thus,
state
and user-definedstobj
s may appear in the form supplied tomake-event
. Henceforth, we will refer to this evaluation as ``expansion.'' Expansion is actually done in a way that restores ACL2's built-instate
global variables, including the logical world, to their pre-expansion values (with a few exceptions -- see make-event-details -- and where we note that changes to user-definedstate
global variables (see assign) are preserved). So, for example, events might be evaluated during expansion, but they will disappear from the logical world after expansion returns its result. Moreover, proofs are enabled by default at the start of expansion (see ld-skip-proofsp) if keyword:CHECK-EXPANSION
is supplied and has a non-nil
value.``expansion result'' -- The above expansion may result in an ordinary (non-
state
, non-stobj
) value, which we call the ``expansion result.'' Or, expansion may result in a multiple value of the form(mv erp val state)
, or, more generally,(mv erp val state stobj-1 ... stobj-k)
where eachstobj-i
is a stobj; then the expansion result isval
unlesserp
is notnil
, in which case there is no expansion result, and the originalmake-event
evaluates to a soft error. In either case (single or multiple value), eitherval
is an embedded event form (see embedded-event-form), or else the originalmake-event
evaluates to a soft error, printed as described under ``Error Reporting'' below.``evaluated again'' -- the expansion result is evaluated in place of the original
make-event
.
The expansion process can invoke subsidiary calls of make-event
, and
the expansion result can (perhaps after macroexpansion) be a call of
make-event
. It can be useful to track all these make-event
calls.
The state global variable make-event-debug
may be set to a
non-nil
value, for example (assign make-event-debug t)
, in order to
see a trace of the expansion process, where a level is displayed (as in
``3>
'') to indicate the depth of subsidiary expansions.
Expansion of a make-event
call will yield an event that replaces the
original make-event
call. In particular, if you put a make-event
form in a book, then in essence it is replaced by its expansion result,
created during the proof pass of the certify-book
process. We now
elaborate on this idea of keeping the original expansion.
A make-event
call generates a ``make-event
replacement'' that may be
stored by the system. In the simplest case, this replacement is the
expansion result. When a book is certified, these replacements are stored in
a book's certificate (technically, in the :EXPANSION-ALIST
field). Thus,
although the book is not textually altered during certification, one may
imagine a ``book expansion'' corresponding to the original book, in which
events are substituted by replacements that were generated during the proof
phase of certification. A subsequent include-book
will then include
the book expansion corresponding to the indicated book. When a book is
compiled during certify-book
, it is actually the corresponding book
expansion, stored as a temporary file, that is compiled instead. That
temporary file is deleted after compilation unless one first evaluates the
form (assign keep-tmp-files t)
. Note however that all of the original
forms must still be legal events; see embedded-event-form. So for
example, if the first event in a book is (local (defmacro my-id (x) x))
,
and is followed by (my-id (make-event ...))
, the final
``include-book
'' pass of certify-book
will fail because my-id
is not defined when the my-id
call is encountered.
A make-event
replacement might not be the expansion when either of the
keyword arguments :CHECK-EXPANSION
or :EXPANSION?
is supplied. We
deal with the latter in the final section, on Advanced Expansion Control. If
:CHECK-EXPANSION t
is supplied and the expansion is exp
, then the
replacement is obtained from the original make-event
call, by
substituting exp
for t
as the value of keyword :CHECK-EXPANSION
.
Such a make-event
call -- during the second pass of an
encapsulate
or during event processing on behalf of include-book
-- will do the expansion again and check that the expansion result is equal
to the original expansion result, exp
. In the unusual case that you know
the expected expansion result, res
, you can specify
:CHECK-EXPANSION res
in the first place, so that the check is also done
during the initial evaluation of the make-event
form. IMPORTANT BUT
OBSCURE DETAIL: That expansion check is only done when processing events, not
during a preliminary load of a book's compiled file. The following paragraph
elaborates.
(Here are details on the point made just above, for those who use the
:CHECK-EXPANSION
argument to perform side-effects on the state.
When you include a book, ACL2 generally loads a compiled file before
processing the events in the book; see book-compiled-file. While it is true
that a non-nil
:CHECK-EXPANSION
argument causes include-book
to
perform expansion of the make-event
form during event processing it does
not perform expansion when the compiled file (or expansion file; again,
see book-compiled-file) is loaded.)
ACL2 performs the following space-saving optimization: when the expansion
result is a local
event, then the make-event
replacement is
(local (value-triple :ELIDED))
.
The notion of ``expansion'' and ``replacement'' extend to the case that a
call of make-event
is found in the course of macroexpansion. The
following example illustrates this point.
(encapsulate () (defmacro my-mac () '(make-event '(defun foo (x) x))) (my-mac)) :pe :hereThe above call of
pe
shows that the form (my-mac)
has a
make-event
expansion (and replacement) of (DEFUN FOO (X) X)
:
(ENCAPSULATE NIL (DEFMACRO MY-MAC NIL '(MAKE-EVENT '(DEFUN FOO (X) X))) (RECORD-EXPANSION (MY-MAC) (DEFUN FOO (X) X)))
Error Reporting
Suppose that expansion produces a soft error as described above. That is,
suppose that the argument of a make-event
call evaluates to a multiple
value (mv erp val state ...)
where erp
is not nil
. If erp
is
a string, then that string is printed in the error message. If erp
is
a cons
pair whose car
is a string, then the error prints
"~@0"
with #\0
bound to that cons
pair; see fmt. Any other
non-nil
value of erp
causes a generic error message to be printed.
Restriction to Event Contexts
A make-event
call must occur either at the top level, or during
make-event
expansion, or as an argument of an event constructor. We
explain in more detail below. This restriction is imposed to enable ACL2 to
track expansions produced by make-event
.
The following examples illustrate this restriction.
; Legal: (progn (with-output :on summary (make-event '(defun foo (x) x)))) ; Illegal: (mv-let (erp val state) (make-event '(defun foo (x) x)) (mv erp val state))
More precisely: a make-event
call that is not itself evaluated during
make-event
expansion is subject to the following requirement. After
macroexpansion has taken place, such a make-event
call must be in an
``event context'', defined recursively as follows. (All but the first two
cases below correspond to similar cases for constructing events;
see embedded-event-form.)
o A form submitted at the top level, or more generally, supplied to a call of
ld
, is in an event context.o A form occurring at the top level of a book is in an event context.
o If
(
LOCAL
x1)
is in an event context, then so isx1
.o If
(
SKIP-PROOFS
x1)
is in an event context, then so isx1
.o If
(
MAKE-EVENT
x ...)
is in an event context and its expansionx1
is an embedded event form, thenx1
is in an event context.o If
(
WITH-OUTPUT
... x1)
,(
WITH-PROVER-STEP-LIMIT
... x1 ...)
, or(
WITH-PROVER-TIME-LIMIT
... x1)
is in an event context, then so isx1
.o For any call of
PROGN
orPROGN!
, each of its arguments is in an event context.o For any call of
ENCAPSULATE
, each of its arguments except the first (the signature list) is in an event context.o If
(RECORD-EXPANSION x1 x2)
is in an event context, thenx1
andx2
are in event contexts. Note:record-expansion
is intended for use only by the implementation, which imposes the additional restriction thatx1
and its subsidiarymake-event
calls (if any) must specify a:CHECK-EXPANSION
argument that is a consp.
Low-level remark, for system implementors. There is the one exception to
the above restriction: a single state-global-let*
form immediately
under a progn!
call. For example:
(progn! (state-global-let* <bindings> (make-event ...)))However, the following form may be preferable (see progn!):
(progn! :STATE-GLOBAL-BINDINGS <bindings> (make-event ...))Also see remove-untouchable for an interesting use of this exception.
Examples Illustrating How to Access State
You can modify the ACL2 state by doing your state-changing computation during the expansion phase, before expansion returns the event that is submitted. Here are some examples.
First consider the following. Notice that expansion modifies state global
my-global
during make-event
expansion, and then expansion returns a
defun
event to be evaluated.
(make-event (er-progn (assign my-global (length (w state))) (value '(defun foo (x) (cons x x)))))Then we get:
ACL2 !>(@ my-global) 72271 ACL2 !>:pe foo L 1:x(MAKE-EVENT (ER-PROGN # #)) >L (DEFUN FOO (X) (CONS X X)) ACL2 !>
Here's a slightly fancier example, where the computation affects the
defun
. In a new session, execute:
(make-event (er-progn (assign my-global (length (w state))) (value `(defun foo (x) (cons x ,(@ my-global))))))Then:
ACL2 !>(@ my-global) 72271 ACL2 !>:pe foo L 1:x(MAKE-EVENT (ER-PROGN # #)) >L (DEFUN FOO (X) (CONS X 72271)) ACL2 !>Note that ACL2 table events may avoid the need to use state globals. For example, instead of the example above, consider this example in a new session.
(make-event (let ((world-len (length (w state)))) `(progn (table my-table :STORED-WORLD-LENGTH ,world-len) (defun foo (x) (cons x ,world-len)))))Then:
ACL2 !>(table my-table) ((:STORED-WORLD-LENGTH . 72271)) ACL2 !>:pe foo 1:x(MAKE-EVENT (LET # #)) >L (DEFUN FOO (X) (CONS X 72271)) ACL2 !>
By the way, most built-in state globals revert after expansion. But
your own global (like my-global
above) can be set during expansion, and
the new value will persist.
Advanced Expansion Control
We conclude this documentation section by discussing three kinds of
additional control over make-event
expansion. These are all illustrated
in community book books/make-event/make-event-keywords-or-exp.lisp
.
The discussion below is split into the following three parts.
(1) The value produced by expansion may have the form (:DO-PROOFS exp)
,
which specifies exp
as the expansion result, to be evaluated without
skipping proofs even when including a book.
(2) The value produced by expansion may have the form
(:OR exp-1 ... exp-k)
, which specifies that the first form exp-i
to
evaluate without error is the expansion result.
(3) The keyword argument :EXPANSION?
can serve to eliminate the storing
of make-event
replacements, as described above for the ``book expansion''
of a book.
We now elaborate on each of these.
(1) :DO-PROOFS
``call'' produced by expansion.
We have discussed the expansion result produced by the expansion phase of
evaluating a make-event
call. However, if the expansion phase produces
an expression of the form (:DO-PROOFS exp)
, then the expansion result is
actually exp
. The :DO-PROOFS
wrapper indicates that even if proofs
are currently being skipped (see ld-skip-proofsp), then evaluation of
exp
should take place with proofs not skipped. For example, proofs will
be performed when evaluating the make-event
expansion, namely the
indicated defthm
event, in the following example.
(set-ld-skip-proofsp t state) (make-event '(:DO-PROOFS (defthm app-assoc (equal (append (append x y) z) (append x y z)))))
Note that such use of :DO-PROOFS
causes proofs to be performed when
evaluating the expansion while including an uncertified book. But when
including a certified book, then unless :CHECK-EXPANSION
is supplied a
non-nil
value, the make-event
replacement will just be the expansion,
which does not include the :DO-PROOFS
wrapper and hence will be evaluated
with proofs skipped.
(2) :OR
``call'' produced by expansion.
There may be times where you want to try different expansions. For example,
the community book books/make-event/proof-by-arith.lisp
attempts to admit
a given event, which we'll denote EV
, by trying events of the following
form as BOOK
varies over different community books.
(encapsulate () (local (include-book BOOK :DIR :SYSTEM)) EV)A naive implementation of this macro would evaluate all such
encapsulate
events until one succeeds, and then return that successful
event as the expansion. Then that event would need to be evaluated again!
With some hacking one could avoid that re-evaluation by using
skip-proofs
, but that won't work if you are trying to create a
certified book without skipped proofs. Instead, the implementation creates
an expansion of the form (:OR ev-1 ev-2 ... ev-k)
, where the list
(ev-1 ev-2 ... ev-k)
enumerates the generated encapsulate events. In
general, for this ``disjunctive case'' of a result from expansion, each
ev-i
is evaluated in sequence, and the first that succeeds without error
is considered to be the expansion result -- and a repeat evaluation is
avoided. If evaluation of each ev-i
results in an error, then so does
the make-event
call.This special use of :OR
in a value produced by expansion is only
supported at the top level. That is, the result can be
(:OR ev-1 ev-2 ... ev-k)
but then each ev-i
must be a legal expansion
result, without such further use of :OR
-- except, ev-i
may be
(:DO-PROOFS ev-i')
, where ev-i'
then would serve as the expansion
rather than ev-i
.
(3) The :EXPANSION?
keyword argument.
If keyword argument :EXPANSION?
has a nonnil
value, then the
:CHECK-EXPANSION
keyword must be omitted or have value nil
or t
,
hence not a cons pair.
The idea of the :EXPANSION?
keyword is to give you a way to avoid storing
expansion results in a book's certificate. Roughly speaking, when the
expansion result matches the value of :EXPANSION?
, then no expansion
result is stored for the event by book certification; then when the book is
later included, the value of :EXPANSION?
is used as the expansion, thus
bypassing the expansion phase. One could say that the event is its own
make-event replacement, but it is more accurate to say that there is no
make-event replacement at all, since nothing is stored in the certificate for
this event. Below, we elaborate on make-event replacements when
:EXPANSION
is used and also discuss other properties of this keyword.
We modify the notion of ``expansion result'' for make-event
forms to
comprehend the use of the :EXPANSION?
keyword. For that purpose, let's
consider a call of make-event
to be ``reducible'' if it has an
:EXPANSION?
keyword with non-nil
value, exp
, and its
:CHECK-EXPANSION
keyword is missing or has value nil
, in which case
the ``reduction'' of this make-event
call is defined to be exp
. The
expansion result as originally defined is modified by the following
``recursive reduction'' process: recur through the original expansion,
passing through calls of local
, skip-proofs
, with-output
,
with-prover-step-limit
, and with-prover-time-limit
, and
replacing (recursively) any reducible call of make-event
by its
reduction. Furthermore, we refer to two forms as ``reduction equivalent'' if
their recursive reductions are equal. Note that the recursive reduction
process does not pass through progn
or encapsulate
, but that
process is applied to the computation of expansions for their subsidiary
make-event
calls.
To explain further the effect of :EXPANSION? exp
, we split into the
following two cases.
o Case 1: Evaluation is not taking place when including a book or evaluating
the second pass of an encapsulate
event; more precisely, the value of
(ld-skip-proofsp state)
is not the symbol INCLUDE-BOOK
. There are
two subcases.
- Case 1a: The expansion result is not reduction-equivalent to
exp
. Then themake-event
call is processed as though the:EXPANSION?
keyword had been omitted.- Case 2a: The expansion result is reduction-equivalent to
exp
. Then there is nomake-event
replacement for this call ofmake-event
; no replacement will be put into the certificate file for a book containing thismake-event
call. When that book is subsequently included, the original form will be evaluated in the manner described in the next case.
o Case 2: Evaluation is taking place when including a book or evaluating the
second pass of an encapsulate
event; more precisely, the value of
(ld-skip-proofsp state)
is the symbol INCLUDE-BOOK
. Then the
expansion is exp
. The expansion phase is skipped unless
:CHECK-EXPANSION
is t
.
The :EXPANSION?
keyword can be particularly useful in concert with the
disjunctive (``:OR
'') case (2) discussed above. Suppose that expansion
produces a value as discussed in (2) above, (:OR exp-1 ... exp-k)
. If
one of these expressions exp-i
is more likely than the others to be the
expansion, then you may wish to specify :EXPANSION? exp-i
, as this will
avoid storing a make-event
replacement in that common case. This could
be useful if the expressions are large, to avoid enlarging the
certificate file for a book containing the make-event
call.
It is legal to specify both :EXPANSION? exp
and :CHECK-EXPANSION t
.
When either (ld-skip-proofsp state)
is the symbol INCLUDE-BOOK
, or
evaluation is taking place in raw Lisp, then this combination is treated the
same as if :EXPANSION?
is omitted and the value of :CHECK-EXPANSION
is exp
. Otherwise, this combination is treated the same as
:CHECK-EXPANSION t
, modified to accommodate the effect of :EXPANSION?
as discussed above: if the expansion is indeed the value of :EXPANSION?
,
then no make-event
replacement is generated.