Subject: [clisp-list] macros for CLISP

From: Michael Graffam <[email-masked]>
Date: Tue Mar 9 06:35:05 2004

Hello everyone,

Is there a CLISP guru who would be willing to help me with getting some
macro code to work w/ CLISP?

I have taken some macro code from the Matlisp project (which uses special
FORTRAN-style matrices for LAPACK/BLAS) and ported it to use regular
Lisp arrays instead.

Specifically, the code in question is a macro so that [2 3 ; 4 5]
is read as #2A((2 3)(4 5)).

After crawling through the relevant code (without entirely
understanding how it works) I managed to get it working with CMUCL.

But what I really want it is to have it working in CLISP.

Basically, I'm trying to kludge together some various BSD-licensed pieces
of code to put together a small linear algebra package for Common
Lisp. I really like this feature of Matlisp, however, so I thought I'd
carry it along.

---

From: Pascal J.Bourguignon <[email-masked]>
Date: Tue Mar 9 06:45:05 2004

> Lisp. I really like this feature of Matlisp, however, so I thought I'd

It would be  easier for us to help  you if we could see  the source of
this "macro", don't you think?

Actually, it  must be a  macro-character function assigned to  the #\[
character. I  don't see any reason  why it should not  work equally on
two implementations of Common-Lisp, that's pretty standard stuff...

-- 
__Pascal_Bourguignon__                     http://www.informatimago.com/
There is no worse tyranny than to force a man to pay for what he doesn't
want merely because you think it would be good for him.--Robert Heinlein
http://www.theadvocates.org/

---

From: Pascal J.Bourguignon <[email-masked]>
Date: Tue Mar 9 11:34:03 2004

Michael Graffam writes:
> Why this works in CMUCL but not CLISP continues to elude me. There
> seems to be some special-casing for Allegro and such too. Is this code
> just poorly written, or is stream I/O this finicky in CL?

I  don't know but  your code  looks quite  complicated.  Let  the lisp
reader do its work, and avoid concatenating strings like that!

I don't know that a #\^m is.  The carriage return is #\return, and the
line feed is \#linefeed.  You could include the page feed too (#\page).

Here is what I would use for null-string-p (if I needed it):

(defun null-string-p (s)
  (or (= 0 (length s))
      (not (find-if (lambda (ch) (not (member ch +space-list+))) s))))

I don't  see any reason why to  use the nohang functions.  You are not
doing dynamic user interaction while reading a matrix are you?

> (return-from parse-matrix-expression-1
>                 (list 'make-matrix-from-reader 
>                       (list 'quote (list n m)) 
>                       :initial-contents `(list ,@matrix-1)))))

Macro characters are not macros!  You don't return an expression to be
evaluated, you return the data item read.  

The  following implementation works  as well  in clisp  as in  sbcl or
cmucl. You  would have to  add computing of  element type. It  is much
less complicated  to compute the  dimension while reading  the matrix,
otherwise the  list where I  store the data  could be mixed  with data
lists.

(defconstant +space-list+ '(#\newline #\Return #\Linefeed #\space #\tab #\page))
;; Note, usually, I prefer to keep characters in strings,
;; and using POSITION instead of MEMBER.
;; It's just harder to come with a string literal containing these characters.

(defun looking-at (stream char)
  (loop
   (let ((next-char (read-char stream nil nil t)))
     (cond
      ((null next-char)     (return-from looking-at nil))
      ((eql next-char char) (return-from looking-at t))
      ((member next-char +space-list+ :test (function eql)))
      (t (unread-char next-char stream)
         (return-from looking-at nil))))));;looking-at
;; You'll tell me how this looking-at works on allegro!

(defun parse-matrix-expression (stream char)
  "
Parses a vector or a matrix.
array    --> '[' contents ']' .
contents --> row { ';' row } .
row      --> item { item } .
"
  (declare (ignore char))
  (multiple-value-bind (dimensions contents)
      (let ((nitems nil)
            (nrow   0)
            (rows   '())
            (items  '()))
        (loop
         (cond
          ((or (looking-at stream #\;) (looking-at stream #\,))
           (if (null nitems)
             (setf nitems (length items))
             (when (/= nitems (length items))
               (error "Different number of items in ~:R row; ~
                        expected ~D, found ~D" nrow nitems (length items))))
           (push (nreverse items) rows)
           (incf nrow)
           (setf items '()))
          ((looking-at stream #\])
           (when items (push (nreverse items) rows) (incf nrow))
           (return))
          (t (push (read stream t nil t) items))))
        (if (null nitems)
          (values (list (length (car rows))) (car rows))
          (values (list nrow nitems)         (nreverse rows))))
    (make-array dimensions :initial-contents contents)))

(set-macro-character #\] (get-macro-character #\) ))
(set-macro-character #\[ (function parse-matrix-expression))
;; Remember to re-evaluate this set-macro-character call after
;; modifying parse-matrix-expression!

(with-input-from-string  (in "a b c ; d e f ; [ 1 2 ; 3 4 ] g h ] ")
  (parse-matrix-expression in #\[))
--> #2A((A B C) (D E F) (#2A((1 2) (3 4)) G H))

(read-from-string  "( a matrix [ a b c ; d e f ; [ 1 2 ; 3 4 ] g h ] )")
--> (A MATRIX |:| #2A((A B C) (D E F) (#2A((1 2) (3 4)) G H))) ;
    54

(read-from-string  "( a vector [ a b c d e f ] )")
--> (A VECTOR #(A B C D E F)) ;
    28

(read-from-string  "( a vector [ (a b) (c d) (e f) ] )")
--> (A VECTOR #((A B) (C D) (E F))) ;
    34

(read-from-string  "( a matrix [ a b ; c d ; e f ] )")
--> (A MATRIX #2A((A B) (C D) (E F))) ;
    32

---

From: Michael Graffam <[email-masked]>
Date: Tue Mar 9 16:22:09 2004

> reader do its work, and avoid concatenating strings like that!

Yeah, I'd have to agree with you there. The code in question is
essentially straight out of the Matlisp distribution. This was my first
exposure to Lisp macro characters (I like it!), and after getting the hang
of it a bit, I ended up re-writing the entire thing in a much simpler way
that works in CLISP, CMUCL and others.

> evaluated, you return the data item read.

Are you sure? I just finished writing a macro character that tried to
return a list, but which failed with an eval error -- so I ended up
inserting a quote in front of it all.

Or maybe I'm misunderstanding you.

> otherwise the  list where I  store the data  could be mixed  with data

Thanks! I look forward to studying it.

I must say, the more Lisp I learn, the more I like it and can't imagine
how I used other languages. I suppose this is the general consensus :)

---

From: Pascal J.Bourguignon <[email-masked]>
Date: Tue Mar 9 17:59:10 2004

> inserting a quote in front of it all.

Absolutely.

(set-macro-character #\$ (lambda (stream char) '(1 2 3)))
--> T
(read-from-string " $ ")
--> (1 2 3) ;
    2

(set-macro-character #\$ (lambda (stream char) '(+ 1 2 3)))
--> T
(read-from-string " $ ")
--> (+ 1 2 3) ;
    2

(set-macro-character
 #\$ (lambda (stream char)
       (let ((n 2) (m 3) (matrix-1 '('(1 2) '(a b) '(4 5))))
         (list 'make-matrix-from-reader
               (list 'quote (list n m))
               :initial-contents `(list ,@matrix-1))
         )))
