;;;; -*- Mode: Lisp; Syntax: ANSI-Common-Lisp; Base: 10; Package: UFFI -*-
;;;; *************************************************************************
;;;; FILE IDENTIFICATION
;;;;
;;;; Name:          function.lisp
;;;; Purpose:       UFFI source to C function definitions
;;;; Programmer:    Kevin M. Rosenberg
;;;; Date Started:  Feb 2002
;;;;
;;;; This file, part of UFFI, is Copyright (c) 2002-2010 by Kevin M. Rosenberg
;;;;
;;;; *************************************************************************

(in-package #:uffi)

(defun process-function-args (args)
  (if (null args)
      #+(or lispworks cmu sbcl scl cormanlisp digitool) nil
      #+allegro '(:void)
      #+openmcl (values nil nil)

      ;; args not null
      #+(or lispworks allegro cmu sbcl scl digitool cormanlisp)
      (let (processed)
        (dolist (arg args)
          (push (process-one-function-arg arg) processed))
        (nreverse processed))
      #+openmcl
      (let ((processed nil)
            (params nil))
        (dolist (arg args)
          (let ((name (car arg))
                (type (convert-from-uffi-type (cadr arg) :routine)))
            ;;(when (and (listp type) (eq (car type) :address))
            ;;(setf type :address))
            (push name params)
            (push type processed)
            (push name processed)))
        (values (nreverse params) (nreverse processed)))
    ))

(defun process-one-function-arg (arg)
  (let ((name (car arg))
        (type (convert-from-uffi-type (cadr arg) :routine)))
    #+(or cmu sbcl scl)
    ;(list name type :in)
    `(,name ,type ,@(if (= (length arg) 3) (list (third arg)) (values)))
    #+(or allegro lispworks digitool)
    (if (and (listp type) (listp (car type)))
        (append (list name) type)
      (list name type))
    #+openmcl
    (declare (ignore name type))
    ))


(defun allegro-convert-return-type (type)
  (if (and (listp type) (not (listp (car type))))
      (list type)
    type))

(defun funcallable-lambda-list (args)
  (let ((ll nil))
    (dolist (arg args)
      (push (car arg) ll))
    (nreverse ll)))

#|
(defmacro def-funcallable (name args &key returning)
  (let ((result-type (convert-from-uffi-type returning :return))
        (function-args (process-function-args args)))
    #+lispworks
    `(fli:define-foreign-funcallable ,name ,function-args
      :result-type ,result-type
      :language :ansi-c
      :calling-convention :cdecl)
    #+(or cmu scl sbcl)
    ;; requires the type of the function pointer be declared correctly!
    (let* ((ptrsym (gensym))
           (ll (funcallable-lambda-list args)))
      `(defun ,name ,(cons ptrsym ll)
        (alien::alien-funcall ,ptrsym ,@ll)))
    #+openmcl
    (multiple-value-bind (params args) (process-function-args args)
      (let ((ptrsym (gensym)))
        `(defun ,name ,(cons ptrsym params)
          (ccl::ff-call ,ptrsym ,@args ,result-type))))
    #+allegro
    ;; this is most definitely wrong
    (let* ((ptrsym (gensym))
           (ll (funcallable-lambda-list args)))
      `(defun ,name ,(cons ptrsym ll)
        (system::ff-funcall ,ptrsym ,@ll)))
    ))
|#

(defun convert-lispworks-args (args)
  (loop for arg in args
        with processed = nil
        do
        (if (and (= (length arg) 3) (eq (third arg) :out))
            (push (list (first arg)
                        (list :reference-return (second arg))) processed)
            (push (subseq arg 0 2) processed))
        finally (return (nreverse processed))))

(defun preprocess-names (names)
  (let ((fname (gensym)))
    (if (atom names)
        (values (list names fname) fname (uffi::make-lisp-name names))
        (values (list (first names) fname) fname (second names)))))

(defun preprocess-args (args)
  (loop for arg in args
        with lisp-args = nil and out = nil and processed = nil
        do
        (if (= (length arg) 3)
            (ecase (third arg)
              (:in
               (progn
                 (push (first arg) lisp-args)
                 (push (list (first arg) (second arg)) processed)))
              (:out
               (progn
                 (push (list (first arg) (second arg)) out)
                 (push (list (first arg) (list '* (second arg))) processed))))
            (progn
              (push (first arg) lisp-args)
              (push arg processed)))
        finally (return (values (nreverse lisp-args)
                                (nreverse out)
                                (nreverse processed)))))


(defmacro def-function (names args &key module returning)
  (multiple-value-bind (lisp-args out processed)
      (preprocess-args args)
    (declare (ignorable lisp-args processed))
    (if (= (length out) 0)
        `(%def-function ,names ,args
          ,@(if module (list :module module) (values))
          ,@(if returning (list :returning returning) (values)))

        #+(or cmu scl sbcl)
        `(%def-function ,names ,args
          ,@(if returning (list :returning returning) (values)))
        #+(or lispworks5 lispworks6)
        (multiple-value-bind (name-pair fname lisp-name)
            (preprocess-names names)
          `(progn
               (%def-function ,name-pair ,(convert-lispworks-args args)
                              ,@(if module (list :module module) (values))
                              ,@(if returning (list :returning returning) (values)))
               (defun ,lisp-name ,lisp-args
                 (,fname ,@(mapcar
                            #'(lambda (arg)
                                (cond ((member (first arg) lisp-args)
                                       (first arg))
                                      ((member (first arg) out :key #'first)
                                       t)))
                          args)))))
        #+(and lispworks (not lispworks5) (not lispworks 6))
        `(%def-function ,names ,(convert-lispworks-args args)
          ,@(if module (list :module module) (values))
          ,@(if returning (list :returning returning) (values)))
        #-(or cmu scl sbcl lispworks)
        (multiple-value-bind (name-pair fname lisp-name)
            (preprocess-names names)
          `(progn
            (%def-function ,name-pair ,processed
             :module ,module :returning ,returning)
            ;(declaim (inline ,fname))
            (defun ,lisp-name ,lisp-args
              (with-foreign-objects ,out
                (values (,fname ,@(mapcar #'first args))
                        ,@(mapcar #'(lambda (arg)
                                      (list 'deref-pointer
                                            (first arg)
                                            (second arg))) out))))))
        )))


