Question

Here's a macro definition which constructs a loop with the specified variable name bound to a new line in the specified file each time through the loop:

(defmacro with-string-from-file ((in-string filename
                                   &key (if-does-not-exist :error))
                                   &rest body)
  (let ((working-stream (gensym)))
    `(with-open-file (,working-stream
                       ,filename 
                       :direction :input
                       :external-format charset:iso-8859-1
                       :if-does-not-exist ,if-does-not-exist)
       (let* ((,in-string))
         (when ,working-stream
           (loop while (setf ,in-string (read-line
                                          ,working-stream
                                          nil))
             do ,@body))))))

(princ "test 1:") (terpri)

(with-string-from-file (the-string "/etc/resolv.conf"
                         :if-does-not-exist nil)
  (princ "one line is \"")
  (princ the-string)
  (princ "\"")
  (terpri))

; Test 2 has not been introduced yet.

(princ "test 3:") (terpri)

(with-string-from-file (the-string "/etc/resolv.conf.nonexistent"
                         :if-does-not-exist nil)
  (princ "one line is \"")
  (princ the-string)
  (princ "\"")
  (terpri))

(princ "test 4:") (terpri)

(with-string-from-file (the-string "/etc/resolv.conf.nonexistent")
  (princ "one line is \"")
  (princ the-string)
  (princ "\"")
  (terpri))

The output:

test 1:
one line is "# Dynamic resolv.conf(5) file for glibc resolver(3) generated by resolvconf(8)"
one line is "#     DO NOT EDIT THIS FILE BY HAND -- YOUR CHANGES WILL BE OVERWRITTEN"
one line is "nameserver 192.168.6.1"
one line is "search x441afea5.org"
test 3:
test 4:
*** - OPEN: File #P"/etc/resolv.conf.nonexistent" does not exist

This works whether running the file directly, or compiling it first and then running the compiled file.

Now I attempt to add a feature to the macro so I can specify the input character set:

(defmacro with-string-from-file ((in-string filename
                                   &key (if-does-not-exist :error)
                                        (external-format charset:iso-8859-1))
                                   &rest body)
  (let ((working-stream (gensym)))
    `(with-open-file (,working-stream
                       ,filename 
                       :direction :input
                       :external-format ,external-format
                       :if-does-not-exist ,if-does-not-exist)
       (let* ((,in-string))
         (when ,working-stream
           (loop while (setf ,in-string (read-line
                                          ,working-stream
                                          nil))
             do ,@body))))))

(princ "test 1:") (terpri)

(with-string-from-file (the-string "/etc/resolv.conf"
                         :if-does-not-exist nil)
  (princ "one line is \"")
  (princ the-string)
  (princ "\"")
  (terpri))

(princ "test 2:") (terpri)

(with-string-from-file (the-string "/etc/resolv.conf"
                         :external-format charset:utf-8
                         :if-does-not-exist nil)
  (princ "one line is \"")
  (princ the-string)
  (princ "\"")
  (terpri))

(princ "test 3:") (terpri)

(with-string-from-file (the-string "/etc/resolv.conf.nonexistent"
                         :if-does-not-exist nil)
  (princ "one line is \"")
  (princ the-string)
  (princ "\"")
  (terpri))

(princ "test 4:") (terpri)

(with-string-from-file (the-string "/etc/resolv.conf.nonexistent")
  (princ "one line is \"")
  (princ the-string)
  (princ "\"")
  (terpri))

When I run the file directly it works:

test 1:
one line is "# Dynamic resolv.conf(5) file for glibc resolver(3) generated by resolvconf(8)"
one line is "#     DO NOT EDIT THIS FILE BY HAND -- YOUR CHANGES WILL BE OVERWRITTEN"
one line is "nameserver 192.168.6.1"
one line is "search x441afea5.org"
test 2:
one line is "# Dynamic resolv.conf(5) file for glibc resolver(3) generated by resolvconf(8)"
one line is "#     DO NOT EDIT THIS FILE BY HAND -- YOUR CHANGES WILL BE OVERWRITTEN"
one line is "nameserver 192.168.6.1"
one line is "search x441afea5.org"
test 3:
test 4:
*** - OPEN: File #P"/etc/resolv.conf.nonexistent" does not exist

But when I try to compile it, I get this error:

;; Compiling file /u/home/clisp/experiments/y1.lisp ...
*** - PRINT: Despite *PRINT-READABLY*, #<ENCODING CHARSET:ISO-8859-1 :UNIX>
      cannot be printed readably.

How can I get this to compile? I'd like to specify a character set explicitly with charset:, rather than potentially having those character set names conflict with symbols in my main namespace.

Was it helpful?

Solution

Problem

The problem is that the default argument to the macro should be a symbol, not its value.

Explanation

Using objects as macro default args is okay for self-evaluating objects, like strings, numbers, keywords, but no good with more complex objects.

When you use the macro, it expands into code

... :external-format #<ENCODING CHARSET:ISO-8859-1 :UNIX> ...

and CLISP cannot write it into the fas file (which is just a text file).

Solution

Replace (external-format charset:iso-8859-1) with (external-format 'charset:iso-8859-1).

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top