3 (declaim (inline lispify))
4 (defun lispify (name &optional (package nil))
5 "Returns the interned symbol for name in Lisp style."
9 (values (intern (cxx-to-lisp name)))
10 (values (intern (cxx-to-lisp name) package))))
12 (defmacro define-string-transform (name documentation &body states)
13 "Defines a function to transform a string."
14 (let ((output (gensym))
19 (declare (simple-string input)
21 ;; At least on sbcl 1.0.25.debian CONCATENATE is faster than
22 ;; VECTOR-PUSH-EXTEND.
25 (,length (length input))
27 (declare (base-char char))
28 (macrolet ((next-char ()
29 `(if (>= ,',index ,',length)
30 (return-from transform ,',output)
32 (setf char (aref input ,',index))
38 `(setf ,',output (concatenate 'string
43 (next-char) ;; Get first char
45 (mapcar #'(lambda (state)
46 (if (stringp (second state))
47 `(,(first state) . ,(cddr state))
52 (define-string-transform lisp-to-cxx
53 "Converts LISP-STYLE to camelCase.
54 Note that (LISP-TO-CXX (CXX-TO-LIST SOME-STRING)) will not necessarily return
55 a string equal to SOME-STRING."
57 "Down case, convert _ and dispatch."
59 (#\- (go-next camel-upcase))
60 (#\. (go-next namespace))
61 (t (append-char (char-downcase char))
64 "Convert camelCase to lisp-style."
73 (define-string-transform cxx-to-lisp
74 "Returns camelCase STRING in lisp-style."
76 "Strip leading Q or K."
78 (#\K (go-next default))
79 (#\Q (go-next default))
82 "Up case, convert _ and dispatch."
84 (#\: (go-next namespace))
85 (#\_ (append-char #\-)
87 (#\ (append-char #\-) ;; space (cast operators)
89 (t (append-char (char-upcase char))
90 (if (lower-case-p char)
94 "C++ namespace separator"
95 (assert (eql #\: char))
99 "Convert camelCase to lisp-style."
100 (if (upper-case-p char)
108 (defmethod print-object ((object smoke-standard-object) stream)
109 (if (slot-boundp object 'pointer)
110 (print-unreadable-object (object stream :type t)
111 (when (const-p object)
112 (princ "CONST " stream))
113 (princ (pointer object) stream))
116 (defclass smoke-standard-class (standard-class smoke-class)
118 (:documentation "A Smoke C++ class"))
120 (defclass cxx:class (smoke-standard-class)
122 (:documentation "Metaclass to extend Smoke Objects."))
124 (defclass smoke-multi-superclass-mixin ()
125 ((extra-objects :reader extra-objects
126 :initarg :extra-objects)))
128 (defmethod closer-mop:validate-superclass ((class smoke-standard-class)
129 (superclass standard-class))
132 (defmethod closer-mop:validate-superclass ((class cxx:class)
133 (superclass smoke-standard-class))
136 ;; Sets NIL superclass to SMOKE-STANDARD-OBJECT instead of the default
138 (defun init-smoke-standard-class (class next-method
139 &rest args &key direct-superclasses
141 (apply next-method class
142 :direct-superclasses (or direct-superclasses
143 (list (find-class 'smoke-standard-object)))
146 (defmethod initialize-instance :around ((class smoke-standard-class) &rest args)
147 (apply #'init-smoke-standard-class class #'call-next-method args))
149 (defmethod reinitialize-instance :around ((class smoke-standard-class) &rest args)
150 (apply #'init-smoke-standard-class class #'call-next-method args))
153 (defun init-cxx-class (class next-method &rest args &key direct-superclasses
154 direct-default-initargs &allow-other-keys)
155 (assert (not (null direct-superclasses))
156 (direct-superclasses)
157 "No superclass supplied for class ~A" class)
158 (let ((superclass (first direct-superclasses))
159 (extra-superclasses (remove-if-not #'(lambda (class)
160 (typep class 'smoke-standard-class))
161 (rest direct-superclasses))))
162 (assert (typep superclass 'smoke-standard-class)
163 ((first direct-superclasses))
164 "The first superclass ~A must be an subclass of an Smoke class."
166 (assert (virtual-destructor-p superclass)
168 "The superclass ~A of ~A has a non virtual destructor."
170 (when extra-superclasses
171 (dolist (superclass extra-superclasses)
172 (unless (virtual-destructor-p superclass)
173 (cerror "Continue anyway"
174 "The superclass ~A of ~A has a non virtual destructor."
176 (setf direct-superclasses
177 (append direct-superclasses
178 (list (find-class 'smoke-multi-superclass-mixin))))
179 (push `(:extra-objects ,extra-superclasses ,#'(lambda ()
181 direct-default-initargs))
185 :smoke (smoke superclass)
186 :direct-superclasses direct-superclasses
187 :direct-default-initargs direct-default-initargs
190 (defmethod reinitialize-instance :around ((class cxx:class) &rest args)
191 (apply #'init-cxx-class class #'call-next-method args))
193 (defmethod initialize-instance :around ((class cxx:class) &rest args)
194 (apply #'init-cxx-class class #'call-next-method args))
196 (defun smoke-class-symbols (classes)
197 (let ((class-symbols))
198 (dolist (class classes class-symbols)
199 (if (external-p class)
200 (let ((real-class (find-smoke-class class nil)))
202 (push (class-name real-class) class-symbols)))
203 (push (lispify (name class)) class-symbols)))))
205 (defun make-smoke-classes (package smoke)
206 "Constructs a lisp class in PACKAGE for each one in the Smoke module SMOKE."
207 (declare (optimize (speed 3)))
208 (let ((impl-package *package*)
209 (*package* (find-package package)))
210 (add-id-class-map smoke)
213 (unless (external-p class)
214 (with-simple-restart (skip "Skip generating class ~A" (name class))
216 ;; There is a QGlobalSpace class per Smoke module.
217 ;; Put it in *package* and not PACKAGE to avoid
218 ;; clashes between multiple modules.
219 (if (string= "QGlobalSpace" (name class))
220 (lispify "QGlobalSpace" impl-package)
221 (lispify (name class)))))
223 (closer-mop:ensure-class class-name
226 (smoke-class-direct-superclasses class))
229 :metaclass 'smoke-standard-class))
230 (when (eql (symbol-package class-name) *package*)
231 (export class-name))))))
234 (defclass smoke-gf (cxx-generic-function)
235 ((cxx-name :reader name :initarg :cxx-name
237 :documentation "The C++ name of the method."))
238 (:metaclass closer-mop:funcallable-standard-class)
239 (:documentation "Smoke generic function."))
241 (declaim (inline smoke-class-of))
242 (defun smoke-class-of (object)
243 "Returns the class of OBJECT or OBJECT iff it already is a class."
244 (declare (optimize (speed 3)))
245 (if (typep object 'smoke-class)
249 ;;; To speed up the startup
250 ;;; ENSURE-METHOD is only called as needed.
251 (defmethod no-applicable-method ((gf smoke-gf) &rest args)
252 "Calls the smoke method."
253 (declare (dynamic-extent args)
254 (optimize (speed 3)))
255 (call-using-args (first args) (name gf) (rest args)))
257 (defmethod add-method :after ((gf cxx-method-generic-function) method)
258 "Adds a method which calls the smoke method, to make call-next-method work."
259 (when (null (rest (closer-mop:generic-function-methods gf)))
260 (let ((lambda-list (closer-mop:method-lambda-list method)))
261 (closer-mop:ensure-method
263 `(lambda ,lambda-list
264 (declare (optimize (speed 3)))
265 (call-using-args ,(first lambda-list)
266 (name ,(cxx-generic-function gf))
267 (list ,@(rest lambda-list))))))))
269 (defcallback destructed :void
270 ((object-pointer :pointer))
271 (declare (optimize (speed 3)))
272 (let ((object (get-object object-pointer)))
273 ;; The destructed callback can be the result of deleting the object
274 ;; in a finalizer. In that case the object is already removed from
275 ;; *object-map* when the callback is invoked. Thus OBJECT can be NIL.
277 (when (typep object 'smoke-multi-superclass-mixin)
278 (dolist (extra-object (extra-objects object))
279 (unless (null-pointer-p (pointer extra-object))
280 (remove-object (pointer extra-object))
281 (delete-object extra-object))))
282 (remove-finalizer object)
283 (remove-object object-pointer)
284 (setf (slot-value object 'pointer) (null-pointer)))))
286 (declaim (inline argument-to-lisp))
287 (defun argument-to-lisp (stack-item type)
288 ;; FIXME do not take ownership of stack allocated objects.
289 ;; It looks like there is no stack allocation in Qt virtual method signatures.
290 (type-to-lisp stack-item type))
292 (defun stack-to-args (stack arg &optional (args nil))
293 "Returns the arguments in STACK, where ARG is the type
294 of the first argument, as an list of Lisp objects."
297 (stack-to-args (inc-pointer stack
298 (foreign-type-size 'smoke-stack-item))
300 (cons (argument-to-lisp (mem-ref stack 'smoke-stack-item)
304 (defun convert-argument (argument type &optional disown)
305 "Returns ARGUMENT converted to TYPE and removes the ownership when
306 it is passed on the stack."
307 (flet ((disown (object)
308 (remove-finalizer object)
309 (when (typep object 'smoke-standard-object)
310 (remove-object (pointer object)))))
311 (let ((rank (get-conversion-sequence argument type nil)))
313 (let ((rank (get-conversion-sequence argument type t)))
315 (error "Can not convert the argument ~S to ~A."
316 argument (name type))
317 (let ((ret (funcall (conversion-function-name rank)
319 (when (and disown (stack-p type))
322 (prog1 (funcall (conversion-function-name rank) argument)
323 (when (and disown (stack-p type))
324 (disown argument)))))))
326 (defun put-returnvalue (stack value type object)
327 (unless (void-p type)
328 (let ((stack (make-call-stack stack)))
329 (setf (call-stack-top stack) (call-stack-pointer stack))
330 (let ((converted-value (convert-argument value type t)))
331 (push-smoke-stack stack converted-value (type-id type))))))
333 (defun get-gf-for-method (smoke-method)
334 (declare (smoke-method smoke-method)
335 (optimize (speed 3)))
336 (symbol-function (lispify (name smoke-method) "CXX")))
338 ;; Receive virtual function calls.
339 (defcallback dispatch-method :boolean
340 ((method smoke-index)
341 (object-ptr :pointer)
344 (declare (optimize (speed 3)))
345 (let ((object (get-object object-ptr)))
346 ;; The Lisp OBJECT can be gc'ed but we might still receive a
347 ;; QObject destructed event when the C++ instance is deleted in
348 ;; the finalizer. Thus OBJECT might be NIL.
349 (unless (null object)
350 (let* ((method (make-smoke-method
351 :smoke (smoke (class-of object))
355 (return-from dispatch-method
356 (let ((gf (get-gf-for-method method)))
357 (declare (function gf))
358 (if (null (gf-methods gf))
361 (error "Abstract method ~A of ~A called."
362 (method-declaration method) object))
370 (inc-pointer stack (foreign-type-size
372 (get-first-argument method)))
373 (return-type method) object)
376 ;; Restarts to prevent stack unwinding across the C++ stack.
378 :report (lambda (stream)
379 (declare (stream stream))
381 "Call default implementation ~A instead."
383 :test (lambda (condition)
384 (declare (ignore condition))
386 (return-from dispatch-method nil))
387 (use-returnvalue (return-value)
388 :report (lambda (stream)
389 (declare (stream stream))
390 (format stream "Supply a return value for ~A."
391 (method-declaration method)))
392 :test (lambda (condition)
393 (declare (ignore condition))
394 (not (void-p (return-type method))))
395 :interactive (lambda ()
396 (format *query-io* "~&Enter a new return value: ")
397 (multiple-value-list (eval (read *query-io*))))
398 (put-returnvalue stack return-value
400 (get-object object-ptr))
401 (return-from dispatch-method t))
403 :report (lambda (stream)
404 (declare (stream stream))
405 (format stream "Return void for ~A."
406 (method-declaration method)))
407 :test (lambda (condition)
408 (declare (ignore condition))
409 (void-p (return-type method)))
410 (return-from dispatch-method (values)))
412 :report (lambda (stream)
413 (declare (stream stream))
414 (format stream "Try again calling ~A."
415 (method-declaration method))))))
418 ;;FIXME use CHANGE-CLASS instead?
419 (defgeneric cast (object class)
420 (declare (optimize (speed 3)))
421 (:documentation "Returns a pointer of type CLASS to the C++ object of OBJECT.")
422 (:method (object class)
423 (declare (optimize (speed 3)))
424 (assert (derived-p (class-of object) class)
426 "Can not cast object ~A of class ~A to class ~A."
427 object (name (class-of object)) (name class))
428 (smoke-cast (smoke-module-pointer (smoke (class-of object))) (pointer object)
429 (id (class-of object))
430 (smoke-class-id (smoke-module-pointer (smoke (class-of object)))
431 (name-pointer class))))
432 (:method ((object smoke-multi-superclass-mixin) class)
433 (if (derived-p (class-of object) class)
435 (let ((extra-object (find-if #'(lambda (o)
436 (derived-p (class-of o) class))
437 (extra-objects object))))
440 "Can not cast object ~A to class ~A."
442 (cast extra-object class)))))
444 (defun upcast (object class)
445 (assert (derived-p class (class-of object))
447 "Can not upcast object ~A of class ~A to class ~A."
448 object (name (class-of object)) (name class))
449 (smoke-cast (smoke-module-pointer (smoke class)) (pointer object)
450 (id (class-of object)) (id (real-class class))))
453 ;; The constructor name is the name of the class minus any namespace parts.
454 (defun constructor-name (class)
455 (declare (optimize (speed 3)))
456 (let* ((name (the simple-string (name class)))
457 (name-start (search "::" name :from-end t)))
459 (subseq name (+ name-start 2))
462 (defun call-constructor (class arguments)
463 (multiple-value-bind (method sequence)
464 (#-sbcl find-best-viable-function
465 #+sbcl find-best-viable-function-cached
466 (constructor-name class)
470 (error "No constructor for class ~A with
471 the arguments ~S." class arguments))
472 (pointer-call method (null-pointer)
473 (mapcar #'(lambda (conversion argument)
474 (funcall conversion argument))
475 sequence arguments))))
477 (defmethod initialize-instance :after ((object smoke-standard-object)
484 "Initializes a Smoke object. Calls its constructor with the arguments supplied
485 by the key :ARGS and sets the smoke binding."
486 (declare (optimize (speed 3)))
487 (assert (not (and (slot-boundp object 'pointer)
489 ((slot-value object 'pointer) args)
490 "Pointer ~A bound and constructor argument :ARGS ~S supplied."
491 (slot-value object 'pointer) args)
492 (unless (slot-boundp object 'pointer)
494 (setf (slot-value object 'pointer)
495 (call-constructor (class-of object)
497 (arg3p (list arg0 arg1 arg2 arg3))
498 (arg2p (list arg0 arg1 arg2))
499 (arg1p (list arg0 arg1))
501 (setf (slot-value object 'pointer)
502 (call-constructor (class-of object) args)))
504 (take-ownership object)
505 (add-object object)))
507 (defun construct-extra-objects (object extra-objects)
508 (loop for class in extra-objects
509 collect (let ((extra-object (make-instance (first extra-objects)
510 :pointer (call-constructor (first extra-objects)
512 (set-binding extra-object)
513 (setf (get-object (pointer extra-object)) object)
516 (defmethod initialize-instance :after ((object smoke-multi-superclass-mixin)
518 (setf (slot-value object 'extra-objects)
519 (construct-extra-objects object (extra-objects object))))
521 (defmethod make-finalize ((object smoke-multi-superclass-mixin))
522 (let ((pointer (pointer object))
523 (extra-objects (extra-objects object))
524 (class (class-of object)))
526 (declare (optimize (speed 3)))
528 (delete-pointer pointer class)
529 (dolist (object extra-objects)
530 (delete-object object)))
532 (report-finalize-error condition 't (name class) pointer))))))
534 (defmethod instance-to-lisp (pointer class type)
535 (declare (type smoke-standard-class class)
536 (optimize (speed 3)))
537 (let ((ret (make-instance class :pointer pointer
538 :const-p (const-p type))))
544 (defun keep-wrapper (object new-owner)
545 (declare (type smoke-standard-object object)
546 (optimize (speed 3)))
547 (when (member object (owned-objects new-owner))
548 (cerror "Ignore" "~A has already been added to ~A."
550 (push object (owned-objects new-owner)))
552 (declaim (inline remove-wrapper-object))
553 (defun remove-wrapper-object (object owner)
554 (remove object (owned-objects owner)))
556 (defun transfer-ownership-to (object new-owner)
557 "Transfers the ownership of OBJECT to C++."
558 (declare (optimize (speed 3)))
559 (remove-finalizer object)
560 (if (virtual-destructor-p (class-of object))
561 (keep-wrapper object new-owner)
562 (remove-object (pointer object))))
564 (defun take-ownership (object &optional current-owner)
565 "Assigns the ownership of OBJECT to Lisp. i.e.:
566 cl-smoke is responsible for deleting the object."
568 (remove-wrapper-object object current-owner))
569 (set-finalizer object))