CLAUSE-PROCESSOR.html -- ACL2 Version 6.3

## CLAUSE-PROCESSOR

make or apply a `:clause-processor` rule (goal-level simplifier)

    Major Section:  RULE-CLASSES

See rule-classes for a general discussion of rule classes, including how they are used to build rules from formulas and a discussion of the various keywords in a rule class description.

    Example (which we'll return to, below):
    (defthm correctness-of-note-fact-clause-processor
      (implies (and (pseudo-term-listp cl)
                    (alistp a)
                    (evl0 (conjoin-clauses
                           (note-fact-clause-processor cl term))
                          a))
               (evl0 (disjoin cl) a))
      :rule-classes :clause-processor)

Also see define-trusted-clause-processor for documentation of an analogous utility that does not require the clause-processor to be proved correct. But please read the present documentation before reading about that utility. Both utilities designate functions ``clause-processors''. Such functions must be executable -- hence not constrained by virtue of being introduced in the signature of an `encapsulate` -- and must respect stobj and output arity restrictions. For example, something like `(car (mv ...))` is illegal; also see signature.

We begin this documentation with an introduction, focusing on an example, and then conclude with details. You might find it most useful simply to look at the examples in community books directory `books/clause-processors/`; see file `Readme.lsp` in that directory.

A `:clause-processor` rule installs a simplifier at the level of goals, where a goal is represented as a clause: a list of terms that is implicitly viewed as a disjunction (the application of `OR`). For example, if ACL2 prints a goal in the form `(implies (and p q) r)`, then the clause might be the one-element list containing the internal representation of this term -- `(implies (if p q 'nil) r)` -- but more likely, the corresponding clause is `((not p) (not q) r)`. Note that the members of a clause are translated terms; see term. For example, they do not contains calls of the macro `AND`, and constants are quoted.

Note that clause-processor simplifiers are similar to metafunctions, and similar efficiency considerations apply. See meta, in particular the discussion on how to ``make a metafunction maximally efficient.''

Unlike rules of class `:``meta`, rules of class `:clause-processor` must be applied by explicit `:clause-processor` hints; they are not applied automatically (unless by way of computed hints; see computed-hints). But `:clause-processor` rules can be useful in situations for which it is more convenient to code a simplifier that manipulates the entire goal clause rather than individual subterms of terms in the clause.

We begin with a simple illustrative example: a clause-processor that assumes an alleged fact (named `term` in the example) and creates a separate goal to prove that fact. We can extend the hypotheses of the current goal (named `cl` in the example) with a term by adding the negation of that term to the clause (disjunctive) representation of that goal. So the following returns a list of two clauses: the result of adding `term` as a hypothesis to the input clause, as just described, and a second clause consisting only of that term. This list of two clauses can be viewed as the conjunction of the first clause and the second clause (where again, each clause is viewed as a disjunction).

    (defun note-fact-clause-processor (cl term)
      (declare (xargs :guard t)) ; optional, for better efficiency
      (list (cons (list 'not term)
                  cl)
            (list term)))

As with `:``meta` rules, we need to introduce a suitable evaluator; see defevaluator if you want details. Since we expect to reason about the function `NOT`, because of its role in `note-fact-clause-processor` as defined above, we include `NOT` in the set of functions known to this evaluator. We also include `IF`, as is often a good idea.

    (defevaluator evl0 evl0-list
      ((not x) (if x y z)))

ACL2 can now prove the following theorem automatically. (This is the example displayed at the outset of this documentation topic.) Of course, `:clause-processor` rules about clause-processor functions less trivial than `note-fact-clause-processor` may require lemmas to be proved first! The function `disjoin` takes a clause and returns its disjunction (the result of applying `OR` to its members), and `conjoin-clauses` applies `disjoin` to every element of a given list of clauses and then conjoins (applies `AND`) to the corresponding list of resulting terms.

    (defthm correctness-of-note-fact-clause-processor
      (implies (and (pseudo-term-listp cl)
                    (alistp a)
                    (evl0 (conjoin-clauses
                           (note-fact-clause-processor cl term))
                          a))
               (evl0 (disjoin cl) a))
      :rule-classes :clause-processor)

Now let us submit a silly but illustrative example theorem to ACL2, to show how a corresponding `:clause-processor` hint is applied. The hint says to apply the clause-processor function, `note-fact-clause-processor`, to the current goal clause and a ``user hint'' as the second argument of that function, in this case `(equal a a)`. Thus, a specific variable, `clause`, is always bound to the current goal clause for the evaluation of the `:clause-processor` hint, to produce a list of clauses. Since two subgoals are created below, we know that this list contained two clauses. Indeed, these are the clauses returned when `note-fact-clause-processor` is applied to two arguments: the current clause, which is the one-element list `((equal (car (cons x y)) x))`, and the user hint, `(equal a a)`.

    ACL2 !>(thm (equal (car (cons x y))
                       x)
                :hints
                (("Goal"
                  :clause-processor
                  (note-fact-clause-processor clause '(equal a a)))))

    [Note:  A hint was supplied for our processing of the goal above.
    Thanks!]

    We now apply the verified :CLAUSE-PROCESSOR function NOTE-FACT-CLAUSE-
    PROCESSOR to produce two new subgoals.

    Subgoal 2
    (IMPLIES (EQUAL A A)
             (EQUAL (CAR (CONS X Y)) X)).

    But we reduce the conjecture to T, by the :executable-counterpart of
    IF and the simple :rewrite rule CAR-CONS.

    Subgoal 1
    (EQUAL A A).

    But we reduce the conjecture to T, by primitive type reasoning.

    Q.E.D.

    Summary
    Form:  ( THM ...)
    Rules: ((:EXECUTABLE-COUNTERPART IF)
            (:EXECUTABLE-COUNTERPART NOT)
            (:FAKE-RUNE-FOR-TYPE-SET NIL)
            (:REWRITE CAR-CONS))
    Warnings:  None
    Time:  0.00 seconds (prove: 0.00, print: 0.00, other: 0.00)

    Proof succeeded.
    ACL2 !>

That concludes our introduction to clause-processor rules and hints. We turn now to detailed documentation.

The signature of a clause-processor function, `CL-PROC`, must have one of the following forms. Here, each `st_i` is a stobj (possibly `state`) while the other parameters and results are not stobjs (see stobj). Note that there need not be input stobjs in [3] -- i.e., `k` can be 0 -- and even if there are, there need not be output stobjs.

    [1]  ((CL-PROC cl) => cl-list)

    [2]  ((CL-PROC cl hint) => cl-list)

    [3]  ((CL-PROC cl hint st_1 ... st_k) => (mv erp cl-list st_i1 ... st_in))

In [3], we think of the first component of the result as an error flag. Indeed, a proof will instantly abort if that error flag is not `nil`.

We next discuss the legal forms of `:clause-processor` rules, followed below by a discussion of `:clause-processor` hints. In the discussion below, we use lower-case names to represent specific symbols, for example `implies`, and we use upper-case names to represent more arbitrary pieces of syntax (which we will describe), for example, `CL`.

If a `:``rule-classes` specification includes `:clause-processor`, then the corresponding term must have the following form. (Additional ``meta-extract'' hypotheses, not shown or discussed below, may be included as desired in order to use facts from the logical `world` to help prove the rule; see meta-extract for explanation of this advanced feature.)

    (implies (and (pseudo-term-listp CL)
                  (alistp A)
                  (EVL (conjoin-clauses <CL-LIST>)
                        B))
             (EVL (disjoin CL) A))

Here `EVL` is a known evaluator; `CL` and `A` are distinct non-stobj variables; and `<CL-LIST>` is an expression representing the clauses returned by the clause-processor function `CL-PROC`, whose form depends on the signature of that function, as follows. Typically `B` is `A`, but it can be any term (useful when generalization is occurring; see the example ``Test generalizing alist'' in community book `books/clause-processors/basic-examples.lisp`). For cases [1] and [2] above, `<CL-LIST>` is of the form `(CL-PROC CL)` or `(CL-PROC CL HINT)`, respectively, where in the latter case `HINT` is a non-stobj variable distinct from the variables `CL` and `A`. For case [3], `<CL-LIST>` is of the form

    (clauses-result (CL-PROC CL HINT st_1 ... st_k))

where the `st_i` are the specific stobj names mentioned in [3]. Logically, `clauses-result` returns the `cadr` if the `car` is `NIL`, and otherwise (for the error case) returns a list containing the empty (false) clause. So in the non-error case, `clauses-result` picks out the second result, denoted `cl-list` in [3] above, and in the error case the implication above trivially holds.

In the above theorem, we are asked to prove `(EVL (disjoin CL) A)` assuming that the conjunction of all clauses produced by the clause processor evaluates to a non-`nil` value under some alist `B`. In fact, we can choose `B` so as to allow us to assume evaluations of the generated clauses over many different alists. This technique is discussed in the community book `books/clause-processors/multi-env-trick.lisp`, which introduces some macros that may be helpful in accomplishing proofs of this type.

The clause-processor function, `CL`, must have a guard that ACL2 can trivially prove from the hypotheses that the first argument of `CL` is known to be a `pseudo-term-listp` and any stobj arguments are assumed to satisfy their stobj predicates.

Next we specify the legal forms for `:clause-processor` hints. These depend on the signature as described in [1] through [3] above. Below, as above, `CL-PROC` is the clause-processor function, and references to ```clause`'' refer to that exact variable (not, for example, to `cl`). In each of the three cases, the forms shown for that case are equivalent; in particular, the `:function` syntax is simply a convenience for the final form in each case.

Signature [1], `((cl-proc cl) => cl-list)`:

    :clause-processor CL-PROC
    :clause-processor (:function CL-PROC)
    :clause-processor (CL-PROC clause)

or any term macroexpanding to `(CL-PROC clause)`.

Signature [2], ((cl-proc cl hint) => cl-list):

    :clause-processor (:function CL-PROC :hint HINT)
    :clause-processor (CL-PROC clause HINT)

or any term macroexpanding to `(CL-PROC clause HINT)`, where `HINT` is any term with at most `CLAUSE` free.

Signature [3], ((CL-PROC cl hint ...) => (mv erp cl-list ...))

    :clause-processor (:function CL-PROC :hint HINT)
    :clause-processor (CL-PROC clause HINT st_1 ... st_k)

or any term macroexpanding to `(CL-PROC clause HINT st_1 ... st_k)`, where `HINT` is any term with at most `CLAUSE` free.

A `:clause-processor` hint causes the proof to abort if the result returned by evaluating the suitable `CL-PROC` call, as above, is not a list of clauses, i.e., a list of (translated) term lists. The proof also aborts if in case [3] the first (`erp`) value returned is not `nil`, in which case `erp` is used for printing an error message as follows: if it is a string, then that string is printed; but if it is a non-empty true list whose first element is a string, then it is printed as though by `(fmt ~@0 (list (cons #\0 erp)) ...)` (see fmt). Otherwise, a non-`nil` `erp` value causes a generic error message to be printed.

If there is no error as above, but the `CL-PROC` call returns clause list whose single element is equal to the input clause, then the hint is ignored since we are left with the goal with which we started. In that case, the other prover processes are then applied as usual.

You can see all current `:clause-processor` rules by issuing the following command: `(print-clause-processor-rules)`.

The following paper discusses ACL2 clause-processors at a high level suitable for a non-ACL2 audience:

> M. Kaufmann, J S. Moore, S. Ray, and E. Reeber, ``Integrating External Deduction Tools with ACL2.'' Journal of Applied Logic (Special Issue: Empirically Successful Computerized Reasoning), Volume 7, Issue 1, March 2009, pp. 3--25. Also published online (DOI `10.1016/j.jal.2007.07.002`). Preliminary version in: Proceedings of the 6th International Workshop on the Implementation of Logics (IWIL 2006) (C. Benzmueller, B. Fischer, and G. Sutcliffe, editors), CEUR Workshop Proceedings Vol. 212, Phnom Penh, Cambodia, pp. 7-26, November 2006, http://ceur-ws.org/Vol-212/.