0.8alpha.0.27:
[sbcl.git] / contrib / sb-simple-streams / internal.lisp
diff --git a/contrib/sb-simple-streams/internal.lisp b/contrib/sb-simple-streams/internal.lisp
new file mode 100644 (file)
index 0000000..4f79d4b
--- /dev/null
@@ -0,0 +1,425 @@
+;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Package: STREAM -*-
+
+;;; This code is in the public domain.
+
+;;; The cmucl implementation of simple-streams was done by Paul Foley,
+;;; who placed the code in the public domain.  Sbcl port by Rudi
+;;; Schlatte.
+
+(in-package "SB-SIMPLE-STREAMS")
+
+;;;
+;;; HELPER FUNCTIONS
+;;;
+
+;; All known stream flags.  Note that the position in the constant
+;; list is significant (cf. %flags below).
+(sb-int:defconstant-eqx +flag-bits+
+                        '(:simple       ; instance is valid
+                          :input :output ; direction
+                          :dual :string        ; type of stream
+                          :eof          ; latched EOF
+                          :dirty        ; output buffer needs write
+                          :interactive) ; interactive stream
+                        #'equal)
+
+(eval-when (:compile-toplevel :load-toplevel :execute)
+  (defun %flags (flags)
+    (loop for flag in flags
+         as pos = (position flag +flag-bits+)
+       when (eq flag :gray) do
+         (error "Gray streams not supported.")
+       if pos
+         sum (ash 1 pos) into bits
+       else
+         collect flag into unused
+      finally (when unused
+               (warn "Invalid stream instance flag~P: ~{~S~^, ~}"
+                     (length unused) unused))
+             (return bits))))
+
+;;; Setup an environment where sm, funcall-stm-handler and
+;;; funcall-stm-handler-2 are valid and efficient for a stream of type
+;;; class-name or for the stream argument (in which case the
+;;; class-name argument is ignored).  In nested with-stream-class
+;;; forms, the inner with-stream-class form must specify a stream
+;;; argument if the outer one specifies one, or the wrong object will
+;;; be accessed.
+(defmacro with-stream-class ((class-name &optional stream) &body body)
+  (if stream
+      (let ((stm (gensym "STREAM"))
+           (slt (gensym)))
+       `(let* ((,stm ,stream)
+               (,slt (sb-pcl::std-instance-slots ,stm)))
+          (declare (type ,class-name ,stm) (ignorable ,slt))
+          (macrolet ((sm (slot-name stream)
+                       (declare (ignore stream))
+                       (let ((slot-access (gethash slot-name
+                                                   *slot-access-functions*)))
+                         (cond ((sb-int:fixnump (cdr slot-access))
+                                 ;; Get value in nth slot
+                                `(the ,(car slot-access)
+                                   (sb-pcl::clos-slots-ref ,',slt
+                                                            ,(cdr slot-access))))
+                               (slot-access
+                                 ;; Call memorized function
+                                `(the ,(car slot-access) (,(cdr slot-access)
+                                                           ,',stm)))
+                               (t
+                                 ;; Use slot-value
+                                 `(slot-value ,',stm ',slot-name)))))
+                     (add-stream-instance-flags (stream &rest flags)
+                       (declare (ignore stream))
+                       `(setf (sm %flags ,',stm) (logior (sm %flags ,',stm)
+                                                         ,(%flags flags))))
+                     (remove-stream-instance-flags (stream &rest flags)
+                       (declare (ignore stream))
+                       `(setf (sm %flags ,',stm) (logandc2 (sm %flags ,',stm)
+                                                           ,(%flags flags))))
+                     (any-stream-instance-flags (stream &rest flags)
+                       (declare (ignore stream))
+                       `(not (zerop (logand (sm %flags ,',stm)
+                                            ,(%flags flags))))))
+            ,@body)))
+      `(macrolet ((sm (slot-name stream)
+                   (let ((slot-access (gethash slot-name
+                                               *slot-access-functions*)))
+                     (cond ((sb-int:fixnump (cdr slot-access))
+                            `(the ,(car slot-access)
+                               (sb-pcl::clos-slots-ref
+                                (sb-pcl::std-instance-slots ,stream)
+                                ,(cdr slot-access))))
+                           (slot-access
+                            `(the ,(car slot-access) (,(cdr slot-access)
+                                                       ,stream)))
+                           (t `(slot-value ,stream ',slot-name))))))
+        ,@body)))
+
+(defmacro sm (slot-name stream)
+  (let ((slot-access (gethash slot-name *slot-access-functions*)))
+    (warn "Using ~S macro outside ~S" 'sm 'with-stream-class)
+    (cond ((sb-int:fixnump (cdr slot-access))
+          `(the ,(car slot-access) (sb-pcl::clos-slots-ref
+                                    (sb-pcl::std-instance-slots ,stream)
+                                    ,(cdr slot-access))))
+         (slot-access
+          `(the ,(car slot-access) (,(cdr slot-access) ,stream)))
+         (t `(slot-value ,stream ',slot-name)))))
+
+(defmacro funcall-stm-handler (slot-name stream &rest args)
+  (let ((s (gensym)))
+    `(let ((,s ,stream))
+       (funcall (sm ,slot-name ,s) ,s ,@args))))
+
+(defmacro funcall-stm-handler-2 (slot-name arg1 stream &rest args)
+  (let ((s (gensym)))
+    `(let ((,s ,stream))
+       (funcall (sm ,slot-name ,s) ,arg1 ,s ,@args))))
+
+(defmacro add-stream-instance-flags (stream &rest flags)
+  "Set the given flag bits in STREAM."
+  (let ((s (gensym "STREAM")))
+    `(let ((,s ,stream))
+       (with-stream-class (simple-stream ,s)
+        (setf (sm %flags ,s) (logior (sm %flags ,s) ,(%flags flags)))))))
+
+(defmacro remove-stream-instance-flags (stream &rest flags)
+  "Clear the given flag bits in STREAM."
+  (let ((s (gensym "STREAM")))
+    `(let ((,s ,stream))
+       (with-stream-class (simple-stream ,s)
+        (setf (sm %flags ,s) (logandc2 (sm %flags ,s) ,(%flags flags)))))))
+
+(defmacro any-stream-instance-flags (stream &rest flags)
+  "Determine whether any one of the FLAGS is set in STREAM."
+  (let ((s (gensym "STREAM")))
+    `(let ((,s ,stream))
+       (with-stream-class (simple-stream ,s)
+        (not (zerop (logand (sm %flags ,s) ,(%flags flags))))))))
+
+
+(declaim (inline buffer-sap bref (setf bref) buffer-copy))
+
+(defun buffer-sap (thing &optional offset)
+  (declare (type simple-stream-buffer thing) (type (or fixnum null) offset)
+          (optimize (speed 3) (space 2) (debug 0) (safety 0)
+                    ;; Suppress the note about having to box up the return:
+                    (sb-ext:inhibit-warnings 3)))
+  (let ((sap (if (vectorp thing) (sb-sys:vector-sap thing) thing)))
+    (if offset (sb-sys:sap+ sap offset) sap)))
+
+(defun bref (buffer index)
+  (declare (type simple-stream-buffer buffer)
+          (type (integer 0 #.most-positive-fixnum) index))
+  (sb-sys:sap-ref-8 (buffer-sap buffer) index))
+
+(defun (setf bref) (octet buffer index)
+  (declare (type (unsigned-byte 8) octet)
+          (type simple-stream-buffer buffer)
+          (type (integer 0 #.most-positive-fixnum) index))
+  (setf (sb-sys:sap-ref-8 (buffer-sap buffer) index) octet))
+
+(defun buffer-copy (src soff dst doff length)
+  (declare (type simple-stream-buffer src dst)
+          (type fixnum soff doff length))
+  (sb-sys:without-gcing ;; is this necessary??
+   (sb-kernel:system-area-copy (buffer-sap src) (* soff 8)
+                               (buffer-sap dst) (* doff 8)
+                               (* length 8))))
+
+(defun allocate-buffer (size)
+  (if (= size sb-impl::bytes-per-buffer)
+      (sb-impl::next-available-buffer)
+      (make-array size :element-type '(unsigned-byte 8))))
+
+(defun free-buffer (buffer)
+  (when (not (vectorp buffer))
+    (push buffer sb-impl::*available-buffers*))
+  t)
+
+(defun %fd-open (pathname direction if-exists if-exists-given
+                         if-does-not-exist if-does-not-exist-given)
+  (declare (type pathname pathname)
+          (type (member :input :output :io :probe) direction)
+          (type (member :error :new-version :rename :rename-and-delete
+                        :overwrite :append :supersede nil) if-exists)
+          (type (member :error :create nil) if-does-not-exist))
+  (multiple-value-bind (input output mask)
+      (ecase direction
+       (:input (values t nil sb-unix:o_rdonly))
+       (:output (values nil t sb-unix:o_wronly))
+       (:io (values t t sb-unix:o_rdwr))
+       (:probe (values t nil sb-unix:o_rdonly)))
+    (declare (type sb-int:index mask))
+    (let ((name (cond ((sb-int:unix-namestring pathname input))
+                     ((and input (eq if-does-not-exist :create))
+                      (sb-int:unix-namestring pathname nil)))))
+      ;; Process if-exists argument if we are doing any output.
+      (cond (output
+            (unless if-exists-given
+              (setf if-exists
+                    (if (eq (pathname-version pathname) :newest)
+                        :new-version
+                        :error)))
+            (case if-exists
+              ((:error nil)
+               (setf mask (logior mask sb-unix:o_excl)))
+              ((:rename :rename-and-delete)
+               (setf mask (logior mask sb-unix:o_creat)))
+              ((:new-version :supersede)
+               (setf mask (logior mask sb-unix:o_trunc)))
+              (:append
+               (setf mask (logior mask sb-unix:o_append)))))
+           (t
+            (setf if-exists nil)))     ; :ignore-this-arg
+      (unless if-does-not-exist-given
+       (setf if-does-not-exist
+             (cond ((eq direction :input) :error)
+                   ((and output
+                         (member if-exists '(:overwrite :append)))
+                    :error)
+                   ((eq direction :probe)
+                    nil)
+                   (t
+                    :create))))
+      (if (eq if-does-not-exist :create)
+         (setf mask (logior mask sb-unix:o_creat)))
+
+      (let ((original (if (member if-exists
+                                  '(:rename :rename-and-delete))
+                          (sb-impl::pick-backup-name name)
+                          nil))
+           (delete-original (eq if-exists :rename-and-delete))
+           (mode #o666))
+       (when original
+         ;; We are doing a :rename or :rename-and-delete.
+         ;; Determine if the file already exists, make sure the original
+         ;; file is not a directory and keep the mode
+         (let ((exists
+                (and name
+                     (multiple-value-bind
+                           (okay err/dev inode orig-mode)
+                         (sb-unix:unix-stat name)
+                       (declare (ignore inode)
+                                (type (or sb-int:index null) orig-mode))
+                       (cond
+                         (okay
+                          (when (and output (= (logand orig-mode #o170000)
+                                               #o40000))
+                            (error 'sb-int:simple-file-error
+                                :pathname pathname
+                                :format-control
+                                "Cannot open ~S for output: Is a directory."
+                                :format-arguments (list name)))
+                          (setf mode (logand orig-mode #o777))
+                          t)
+                         ((eql err/dev sb-unix:enoent)
+                          nil)
+                         (t
+                          (error 'sb-int:simple-file-error
+                                 :pathname pathname
+                                 :format-control "Cannot find ~S: ~A"
+                                 :format-arguments
+                                   (list name
+                                     (sb-int:strerror err/dev)))))))))
+           (unless (and exists
+                        (rename-file name original))
+             (setf original nil)
+             (setf delete-original nil)
+             ;; In order to use SUPERSEDE instead, we have
+             ;; to make sure unix:o_creat corresponds to
+             ;; if-does-not-exist.  unix:o_creat was set
+             ;; before because of if-exists being :rename.
+             (unless (eq if-does-not-exist :create)
+               (setf mask (logior (logandc2 mask sb-unix:o_creat)
+                                  sb-unix:o_trunc)))
+             (setf if-exists :supersede))))
+       
+       ;; Okay, now we can try the actual open.
+       (loop
+         (multiple-value-bind (fd errno)
+             (if name
+                 (sb-unix:unix-open name mask mode)
+                 (values nil sb-unix:enoent))
+           (cond ((sb-int:fixnump fd)
+                  (return (values fd name original delete-original)))
+                 ((eql errno sb-unix:enoent)
+                  (case if-does-not-exist
+                    (:error
+                      (cerror "Return NIL."
+                              'sb-int:simple-file-error
+                              :pathname pathname
+                              :format-control "Error opening ~S, ~A."
+                              :format-arguments
+                                  (list pathname
+                                        (sb-int:strerror errno))))
+                    (:create
+                      (cerror "Return NIL."
+                              'sb-int:simple-file-error
+                              :pathname pathname
+                              :format-control
+                                  "Error creating ~S, path does not exist."
+                              :format-arguments (list pathname))))
+                  (return nil))
+                 ((eql errno sb-unix:eexist)
+                  (unless (eq nil if-exists)
+                    (cerror "Return NIL."
+                            'sb-int:simple-file-error
+                            :pathname pathname
+                            :format-control "Error opening ~S, ~A."
+                            :format-arguments
+                                (list pathname
+                                      (sb-int:strerror errno))))
+                  (return nil))
+                  #+nil ; FIXME: reinstate this; error reporting is nice.
+                 ((eql errno sb-unix:eacces)
+                  (cerror "Try again."
+                          'sb-int:simple-file-error
+                          :pathname pathname
+                          :format-control "Error opening ~S, ~A."
+                          :format-arguments
+                              (list pathname
+                                    (sb-int:strerror errno))))
+                 (t
+                  (cerror "Return NIL."
+                          'sb-int:simple-file-error
+                          :pathname pathname
+                          :format-control "Error opening ~S, ~A."
+                          :format-arguments
+                              (list pathname
+                                    (sb-int:strerror errno)))
+                  (return nil)))))))))
+
+(defun open-fd-stream (pathname &key (direction :input)
+                               (element-type 'base-char)
+                               (if-exists nil if-exists-given)
+                               (if-does-not-exist nil if-does-not-exist-given)
+                               (external-format :default))
+  (declare (type (or pathname string stream) pathname)
+          (type (member :input :output :io :probe) direction)
+          (type (member :error :new-version :rename :rename-and-delete
+                        :overwrite :append :supersede nil) if-exists)
+          (type (member :error :create nil) if-does-not-exist)
+          (ignore external-format))
+  (setq pathname (pathname pathname))
+  (multiple-value-bind (fd namestring original delete-original)
+      (%fd-open pathname direction if-exists if-exists-given
+               if-does-not-exist if-does-not-exist-given)
+    (when fd
+      (case direction
+       ((:input :output :io)
+        (sb-sys:make-fd-stream fd
+                                :input (member direction '(:input :io))
+                                :output (member direction '(:output :io))
+                                :element-type element-type
+                                :file namestring
+                                :original original
+                                :delete-original delete-original
+                                :pathname pathname
+                                :input-buffer-p t
+                                :auto-close t))
+       (:probe
+        (let ((stream (sb-impl::%make-fd-stream :name namestring :fd fd
+                                                 :pathname pathname
+                                                 :element-type element-type)))
+          (close stream)
+          stream))))))
+
+
+;; Make PATHNAME and NAMESTRING work
+(defun cl::file-name (stream &optional new-name)
+  (typecase stream
+    (file-simple-stream
+     (with-stream-class (file-simple-stream stream)
+       (cond (new-name
+             (setf (sm pathname stream) new-name)
+             (setf (sm filename stream) (sb-int:unix-namestring new-name nil))
+             t)
+            (t
+             (sm pathname stream)))))
+    (sb-sys::file-stream
+     (cond (new-name
+           (setf (sb-impl::fd-stream-pathname stream) new-name)
+           (setf (sb-impl::fd-stream-file stream)
+                 (sb-int:unix-namestring new-name nil))
+           t)
+          (t
+           (sb-impl::fd-stream-pathname stream))))))
+
+;; Experimental "filespec" stuff
+
+;; sat: Hooks to parse URIs etc apparently go here
+
+(defstruct (filespec-parser
+            (:constructor make-filespec-parser (name priority function)))
+  name
+  priority
+  function)
+
+(defvar *filespec-parsers* ())
+
+(defun add-filespec (name priority function)
+  (let ((filespec (make-filespec-parser name priority function)))
+    (setf *filespec-parsers*
+         (stable-sort (cons filespec (delete name *filespec-parsers*
+                                             :key #'filespec-parser-name))
+                      #'>
+                      :key #'filespec-parser-priority)))
+  t)
+
+(defmacro define-filespec (name lambda-list &body body)
+  (let ((truename (if (consp name) (first name) name))
+       (priority (if (consp name) (second name) 0)))
+    `(add-filespec ',truename ,priority (lambda ,lambda-list
+                                         (block ,truename
+                                           ,@body)))))
+
+(defun parse-filespec (string &optional (errorp t))
+  (dolist (i *filespec-parsers* (when errorp
+                                 (error "~S not recognised." string)))
+    (let ((result (ignore-errors
+                   (funcall (filespec-parser-function i) string))))
+      (when result (return result)))))
+
+(define-filespec pathname (string)
+  (pathname string))