Make README leaner
[cffi.git] / src / c2ffi / generator.lisp
blob6010957988f70378cdec35a07ae7d7c139919d19
1 ;;;; -*- Mode: lisp; indent-tabs-mode: nil -*-
2 ;;;
3 ;;; generator.lisp --- Generate CFFI bindings for a c2ffi output.
4 ;;;
5 ;;; Copyright (C) 2015, Attila Lendvai <attila@lendvai.name>
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 ;;;
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.
33 ;;;
34 ;;; Each CFFI form is also EVAL'd during generation because the CFFI
35 ;;; type lookup/parsing mechanism is used while generating the output.
36 ;;;
37 ;;; Nomenclature:
38 ;;;
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
41 ;;; the cffi name.
42 ;;;
43 ;;; Possible improvments:
44 ;;;
45 ;;; - generate an additional grovel file for C inline function
46 ;;; declarations found in header files
47 ;;;
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 -*-~%~
68 ;;;~%~
69 ;;; This file has been automatically generated by cffi/c2ffi. Editing it by hand is not wise.~%~
70 ;;;~%~%"
71 :test 'equal)
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* "~&")
84 (write form
85 :stream *c2ffi-output-stream*
86 :circle t
87 :pretty t
88 :escape t
89 :readably t)
90 (format *c2ffi-output-stream* "~%~%")
91 (unless (member (first form) '(cffi:defcfun alexandria:define-constant) :test 'eq)
92 (eval form)))
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")
102 :test 'equal)
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))
111 ;;;;;;
112 ;;; Utilities
114 (defun compile-rules (rules)
115 (case rules
116 (:all 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
121 (string)
122 (funcall scanner string 0 (length string)))))
123 rules))))
125 (defun include-definition? (name source-location
126 include-definitions exclude-definitions
127 include-sources exclude-sources)
128 (labels
129 ((covered-by-a-rule? (name rules)
130 (or (eq rules :all)
131 (not (null (some (rcurry #'funcall name) rules)))))
132 (weak? (rules)
133 (eq :all rules))
134 (strong? (name rules)
135 (and name
136 (not (weak? 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
147 incl-src/strong))
148 (excl/strong (or excl-def/strong
149 excl-src/strong))
150 (incl/weak (or incl-def/weak
151 incl-src/weak))
152 (excl/weak (or excl-def/weak
153 excl-src/weak)))
154 (or incl-def/strong
155 (and (not excl/strong)
156 (or incl/strong
157 (and incl/weak
158 ;; we want src exclude rules to be stronger
159 (not excl-src/weak))
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))
166 byte-size))
168 (defmacro assume (condition &optional format-control &rest format-arguments)
169 "Similar to ASSERT, but WARN's only."
170 `(unless ,condition
171 ,(if format-control
172 `(warn ,format-control ,@format-arguments)
173 `(warn "ASSUME failed: ~S" ',condition))))
175 (defun canonicalize-transformer-hook (hook)
176 (etypecase hook
177 ((and (or function symbol)
178 (not null))
179 hook)
180 (string
181 (the symbol (safe-read-from-string hook)))))
183 ;;;;;;
184 ;;; Json access
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))
190 (result (cond
191 (entry
192 (cdr entry))
193 (otherwise?
194 otherwise)
195 (t (error "Key ~S not found in json entry ~S." key alist)))))
196 (if (equal result "")
198 result)))
200 (defmacro with-json-values ((json-entry &rest args) &body body)
201 (if (null args)
202 `(progn
203 ,@body)
204 (once-only (json-entry)
205 `(let (,@(loop
206 :for entry :in args
207 :collect (let* ((args (ensure-list entry))
208 (name (pop args))
209 (key (or (pop args)
210 (make-keyword (symbol-name name)))))
211 (destructuring-bind
212 ;; using &optional would trigger a warning (on SBCL)
213 (&key (otherwise nil otherwise?))
214 args
215 `(,name
216 (json-value ,json-entry ,key ,@(when otherwise?
217 `(:otherwise ,otherwise))))))))
218 ,@body))))
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))
224 alist)))
225 (when outliers
226 (warn "Unexpected key(s) in json entry ~S: ~S" alist outliers))))
228 ;;;;;;
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)
241 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)
248 (format nil "~A"
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)
254 (etypecase name
255 (string
256 (assert (not (zerop (length name)))))
257 (cons
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)
263 (apply hook
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)
276 :test 'equal)
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)
287 (not (null name)))
288 (stringp 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)
292 name
293 (intern 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*)))
300 cffi-name))
301 cffi-name))
303 (defun default-callback-factory (&key &allow-other-keys)
304 (values))
306 (defun default-ffi-name-transformer (name kind &key &allow-other-keys)
307 (check-type name string)
308 (case kind
309 #+nil
310 ((:constant :member)
311 (assert (not (symbolp name)))
312 (format nil "+~A+" name))
313 (t 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))
319 (:preserve name)
320 ;; (:invert no, you don't)
323 (defun camelcased? (name)
324 (and (>= (length name) 3)
325 (let ((lower 0)
326 (upper 0))
327 (loop
328 :for char :across name
329 :do (cond
330 ((upper-case-p char)
331 (incf upper))
332 ((lower-case-p char)
333 (incf lower))))
334 (unless (or (zerop lower)
335 (zerop upper))
336 (let ((ratio (/ upper lower)))
337 (and (<= 0.05 ratio 0.5)))))))
339 (defun camelcase-to-dash-separated (name)
340 (coerce (loop
341 :for char :across name
342 :for index :from 0
343 :when (and (upper-case-p char)
344 (not (zerop index)))
345 :collect #\-
346 :collect (char-downcase char))
347 'string))
349 (defun maybe-camelcase-to-dash-separated (name)
350 (if (camelcased? name)
351 (camelcase-to-dash-separated name)
352 name))
354 (defun default-ffi-name-export-predicate (symbol &key &allow-other-keys)
355 (declare (ignore symbol))
356 nil)
358 (defun default-ffi-type-transformer (type context &key &allow-other-keys)
359 (declare (ignore context))
360 (cond
361 ((and (consp type)
362 (eq :pointer (first type)))
363 (let ((pointed-to-type (second type)))
364 (if (eq pointed-to-type :char)
365 :string
366 type)))
368 type)))
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)
375 `(block ,type-block
376 (handler-bind
377 ((cffi::foreign-type-error
378 (lambda (_)
379 (declare (ignore _))
380 (when ,enabled
381 (return-from ,type-block ,on-failure-form)))))
382 ,@body))))
384 (defun %json-type-to-cffi-type (json-entry)
385 (with-json-values (json-entry tag)
386 (let ((cffi-type
387 (cond
388 ((switch (tag :test 'equal)
389 (":void" :void)
390 (":_Bool" :bool)
391 ;; regarding :signed-char see https://stackoverflow.com/questions/436513/char-signed-char-char-unsigned-char
392 (":char" :char)
393 (":signed-char" :char)
394 (":unsigned-char" :unsigned-char)
395 (":short" :short)
396 (":unsigned-short" :unsigned-short)
397 (":int" :int)
398 (":unsigned-int" :unsigned-int)
399 (":long" :long)
400 (":unsigned-long" :unsigned-long)
401 (":long-long" :long-long)
402 (":unsigned-long-long" :unsigned-long-long)
403 (":float" :float)
404 (":double" :double)
405 ;; TODO FIXME
406 ;;(":long-double" :long-double)
408 ;; return the result of the condition expression
410 ((or (progn
411 (assert (not (member tag +c-builtin-types+ :test 'equal)) ()
412 "Not all C basic types are covered! The outlier is: ~S" tag)
413 nil)
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")
420 :struct
421 :union))
422 (cffi-name (if name
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")
428 (equal tag "union"))
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")
436 :struct
437 :union)))
438 (assert (and (consp form)
439 (member (first form) '(cffi:defcstruct cffi:defcunion))))
440 `(,kind ,(first (ensure-list (second form))))))
441 ((equal tag ":enum")
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))
447 :enum)))
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)
450 cffi-name)))
451 ((equal tag "enum")
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
478 ;; skipped.
479 (find-cffi-type-or-die cffi-name)
480 cffi-name)))))
481 (assert cffi-type () "Failed to map ~S to a cffi type" json-entry)
482 cffi-type)))
484 (defun should-export-p (symbol)
485 (and symbol
486 (symbolp 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)))
493 (if context?
494 (call-hook *ffi-type-transformer* cffi-type context)
495 cffi-type)))
497 ;;;;;;
498 ;;; Entry point, the "API"
500 (defun process-c2ffi-spec-file (c2ffi-spec-file package-name
501 &key
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).
508 prelude
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*)
519 foreign-library-name
520 foreign-library-spec
521 (emit-generated-name-mappings t)
522 (include-sources :all)
523 exclude-sources
524 (include-definitions :all)
525 exclude-definitions)
526 "Generates a lisp file with CFFI definitions from C2FFI-SPEC-FILE.
527 PACKAGE-NAME will be overwritten, it assumes full control over the
528 target package."
529 (check-type c2ffi-spec-file (or pathname string))
530 (macrolet ((@ (var)
531 `(setf ,var (compile-rules ,var))))
532 (@ include-sources)
533 (@ exclude-sources)
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
549 ;; later.
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)))
555 +name-kinds+))
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
567 (mapc 'output/code
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)))
582 (etypecase prelude
583 (null)
584 (string
585 (output/string prelude))
586 (pathname
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)
601 0))))
602 (if (include-definition?
603 name source-location-file
604 include-definitions exclude-definitions
605 include-sources exclude-sources)
606 (progn
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
611 ;; definition
612 (when (and emitted-definition
613 form-callback)
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
623 (maphash
624 (lambda (package-name symbols)
625 (output/export (sort (remove-if-not #'should-export-p symbols) #'string<)
626 package-name))
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+))))))))
645 output)
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
653 (push s all)))
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))))
659 grouped))
661 ;;;;;;
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*)))
669 (if processor
670 (let ((definition-form
671 (handler-bind
672 ((unsupported-type
673 (lambda (e)
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
678 (lambda (e)
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)
685 definition-form))
686 (progn
687 (warn "No cffi/c2ffi processor defined for ~A" json-entry)
688 (values)))))
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)
694 ,@body))))
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)
708 ,cffi-type
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)))
715 kind
716 (null struct-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?
734 #+nil
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))
748 `(,(if argument-name
749 (json-name-to-cffi-name argument-name :argument)
750 (symbolicate '#:arg (princ-to-string index)))
751 ,cffi-type)))))
752 (let ((cffi-args (loop
753 :for arg :in parameters
754 :for index :upfrom 1
755 :collect (process-arg arg index))))
756 (cond
757 ((and uses-struct-by-value?
758 (not *assume-struct-by-value-support*))
759 (values))
760 (inline
761 ;; TODO inline functions should go into a separate grovel file?
762 (output/string "~&;; Skipping inline function ~S" function-name)
763 (values))
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))
766 ,@(append cffi-args
767 (when variadic
768 '(&rest))))))))))
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)
777 (let ((bitmasks 0)
778 (non-bitmasks 0))
779 (labels
780 ((for-bitmask-statistics (name value)
781 (declare (ignore name))
782 (if (cffi::single-bit-p value)
783 (incf bitmasks)
784 (incf non-bitmasks)))
785 (for-enum-body (name value)
786 `(,(json-name-to-cffi-name name :member)
787 ,value))
788 (process-fields (visitor)
789 (loop
790 :for json-entry :in fields
791 :do (expected-json-keys json-entry :tag :name :value)
792 :collect
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))
800 0.8)
801 'cffi:defbitfield
802 'cffi:defcenum)
803 ,(json-name-to-cffi-name (or name
804 (register-anon-entity
806 (generate-anon-name '#:anon-enum-)))
807 :enum
808 (null name))
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
814 (number)
815 (t 'equal))))
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))))
822 (cond
823 ((not value)
824 ;; #define __FOO_H and friends... just ignore them.
825 (values))
826 ((and (member cffi-type '(:int :unsigned-int
827 :long :unsigned-long
828 :long-long :unsigned-long-long))
829 (integerp value))
830 (make-define-constant-form name value))
831 ((and (member cffi-type '(:float :double))
832 (floatp value))
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)
838 (values)))))