git.fiddlerwoaroof.com
Browse code

feature: add safe objc method calling for repl use

Ed Langley authored on 21/10/2019 04:06:19
Showing 2 changed files
... ...
@@ -117,11 +117,25 @@
117 117
   (sel o-selector)
118 118
   &rest)
119 119
 
120
+(defmacro safe-objc-msg-send (result-type thing selector &rest args)
121
+  (alexandria:once-only (thing selector)
122
+    `(if [,thing @(respondsToSelector:) :pointer ,selector]b
123
+         ,(ccase result-type
124
+            (string `[,thing ,selector ,@args]s)
125
+            (nsstring `[,thing ,selector ,@args]@)
126
+            (num `[,thing ,selector ,@args]#)
127
+            (bool `[,thing ,selector ,@args]b)
128
+            (id `[,thing ,selector ,@args]))
129
+         (error "invalid selector"))))
130
+
120 131
 ;;; This is a macro, because objc-msg-send is a macro.... which makes "apply" impossible
121 132
 ;;; \o/
122 133
 (defmacro objc-msg-send-nsstring (thing selector &rest args)
123 134
   `[[,thing ,selector ,@args] @(UTF8String)]s)
124 135
 
136
+(defmacro objc-msg-send-bool (thing selector &rest args)
137
+  `(= 1 [,thing ,selector ,@args]#))
138
+
125 139
 (defcfun (class-copy-method-list "class_copyMethodList" :library foundation)
126 140
     :pointer
127 141
   (cls o-class)
... ...
@@ -10,42 +10,50 @@
10 10
   (lambda (s c b)
11 11
     (declare (ignore c b))
12 12
     (let ((class-name (coerce (loop for next-char = (peek-char nil s nil nil t)
13
-                                 while next-char
14
-                                 until (funcall test next-char)
15
-                                 collect (read-char s t nil t)
16
-                                 finally (when (and (not (member next-char
17
-                                                                 stop-before-chars))
18
-                                                    (funcall test next-char))
19
-                                           (read-char s t nil t)))
13
+                                    while next-char
14
+                                    until (funcall test next-char)
15
+                                    collect (read-char s t nil t)
16
+                                    finally (when (and (not (member next-char
17
+                                                                    stop-before-chars))
18
+                                                       (funcall test next-char))
19
+                                              (read-char s t nil t)))
20 20
 
21 21
                               'string)))
22 22
       `(,symbol-prefix ,class-name))))
23 23
 
24
+(defun read-objc-form (s char)
25
+  (declare (ignore char))
26
+  (flet ((get-call-form (safe-p result-type base-form)
27
+           (if safe-p
28
+               `(safe-objc-msg-send ,result-type)
29
+               `(,base-form))))
30
+    (let* ((info (read-delimited-list #\] s t))
31
+           (safe-p (when (eql #\? (peek-char nil s nil #\p t))
32
+                     (read-char s t nil t)))
33
+           (msg-send (case (peek-char nil s nil #\p t)
34
+                       (#\# (read-char s t nil t) (get-call-form safe-p 'num 'objc-msg-send-int))
35
+                       (#\& (read-char s t nil t) (get-call-form safe-p 'id 'objc-msg-send))
36
+                       (#\@ (read-char s t nil t) (get-call-form safe-p 'nsstring 'objc-msg-send-nsstring))
37
+                       (#\b (read-char s t nil t) (get-call-form safe-p 'bool 'objc-msg-send-bool))
38
+                       (#\s (read-char s t nil t) (get-call-form safe-p 'string 'objc-msg-send-string))
39
+                       (t                         (get-call-form safe-p 'id 'objc-msg-send)))))
40
+      (when info
41
+        (destructuring-bind (obj message . args) info
42
+          `(,@msg-send ,obj ,message ,@args))))))
43
+
24 44
 (named-readtables:defreadtable :objc-readtable
25 45
   (:merge :standard)
26 46
   (:syntax-from :standard #\) #\])
27
-  (:macro-char #\[ (lambda (s char)
28
-                     char
29
-                     (let ((info (read-delimited-list #\] s t))
30
-                           (msg-send (case (peek-char nil s nil #\p t)
31
-                                       (#\# (read-char s t nil t) 'objc-msg-send-int)
32
-                                       (#\s (read-char s t nil t) 'objc-msg-send-string)
33
-                                       (#\@ (read-char s t nil t) 'objc-msg-send-nsstring)
34
-                                       (#\& (read-char s t nil t) 'objc-msg-send)
35
-                                       (t 'objc-msg-send))))
36
-                       (when info
37
-                         (destructuring-bind (obj message . args) info
38
-                           `(,msg-send ,obj ,message ,@args)))))
39
-               nil)
47
+  (:macro-char #\[ 'read-objc-form nil)
40 48
   (:dispatch-macro-char #\# #\@
41 49
                         (lambda (s c b)
42 50
                           c b
43 51
                           (let ((class-name (coerce (loop for c = (peek-char nil s nil nil t)
44
-                                                       until (or (null c)
45
-                                                                 (serapeum:whitespacep c)
46
-                                                                 (member c
47
-                                                                         '(#\) #\(  #\[ #\])))
48
-                                                       collect (read-char s t nil t))
52
+                                                          until (or (null c)
53
+                                                                    (serapeum:whitespacep c)
54
+                                                                    (member c
55
+                                                                            '(#\) #\(  #\[ #\])))
56
+                                                          collect (read-char s t nil t))
49 57
                                                     'string)))
50 58
                             `(ensure-class ,class-name))))
51 59
   (:macro-char #\@ :dispatch t)