#lang racket
(require ffi/unsafe)
(define dbm-lib
  (with-handlers
      ([exn:fail:filesystem?
        (λ (e) 
          (ffi-lib "libgdbm")
          (ffi-lib "libgdbm_compat"))])
    (ffi-lib "libdbm")))
(define alternatives
  `#hasheq([dbm_error . ,void]
           [dbm_clearerr . ,void]))
(define (get-dbm obj typ)
  (get-ffi-obj 
   (symbol->string obj) dbm-lib typ
   (lambda ()
     (hash-ref alternatives obj
               (lambda ()
                 (error 'dbm-lib "Installed dbm does not provide: ~a" obj))))))
(define-syntax-rule (define-dbm obj typ)
  (define obj (get-dbm 'obj typ)))
(define-cpointer-type _DBM)
(define _mode_t _uint)
(define-cstruct _datum 
  ([dptr _string]
   [dsize _int]))
(define (string->datum s)
  (make-datum s (add1 (string-length s))))
(define (datum->string d)
  (datum-dptr d))
(define-dbm dbm_error (_fun _DBM -> _int))
(define-dbm dbm_clearerr (_fun _DBM -> _int))
(define-dbm dbm_delete (_fun _DBM _datum -> _int))
(define-dbm dbm_close (_fun _DBM -> _int))
(define-dbm dbm_nextkey (_fun _DBM -> _datum))
(define-dbm dbm_firstkey (_fun _DBM -> _datum))
(define-dbm dbm_open 
  (_fun _path _uint _mode_t
        -> _DBM/null))
(define _store_mode_t
  (_enum '(DBM_INSERT DBM_REPLACE)))
(define-dbm dbm_store
  (_fun _DBM _datum _datum _store_mode_t
        -> _int))
(define-dbm dbm_fetch
  (_fun _DBM _datum
        -> _datum))
(provide datum?
         string->datum
         datum->string
         dbm_error
         dbm_clearerr
         dbm_delete
         dbm_close
         dbm_nextkey
         dbm_firstkey
         dbm_open
         dbm_store
         dbm_fetch)