It is occasionally handy for programs that process input files to
count the number of characters and lines seen so far, and the number
of characters seen on the current line, so that useful messages may be
reported in case of parsing errors, etc. Here is a character input
stream class that keeps track of these counts. Note that all
character input streams must implement stream-read-char
and
stream-unread-char
.
(defclass wrapped-stream (fundamental-stream) ((stream :initarg :stream :reader stream-of))) (defmethod stream-element-type ((stream wrapped-stream)) (stream-element-type (stream-of stream))) (defmethod close ((stream wrapped-stream) &key abort) (close (stream-of stream) :abort abort)) (defclass wrapped-character-input-stream (wrapped-stream fundamental-character-output-stream) ()) (defmethod stream-read-char ((stream wrapped-character-input-stream)) (read-char (stream-of stream) nil :eof)) (defmethod stream-unread-char ((stream wrapped-character-input-stream) char) (unread-char char (stream-of stream))) (defclass counting-character-input-stream (wrapped-character-input-stream) ((char-count :initform 1 :accessor char-count-of) (line-count :initform 1 :accessor line-count-of) (col-count :initform 1 :accessor col-count-of) (prev-col-count :initform 1 :accessor prev-col-count-of))) (defmethod stream-read-char ((stream counting-character-input-stream)) (with-accessors ((inner-stream stream-of) (chars char-count-of) (lines line-count-of) (cols col-count-of) (prev prev-col-count-of)) stream (let ((char (call-next-method))) (cond ((eql char :eof) :eof) ((char= char #\Newline) (incf lines) (incf chars) (setf prev cols) (setf cols 1) char) (t (incf chars) (incf cols) char))))) (defmethod stream-unread-char ((stream counting-character-input-stream) char) (with-accessors ((inner-stream stream-of) (chars char-count-of) (lines line-count-of) (cols col-count-of) (prev prev-col-count-of)) stream (cond ((char= char #\Newline) (decf lines) (decf chars) (setf cols prev)) (t (decf chars) (decf cols) char)) (call-next-method)))
The default methods for stream-read-char-no-hang
,
stream-peek-char
, stream-listen
,
stream-clear-input
, stream-read-line
, and
stream-read-sequence
should be sufficient (though the last two
will probably be slower than methods that forwarded directly).
Here's a sample use of this class:
(with-input-from-string (input "1 2 3 :foo ") (let ((counted-stream (make-instance 'counting-character-input-stream :stream input))) (loop for thing = (read counted-stream) while thing unless (numberp thing) do (error "Non-number ~S (line ~D, column ~D)" thing (line-count-of counted-stream) (- (col-count-of counted-stream) (length (format nil "~S" thing)))) end do (print thing))))1 2 3 Non-number :FOO (line 2, column 5) [Condition of type SIMPLE-ERROR]