我想定义一个LISP宏,例如dolist
,它允许我定义一个可选的输出参数。在下面的案例研究中,此宏将称为doread
。它将从文件中读取行并返回以这种方式找到的行数。
(let ((lines 0))
(doread (line file lines)
;; do something with line
(incf lines)))
问题是让lines
在上面的宏中工作
我可以使用&key来完成我想做的事情,但不能使用&optional&key来做(因为我想控制文件的读取方式,所以需要&key;例如,使用read
或read-line
或其他方法) 。
现在,以下方法可以但起作用,以错误的方式进行。这里的out
参数必须是&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
我想做的是以下操作(请注意,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)))
如果可行,我可以做类似的事情。
(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)))))
但是我用&optional out
我得到了
loading /Users/timm/gits/timm/lisp/src/lib/macros.lisp
*** - GETF: the property list (#'READ-LINE) has an odd length
答案 0 :(得分:6)
您不能混合使用&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)))))
DO-READ
的语法可以写为:
(DO-READ (ENTRY [SOURCE|(SOURCE &KEY TAKE)] &OPTIONAL RESULT) . BODY)
这不是不寻常的语法标准Lisp格式(请参见LET
,lambda列表中的关键字synax,defstruct
等)。
您可以在TAKE
中添加更多关键字参数。
在宏中,我更喜欢发出LOOP关键字作为关键字,而不是符号
在宏的定义包中;否则,在进行宏扩展时
代码,您很可能会获得宏的前缀
包(即SO::WITH
而不是:WITH
)
不可读。
从READ-LINE
返回NIL是可以的,但不能从READ
返回NIL,因为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)))
打印所有字符并返回#\f
。
(with-input-from-string (in (format nil "~{~a~%~}" *features*))
(let ((lines 0))
(do-read (line in lines)
(incf lines))))
打印字符串中的行数。
答案 1 :(得分:3)
为我工作:
(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)))
(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))))))
在LispWorks中使用它:
CL-USER 104 > (para1 (capi:prompt-for-file "text file"))
;;; -*- mode: Lisp; Base: 10 ; Syntax: ANSI-Common-Lisp ; buffer-read-only: t; -*-
;;; This is ASDF 3.3.3: Another System Definition Facility.
;;;
;;; Feedback, bug reports, and patches are all welcome:
;;; please mail to <asdf-devel@common-lisp.net>.
;;; Note first that the canonical source for ASDF is presently
;;; <URL:http://common-lisp.net/project/asdf/>.
;;;
;;; If you obtained this copy from anywhere else, and you experience
;;; trouble using it, or find bugs, you may want to check at the
;;; location above for a more recent version (and for documentation
;;; and test files, if your copy came without them) before reporting
;;; bugs. There are usually two "supported" revisions - the git master
;;; branch is the latest development version, whereas the git release
;;; branch may be slightly older but is considered `stable'
15
仅当您要指定:take
时,还需要提供可选的arg。这是一个常见的陷阱,这就是为什么不喜欢使用可选参数和关键字args的组合。