summaryrefslogtreecommitdiff
path: root/Examples/s-exp/uffi.lisp
diff options
context:
space:
mode:
Diffstat (limited to 'Examples/s-exp/uffi.lisp')
-rw-r--r--Examples/s-exp/uffi.lisp450
1 files changed, 450 insertions, 0 deletions
diff --git a/Examples/s-exp/uffi.lisp b/Examples/s-exp/uffi.lisp
new file mode 100644
index 0000000..5ddb4c2
--- /dev/null
+++ b/Examples/s-exp/uffi.lisp
@@ -0,0 +1,450 @@
+;;; This is experimental code that uses the s-expression
+;;; representation of a C/C++ library interface to generate Foreign
+;;; Function Interface definitions for use with Kevin Rosenberg's
+;;; UFFI.
+;;;
+;;; Written by Matthias Koeppe <mkoeppe@mail.math.uni-magdeburg.de>
+
+(eval-when (:compile-toplevel :load-toplevel :execute)
+ (require 'port) ; from CLOCC
+ (require 'uffi))
+
+(in-package :cl-user)
+
+;; Interaction with the SWIG binary
+
+(defvar *swig-source-directory* #p"/home/mkoeppe/s/swig1.3/")
+
+(defvar *swig-program* (merge-pathnames "preinst-swig" *swig-source-directory*))
+
+(defun run-swig (swig-interface-file-name &key directory-search-list module
+ ignore-errors c++)
+ (let ((temp-file-name "/tmp/swig.lsp"))
+ (let ((process
+ (port:run-prog (namestring *swig-program*)
+ :output t
+ :args `(,@(and c++ '("-c++"))
+ "-sexp"
+ ,@(mapcar (lambda (dir)
+ (concatenate 'string
+ "-I" (namestring dir)))
+ directory-search-list)
+ ,@(and module
+ `("-module" ,module))
+ "-o" ,temp-file-name
+ ,(namestring swig-interface-file-name)))))
+ #+cmu (unless (or (zerop (ext:process-exit-code process))
+ ignore-errors)
+ (error "Process swig exited abnormally"))
+ (with-open-file (s temp-file-name)
+ (read s)))))
+
+;; Type system
+
+(defun parse-swigtype (type-string &key start end junk-ok)
+ "Parse TYPE-STRING as SWIG's internal representation of C/C++
+types. Return two values: The type description (an improper list) and
+the terminating index into TYPE-STRING."
+ ;; SWIG's internal representation is described in Source/Swig/stype.c
+ (unless start
+ (setq start 0))
+ (unless end
+ (setq end (length type-string)))
+ (flet ((prefix-match (prefix)
+ (let ((position (mismatch prefix type-string :start2 start :end2 end)))
+ (or (not position)
+ (= position (length prefix)))))
+ (bad-type-error (reason)
+ (error "Bad SWIG type (~A): ~A" reason
+ (subseq type-string start end)))
+ (type-char (index)
+ (and (< index (length type-string))
+ (char type-string index)))
+ (cons-and-recurse (prefix start end)
+ (multiple-value-bind (type-description index)
+ (parse-swigtype type-string :start start :end end
+ :junk-ok junk-ok)
+ (values (cons prefix type-description)
+ index))))
+ (cond
+ ((prefix-match "p.") ; pointer
+ (cons-and-recurse '* (+ start 2) end))
+ ((prefix-match "r.") ; C++ reference
+ (cons-and-recurse '& (+ start 2) end))
+ ((prefix-match "a(") ; array
+ (let ((closing-paren (position #\) type-string
+ :start (+ start 2)
+ :end end)))
+ (unless closing-paren
+ (bad-type-error "missing right paren"))
+ (unless (eql (type-char (+ closing-paren 1)) #\.)
+ (bad-type-error "missing dot"))
+ (cons-and-recurse (list 'ARRAY (subseq type-string (+ start 2) closing-paren))
+ (+ closing-paren 2) end)))
+ ((prefix-match "q(") ; qualifier (const, volatile)
+ (let ((closing-paren (position #\) type-string
+ :start (+ start 2)
+ :end end)))
+ (unless closing-paren
+ (bad-type-error "missing right paren"))
+ (unless (eql (type-char (+ closing-paren 1)) #\.)
+ (bad-type-error "missing dot"))
+ (cons-and-recurse (list 'QUALIFIER (subseq type-string (+ start 2) closing-paren))
+ (+ closing-paren 2) end)))
+ ((prefix-match "m(") ; C++ member pointer
+ (multiple-value-bind (class-type class-end-index)
+ (parse-swigtype type-string :junk-ok t
+ :start (+ start 2) :end end)
+ (unless (eql (type-char class-end-index) #\))
+ (bad-type-error "missing right paren"))
+ (unless (eql (type-char (+ class-end-index 1)) #\.)
+ (bad-type-error "missing dot"))
+ (cons-and-recurse (list 'MEMBER-POINTER class-type)
+ (+ class-end-index 2) end)))
+ ((prefix-match "f(") ; function
+ (loop with index = (+ start 2)
+ until (eql (type-char index) #\))
+ collect (multiple-value-bind (arg-type arg-end-index)
+ (parse-swigtype type-string :junk-ok t
+ :start index :end end)
+ (case (type-char arg-end-index)
+ (#\, (setq index (+ arg-end-index 1)))
+ (#\) (setq index arg-end-index))
+ (otherwise (bad-type-error "comma or right paren expected")))
+ arg-type)
+ into arg-types
+ finally (unless (eql (type-char (+ index 1)) #\.)
+ (bad-type-error "missing dot"))
+ (return (cons-and-recurse (cons 'FUNCTION arg-types)
+ (+ index 2) end))))
+ ((prefix-match "v(") ;varargs
+ (let ((closing-paren (position #\) type-string
+ :start (+ start 2)
+ :end end)))
+ (unless closing-paren
+ (bad-type-error "missing right paren"))
+ (values (list 'VARARGS (subseq type-string (+ start 2) closing-paren))
+ (+ closing-paren 1))))
+ (t (let ((junk-position (position-if (lambda (char)
+ (member char '(#\, #\( #\) #\.)))
+ type-string
+ :start start :end end)))
+ (cond (junk-position ; found junk
+ (unless junk-ok
+ (bad-type-error "trailing junk"))
+ (values (subseq type-string start junk-position)
+ junk-position))
+ (t
+ (values (subseq type-string start end)
+ end))))))))
+
+(defun swigtype-function-p (swigtype)
+ "Check whether SWIGTYPE designates a function. If so, the second
+value is the list of argument types, and the third value is the return
+type."
+ (if (and (consp swigtype)
+ (consp (first swigtype))
+ (eql (first (first swigtype)) 'FUNCTION))
+ (values t (rest (first swigtype)) (rest swigtype))
+ (values nil nil nil)))
+
+
+;; UFFI
+
+(defvar *uffi-definitions* '())
+
+(defconstant *uffi-default-primitive-type-alist*
+ '(("char" . :char)
+ ("unsigned char" . :unsigned-byte)
+ ("signed char" . :byte)
+ ("short" . :short)
+ ("signed short" . :short)
+ ("unsigned short" . :unsigned-short)
+ ("int" . :int)
+ ("signed int" . :int)
+ ("unsigned int" . :unsigned-int)
+ ("long" . :long)
+ ("signed long" . :long)
+ ("unsigned long" . :unsigned-long)
+ ("float" . :float)
+ ("double" . :double)
+ ((* . "char") . :cstring)
+ ((* . "void") . :pointer-void)
+ ("void" . :void)))
+
+(defvar *uffi-primitive-type-alist* *uffi-default-primitive-type-alist*)
+
+(defun uffi-type-spec (type-list)
+ "Return the UFFI type spec equivalent to TYPE-LIST, or NIL if there
+is no representation."
+ (let ((primitive-type-pair
+ (assoc type-list *uffi-primitive-type-alist* :test 'equal)))
+ (cond
+ (primitive-type-pair
+ (cdr primitive-type-pair))
+ ((and (consp type-list)
+ (eql (first type-list) '*))
+ (let ((base-type-spec (uffi-type-spec (rest type-list))))
+ (cond
+ ((not base-type-spec)
+ :pointer-void)
+ (t
+ (list '* base-type-spec)))))
+ (t nil))))
+
+;; Parse tree
+
+(defvar *uffi-output* nil)
+
+(defun emit-uffi-definition (uffi-definition)
+ (format *uffi-output* "~&~S~%" uffi-definition)
+ (push uffi-definition *uffi-definitions*))
+
+(defun make-cl-symbol (c-identifier &key uninterned)
+ (let ((name (substitute #\- #\_ (string-upcase c-identifier))))
+ (if uninterned
+ (make-symbol name)
+ (intern name))))
+
+(defvar *class-scope* '() "A stack of names of nested C++ classes.")
+
+(defvar *struct-fields* '())
+
+(defvar *linkage* :C "NIL or :C")
+
+(defgeneric handle-node (node-type &key &allow-other-keys)
+ (:documentation "Handle a node of SWIG's parse tree of a C/C++ program"))
+
+(defmethod handle-node ((node-type t) &key &allow-other-keys)
+ ;; do nothing for unknown node types
+ nil)
+
+(defmethod handle-node ((node-type (eql 'cdecl)) &key name decl storage parms type &allow-other-keys)
+ (let ((swigtype (parse-swigtype (concatenate 'string decl type))))
+ (let ((*print-pretty* nil) ; or FUNCTION would be printed as #' by cmucl
+ (*print-circle* t))
+ (format *uffi-output* "~&;; C Declaration: ~A ~A ~A ~A~%;; with-parms ~W~%;; of-type ~W~%"
+ storage type name decl parms swigtype))
+ (multiple-value-bind (function-p arg-swigtype-list return-swigtype)
+ (swigtype-function-p swigtype)
+ (declare (ignore arg-swigtype-list))
+ (cond
+ ((and (null *class-scope*) function-p
+ (or (eql *linkage* :c)
+ (string= storage "externc")))
+ ;; ordinary top-level function with C linkage
+ (let ((argnum 0)
+ (argname-list '()))
+ (flet ((unique-argname (name)
+ ;; Sometimes the functions in SWIG interfaces
+ ;; do not have unique names. Make them unique
+ ;; by adding a suffix. Also avoid symbols
+ ;; that are specially bound.
+ (unless name
+ (setq name (format nil "arg~D" argnum)))
+ (let ((argname (make-cl-symbol name)))
+ (when (boundp argname) ;specially bound
+ (setq argname (make-cl-symbol name :uninterned t)))
+ (push argname argname-list)
+ argname)))
+ (let ((uffi-arg-list
+ (mapcan (lambda (param)
+ (incf argnum)
+ (destructuring-bind (&key name type &allow-other-keys) param
+ (let ((uffi-type (uffi-type-spec (parse-swigtype type))))
+ (cond
+ ((not uffi-type)
+ (format *uffi-output* "~&;; Warning: Cannot handle type ~S of argument `~A'~%"
+ type name)
+ (return-from handle-node))
+ ((eq uffi-type :void)
+ '())
+ (t
+ (let ((symbol (unique-argname name)))
+ (list `(,symbol ,uffi-type))))))))
+ parms))
+ (uffi-return-type
+ (uffi-type-spec return-swigtype)))
+ (unless uffi-return-type
+ (format *uffi-output* "~&;; Warning: Cannot handle return type `~S'~%"
+ return-swigtype)
+ (return-from handle-node))
+ (emit-uffi-definition `(UFFI:DEF-FUNCTION ,name ,uffi-arg-list :RETURNING ,uffi-return-type))))))
+ ((and (not (null *class-scope*)) (null (rest *class-scope*))
+ (not function-p)) ; class/struct member (no nested structs)
+ (let ((uffi-type (uffi-type-spec swigtype)))
+ (unless uffi-type
+ (format *uffi-output* "~&;; Warning: Cannot handle type ~S of struct field `~A'~%"
+ type name)
+ (return-from handle-node))
+ (push `(,(make-cl-symbol name) ,uffi-type) *struct-fields*)))))))
+
+(defmethod handle-node ((node-type (eql 'class)) &key name children kind &allow-other-keys)
+ (format *uffi-output* "~&;; Class ~A~%" name)
+ (let ((*class-scope* (cons name *class-scope*))
+ (*struct-fields* '()))
+ (dolist (child children)
+ (apply 'handle-node child))
+ (emit-uffi-definition `(,(if (string= kind "union")
+ 'UFFI:DEF-UNION
+ 'UFFI:DEF-STRUCT)
+ ,(make-cl-symbol name) ,@(nreverse *struct-fields*)))))
+
+(defmethod handle-node ((node-type (eql 'top)) &key children &allow-other-keys)
+ (dolist (child children)
+ (apply 'handle-node child)))
+
+(defmethod handle-node ((node-type (eql 'include)) &key name children &allow-other-keys)
+ (format *uffi-output* ";; INCLUDE ~A~%" name)
+ (dolist (child children)
+ (apply 'handle-node child)))
+
+(defmethod handle-node ((node-type (eql 'extern)) &key name children &allow-other-keys)
+ (format *uffi-output* ";; EXTERN \"C\" ~A~%" name)
+ (let ((*linkage* :c))
+ (dolist (child children)
+ (apply 'handle-node child))))
+
+;;(defun compute-uffi-definitions (swig-interface)
+;; (let ((*uffi-definitions* '()))
+;; (handle-node swig-interface)
+;; *uffi-definitions*))
+
+;; Test instances
+
+#||
+
+#+ignore
+(defvar *gifplot-interface*
+ (run-swig (merge-pathnames "Examples/GIFPlot/Interface/gifplot.i"
+ *swig-source-directory*)
+ :directory-search-list (list (merge-pathnames "Examples/GIFPlot/Interface/" *swig-source-directory*))))
+
+(defvar *simple-gifplot-interface*
+ (run-swig (merge-pathnames "Examples/GIFPlot/Include/gifplot.h"
+ *swig-source-directory*)
+ :directory-search-list (list (merge-pathnames "Examples/GIFPlot/Interface/" *swig-source-directory*))
+ :module "gifplot"))
+
+(defvar *cplex-glue-directory* #p"/home/mkoeppe/cvs/cplex-glue/")
+
+(defvar *cplex-glue-interface*
+ (run-swig (merge-pathnames "cplex.i" *cplex-glue-directory*)
+ :directory-search-list (list (merge-pathnames "Lib/guile"
+ *swig-source-directory*)
+ *cplex-glue-directory*)
+ :ignore-errors t))
+
+
+
+(require 'uffi)
+
+;;(let ((*uffi-primitive-type-alist* (cons '("Pixel" . :unsigned-int) *uffi-default-primitive-type-alist*)))
+;; (eval (cons 'progn (compute-uffi-definitions *simple-gifplot-interface*))))
+
+
+(with-open-file (f "/tmp/swig-uffi.lisp" :direction :output
+ :if-exists :supersede)
+ (let ((*uffi-definitions* '())
+ (*uffi-output* f)
+ (*uffi-primitive-type-alist*
+ (cons '("Pixel" . :unsigned-int) *uffi-default-primitive-type-alist*)))
+ (apply 'handle-node *simple-gifplot-interface*)))
+
+#+cplex
+(with-open-file (f "/tmp/swig-uffi.lisp" :direction :output)
+ (let ((*uffi-definitions* '())
+ (*uffi-output* f)
+ (*uffi-primitive-type-alist*
+ (cons '("Pixel" . :unsigned-int) *uffi-default-primitive-type-alist*)))
+ (apply 'handle-node *cplex-glue-interface*)))
+
+(compile-file "/tmp/swig-uffi.lisp")
+
+(uffi:load-foreign-library (merge-pathnames "Examples/GIFPlot/libgifplot.a"
+ *swig-source-directory*))
+
+(load "/tmp/swig-uffi.lisp")
+
+(load (merge-pathnames "Examples/GIFPlot/Common-Lisp/full/runme.lisp" *swig-source-directory*))
+
+(action (namestring (merge-pathnames "Examples/GIFPlot/Common-Lisp/full/cmap"
+ *swig-source-directory*)))
+
+||#
+
+;;; Link to SWIG itself
+
+#||
+
+(defparameter *c++-compiler* "g++")
+
+(defun stdc++-library (&key env)
+ (let ((error-output (make-string-output-stream)))
+ (let ((name-output (make-string-output-stream)))
+ (let ((proc (ext:run-program
+ *c++-compiler*
+ '("-print-file-name=libstdc++.so")
+ :env env
+ :input nil
+ :output name-output
+ :error error-output)))
+ (unless proc
+ (error "Could not run ~A" *c++-compiler*))
+ (unless (zerop (ext:process-exit-code proc))
+ (system:serve-all-events 0)
+ (error "~A failed:~%~A" *c++-compiler*
+ (get-output-stream-string error-output))))
+ (string-right-trim '(#\Newline) (get-output-stream-string name-output)))))
+
+(defvar *swig-interface* nil)
+
+(defvar *swig-uffi-pathname* #p"/tmp/swig-uffi.lisp")
+
+(defun link-swig ()
+ (setq *swig-interface*
+ (run-swig (merge-pathnames "Source/swig.i" *swig-source-directory*)
+ :directory-search-list
+ (list (merge-pathnames "Source/" *swig-source-directory*))
+ :module "swig"
+ :ignore-errors t
+ :c++ t))
+ (with-open-file (f *swig-uffi-pathname* :direction :output)
+ (let ((*linkage* :c++)
+ (*uffi-definitions* '())
+ (*uffi-output* f)
+ (*uffi-primitive-type-alist* *uffi-default-primitive-type-alist*))
+ (apply 'handle-node *swig-interface*)))
+ (compile-file *swig-uffi-pathname*)
+ (alien:load-foreign (merge-pathnames "Source/libswig.a"
+ *swig-source-directory*)
+ :libraries (list (stdc++-library)))
+ ;; FIXME: UFFI stuffes a "-l" in front of the passed library names
+ ;; (uffi:load-foreign-library (merge-pathnames "Source/libswig.a"
+ ;; *swig-source-directory*)
+ ;; :supporting-libraries
+ ;; (list (stdc++-library)))
+ (load (compile-file-pathname *swig-uffi-pathname*)))
+
+||#
+
+;;;; TODO:
+
+;; * How to do type lookups? Is everything important that SWIG knows
+;; about the types written out? What to make of typemaps?
+;;
+;; * Wrapped functions should probably automatically COERCE their
+;; arguments (as of type DOUBLE-FLOAT), to make the functions more
+;; flexible?
+;;
+;; * Why are the functions created by FFI interpreted?
+;;
+;; * We can't deal with more complicated structs and C++ classes
+;; directly with the FFI; we have to emit SWIG wrappers that access
+;; those classes.
+;;
+;; * A CLOS layer where structure fields are mapped as slots. It
+;; looks like we need MOP functions to implement this.
+;;
+;; * Maybe modify SWIG so that key-value hashes are distinguished from
+;; value-value hashes.