I'm working on FFI wrapper for SDL2 library in Racket. The library includes several variadic functions (e.g. SDL_SetError, SDL_LogMessage etc); Racket FFI does not have straight way for importing those, but there's a workaround showcased here. Despite having little experience with Racket macros, I've decided to create one from the linked code to avoid excess code duplication. Here's my code:
#lang racket
(require (for-syntax racket/syntax)
(for-syntax racket/format)
ffi/unsafe)
(define sdl-lib (ffi-lib "libSDL2-2.0" '("0" #f)))
(define-syntax (define-vararg-func stx)
(syntax-case stx ()
[(_ name (arg-types ...) ret-type)
(with-syntax*
([name-str (~a (syntax->datum #'name))]
[arglist
(map
(lambda (arg)
(gensym (syntax->datum arg)))
(syntax->list #'(arg-types ...)))]
[full-arglist (append (syntax->list #'arglist) 'args)]
[final-call (append '(apply fun) (syntax->list #'arglist) '(args))])
#'(define name
(let ([interfaces (make-hash)])
(lambda full-arglist
(define itypes
(append (list arg-types ...)
(map (lambda (x)
(cond
[(and (integer? x) (exact? x)) _int]
[(and (number? x) (real? x)) _double*]
[(string? x) _string]
[(bytes? x) _bytes]
[(symbol? x) _symbol]
[else
(error
"don't know how to deal with ~e" x)]))
args)))
(let ([fun (hash-ref
interfaces itypes
(lambda ()
(let ([i (get-ffi-obj
name-str sdl-lib
(_cprocedure itypes ret-type))])
(hash-set! interfaces itypes i)
i)))])
final-call)))))]))
;; macro usage
(define-vararg-func SDL_SetError (_string) _int)
(SDL_SetError "test %s %d" "test" 42)
(define SDL_GetError (get-ffi-obj "SDL_GetError" sdl-lib (_cprocedure '() _string)))
(displayln (SDL_GetError))
I'd love any feedback on the macro and suggestions on how to improve its readability.