Coverage report: /home/ellis/.stash/quicklisp/dists/quicklisp/software/cffi-20250622-git/src/enum.lisp
Kind | Covered | All | % |
expression | 0 | 462 | 0.0 |
branch | 0 | 74 | 0.0 |
Key
Not instrumented
Conditionalized out
Executed
Not executed
Both branches taken
One branch taken
Neither branch taken
1
;;;; -*- Mode: lisp; indent-tabs-mode: nil -*-
3
;;; enum.lisp --- Defining foreign constants as Lisp keywords.
5
;;; Copyright (C) 2005-2006, James Bielman <jamesjb@jamesjb.com>
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:
15
;;; The above copyright notice and this permission notice shall be
16
;;; included in all copies or substantial portions of the Software.
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.
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
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?
40
;;;# Foreign Constants as Lisp Keywords
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.
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.
50
(defclass foreign-enum (named-foreign-type enhanced-foreign-type)
52
:initform (error "Must specify KEYWORD-VALUES.")
53
:initarg :keyword-values
54
:reader keyword-values)
56
:initform (error "Must specify VALUE-KEYWORDS.")
57
:initarg :value-keywords
58
:reader value-keywords)
59
(allow-undeclared-values
61
:initarg :allow-undeclared-values
62
:reader allow-undeclared-values))
63
(:documentation "Describes a foreign enumerated type."))
66
'(and symbol (not null)))
68
(defparameter +valid-enum-base-types+ *built-in-integer-types*)
70
(defun parse-foreign-enum-like (type-name base-type values
71
&optional field-mode-p)
72
(let ((keyword-values (make-hash-table :test 'eq))
73
(value-keywords (make-hash-table))
74
(field-keywords (list))
75
(bit-index->keyword (make-array 0 :adjustable t
77
(default-value (if field-mode-p 1 0))
78
(most-extreme-value 0)
79
(has-negative-value? nil))
81
(destructuring-bind (keyword &optional (value default-value valuep))
83
(check-type keyword enum-key)
84
;;(check-type value integer)
85
(when (> (abs value) (abs most-extreme-value))
86
(setf most-extreme-value value))
88
(setf has-negative-value? t))
91
(when (and (>= value default-value)
93
(setf default-value (ash value 1)))
94
(setf default-value (ash default-value 1)))
95
(setf default-value (1+ value)))
96
(if (gethash keyword keyword-values)
97
(error "A foreign enum cannot contain duplicate keywords: ~S."
99
(setf (gethash keyword keyword-values) value))
100
;; This is completely arbitrary behaviour: we keep the last
101
;; value->keyword mapping. I suppose the opposite would be
102
;; just as good (keeping the first). Returning a list with all
103
;; the keywords might be a solution too? Suggestions
105
(setf (gethash value value-keywords) keyword)
106
(when (and field-mode-p
107
(single-bit-p value))
108
(let ((bit-index (1- (integer-length value))))
109
(push keyword field-keywords)
110
(when (<= (array-dimension bit-index->keyword 0)
112
(setf bit-index->keyword
113
(adjust-array bit-index->keyword (1+ bit-index)
114
:initial-element nil)))
115
(setf (aref bit-index->keyword bit-index)
119
(setf base-type (canonicalize-foreign-type base-type))
120
;; I guess we don't lose much by not strictly adhering to
121
;; the C standard here, and some libs out in the wild are
122
;; already using e.g. :double.
124
(assert (member base-type +valid-enum-base-types+ :test 'eq) ()
125
"Invalid base type ~S for enum type ~S. Must be one of ~S."
126
base-type type-name +valid-enum-base-types+))
127
;; details: https://stackoverflow.com/questions/1122096/what-is-the-underlying-type-of-a-c-enum
128
(let ((bits (integer-length most-extreme-value)))
130
(let ((most-uint-bits (load-time-value (* (foreign-type-size :unsigned-int) 8)))
131
(most-ulong-bits (load-time-value (* (foreign-type-size :unsigned-long) 8)))
132
(most-ulonglong-bits (load-time-value (* (foreign-type-size :unsigned-long-long) 8))))
133
(or (if has-negative-value?
135
((<= (1+ bits) most-uint-bits)
137
((<= (1+ bits) most-ulong-bits)
139
((<= (1+ bits) most-ulonglong-bits)
142
((<= bits most-uint-bits)
144
((<= bits most-ulong-bits)
146
((<= bits most-ulonglong-bits)
147
:unsigned-long-long)))
148
(error "Enum value ~S of enum ~S is too large to store."
149
most-extreme-value type-name))))))
150
(values base-type keyword-values value-keywords
151
field-keywords (when field-mode-p
152
(alexandria:copy-array
153
bit-index->keyword :adjustable nil
154
:fill-pointer nil)))))
156
(defun make-foreign-enum (type-name base-type values &key allow-undeclared-values)
157
"Makes a new instance of the foreign-enum class."
159
(base-type keyword-values value-keywords)
160
(parse-foreign-enum-like type-name base-type values)
161
(make-instance 'foreign-enum
163
:actual-type (parse-type base-type)
164
:keyword-values keyword-values
165
:value-keywords value-keywords
166
:allow-undeclared-values allow-undeclared-values)))
168
(defun %defcenum-like (name-and-options enum-list type-factory)
169
(discard-docstring enum-list)
170
(destructuring-bind (name &optional base-type &rest args)
171
(ensure-list name-and-options)
172
(let ((type (apply type-factory name base-type enum-list args)))
173
`(eval-when (:compile-toplevel :load-toplevel :execute)
174
(notice-foreign-type ',name
175
;; ,type is not enough here, someone needs to
176
;; define it when we're being loaded from a fasl.
177
(,type-factory ',name ',base-type ',enum-list ,@args))
179
(mapcar (lambda (key)
180
(unless (keywordp key)
181
`(defconstant ,key ,(foreign-enum-value type key))))
182
(foreign-enum-keyword-list type)))))))
184
(defmacro defcenum (name-and-options &body enum-list)
185
"Define an foreign enumerated type."
186
(%defcenum-like name-and-options enum-list 'make-foreign-enum))
188
(defun hash-keys-to-list (ht)
189
(loop for k being the hash-keys in ht collect k))
191
(defun foreign-enum-keyword-list (enum-type)
192
"Return a list of KEYWORDS defined in ENUM-TYPE."
193
(hash-keys-to-list (keyword-values (ensure-parsed-base-type enum-type))))
195
;;; These [four] functions could be good canditates for compiler macros
196
;;; when the value or keyword is constant. I am not going to bother
197
;;; until someone has a serious performance need to do so though. --jamesjb
198
(define-compiler-macro %foreign-enum-value (&whole whole
199
type keyword &key errorp)
200
(if (constantp keyword)
201
(let ((v (eval keyword)))
202
(if (typep v 'enum-key)
203
(foreign-enum-value type v :errorp errorp)
207
(defun %foreign-enum-value (type keyword &key errorp)
208
(check-type keyword enum-key)
209
(or (gethash keyword (keyword-values type))
211
(error "~S is not defined as a keyword for enum type ~S."
214
(defun foreign-enum-value (type keyword &key (errorp t))
215
"Convert a KEYWORD into an integer 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-value type-obj keyword :errorp errorp))))
221
(defun %foreign-enum-keyword (type value &key errorp)
222
(check-type value integer)
223
(or (gethash value (value-keywords type))
225
(error "~S is not defined as a value for enum type ~S."
228
(defun foreign-enum-keyword (type value &key (errorp t))
229
"Convert an integer VALUE into a keyword according to the enum TYPE."
230
(let ((type-obj (ensure-parsed-base-type type)))
231
(if (not (typep type-obj 'foreign-enum))
232
(error "~S is not a foreign enum type." type)
233
(%foreign-enum-keyword type-obj value :errorp errorp))))
235
(defmethod translate-to-foreign (value (type foreign-enum))
236
(if (typep value 'enum-key)
237
(%foreign-enum-value type value :errorp t)
240
(defmethod translate-into-foreign-memory
241
(value (type foreign-enum) pointer)
242
(setf (mem-aref pointer (unparse-type (actual-type type)))
243
(translate-to-foreign value type)))
245
(defmethod translate-from-foreign (value (type foreign-enum))
246
(if (allow-undeclared-values type)
247
(or (%foreign-enum-keyword type value :errorp nil)
249
(%foreign-enum-keyword type value :errorp t)))
251
(defmethod expand-to-foreign (value (type foreign-enum))
252
;; once-only prevents compiler macro on %foreign-enum-value, so
253
;; expand constant values here too
254
(if (constantp value)
255
(let ((v (eval value)))
256
(if (typep v 'enum-key)
257
(%foreign-enum-value type v :errorp t)
260
`(if (typep ,value 'enum-key)
261
(%foreign-enum-value ,type ,value :errorp t)
264
;;; There are two expansions necessary for an enum: first, the enum
265
;;; keyword needs to be translated to an int, and then the int needs
266
;;; to be made indirect.
267
(defmethod expand-to-foreign-dyn-indirect (value var body (type foreign-enum))
268
(expand-to-foreign-dyn-indirect ; Make the integer indirect
269
(with-unique-names (feint)
270
(call-next-method value feint (list feint) type)) ; TRANSLATABLE-FOREIGN-TYPE method
275
;;;# Foreign Bitfields as Lisp keywords
277
;;; DEFBITFIELD is an abstraction similar to the one provided by DEFCENUM.
278
;;; With some changes to DEFCENUM, this could certainly be implemented on
281
(defclass foreign-bitfield (foreign-enum)
283
:initform (error "Must specify FIELD-KEYWORDS.")
284
:initarg :field-keywords
285
:reader field-keywords)
287
:initform (error "Must specify BIT-INDEX->KEYWORD")
288
:initarg :bit-index->keyword
289
:reader bit-index->keyword))
290
(:documentation "Describes a foreign bitfield type."))
292
(defun make-foreign-bitfield (type-name base-type values)
293
"Makes a new instance of the foreign-bitfield class."
295
(base-type keyword-values value-keywords
296
field-keywords bit-index->keyword)
297
(parse-foreign-enum-like type-name base-type values t)
298
(make-instance 'foreign-bitfield
300
:actual-type (parse-type base-type)
301
:keyword-values keyword-values
302
:value-keywords value-keywords
303
:field-keywords field-keywords
304
:bit-index->keyword bit-index->keyword)))
306
(defmacro defbitfield (name-and-options &body masks)
307
"Define an foreign enumerated type."
308
(%defcenum-like name-and-options masks 'make-foreign-bitfield))
310
(defun foreign-bitfield-symbol-list (bitfield-type)
311
"Return a list of SYMBOLS defined in BITFIELD-TYPE."
312
(field-keywords (ensure-parsed-base-type bitfield-type)))
314
(defun %foreign-bitfield-value (type symbols)
315
(declare (optimize speed))
316
(labels ((process-one (symbol)
317
(check-type symbol symbol)
318
(or (gethash symbol (keyword-values type))
319
(error "~S is not a valid symbol for bitfield type ~S."
321
(declare (dynamic-extent #'process-one))
324
(reduce #'logior symbols :key #'process-one))
328
(process-one symbols)))))
330
(defun foreign-bitfield-value (type symbols)
331
"Convert a list of symbols into an integer according to the TYPE bitfield."
332
(let ((type-obj (ensure-parsed-base-type type)))
333
(assert (typep type-obj 'foreign-bitfield) ()
334
"~S is not a foreign bitfield type." type)
335
(%foreign-bitfield-value type-obj symbols)))
337
(define-compiler-macro foreign-bitfield-value (&whole form type symbols)
338
"Optimize for when TYPE and SYMBOLS are constant."
339
(declare (notinline foreign-bitfield-value))
340
(if (and (constantp type) (constantp symbols))
341
(foreign-bitfield-value (eval type) (eval symbols))
344
(defun %foreign-bitfield-symbols (type value)
345
(check-type value integer)
346
(check-type type foreign-bitfield)
348
:with bit-index->keyword = (bit-index->keyword type)
349
:for bit-index :from 0 :below (array-dimension bit-index->keyword 0)
350
:for mask = 1 :then (ash mask 1)
351
:for key = (aref bit-index->keyword bit-index)
353
(= (logand value mask) mask))
356
(defun foreign-bitfield-symbols (type value)
357
"Convert an integer VALUE into a list of matching symbols according to
359
(let ((type-obj (ensure-parsed-base-type type)))
360
(if (not (typep type-obj 'foreign-bitfield))
361
(error "~S is not a foreign bitfield type." type)
362
(%foreign-bitfield-symbols type-obj value))))
364
(define-compiler-macro foreign-bitfield-symbols (&whole form type value)
365
"Optimize for when TYPE and SYMBOLS are constant."
366
(declare (notinline foreign-bitfield-symbols))
367
(if (and (constantp type) (constantp value))
368
`(quote ,(foreign-bitfield-symbols (eval type) (eval value)))
371
(defmethod translate-to-foreign (value (type foreign-bitfield))
374
(%foreign-bitfield-value type (ensure-list value))))
376
(defmethod translate-from-foreign (value (type foreign-bitfield))
377
(%foreign-bitfield-symbols type value))
379
(defmethod expand-to-foreign (value (type foreign-bitfield))
380
(flet ((expander (value type)
381
`(if (integerp ,value)
383
(%foreign-bitfield-value ,type (ensure-list ,value)))))
384
(if (constantp value)
385
(eval (expander value type))
386
(expander value type))))
388
(defmethod expand-from-foreign (value (type foreign-bitfield))
389
(flet ((expander (value type)
390
`(%foreign-bitfield-symbols ,type ,value)))
391
(if (constantp value)
392
(eval (expander value type))
393
(expander value type))))