-
Content count
5009 -
Joined
-
Last visited
Single Status Update
See all updates by Remilia Scarlet
-
Common Lisp only "kinda" has Enumerated types. Normally instead of doing something like this in C
typedef enum { tiger, snow_leopard, pandaren, worgen } fursonas;
You'd instead do something like this:
(deftype fursonas () '(member :tiger :snowleopard :pandaren :worgen))
Unfortunately you can't do stuff exactly like this natively:
typedef enum { nosona = -1 tiger = 0, snow_leopard, pandaren , worgen, NUM_FURSONAS } fursonas;
The closest you could get is to use constants and a global variable:
(alexandria:define-constant +no-sona+ -1) (alexandria:define-constant +tiger+ 0) (alexandria:define-constant +snow-leopard+ 1) (alexandria:define-constant +pandaren+ 2) (alexandria:define-constant +worgen+ 3) (defparameter *num-fursonas* 5)
I don't particularly like this since I have to manually count the constants to give a value to the global variable. There's also the issue of typing.
Thankfully Lisp is homoiconic and has kick-ass macros.
(defmacro defenum (type-name (&rest values) &key super-type make-length (package *package*)) (let ((ret nil) (start-val 0) (sym nil) (valid-vals nil)) (dolist (val values) (if (not (symbolp val)) (cond ((not (consp val)) (error "Enum values must be symbols or CONSes")) ((not (symbolp (car val))) (error "Enum names must be a symbol")) ((not (or (numberp (cdr val)) (numberp (cadr val)))) (error "The CDR of an Enum's value must be a number")) (t (setf sym (car val)) (setf start-val (if (numberp (cdr val)) (cdr val) (cadr val))))) (setf sym val)) (setf ret (nconc ret (list (list 'alexandria:define-constant (find-symbol (symbol-name sym) package) start-val :test (quote 'equal))))) (if (find start-val valid-vals :test #'=) (error "Duplicate enum value") (push start-val valid-vals)) (incf start-val)) (setf valid-vals (sort valid-vals #'<)) (when make-length (setf ret (nconc ret (list `(defparameter ,(intern (string-upcase (format nil "*num-~a*" type-name)) package) (1- (length #+sbcl (sb-ext:typexpand (quote ,type-name)) #+clisp (ext:type-expand (quote ,type-name)) #+ccl (ccl::type-expand (quote ,type-name))))))))) `(progn (deftype ,type-name ,super-type (quote (member ,@valid-vals))) ,@ret)))
This doesn't totally get around the typing issue, but it's a step in the right direction. It does at least allow me to do the whole sequential value thing, though, and can automatically update the global variable (if you even want one) as-needed. Thus I can do this:
(defenum my-enum/fursonas (+tiger+ +snow-leopard+ +pandaren+ +worgen+)) ;; Or for another example... (defenum bt-buttons ((+bt-no-button+ -1) (+bt-attack+ 0) +bt-strafe+ +bt-run+ +bt-use+ +bt-ready-knife+ +bt-ready-pistol+ +bt-ready-machine-gun+ +bt-ready-chaingun+ +bt-next-weapon+ +bt-prev-weapon+ +bt-esc+ +bt-pause+ +bt-strafe-left+ +bt-strafe-right+ +bt-move-forward+ +bt-move-backward+ +bt-turn-left+ +bt-turn-right+) :make-length t)
Again, not perfect, but close, and Type Safe Enough For Me™. If you're curious, this macro expands at compile time (because yes, Common Lisp is (usually) compiled to machine code) to this:
(PROGN (DEFTYPE BT-BUTTONS () '(MEMBER -1 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17)) (ALEXANDRIA.0.DEV:DEFINE-CONSTANT +BT-NO-BUTTON+ -1 :TEST 'EQUAL) (ALEXANDRIA.0.DEV:DEFINE-CONSTANT +BT-ATTACK+ 0 :TEST 'EQUAL) (ALEXANDRIA.0.DEV:DEFINE-CONSTANT +BT-STRAFE+ 1 :TEST 'EQUAL) (ALEXANDRIA.0.DEV:DEFINE-CONSTANT +BT-RUN+ 2 :TEST 'EQUAL) (ALEXANDRIA.0.DEV:DEFINE-CONSTANT +BT-USE+ 3 :TEST 'EQUAL) (ALEXANDRIA.0.DEV:DEFINE-CONSTANT +BT-READY-KNIFE+ 4 :TEST 'EQUAL) (ALEXANDRIA.0.DEV:DEFINE-CONSTANT +BT-READY-PISTOL+ 5 :TEST 'EQUAL) (ALEXANDRIA.0.DEV:DEFINE-CONSTANT +BT-READY-MACHINE-GUN+ 6 :TEST 'EQUAL) (ALEXANDRIA.0.DEV:DEFINE-CONSTANT +BT-READY-CHAINGUN+ 7 :TEST 'EQUAL) (ALEXANDRIA.0.DEV:DEFINE-CONSTANT +BT-NEXT-WEAPON+ 8 :TEST 'EQUAL) (ALEXANDRIA.0.DEV:DEFINE-CONSTANT +BT-PREV-WEAPON+ 9 :TEST 'EQUAL) (ALEXANDRIA.0.DEV:DEFINE-CONSTANT +BT-ESC+ 10 :TEST 'EQUAL) (ALEXANDRIA.0.DEV:DEFINE-CONSTANT +BT-PAUSE+ 11 :TEST 'EQUAL) (ALEXANDRIA.0.DEV:DEFINE-CONSTANT +BT-STRAFE-LEFT+ 12 :TEST 'EQUAL) (ALEXANDRIA.0.DEV:DEFINE-CONSTANT +BT-STRAFE-RIGHT+ 13 :TEST 'EQUAL) (ALEXANDRIA.0.DEV:DEFINE-CONSTANT +BT-MOVE-FORWARD+ 14 :TEST 'EQUAL) (ALEXANDRIA.0.DEV:DEFINE-CONSTANT +BT-MOVE-BACKWARD+ 15 :TEST 'EQUAL) (ALEXANDRIA.0.DEV:DEFINE-CONSTANT +BT-TURN-LEFT+ 16 :TEST 'EQUAL) (ALEXANDRIA.0.DEV:DEFINE-CONSTANT +BT-TURN-RIGHT+ 17 :TEST 'EQUAL) (DEFPARAMETER *NUM-BT-BUTTONS* (1- (LENGTH (SB-EXT:TYPEXPAND 'BT-BUTTONS)))))
Not 100% the same, but close enough for me and my needs.