1 ;;;; -*- Mode: lisp; indent-tabs-mode: nil -*-
3 ;;; generator.lisp --- Generate CFFI bindings for a c2ffi output.
5 ;;; Copyright (C) 2015, Attila Lendvai <attila@lendvai.name>
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.
28 (in-package #:cffi
/c2ffi
)
30 ;;; Output generation happens in one phase, straight into the output
31 ;;; stream. There's minimal look-ahead (for source-location and name)
32 ;;; which is needed to apply user specified filters in time.
34 ;;; Each CFFI form is also EVAL'd during generation because the CFFI
35 ;;; type lookup/parsing mechanism is used while generating the output.
39 ;;; - variable names in this file are to be interpreted in the
40 ;;; C,c2ffi,json context, and 'cffi' is added to names that denote
43 ;;; Possible improvments:
45 ;;; - generate an additional grovel file for C inline function
46 ;;; declarations found in header files
48 ;;; - generate struct-by-value DEFCFUN's into a separate file so that
49 ;;; users can decide whether to depend on libffi, or they can make do
50 ;;; without those definitions
52 (defvar *allow-pointer-type-simplification
* t
)
53 (defvar *allow-skipping-struct-fields
* t
)
54 (defvar *assume-struct-by-value-support
* t
)
55 ;; Called on the json name and may return a symbol to be used, or a string.
56 (defvar *ffi-name-transformer
* 'default-ffi-name-transformer
)
57 ;; Called on the already transformed name to decide whether to export it
58 (defvar *ffi-name-export-predicate
* 'default-ffi-name-export-predicate
)
59 ;; Called on the CFFI type, e.g. to turn (:pointer :char) into a :string.
60 (defvar *ffi-type-transformer
* 'default-ffi-type-transformer
)
61 ;; May return up to two closures using VALUES. The first one will be called
62 ;; with each emitted form, and the second one once, at the end. They both may
63 ;; return a list of forms that will be emitted using OUTPUT/CODE.
64 (defvar *callback-factory
* 'default-callback-factory
)
66 (define-constant +generated-file-header
+
67 ";;; -*- Mode: lisp -*-~%~
69 ;;; This file has been automatically generated by cffi/c2ffi. Editing it by hand is not wise.~%~
73 (defvar *c2ffi-output-stream
*)
75 (defun output/export
(names package
)
76 (let ((names (uiop:ensure-list names
)))
77 ;; Make sure we have something PRINT-READABLY as a package name,
78 ;; i.e. not a SIMPLE-BASE-STRING on SBCL.
79 (output/code
`(export ',names
',(make-symbol (package-name package
))))))
81 (defun output/code
(form)
82 (check-type form cons
)
83 (format *c2ffi-output-stream
* "~&")
85 :stream
*c2ffi-output-stream
*
90 (format *c2ffi-output-stream
* "~%~%")
91 (unless (member (first form
) '(cffi:defcfun alexandria
:define-constant
) :test
'eq
)
94 (defun output/string
(message-control &rest message-arguments
)
95 (apply 'format
*c2ffi-output-stream
* message-control message-arguments
))
97 ;; NOTE: as per c2ffi json output. A notable difference to
98 ;; CFFI::*BUILT-IN-FOREIGN-TYPES* is the presence of :SIGNED-CHAR.
99 (define-constant +c-builtin-types
+ '(":void" ":_Bool" ":char" ":signed-char" ":unsigned-char" ":short"
100 ":unsigned-short" ":int" ":unsigned-int" ":long" ":unsigned-long"
101 ":long-long" ":unsigned-long-long" ":float" ":double" ":long-double")
104 (define-condition unsupported-type
(cffi::foreign-type-error
)
105 ((json-definition :initarg
:json-definition
106 :accessor json-definition-of
)))
108 (defun unsupported-type (json-entry)
109 (error 'unsupported-type
:type-name nil
:json-definition json-entry
))
114 (defun compile-rules (rules)
117 (t (mapcar (lambda (pattern)
118 (check-type pattern string
"Patterns in the inclusion/exclusion rules must be strings.")
119 (let ((scanner (cl-ppcre:create-scanner pattern
)))
120 (named-lambda cffi
/c2ffi
/cl-ppcre-rule-matcher
122 (funcall scanner string
0 (length string
)))))
125 (defun include-definition?
(name source-location
126 include-definitions exclude-definitions
127 include-sources exclude-sources
)
129 ((covered-by-a-rule?
(name rules
)
131 (not (null (some (rcurry #'funcall name
) rules
)))))
134 (strong?
(name rules
)
137 (covered-by-a-rule? name rules
))))
138 (let* ((excl-def/weak
(weak? exclude-definitions
))
139 (excl-def/strong
(strong? name exclude-definitions
))
140 (incl-def/weak
(weak? include-definitions
))
141 (incl-def/strong
(strong? name include-definitions
))
142 (excl-src/weak
(weak? exclude-sources
))
143 (excl-src/strong
(strong? source-location exclude-sources
))
144 (incl-src/weak
(weak? include-sources
))
145 (incl-src/strong
(strong? source-location include-sources
))
146 (incl/strong
(or incl-def
/strong
148 (excl/strong
(or excl-def
/strong
150 (incl/weak
(or incl-def
/weak
152 (excl/weak
(or excl-def
/weak
155 (and (not excl
/strong
)
158 ;; we want src exclude rules to be stronger
160 (not excl
/weak
)))))))
162 (defun coerce-to-byte-size (bit-size)
163 (let ((byte-size (/ bit-size
8)))
164 (unless (integerp byte-size
)
165 (error "Non-byte size encountered where it wasn't expected (~A bits)" bit-size
))
168 (defmacro assume
(condition &optional format-control
&rest format-arguments
)
169 "Similar to ASSERT, but WARN's only."
172 `(warn ,format-control
,@format-arguments
)
173 `(warn "ASSUME failed: ~S" ',condition
))))
175 (defun canonicalize-transformer-hook (hook)
177 ((and (or function symbol
)
181 (the symbol
(safe-read-from-string hook
)))))
186 (defun json-value (alist key
&key
(otherwise nil otherwise?
))
187 (check-type alist list
)
188 (check-type key
(and symbol
(not null
)))
189 (let* ((entry (assoc key alist
))
195 (t (error "Key ~S not found in json entry ~S." key alist
)))))
196 (if (equal result
"")
200 (defmacro with-json-values
((json-entry &rest args
) &body body
)
204 (once-only (json-entry)
207 :collect
(let* ((args (ensure-list entry
))
210 (make-keyword (symbol-name name
)))))
212 ;; using &optional would trigger a warning (on SBCL)
213 (&key
(otherwise nil otherwise?
))
216 (json-value ,json-entry
,key
,@(when otherwise?
217 `(:otherwise
,otherwise
))))))))
220 (defun expected-json-keys (alist &rest keys
)
221 (let* ((keys (list* :location keys
))
222 (outliers (remove-if (lambda (el)
223 (member (car el
) keys
:test
'eq
))
226 (warn "Unexpected key(s) in json entry ~S: ~S" alist outliers
))))
229 ;;; Namespaces, names and conversions
231 ;; an alist of (name . hashtable)
232 (defvar *generated-names
*)
233 (defvar *anon-name-counter
*)
234 (defvar *anon-entities
*)
236 (defun register-anon-entity (id name
)
237 (check-type id integer
)
238 (check-type name string
)
239 (assert (not (zerop (length name
))))
240 (setf (gethash id
*anon-entities
*) name
)
243 (defun lookup-anon-entity (id)
244 (or (gethash id
*anon-entities
*)
245 (error "Could not find anonymous entity with id ~S." id
)))
247 (defun generate-anon-name (base-name)
249 (strcat (symbol-name base-name
)
250 (princ-to-string (incf *anon-name-counter
*)))))
252 (defun valid-name-or-die (name)
253 ;; checks for valid json names (*not* CFFI names)
256 (assert (not (zerop (length name
)))))
258 (assert (= 2 (length name
)))
259 (assert (member (first name
) '(:struct
:union
:enum
)))
260 (valid-name-or-die (second name
)))))
262 (defun call-hook (hook &rest args
)
264 ;; indiscriminately add one keyword arg entry to warn
265 (append args
'(just-a-warning "Make sure your transformer hook has &key &allow-other-keys for future extendability."))))
267 (defun find-cffi-type-or-die (type-name &optional
(namespace :default
))
268 (when (eq namespace
:enum
)
269 ;; TODO FIXME this should be cleaned up in CFFI. more about namespace confusion at:
270 ;; https://bugs.launchpad.net/cffi/+bug/1527947
271 (setf namespace
:default
))
272 (cffi::find-type-parser type-name namespace
))
274 (define-constant +name-kinds
+ '(:struct
:union
:function
:variable
:type
275 :constant
:field
:argument
:enum
:member
)
278 (deftype ffi-name-kind
()
279 '#.
(list* 'member
+name-kinds
+))
281 (defun json-name-to-cffi-name (name kind
&optional anonymous
)
282 (check-type name string
)
283 (check-type kind ffi-name-kind
)
284 (when *ffi-name-transformer
*
285 (setf name
(call-hook *ffi-name-transformer
* name kind
))
286 (unless (or (and (symbolp name
)
289 (error "The FFI-NAME-TRANSFORMER ~S returned with ~S which is not a valid name."
290 *ffi-name-transformer
* name
)))
291 (let ((cffi-name (if (symbolp name
)
294 (when (and (not anonymous
)
295 (boundp '*generated-names
*))
296 ;; TODO FIXME this function also gets called for e.g. argument types of a function. and
297 ;; if the function ends up *not* getting emitted, e.g. because of a missing type, then
298 ;; we wrongly record here the missing type in the *generated-names* registry.
299 (setf (gethash name
(cdr (assoc kind
*generated-names
*)))
303 (defun default-callback-factory (&key
&allow-other-keys
)
306 (defun default-ffi-name-transformer (name kind
&key
&allow-other-keys
)
307 (check-type name string
)
311 (assert (not (symbolp name
)))
312 (format nil
"+~A+" name
))
315 (defun change-case-to-readtable-case (name &optional
(reatable *readtable
*))
316 (ecase (readtable-case reatable
)
317 (:upcase
(string-upcase name
))
318 (:downcase
(string-downcase name
))
320 ;; (:invert no, you don't)
323 (defun camelcased?
(name)
324 (and (>= (length name
) 3)
328 :for char
:across name
334 (unless (or (zerop lower
)
336 (let ((ratio (/ upper lower
)))
337 (and (<= 0.05 ratio
0.5)))))))
339 (defun camelcase-to-dash-separated (name)
341 :for char
:across name
343 :when
(and (upper-case-p char
)
346 :collect
(char-downcase char
))
349 (defun maybe-camelcase-to-dash-separated (name)
350 (if (camelcased? name
)
351 (camelcase-to-dash-separated name
)
354 (defun default-ffi-name-export-predicate (symbol &key
&allow-other-keys
)
355 (declare (ignore symbol
))
358 (defun default-ffi-type-transformer (type context
&key
&allow-other-keys
)
359 (declare (ignore context
))
362 (eq :pointer
(first type
)))
363 (let ((pointed-to-type (second type
)))
364 (if (eq pointed-to-type
:char
)
370 (defun function-pointer-type-name ()
371 (symbolicate '#:function-pointer
))
373 (defmacro with-allowed-foreign-type-errors
((on-failure-form &key
(enabled t
)) &body body
)
374 (with-unique-names (type-block)
377 ((cffi::foreign-type-error
381 (return-from ,type-block
,on-failure-form
)))))
384 (defun %json-type-to-cffi-type
(json-entry)
385 (with-json-values (json-entry tag
)
388 ((switch (tag :test
'equal
)
391 ;; regarding :signed-char see https://stackoverflow.com/questions/436513/char-signed-char-char-unsigned-char
393 (":signed-char" :char
)
394 (":unsigned-char" :unsigned-char
)
396 (":unsigned-short" :unsigned-short
)
398 (":unsigned-int" :unsigned-int
)
400 (":unsigned-long" :unsigned-long
)
401 (":long-long" :long-long
)
402 (":unsigned-long-long" :unsigned-long-long
)
406 ;;(":long-double" :long-double)
408 ;; return the result of the condition expression
411 (assert (not (member tag
+c-builtin-types
+ :test
'equal
)) ()
412 "Not all C basic types are covered! The outlier is: ~S" tag
)
414 (equal tag
":struct")
415 (equal tag
":union"))
416 ;; ":struct" is a "struct foo-struct var" kind of reference
417 (expected-json-keys json-entry
:name
:tag
:id
)
418 (with-json-values (json-entry name id
)
419 (let* ((kind (if (equal tag
":struct")
423 (json-name-to-cffi-name name kind
)
424 (lookup-anon-entity id
))))
425 (find-cffi-type-or-die cffi-name kind
)
426 `(,kind
,cffi-name
))))
427 ((or (equal tag
"struct")
429 ;; "struct" denotes a "struct {} var", or "typedef struct {} my_type"
430 ;; kind of inline anonymous declaration. Let's call PROCESS-C2FFI-ENTRY
431 ;; to emit it for us, and return with the generated name (first value)
432 ;; as if it was a standalone toplevel struct definition.
433 ;; TODO is it a problem that we don't invoke the CALLBACK-FACTORY stuff here?
434 (let ((form (process-c2ffi-entry json-entry
))
435 (kind (if (equal tag
"struct")
438 (assert (and (consp form
)
439 (member (first form
) '(cffi:defcstruct cffi
:defcunion
))))
440 `(,kind
,(first (ensure-list (second form
))))))
442 ;; ":enum" is an "enum foo var" kind of reference
443 (expected-json-keys json-entry
:name
:tag
:id
)
444 (with-json-values (json-entry name id
)
445 (let ((cffi-name (json-name-to-cffi-name (or name
446 (lookup-anon-entity id
))
448 (find-cffi-type-or-die cffi-name
:enum
)
449 ;; TODO FIXME this would be the proper one, but CFFI is broken: `(:enum ,cffi-name)
452 ;; "enum" is an inline "typedef enum {m1, m2} var" kind of inline declaration
453 (expected-json-keys json-entry
:name
:tag
:id
)
454 ;; TODO FIXME similarly to struct, but it would be nice to see an example
455 (error "not yet implemented"))
456 ((equal tag
":array")
457 (expected-json-keys json-entry
:tag
:type
:size
)
458 (with-json-values (json-entry type size
)
459 (check-type size integer
)
460 `(:array
,(json-type-to-cffi-type type
) ,size
)))
461 ((equal tag
":pointer")
462 (expected-json-keys json-entry
:tag
:type
:id
)
463 (with-json-values (json-entry type
)
464 `(:pointer
,(with-allowed-foreign-type-errors
465 (:void
:enabled
*allow-pointer-type-simplification
*)
466 (json-type-to-cffi-type type
)))))
467 ((equal tag
":function-pointer")
468 (expected-json-keys json-entry
:tag
)
469 (function-pointer-type-name))
470 ((equal tag
":function")
471 (unsupported-type json-entry
))
473 (assert (not (starts-with #\
: tag
)))
474 (let ((cffi-name (json-name-to-cffi-name tag
:type
)))
475 ;; TODO FIXME json-name-to-cffi-name collects the mentioned
476 ;; types to later emit +TYPE-NAMES+, but if this next
477 ;; find-cffi-type-or-die dies then the entire function is
479 (find-cffi-type-or-die cffi-name
)
481 (assert cffi-type
() "Failed to map ~S to a cffi type" json-entry
)
484 (defun should-export-p (symbol)
487 (not (keywordp symbol
))
488 *ffi-name-export-predicate
*
489 (call-hook *ffi-name-export-predicate
* symbol
)))
491 (defun json-type-to-cffi-type (json-entry &optional
(context nil context?
))
492 (let ((cffi-type (%json-type-to-cffi-type json-entry
)))
494 (call-hook *ffi-type-transformer
* cffi-type context
)
498 ;;; Entry point, the "API"
500 (defun process-c2ffi-spec-file (c2ffi-spec-file package-name
502 (allow-pointer-type-simplification *allow-pointer-type-simplification
*)
503 (allow-skipping-struct-fields *allow-skipping-struct-fields
*)
504 (assume-struct-by-value-support *assume-struct-by-value-support
*)
505 ;; either a pathname or a string (will be copied as is),
506 ;; or a function that will be funcall'd with one argument
507 ;; to emit a form (i.e. OUTPUT/CODE).
509 (output (make-pathname :name
(strcat (pathname-name c2ffi-spec-file
) ".cffi-tmp")
510 :type
"lisp" :defaults c2ffi-spec-file
))
511 (output-encoding asdf
:*default-encoding
*)
512 ;; The args following this point are mirrored in the ASDF
513 ;; component on the same name.
514 (ffi-name-transformer *ffi-name-transformer
*)
515 (ffi-name-export-predicate *ffi-name-export-predicate
*)
516 ;; as per CFFI:DEFINE-FOREIGN-LIBRARY and CFFI:LOAD-FOREIGN-LIBRARY
517 (ffi-type-transformer *ffi-type-transformer
*)
518 (callback-factory *callback-factory
*)
521 (emit-generated-name-mappings t
)
522 (include-sources :all
)
524 (include-definitions :all
)
526 "Generates a lisp file with CFFI definitions from C2FFI-SPEC-FILE.
527 PACKAGE-NAME will be overwritten, it assumes full control over the
529 (check-type c2ffi-spec-file
(or pathname string
))
531 `(setf ,var
(compile-rules ,var
))))
534 (@ include-definitions
)
535 (@ exclude-definitions
))
536 (with-standard-io-syntax
537 (with-input-from-file (in c2ffi-spec-file
:external-format
(asdf/driver
:encoding-external-format
:utf-8
))
538 (with-output-to-file (*c2ffi-output-stream
* output
:if-exists
:supersede
539 :external-format
(asdf/driver
:encoding-external-format output-encoding
))
540 (let* ((*package
* (or (find-package package-name
)
541 (make-package package-name
)))
542 ;; Make sure we use an uninterned symbol, so that it's neutral to READTABLE-CASE.
543 (package-name (make-symbol (package-name *package
*)))
544 ;; Let's rebind a copy, so that when we are done with
545 ;; the generation (which also EVAL's the forms) then
546 ;; the CFFI type repository is also reverted back to
547 ;; the previous state. This avoids redefinition warning
548 ;; when the generated file gets compiled and loaded
550 (cffi::*type-parsers
* (copy-hash-table cffi
::*type-parsers
*))
551 (*anon-name-counter
* 0)
552 (*anon-entities
* (make-hash-table))
553 (*generated-names
* (mapcar (lambda (key)
554 `(,key .
,(make-hash-table :test
'equal
)))
556 (*allow-pointer-type-simplification
* allow-pointer-type-simplification
)
557 (*allow-skipping-struct-fields
* allow-skipping-struct-fields
)
558 (*assume-struct-by-value-support
* assume-struct-by-value-support
)
559 (*ffi-name-transformer
* (canonicalize-transformer-hook ffi-name-transformer
))
560 (*ffi-name-export-predicate
* (canonicalize-transformer-hook ffi-name-export-predicate
))
561 (*ffi-type-transformer
* (canonicalize-transformer-hook ffi-type-transformer
))
562 (*callback-factory
* (canonicalize-transformer-hook callback-factory
))
563 (*read-default-float-format
* 'double-float
)
564 (json (json:decode-json in
)))
565 (output/string
+generated-file-header
+)
566 ;; some forms that are always emitted
568 ;; Make sure the package exists. We don't even want to :use COMMON-LISP here,
569 ;; to avoid any possible name clashes.
570 `((uiop:define-package
,package-name
(:use
))
571 (in-package ,package-name
)
572 (cffi:defctype
,(function-pointer-type-name) :pointer
)))
573 (when (and foreign-library-name
574 foreign-library-spec
)
575 (when (stringp foreign-library-name
)
576 (setf foreign-library-name
(safe-read-from-string foreign-library-name
)))
577 (output/code
`(cffi:define-foreign-library
,foreign-library-name
578 ,@foreign-library-spec
))
579 ;; TODO: Unconditionally emitting a USE-FOREIGN-LIBRARY may not be smart.
580 ;; For details see: https://bugs.launchpad.net/cffi/+bug/1593635
581 (output/code
`(cffi:use-foreign-library
,foreign-library-name
)))
585 (output/string prelude
))
587 (with-input-from-file (prelude-stream prelude
)
588 (alexandria:copy-stream prelude-stream
*c2ffi-output-stream
*
589 :element-type
'character
)))
590 ((or symbol function
)
591 (funcall prelude
'output
/code
)))
593 ;; Let's enumerate the entries
594 (multiple-value-bind (form-callback epilogue-callback
)
595 (funcall *callback-factory
*)
596 (dolist (json-entry json
)
597 (with-json-values (json-entry name location
)
598 (let ((source-location-file (subseq location
600 (or (position #\
: location
)
602 (if (include-definition?
603 name source-location-file
604 include-definitions exclude-definitions
605 include-sources exclude-sources
)
607 (output/string
"~&~%;; ~S" location
)
608 (let ((emitted-definition (process-c2ffi-entry json-entry
)))
610 ;; Call the plugin to let the user emit a form after the given
612 (when (and emitted-definition
614 (map nil
'output
/code
(call-hook form-callback emitted-definition
)))))
615 (output/string
"~&;; Skipped ~S due to filters" name
)))))
617 ;; Call the plugin to let the user append multiple forms after the
618 ;; emitted definitions
619 (when epilogue-callback
620 (map nil
'output
/code
(call-hook epilogue-callback
))))
622 ;; emit optional exports
624 (lambda (package-name symbols
)
625 (output/export
(sort (remove-if-not #'should-export-p symbols
) #'string
<)
627 (get-all-names-by-package *generated-names
*))
630 ;; emit optional mappings
631 (when emit-generated-name-mappings
632 (mapcar (lambda (entry)
633 (destructuring-bind (kind variable-name
) entry
634 (output/code
`(defparameter
635 ,(intern (symbol-name variable-name
))
636 ',(hash-table-alist (cdr (assoc kind
*generated-names
*)))))))
637 `((:function
#:+function-names
+)
638 (:struct
#:+struct-names
+)
639 (:union
#:+union-names
+)
640 (:variable
#:+variable-names
+)
641 (:type
#:+type-names
+)
642 (:constant
#:+constant-names
+)
643 (:argument
#:+argument-names
+)
644 (:field
#:+field-names
+))))))))
647 (defun get-all-names-by-package (name-collection)
648 (let ((tables (mapcar #'cdr name-collection
))
650 (grouped (make-hash-table)))
651 (loop :for table
:in tables
:do
652 (loop :for s
:being
:the
:hash-values
:of table
:do
654 (remove-duplicates all
:test
#'eq
)
655 (loop :for name
:in all
656 :for package-name
:= (package-name (symbol-package name
))
657 :do
(setf (gethash package-name grouped
)
658 (cons name
(gethash package-name grouped
))))
662 ;;; Processors for various definitions
664 (defvar *c2ffi-entry-processors
* (make-hash-table :test
'equal
))
666 (defun process-c2ffi-entry (json-entry)
667 (let* ((kind (json-value json-entry
:tag
))
668 (processor (gethash kind
*c2ffi-entry-processors
*)))
670 (let ((definition-form
674 (warn "Skip definition because cannot map ~S to any CFFI type. The definition is ~S"
675 (json-definition-of e
) json-entry
)
676 (return-from process-c2ffi-entry
(values))))
677 (cffi::undefined-foreign-type-error
679 (output/string
"~&;; Skipping definition ~S because of missing type ~S"
680 json-entry
(cffi::foreign-type-error
/compound-name e
))
681 (return-from process-c2ffi-entry
(values)))))
682 (funcall processor json-entry
))))
683 (when definition-form
684 (output/code definition-form
)
687 (warn "No cffi/c2ffi processor defined for ~A" json-entry
)
690 (defmacro define-processor
(kind args
&body body
)
691 `(setf (gethash ,(string-downcase kind
) *c2ffi-entry-processors
*)
692 (named-lambda ,(symbolicate 'c2ffi-processor
/ kind
) (-json-entry-)
693 (with-json-values (-json-entry- ,@args
)
696 (defun %process-struct-like
(json-entry kind definer anon-base-name
)
697 (expected-json-keys json-entry
:tag
:ns
:name
:id
:bit-size
:bit-alignment
:fields
)
698 (with-json-values (json-entry tag
(struct-name :name
) fields bit-size id
)
699 (assert (member tag
'(":struct" "struct" ":union" "union") :test
'equal
))
700 (flet ((process-field (json-entry)
701 (with-json-values (json-entry (field-name :name
) bit-offset type
)
702 (let ((cffi-type (with-allowed-foreign-type-errors
703 ('failed
:enabled
*allow-skipping-struct-fields
*)
704 (json-type-to-cffi-type type
`(,kind
,struct-name
,field-name
)))))
705 (if (eq cffi-type
'failed
)
706 (output/string
"~&;; skipping field due to missing type ~S, full json entry: ~S" type json-entry
)
707 `(,(json-name-to-cffi-name field-name
:field
)
709 ,@(unless (eq kind
:union
)
710 `(:offset
,(coerce-to-byte-size bit-offset
)))))))))
711 `(,definer
(,(json-name-to-cffi-name (or struct-name
712 (register-anon-entity
714 (generate-anon-name anon-base-name
)))
717 :size
,(coerce-to-byte-size bit-size
))
718 ,@(remove nil
(mapcar #'process-field fields
))))))
720 (define-processor struct
()
721 (%process-struct-like -json-entry-
:struct
'cffi
:defcstruct
'#:anon-struct-
))
723 (define-processor union
()
724 (%process-struct-like -json-entry-
:union
'cffi
:defcunion
'#:anon-union-
))
726 (define-processor typedef
(name type
)
727 (expected-json-keys -json-entry-
:tag
:name
:ns
:type
)
728 `(cffi:defctype
,(json-name-to-cffi-name name
:type
)
729 ,(json-type-to-cffi-type type
`(:typedef
,name
))))
731 (define-processor function
(return-type (function-name :name
) parameters inline variadic storage-class
)
732 (declare (ignore storage-class
))
733 ;; TODO does storage-class matter for FFI accessibility?
735 (assume (equal "extern" storage-class
)
736 "Unexpected function STORAGE-CLASS: ~S for function ~S" storage-class function-name
)
737 (expected-json-keys -json-entry-
:tag
:name
:return-type
:parameters
:variadic
:inline
:storage-class
:ns
)
738 (let ((uses-struct-by-value? nil
))
739 (flet ((process-arg (json-entry index
)
740 (expected-json-keys json-entry
:tag
:name
:type
)
741 (with-json-values (json-entry tag
(argument-name :name
) type
)
742 (assert (equal tag
"parameter"))
743 (let* ((cffi-type (json-type-to-cffi-type type
`(:function
,function-name
,argument-name
)))
744 (canonicalized-type (cffi::canonicalize-foreign-type cffi-type
)))
745 (when (and (consp canonicalized-type
)
746 (member (first canonicalized-type
) '(:struct
:union
)))
747 (setf uses-struct-by-value? t
))
749 (json-name-to-cffi-name argument-name
:argument
)
750 (symbolicate '#:arg
(princ-to-string index
)))
752 (let ((cffi-args (loop
753 :for arg
:in parameters
755 :collect
(process-arg arg index
))))
757 ((and uses-struct-by-value?
758 (not *assume-struct-by-value-support
*))
761 ;; TODO inline functions should go into a separate grovel file?
762 (output/string
"~&;; Skipping inline function ~S" function-name
)
764 (t `(cffi:defcfun
(,function-name
,(json-name-to-cffi-name function-name
:function
))
765 ,(json-type-to-cffi-type return-type
`(:function
,function-name
:return-type
))
770 (define-processor extern
(name type
)
771 (expected-json-keys -json-entry-
:tag
:name
:type
)
772 `(cffi:defcvar
(,name
,(json-name-to-cffi-name name
:variable
))
773 ,(json-type-to-cffi-type type
`(:variable
,name
))))
775 ;; ((TAG . enum) (NS . 0) (NAME . ) (ID . 3) (LOCATION . /usr/include/bits/confname.h:24:1) (FIELDS ((TAG . field) (NAME . _PC_LINK_MAX) (VALUE . 0)) ((TAG . field) (NAME . _PC_MAX_CANON) (VALUE . 1)) ((TAG . field) (NAME . _PC_MAX_INPUT) (VALUE . 2)) ((TAG . field) (NAME . _PC_NAME_MAX) (VALUE . 3)) ((TAG . field) (NAME . _PC_PATH_MAX) (VALUE . 4)) ((TAG . field) (NAME . _PC_PIPE_BUF) (VALUE . 5)) ((TAG . field) (NAME . _PC_CHOWN_RESTRICTED) (VALUE . 6)) ((TAG . field) (NAME . _PC_NO_TRUNC) (VALUE . 7)) ((TAG . field) (NAME . _PC_VDISABLE) (VALUE . 8)) ((TAG . field) (NAME . _PC_SYNC_IO) (VALUE . 9)) ((TAG . field) (NAME . _PC_ASYNC_IO) (VALUE . 10)) ((TAG . field) (NAME . _PC_PRIO_IO) (VALUE . 11)) ((TAG . field) (NAME . _PC_SOCK_MAXBUF) (VALUE . 12)) ((TAG . field) (NAME . _PC_FILESIZEBITS) (VALUE . 13)) ((TAG . field) (NAME . _PC_REC_INCR_XFER_SIZE) (VALUE . 14)) ((TAG . field) (NAME . _PC_REC_MAX_XFER_SIZE) (VALUE . 15)) ((TAG . field) (NAME . _PC_REC_MIN_XFER_SIZE) (VALUE . 16)) ((TAG . field) (NAME . _PC_REC_XFER_ALIGN) (VALUE . 17)) ((TAG . field) (NAME . _PC_ALLOC_SIZE_MIN) (VALUE . 18)) ((TAG . field) (NAME . _PC_SYMLINK_MAX) (VALUE . 19)) ((TAG . field) (NAME . _PC_2_SYMLINKS) (VALUE . 20))))
776 (define-processor enum
(name fields id
)
780 ((for-bitmask-statistics (name value
)
781 (declare (ignore name
))
782 (if (cffi::single-bit-p value
)
784 (incf non-bitmasks
)))
785 (for-enum-body (name value
)
786 `(,(json-name-to-cffi-name name
:member
)
788 (process-fields (visitor)
790 :for json-entry
:in fields
791 :do
(expected-json-keys json-entry
:tag
:name
:value
)
793 (with-json-values (json-entry tag name value
)
794 (assert (equal tag
"field"))
795 (check-type value integer
)
796 (funcall visitor name value
)))))
797 (process-fields #'for-bitmask-statistics
)
798 `(,(if (> (/ bitmasks
799 (+ non-bitmasks bitmasks
))
803 ,(json-name-to-cffi-name (or name
804 (register-anon-entity
806 (generate-anon-name '#:anon-enum-
)))
809 ,@(process-fields #'for-enum-body
)))))
811 (defun make-define-constant-form (name value
)
812 (valid-name-or-die name
)
813 (let ((test-fn (typecase value
816 `(alexandria:define-constant
,(json-name-to-cffi-name name
:constant
)
817 ,value
,@(when test-fn
`(:test
',test-fn
)))))
819 (define-processor const
(name type
(value :value
:otherwise nil
))
820 (expected-json-keys -json-entry-
:tag
:name
:type
:value
:ns
)
821 (let ((cffi-type (json-type-to-cffi-type type
`(:contant
,name
))))
824 ;; #define __FOO_H and friends... just ignore them.
826 ((and (member cffi-type
'(:int
:unsigned-int
828 :long-long
:unsigned-long-long
))
830 (make-define-constant-form name value
))
831 ((and (member cffi-type
'(:float
:double
))
833 (make-define-constant-form name value
))
834 ((member cffi-type
'(:string
(:pointer
:char
)) :test
'equal
)
835 (make-define-constant-form name value
))
837 (warn "Don't know how to emit a constant of CFFI type ~S, with value ~S (json type is ~S)." cffi-type value type
)