(in-package :sb-posix)

(define-designator filename (string c-string)
  ("A STRING designating a filename in native namestring syntax.

Note that native namestring syntax is distinct from Lisp namestring syntax:

  \(pathname \"/foo*/bar\")

is a wild pathname with a pattern-matching directory component.
SB-EXT:PARSE-NATIVE-NAMESTRING may be used to construct Lisp pathnames that
denote POSIX filenames as understood by system calls, and
SB-EXT:NATIVE-NAMESTRING can be used to coerce them into strings in the native
namestring syntax.

Note also that POSIX filename syntax does not distinguish the names of files
from the names of directories: in order to parse the name of a directory in
POSIX filename syntax into a pathname MY-DEFAULTS for which

  \(merge-pathnames (make-pathname :name \"FOO\" :case :common)
                    my-defaults)

returns a pathname that denotes a file in the directory, supply a true
:AS-DIRECTORY argument to SB-EXT:PARSE-NATIVE-NAMESTRING. Likewise, to supply
the name of a directory to a POSIX function in non-directory syntax, supply a
true :AS-FILE argument to SB-EXT:NATIVE-NAMESTRING."
   "Designator for a FILENAME: a STRING designating itself, or a
designator for a PATHNAME designating the corresponding native namestring."
   "Converts FILENAME-DESIGNATOR into a FILENAME.")
  (pathname
   (sb-ext:native-namestring (translate-logical-pathname filename)
                             :as-file t))
  (string
   filename)
  (stream
   (filename (pathname filename))))

(define-designator file-descriptor (fixnum (integer 32))
    ("A FIXNUM designating a native file descriptor.

SB-SYS:MAKE-FD-STREAM can be used to construct a FILE-STREAM associated with a
native file descriptor.

Note that mixing I/O operations on a FILE-STREAM with operations directly on its
descriptor may produce unexpected results if the stream is buffered."
     "Designator for a FILE-DESCRIPTOR: either a fixnum designating itself, or
a FILE-STREAM designating the underlying file-descriptor."
     "Converts FILE-DESCRIPTOR-DESIGNATOR into a FILE-DESCRIPTOR.")
  (file-stream
   (sb-sys:fd-stream-fd file-descriptor))
  (fixnum
   file-descriptor))

(define-designator sap-or-nil (sb-sys:system-area-pointer sb-sys:system-area-pointer)
    ()
  (null (sb-sys:int-sap 0))
  (sb-sys:system-area-pointer sap-or-nil))

(define-designator alien-pointer-to-anything-or-nil (sb-alien-internals::alien-value (* t))
    ()
  (null (sb-alien:sap-alien (sb-sys:int-sap 0) (* t)))
  ((alien (* t)) alien-pointer-to-anything-or-nil))

(defun lisp-for-c-symbol (name)
  (etypecase name
    (list
     (lisp-for-c-symbol (car name)))
    (string
     (let ((root (if (eql #\_ (char name 0)) (subseq name 1) name)))
       (intern (substitute #\- #\_ (string-upcase root)) :sb-posix)))))

;; Note: this variable is set in interface.lisp.  defined here for
;; clarity and so the real-c-name compile as desired.
(defparameter *c-functions-in-runtime* nil)

(defun real-c-name (name)
  (let  ((maybe-name
          (etypecase name
            (list
             (destructuring-bind (name &key c-name options) name
               (declare (ignorable options))
               (if c-name
                   c-name
                   (cond #+largefile
                         ((or (eql options :largefile)
                              (member :largefile options))
                          (format nil "~a_largefile" name))
                         (t
                          name)))))
            (string
             name))))
    (if (member maybe-name *c-functions-in-runtime*
                :test #'string=)
        (format nil "_~A" maybe-name)
        maybe-name)))

(defmacro define-call-internally (lisp-name c-name return-type error-predicate
                                  &rest arguments)
  (if (sb-sys:find-foreign-symbol-address c-name)
      `(progn
        (declaim (inline ,lisp-name))
        (defun ,lisp-name ,(mapcar #'car arguments)
          (let ((r (alien-funcall
                    (extern-alien
                     ,c-name
                     (function ,return-type
                               ,@(mapcar
                                  (lambda (x)
                                    (gethash (cadr x)
                                             *designator-types*
                                             (cadr x)))
                                  arguments)))
                    ,@(mapcar (lambda (x)
                                (if (nth-value 1
                                               (gethash (cadr x)
                                                        *designator-types*))
                                    `(,(intern (symbol-name (cadr x))
                                               :sb-posix)
                                      ,(car x))
                                    (car x)))
                              arguments))))
            (if (,error-predicate r) (syscall-error ',lisp-name) r))))
      `(sb-int:style-warn "Didn't find definition for ~S" ,c-name)))

(defmacro define-call (name return-type error-predicate &rest arguments)
  (let ((lisp-name (lisp-for-c-symbol name))
        (real-c-name (real-c-name name)))
    `(progn
       (export ',lisp-name :sb-posix)
       (define-call-internally ,lisp-name
           ,real-c-name
         ,return-type
         ,error-predicate
         ,@arguments))))

(defmacro define-entry-point (name arglist &body body)
  (let ((lisp-name (lisp-for-c-symbol name)))
    `(progn
      (export ',lisp-name :sb-posix)
      (declaim (inline ,lisp-name))
      (defun ,lisp-name ,arglist
        ,@body))))

(defmacro define-simple-call (name return-type &rest arguments)
  (multiple-value-bind (lisp-name c-name)
      (values name (substitute #\_ #\- (string-downcase name)))
    `(progn
       (export ',lisp-name :sb-posix)
       (defun ,lisp-name ,(mapcar #'first arguments)
         (alien-funcall (extern-alien ,c-name (function ,return-type
                                                        ,@(mapcar #'second arguments)))
                        ,@(mapcar #'first arguments))))))
