;;;; Objective-CL, an Objective-C bridge for Common Lisp.
;;;; Copyright (C) 2007, 2008 Matthias Andreas Benkard.
;;;;
;;;; This program is free software: you can redistribute it and/or
;;;; modify it under the terms of the GNU Lesser General Public License
;;;; as published by the Free Software Foundation, either version 3 of
;;;; the License, or (at your option) any later version.
;;;;
;;;; This program is distributed in the hope that it will be useful, but
;;;; WITHOUT ANY WARRANTY; without even the implied warranty of
;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
;;;; Lesser General Public License for more details.
;;;;
;;;; You should have received a copy of the GNU Lesser General Public
;;;; License along with this program. If not, see
;;;; .
(in-package #:objective-cl)
(defclass objective-c-generic-function (standard-generic-function)
()
(:metaclass funcallable-standard-class)
(:documentation "An Objective-C dispatch function for a given selector.
## Description:
Every Lisp-defined __objective-c-method__ belongs to a corresponding
__objective-c-generic-function__ that handles **method combination** and
registration of newly added **method**s with the Objective-C runtime.
It is recommended to create all __objective-c-generic-functions__ by
means of the macro __define-objective-c-generic-function__.
If you subclass this **class**, be aware that there is no protocol that
describes its behaviour, so the consequences of overriding any
**generic function**s **applicable** to the **class** are undefined."))
(defclass objective-c-method (standard-method)
((return-type :initarg :return-type
:accessor method-return-type)
(argument-types :initarg :argument-types
:accessor method-argument-types))
(:metaclass standard-class)
(:documentation "An Objective-C method implemented in Lisp.
## Description:
Instances of __objective-c-method__ are similar to instances of
__standard-method__ except that they know about their foreign argument
and return types and recognise different **qualifier**s that they
communicate to the __objective-c-generic-function__ that they belong to.
It is recommended to create all __objective-c-methods__ by means of the
macro __define-objective-c-method__.
There is no protocol that defines the behaviour of this **class**, so
the consequences of subclassing it and overriding its **method**s is
undefined."))
(defun qualifiers-return-type (qualifiers)
(find-if #'(lambda (x)
(or (not (keywordp x))
(typep x 'objective-c-type-keyword)))
qualifiers))
(defmacro defobjcmethod (name &rest args)
`(define-objective-c-method ,name ,@args))
;; Put simply, a DEFINE-OBJECTIVE-C-METHOD form is transformed into a
;; DEFMETHOD form along with the code needed for initialisation of the
;; corresponding Objective-C method and registration of the callback.
;;
;; Note that mapping Objective-C methods to generic functions in a naive
;; way breaks super calls, because the following can (and will) happen:
;;
;; 1. Someone sends a message to a Lisp-based instance.
;;
;; 2. The callback is entered. It calls the generic function of the
;; same name, which invokes the effective method applying to the
;; object and method arguments.
;;
;; 2. The method calls super.
;;
;; 3. The callback for the parent class is entered. It, in turn, calls
;; the generic function of the same name, which invokes the same
;; effective method called in step 1. Oops.
;;
;; There are two solutions to this problem:
;;
;; 1. Do not use generic functions and methods at all, but define a
;; function for each callback.
;;
;; 2. Pass the class name as the first argument so that the dispatch
;; mechanism may dispatch over it.
;;
;; Option (2), which is what we do, is preferable because it allows the
;; user to dispatch over an arbitrary set of arguments other than the
;; first by reusing the standard CLOS generic function dispatch
;; mechanism.
(defmacro define-objective-c-method (name &rest args)
"Define a new Objective-C method.
*args* ::= \\{*qualifier*\\}\\* *lambda-list* [[\\{*declaration*\\}\\* | *docstring*]] \\{*form*\\}\\*
## Arguments and Values:
*name* --- a **symbol**.
*qualifier* --- a **method qualifier**.
*return-type* --- a *typespec*.
*lambda-list* --- a **modified lambda list**.
*docstring* --- a **string** (not evaluated).
*declaration* --- a **local declaration** (not evaluated).
*forms* --- an **implicit progn**.
## Description:
_define-objective-c-generic-function_ is like __defgeneric__ except in the
following aspects:
1. *name* is immediately replaced by a *symbol* **intern**ed in package
_objective-c-methods_.
2. The default value for the _:generic-function-class_ option is
_objective-cl:objective-c-generic-function_.
3. The default value for the _:method-class_ option is
_objective-cl:objective-c-method_.
_define-objective-c-generic-function_ recognises the same *options* as
__defgeneric__, including _:generic-function-class_ and _:method-class_.
The **lexical environment** of *body* is augmented to include the
function __super__.
## Example:
#.(enable-method-syntax)
(define-objective-c-class ns::mlk-my-class (ns::ns-object)
((foos :initargs :foos)
(foo-count :foreign-type :int)))
=> NS::MLK-MY-CLASS
(define-objective-c-generic-function #/foo:bar:stuff:do: (self y z a b))
=> #
(define-objective-c-method #/foo:bar:stuff:do: :int
((self ns::mlk-my-class) (y :int) z a (b ns::ns-number))
(format t \"Hello! Z and A are ~A and~
~&~A, respectively.~
~&Have a nice day.\" z a)
(+ y 20))
=> #
(#/foo:bar:stuff:do: (#/new (find-objc-class 'ns::mlk-my-class))
100
30
\"FOO!\"
5)
=> Output:
Hello! Z and A are # and
#, respectively.
Have a nice day.
=> 120
#.(disable-method-syntax)
## Note 1:
It is not generally possible to define methods after a class has already
been registered with the Objective-C runtime. The latter inevitably
happens when Objective-CL first sees an instance of the class.
## Note 2:
If you do not call __define-objective-c-generic-function__ before using
__define-objective-c-method__, it will be called implicitly by the
latter. There is nothing wrong with relying on this; in fact, if you do
not want to set any options for the generic function, your code will
probably seem less cramped if you leave the redundant
__define-objective-c-generic-function__ calls out.
## Note 3:
It is customary to use the #/ notation enabled by
__enable-method-syntax__ to write method names for
__define-objective-c-generic-function__.
## Note 4:
Do not call __call-next-method__ in the body of an
__objective-c-method__. Its behaviour is quite different from
__super__, ill-defined, and probably not desirable anyway.
Use __super__ instead.
## See also:
__define-objective-c-generic-function__, __define-objective-c-class__,
__super__"
(let ((qualifiers (list)))
(loop until (listp (first args))
do (push (pop args) qualifiers))
(setq qualifiers (nreverse qualifiers))
(destructuring-bind (lambda-list . body) args
(let ((lambda-list (copy-list lambda-list)))
(loop for arg-cons on lambda-list
for arg = (car arg-cons)
until (member arg lambda-list-keywords)
if (listp arg)
if (typep (second arg) 'objective-c-type-keyword)
collect (second arg) into type-specifiers
and collect (first arg) into arg-names
and do (setf (car arg-cons) (first arg))
else
;; We simply map all non-OBJECTIVE-C-TYPE-KEYWORD
;; specialisers to :ID. This makes sense: If the
;; specialiser is an Objective-C type, the type
;; specifier should obviously be :ID. If it's a
;; non-Objective-C CLOS class, we're going to pass
;; Objective-C objects of the LISP-VALUE-WRAPPER-MIXIN
;; kind to the method, whose type specifier is :ID as
;; well.
collect :id into type-specifiers
and collect (first arg) into arg-names
else
collect :id into type-specifiers
and collect arg into arg-names
finally (let ((super-args-sym (gensym))
(captured-args-sym (gensym))
(class-arg-sym (gensym))
(class-name (intern (symbol-name (cadar lambda-list))
'#:objective-c-classes))
(real-name (intern (symbol-name name)
'#:objective-c-methods)))
(return
`(progn
(eval-when (:compile-toplevel :load-toplevel :execute)
(unless (fboundp ',real-name)
;; Note that we need not specify a
;; :LAMBDA-LIST here, as not supplying
;; it means it's simply going to be
;; initialised when a method is first
;; added to the generic function.
;;
;; For some reason,
;; ENSURE-GENERIC-FUNCTION raises an
;; error on Allegro CL claiming that
;; MAKE-INSTANCE of
;; OBJECTIVE-C-GENERIC-FUNCTION does
;; not understand the
;; :GENERIC-FUNCTION-CLASS initarg.
;; Calling
;; ENSURE-GENERIC-FUNCTION-USING-CLASS
;; instead does not display this
;; behaviour. Weird.
(ensure-generic-function-using-class
nil
',real-name
:generic-function-class
'objective-c-generic-function
:method-class
'objective-c-method)))
(defmethod ,real-name
argtypes-start ,@type-specifiers argtypes-end
,@qualifiers
((,class-arg-sym (eql ',class-name))
,@lambda-list)
(let ((,captured-args-sym (list ,@arg-names)))
(flet ((super (&rest ,super-args-sym)
(invoke-by-name-super-v
(first ,captured-args-sym)
,(generic-function-name->method-name
name)
(objcl-class-superclass
(find-objc-class ',class-name))
(or ,super-args-sym
(rest ,captured-args-sym)))))
(declare (ignorable (function super)))
,@body)))))))))))
(defun super (&rest args)
"Send a super message to the receiver of the current message.
## Arguments and Values:
*args* --- a **list**.
## Description:
A _super_ call is only valid inside the body of a **method** defined by
means of __define-objective-c-method__. In this case, the Objective-C
**method** of the same name defined in the superclass of the class
specialised over by the method is called. In other words, a super call
is made.
The behaviour is similar in spirit to calling __call-next-method__ in
the body of a __standard-method__ except that it will be done by the
Objective-C runtime, thereby ignoring any pure-Lisp superclasses.
__super__ is therefore single-inheritance only.
If __super__ is called with no arguments, the original method arguments
are passed to the super method. Otherwise, the supplied arguments are
passed.
## Note:
Do not call __call-next-method__ in the body of an
__objective-c-method__. Its behaviour is quite different from
__super__, ill-defined, and probably not desirable anyway.
## Examples:
(define-objective-c-method #/characterAtIndex: :short ((self ns::my-string) (index :unsigned-long))
(if (weird-string-p self)
(super (1+ index))
(super)))"
(declare (ignore args))
(error "Tried to call ~S outside an Objective-C method." 'super))
(defmacro defobjcgeneric (name lambda-list &body options)
"Define a new Objective-C generic function.
## Arguments and Values:
*name* --- a *symbol*.
*lambda-list* --- a **generic function lambda list**.
*options* --- a *list* (not evaluated).
## Description:
This macro is equivalent to __define-objective-c-generic-function__.
## See also:
__define-objective-c-generic-function__, __defobjcmethod__"
`(define-objective-c-generic-function ,name ,lambda-list ,@options))
(defmacro define-objective-c-generic-function (name lambda-list &body options)
"Define a new Objective-C generic function.
## Arguments and Values:
*name* --- a *symbol*.
*lambda-list* --- a **generic function lambda list**.
*options* --- a *list* (not evaluated).
## Description:
_define-objective-c-generic-function_ is like __defgeneric__ except in the
following aspects:
1. *name* is immediately replaced by a *symbol* **intern**ed in package
_objective-c-methods_.
2. The default value for the _:generic-function-class_ option is
_objective-cl:objective-c-generic-function_.
3. The default value for the _:method-class_ option is
_objective-cl:objective-c-method_.
_define-objective-c-generic-function_ recognises the same *options* as
__defgeneric__, including _:generic-function-class_ and _:method-class_.
## Example:
See __define-objective-c-method__.
## Note:
It is customary to use the #/ notation enabled by
__enable-method-syntax__ to write method names for
__define-objective-c-generic-function__.
## See also:
__define-objective-c-method__, __define-objective-c-class__"
`(defgeneric ,(intern (symbol-name name) '#:objective-c-methods)
(,(gensym "CLASS") ,@lambda-list)
,@(unless (position :generic-function-class
options
:key #'car)
`((:generic-function-class objcl:objective-c-generic-function)))
,@(unless (position :method-class
options
:key #'car)
`((:method-class objcl:objective-c-method)))
,@options))
(defvar *callback-names* (make-hash-table :test #'eql))
(defun intern-callback-name (method)
(or (gethash method *callback-names* nil)
(setf (gethash method *callback-names* nil)
(intern (format nil "~A ~S ~A"
(generic-function-name
(method-generic-function method))
(class-name (second (method-specializers method)))
(sort (copy-list (method-qualifiers method))
#'string<
:key #'string))
'#:objective-cl))))
(defmethod add-method :after ((gf objective-c-generic-function)
(method objective-c-method))
;; FIXME: Support &REST arguments.
(let* ((class (second (method-specializers method)))
(method-name (generic-function-name->selector
(generic-function-name gf)))
(registered-p (foreign-class-registered-p class))
(return-type (typespec (method-return-type method)))
(method-argument-types (method-argument-types method))
(argument-types (list* (first method-argument-types)
:selector
(rest method-argument-types)))
(return-typestring (print-typespec-to-string return-type))
(arg-typestrings (mapcar #'print-typespec-to-string
argument-types))
(callback-name (intern-callback-name method))
(arg-symbols (mapcar #'(lambda (x)
(declare (ignore x))
(gensym "ARG"))
argument-types)))
(eval (loop for type in argument-types
for typespec = (typespec type)
for symbol in arg-symbols
for first-arg-p = t then nil
collect (list symbol (typespec->c-type typespec)) into cffi-lambda-list
if (member (typespec-primary-type typespec) '(:id :class :selector))
collect `(coerce-object ,symbol ',type)
#+nil
`(intern-pointer-wrapper ',type
:pointer ,symbol
:skip-wrapper-unwrapping ,first-arg-p)
into arguments
else
collect symbol into arguments
finally (return
`(defcallback ,callback-name
,(typespec->c-type return-type)
,cffi-lambda-list
(declare (ignorable ,(cadr arg-symbols)))
(unwind-protect
(,(if (member (typespec-primary-type return-type)
'(:id :class :selector))
'pointer
'progn)
(coerce-object
(,(generic-function-name gf)
;; Pass the class this method is
;; being defined for as the first
;; argument. This is needed so that
;; super calls can work.
',(class-name class)
;; Leave the second argument (the
;; selector) out.
,@(list* (car arguments) (cddr arguments)))
',return-type))
;; FIXME: We may want to wrap signalled
;; SERIOUS-CONDITIONS in some kind of
;; Objective-C exception object and put
;; it into *OBJCL-CURRENT-EXCEPTION*. Or
;; maybe we don't, assuming the Lisp
;; system can handle a few layers of C
;; functions between a condition's
;; signalling and handling, in which case
;; we'd only destroy the restart
;; mechanism by pseudo-handling
;; conditions in this way.
(%objcl-acquire-lock *objcl-current-exception-lock*))))))
(let ((callback (get-callback callback-name)))
(with-foreign-object (arg-typestring-buffer :string
(- (length arg-typestrings) 2))
(with-foreign-string-pool (register-temp allocate-temp)
(loop for i from 0
for typestring in (cddr arg-typestrings)
do (setf (mem-aref arg-typestring-buffer :string i)
(allocate-temp typestring)))
(%objcl-add-method (pointer-to class)
(symbol->objc-class-name (class-name class))
(pointer-to method-name)
callback
(- (length arg-typestrings) 2)
return-typestring
arg-typestring-buffer
(apply #'concatenate
'string
return-typestring
arg-typestrings)
(if registered-p 1 0))))))
#+(or) (format t "~&ADD-METHOD:~& ~A, ~A" gf method))
(defmethod initialize-instance :around ((method objective-c-method)
&rest initargs
&key documentation
function
lambda-list
qualifiers
specializers
&allow-other-keys)
(declare (ignore documentation function lambda-list specializers))
#+(or) (format t "~&INITIALIZE-INSTANCE:~& ~S" initargs)
(let* ((argtypes-start (position 'argtypes-start qualifiers))
(argtypes-end (position 'argtypes-end qualifiers))
(argument-types (subseq qualifiers (1+ argtypes-start) argtypes-end))
(qualifiers (append (subseq qualifiers 0 argtypes-start)
(subseq qualifiers (1+ argtypes-end))))
(new-initargs (copy-list initargs))
(return-type (qualifiers-return-type qualifiers)))
(setf (getf new-initargs :qualifiers) (remove return-type qualifiers))
(apply #'call-next-method
method
:return-type (or return-type :id)
:argument-types argument-types
new-initargs)))