enum.lisp - clic - Clic is an command line interactive client for gopher written in Common LISP (HTM) git clone git://bitreich.org/clic/ git://enlrupgkhuxnvlhsf6lc3fziv5h2hhfrinws65d7roiv6bfj7d652fid.onion/clic/ (DIR) Log (DIR) Files (DIR) Refs (DIR) Tags (DIR) README (DIR) LICENSE --- enum.lisp (15859B) --- 1 ;;;; -*- Mode: lisp; indent-tabs-mode: nil -*- 2 ;;; 3 ;;; enum.lisp --- Defining foreign constants as Lisp keywords. 4 ;;; 5 ;;; Copyright (C) 2005-2006, James Bielman <jamesjb@jamesjb.com> 6 ;;; 7 ;;; Permission is hereby granted, free of charge, to any person 8 ;;; obtaining a copy of this software and associated documentation 9 ;;; files (the "Software"), to deal in the Software without 10 ;;; restriction, including without limitation the rights to use, copy, 11 ;;; modify, merge, publish, distribute, sublicense, and/or sell copies 12 ;;; of the Software, and to permit persons to whom the Software is 13 ;;; furnished to do so, subject to the following conditions: 14 ;;; 15 ;;; The above copyright notice and this permission notice shall be 16 ;;; included in all copies or substantial portions of the Software. 17 ;;; 18 ;;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 19 ;;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 20 ;;; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 21 ;;; NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 22 ;;; HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 23 ;;; WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 ;;; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 ;;; DEALINGS IN THE SOFTWARE. 26 ;;; 27 28 (in-package #:cffi) 29 30 ;; TODO the accessors names are rather inconsistent: 31 ;; FOREIGN-ENUM-VALUE FOREIGN-BITFIELD-VALUE 32 ;; FOREIGN-ENUM-KEYWORD FOREIGN-BITFIELD-SYMBOLS 33 ;; FOREIGN-ENUM-KEYWORD-LIST FOREIGN-BITFIELD-SYMBOL-LIST 34 ;; I'd rename them to: FOREIGN-*-KEY(S) and FOREIGN-*-ALL-KEYS -- attila 35 36 ;; TODO bitfield is a confusing name, because the C standard calls 37 ;; the "int foo : 3" type as a bitfield. Maybe rename to defbitmask? 38 ;; -- attila 39 40 ;;;# Foreign Constants as Lisp Keywords 41 ;;; 42 ;;; This module defines the DEFCENUM macro, which provides an 43 ;;; interface for defining a type and associating a set of integer 44 ;;; constants with keyword symbols for that type. 45 ;;; 46 ;;; The keywords are automatically translated to the appropriate 47 ;;; constant for the type by a type translator when passed as 48 ;;; arguments or a return value to a foreign function. 49 50 (defclass foreign-enum (named-foreign-type enhanced-foreign-type) 51 ((keyword-values 52 :initform (error "Must specify KEYWORD-VALUES.") 53 :initarg :keyword-values 54 :reader keyword-values) 55 (value-keywords 56 :initform (error "Must specify VALUE-KEYWORDS.") 57 :initarg :value-keywords 58 :reader value-keywords)) 59 (:documentation "Describes a foreign enumerated type.")) 60 61 (deftype enum-key () 62 '(and symbol (not null))) 63 64 (defparameter +valid-enum-base-types+ *built-in-integer-types*) 65 66 (defun parse-foreign-enum-like (type-name base-type values 67 &optional field-mode-p) 68 (let ((keyword-values (make-hash-table :test 'eq)) 69 (value-keywords (make-hash-table)) 70 (field-keywords (list)) 71 (bit-index->keyword (make-array 0 :adjustable t 72 :element-type t)) 73 (default-value (if field-mode-p 1 0)) 74 (most-extreme-value 0) 75 (has-negative-value? nil)) 76 (dolist (pair values) 77 (destructuring-bind (keyword &optional (value default-value valuep)) 78 (ensure-list pair) 79 (check-type keyword enum-key) 80 ;;(check-type value integer) 81 (when (> (abs value) (abs most-extreme-value)) 82 (setf most-extreme-value value)) 83 (when (minusp value) 84 (setf has-negative-value? t)) 85 (if field-mode-p 86 (if valuep 87 (when (and (>= value default-value) 88 (single-bit-p value)) 89 (setf default-value (ash value 1))) 90 (setf default-value (ash default-value 1))) 91 (setf default-value (1+ value))) 92 (if (gethash keyword keyword-values) 93 (error "A foreign enum cannot contain duplicate keywords: ~S." 94 keyword) 95 (setf (gethash keyword keyword-values) value)) 96 ;; This is completely arbitrary behaviour: we keep the last 97 ;; value->keyword mapping. I suppose the opposite would be 98 ;; just as good (keeping the first). Returning a list with all 99 ;; the keywords might be a solution too? Suggestions 100 ;; welcome. --luis 101 (setf (gethash value value-keywords) keyword) 102 (when (and field-mode-p 103 (single-bit-p value)) 104 (let ((bit-index (1- (integer-length value)))) 105 (push keyword field-keywords) 106 (when (<= (array-dimension bit-index->keyword 0) 107 bit-index) 108 (setf bit-index->keyword 109 (adjust-array bit-index->keyword (1+ bit-index) 110 :initial-element nil))) 111 (setf (aref bit-index->keyword bit-index) 112 keyword))))) 113 (if base-type 114 (progn 115 (setf base-type (canonicalize-foreign-type base-type)) 116 ;; I guess we don't lose much by not strictly adhering to 117 ;; the C standard here, and some libs out in the wild are 118 ;; already using e.g. :double. 119 #+nil 120 (assert (member base-type +valid-enum-base-types+ :test 'eq) () 121 "Invalid base type ~S for enum type ~S. Must be one of ~S." 122 base-type type-name +valid-enum-base-types+)) 123 ;; details: https://stackoverflow.com/questions/1122096/what-is-the-underlying-type-of-a-c-enum 124 (let ((bits (integer-length most-extreme-value))) 125 (setf base-type 126 (let ((most-uint-bits (load-time-value (* (foreign-type-size :unsigned-int) 8))) 127 (most-ulong-bits (load-time-value (* (foreign-type-size :unsigned-long) 8))) 128 (most-ulonglong-bits (load-time-value (* (foreign-type-size :unsigned-long-long) 8)))) 129 (or (if has-negative-value? 130 (cond 131 ((<= (1+ bits) most-uint-bits) 132 :int) 133 ((<= (1+ bits) most-ulong-bits) 134 :long) 135 ((<= (1+ bits) most-ulonglong-bits) 136 :long-long)) 137 (cond 138 ((<= bits most-uint-bits) 139 :unsigned-int) 140 ((<= bits most-ulong-bits) 141 :unsigned-long) 142 ((<= bits most-ulonglong-bits) 143 :unsigned-long-long))) 144 (error "Enum value ~S of enum ~S is too large to store." 145 most-extreme-value type-name)))))) 146 (values base-type keyword-values value-keywords 147 field-keywords (when field-mode-p 148 (alexandria:copy-array 149 bit-index->keyword :adjustable nil 150 :fill-pointer nil))))) 151 152 (defun make-foreign-enum (type-name base-type values) 153 "Makes a new instance of the foreign-enum class." 154 (multiple-value-bind 155 (base-type keyword-values value-keywords) 156 (parse-foreign-enum-like type-name base-type values) 157 (make-instance 'foreign-enum 158 :name type-name 159 :actual-type (parse-type base-type) 160 :keyword-values keyword-values 161 :value-keywords value-keywords))) 162 163 (defun %defcenum-like (name-and-options enum-list type-factory) 164 (discard-docstring enum-list) 165 (destructuring-bind (name &optional base-type) 166 (ensure-list name-and-options) 167 (let ((type (funcall type-factory name base-type enum-list))) 168 `(eval-when (:compile-toplevel :load-toplevel :execute) 169 (notice-foreign-type ',name 170 ;; ,type is not enough here, someone needs to 171 ;; define it when we're being loaded from a fasl. 172 (,type-factory ',name ',base-type ',enum-list)) 173 ,@(remove nil 174 (mapcar (lambda (key) 175 (unless (keywordp key) 176 `(defconstant ,key ,(foreign-enum-value type key)))) 177 (foreign-enum-keyword-list type))))))) 178 179 (defmacro defcenum (name-and-options &body enum-list) 180 "Define an foreign enumerated type." 181 (%defcenum-like name-and-options enum-list 'make-foreign-enum)) 182 183 (defun hash-keys-to-list (ht) 184 (loop for k being the hash-keys in ht collect k)) 185 186 (defun foreign-enum-keyword-list (enum-type) 187 "Return a list of KEYWORDS defined in ENUM-TYPE." 188 (hash-keys-to-list (keyword-values (ensure-parsed-base-type enum-type)))) 189 190 ;;; These [four] functions could be good canditates for compiler macros 191 ;;; when the value or keyword is constant. I am not going to bother 192 ;;; until someone has a serious performance need to do so though. --jamesjb 193 (defun %foreign-enum-value (type keyword &key errorp) 194 (check-type keyword enum-key) 195 (or (gethash keyword (keyword-values type)) 196 (when errorp 197 (error "~S is not defined as a keyword for enum type ~S." 198 keyword type)))) 199 200 (defun foreign-enum-value (type keyword &key (errorp t)) 201 "Convert a KEYWORD into an integer according to the enum TYPE." 202 (let ((type-obj (ensure-parsed-base-type type))) 203 (if (not (typep type-obj 'foreign-enum)) 204 (error "~S is not a foreign enum type." type) 205 (%foreign-enum-value type-obj keyword :errorp errorp)))) 206 207 (defun %foreign-enum-keyword (type value &key errorp) 208 (check-type value integer) 209 (or (gethash value (value-keywords type)) 210 (when errorp 211 (error "~S is not defined as a value for enum type ~S." 212 value type)))) 213 214 (defun foreign-enum-keyword (type value &key (errorp t)) 215 "Convert an integer VALUE into a keyword according to the enum TYPE." 216 (let ((type-obj (ensure-parsed-base-type type))) 217 (if (not (typep type-obj 'foreign-enum)) 218 (error "~S is not a foreign enum type." type) 219 (%foreign-enum-keyword type-obj value :errorp errorp)))) 220 221 (defmethod translate-to-foreign (value (type foreign-enum)) 222 (if (typep value 'enum-key) 223 (%foreign-enum-value type value :errorp t) 224 value)) 225 226 (defmethod translate-into-foreign-memory 227 (value (type foreign-enum) pointer) 228 (setf (mem-aref pointer (unparse-type (actual-type type))) 229 (translate-to-foreign value type))) 230 231 (defmethod translate-from-foreign (value (type foreign-enum)) 232 (%foreign-enum-keyword type value :errorp t)) 233 234 (defmethod expand-to-foreign (value (type foreign-enum)) 235 (once-only (value) 236 `(if (typep ,value 'enum-key) 237 (%foreign-enum-value ,type ,value :errorp t) 238 ,value))) 239 240 ;;; There are two expansions necessary for an enum: first, the enum 241 ;;; keyword needs to be translated to an int, and then the int needs 242 ;;; to be made indirect. 243 (defmethod expand-to-foreign-dyn-indirect (value var body (type foreign-enum)) 244 (expand-to-foreign-dyn-indirect ; Make the integer indirect 245 (with-unique-names (feint) 246 (call-next-method value feint (list feint) type)) ; TRANSLATABLE-FOREIGN-TYPE method 247 var 248 body 249 (actual-type type))) 250 251 ;;;# Foreign Bitfields as Lisp keywords 252 ;;; 253 ;;; DEFBITFIELD is an abstraction similar to the one provided by DEFCENUM. 254 ;;; With some changes to DEFCENUM, this could certainly be implemented on 255 ;;; top of it. 256 257 (defclass foreign-bitfield (foreign-enum) 258 ((field-keywords 259 :initform (error "Must specify FIELD-KEYWORDS.") 260 :initarg :field-keywords 261 :reader field-keywords) 262 (bit-index->keyword 263 :initform (error "Must specify BIT-INDEX->KEYWORD") 264 :initarg :bit-index->keyword 265 :reader bit-index->keyword)) 266 (:documentation "Describes a foreign bitfield type.")) 267 268 (defun make-foreign-bitfield (type-name base-type values) 269 "Makes a new instance of the foreign-bitfield class." 270 (multiple-value-bind 271 (base-type keyword-values value-keywords 272 field-keywords bit-index->keyword) 273 (parse-foreign-enum-like type-name base-type values t) 274 (make-instance 'foreign-bitfield 275 :name type-name 276 :actual-type (parse-type base-type) 277 :keyword-values keyword-values 278 :value-keywords value-keywords 279 :field-keywords field-keywords 280 :bit-index->keyword bit-index->keyword))) 281 282 (defmacro defbitfield (name-and-options &body masks) 283 "Define an foreign enumerated type." 284 (%defcenum-like name-and-options masks 'make-foreign-bitfield)) 285 286 (defun foreign-bitfield-symbol-list (bitfield-type) 287 "Return a list of SYMBOLS defined in BITFIELD-TYPE." 288 (field-keywords (ensure-parsed-base-type bitfield-type))) 289 290 (defun %foreign-bitfield-value (type symbols) 291 (declare (optimize speed)) 292 (labels ((process-one (symbol) 293 (check-type symbol symbol) 294 (or (gethash symbol (keyword-values type)) 295 (error "~S is not a valid symbol for bitfield type ~S." 296 symbol type)))) 297 (declare (dynamic-extent #'process-one)) 298 (cond 299 ((consp symbols) 300 (reduce #'logior symbols :key #'process-one)) 301 ((null symbols) 302 0) 303 (t 304 (process-one symbols))))) 305 306 (defun foreign-bitfield-value (type symbols) 307 "Convert a list of symbols into an integer according to the TYPE bitfield." 308 (let ((type-obj (ensure-parsed-base-type type))) 309 (assert (typep type-obj 'foreign-bitfield) () 310 "~S is not a foreign bitfield type." type) 311 (%foreign-bitfield-value type-obj symbols))) 312 313 (define-compiler-macro foreign-bitfield-value (&whole form type symbols) 314 "Optimize for when TYPE and SYMBOLS are constant." 315 (declare (notinline foreign-bitfield-value)) 316 (if (and (constantp type) (constantp symbols)) 317 (foreign-bitfield-value (eval type) (eval symbols)) 318 form)) 319 320 (defun %foreign-bitfield-symbols (type value) 321 (check-type value integer) 322 (check-type type foreign-bitfield) 323 (loop 324 :with bit-index->keyword = (bit-index->keyword type) 325 :for bit-index :from 0 :below (array-dimension bit-index->keyword 0) 326 :for mask = 1 :then (ash mask 1) 327 :for key = (aref bit-index->keyword bit-index) 328 :when (and key 329 (= (logand value mask) mask)) 330 :collect key)) 331 332 (defun foreign-bitfield-symbols (type value) 333 "Convert an integer VALUE into a list of matching symbols according to 334 the bitfield TYPE." 335 (let ((type-obj (ensure-parsed-base-type type))) 336 (if (not (typep type-obj 'foreign-bitfield)) 337 (error "~S is not a foreign bitfield type." type) 338 (%foreign-bitfield-symbols type-obj value)))) 339 340 (define-compiler-macro foreign-bitfield-symbols (&whole form type value) 341 "Optimize for when TYPE and SYMBOLS are constant." 342 (declare (notinline foreign-bitfield-symbols)) 343 (if (and (constantp type) (constantp value)) 344 `(quote ,(foreign-bitfield-symbols (eval type) (eval value))) 345 form)) 346 347 (defmethod translate-to-foreign (value (type foreign-bitfield)) 348 (if (integerp value) 349 value 350 (%foreign-bitfield-value type (ensure-list value)))) 351 352 (defmethod translate-from-foreign (value (type foreign-bitfield)) 353 (%foreign-bitfield-symbols type value)) 354 355 (defmethod expand-to-foreign (value (type foreign-bitfield)) 356 (flet ((expander (value type) 357 `(if (integerp ,value) 358 ,value 359 (%foreign-bitfield-value ,type (ensure-list ,value))))) 360 (if (constantp value) 361 (eval (expander value type)) 362 (expander value type)))) 363 364 (defmethod expand-from-foreign (value (type foreign-bitfield)) 365 (flet ((expander (value type) 366 `(%foreign-bitfield-symbols ,type ,value))) 367 (if (constantp value) 368 (eval (expander value type)) 369 (expander value type))))