Subject: Re: Enumerations
From: rpw3@rpw3.org (Rob Warnock)
Date: Sat, 10 Nov 2007 04:56:14 -0600
Newsgroups: comp.lang.lisp
Message-ID: <joCdna9W_8bTE6janZ2dnUVZ_tmhnZ2d@speakeasy.net>
R. Matthew Emerson <rme@clozure.com> wrote:
+---------------
| Don Geddis <don@geddis.org> writes:
| > I had a sudden idea: maybe I should just use variables that
| > evaluate to integers:
| >         (in-package "ENUM")
| >         (defparameter elementary 0)
+---------------

DEFCONSTANT would probably be more appropriate here than DEFPARAMETER.

CMUCL does the same thing a lot in the UNIX package when defining bits
needed for system calls, e.g., from "cmucl-19c/src/unix.lisp":

    (defconstant o_rdonly 0 "Read-only flag.") 
    (defconstant o_wronly 1 "Write-only flag.")
    (defconstant o_rdwr   2 "Read-write flag.")

+---------------
| OpenMCL has a DEFENUM macro that's used in a few places in the system.
|   http://trac.clozure.com/openmcl/browser/trunk/ccl/lib/macros.lisp#L3191
| It's used in circumstances like this:
| (ccl::defenum (:prefix "MXCSR-" :suffix "-BIT")
|   ie                                    ;invalid exception
|   de                                    ;denormal exception
...[trimmed]...
|   rc1                                   ;rounding control bit 1
|   fz                                    ;flush-to-zero (not-IEEE)
| )
+---------------

CMUCL has a DEF-ENUM in the UNIX package that has less flexibility
in naming [the :prefix & :suffix args in OpenMCL's DEFENUM are a nice
touch here] but a *lot* more flexibility in auto-assigning values.
The macro is:

    (defmacro def-enum (inc cur &rest names)
      (flet ((defform (name)
		 (prog1 (when name `(defconstant ,name ,cur))
		   (setf cur (funcall inc cur 1)))))
	`(progn ,@(mapcar #'defform names))))

Notice that "INC CUR" values of "ASH 1" are useful for bitmasks,
while "+ 0" is useful for sequential integers from 0:

    ;; Input modes. Linux: /usr/include/asm/termbits.h
    (def-enum ash 1 tty-ignbrk tty-brkint tty-ignpar tty-parmrk tty-inpck
	      tty-istrip tty-inlcr tty-igncr tty-icrnl #-bsd tty-iuclc
	      tty-ixon #-bsd tty-ixany tty-ixoff #+bsd tty-ixany
	      #+hpux tty-ienqak #+bsd nil tty-imaxbel)

    ;; output modes
    #-bsd (def-enum ash 1 tty-opost tty-olcuc tty-onlcr tty-ocrnl tty-onocr
			tty-onlret tty-ofill tty-ofdel)
    #+bsd (def-enum ash 1 tty-opost tty-onlcr)
    ...
    ;; special control characters
    #+(or hpux svr4 linux) (def-enum + 0 vintr vquit verase vkill veof
				     #-linux veol #-linux veol2)
    #+bsd (def-enum + 0 veof veol veol2 verase nil vkill nil nil vintr vquit)
    ...

The "(WHEN NAME ...)" in the definition lets you skip values, e.g.;

    > (unix::def-enum + 1 one two nil four nil nil nil eight)

    EIGHT
    > (list eight four two one)

    (8 4 2 1)
    > 

The generality of the INC argument is somewhat marred by the fact
that the function has to have a macro-expansion-time value, so to
supply your own function you need to EVAL-WHEN it [in-line LAMBDAs
don't work here, since INC is passed *unevaluated* to FUNCALL]:

    > (eval-when (:compile-toplevel :load-toplevel :execute)
	(defun x3 (x ign)
	  (declare (ignore ign))
	  (* x 3)))

    X3
    > (unix::def-enum X3 1
	one three nine twenty-seven eighty-one two-hundred-forty-three
	seven-hundred-twenty-nine two-thousand-one-hundred-eighty-seven)

    TWO-THOUSAND-ONE-HUNDRED-EIGHTY-SEVEN
    > (list one three nine two-thousand-one-hundred-eighty-seven)

    (1 3 9 2187)
    > 


-Rob

-----
Rob Warnock			<rpw3@rpw3.org>
627 26th Avenue			<URL:http://rpw3.org/>
San Mateo, CA 94403		(650)572-2607