Next: , Previous: Tutorial-Abstraction, Up: Tutorial


4.7 Option functions in Lisp

We could use foreign-funcall directly every time we wanted to call curl_easy_setopt. However, we can encapsulate some of the necessary information with the following.

  ;;; We will use this type later in a more creative way.  For
  ;;; now, just consider it a marker that this isn't just any
  ;;; pointer.
  (defctype easy-handle :pointer)
   
  (defmacro curl-easy-setopt (easy-handle enumerated-name
                              value-type new-value)
    "Call `curl_easy_setopt' on EASY-HANDLE, using ENUMERATED-NAME
  as the OPTION.  VALUE-TYPE is the CFFI foreign type of the third
  argument, and NEW-VALUE is the Lisp data to be translated to the
  third argument.  VALUE-TYPE is not evaluated."
    `(foreign-funcall "curl_easy_setopt" easy-handle ,easy-handle
                      curl-option ,enumerated-name
                      ,value-type ,new-value curl-code))

Now we define a function for each kind of argument that encodes the correct value-type in the above. This can be done reasonably in the define-curl-options macroexpansion; after all, that is where the different options are listed!

We could make cl:defun forms in the expansion that simply call curl-easy-setopt; however, it is probably easier and clearer to use defcfun. define-curl-options was becoming unwieldy, so I defined some helpers in this new definition.

  (defun curry-curl-option-setter (function-name option-keyword)
    "Wrap the function named by FUNCTION-NAME with a version that
  curries the second argument as OPTION-KEYWORD.
   
  This function is intended for use in DEFINE-CURL-OPTION-SETTER."
    (setf (symbol-function function-name)
            (let ((c-function (symbol-function function-name)))
              (lambda (easy-handle new-value)
                (funcall c-function easy-handle option-keyword
                         new-value)))))
   
  (defmacro define-curl-option-setter (name option-type
                                       option-value foreign-type)
    "Define (with DEFCFUN) a function NAME that calls
  curl_easy_setopt.  OPTION-TYPE and OPTION-VALUE are the CFFI
  foreign type and value to be passed as the second argument to
  easy_setopt, and FOREIGN-TYPE is the CFFI foreign type to be used
  for the resultant function's third argument.
   
  This macro is intended for use in DEFINE-CURL-OPTIONS."
    `(progn
       (defcfun ("curl_easy_setopt" ,name) curl-code
         (easy-handle easy-handle)
         (option ,option-type)
         (new-value ,foreign-type))
       (curry-curl-option-setter ',name ',option-value)))
   
  (defmacro define-curl-options (type-name type-offsets &rest enum-args)
    "As with CFFI:DEFCENUM, except each of ENUM-ARGS is as follows:
   
      (NAME TYPE NUMBER)
   
  Where the arguments are as they are with the CINIT macro defined
  in curl.h, except NAME is a keyword.
   
  TYPE-OFFSETS is a plist of TYPEs to their integer offsets, as
  defined by the CURLOPTTYPE_LONG et al constants in curl.h.
   
  Also, define functions for each option named
  set-`TYPE-NAME'-`OPTION-NAME', where OPTION-NAME is the NAME from
  the above destructuring."
    (flet ((enumerated-value (type offset)
             (+ (getf type-offsets type) offset))
           ;; map PROCEDURE, destructuring each of ENUM-ARGS
           (map-enum-args (procedure)
             (mapcar (lambda (arg) (apply procedure arg)) enum-args))
           ;; build a name like SET-CURL-OPTION-NOSIGNAL
           (make-setter-name (option-name)
             (intern (concatenate
                      'string "SET-" (symbol-name type-name)
                      "-" (symbol-name option-name)))))
      `(progn
         (defcenum ,type-name
           ,@(map-enum-args
              (lambda (name type number)
                (list name (enumerated-value type number)))))
         ,@(map-enum-args
            (lambda (name type number)
              (declare (ignore number))
              `(define-curl-option-setter ,(make-setter-name name)
                 ,type-name ,name ,(ecase type
                                     (long :long)
                                     (objectpoint :pointer)
                                     (functionpoint :pointer)
                                     (off-t :long)))))
         ',type-name)))

Macroexpanding our define-curl-options form once more, we see something different:

  (progn
    (defcenum curl-option
      (:noprogress 43)
      (:nosignal 99)
      (:errorbuffer 10010)
      (:url 10002))
    (define-curl-option-setter set-curl-option-noprogress
      curl-option :noprogress :long)
    (define-curl-option-setter set-curl-option-nosignal
      curl-option :nosignal :long)
    (define-curl-option-setter set-curl-option-errorbuffer
      curl-option :errorbuffer :pointer)
    (define-curl-option-setter set-curl-option-url
      curl-option :url :pointer)
    'curl-option)

Macroexpanding one of the new define-curl-option-setter forms yields the following:

  (progn
    (defcfun ("curl_easy_setopt" set-curl-option-nosignal) curl-code
      (easy-handle easy-handle)
      (option curl-option)
      (new-value :long))
    (curry-curl-option-setter 'set-curl-option-nosignal ':nosignal))

Finally, let's try this out:

  cffi-user> (set-curl-option-nosignal *easy-handle* 1)
  => 0

Looks like it works just as well. This interface is now reasonably high-level to wash out some of the ugliness of the thinnest possible curl_easy_setopt FFI, without obscuring the remaining C bookkeeping details we will explore.