Version 1.8.6: Standardize on :encoding keyword
[uffi.git] / src / strings.lisp
1 ;;;; -*- Mode: Lisp; Syntax: ANSI-Common-Lisp; Base: 10; Package: UFFI -*-
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   ;; FAST-NATIVE-TO-STRING (suprisingly) works just fine with UTF-8 multibyte character strings
274   ;; However, without knowledge of specific-encoding, the LENGTH call in FAST-NATIVE-TO-STRING
275   ;; may not be incorrect for some encodings/strings.
276   ;; This is a stop-gap until get tech support on why the below fails.
277   (let ((stored-obj (gensym "STR-")))
278     `(let ((,stored-obj ,obj))
279        (if (fli:null-pointer-p ,stored-obj)
280            nil
281            (fast-native-to-string ,stored-obj ,length))))
282   ;; Below code doesn't work on tesing with LW 6.0 testing with a UTF-8 string.
283   ;; fli:convert-from-foreign-string with :external-format of :UTF-8 doesn't
284   ;; properly code multibyte characters.
285 #|
286   (let ((stored-obj (gensym "STR-"))
287         (fe (gensym "FE-"))
288         (ife (gensym "IFE-")))
289     `(let ((,stored-obj ,obj))
290        (if (fli:null-pointer-p ,stored-obj)
291            nil
292            (let* ((,fe (or ,encoding *default-foreign-encoding*))
293                   (,ife (when ,fe (lookup-foreign-encoding ,fe))))
294              (if ,ife
295                  (fli:convert-from-foreign-string
296                   ,stored-obj
297                   ,@(when length (list :length length))
298                   :null-terminated-p ,null-terminated-p
299                   :external-format (list ,ife :eol-style :lf))
300                  (fast-native-to-string ,stored-obj ,length))))))
301 |#
302
303   #+(or cmu scl)
304   (let ((stored-obj (gensym)))
305     `(let ((,stored-obj ,obj))
306        (if (null-pointer-p ,stored-obj)
307            nil
308            (cmucl-naturalize-cstring (alien:alien-sap ,stored-obj)
309                                      :length ,length
310                                      :null-terminated-p ,null-terminated-p))))
311   #+(and sbcl (not sb-unicode))
312   (let ((stored-obj (gensym)))
313     `(let ((,stored-obj ,obj))
314        (if (null-pointer-p ,stored-obj)
315            nil
316            (sbcl-naturalize-cstring (sb-alien:alien-sap ,stored-obj)
317                                     :length ,length
318                                     :null-terminated-p ,null-terminated-p))))
319
320   #+(and sbcl sb-unicode)
321   (let ((stored-obj (gensym "STR-"))
322         (fe (gensym "FE-"))
323         (ife (gensym "IFE-")))
324     `(let ((,stored-obj ,obj))
325        (if (null-pointer-p ,stored-obj)
326            nil
327            (let* ((,fe (or ,encoding *default-foreign-encoding*))
328                   (,ife (when ,fe (lookup-foreign-encoding ,fe))))
329              (sb-alien::c-string-to-string (sb-alien:alien-sap ,stored-obj)
330                                            (or ,ife sb-impl::*default-external-format* :latin-1)
331                                            'character)))))
332
333   #+(or openmcl digitool)
334   (let ((stored-obj (gensym "STR-"))
335         (fe (gensym "FE-")))
336     `(let ((,stored-obj ,obj))
337        (if (ccl:%null-ptr-p ,stored-obj)
338            nil
339            #+digitool
340            (ccl:%get-cstring
341             ,stored-obj 0
342             ,@(if length (list length) nil))
343            #+openmcl
344            (let ((,fe (or ,encoding *default-foreign-encoding*)))
345              (case ,fe
346                (:utf-8
347                 (ccl::%get-utf-8-cstring ,stored-obj))
348                (:ucs-2
349                 (ccl::%get-native-utf-16-cstring ,stored-obj))
350                (t
351                  ,@(if length
352                        `((ccl:%str-from-ptr ,stored-obj ,length))
353                        `((ccl:%get-cstring ,stored-obj)))))))))
354   )
355
356
357 (defmacro allocate-foreign-string (size &key (unsigned t))
358   (declare (ignorable unsigned))
359   #+ignore
360   (let ((array-def (gensym)))
361     `(let ((,array-def (list 'alien:array 'c-call:char ,size)))
362        (eval `(alien:cast (alien:make-alien ,,array-def)
363                           ,(if ,unsigned
364                                '(* (alien:unsigned 8))
365                              '(* (alien:signed 8)))))))
366
367   #+(or cmu scl)
368   `(alien:make-alien ,(if unsigned
369                              '(alien:unsigned 8)
370                              '(alien:signed 8))
371     ,size)
372
373   #+sbcl
374   `(sb-alien:make-alien ,(if unsigned
375                              '(sb-alien:unsigned 8)
376                              '(sb-alien:signed 8))
377     ,size)
378
379   #+lispworks
380   `(fli:allocate-foreign-object :type
381                                 ,(if unsigned
382                                      ''(:unsigned :char)
383                                    :char)
384                                 :nelems ,size)
385   #+allegro
386   `(ff:allocate-fobject :char :c ,size)
387   #+(or openmcl digitool)
388   `(new-ptr ,size)
389   )
390
391 (defun foreign-string-length (foreign-string)
392   #+allegro `(ff:foreign-strlen ,foreign-string)
393   #-allegro
394   `(loop with size = 0
395     until (char= (deref-array ,foreign-string '(:array :unsigned-char) size) #\Null)
396     do (incf size)
397     finally return size))
398
399
400 (defmacro with-foreign-string ((foreign-string lisp-string &optional encoding)
401                                &body body)
402   (let ((result (gensym))
403         (fe (gensym)))
404     `(let* ((,fe ,encoding)
405             (,foreign-string (convert-to-foreign-string ,lisp-string ,fe))
406             (,result (progn ,@body)))
407       (declare (dynamic-extent ,foreign-string))
408       (free-foreign-object ,foreign-string)
409       ,result)))
410
411 (defmacro with-foreign-strings (bindings &body body)
412   `(with-foreign-string ,(car bindings)
413     ,@(if (cdr bindings)
414           `((with-foreign-strings ,(cdr bindings) ,@body))
415           body)))
416
417 ;; Modified from CMUCL's source to handle non-null terminated strings
418 #+cmu
419 (defun cmucl-naturalize-cstring (sap &key length (null-terminated-p t))
420   (declare (type system:system-area-pointer sap))
421   (locally
422       (declare (optimize (speed 3) (safety 0)))
423     (let ((null-terminated-length
424            (when null-terminated-p
425              (loop
426                  for offset of-type fixnum upfrom 0
427                  until (zerop (system:sap-ref-8 sap offset))
428                  finally (return offset)))))
429       (if length
430           (if (and null-terminated-length
431                    (> (the fixnum length) (the fixnum null-terminated-length)))
432               (setq length null-terminated-length))
433         (setq length null-terminated-length)))
434     (let ((result (make-string length)))
435       (kernel:copy-from-system-area sap 0
436                                     result (* vm:vector-data-offset
437                                               vm:word-bits)
438                                     (* length vm:byte-bits))
439       result)))
440
441 #+scl
442 ;; kernel:copy-from-system-area doesn't work like it does on CMUCL or SBCL,
443 ;; so have to iteratively copy from sap
444 (defun cmucl-naturalize-cstring (sap &key length (null-terminated-p t))
445   (declare (type system:system-area-pointer sap))
446   (locally
447       (declare (optimize (speed 3) (safety 0)))
448     (let ((null-terminated-length
449            (when null-terminated-p
450              (loop
451                  for offset of-type fixnum upfrom 0
452                  until (zerop (system:sap-ref-8 sap offset))
453                  finally (return offset)))))
454       (if length
455           (if (and null-terminated-length
456                    (> (the fixnum length) (the fixnum null-terminated-length)))
457               (setq length null-terminated-length))
458         (setq length null-terminated-length)))
459     (let ((result (make-string length)))
460       (dotimes (i length)
461         (declare (type fixnum i))
462         (setf (char result i) (code-char (system:sap-ref-8 sap i))))
463       result)))
464
465 #+(and sbcl (not sb-unicode))
466 (defun sbcl-naturalize-cstring (sap &key length (null-terminated-p t))
467   (declare (type sb-sys:system-area-pointer sap)
468            (type (or null fixnum) length))
469   (locally
470    (declare (optimize (speed 3) (safety 0)))
471    (let ((null-terminated-length
472           (when null-terminated-p
473             (loop
474              for offset of-type fixnum upfrom 0
475              until (zerop (sb-sys:sap-ref-8 sap offset))
476              finally (return offset)))))
477      (if length
478          (if (and null-terminated-length
479                   (> (the fixnum length) (the fixnum null-terminated-length)))
480              (setq length null-terminated-length))
481        (setq length null-terminated-length)))
482    (let ((result (make-string length)))
483        (funcall *system-copy-fn* sap 0 result +system-copy-offset+
484                 (* length +system-copy-multiplier+))
485        result)))
486
487
488 (eval-when (:compile-toplevel :load-toplevel :execute)
489    (def-function "strlen"
490      ((str (* :unsigned-char)))
491      :returning :unsigned-int))
492
493 (def-type char-ptr-def (* :unsigned-char))
494
495 #+(or (and allegro (not ics)) (and lispworks (not lispworks5) (not lispworks6)))
496 (defun fast-native-to-string (s len)
497   (declare (optimize (speed 3) (space 0) (safety 0) (compilation-speed 0))
498            (type char-ptr-def s))
499   (let* ((len (or len (strlen s)))
500          (str (make-string len)))
501     (declare (fixnum len)
502              (type (simple-array #+lispworks base-char
503                                  #-lispworks (signed-byte 8) (*)) str))
504     (dotimes (i len str)
505       (setf (aref str i)
506         (uffi:deref-array s '(:array :char) i)))))
507
508 #+(or (and allegro ics) lispworks5 lispworks6)
509 (defun fast-native-to-string (s len)
510   (declare (optimize (speed 3) (space 0) (safety 0) (compilation-speed 0))
511            (type char-ptr-def s))
512   (let* ((len (or len (strlen s)))
513          (str (make-string len)))
514     (dotimes (i len str)
515       (setf (schar str i) (code-char (uffi:deref-array s '(:array :unsigned-byte) i))))))