--> T
(read-from-string  " $ ")
--> (MAKE-MATRIX-FROM-READER '(2 3) :INITIAL-CONTENTS (LIST '(1 2) '(A B) '(4 5))) ;
    2
(defparameter m (read-from-string  " $ "))
--> M
(aref m 1 1)
--> *** - AREF: (MAKE-MATRIX-FROM-READER '(2 3) :INITIAL-CONTENTS (LIST '(1 2) '(A B) '(4 5))) is not an array

> how I used other languages. I suppose this is the general consensus :)

Indeed.

---

From: Michael Graffam <[email-masked]>
Date: Tue Mar 9 18:45:04 2004

> (set-macro-character #\$ (lambda (stream char) '(1 2 3)))

Heh. And I thought I was confused before.

Ok, so why the (apparent) different in behavior between read-from-string
and the REPL? Observe:

[14]> (set-macro-character #\$ (lambda (stream char) '(1 2 3)))
T

[17]> (read-from-string "$")
(1 2 3) ;
1

Ok -- so far, so good. But:

[18]> $

*** - EVAL: 1 is not a function name

The returned item is being eval'd. This is OK for matrices, since the
matrix evals to itself, but when returning lists -- what to do?

In another application, I want {:a-line :line #(1 2 3) other stuff}
to expand to ((:label . :a-line) (:type . :line) (:anchor . #(1 2 3)) other
		stuff)

By inserting the quote as I described, I can successfully build my
a-lists using this form, which nicely saves a bit of repetative typing --
but sure enough, when used from read-to-string I get a leading ' which
isn't what I want.

---

From: Pascal J.Bourguignon <[email-masked]>
Date: Tue Mar 9 22:17:00 2004

> Ok, so why the (apparent) different in behavior between read-from-string

Why do you think it's called a REPL?

Read Eval Print Loop!
     ^^^^
Of course (1 2 3) is not a function call, you cannot evaluate it!
If you typed directly (1 2 3) to the REPL you'd get the same error.

> *** - EVAL: 1 is not a function name

[19]> (1 2 3)

> matrix evals to itself, but when returning lists -- what to do?

Read the data don't evaluate it!  You can evaluate only programs, not data.
 
> but sure enough, when used from read-to-string I get a leading ' which

You should insert the quote in your input, not in the reader macro.
[112]> '$
(1 2 3)

---

From: Michael Graffam <[email-masked]>
Date: Tue Mar 9 22:25:01 2004

> Why do you think it's called a REPL?

{ ... and then he was enlightened ... }

> You should insert the quote in your input, not in the reader macro.

Yes. I realize now that I was implicitely trying to avoid exactly
that as I begin playing with set-macro-character. Matlisp allowed '[2 ; 3]
and [2 ; 3] and I was trying to mimick that.

Thanks for setting me straight.

Manually inserting the ' is the proper way.

Regards,

Mike