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.