Tim*_*ies 4 lisp macros common-lisp
我想定义一个LISP宏dolist,让我定义一个可选的输出参数。在下面的案例研究中,此宏将称为doread。它将从文件中读取行并返回以这种方式找到的行数。
(let ((lines 0))
(doread (line file lines)
;; do something with line
(incf lines)))
Run Code Online (Sandbox Code Playgroud)
问题是要使它lines在上面的宏中起作用
我可以使用&key来完成我想做的事情,但不能使用&optional&key来做(因为我想控制文件的读取方式,所以需要&key;例如,使用reador read-line或其他方法)。
现在下面的作品但到作品走错了路。这里的out参数必须是a &key而不是&optional:
;; this way works...
(defmacro doread ((it f &key out (take #'read)) &body body)
"Iterator for running over files or strings."
(let ((str (gensym)))
`(with-open-file (,str f)
(loop for ,it = (funcall ,take ,str nil)
while ,it do
(progn ,@body))
,out)))
;; lets me define something that reads first line of a file
(defun para1 (f)
"Read everything up to first blank line."
(with-output-to-string (s)
(doread (x f :take #'read-line)
(if (equalp "" (string-trim '(#\Space #\Tab) x))
(return)
(format s "~a~%" x)))))
(print (para1 sometime)) ; ==> shows all up to first blank line
Run Code Online (Sandbox Code Playgroud)
我想做的是以下内容(请注意,out现在已移入&optional:
(defmacro doread ((it f &optional out &key (take #'read)) &body body)
"Iterator for running over files or strings."
(let ((str (gensym)))
`(with-open-file (,str f)
(loop for ,it = (funcall ,take ,str nil)
while ,it do
(progn ,@body))
,out)))
Run Code Online (Sandbox Code Playgroud)
如果可行,我可以做类似的事情。
(defun para1 (f)
"Print everything up to first blank line.
Return the lines found in that way"
(let ((lines 0))
(doread (x f lines :take #'read-line)
(if (equalp "" (string-trim '(#\Space #\Tab) x))
(return)
(and (incf lines) (format t "~a~%" x)))))
Run Code Online (Sandbox Code Playgroud)
但是我用&optional out我得到
loading /Users/timm/gits/timm/lisp/src/lib/macros.lisp
*** - GETF: the property list (#'READ-LINE) has an odd length
Run Code Online (Sandbox Code Playgroud)
您不能混合&optional并且&key期望只能传递关键字参数。但是,您可以定义一种语法,以允许与源关联的参数的可选列表。
例如:
(defpackage :so (:use :cl :alexandria))
(in-package :so)
(defmacro do-read ((entry source &optional result) &body body)
(destructuring-bind (source &key (take '#'read)) (ensure-list source)
(once-only (take)
`(loop
:with ,entry
:do (setf ,entry (handler-case (funcall ,take ,source)
(end-of-file () (loop-finish))))
(progn ,@body)
:finally (return ,result)))))
Run Code Online (Sandbox Code Playgroud)
的语法DO-READ可以写为:
(DO-READ (ENTRY [SOURCE|(SOURCE &KEY TAKE)] &OPTIONAL RESULT) . BODY)
Run Code Online (Sandbox Code Playgroud)
这不是标准Lisp格式的异常语法(请参见LET,lambda-lists中的关键字synax defstruct等)。您可以与添加更多的关键字参数TAKE。
在宏中,我更喜欢发出LOOP关键字作为关键字,而不是宏的定义包中的符号。否则,在对代码进行宏扩展时,您可能会获得宏包的前缀符号(即SO::WITH而不是:WITH),这很快就会变得不可读。
从NIL返回NIL READ-LINE是很好的,但从不返回NIL READ,因为NIL可能是一个成功读取的值。通常,由于TAKE是由用户提供的,因此您不知道NIL是否可以接受。这就是为什么我抓住了
END-OF-FILE。如果您想从其他来源阅读,也可以检查辅助返回值,或记录它们也表明情况。
所述ENTRY变量的范围被扩展,使得RESULT可以是
ENTRY本身; 在您的情况下,OUT不能等于IT,因为一旦退出循环,就无法再访问它。这是次要的一点,但这很有用。
WITH-OPEN-FILE如果您想从文件(流)以外的其他内容中读取数据,则不包括在内。
#'READ 引用了,这在这里并不重要,但是在宏中有一个良好的习惯,这样您就可以在评估时而不是在宏扩展时实际评估事物。
(with-input-from-string (in "abcdef")
(do-read (char (in :take #'read-char) char)
(print char)))
Run Code Online (Sandbox Code Playgroud)
打印所有字符并返回#\f。
(with-input-from-string (in (format nil "~{~a~%~}" *features*))
(let ((lines 0))
(do-read (line in lines)
(incf lines))))
Run Code Online (Sandbox Code Playgroud)
打印字符串中的行数。