;; name is either a string representing foreign name, or a list
;; of foreign-name as a string and lisp name as a symbol
(defmacro %def-function (names args &key module returning)
  #+(or cmu sbcl scl allegro openmcl digitool cormanlisp) (declare (ignore module))

  (let* ((result-type (convert-from-uffi-type returning :return))
         (function-args (process-function-args args))
         (foreign-name (if (atom names) names (car names)))
         (lisp-name (if (atom names) (make-lisp-name names) (cadr names))))
    ;; todo: calling-convention :stdcall for cormanlisp
    #+allegro
    `(ff:def-foreign-call (,lisp-name ,foreign-name)
         ,function-args
       :returning ,(allegro-convert-return-type result-type)
       :call-direct t
       :strings-convert nil)
    #+(or cmu scl)
    `(alien:def-alien-routine (,foreign-name ,lisp-name)
         ,result-type
       ,@function-args)
    #+sbcl
    `(sb-alien:define-alien-routine (,foreign-name ,lisp-name)
         ,result-type
       ,@function-args)
    #+lispworks
    `(fli:define-foreign-function (,lisp-name ,foreign-name :source)
         ,function-args
       ,@(if module (list :module module) (values))
       :result-type ,result-type
      :language :ansi-c
       #+:mswindows :calling-convention #+:mswindows :cdecl)
    #+digitool
    `(eval-when (:compile-toplevel :load-toplevel :execute)
       (ccl:define-entry-point (,lisp-name ,foreign-name)
         ,function-args
         ,result-type))
    #+openmcl
    (declare (ignore function-args))
    #+(and openmcl darwinppc-target)
    (setf foreign-name (concatenate 'string "_" foreign-name))
    #+openmcl
    (multiple-value-bind (params args) (process-function-args args)
      `(defun ,lisp-name ,params
         (ccl::external-call ,foreign-name ,@args ,result-type)))
    #+cormanlisp
    `(ct:defun-dll ,lisp-name (,function-args)
       :return-type ,result-type
       ,@(if module (list :library-name module) (values))
       :entry-name ,foreign-name
       :linkage-type ,calling-convention) ; we need :pascal
    ))




