Split *type-parsers* into three EQL hash tables
[cffi.git] / src / c2ffi / generator.lisp
blob104fdbbc32a67901e1ff98be10dbccbae4d47cc3
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 (eval-when (:compile-toplevel :load-toplevel :execute)
275 (define-constant +name-kinds+ '(:struct :union :function :variable :type
276 :constant :field :argument :enum :member)
277 :test 'equal))
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)
288 (not (null name)))
289 (stringp 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)
293 name
294 (intern 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*)))
301 cffi-name))
302 cffi-name))
304 (defun default-callback-factory (&key &allow-other-keys)
305 (values))
307 (defun default-ffi-name-transformer (name kind &key &allow-other-keys)
308 (check-type name string)
309 (case kind
310 #+nil
311 ((:constant :member)
312 (assert (not (symbolp name)))
313 (format nil "+~A+" name))
314 (t 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))
320 (:preserve name)
321 ;; (:invert no, you don't)
324 (defun camelcased? (name)
325 (and (>= (length name) 3)
326 (let ((lower 0)
327 (upper 0))
328 (loop
329 :for char :across name
330 :do (cond
331 ((upper-case-p char)
332 (incf upper))
333 ((lower-case-p char)
334 (incf lower))))
335 (unless (or (zerop lower)
336 (zerop upper))
337 (let ((ratio (/ upper lower)))
338 (and (<= 0.05 ratio 0.5)))))))
340 (defun camelcase-to-dash-separated (name)
341 (coerce (loop
342 :for char :across name
343 :for index :from 0
344 :when (and (upper-case-p char)
345 (not (zerop index)))
346 :collect #\-
347 :collect (char-downcase char))
348 'string))
350 (defun maybe-camelcase-to-dash-separated (name)
351 (if (camelcased? name)
352 (camelcase-to-dash-separated name)
353 name))
355 (defun default-ffi-name-export-predicate (symbol &key &allow-other-keys)
356 (declare (ignore symbol))
357 nil)
359 (defun default-ffi-type-transformer (type context &key &allow-other-keys)
360 (declare (ignore context))
361 (cond
362 ((and (consp type)
363 (eq :pointer (first type)))
364 (let ((pointed-to-type (second type)))
365 (if (eq pointed-to-type :char)
366 :string
367 type)))
369 type)))
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)
376 `(block ,type-block
377 (handler-bind
378 ((cffi::foreign-type-error
379 (lambda (_)
380 (declare (ignore _))
381 (when ,enabled
382 (return-from ,type-block ,on-failure-form)))))
383 ,@body))))
385 (defun %json-type-to-cffi-type (json-entry)
386 (with-json-values (json-entry tag)
387 (let ((cffi-type
388 (cond
389 ((switch (tag :test 'equal)
390 (":void" :void)
391 (":_Bool" :bool)
392 ;; regarding :signed-char see https://stackoverflow.com/questions/436513/char-signed-char-char-unsigned-char
393 (":char" :char)
394 (":signed-char" :char)
395 (":unsigned-char" :unsigned-char)
396 (":short" :short)
397 (":unsigned-short" :unsigned-short)
398 (":int" :int)
399 (":unsigned-int" :unsigned-int)
400 (":long" :long)
401 (":unsigned-long" :unsigned-long)
402 (":long-long" :long-long)
403 (":unsigned-long-long" :unsigned-long-long)
404 (":float" :float)
405 (":double" :double)
406 ;; TODO FIXME
407 ;;(":long-double" :long-double)
409 ;; return the result of the condition expression
411 ((or (progn
412 (assert (not (member tag +c-builtin-types+ :test 'equal)) ()
413 "Not all C basic types are covered! The outlier is: ~S" tag)
414 nil)
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")
421 :struct
422 :union))
423 (cffi-name (if name
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")
429 (equal tag "union"))
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")
437 :struct
438 :union)))
439 (assert (and (consp form)
440 (member (first form) '(cffi:defcstruct cffi:defcunion))))
441 `(,kind ,(first (ensure-list (second form))))))
442 ((equal tag ":enum")
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))
448 :enum)))
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)
451 cffi-name)))
452 ((equal tag "enum")
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
479 ;; skipped.
480 (find-cffi-type-or-die cffi-name)
481 cffi-name)))))
482 (assert cffi-type () "Failed to map ~S to a cffi type" json-entry)
483 cffi-type)))
485 (defun should-export-p (symbol)
486 (and symbol
487 (symbolp 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)))
494 (if context?
495 (call-hook *ffi-type-transformer* cffi-type context)
496 cffi-type)))
498 ;;;;;;
499 ;;; Entry point, the "API"
501 (defun process-c2ffi-spec-file (c2ffi-spec-file package-name
502 &key
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).
509 prelude
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*)
520 foreign-library-name
521 foreign-library-spec
522 (emit-generated-name-mappings t)
523 (include-sources :all)
524 exclude-sources
525 (include-definitions :all)
526 exclude-definitions)
527 "Generates a lisp file with CFFI definitions from C2FFI-SPEC-FILE.
528 PACKAGE-NAME will be overwritten, it assumes full control over the
529 target package."
530 (check-type c2ffi-spec-file (or pathname string))
531 (macrolet ((@ (var)
532 `(setf ,var (compile-rules ,var))))
533 (@ include-sources)
534 (@ exclude-sources)
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
550 ;; later.
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)))
558 +name-kinds+))
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
570 (mapc 'output/code
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://bugs.launchpad.net/cffi/+bug/1593635
584 (output/code `(cffi:use-foreign-library ,foreign-library-name)))
585 (etypecase prelude
586 (null)
587 (string
588 (output/string prelude))
589 (pathname
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)
604 0))))
605 (if (include-definition?
606 name source-location-file
607 include-definitions exclude-definitions
608 include-sources exclude-sources)
609 (progn
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
614 ;; definition
615 (when (and emitted-definition
616 form-callback)
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
626 (maphash
627 (lambda (package-name symbols)
628 (output/export (sort (remove-if-not #'should-export-p symbols) #'string<)
629 package-name))
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+))))))))
648 output)
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
656 (push s all)))
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))))
662 grouped))
664 ;;;;;;
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*)))
672 (if processor
673 (let ((definition-form
674 (handler-bind
675 ((unsupported-type
676 (lambda (e)
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
681 (lambda (e)
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)
688 definition-form))
689 (progn
690 (warn "No cffi/c2ffi processor defined for ~A" json-entry)
691 (values)))))
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)
697 ,@body))))
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)
711 ,cffi-type
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)))
718 kind
719 (null struct-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?
737 #+nil
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))
751 `(,(if argument-name
752 (json-name-to-cffi-name argument-name :argument)
753 (symbolicate '#:arg (princ-to-string index)))
754 ,cffi-type)))))
755 (let ((cffi-args (loop
756 :for arg :in parameters
757 :for index :upfrom 1
758 :collect (process-arg arg index))))
759 (cond
760 ((and uses-struct-by-value?
761 (not *assume-struct-by-value-support*))
762 (values))
763 (inline
764 ;; TODO inline functions should go into a separate grovel file?
765 (output/string "~&;; Skipping inline function ~S" function-name)
766 (values))
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))
769 ,@(append cffi-args
770 (when variadic
771 '(&rest))))))))))
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)
780 (let ((bitmasks 0)
781 (non-bitmasks 0))
782 (labels
783 ((for-bitmask-statistics (name value)
784 (declare (ignore name))
785 (if (cffi::single-bit-p value)
786 (incf bitmasks)
787 (incf non-bitmasks)))
788 (for-enum-body (name value)
789 `(,(json-name-to-cffi-name name :member)
790 ,value))
791 (process-fields (visitor)
792 (loop
793 :for json-entry :in fields
794 :do (expected-json-keys json-entry :tag :name :value)
795 :collect
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))
803 0.8)
804 'cffi:defbitfield
805 'cffi:defcenum)
806 ,(json-name-to-cffi-name (or name
807 (register-anon-entity
809 (generate-anon-name '#:anon-enum-)))
810 :enum
811 (null name))
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
817 (number)
818 (t 'equal))))
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))))
825 (cond
826 ((not value)
827 ;; #define __FOO_H and friends... just ignore them.
828 (values))
829 ((and (member cffi-type '(:int :unsigned-int
830 :long :unsigned-long
831 :long-long :unsigned-long-long))
832 (integerp value))
833 (make-define-constant-form name value))
834 ((and (member cffi-type '(:float :double))
835 (floatp value))
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)
841 (values)))))