Reputation: 4348
In Common Lisp, is there an easy way to read a single whitespace-delimited word from an input stream? Essentially I'm looking for something equivalent to C's scanf("%s", somevar);
.
I did come up with the following:
(defun read-word-from-stream (in)
(peek-char t in) ; skip initial whitespace
(do ((str (make-array 16 :fill-pointer 0 :element-type 'standard-char :adjustable t)
(progn (vector-push-extend (read-char in) str) str)))
((let ((c (peek-char nil in)))
(or (char= c #\Newline) (char= c #\Space))) str)))
...which, while functional for my limited needs, feels a bit kludgey for an operation so simple. Ideally I would have a method do it for me, but what would be the cleanest and shortest correct way to do this, using any Common Lisp libraries available (preferably something that works with flexi-streams)?
Upvotes: 4
Views: 2447
Reputation: 85913
Though I posted in a comment that there's no standard way to do this, in part because there's no universal concept of whitespace. (Your version includes Space and Newline, but what about Tab, Vertical Tab, Carriage Return, etc.?) That said, your use of peek-char reminded me that peek-char takes an optional peek-type argument that indicates whether whitespace should be skipped or not. If you use both types of peeks, then when they disagree, you must have hit a whitespace character. That means that you can read up until a whitespace character (where the exact meaning of whitespace character is determined by the implementation) with a function like this:
(defun read-string (&optional (stream *standard-input*))
(loop
for c = (peek-char nil stream nil nil) ; include whitespace
while (and c (eql c (peek-char t stream nil nil))) ; skip whitespace
collect (read-char stream) into letters
finally (return (coerce letters 'string))))
CL-USER> (read-string)
this is some input
"this"
I used the (coerce letters 'string) to get a string here, but you could also use with-output-to-string:
(defun read-string (&optional (stream *standard-input*))
(with-output-to-string (out)
(loop
for c = (peek-char nil stream nil nil)
while (and c (eql c (peek-char t stream nil nil)))
do (write-char (read-char stream) out))))
CL-USER> (read-string)
some more input
"some"
The glossary entry for whitespace says:
whitespace n. 1. one or more characters that are either the graphic character #\Space or else non-graphic characters such as #\Newline that only move the print position. 2. a. n. the syntax type of a character that is a token separator. For details, see Section 2.1.4.7 (Whitespace Characters). b. adj. (of a character) having the whitespace[2a] syntax type[2]. c. n. a whitespace[2b] character.
Based on the first definition, it's easy to define a rough approximation (this doesn't check the print position; I'm not sure if there's a portable way to check that):
(defun whitespace-char-p (x)
(or (char= #\space x)
(not (graphic-char-p x))))
Then it's easy to do:
(defun read-until (test &optional (stream *standard-input*))
(with-output-to-string (out)
(loop for c = (peek-char nil stream nil nil)
while (and c (not (funcall test c)))
do (write-char (read-char stream) out))))
CL-USER> (read-until 'whitespace-char-p)
this is some input
"this"
Upvotes: 7