Recently Written · git

clic

MIRROR ONLY Gopher client with pretty colours in Common Lisp

git clone https://github.com/equwal/clic

Log | Files | Refs


3rdparties/software/cffi_0.21.0/src/c2ffi/generator.lisp (38985 bytes)

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 ;;;
27 
28 (in-package #:cffi/c2ffi)
29 
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
51 
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)
65 
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)
72 
73 (defvar *c2ffi-output-stream*)
74 
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))))))
80 
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)))
93 
94 (defun output/string (message-control &rest message-arguments)
95   (apply 'format *c2ffi-output-stream* message-control message-arguments))
96 
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)
103 
104 (define-condition unsupported-type (cffi::foreign-type-error)
105   ((json-definition :initarg :json-definition
106                     :accessor json-definition-of)))
107 
108 (defun unsupported-type (json-entry)
109   (error 'unsupported-type :type-name nil :json-definition json-entry))
110 
111 ;;;;;;
112 ;;; Utilities
113 
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))))
124 
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)))))))
161 
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))
167 
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))))
174 
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)))))
182 
183 ;;;;;;
184 ;;; Json access
185 
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 "")
197         nil
198         result)))
199 
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))))
219 
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))))
227 
228 ;;;;;;
229 ;;; Namespaces, names and conversions
230 
231 ;; an alist of (name . hashtable)
232 (defvar *generated-names*)
233 (defvar *anon-name-counter*)
234 (defvar *anon-entities*)
235 
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)
242 
243 (defun lookup-anon-entity (id)
244   (or (gethash id *anon-entities*)
245       (error "Could not find anonymous entity with id ~S." id)))
246 
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*)))))
251 
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)))))
261 
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."))))
266 
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))
273 
274 (define-constant +name-kinds+ '(:struct :union :function :variable :type
275                                 :constant :field :argument :enum :member)
276   :test 'equal)
277 
278 (deftype ffi-name-kind ()
279   '#.(list* 'member +name-kinds+))
280 
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))
302 
303 (defun default-callback-factory (&key &allow-other-keys)
304   (values))
305 
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)))
314 
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)
321     ))
322 
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)))))))
338 
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))
348 
349 (defun maybe-camelcase-to-dash-separated (name)
350   (if (camelcased? name)
351       (camelcase-to-dash-separated name)
352       name))
353 
354 (defun default-ffi-name-export-predicate (symbol &key &allow-other-keys)
355   (declare (ignore symbol))
356   nil)
357 
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)))
367     (t
368      type)))
369 
370 (defun function-pointer-type-name ()
371   (symbolicate '#:function-pointer))
372 
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))))
383 
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)
407                 )
408               ;; return the result of the condition expression
409               )
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))
472              (t
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)))
483 
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)))
490 
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)))
496 
497 ;;;;;;
498 ;;; Entry point, the "API"
499 
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)))
592           ;;
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
599                                                     0
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)))
609                           ;;
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)))))
616             ;;
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))))
621           ;;
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*))
628 
629           ;;
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)
646 
647 (defun get-all-names-by-package (name-collection)
648   (let ((tables (mapcar #'cdr name-collection))
649         all
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))
660 
661 ;;;;;;
662 ;;; Processors for various definitions
663 
664 (defvar *c2ffi-entry-processors* (make-hash-table :test 'equal))
665 
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)))))
689 
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))))
695 
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
713                                                 id
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))))))
719 
720 (define-processor struct ()
721   (%process-struct-like -json-entry- :struct 'cffi:defcstruct '#:anon-struct-))
722 
723 (define-processor union ()
724   (%process-struct-like -json-entry- :union 'cffi:defcunion '#:anon-union-))
725 
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))))
730 
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))))))))))
769 
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))))
774 
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
805                                          id
806                                          (generate-anon-name '#:anon-enum-)))
807                                     :enum
808                                     (null name))
809          ,@(process-fields #'for-enum-body)))))
810 
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)))))
818 
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))
836       (t
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)))))