Common Lisp skips whitespace, and skips whitespace

It's well known to Lispers that Common Lisp's library is remarkably complete in some places, and remarkably incomplete in others. I ran into both cases yesterday. I needed to see what was behind some whitespace, so I did the obvious thing:

(defun skip-whitespace (stream &optional eof-error-p)
  "Skip leading whitespace on STREAM, and return the first
non-whitespace character without removing it."
  (let ((c (peek-char stream eof-error-p)))
    (cond ((whitespacep c) (read-char stream eof-error-p)
                           (skip-whitespace stream eof-error-p))
          (t c))))

There are two problems with this code. The first is that whitespacep is missing. Did I get the name wrong? No, it simply doesn't exist. Common Lisp has graphic-char-p, alpha-char-p, digit-char-p, alphanumericp, standard-char-p, upper-case-p, lower-case-p, and even both-case-p (which is not quite what it sounds like), but nothing for whitespace. And you can't even easily assemble it out of the available predicates.

Oh well, it's easy to fake.

(defun whitespacep (c)
  (member c '(#\  #\Tab #\Return #\Newline)))

The second problem was a mysterious type error: peek-char didn't like *standard-input*; it wanted a character or a boolean instead. Huh?

If I had bothered to read the Hyperspec (or even look closely at the argument list Slime displayed), I would have known that peek-char's first argument isn't the stream, it's the whitespace setting. The function I wanted is already built in to the language: (peek-char t stream) skips whitespace and returns the first non-whitespace character. Obviously I'm not the first person to want this. peek-char may not be the most obvious place to put this feature, but this is one of those places where Common Lisp is almost absurdly complete. It may not be able to identify whitespace characters, but it knows what to do with them.

Edit: fixed an uninteresting third problem: I forgot to pass eof-error-p to peek-char.

4 comments:

  1. Common Lisp? Don't program recursive functions like that in Common Lisp. That would work in Scheme - but not in plain Common Lisp. Tail Calls are not optimized - only in implementations using the compiler with some compilation settings... The general style rule is to avoid this (unless you have a special demand for it and knwo what you are doing).

    ReplyDelete
  2. Oops! SBCL optimizes tail-calls, so I was OK, but this is a bad habit to get into. Well, a bad habit for CL - trouble is, it makes nice pseudocode...

    ReplyDelete
  3. Actually I find loops expressed as LOOPs to be clearer and easier to read. Try to write it as a LOOP and compare that with the tail-recursive version.

    ReplyDelete
  4. I agree in principle - recursion is unnecessarily general for loops. But neither the LOOP nor DO versions is very nice. Since Blogger doesn't like PRE, I'll respond in a post...

    ReplyDelete

It's OK to comment on old posts.