Change Lispwork's foreign string conversion to make wide character strings
[uffi.git] / src / strings.lisp
1 ;;;; -*- Mode: Lisp; Syntax: ANSI-Common-Lisp; Base: 10 -*-
2 ;;;; *************************************************************************
3 ;;;; FILE IDENTIFICATION
4 ;;;;
5 ;;;; Name:          strings.lisp
6 ;;;; Purpose:       UFFI source to handle strings, cstrings, and foreigns
7 ;;;; Programmer:    Kevin M. Rosenberg
8 ;;;; Date Started:  Feb 2002
9 ;;;;
10 ;;;; This file, part of UFFI, is Copyright (c) 2002-2010 by Kevin M. Rosenberg
11 ;;;; *************************************************************************
12
13 (in-package #:uffi)
14
15
16 (def-pointer-var +null-cstring-pointer+
17     #+(or cmu sbcl scl) nil
18     #+allegro 0
19     #+lispworks (fli:make-pointer :address 0 :type '(:unsigned :char))
20     #+(or openmcl digitool) (ccl:%null-ptr)
21 )
22
23 (defmacro convert-from-cstring (obj)
24   "Converts a string from a c-call. Same as convert-from-foreign-string, except
25 that LW/CMU automatically converts strings from c-calls."
26   #+(or cmu sbcl lispworks scl) obj
27   #+allegro
28   (let ((stored (gensym)))
29     `(let ((,stored ,obj))
30        (if (zerop ,stored)
31            nil
32            (values (excl:native-to-string ,stored)))))
33   #+(or openmcl digitool)
34   (let ((stored (gensym)))
35     `(let ((,stored ,obj))
36        (if (ccl:%null-ptr-p ,stored)
37            nil
38          (values (ccl:%get-cstring ,stored)))))
39   )
40
41 (defmacro convert-to-cstring (obj)
42   #+(or cmu sbcl scl lispworks) obj
43   #+allegro
44   (let ((stored (gensym)))
45     `(let ((,stored ,obj))
46        (if (null ,stored)
47            0
48            (values (excl:string-to-native ,stored)))))
49   #+(or openmcl digitool)
50   (let ((stored (gensym)))
51     `(let ((,stored ,obj))
52        (if (null ,stored)
53            +null-cstring-pointer+
54            (let ((ptr (new-ptr (1+ (length ,stored)))))
55              (ccl::%put-cstring ptr ,stored)
56              ptr))))
57   )
58
59 (defmacro free-cstring (obj)
60   (declare (ignorable obj))
61   #+allegro
62   (let ((stored (gensym)))
63     `(let ((,stored ,obj))
64        (unless (zerop ,stored)
65          (ff:free-fobject ,stored))))
66   #+(or openmcl digitool)
67   (let ((stored (gensym)))
68     `(let ((,stored ,obj))
69        (unless (ccl:%null-ptr-p ,stored)
70          (dispose-ptr ,stored))))
71   )
72
73 (defmacro with-cstring ((cstring lisp-string) &body body)
74   #+(or cmu sbcl scl lispworks)
75   `(let ((,cstring ,lisp-string)) ,@body)
76   #+allegro
77   (let ((acl-native (gensym))
78         (stored-lisp-string (gensym)))
79     `(let ((,stored-lisp-string ,lisp-string))
80        (excl:with-native-string (,acl-native ,stored-lisp-string)
81          (let ((,cstring (if ,stored-lisp-string ,acl-native 0)))
82            ,@body))))
83   #+(or openmcl digitool)
84   (let ((stored-lisp-string (gensym)))
85     `(let ((,stored-lisp-string ,lisp-string))
86        (if (stringp ,stored-lisp-string)
87            (ccl:with-cstrs ((,cstring ,stored-lisp-string))
88              ,@body)
89            (let ((,cstring +null-cstring-pointer+))
90              ,@body))))
91   )
92
93 (defmacro with-cstrings (bindings &rest body)
94   (if bindings
95       `(with-cstring ,(car bindings)
96         (with-cstrings ,(cdr bindings)
97           ,@body))
98       `(progn ,@body)))
99
100 ;;; Foreign string functions
101
102 (defun %convert-to-foreign-string (str encoding)
103   (declare (ignorable str encoding))
104   #+(or cmu scl)
105   (etypecase str
106     (null
107      (alien:sap-alien (system:int-sap 0) (* (alien:unsigned 8))))
108     (string
109      (locally
110          (declare (optimize (speed 3) (safety 0)))
111        (let* ((size (length str))
112               (storage (alien:make-alien (alien:unsigned 8) (1+ size))))
113          (declare (fixnum size))
114          (setq storage (alien:cast storage (* (alien:unsigned 8))))
115          (dotimes (i size)
116            (declare (fixnum i))
117            (setf (alien:deref storage i)
118                  (char-code (char str i))))
119          (setf (alien:deref storage size) 0)
120          storage))))
121
122   #+(and sbcl (not sb-unicode))
123   (etypecase str
124     (null
125      (sb-alien:sap-alien (sb-sys:int-sap 0) (* (sb-alien:unsigned 8))))
126     (string
127      (locally
128          (declare (optimize (speed 3) (safety 0)))
129        (let* ((size (length str))
130               (storage (sb-alien:make-alien (sb-alien:unsigned 8) (1+ size))))
131          (declare (fixnum i))
132          (setq storage (sb-alien:cast storage (* (sb-alien:unsigned 8))))
133          (dotimes (i size)
134            (declare (fixnum i))
135            (setf (sb-alien:deref storage i)
136                  (char-code (char str i))))
137          (setf (sb-alien:deref storage size) 0))
138        storage)))
139
140   #+(and sbcl sb-unicode)
141   (etypecase str
142     (null
143      (sb-alien:sap-alien (sb-sys:int-sap 0) (* (sb-alien:unsigned 8))))
144     (string
145      (locally
146          (declare (optimize (speed 3) (safety 0)))
147        (let* ((fe (or encoding *default-foreign-encoding*))
148               (ife (when fe (lookup-foreign-encoding fe))))
149          (if ife
150              (let* ((octets (sb-ext:string-to-octets str :external-format ife))
151                     (size (length octets))
152                     (storage (sb-alien:make-alien (sb-alien:unsigned 8) (+ size 2))))
153                (declare (fixnum size))
154                (setq storage (sb-alien:cast storage (* (sb-alien:unsigned 8))))
155                (dotimes (i size)
156                  (declare (fixnum i))
157                  (setf (sb-alien:deref storage i) (svref octets i)))
158                ;; terminate with 2 nulls, maybe needed for some encodings
159                (setf (sb-alien:deref storage size) 0)
160                (setf (sb-alien:deref storage (1+ size)) 0)
161                storage)
162
163              (let* ((size (length str))
164                     (storage (sb-alien:make-alien (sb-alien:unsigned 8) (1+ size))))
165                (declare (fixnum size))
166                (setq storage (sb-alien:cast storage (* (sb-alien:unsigned 8))))
167                (dotimes (i size)
168                  (declare (fixnum i))
169                  (setf (sb-alien:deref storage i)
170                        (char-code (char str i))))
171                (setf (sb-alien:deref storage size) 0)
172                storage))))))
173
174   #+(and openmcl openmcl-unicode-strings)
175   (if (null str)
176       +null-cstring-pointer+
177       (locally
178           (declare (optimize (speed 3) (safety 0)))
179         (let* ((fe (or encoding *default-foreign-encoding*))
180                (ife (when fe (lookup-foreign-encoding fe))))
181           (if ife
182               (let* ((octets (ccl:encode-string-to-octets str :external-format ife))
183                      (size (length octets))
184                      (ptr (new-ptr (+ size 2))))
185                 (declare (fixnum size))
186                 (dotimes (i size)
187                   (declare (fixnum i))
188                   (setf (ccl:%get-unsigned-byte ptr i) (svref octets i)))
189                 (setf (ccl:%get-unsigned-byte ptr size) 0)
190                 (setf (ccl:%get-unsigned-byte ptr (1+ size)) 0)
191                 ptr)
192
193               (let ((ptr (new-ptr (1+ (length str)))))
194                 (ccl::%put-cstring ptr str)
195                 ptr)))))
196
197   #+(or digitool (and openmcl (not openmcl-unicode-strings)))
198   (if (null str)
199       +null-cstring-pointer+
200       (let ((ptr (new-ptr (1+ (length str)))))
201         (ccl::%put-cstring ptr str)
202         ptr))
203
204   #+(or allegro lispworks)
205   nil
206   )
207
208 (defmacro convert-to-foreign-string (obj &optional encoding)
209   (declare (ignorable encoding))
210   #+allegro
211   (let ((stored (gensym "STR-"))
212         (fe (gensym "FE-"))
213         (ife (gensym "IFE-")))
214     `(let* ((,stored ,obj)
215             (,fe (or encoding *default-foreign-encoding*))
216             (,ife (when ,fe
217                     (lookup-foreign-encoding ,fe))))
218        (cond
219          ((null ,stored)
220           0)
221          ((null ,ife)
222           (values (excl:string-to-native ,stored)))
223          (t
224            (values (excl:string-to-native ,stored :external-format ,ife))))))
225
226   #+lispworks
227   (let ((stored (gensym "STR-"))
228         (fe (gensym "EF-"))
229         (ife (gensym "NEF-")))
230     `(let* ((,stored ,obj)
231             (,fe (or ,encoding *default-foreign-encoding*))
232             (,ife (when ,fe
233                     (lookup-foreign-encoding ,fe))))
234        (cond
235          ((null ,stored)
236           +null-cstring-pointer+)
237          ((null ,ife)
238           (fli:convert-to-foreign-string ,stored))
239          (t
240           (fli:convert-to-foreign-string ,stored :external-format ,ife)))))
241
242   #+(or cmu scl sbcl digitool openmcl)
243   `(%convert-to-foreign-string ,obj (lookup-foreign-encoding
244                                      (or ,encoding *default-foreign-encoding*)))
245 )
246
247
248 ;; Either length or null-terminated-p must be non-nil
249 (defmacro convert-from-foreign-string (obj &key
250                                        length
251                                        encoding
252                                        (null-terminated-p t))
253   (declare (ignorable length encoding null-terminated-p))
254   #+allegro
255   (let ((stored-obj (gensym "STR-"))
256         (fe (gensym "FE-"))
257         (ife (gensym "IFE-")))
258     `(let ((,stored-obj ,obj))
259        (if (zerop ,stored-obj)
260            nil
261            (let* ((,fe (or ,encoding *default-foreign-encoding*))
262                   (,ife (when ,fe (lookup-foreign-encoding ,fe))))
263              (if ,ife
264                  (values
265                   (excl:native-to-string
266                    ,stored-obj
267                    ,@(when length (list :length length))
268                    :truncate (not ,null-terminated-p)
269                    :external-format ,ife))
270                  (fast-native-to-string ,stored-obj ,length))))))
271
272   #+lispworks
273   #|
274   ;; FAST-NATIVE-TO-STRING (suprisingly) works just fine to make strings
275   ;; for formatted printing with Lispworks and UTF-8 multibyte character strings.
276   ;; However, without knowledge of specific-encoding, the LENGTH call in FAST-NATIVE-TO-STRING
277   ;; will be be incorrect for some encodings/strings and strings consist of octets rather
278   ;; than wide characters
279   ;; This is a stop-gap until get tech support on why the below fails.
280   (let ((stored-obj (gensym "STR-")))
281     `(let ((,stored-obj ,obj))
282        (if (fli:null-pointer-p ,stored-obj)
283            nil
284            (fast-native-to-string ,stored-obj ,length))))
285   |#
286   #|
287   ;; Below code doesn't work on tesing with LW 6.0 testing with a UTF-8 string.
288   ;; fli:convert-from-foreign-string with :external-format of :UTF-8 doesn't
289   ;; properly code multibyte characters.
290   |#
291   (let ((stored-obj (gensym "STR-"))
292         (fe (gensym "FE-"))
293         (ife (gensym "IFE-")))
294     `(let ((,stored-obj ,obj))
295        (if (fli:null-pointer-p ,stored-obj)
296            nil
297            (let* ((,fe (or ,encoding *default-foreign-encoding*))
298                   (,ife (when ,fe (lookup-foreign-encoding ,fe))))
299              (if ,ife
300                  (fli:convert-from-foreign-string
301                   ,stored-obj
302                   ,@(when length (list :length length))
303                   :null-terminated-p ,null-terminated-p
304                   :external-format (list ,ife :eol-style :lf))
305                  (fast-native-to-string ,stored-obj ,length))))))
306
307   #+(or cmu scl)
308   (let ((stored-obj (gensym)))
309     `(let ((,stored-obj ,obj))
310        (if (null-pointer-p ,stored-obj)
311            nil
312            (cmucl-naturalize-cstring (alien:alien-sap ,stored-obj)
313                                      :length ,length
314                                      :null-terminated-p ,null-terminated-p))))
315   #+(and sbcl (not sb-unicode))
316   (let ((stored-obj (gensym)))
317     `(let ((,stored-obj ,obj))
318        (if (null-pointer-p ,stored-obj)
319            nil
320            (sbcl-naturalize-cstring (sb-alien:alien-sap ,stored-obj)
321                                     :length ,length
322                                     :null-terminated-p ,null-terminated-p))))
323
324   #+(and sbcl sb-unicode)
325   (let ((stored-obj (gensym "STR-"))
326         (fe (gensym "FE-"))
327         (ife (gensym "IFE-")))
328     `(let ((,stored-obj ,obj))
329        (if (null-pointer-p ,stored-obj)
330            nil
331            (let* ((,fe (or ,encoding *default-foreign-encoding*))
332                   (,ife (when ,fe (lookup-foreign-encoding ,fe))))
333              (sb-alien::c-string-to-string (sb-alien:alien-sap ,stored-obj)
334                                            (or ,ife sb-impl::*default-external-format* :latin-1)
335                                            'character)))))
336
337   #+(or openmcl digitool)
338   (let ((stored-obj (gensym "STR-"))
339         (fe (gensym "FE-")))
340     `(let ((,stored-obj ,obj))
341        (if (ccl:%null-ptr-p ,stored-obj)
342            nil
343            #+digitool
344            (ccl:%get-cstring
345             ,stored-obj 0
346             ,@(if length (list length) nil))
347            #+openmcl
348            (let ((,fe (or ,encoding *default-foreign-encoding*)))
349              (case ,fe
350                (:utf-8
351                 (ccl::%get-utf-8-cstring ,stored-obj))
352                (:ucs-2
353                 (ccl::%get-native-utf-16-cstring ,stored-obj))
354                (t
355                  ,@(if length
356                        `((ccl:%str-from-ptr ,stored-obj ,length))
357                        `((ccl:%get-cstring ,stored-obj)))))))))
358   )
359
360
361 (defmacro allocate-foreign-string (size &key (unsigned t))
362   (declare (ignorable unsigned))
363   #+ignore
364   (let ((array-def (gensym)))
365     `(let ((,array-def (list 'alien:array 'c-call:char ,size)))
366        (eval `(alien:cast (alien:make-alien ,,array-def)
367                           ,(if ,unsigned
368                                '(* (alien:unsigned 8))
369                              '(* (alien:signed 8)))))))
370
371   #+(or cmu scl)
372   `(alien:make-alien ,(if unsigned
373                              '(alien:unsigned 8)
374                              '(alien:signed 8))
375     ,size)
376
377   #+sbcl
378   `(sb-alien:make-alien ,(if unsigned
379                              '(sb-alien:unsigned 8)
380                              '(sb-alien:signed 8))
381     ,size)
382
383   #+lispworks
384   `(fli:allocate-foreign-object :type
385                                 ,(if unsigned
386                                      ''(:unsigned :char)
387                                    :char)
388                                 :nelems ,size)
389   #+allegro
390   `(ff:allocate-fobject :char :c ,size)
391   #+(or openmcl digitool)
392   `(new-ptr ,size)
393   )
394
395 (defun foreign-string-length (foreign-string)
396   #+allegro `(ff:foreign-strlen ,foreign-string)
397   #-allegro
398   `(loop with size = 0
399     until (char= (deref-array ,foreign-string '(:array :unsigned-char) size) #\Null)
400     do (incf size)
401     finally return size))
402
403
404 (defmacro with-foreign-string ((foreign-string lisp-string &optional encoding)
405                                &body body)
406   (let ((result (gensym))
407         (fe (gensym)))
408     `(let* ((,fe ,encoding)
409             (,foreign-string (convert-to-foreign-string ,lisp-string ,fe))
410             (,result (progn ,@body)))
411       (declare (dynamic-extent ,foreign-string))
412       (free-foreign-object ,foreign-string)
413       ,result)))
414
415 (defmacro with-foreign-strings (bindings &body body)
416   `(with-foreign-string ,(car bindings)
417     ,@(if (cdr bindings)
418           `((with-foreign-strings ,(cdr bindings) ,@body))
419           body)))
420
421 ;; Modified from CMUCL's source to handle non-null terminated strings
422 #+cmu
423 (defun cmucl-naturalize-cstring (sap &key length (null-terminated-p t))
424   (declare (type system:system-area-pointer sap))
425   (locally
426       (declare (optimize (speed 3) (safety 0)))
427     (let ((null-terminated-length
428            (when null-terminated-p
429              (loop
430                  for offset of-type fixnum upfrom 0
431                  until (zerop (system:sap-ref-8 sap offset))
432                  finally (return offset)))))
433       (if length
434           (if (and null-terminated-length
435                    (> (the fixnum length) (the fixnum null-terminated-length)))
436               (setq length null-terminated-length))
437         (setq length null-terminated-length)))
438     (let ((result (make-string length)))
439       (kernel:copy-from-system-area sap 0
440                                     result (* vm:vector-data-offset
441                                               vm:word-bits)
442                                     (* length vm:byte-bits))
443       result)))
444
445 #+scl
446 ;; kernel:copy-from-system-area doesn't work like it does on CMUCL or SBCL,
447 ;; so have to iteratively copy from sap
448 (defun cmucl-naturalize-cstring (sap &key length (null-terminated-p t))
449   (declare (type system:system-area-pointer sap))
450   (locally
451       (declare (optimize (speed 3) (safety 0)))
452     (let ((null-terminated-length
453            (when null-terminated-p
454              (loop
455                  for offset of-type fixnum upfrom 0
456                  until (zerop (system:sap-ref-8 sap offset))
457                  finally (return offset)))))
458       (if length
459           (if (and null-terminated-length
460                    (> (the fixnum length) (the fixnum null-terminated-length)))
461               (setq length null-terminated-length))
462         (setq length null-terminated-length)))
463     (let ((result (make-string length)))
464       (dotimes (i length)
465         (declare (type fixnum i))
466         (setf (char result i) (code-char (system:sap-ref-8 sap i))))
467       result)))
468
469 #+(and sbcl (not sb-unicode))
470 (defun sbcl-naturalize-cstring (sap &key length (null-terminated-p t))
471   (declare (type sb-sys:system-area-pointer sap)
472            (type (or null fixnum) length))
473   (locally
474    (declare (optimize (speed 3) (safety 0)))
475    (let ((null-terminated-length
476           (when null-terminated-p
477             (loop
478              for offset of-type fixnum upfrom 0
479              until (zerop (sb-sys:sap-ref-8 sap offset))
480              finally (return offset)))))
481      (if length
482          (if (and null-terminated-length
483                   (> (the fixnum length) (the fixnum null-terminated-length)))
484              (setq length null-terminated-length))
485        (setq length null-terminated-length)))
486    (let ((result (make-string length)))
487        (funcall *system-copy-fn* sap 0 result +system-copy-offset+
488                 (* length +system-copy-multiplier+))
489        result)))
490
491
492 (eval-when (:compile-toplevel :load-toplevel :execute)
493    (def-function "strlen"
494      ((str (* :unsigned-char)))
495      :returning :unsigned-int))
496
497 (def-type char-ptr-def (* :unsigned-char))
498
499 #+(or (and allegro (not ics)) (and lispworks (not lispworks5) (not lispworks6)))
500 (defun fast-native-to-string (s len)
501   (declare (optimize (speed 3) (space 0) (safety 0) (compilation-speed 0))
502            (type char-ptr-def s))
503   (let* ((len (or len (strlen s)))
504          (str (make-string len)))
505     (declare (fixnum len)
506              (type (simple-array #+lispworks base-char
507                                  #-lispworks (signed-byte 8) (*)) str))
508     (dotimes (i len str)
509       (setf (aref str i)
510         (uffi:deref-array s '(:array :char) i)))))
511
512 #+(or (and allegro ics) lispworks5 lispworks6)
513 (defun fast-native-to-string (s len)
514   (declare (optimize (speed 3) (space 0) (safety 0) (compilation-speed 0))
515            (type char-ptr-def s))
516   (let* ((len (or len (strlen s)))
517          (str (make-string len)))
518     (dotimes (i len str)
519       (setf (schar str i) (code-char (uffi:deref-array s '(:array :unsigned-byte) i))))))