Subject: Re: gcc in Lisp
From: rpw3@rpw3.org (Rob Warnock)
Date: Thu, 12 Feb 2004 07:57:49 -0600
Newsgroups: comp.lang.lisp
Message-ID: <qg-dnRyg-4vAG7bdXTWc-g@speakeasy.net>
Alain Picard  <Alain.Picard-please-no-spam@memetrics.com> wrote:
+---------------
| Joe Marshall <prunesquallor@comcast.net> writes:
| > Allow me to echo this sentiment.  There are a few utilities that
| > you'll want to write, but once you have them, the rest becomes a lot
| > easier than you'd think.
| 
| Actually, since I spent the day "scripting in CL", as it was
| just too painful to try to remember all the @#$#@%! /bin/sh syntax,
| perhaps these common utilities should be packaged up and submitted,
| say, to CLIKI?  Heck, just posting them to this group would be
| a great start.
+---------------

Bear in mind that the utilities you end up needing/using are typically
*very* platform-specific [and by "platform" I include both the CL
implementation and the operating system] and application-specific.
But just to get us started, here's one small example...

ANSI CL provides no standard way to read a single TTY character without
echoing it or requiring a newline, something one often needs to do when
coding the usual "more"-like pager or "curses"-like menu interaction.
Here's one cut at it that works well enough for me in CMUCL-18e on
FreeBSD 4.x:

#| What "less" does:
s.c_lflag &= ~(ICANON|ECHO|ECHOE|ECHOK|ECHONL);
s.c_oflag |=  (OPOST|ONLCR|TAB3);
s.c_oflag &= ~(OCRNL|ONOCR|ONLRET);
s.c_cc[VMIN] = 1;
s.c_cc[VTIME] = 0;
|#

(use-package :alien)
(use-package :unix)

(defun read-char-no-echo-cbreak (&optional (stream *query-io*))
  (with-alien ((old (struct termios))
               (new (struct termios)))
    (let ((e0 (unix-tcgetattr 0 old))
          (e1 (unix-tcgetattr 0 new))
          (bits (logior tty-icanon tty-echo tty-echoe tty-echok tty-echonl)))
      (declare (ignorable e0 e1))
      (unwind-protect
          (progn
            (setf (slot new 'c-lflag) (logandc2 (slot old 'c-lflag) bits))
            (setf (deref (slot new 'c-cc) vmin) 1)
            (setf (deref (slot new 'c-cc) vtime) 0)
            (unix-tcsetattr 0 tcsadrain new)
            (read-char stream))

And a helper routine that uses it:

(defun space-or-q-p (&optional (prompt "[More... (q to quit)] "))
  (format *query-io* "~A" prompt)
  (force-output *query-io*)
  (let* ((c (read-char-no-echo-cbreak *query-io*))
         (bs (make-string (1+ (length prompt)) :initial-element #\backspace))
         (sp (make-string (1+ (length prompt)) :initial-element #\space)))
    (format *query-io* "~A~A~A" bs sp bs)	; Erase the prompt.
    (force-output *query-io*)
    (char-equal c #\q)))

which would be used in a "pager" something like this [untested fragment]:

	(loop for line = (get-next-line)
	      and line-num from 1
	  do (format t "~a~%" line)
	     (when (and (zerop (mod line-num page-length))
		        (space-or-q-p))
               (return (values))))

The above code conses excessively in a couple of places, and could
certainly be optimized and/or cleaned up further [e.g., LOAD-TIME-VALUE
in a couple of places would help].


-Rob

-----
Rob Warnock			<rpw3@rpw3.org>
627 26th Avenue			<URL:http://rpw3.org/>
San Mateo, CA 94403		(650)572-2607