Return the last argument, perhaps with side effects
If you encounter a call of
(equal (return-last x y z) z)
It may also be useful to know that unlike other ACL2 functions,
ACL2 !>(defun foo (fn x y z) (return-last fn x (mv y z))) Since FOO is non-recursive, its admission is trivial. We observe that the type of FOO is described by the theorem (AND (CONSP (FOO FN X Y Z)) (TRUE-LISTP (FOO FN X Y Z))). We used primitive type reasoning. (FOO * * * *) => (MV * *). Summary Form: ( DEFUN FOO ...) Rules: ((:FAKE-RUNE-FOR-TYPE-SET NIL)) Time: 0.01 seconds (prove: 0.00, print: 0.00, other: 0.01) FOO ACL2 !>(foo 'bar 3 4 5) (4 5) ACL2 !>(mv-let (a b) (foo 'bar 3 4 5) (cons b a)) (5 . 4) ACL2 !>
Most readers would be well served to avoid reading the rest of this
documentation of
ACL2 !>:trans1 (prog2$ (cw "Some CW printing...~%") (+ 3 4)) (RETURN-LAST 'PROGN (CW "Some CW printing...~%") (+ 3 4)) ACL2 !>
Notice that a call of
ACL2 !>:q Exiting the ACL2 read-eval-print loop. To re-enter, execute (LP). ? (macroexpand-1 '(RETURN-LAST 'PROGN (CW "Some CW printing...~%") (+ 3 4))) (PROGN (LET ((*AOKP* T)) (CW "Some CW printing...~%")) (+ 3 4)) T ?
Thus, the original
Remark for those who use defattach. The binding of
In general, a form
ACL2 !>(with-guard-checking :none (car 3)) ; no guard violation NIL ACL2 !>:trans1 (with-guard-checking :none (car 3)) (WITH-GUARD-CHECKING1 (CHK-WITH-GUARD-CHECKING-ARG :NONE) (CAR 3)) ACL2 !>:trans1 (WITH-GUARD-CHECKING1 (CHK-WITH-GUARD-CHECKING-ARG :NONE) (CAR 3)) (RETURN-LAST 'WITH-GUARD-CHECKING1-RAW (CHK-WITH-GUARD-CHECKING-ARG :NONE) (CAR 3)) ACL2 !>:q Exiting the ACL2 read-eval-print loop. To re-enter, execute (LP). ? [RAW LISP] (macroexpand-1 '(RETURN-LAST 'WITH-GUARD-CHECKING1-RAW (CHK-WITH-GUARD-CHECKING-ARG :NONE) (CAR 3))) (WITH-GUARD-CHECKING1-RAW (CHK-WITH-GUARD-CHECKING-ARG :NONE) (CAR 3)) T ? [RAW LISP] (pprint (macroexpand-1 '(WITH-GUARD-CHECKING1-RAW (CHK-WITH-GUARD-CHECKING-ARG :NONE) (CAR 3)))) (LET ((ACL2_GLOBAL_ACL2::GUARD-CHECKING-ON (CHK-WITH-GUARD-CHECKING-ARG :NONE))) (DECLARE (SPECIAL ACL2_GLOBAL_ACL2::GUARD-CHECKING-ON)) (CAR 3)) ? [RAW LISP]
The above raw Lisp code binds the state global variable
The intended use of
When a form is submitted in the top-level loop, it is handled by ACL2's
custom evaluator. That evaluator is specified to respect the semantics of the
expression supplied to it: briefly put, if an expression
The following log shows how a time$ call can generate a call of the
evaluator for the last argument of
ACL2 !>:trans1 (time$ (+ 3 4)) (TIME$1 (LIST 0 NIL NIL NIL NIL) (+ 3 4)) ACL2 !>:trans1 (TIME$1 (LIST 0 NIL NIL NIL NIL) (+ 3 4)) (RETURN-LAST 'TIME$1-RAW (LIST 0 NIL NIL NIL NIL) (+ 3 4)) ACL2 !>(time$ (+ 3 4)) ; (EV-REC *RETURN-LAST-ARG3* ...) took ; 0.00 seconds realtime, 0.00 seconds runtime ; (1,120 bytes allocated). 7 ACL2 !>
We now show how things can go wrong in other than the ``intended use'' case
described above. In the example below, the macro
ACL2 !>(defttag t) TTAG NOTE: Adding ttag :T from the top level loop. T ACL2 !>(progn! (set-raw-mode t) (defmacro mac-raw (x y) `(progn (print (quote ,(cadr x))) (terpri) ; newline ,y))) Summary Form: ( PROGN! (SET-RAW-MODE T) ...) Rules: NIL Time: 0.01 seconds (prove: 0.00, print: 0.00, other: 0.01) NIL ACL2 !>(defmacro-last mac) [[ ... output omitted ... ]] RETURN-LAST-TABLE ACL2 !>(return-last 'mac-raw '3 nil) *********************************************** ************ ABORTING from raw Lisp *********** ********** (see :DOC raw-lisp-error) ********** Error: Fault during read of memory address #x120000300006 *********************************************** If you didn't cause an explicit interrupt (Control-C), then the root cause may be call of a :program mode function that has the wrong guard specified, or even no guard specified (i.e., an implicit guard of t). See :DOC guards. To enable breaks into the debugger (also see :DOC acl2-customization): (SET-DEBUGGER-ENABLE T) ACL2 !>(top-level (return-last 'mac-raw '3 nil)) 3 NIL ACL2 !>
We next describe how to extend the behavior of
(defttag :profiling) (progn! (set-raw-mode t) (load (concatenate 'string (cbd) "profiling-raw.lsp"))) (defmacro-last with-profiling)
The first event introduces a trust tag. The second loads a file into raw
Lisp that defines a macro,
The example above illustrates the following methodology for introducing a macro that returns its last argument but produces useful side-effects with raw Lisp code.
(1) Introduce a trust tag (see defttag).
(2) Using progn!, load into raw Lisp a file defining a macro,
RAW-NAME , that takes two arguments, returning its last (second) argument but using the first argument to produce desired side effects during evaluation of that last argument.(3) Evaluate the form
(defmacro-last NAME :raw RAW-NAME) . This will introduceNAME as an ACL2 macro that expands to a corresponding call ofRAW-NAME (see (2) above) in raw Lisp. The specification of keyword value of:raw asRAW-NAME may be omitted ifRAW-NAME is the result of modifying the symbolNAME by suffixing the string"-RAW" to the symbol-name ofNAME .
WARNING: Not every use of
(defttag t) (set-raw-mode-on state) (defmacro mac-raw (str y) ; print message is an atom `(let ((result (consp ,y)) (str ,str)) (or result (prog2$ (fmx ,str ',y) nil)))) (set-raw-mode-off state) (defmacro-last mac) ; Horrible error: (mac "Not a cons: ~x0~%" 17) ; Works, but probably many would consider it awkward to use top-level: (top-level (mac "Not a cons: ~x0~%" 17))
In such cases we suggest supplying keyword
(defmacro-last mac :top-level-ok nil)
Then any attempt to call
It is useful to explore what is done by
ACL2 !>:trans1 (defmacro-last with-profiling) (PROGN (DEFMACRO WITH-PROFILING (X Y) (LIST 'RETURN-LAST (LIST 'QUOTE 'WITH-PROFILING-RAW) X Y)) (TABLE RETURN-LAST-TABLE 'WITH-PROFILING-RAW 'WITH-PROFILING)) ACL2 !>:trans1 (with-profiling '(assoc-eq fgetprop rewrite) (mini-proveall)) (RETURN-LAST 'WITH-PROFILING-RAW '(ASSOC-EQ FGETPROP REWRITE) (MINI-PROVEALL)) ACL2 !>:q Exiting the ACL2 read-eval-print loop. To re-enter, execute (LP). ? [RAW LISP] (macroexpand-1 '(RETURN-LAST 'WITH-PROFILING-RAW '(ASSOC-EQ FGETPROP REWRITE) (MINI-PROVEALL))) (WITH-PROFILING-RAW '(ASSOC-EQ FGETPROP REWRITE) (MINI-PROVEALL)) T ? [RAW LISP]
To understand the macro
We mentioned above that ACL2 tends to print calls of prog2$ or
time$ (or other such utilities) instead of calls of
Calls of
*
progn , associated with prog2$
*mbe1-raw , associated withmbe1 , a version ofmbe
*ec-call1-raw , associated withec-call1 (a variant of ec-call)
*with-guard-checking1-raw , associated withwith-guard-checking1 (a variant of with-guard-checking)
Note that because of its special status, it is illegal to trace
For any object
We conclude by warning that as a user, you take responsibility for not
compromising the soundness or error handling of ACL2 when you define a macro
in raw Lisp and especially when you install it as a key of return-last-table, either directly or (more likely) using
The following is correct, and illustrates care taken to return multiple values.
:q (defmacro my-time1-raw (val form) (declare (ignore val)) `(let ((start-time (get-internal-run-time)) (result (multiple-value-list ,form)) (end-time (get-internal-run-time))) (format t "Total time: ~s~%" (float (/ (- end-time start-time) internal-time-units-per-second))) (values-list result))) (lp) (defttag t) (defmacro-last my-time1) (defmacro my-time (form) `(my-time1 nil ,form))
Then for example:
ACL2 !>(my-time (equal (make-list 1000000) (make-list 1000000))) Total time: 0.12 T ACL2 !>
But if instead we provide the following more naive implementation, of the above raw Lisp macro, the above evaluation can produce an error, for example if the host Lisp is CCL.
(defmacro my-time1-raw (val form) (declare (ignore val)) `(let ((start-time (get-internal-run-time)) (result ,form) (end-time (get-internal-run-time))) (format t "Total time: ~s~%" (float (/ (- end-time start-time) internal-time-units-per-second))) result)) ; WRONG -- need multiple values returned!
Here is a second, similar example. This time we'll start with the error; can you spot it?
(defttag t) (progn! (set-raw-mode t) (defmacro foo-raw (x y) `(prog1 ,y (cw "Message showing argument 1: ~x0~%" ,x)))) (defmacro-last foo)
We then can wind up with a hard Lisp error:
ACL2 !>(foo 3 (mv 4 5)) Message showing argument 1: 3 *********************************************** ************ ABORTING from raw Lisp *********** ********** (see :DOC raw-lisp-error) ********** Error: value NIL is not of the expected type REAL. *********************************************** If you didn't cause an explicit interrupt (Control-C), then the root cause may be call of a :program mode function that has the wrong guard specified, or even no guard specified (i.e., an implicit guard of t). See :DOC guards. To enable breaks into the debugger (also see :DOC acl2-customization): (SET-DEBUGGER-ENABLE T) ACL2 !>
Here is a corrected version of the above macro. The point here is that
(progn! (set-raw-mode t) (defmacro foo-raw (x y) `(our-multiple-value-prog1 ;; better ,y (cw "Message showing argument 1: ~x0~%" ,x))))
Function:
(defun return-last (fn eager-arg last-arg) (declare (ignore fn eager-arg) (xargs :guard (if (equal fn 'mbe1-raw) (equal last-arg eager-arg) t))) last-arg)