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://github.com/cffi/cffi/issues/266
271 (setf namespace
:default
))
272 (cffi::find-type-parser type-name namespace
))
274 (eval-when (:compile-toplevel
:load-toplevel
:execute
)
275 (define-constant +name-kinds
+ '(:struct
:union
:function
:variable
:type
276 :constant
:field
:argument
:enum
:member
)
279 (deftype ffi-name-kind
()
280 '#.
(list* 'member
+name-kinds
+))
282 (defun json-name-to-cffi-name (name kind
&optional anonymous
)
283 (check-type name string
)
284 (check-type kind ffi-name-kind
)
285 (when *ffi-name-transformer
*
286 (setf name
(call-hook *ffi-name-transformer
* name kind
))
287 (unless (or (and (symbolp name
)
290 (error "The FFI-NAME-TRANSFORMER ~S returned with ~S which is not a valid name."
291 *ffi-name-transformer
* name
)))
292 (let ((cffi-name (if (symbolp name
)
295 (when (and (not anonymous
)
296 (boundp '*generated-names
*))
297 ;; TODO FIXME this function also gets called for e.g. argument types of a function. and
298 ;; if the function ends up *not* getting emitted, e.g. because of a missing type, then
299 ;; we wrongly record here the missing type in the *generated-names* registry.
300 (setf (gethash name
(cdr (assoc kind
*generated-names
*)))
304 (defun default-callback-factory (&key
&allow-other-keys
)
307 (defun default-ffi-name-transformer (name kind
&key
&allow-other-keys
)
308 (check-type name string
)
312 (assert (not (symbolp name
)))
313 (format nil
"+~A+" name
))
316 (defun change-case-to-readtable-case (name &optional
(reatable *readtable
*))
317 (ecase (readtable-case reatable
)
318 (:upcase
(string-upcase name
))
319 (:downcase
(string-downcase name
))
321 ;; (:invert no, you don't)
324 (defun camelcased?
(name)
325 (and (>= (length name
) 3)
329 :for char
:across name
335 (unless (or (zerop lower
)
337 (let ((ratio (/ upper lower
)))
338 (and (<= 0.05 ratio
0.5)))))))
340 (defun camelcase-to-dash-separated (name)
342 :for char
:across name
344 :when
(and (upper-case-p char
)
347 :collect
(char-downcase char
))
350 (defun maybe-camelcase-to-dash-separated (name)
351 (if (camelcased? name
)
352 (camelcase-to-dash-separated name
)
355 (defun default-ffi-name-export-predicate (symbol &key
&allow-other-keys
)
356 (declare (ignore symbol
))
359 (defun default-ffi-type-transformer (type context
&key
&allow-other-keys
)
360 (declare (ignore context
))
363 (eq :pointer
(first type
)))
364 (let ((pointed-to-type (second type
)))
365 (if (eq pointed-to-type
:char
)
371 (defun function-pointer-type-name ()
372 (symbolicate '#:function-pointer
))
374 (defmacro with-allowed-foreign-type-errors
((on-failure-form &key
(enabled t
)) &body body
)
375 (with-unique-names (type-block)
378 ((cffi::foreign-type-error
382 (return-from ,type-block
,on-failure-form
)))))
385 (defun %json-type-to-cffi-type
(json-entry)
386 (with-json-values (json-entry tag
)
389 ((switch (tag :test
'equal
)
392 ;; regarding :signed-char see https://stackoverflow.com/questions/436513/char-signed-char-char-unsigned-char
394 (":signed-char" :char
)
395 (":unsigned-char" :unsigned-char
)
397 (":unsigned-short" :unsigned-short
)
399 (":unsigned-int" :unsigned-int
)
401 (":unsigned-long" :unsigned-long
)
402 (":long-long" :long-long
)
403 (":unsigned-long-long" :unsigned-long-long
)
407 ;;(":long-double" :long-double)
409 ;; return the result of the condition expression
412 (assert (not (member tag
+c-builtin-types
+ :test
'equal
)) ()
413 "Not all C basic types are covered! The outlier is: ~S" tag
)
415 (equal tag
":struct")
416 (equal tag
":union"))
417 ;; ":struct" is a "struct foo-struct var" kind of reference
418 (expected-json-keys json-entry
:name
:tag
:id
)
419 (with-json-values (json-entry name id
)
420 (let* ((kind (if (equal tag
":struct")
424 (json-name-to-cffi-name name kind
)
425 (lookup-anon-entity id
))))
426 (find-cffi-type-or-die cffi-name kind
)
427 `(,kind
,cffi-name
))))
428 ((or (equal tag
"struct")
430 ;; "struct" denotes a "struct {} var", or "typedef struct {} my_type"
431 ;; kind of inline anonymous declaration. Let's call PROCESS-C2FFI-ENTRY
432 ;; to emit it for us, and return with the generated name (first value)
433 ;; as if it was a standalone toplevel struct definition.
434 ;; TODO is it a problem that we don't invoke the CALLBACK-FACTORY stuff here?
435 (let ((form (process-c2ffi-entry json-entry
))
436 (kind (if (equal tag
"struct")
439 (assert (and (consp form
)
440 (member (first form
) '(cffi:defcstruct cffi
:defcunion
))))
441 `(,kind
,(first (ensure-list (second form
))))))
443 ;; ":enum" is an "enum foo var" kind of reference
444 (expected-json-keys json-entry
:name
:tag
:id
)
445 (with-json-values (json-entry name id
)
446 (let ((cffi-name (json-name-to-cffi-name (or name
447 (lookup-anon-entity id
))
449 (find-cffi-type-or-die cffi-name
:enum
)
450 ;; TODO FIXME this would be the proper one, but CFFI is broken: `(:enum ,cffi-name)
453 ;; "enum" is an inline "typedef enum {m1, m2} var" kind of inline declaration
454 (expected-json-keys json-entry
:name
:tag
:id
)
455 ;; TODO FIXME similarly to struct, but it would be nice to see an example
456 (error "not yet implemented"))
457 ((equal tag
":array")
458 (expected-json-keys json-entry
:tag
:type
:size
)
459 (with-json-values (json-entry type size
)
460 (check-type size integer
)
461 `(:array
,(json-type-to-cffi-type type
) ,size
)))
462 ((equal tag
":pointer")
463 (expected-json-keys json-entry
:tag
:type
:id
)
464 (with-json-values (json-entry type
)
465 `(:pointer
,(with-allowed-foreign-type-errors
466 (:void
:enabled
*allow-pointer-type-simplification
*)
467 (json-type-to-cffi-type type
)))))
468 ((equal tag
":function-pointer")
469 (expected-json-keys json-entry
:tag
)
470 (function-pointer-type-name))
471 ((equal tag
":function")
472 (unsupported-type json-entry
))
474 (assert (not (starts-with #\
: tag
)))
475 (let ((cffi-name (json-name-to-cffi-name tag
:type
)))
476 ;; TODO FIXME json-name-to-cffi-name collects the mentioned
477 ;; types to later emit +TYPE-NAMES+, but if this next
478 ;; find-cffi-type-or-die dies then the entire function is
480 (find-cffi-type-or-die cffi-name
)
482 (assert cffi-type
() "Failed to map ~S to a cffi type" json-entry
)
485 (defun should-export-p (symbol)
488 (not (keywordp symbol
))
489 *ffi-name-export-predicate
*
490 (call-hook *ffi-name-export-predicate
* symbol
)))
492 (defun json-type-to-cffi-type (json-entry &optional
(context nil context?
))
493 (let ((cffi-type (%json-type-to-cffi-type json-entry
)))
495 (call-hook *ffi-type-transformer
* cffi-type context
)
499 ;;; Entry point, the "API"
501 (defun process-c2ffi-spec-file (c2ffi-spec-file package-name
503 (allow-pointer-type-simplification *allow-pointer-type-simplification
*)
504 (allow-skipping-struct-fields *allow-skipping-struct-fields
*)
505 (assume-struct-by-value-support *assume-struct-by-value-support
*)
506 ;; either a pathname or a string (will be copied as is),
507 ;; or a function that will be funcall'd with one argument
508 ;; to emit a form (i.e. OUTPUT/CODE).
510 (output (make-pathname :name
(strcat (pathname-name c2ffi-spec-file
) ".cffi-tmp")
511 :type
"lisp" :defaults c2ffi-spec-file
))
512 (output-encoding asdf
:*default-encoding
*)
513 ;; The args following this point are mirrored in the ASDF
514 ;; component on the same name.
515 (ffi-name-transformer *ffi-name-transformer
*)
516 (ffi-name-export-predicate *ffi-name-export-predicate
*)
517 ;; as per CFFI:DEFINE-FOREIGN-LIBRARY and CFFI:LOAD-FOREIGN-LIBRARY
518 (ffi-type-transformer *ffi-type-transformer
*)
519 (callback-factory *callback-factory
*)
522 (emit-generated-name-mappings t
)
523 (include-sources :all
)
525 (include-definitions :all
)
527 "Generates a lisp file with CFFI definitions from C2FFI-SPEC-FILE.
528 PACKAGE-NAME will be overwritten, it assumes full control over the
530 (check-type c2ffi-spec-file
(or pathname string
))
532 `(setf ,var
(compile-rules ,var
))))
535 (@ include-definitions
)
536 (@ exclude-definitions
))
537 (with-standard-io-syntax
538 (with-input-from-file (in c2ffi-spec-file
:external-format
(uiop:encoding-external-format
:utf-8
))
539 (with-output-to-file (*c2ffi-output-stream
* output
:if-exists
:supersede
540 :external-format
(uiop:encoding-external-format output-encoding
))
541 (let* ((*package
* (or (find-package package-name
)
542 (make-package package-name
)))
543 ;; Make sure we use an uninterned symbol, so that it's neutral to READTABLE-CASE.
544 (package-name (make-symbol (package-name *package
*)))
545 ;; Let's rebind a copy, so that when we are done with
546 ;; the generation (which also EVAL's the forms) then
547 ;; the CFFI type repository is also reverted back to
548 ;; the previous state. This avoids redefinition warning
549 ;; when the generated file gets compiled and loaded
551 (cffi::*default-type-parsers
* (copy-hash-table cffi
::*default-type-parsers
*))
552 (cffi::*struct-type-parsers
* (copy-hash-table cffi
::*struct-type-parsers
*))
553 (cffi::*union-type-parsers
* (copy-hash-table cffi
::*union-type-parsers
*))
554 (*anon-name-counter
* 0)
555 (*anon-entities
* (make-hash-table))
556 (*generated-names
* (mapcar (lambda (key)
557 `(,key .
,(make-hash-table :test
'equal
)))
559 (*allow-pointer-type-simplification
* allow-pointer-type-simplification
)
560 (*allow-skipping-struct-fields
* allow-skipping-struct-fields
)
561 (*assume-struct-by-value-support
* assume-struct-by-value-support
)
562 (*ffi-name-transformer
* (canonicalize-transformer-hook ffi-name-transformer
))
563 (*ffi-name-export-predicate
* (canonicalize-transformer-hook ffi-name-export-predicate
))
564 (*ffi-type-transformer
* (canonicalize-transformer-hook ffi-type-transformer
))
565 (*callback-factory
* (canonicalize-transformer-hook callback-factory
))
566 (*read-default-float-format
* 'double-float
)
567 (json (json:decode-json in
)))
568 (output/string
+generated-file-header
+)
569 ;; some forms that are always emitted
571 ;; Make sure the package exists. We don't even want to :use COMMON-LISP here,
572 ;; to avoid any possible name clashes.
573 `((uiop:define-package
,package-name
(:use
))
574 (in-package ,package-name
)
575 (cffi:defctype
,(function-pointer-type-name) :pointer
)))
576 (when (and foreign-library-name
577 foreign-library-spec
)
578 (when (stringp foreign-library-name
)
579 (setf foreign-library-name
(safe-read-from-string foreign-library-name
)))
580 (output/code
`(cffi:define-foreign-library
,foreign-library-name
581 ,@foreign-library-spec
))
582 ;; TODO: Unconditionally emitting a USE-FOREIGN-LIBRARY may not be smart.
583 ;; For details see: https://github.com/cffi/cffi/issues/272
584 (output/code
`(cffi:use-foreign-library
,foreign-library-name
)))
588 (output/string prelude
))
590 (with-input-from-file (prelude-stream prelude
)
591 (alexandria:copy-stream prelude-stream
*c2ffi-output-stream
*
592 :element-type
'character
)))
593 ((or symbol function
)
594 (funcall prelude
'output
/code
)))
596 ;; Let's enumerate the entries
597 (multiple-value-bind (form-callback epilogue-callback
)
598 (funcall *callback-factory
*)
599 (dolist (json-entry json
)
600 (with-json-values (json-entry name location
)
601 (let ((source-location-file (subseq location
603 (or (position #\
: location
)
605 (if (include-definition?
606 name source-location-file
607 include-definitions exclude-definitions
608 include-sources exclude-sources
)
610 (output/string
"~&~%;; ~S" location
)
611 (let ((emitted-definition (process-c2ffi-entry json-entry
)))
613 ;; Call the plugin to let the user emit a form after the given
615 (when (and emitted-definition
617 (map nil
'output
/code
(call-hook form-callback emitted-definition
)))))
618 (output/string
"~&;; Skipped ~S due to filters" name
)))))
620 ;; Call the plugin to let the user append multiple forms after the
621 ;; emitted definitions
622 (when epilogue-callback
623 (map nil
'output
/code
(call-hook epilogue-callback
))))
625 ;; emit optional exports
627 (lambda (package-name symbols
)
628 (output/export
(sort (remove-if-not #'should-export-p symbols
) #'string
<)
630 (get-all-names-by-package *generated-names
*))
633 ;; emit optional mappings
634 (when emit-generated-name-mappings
635 (mapcar (lambda (entry)
636 (destructuring-bind (kind variable-name
) entry
637 (output/code
`(defparameter
638 ,(intern (symbol-name variable-name
))
639 ',(hash-table-alist (cdr (assoc kind
*generated-names
*)))))))
640 `((:function
#:+function-names
+)
641 (:struct
#:+struct-names
+)
642 (:union
#:+union-names
+)
643 (:variable
#:+variable-names
+)
644 (:type
#:+type-names
+)
645 (:constant
#:+constant-names
+)
646 (:argument
#:+argument-names
+)
647 (:field
#:+field-names
+))))))))
650 (defun get-all-names-by-package (name-collection)
651 (let ((tables (mapcar #'cdr name-collection
))
653 (grouped (make-hash-table)))
654 (loop :for table
:in tables
:do
655 (loop :for s
:being
:the
:hash-values
:of table
:do
657 (remove-duplicates all
:test
#'eq
)
658 (loop :for name
:in all
659 :for package-name
:= (package-name (symbol-package name
))
660 :do
(setf (gethash package-name grouped
)
661 (cons name
(gethash package-name grouped
))))
665 ;;; Processors for various definitions
667 (defvar *c2ffi-entry-processors
* (make-hash-table :test
'equal
))
669 (defun process-c2ffi-entry (json-entry)
670 (let* ((kind (json-value json-entry
:tag
))
671 (processor (gethash kind
*c2ffi-entry-processors
*)))
673 (let ((definition-form
677 (warn "Skip definition because cannot map ~S to any CFFI type. The definition is ~S"
678 (json-definition-of e
) json-entry
)
679 (return-from process-c2ffi-entry
(values))))
680 (cffi::undefined-foreign-type-error
682 (output/string
"~&;; Skipping definition ~S because of missing type ~S"
683 json-entry
(cffi::foreign-type-error
/compound-name e
))
684 (return-from process-c2ffi-entry
(values)))))
685 (funcall processor json-entry
))))
686 (when definition-form
687 (output/code definition-form
)
690 (warn "No cffi/c2ffi processor defined for ~A" json-entry
)
693 (defmacro define-processor
(kind args
&body body
)
694 `(setf (gethash ,(string-downcase kind
) *c2ffi-entry-processors
*)
695 (named-lambda ,(symbolicate 'c2ffi-processor
/ kind
) (-json-entry-)
696 (with-json-values (-json-entry- ,@args
)
699 (defun %process-struct-like
(json-entry kind definer anon-base-name
)
700 (expected-json-keys json-entry
:tag
:ns
:name
:id
:bit-size
:bit-alignment
:fields
)
701 (with-json-values (json-entry tag
(struct-name :name
) fields bit-size id
)
702 (assert (member tag
'(":struct" "struct" ":union" "union") :test
'equal
))
703 (flet ((process-field (json-entry)
704 (with-json-values (json-entry (field-name :name
) bit-offset type
)
705 (let ((cffi-type (with-allowed-foreign-type-errors
706 ('failed
:enabled
*allow-skipping-struct-fields
*)
707 (json-type-to-cffi-type type
`(,kind
,struct-name
,field-name
)))))
708 (if (eq cffi-type
'failed
)
709 (output/string
"~&;; skipping field due to missing type ~S, full json entry: ~S" type json-entry
)
710 `(,(json-name-to-cffi-name field-name
:field
)
712 ,@(unless (eq kind
:union
)
713 `(:offset
,(coerce-to-byte-size bit-offset
)))))))))
714 `(,definer
(,(json-name-to-cffi-name (or struct-name
715 (register-anon-entity
717 (generate-anon-name anon-base-name
)))
720 :size
,(coerce-to-byte-size bit-size
))
721 ,@(remove nil
(mapcar #'process-field fields
))))))
723 (define-processor struct
()
724 (%process-struct-like -json-entry-
:struct
'cffi
:defcstruct
'#:anon-struct-
))
726 (define-processor union
()
727 (%process-struct-like -json-entry-
:union
'cffi
:defcunion
'#:anon-union-
))
729 (define-processor typedef
(name type
)
730 (expected-json-keys -json-entry-
:tag
:name
:ns
:type
)
731 `(cffi:defctype
,(json-name-to-cffi-name name
:type
)
732 ,(json-type-to-cffi-type type
`(:typedef
,name
))))
734 (define-processor function
(return-type (function-name :name
) parameters inline variadic storage-class
)
735 (declare (ignore storage-class
))
736 ;; TODO does storage-class matter for FFI accessibility?
738 (assume (equal "extern" storage-class
)
739 "Unexpected function STORAGE-CLASS: ~S for function ~S" storage-class function-name
)
740 (expected-json-keys -json-entry-
:tag
:name
:return-type
:parameters
:variadic
:inline
:storage-class
:ns
)
741 (let ((uses-struct-by-value? nil
))
742 (flet ((process-arg (json-entry index
)
743 (expected-json-keys json-entry
:tag
:name
:type
)
744 (with-json-values (json-entry tag
(argument-name :name
) type
)
745 (assert (equal tag
"parameter"))
746 (let* ((cffi-type (json-type-to-cffi-type type
`(:function
,function-name
,argument-name
)))
747 (canonicalized-type (cffi::canonicalize-foreign-type cffi-type
)))
748 (when (and (consp canonicalized-type
)
749 (member (first canonicalized-type
) '(:struct
:union
)))
750 (setf uses-struct-by-value? t
))
752 (json-name-to-cffi-name argument-name
:argument
)
753 (symbolicate '#:arg
(princ-to-string index
)))
755 (let ((cffi-args (loop
756 :for arg
:in parameters
758 :collect
(process-arg arg index
))))
760 ((and uses-struct-by-value?
761 (not *assume-struct-by-value-support
*))
764 ;; TODO inline functions should go into a separate grovel file?
765 (output/string
"~&;; Skipping inline function ~S" function-name
)
767 (t `(cffi:defcfun
(,function-name
,(json-name-to-cffi-name function-name
:function
))
768 ,(json-type-to-cffi-type return-type
`(:function
,function-name
:return-type
))
773 (define-processor extern
(name type
)
774 (expected-json-keys -json-entry-
:tag
:name
:type
)
775 `(cffi:defcvar
(,name
,(json-name-to-cffi-name name
:variable
))
776 ,(json-type-to-cffi-type type
`(:variable
,name
))))
778 ;; ((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))))
779 (define-processor enum
(name fields id
)
783 ((for-bitmask-statistics (name value
)
784 (declare (ignore name
))
785 (if (cffi::single-bit-p value
)
787 (incf non-bitmasks
)))
788 (for-enum-body (name value
)
789 `(,(json-name-to-cffi-name name
:member
)
791 (process-fields (visitor)
793 :for json-entry
:in fields
794 :do
(expected-json-keys json-entry
:tag
:name
:value
)
796 (with-json-values (json-entry tag name value
)
797 (assert (equal tag
"field"))
798 (check-type value integer
)
799 (funcall visitor name value
)))))
800 (process-fields #'for-bitmask-statistics
)
801 `(,(if (> (/ bitmasks
802 (+ non-bitmasks bitmasks
))
806 ,(json-name-to-cffi-name (or name
807 (register-anon-entity
809 (generate-anon-name '#:anon-enum-
)))
812 ,@(process-fields #'for-enum-body
)))))
814 (defun make-define-constant-form (name value
)
815 (valid-name-or-die name
)
816 (let ((test-fn (typecase value
819 `(alexandria:define-constant
,(json-name-to-cffi-name name
:constant
)
820 ,value
,@(when test-fn
`(:test
',test-fn
)))))
822 (define-processor const
(name type
(value :value
:otherwise nil
))
823 (expected-json-keys -json-entry-
:tag
:name
:type
:value
:ns
)
824 (let ((cffi-type (json-type-to-cffi-type type
`(:contant
,name
))))
827 ;; #define __FOO_H and friends... just ignore them.
829 ((and (member cffi-type
'(:int
:unsigned-int
831 :long-long
:unsigned-long-long
))
833 (make-define-constant-form name value
))
834 ((and (member cffi-type
'(:float
:double
))
836 (make-define-constant-form name value
))
837 ((member cffi-type
'(:string
(:pointer
:char
)) :test
'equal
)
838 (make-define-constant-form name value
))
840 (warn "Don't know how to emit a constant of CFFI type ~S, with value ~S (json type is ~S)." cffi-type value type
)