strings.lisp - clic - Clic is an command line interactive client for gopher written in Common LISP
 (HTM) git clone git://bitreich.org/clic/ git://enlrupgkhuxnvlhsf6lc3fziv5h2hhfrinws65d7roiv6bfj7d652fid.onion/clic/
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) Tags
 (DIR) README
 (DIR) LICENSE
       ---
       strings.lisp (13274B)
       ---
            1 ;;;; -*- Mode: lisp; indent-tabs-mode: nil -*-
            2 ;;;
            3 ;;; strings.lisp --- Operations on foreign strings.
            4 ;;;
            5 ;;; Copyright (C) 2005-2006, James Bielman  <jamesjb@jamesjb.com>
            6 ;;; Copyright (C) 2005-2007, Luis Oliveira  <loliveira@common-lisp.net>
            7 ;;;
            8 ;;; Permission is hereby granted, free of charge, to any person
            9 ;;; obtaining a copy of this software and associated documentation
           10 ;;; files (the "Software"), to deal in the Software without
           11 ;;; restriction, including without limitation the rights to use, copy,
           12 ;;; modify, merge, publish, distribute, sublicense, and/or sell copies
           13 ;;; of the Software, and to permit persons to whom the Software is
           14 ;;; furnished to do so, subject to the following conditions:
           15 ;;;
           16 ;;; The above copyright notice and this permission notice shall be
           17 ;;; included in all copies or substantial portions of the Software.
           18 ;;;
           19 ;;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
           20 ;;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
           21 ;;; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
           22 ;;; NONINFRINGEMENT.  IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
           23 ;;; HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
           24 ;;; WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
           25 ;;; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
           26 ;;; DEALINGS IN THE SOFTWARE.
           27 ;;;
           28 
           29 (in-package #:cffi)
           30 
           31 ;;;# Foreign String Conversion
           32 ;;;
           33 ;;; Functions for converting NULL-terminated C-strings to Lisp strings
           34 ;;; and vice versa.  The string functions accept an ENCODING keyword
           35 ;;; argument which is used to specify the encoding to use when
           36 ;;; converting to/from foreign strings.
           37 
           38 (defvar *default-foreign-encoding* :utf-8
           39   "Default foreign encoding.")
           40 
           41 ;;; TODO: refactor, sigh.  Also, this should probably be a function.
           42 (defmacro bget (ptr off &optional (bytes 1) (endianness :ne))
           43   (let ((big-endian (member endianness
           44                             '(:be #+big-endian :ne #+little-endian :re))))
           45     (once-only (ptr off)
           46       (ecase bytes
           47         (1 `(mem-ref ,ptr :uint8 ,off))
           48         (2 (if big-endian
           49                #+big-endian
           50                `(mem-ref ,ptr :uint16 ,off)
           51                #-big-endian
           52                `(dpb (mem-ref ,ptr :uint8 ,off) (byte 8 8)
           53                      (mem-ref ,ptr :uint8 (1+ ,off)))
           54                #+little-endian
           55                `(mem-ref ,ptr :uint16 ,off)
           56                #-little-endian
           57                `(dpb (mem-ref ,ptr :uint8 (1+ ,off)) (byte 8 8)
           58                      (mem-ref ,ptr :uint8 ,off))))
           59         (4 (if big-endian
           60                #+big-endian
           61                `(mem-ref ,ptr :uint32 ,off)
           62                #-big-endian
           63                `(dpb (mem-ref ,ptr :uint8 ,off) (byte 8 24)
           64                      (dpb (mem-ref ,ptr :uint8 (1+ ,off)) (byte 8 16)
           65                           (dpb (mem-ref ,ptr :uint8 (+ ,off 2)) (byte 8 8)
           66                                (mem-ref ,ptr :uint8 (+ ,off 3)))))
           67                #+little-endian
           68                `(mem-ref ,ptr :uint32 ,off)
           69                #-little-endian
           70                `(dpb (mem-ref ,ptr :uint8 (+ ,off 3)) (byte 8 24)
           71                      (dpb (mem-ref ,ptr :uint8 (+ ,off 2)) (byte 8 16)
           72                           (dpb (mem-ref ,ptr :uint8 (1+ ,off)) (byte 8 8)
           73                                (mem-ref ,ptr :uint8 ,off))))))))))
           74 
           75 (defmacro bset (val ptr off &optional (bytes 1) (endianness :ne))
           76   (let ((big-endian (member endianness
           77                             '(:be #+big-endian :ne #+little-endian :re))))
           78     (ecase bytes
           79       (1 `(setf (mem-ref ,ptr :uint8 ,off) ,val))
           80       (2 (if big-endian
           81              #+big-endian
           82              `(setf (mem-ref ,ptr :uint16 ,off) ,val)
           83              #-big-endian
           84              `(setf (mem-ref ,ptr :uint8 (1+ ,off)) (ldb (byte 8 0) ,val)
           85                     (mem-ref ,ptr :uint8 ,off) (ldb (byte 8 8) ,val))
           86              #+little-endian
           87              `(setf (mem-ref ,ptr :uint16 ,off) ,val)
           88              #-little-endian
           89              `(setf (mem-ref ,ptr :uint8 ,off) (ldb (byte 8 0) ,val)
           90                     (mem-ref ,ptr :uint8 (1+ ,off)) (ldb (byte 8 8) ,val))))
           91       (4 (if big-endian
           92              #+big-endian
           93              `(setf (mem-ref ,ptr :uint32 ,off) ,val)
           94              #-big-endian
           95              `(setf (mem-ref ,ptr :uint8 (+ 3 ,off)) (ldb (byte 8 0) ,val)
           96                     (mem-ref ,ptr :uint8 (+ 2 ,off)) (ldb (byte 8 8) ,val)
           97                     (mem-ref ,ptr :uint8 (1+ ,off)) (ldb (byte 8 16) ,val)
           98                     (mem-ref ,ptr :uint8 ,off) (ldb (byte 8 24) ,val))
           99              #+little-endian
          100              `(setf (mem-ref ,ptr :uint32 ,off) ,val)
          101              #-little-endian
          102              `(setf (mem-ref ,ptr :uint8 ,off) (ldb (byte 8 0) ,val)
          103                     (mem-ref ,ptr :uint8 (1+ ,off)) (ldb (byte 8 8) ,val)
          104                     (mem-ref ,ptr :uint8 (+ ,off 2)) (ldb (byte 8 16) ,val)
          105                     (mem-ref ,ptr :uint8 (+ ,off 3)) (ldb (byte 8 24) ,val)))))))
          106 
          107 ;;; TODO: tackle optimization notes.
          108 (defparameter *foreign-string-mappings*
          109   (instantiate-concrete-mappings
          110    ;; :optimize ((speed 3) (debug 0) (compilation-speed 0) (safety 0))
          111    :octet-seq-getter bget
          112    :octet-seq-setter bset
          113    :octet-seq-type foreign-pointer
          114    :code-point-seq-getter babel::string-get
          115    :code-point-seq-setter babel::string-set
          116    :code-point-seq-type babel:simple-unicode-string))
          117 
          118 (defun null-terminator-len (encoding)
          119   (length (enc-nul-encoding (get-character-encoding encoding))))
          120 
          121 (defun lisp-string-to-foreign (string buffer bufsize &key (start 0) end offset
          122                                (encoding *default-foreign-encoding*))
          123   (check-type string string)
          124   (when offset
          125     (setq buffer (inc-pointer buffer offset)))
          126   (with-checked-simple-vector ((string (coerce string 'babel:unicode-string))
          127                                (start start) (end end))
          128     (declare (type simple-string string))
          129     (let ((mapping (lookup-mapping *foreign-string-mappings* encoding))
          130           (nul-len (null-terminator-len encoding)))
          131       (assert (plusp bufsize))
          132       (multiple-value-bind (size end)
          133           (funcall (octet-counter mapping) string start end (- bufsize nul-len))
          134         (funcall (encoder mapping) string start end buffer 0)
          135         (dotimes (i nul-len)
          136           (setf (mem-ref buffer :char (+ size i)) 0))))
          137     buffer))
          138 
          139 ;;; Expands into a loop that calculates the length of the foreign
          140 ;;; string at PTR plus OFFSET, using ACCESSOR and looking for a null
          141 ;;; terminator of LENGTH bytes.
          142 (defmacro %foreign-string-length (ptr offset type length)
          143   (once-only (ptr offset)
          144     `(do ((i 0 (+ i ,length)))
          145          ((zerop (mem-ref ,ptr ,type (+ ,offset i))) i)
          146        (declare (fixnum i)))))
          147 
          148 ;;; Return the length in octets of the null terminated foreign string
          149 ;;; at POINTER plus OFFSET octets, assumed to be encoded in ENCODING,
          150 ;;; a CFFI encoding.  This should be smart enough to look for 8-bit vs
          151 ;;; 16-bit null terminators, as appropriate for the encoding.
          152 (defun foreign-string-length (pointer &key (encoding *default-foreign-encoding*)
          153                               (offset 0))
          154   (ecase (null-terminator-len encoding)
          155     (1 (%foreign-string-length pointer offset :uint8 1))
          156     (2 (%foreign-string-length pointer offset :uint16 2))
          157     (4 (%foreign-string-length pointer offset :uint32 4))))
          158 
          159 (defun foreign-string-to-lisp (pointer &key (offset 0) count
          160                                (max-chars (1- array-total-size-limit))
          161                                (encoding *default-foreign-encoding*))
          162   "Copy at most COUNT bytes from POINTER plus OFFSET encoded in
          163 ENCODING into a Lisp string and return it.  If POINTER is a null
          164 pointer, NIL is returned."
          165   (unless (null-pointer-p pointer)
          166     (let ((count (or count
          167                      (foreign-string-length
          168                       pointer :encoding encoding :offset offset)))
          169           (mapping (lookup-mapping *foreign-string-mappings* encoding)))
          170       (assert (plusp max-chars))
          171       (multiple-value-bind (size new-end)
          172           (funcall (code-point-counter mapping)
          173                    pointer offset (+ offset count) max-chars)
          174         (let ((string (make-string size :element-type 'babel:unicode-char)))
          175           (funcall (decoder mapping) pointer offset new-end string 0)
          176           (values string (- new-end offset)))))))
          177 
          178 ;;;# Using Foreign Strings
          179 
          180 (defun foreign-string-alloc (string &key (encoding *default-foreign-encoding*)
          181                              (null-terminated-p t) (start 0) end)
          182   "Allocate a foreign string containing Lisp string STRING.
          183 The string must be freed with FOREIGN-STRING-FREE."
          184   (check-type string string)
          185   (with-checked-simple-vector ((string (coerce string 'babel:unicode-string))
          186                                (start start) (end end))
          187     (declare (type simple-string string))
          188     (let* ((mapping (lookup-mapping *foreign-string-mappings* encoding))
          189            (count (funcall (octet-counter mapping) string start end 0))
          190            (nul-length (if null-terminated-p
          191                            (null-terminator-len encoding)
          192                            0))
          193            (length (+ count nul-length))
          194            (ptr (foreign-alloc :char :count length)))
          195       (funcall (encoder mapping) string start end ptr 0)
          196       (dotimes (i nul-length)
          197         (setf (mem-ref ptr :char (+ count i)) 0))
          198       (values ptr length))))
          199 
          200 (defun foreign-string-free (ptr)
          201   "Free a foreign string allocated by FOREIGN-STRING-ALLOC."
          202   (foreign-free ptr))
          203 
          204 (defmacro with-foreign-string ((var-or-vars lisp-string &rest args) &body body)
          205   "VAR-OR-VARS is not evaluated and should be a list of the form
          206 \(VAR &OPTIONAL BYTE-SIZE-VAR) or just a VAR symbol.  VAR is
          207 bound to a foreign string containing LISP-STRING in BODY.  When
          208 BYTE-SIZE-VAR is specified then bind the C buffer size
          209 \(including the possible null terminator\(s)) to this variable."
          210   (destructuring-bind (var &optional size-var)
          211       (ensure-list var-or-vars)
          212     `(multiple-value-bind (,var ,@(when size-var (list size-var)))
          213          (foreign-string-alloc ,lisp-string ,@args)
          214        (unwind-protect
          215             (progn ,@body)
          216          (foreign-string-free ,var)))))
          217 
          218 (defmacro with-foreign-strings (bindings &body body)
          219   "See WITH-FOREIGN-STRING's documentation."
          220   (if bindings
          221       `(with-foreign-string ,(first bindings)
          222          (with-foreign-strings ,(rest bindings)
          223            ,@body))
          224       `(progn ,@body)))
          225 
          226 (defmacro with-foreign-pointer-as-string
          227     ((var-or-vars size &rest args) &body body)
          228   "VAR-OR-VARS is not evaluated and should be a list of the form
          229 \(VAR &OPTIONAL SIZE-VAR) or just a VAR symbol.  VAR is bound to
          230 a foreign buffer of size SIZE within BODY.  The return value is
          231 constructed by calling FOREIGN-STRING-TO-LISP on the foreign
          232 buffer along with ARGS." ; fix wording, sigh
          233   (destructuring-bind (var &optional size-var)
          234       (ensure-list var-or-vars)
          235     `(with-foreign-pointer (,var ,size ,size-var)
          236        (progn
          237          ,@body
          238          (values (foreign-string-to-lisp ,var ,@args))))))
          239 
          240 ;;;# Automatic Conversion of Foreign Strings
          241 
          242 (define-foreign-type foreign-string-type ()
          243   (;; CFFI encoding of this string.
          244    (encoding :initform nil :initarg :encoding :reader encoding)
          245    ;; Should we free after translating from foreign?
          246    (free-from-foreign :initarg :free-from-foreign
          247                       :reader fst-free-from-foreign-p
          248                       :initform nil :type boolean)
          249    ;; Should we free after translating to foreign?
          250    (free-to-foreign :initarg :free-to-foreign
          251                     :reader fst-free-to-foreign-p
          252                     :initform t :type boolean))
          253   (:actual-type :pointer)
          254   (:simple-parser :string))
          255 
          256 ;;; describe me
          257 (defun fst-encoding (type)
          258   (or (encoding type) *default-foreign-encoding*))
          259 
          260 ;;; Display the encoding when printing a FOREIGN-STRING-TYPE instance.
          261 (defmethod print-object ((type foreign-string-type) stream)
          262   (print-unreadable-object (type stream :type t)
          263     (format stream "~S" (fst-encoding type))))
          264 
          265 (defmethod translate-to-foreign ((s string) (type foreign-string-type))
          266   (values (foreign-string-alloc s :encoding (fst-encoding type))
          267           (fst-free-to-foreign-p type)))
          268 
          269 (defmethod translate-to-foreign (obj (type foreign-string-type))
          270   (cond
          271     ((pointerp obj)
          272      (values obj nil))
          273     ;; FIXME: we used to support UB8 vectors but not anymore.
          274     ;; ((typep obj '(array (unsigned-byte 8)))
          275     ;;  (values (foreign-string-alloc obj) t))
          276     (t (error "~A is not a Lisp string or pointer." obj))))
          277 
          278 (defmethod translate-from-foreign (ptr (type foreign-string-type))
          279   (unwind-protect
          280        (values (foreign-string-to-lisp ptr :encoding (fst-encoding type)))
          281     (when (fst-free-from-foreign-p type)
          282       (foreign-free ptr))))
          283 
          284 (defmethod free-translated-object (ptr (type foreign-string-type) free-p)
          285   (when free-p
          286     (foreign-string-free ptr)))
          287 
          288 (defmethod expand-to-foreign-dyn-indirect
          289     (value var body (type foreign-string-type))
          290   (alexandria:with-gensyms (str)
          291     (expand-to-foreign-dyn
          292      value
          293      str
          294      (list 
          295       (expand-to-foreign-dyn-indirect str var body (parse-type :pointer)))
          296      type)))
          297 
          298 ;;;# STRING+PTR
          299 
          300 (define-foreign-type foreign-string+ptr-type (foreign-string-type)
          301   ()
          302   (:simple-parser :string+ptr))
          303 
          304 (defmethod translate-from-foreign (value (type foreign-string+ptr-type))
          305   (list (call-next-method) value))