PageRenderTime 47ms CodeModel.GetById 19ms RepoModel.GetById 1ms app.codeStats 0ms

/trunk/Examples/s-exp/uffi.lisp

#
Lisp | 389 lines | 260 code | 35 blank | 94 comment | 1 complexity | 277f511b7033a575b048734d08a629f7 MD5 | raw file
Possible License(s): LGPL-2.1, Cube, GPL-3.0, 0BSD, GPL-2.0
  1. ;;; This is experimental code that uses the s-expression
  2. ;;; representation of a C/C++ library interface to generate Foreign
  3. ;;; Function Interface definitions for use with Kevin Rosenberg's
  4. ;;; UFFI.
  5. ;;;
  6. ;;; Written by Matthias Koeppe <mkoeppe@mail.math.uni-magdeburg.de>
  7. (eval-when (:compile-toplevel :load-toplevel :execute)
  8. (require 'port) ; from CLOCC
  9. (require 'uffi))
  10. (in-package :cl-user)
  11. ;; Interaction with the SWIG binary
  12. (defvar *swig-source-directory* #p"/home/mkoeppe/s/swig1.3/")
  13. (defvar *swig-program* (merge-pathnames "preinst-swig" *swig-source-directory*))
  14. (defun run-swig (swig-interface-file-name &key directory-search-list module
  15. ignore-errors c++)
  16. (let ((temp-file-name "/tmp/swig.lsp"))
  17. (let ((process
  18. (port:run-prog (namestring *swig-program*)
  19. :output t
  20. :args `(,@(and c++ '("-c++"))
  21. "-sexp"
  22. ,@(mapcar (lambda (dir)
  23. (concatenate 'string
  24. "-I" (namestring dir)))
  25. directory-search-list)
  26. ,@(and module
  27. `("-module" ,module))
  28. "-o" ,temp-file-name
  29. ,(namestring swig-interface-file-name)))))
  30. #+cmu (unless (or (zerop (ext:process-exit-code process))
  31. ignore-errors)
  32. (error "Process swig exited abnormally"))
  33. (with-open-file (s temp-file-name)
  34. (read s)))))
  35. ;; Type system
  36. (defun parse-swigtype (type-string &key start end junk-ok)
  37. "Parse TYPE-STRING as SWIG's internal representation of C/C++
  38. types. Return two values: The type description (an improper list) and
  39. the terminating index into TYPE-STRING."
  40. ;; SWIG's internal representation is described in Source/Swig/stype.c
  41. (unless start
  42. (setq start 0))
  43. (unless end
  44. (setq end (length type-string)))
  45. (flet ((prefix-match (prefix)
  46. (let ((position (mismatch prefix type-string :start2 start :end2 end)))
  47. (or (not position)
  48. (= position (length prefix)))))
  49. (bad-type-error (reason)
  50. (error "Bad SWIG type (~A): ~A" reason
  51. (subseq type-string start end)))
  52. (type-char (index)
  53. (and (< index (length type-string))
  54. (char type-string index)))
  55. (cons-and-recurse (prefix start end)
  56. (multiple-value-bind (type-description index)
  57. (parse-swigtype type-string :start start :end end
  58. :junk-ok junk-ok)
  59. (values (cons prefix type-description)
  60. index))))
  61. (cond
  62. ((prefix-match "p.") ; pointer
  63. (cons-and-recurse '* (+ start 2) end))
  64. ((prefix-match "r.") ; C++ reference
  65. (cons-and-recurse '& (+ start 2) end))
  66. ((prefix-match "a(") ; array
  67. (let ((closing-paren (position #\) type-string
  68. :start (+ start 2)
  69. :end end)))
  70. (unless closing-paren
  71. (bad-type-error "missing right paren"))
  72. (unless (eql (type-char (+ closing-paren 1)) #\.)
  73. (bad-type-error "missing dot"))
  74. (cons-and-recurse (list 'ARRAY (subseq type-string (+ start 2) closing-paren))
  75. (+ closing-paren 2) end)))
  76. ((prefix-match "q(") ; qualifier (const, volatile)
  77. (let ((closing-paren (position #\) type-string
  78. :start (+ start 2)
  79. :end end)))
  80. (unless closing-paren
  81. (bad-type-error "missing right paren"))
  82. (unless (eql (type-char (+ closing-paren 1)) #\.)
  83. (bad-type-error "missing dot"))
  84. (cons-and-recurse (list 'QUALIFIER (subseq type-string (+ start 2) closing-paren))
  85. (+ closing-paren 2) end)))
  86. ((prefix-match "m(") ; C++ member pointer
  87. (multiple-value-bind (class-type class-end-index)
  88. (parse-swigtype type-string :junk-ok t
  89. :start (+ start 2) :end end)
  90. (unless (eql (type-char class-end-index) #\))
  91. (bad-type-error "missing right paren"))
  92. (unless (eql (type-char (+ class-end-index 1)) #\.)
  93. (bad-type-error "missing dot"))
  94. (cons-and-recurse (list 'MEMBER-POINTER class-type)
  95. (+ class-end-index 2) end)))
  96. ((prefix-match "f(") ; function
  97. (loop with index = (+ start 2)
  98. until (eql (type-char index) #\))
  99. collect (multiple-value-bind (arg-type arg-end-index)
  100. (parse-swigtype type-string :junk-ok t
  101. :start index :end end)
  102. (case (type-char arg-end-index)
  103. (#\, (setq index (+ arg-end-index 1)))
  104. (#\) (setq index arg-end-index))
  105. (otherwise (bad-type-error "comma or right paren expected")))
  106. arg-type)
  107. into arg-types
  108. finally (unless (eql (type-char (+ index 1)) #\.)
  109. (bad-type-error "missing dot"))
  110. (return (cons-and-recurse (cons 'FUNCTION arg-types)
  111. (+ index 2) end))))
  112. ((prefix-match "v(") ;varargs
  113. (let ((closing-paren (position #\) type-string
  114. :start (+ start 2)
  115. :end end)))
  116. (unless closing-paren
  117. (bad-type-error "missing right paren"))
  118. (values (list 'VARARGS (subseq type-string (+ start 2) closing-paren))
  119. (+ closing-paren 1))))
  120. (t (let ((junk-position (position-if (lambda (char)
  121. (member char '(#\, #\( #\) #\.)))
  122. type-string
  123. :start start :end end)))
  124. (cond (junk-position ; found junk
  125. (unless junk-ok
  126. (bad-type-error "trailing junk"))
  127. (values (subseq type-string start junk-position)
  128. junk-position))
  129. (t
  130. (values (subseq type-string start end)
  131. end))))))))
  132. (defun swigtype-function-p (swigtype)
  133. "Check whether SWIGTYPE designates a function. If so, the second
  134. value is the list of argument types, and the third value is the return
  135. type."
  136. (if (and (consp swigtype)
  137. (consp (first swigtype))
  138. (eql (first (first swigtype)) 'FUNCTION))
  139. (values t (rest (first swigtype)) (rest swigtype))
  140. (values nil nil nil)))
  141. ;; UFFI
  142. (defvar *uffi-definitions* '())
  143. (defconstant *uffi-default-primitive-type-alist*
  144. '(("char" . :char)
  145. ("unsigned char" . :unsigned-byte)
  146. ("signed char" . :byte)
  147. ("short" . :short)
  148. ("signed short" . :short)
  149. ("unsigned short" . :unsigned-short)
  150. ("int" . :int)
  151. ("signed int" . :int)
  152. ("unsigned int" . :unsigned-int)
  153. ("long" . :long)
  154. ("signed long" . :long)
  155. ("unsigned long" . :unsigned-long)
  156. ("float" . :float)
  157. ("double" . :double)
  158. ((* . "char") . :cstring)
  159. ((* . "void") . :pointer-void)
  160. ("void" . :void)))
  161. (defvar *uffi-primitive-type-alist* *uffi-default-primitive-type-alist*)
  162. (defun uffi-type-spec (type-list)
  163. "Return the UFFI type spec equivalent to TYPE-LIST, or NIL if there
  164. is no representation."
  165. (let ((primitive-type-pair
  166. (assoc type-list *uffi-primitive-type-alist* :test 'equal)))
  167. (cond
  168. (primitive-type-pair
  169. (cdr primitive-type-pair))
  170. ((and (consp type-list)
  171. (eql (first type-list) '*))
  172. (let ((base-type-spec (uffi-type-spec (rest type-list))))
  173. (cond
  174. ((not base-type-spec)
  175. :pointer-void)
  176. (t
  177. (list '* base-type-spec)))))
  178. (t nil))))
  179. ;; Parse tree
  180. (defvar *uffi-output* nil)
  181. (defun emit-uffi-definition (uffi-definition)
  182. (format *uffi-output* "~&~S~%" uffi-definition)
  183. (push uffi-definition *uffi-definitions*))
  184. (defun make-cl-symbol (c-identifier &key uninterned)
  185. (let ((name (substitute #\- #\_ (string-upcase c-identifier))))
  186. (if uninterned
  187. (make-symbol name)
  188. (intern name))))
  189. (defvar *class-scope* '() "A stack of names of nested C++ classes.")
  190. (defvar *struct-fields* '())
  191. (defvar *linkage* :C "NIL or :C")
  192. (defgeneric handle-node (node-type &key &allow-other-keys)
  193. (:documentation "Handle a node of SWIG's parse tree of a C/C++ program"))
  194. (defmethod handle-node ((node-type t) &key &allow-other-keys)
  195. ;; do nothing for unknown node types
  196. nil)
  197. (defmethod handle-node ((node-type (eql 'cdecl)) &key name decl storage parms type &allow-other-keys)
  198. (let ((swigtype (parse-swigtype (concatenate 'string decl type))))
  199. (let ((*print-pretty* nil) ; or FUNCTION would be printed as #' by cmucl
  200. (*print-circle* t))
  201. (format *uffi-output* "~&;; C Declaration: ~A ~A ~A ~A~%;; with-parms ~W~%;; of-type ~W~%"
  202. storage type name decl parms swigtype))
  203. (multiple-value-bind (function-p arg-swigtype-list return-swigtype)
  204. (swigtype-function-p swigtype)
  205. (declare (ignore arg-swigtype-list))
  206. (cond
  207. ((and (null *class-scope*) function-p
  208. (or (eql *linkage* :c)
  209. (string= storage "externc")))
  210. ;; ordinary top-level function with C linkage
  211. (let ((argnum 0)
  212. (argname-list '()))
  213. (flet ((unique-argname (name)
  214. ;; Sometimes the functions in SWIG interfaces
  215. ;; do not have unique names. Make them unique
  216. ;; by adding a suffix. Also avoid symbols
  217. ;; that are specially bound.
  218. (unless name
  219. (setq name (format nil "arg~D" argnum)))
  220. (let ((argname (make-cl-symbol name)))
  221. (when (boundp argname) ;specially bound
  222. (setq argname (make-cl-symbol name :uninterned t)))
  223. (push argname argname-list)
  224. argname)))
  225. (let ((uffi-arg-list
  226. (mapcan (lambda (param)
  227. (incf argnum)
  228. (destructuring-bind (&key name type &allow-other-keys) param
  229. (let ((uffi-type (uffi-type-spec (parse-swigtype type))))
  230. (cond
  231. ((not uffi-type)
  232. (format *uffi-output* "~&;; Warning: Cannot handle type ~S of argument `~A'~%"
  233. type name)
  234. (return-from handle-node))
  235. ((eq uffi-type :void)
  236. '())
  237. (t
  238. (let ((symbol (unique-argname name)))
  239. (list `(,symbol ,uffi-type))))))))
  240. parms))
  241. (uffi-return-type
  242. (uffi-type-spec return-swigtype)))
  243. (unless uffi-return-type
  244. (format *uffi-output* "~&;; Warning: Cannot handle return type `~S'~%"
  245. return-swigtype)
  246. (return-from handle-node))
  247. (emit-uffi-definition `(UFFI:DEF-FUNCTION ,name ,uffi-arg-list :RETURNING ,uffi-return-type))))))
  248. ((and (not (null *class-scope*)) (null (rest *class-scope*))
  249. (not function-p)) ; class/struct member (no nested structs)
  250. (let ((uffi-type (uffi-type-spec swigtype)))
  251. (unless uffi-type
  252. (format *uffi-output* "~&;; Warning: Cannot handle type ~S of struct field `~A'~%"
  253. type name)
  254. (return-from handle-node))
  255. (push `(,(make-cl-symbol name) ,uffi-type) *struct-fields*)))))))
  256. (defmethod handle-node ((node-type (eql 'class)) &key name children kind &allow-other-keys)
  257. (format *uffi-output* "~&;; Class ~A~%" name)
  258. (let ((*class-scope* (cons name *class-scope*))
  259. (*struct-fields* '()))
  260. (dolist (child children)
  261. (apply 'handle-node child))
  262. (emit-uffi-definition `(,(if (string= kind "union")
  263. 'UFFI:DEF-UNION
  264. 'UFFI:DEF-STRUCT)
  265. ,(make-cl-symbol name) ,@(nreverse *struct-fields*)))))
  266. (defmethod handle-node ((node-type (eql 'top)) &key children &allow-other-keys)
  267. (dolist (child children)
  268. (apply 'handle-node child)))
  269. (defmethod handle-node ((node-type (eql 'include)) &key name children &allow-other-keys)
  270. (format *uffi-output* ";; INCLUDE ~A~%" name)
  271. (dolist (child children)
  272. (apply 'handle-node child)))
  273. (defmethod handle-node ((node-type (eql 'extern)) &key name children &allow-other-keys)
  274. (format *uffi-output* ";; EXTERN \"C\" ~A~%" name)
  275. (let ((*linkage* :c))
  276. (dolist (child children)
  277. (apply 'handle-node child))))
  278. ;;(defun compute-uffi-definitions (swig-interface)
  279. ;; (let ((*uffi-definitions* '()))
  280. ;; (handle-node swig-interface)
  281. ;; *uffi-definitions*))
  282. ;; Test instances
  283. ;;; Link to SWIG itself
  284. #||
  285. (defparameter *c++-compiler* "g++")
  286. (defun stdc++-library (&key env)
  287. (let ((error-output (make-string-output-stream)))
  288. (let ((name-output (make-string-output-stream)))
  289. (let ((proc (ext:run-program
  290. *c++-compiler*
  291. '("-print-file-name=libstdc++.so")
  292. :env env
  293. :input nil
  294. :output name-output
  295. :error error-output)))
  296. (unless proc
  297. (error "Could not run ~A" *c++-compiler*))
  298. (unless (zerop (ext:process-exit-code proc))
  299. (system:serve-all-events 0)
  300. (error "~A failed:~%~A" *c++-compiler*
  301. (get-output-stream-string error-output))))
  302. (string-right-trim '(#\Newline) (get-output-stream-string name-output)))))
  303. (defvar *swig-interface* nil)
  304. (defvar *swig-uffi-pathname* #p"/tmp/swig-uffi.lisp")
  305. (defun link-swig ()
  306. (setq *swig-interface*
  307. (run-swig (merge-pathnames "Source/swig.i" *swig-source-directory*)
  308. :directory-search-list
  309. (list (merge-pathnames "Source/" *swig-source-directory*))
  310. :module "swig"
  311. :ignore-errors t
  312. :c++ t))
  313. (with-open-file (f *swig-uffi-pathname* :direction :output)
  314. (let ((*linkage* :c++)
  315. (*uffi-definitions* '())
  316. (*uffi-output* f)
  317. (*uffi-primitive-type-alist* *uffi-default-primitive-type-alist*))
  318. (apply 'handle-node *swig-interface*)))
  319. (compile-file *swig-uffi-pathname*)
  320. (alien:load-foreign (merge-pathnames "Source/libswig.a"
  321. *swig-source-directory*)
  322. :libraries (list (stdc++-library)))
  323. ;; FIXME: UFFI stuffes a "-l" in front of the passed library names
  324. ;; (uffi:load-foreign-library (merge-pathnames "Source/libswig.a"
  325. ;; *swig-source-directory*)
  326. ;; :supporting-libraries
  327. ;; (list (stdc++-library)))
  328. (load (compile-file-pathname *swig-uffi-pathname*)))
  329. ||#
  330. ;;;; TODO:
  331. ;; * How to do type lookups? Is everything important that SWIG knows
  332. ;; about the types written out? What to make of typemaps?
  333. ;;
  334. ;; * Wrapped functions should probably automatically COERCE their
  335. ;; arguments (as of type DOUBLE-FLOAT), to make the functions more
  336. ;; flexible?
  337. ;;
  338. ;; * Why are the functions created by FFI interpreted?
  339. ;;
  340. ;; * We can't deal with more complicated structs and C++ classes
  341. ;; directly with the FFI; we have to emit SWIG wrappers that access
  342. ;; those classes.
  343. ;;
  344. ;; * A CLOS layer where structure fields are mapped as slots. It
  345. ;; looks like we need MOP functions to implement this.
  346. ;;
  347. ;; * Maybe modify SWIG so that key-value hashes are distinguished from
  348. ;; value-value hashes.