functions.lisp - clic - Clic is an command line interactive client for gopher written in Common LISP
 (HTM) git clone git://bitreich.org/clic/ git://enlrupgkhuxnvlhsf6lc3fziv5h2hhfrinws65d7roiv6bfj7d652fid.onion/clic/
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) Tags
 (DIR) README
 (DIR) LICENSE
       ---
       functions.lisp (19030B)
       ---
            1 ;;;; -*- Mode: lisp; indent-tabs-mode: nil -*-
            2 ;;;
            3 ;;; functions.lisp --- High-level interface to foreign functions.
            4 ;;;
            5 ;;; Copyright (C) 2005-2006, James Bielman  <jamesjb@jamesjb.com>
            6 ;;; Copyright (C) 2005-2007, Luis Oliveira  <loliveira@common-lisp.net>
            7 ;;;
            8 ;;; Permission is hereby granted, free of charge, to any person
            9 ;;; obtaining a copy of this software and associated documentation
           10 ;;; files (the "Software"), to deal in the Software without
           11 ;;; restriction, including without limitation the rights to use, copy,
           12 ;;; modify, merge, publish, distribute, sublicense, and/or sell copies
           13 ;;; of the Software, and to permit persons to whom the Software is
           14 ;;; furnished to do so, subject to the following conditions:
           15 ;;;
           16 ;;; The above copyright notice and this permission notice shall be
           17 ;;; included in all copies or substantial portions of the Software.
           18 ;;;
           19 ;;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
           20 ;;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
           21 ;;; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
           22 ;;; NONINFRINGEMENT.  IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
           23 ;;; HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
           24 ;;; WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
           25 ;;; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
           26 ;;; DEALINGS IN THE SOFTWARE.
           27 ;;;
           28 
           29 (in-package #:cffi)
           30 
           31 ;;;# Calling Foreign Functions
           32 ;;;
           33 ;;; FOREIGN-FUNCALL is the main primitive for calling foreign
           34 ;;; functions.  It converts each argument based on the installed
           35 ;;; translators for its type, then passes the resulting list to
           36 ;;; CFFI-SYS:%FOREIGN-FUNCALL.
           37 ;;;
           38 ;;; For implementation-specific reasons, DEFCFUN doesn't use
           39 ;;; FOREIGN-FUNCALL directly and might use something else (passed to
           40 ;;; TRANSLATE-OBJECTS as the CALL-FORM argument) instead of
           41 ;;; CFFI-SYS:%FOREIGN-FUNCALL to call the foreign-function.
           42 
           43 (defun translate-objects (syms args types rettype call-form &optional indirect)
           44   "Helper function for FOREIGN-FUNCALL and DEFCFUN.  If 'indirect is T, all arguments are represented by foreign pointers, even those that can be represented by CL objects."
           45   (if (null args)
           46       (expand-from-foreign call-form (parse-type rettype))
           47       (funcall
           48        (if indirect
           49            #'expand-to-foreign-dyn-indirect
           50            #'expand-to-foreign-dyn)
           51        (car args) (car syms)
           52        (list (translate-objects (cdr syms) (cdr args)
           53                                 (cdr types) rettype call-form indirect))
           54        (parse-type (car types)))))
           55 
           56 (defun parse-args-and-types (args)
           57   "Returns 4 values: types, canonicalized types, args and return type."
           58   (let* ((len (length args))
           59          (return-type (if (oddp len) (lastcar args) :void)))
           60     (loop repeat (floor len 2)
           61           for (type arg) on args by #'cddr
           62           collect type into types
           63           collect (canonicalize-foreign-type type) into ctypes
           64           collect arg into fargs
           65           finally (return (values types ctypes fargs return-type)))))
           66 
           67 ;;; While the options passed directly to DEFCFUN/FOREIGN-FUNCALL have
           68 ;;; precedence, we also grab its library's options, if possible.
           69 (defun parse-function-options (options &key pointer)
           70   (destructuring-bind (&key (library :default libraryp)
           71                             (cconv nil cconv-p)
           72                             (calling-convention cconv calling-convention-p)
           73                             (convention calling-convention))
           74       options
           75     (when cconv-p
           76       (warn-obsolete-argument :cconv :convention))
           77     (when calling-convention-p
           78       (warn-obsolete-argument :calling-convention :convention))
           79     (list* :convention
           80            (or convention
           81                (when libraryp
           82                  (let ((lib-options (foreign-library-options
           83                                      (get-foreign-library library))))
           84                    (getf lib-options :convention)))
           85                :cdecl)
           86            ;; Don't pass the library option if we're dealing with
           87            ;; FOREIGN-FUNCALL-POINTER.
           88            (unless pointer
           89              (list :library library)))))
           90 
           91 (defun structure-by-value-p (ctype)
           92   "A structure or union is to be called or returned by value."
           93   (let ((actual-type (ensure-parsed-base-type ctype)))
           94     (or (and (typep actual-type 'foreign-struct-type)
           95              (not (bare-struct-type-p actual-type)))
           96         #+cffi::no-long-long (typep actual-type 'emulated-llong-type))))
           97 
           98 (defun fn-call-by-value-p (argument-types return-type)
           99   "One or more structures in the arguments or return from the function are called by value."
          100   (or (some 'structure-by-value-p argument-types)
          101       (structure-by-value-p return-type)))
          102 
          103 (defvar *foreign-structures-by-value*
          104   (lambda (&rest args)
          105     (declare (ignore args))
          106     (restart-case
          107         (error "Unable to call structures by value without cffi-libffi loaded.")
          108       (load-cffi-libffi () :report "Load cffi-libffi."
          109         (asdf:operate 'asdf:load-op 'cffi-libffi))))
          110   "A function that produces a form suitable for calling structures by value.")
          111 
          112 (defun foreign-funcall-form (thing options args pointerp)
          113   (multiple-value-bind (types ctypes fargs rettype)
          114       (parse-args-and-types args)
          115     (let ((syms (make-gensym-list (length fargs)))
          116           (fsbvp (fn-call-by-value-p ctypes rettype)))
          117       (if fsbvp
          118           ;; Structures by value call through *foreign-structures-by-value*
          119           (funcall *foreign-structures-by-value*
          120                    thing
          121                    fargs
          122                    syms
          123                    types
          124                    rettype
          125                    ctypes
          126                    pointerp)
          127           (translate-objects
          128            syms fargs types rettype
          129            `(,(if pointerp '%foreign-funcall-pointer '%foreign-funcall)
          130              ;; No structures by value, direct call
          131              ,thing
          132              (,@(mapcan #'list ctypes syms)
          133               ,(canonicalize-foreign-type rettype))
          134              ,@(parse-function-options options :pointer pointerp)))))))
          135 
          136 (defmacro foreign-funcall (name-and-options &rest args)
          137   "Wrapper around %FOREIGN-FUNCALL that translates its arguments."
          138   (let ((name (car (ensure-list name-and-options)))
          139         (options (cdr (ensure-list name-and-options))))
          140     (foreign-funcall-form name options args nil)))
          141 
          142 (defmacro foreign-funcall-pointer (pointer options &rest args)
          143   (foreign-funcall-form pointer options args t))
          144 
          145 (defun promote-varargs-type (builtin-type)
          146   "Default argument promotions."
          147   (case builtin-type
          148     (:float :double)
          149     ((:char :short) :int)
          150     ((:unsigned-char :unsigned-short) :unsigned-int)
          151     (t builtin-type)))
          152 
          153 ;; If cffi-sys doesn't provide a %foreign-funcall-varargs macros we
          154 ;; define one that use %foreign-funcall.
          155 (eval-when (:compile-toplevel :load-toplevel :execute)
          156   (unless (fboundp '%foreign-funcall-varargs)
          157     (defmacro %foreign-funcall-varargs (name fixed-args varargs
          158                                         &rest args &key convention library)
          159       (declare (ignore convention library))
          160       `(%foreign-funcall ,name ,(append fixed-args varargs) ,@args)))
          161   (unless (fboundp '%foreign-funcall-pointer-varargs)
          162     (defmacro %foreign-funcall-pointer-varargs (pointer fixed-args varargs
          163                                                 &rest args &key convention)
          164       (declare (ignore convention))
          165       `(%foreign-funcall-pointer ,pointer ,(append fixed-args varargs) ,@args))))
          166 
          167 (defun foreign-funcall-varargs-form (thing options fixed-args varargs pointerp)
          168   (multiple-value-bind (fixed-types fixed-ctypes fixed-fargs)
          169       (parse-args-and-types fixed-args)
          170     (multiple-value-bind (varargs-types varargs-ctypes varargs-fargs rettype)
          171         (parse-args-and-types varargs)
          172       (let ((fixed-syms (make-gensym-list (length fixed-fargs)))
          173             (varargs-syms (make-gensym-list (length varargs-fargs))))
          174         (translate-objects
          175          (append fixed-syms varargs-syms)
          176          (append fixed-fargs varargs-fargs)
          177          (append fixed-types varargs-types)
          178          rettype
          179          `(,(if pointerp '%foreign-funcall-pointer-varargs '%foreign-funcall-varargs)
          180             ,thing
          181             ,(mapcan #'list fixed-ctypes fixed-syms)
          182             ,(append
          183               (mapcan #'list
          184                       (mapcar #'promote-varargs-type varargs-ctypes)
          185                       (loop for sym in varargs-syms
          186                             and type in varargs-ctypes
          187                             if (eq type :float)
          188                               collect `(float ,sym 1.0d0)
          189                             else collect sym))
          190               (list (canonicalize-foreign-type rettype)))
          191             ,@options))))))
          192 
          193 (defmacro foreign-funcall-varargs (name-and-options fixed-args
          194                                    &rest varargs)
          195   "Wrapper around %FOREIGN-FUNCALL that translates its arguments
          196 and does type promotion for the variadic arguments."
          197   (let ((name (car (ensure-list name-and-options)))
          198         (options (cdr (ensure-list name-and-options))))
          199     (foreign-funcall-varargs-form name options fixed-args varargs nil)))
          200 
          201 (defmacro foreign-funcall-pointer-varargs (pointer options fixed-args
          202                                            &rest varargs)
          203   "Wrapper around %FOREIGN-FUNCALL-POINTER that translates its
          204 arguments and does type promotion for the variadic arguments."
          205   (foreign-funcall-varargs-form pointer options fixed-args varargs t))
          206 
          207 ;;;# Defining Foreign Functions
          208 ;;;
          209 ;;; The DEFCFUN macro provides a declarative interface for defining
          210 ;;; Lisp functions that call foreign functions.
          211 
          212 ;; If cffi-sys doesn't provide a defcfun-helper-forms,
          213 ;; we define one that uses %foreign-funcall.
          214 (eval-when (:compile-toplevel :load-toplevel :execute)
          215   (unless (fboundp 'defcfun-helper-forms)
          216     (defun defcfun-helper-forms (name lisp-name rettype args types options)
          217       (declare (ignore lisp-name))
          218       (values
          219        '()
          220        `(%foreign-funcall ,name ,(append (mapcan #'list types args)
          221                                          (list rettype))
          222                           ,@options)))))
          223 
          224 (defun %defcfun (lisp-name foreign-name return-type args options docstring)
          225   (let* ((arg-names (mapcar #'first args))
          226          (arg-types (mapcar #'second args))
          227          (syms (make-gensym-list (length args)))
          228          (call-by-value (fn-call-by-value-p arg-types return-type)))
          229     (multiple-value-bind (prelude caller)
          230         (if call-by-value
          231             (values nil nil)
          232             (defcfun-helper-forms
          233              foreign-name lisp-name (canonicalize-foreign-type return-type)
          234              syms (mapcar #'canonicalize-foreign-type arg-types) options))
          235       `(progn
          236          ,prelude
          237          (defun ,lisp-name ,arg-names
          238            ,@(ensure-list docstring)
          239            ,(if call-by-value
          240                 `(foreign-funcall
          241                   ,(cons foreign-name options)
          242                   ,@(append (mapcan #'list arg-types arg-names)
          243                             (list return-type)))
          244                 (translate-objects
          245                  syms arg-names arg-types return-type caller)))))))
          246 
          247 (defun %defcfun-varargs (lisp-name foreign-name return-type args options doc)
          248   (with-unique-names (varargs)
          249     (let ((arg-names (mapcar #'car args)))
          250       `(defmacro ,lisp-name (,@arg-names &rest ,varargs)
          251          ,@(ensure-list doc)
          252          `(foreign-funcall-varargs
          253            ,'(,foreign-name ,@options)
          254            ,,`(list ,@(loop for (name type) in args
          255                             collect `',type collect name))
          256            ,@,varargs
          257            ,',return-type)))))
          258 
          259 (defgeneric translate-underscore-separated-name (name)
          260   (:method ((name string))
          261     (values (intern (canonicalize-symbol-name-case (substitute #\- #\_ name)))))
          262   (:method ((name symbol))
          263     (substitute #\_ #\- (string-downcase (symbol-name name)))))
          264 
          265 (defun collapse-prefix (l special-words)
          266   (unless (null l)
          267     (multiple-value-bind (newpre skip) (check-prefix l special-words)
          268       (cons newpre (collapse-prefix (nthcdr skip l) special-words)))))
          269 
          270 (defun check-prefix (l special-words)
          271   (let ((pl (loop for i from (1- (length l)) downto 0
          272                   collect (apply #'concatenate 'simple-string (butlast l i)))))
          273     (loop for w in special-words
          274           for p = (position-if #'(lambda (s) (string= s w)) pl)
          275           when p do (return-from check-prefix (values (nth p pl) (1+ p))))
          276     (values (first l) 1)))
          277 
          278 (defgeneric translate-camelcase-name (name &key upper-initial-p special-words)
          279   (:method ((name string) &key upper-initial-p special-words)
          280     (declare (ignore upper-initial-p))
          281     (values (intern (reduce #'(lambda (s1 s2)
          282                                 (concatenate 'simple-string s1 "-" s2))
          283                             (mapcar #'string-upcase
          284                                     (collapse-prefix
          285                                      (split-if #'(lambda (ch)
          286                                                    (or (upper-case-p ch)
          287                                                        (digit-char-p ch)))
          288                                                name)
          289                                      special-words))))))
          290   (:method ((name symbol) &key upper-initial-p special-words)
          291     (apply #'concatenate
          292            'string
          293            (loop for str in (split-if #'(lambda (ch) (eq ch #\-))
          294                                           (string name)
          295                                       :elide)
          296                  for first-word-p = t then nil
          297                  for e = (member str special-words
          298                                  :test #'equal :key #'string-upcase)
          299                  collect (cond
          300                            ((and first-word-p (not upper-initial-p))
          301                             (string-downcase str))
          302                            (e (first e))
          303                            (t (string-capitalize str)))))))
          304 
          305 (defgeneric translate-name-from-foreign (foreign-name package &optional varp)
          306   (:method (foreign-name package &optional varp)
          307     (declare (ignore package))
          308     (let ((sym (translate-underscore-separated-name foreign-name)))
          309       (if varp
          310           (values (intern (format nil "*~A*"
          311                                   (canonicalize-symbol-name-case
          312                                    (symbol-name sym)))))
          313           sym))))
          314 
          315 (defgeneric translate-name-to-foreign (lisp-name package &optional varp)
          316   (:method (lisp-name package &optional varp)
          317     (declare (ignore package))
          318     (let ((name (translate-underscore-separated-name lisp-name)))
          319       (if varp
          320           (string-trim '(#\*) name)
          321           name))))
          322 
          323 (defun lisp-name (spec varp)
          324   (check-type spec string)
          325   (translate-name-from-foreign spec *package* varp))
          326 
          327 (defun foreign-name (spec varp)
          328   (check-type spec (and symbol (not null)))
          329   (translate-name-to-foreign spec *package* varp))
          330 
          331 (defun foreign-options (opts varp)
          332   (if varp
          333       (funcall 'parse-defcvar-options opts)
          334       (parse-function-options opts)))
          335 
          336 (defun lisp-name-p (name)
          337   (and name (symbolp name) (not (keywordp name))))
          338 
          339 (defun %parse-name-and-options (spec varp)
          340   (cond
          341     ((stringp spec)
          342      (values (lisp-name spec varp) spec nil))
          343     ((symbolp spec)
          344      (assert (not (null spec)))
          345      (values spec (foreign-name spec varp) nil))
          346     ((and (consp spec) (stringp (first spec)))
          347      (destructuring-bind (foreign-name &rest options)
          348          spec
          349        (cond
          350          ((or (null options)
          351               (keywordp (first options)))
          352           (values (lisp-name foreign-name varp) foreign-name options))
          353          (t
          354           (assert (lisp-name-p (first options)))
          355           (values (first options) foreign-name (rest options))))))
          356     ((and (consp spec) (lisp-name-p (first spec)))
          357      (destructuring-bind (lisp-name &rest options)
          358          spec
          359        (cond
          360          ((or (null options)
          361               (keywordp (first options)))
          362           (values lisp-name (foreign-name spec varp) options))
          363          (t
          364           (assert (stringp (first options)))
          365           (values lisp-name (first options) (rest options))))))
          366     (t
          367      (error "Not a valid foreign function specifier: ~A" spec))))
          368 
          369 ;;; DEFCFUN's first argument has can have the following syntax:
          370 ;;;
          371 ;;;     1.  string
          372 ;;;     2.  symbol
          373 ;;;     3.  \( string [symbol] options* )
          374 ;;;     4.  \( symbol [string] options* )
          375 ;;;
          376 ;;; The string argument denotes the foreign function's name. The
          377 ;;; symbol argument is used to name the Lisp function. If one isn't
          378 ;;; present, its name is derived from the other. See the user
          379 ;;; documentation for an explanation of the derivation rules.
          380 (defun parse-name-and-options (spec &optional varp)
          381   (multiple-value-bind (lisp-name foreign-name options)
          382       (%parse-name-and-options spec varp)
          383     (values lisp-name foreign-name (foreign-options options varp))))
          384 
          385 ;;; If we find a &REST token at the end of ARGS, it means this is a
          386 ;;; varargs foreign function therefore we define a lisp macro using
          387 ;;; %DEFCFUN-VARARGS. Otherwise, a lisp function is defined with
          388 ;;; %DEFCFUN.
          389 (defmacro defcfun (name-and-options return-type &body args)
          390   "Defines a Lisp function that calls a foreign function."
          391   (let ((docstring (when (stringp (car args)) (pop args))))
          392     (multiple-value-bind (lisp-name foreign-name options)
          393         (parse-name-and-options name-and-options)
          394       (if (eq (lastcar args) '&rest)
          395           (%defcfun-varargs lisp-name foreign-name return-type
          396                             (butlast args) options docstring)
          397           (%defcfun lisp-name foreign-name return-type args options
          398                     docstring)))))
          399 
          400 ;;;# Defining Callbacks
          401 
          402 (defun inverse-translate-objects (args types declarations rettype call)
          403   `(let (,@(loop for arg in args and type in types
          404                  collect (list arg (expand-from-foreign
          405                                     arg (parse-type type)))))
          406      ,@declarations
          407      ,(expand-to-foreign call (parse-type rettype))))
          408 
          409 (defun parse-defcallback-options (options)
          410   (destructuring-bind (&key (cconv :cdecl cconv-p)
          411                             (calling-convention cconv calling-convention-p)
          412                             (convention calling-convention))
          413       options
          414     (when cconv-p
          415       (warn-obsolete-argument :cconv :convention))
          416     (when calling-convention-p
          417       (warn-obsolete-argument :calling-convention :convention))
          418     (list :convention convention)))
          419 
          420 (defmacro defcallback (name-and-options return-type args &body body)
          421   (multiple-value-bind (body declarations)
          422       (parse-body body :documentation t)
          423     (let ((arg-names (mapcar #'car args))
          424           (arg-types (mapcar #'cadr args))
          425           (name (car (ensure-list name-and-options)))
          426           (options (cdr (ensure-list name-and-options))))
          427       `(progn
          428          (%defcallback ,name ,(canonicalize-foreign-type return-type)
          429              ,arg-names ,(mapcar #'canonicalize-foreign-type arg-types)
          430            ,(inverse-translate-objects
          431              arg-names arg-types declarations return-type
          432              `(block ,name ,@body))
          433            ,@(parse-defcallback-options options))
          434          ',name))))
          435 
          436 (declaim (inline get-callback))
          437 (defun get-callback (symbol)
          438   (%callback symbol))
          439 
          440 (defmacro callback (name)
          441   `(%callback ',name))