Macros 如何用另一个s表达式包装和执行lisp s表达式?

Macros 如何用另一个s表达式包装和执行lisp s表达式?,macros,common-lisp,Macros,Common Lisp,我试图用另一个lisp表达式包装一个lisp表达式。我想,一个宏应该能做到,但我不懂这个诀窍。谁能帮帮我,谁知道怎么做 我的实际目标是编写一个宏,用打开的文件表达式将一批封装在一些宏体代码周围 (我想写一个脚本/程序,它打开一个或两个输入文件,逐行处理它们,但也将处理结果输出到几个不同的独立输出文件中。为此,我希望在处理和写入独立输出文件的代码周围堆积具有open file宏调用的,所有这些都是o为宏主体代码打开) 由于打开文件的需要输入或输出流的符号(处理程序)、输出(或输入)文件的路径变量以

我试图用另一个lisp表达式包装一个lisp表达式。我想,一个宏应该能做到,但我不懂这个诀窍。谁能帮帮我,谁知道怎么做

我的实际目标是编写一个宏,用打开的文件表达式将一批
封装在一些宏体代码周围

(我想写一个脚本/程序,它打开一个或两个输入文件,逐行处理它们,但也将处理结果输出到几个不同的独立输出文件中。为此,我希望在处理和写入独立输出文件的代码周围堆积具有open file
宏调用的
,所有这些都是o为宏主体代码打开)

由于打开文件的
需要输入或输出流的符号(处理程序)、输出(或输入)文件的路径变量以及一些附加信息(文件的方向等),因此我想将它们放入列表中

;; Output file-paths:
(defparameter *paths* '("~/out1.lisp" "~/out2.lisp" "~/out3.lisp"))

;; stream handlers (symbols for the output streams)
(defparameter *handlers* '(out1 out2 out3))

;; code which I would love to execute in the body
(print "something1" out1)
(print "something2" out2)
(print "something3" out3)
我多么希望宏被称为:

(with-open-files (*handlers* *paths* '(:direction :output :if-exists :append))
  ;; the third macro argument should be what should be passed to the
  ;; individual `with-open-file` calls
  ;; and it might be without `quote`-ing or with `quote`-ing
  ;; - is there by the way a good-practice for such cases? -
  ;; - is it recommended to have `quote`-ing? Or how would you do that? -
  ;; and then follows the code which should be in the macro body:
  (print "something1" out1)
  (print "something2" out2)
  (print "something3" out3))
宏调用应展开的内容:

(with-open-file (out1 "~/out1.lisp" :direction :output :if-exists :append)
  (with-open-file (out2 "~/out2.lisp" :direction :output :if-exists :append)
    (with-open-file (out3 "~/out3.lisp" :direction :output :if-exists :append)
      (print "something1" out1)
      (print "something2" out2)
      (print "something3" out3))))
作为一个步骤,我认为我必须让一个s表达式包装另一个s表达式

我的第一个问题是:如何用另一个s表达式包装一个s表达式?但此时我还无法处理它。 我所能做的就是写一个函数,它只是溢出一个未执行的表达式。如何写一个宏,它做同样的事情,但在以这种方式扩展它之后也执行代码

(defun wrap (s-expr-1 s-expr-2)
  (append s-expr-1 (list s-expr-2)))

(wrap '(func1 arg1) '(func2 arg2))
;; => (FUNC1 ARG1 (FUNC2 ARG2))

(wrap '(with-open-files (out1 "~/out1.lisp" :direction :output :if-exists :append))
  '(with-open-files (out2 "~/out2.lisp" :direction :output :if-exists :append) 
      (print "something1" out1)
      (print "something2" out2)
      (print "something3" out3)))
其中:

(WITH-OPEN-FILES (OUT1 "~/out1.lisp" :DIRECTION :OUTPUT :IF-EXISTS :APPEND)
 (WITH-OPEN-FILES (OUT2 "~/out2.lisp" :DIRECTION :OUTPUT :IF-EXISTS :APPEND)
  (PRINT "something1" OUT1) 
  (PRINT "something2" OUT2)
  (PRINT "something3" OUT3)))
这样,依次应用
wrap
函数,在输入列表上循环,我就可以构建代码了

但是,这些函数只生成代码,不执行代码。 最后,我会被迫使用
eval
函数来评估构建的代码…(但不知怎的,我知道这不应该这样做。我只是不知道如何编写宏来完成这些事情…实际上,宏就是用来解决这些问题的…)

在执行过程中,我遇到了很大的麻烦。由于不能对宏(而不是函数名)调用
funcall
apply
,我看不到明显的解决方案。有人有过这种情况的经验吗

当用另一个s表达式在宏中包装一个s表达式并对其求值时,下一个问题是,如何处理该列表以使代码扩展到所需的代码,然后进行求值?我只试了几个小时,没走多远

我需要有编写此类宏经验的人的帮助…

请注意,在Lisp中,“handler”通常是一个函数,而不是一个符号。您的命名令人困惑

静止的 如果要生成代码,应该使用宏,而不是函数。 这假设您在编译时知道哪些文件和流 您将使用的变量:

最简单的方法是使用递归:

(defmacro with-open-files ((streams file-names &rest options &key &allow-other-keys) &body body)
  (if (and streams file-names)
      `(with-open-file (,(pop streams) ,(pop file-names) ,@options)
         (with-open-files (,streams ,file-names ,@options)
           ,@body))
      `(progn ,@body)))
测试:

动态 如果您在编译时不知道流和文件是什么,例如。, 它们存储在
*handler*
变量中,不能使用简单 宏以上-你将不得不推出自己的使用 用于装订和 避免变量 捕获。注意
内倒勾如何避免多次 评估(即参数
文件名
选项
评估一次,而不是多次):

在这里,流变量和文件列表都是在运行时计算的

重要的 这里一个重要的实用注意事项是,静态版本更健壮,因为它保证所有流都关闭,而动态版本将无法关闭剩余的流,例如,如果第一个
close
引发异常(这是可以修复的,但不是微不足道的:我们不能忽略错误,因为事实上应该报告错误,但应该报告哪个错误?&c&c)

另一个观察结果是,如果您的流变量列表在编译时未知,则使用它们的
正文
中的代码将无法正确编译(变量将使用动态绑定&c进行编译),这由
未定义变量
编译时警告指示

基本上,动态版本是一个宏观的练习,而静态版本是你应该在实践中使用的

你的具体情况 如果我正确理解了您的需求,您可以执行以下操作 这(未经测试!):


@Gwang JinKim:没关系:
(打开文件(nil-nil…)
扩展到
progn
。我已经做Lisp 20多年了;-)请参见编辑-我添加了“动态”哇,非常感谢!这将是我的下一个问题,如何使它能够与*处理程序*一起使用…是的,如何使它在运行时可调用…所以可以说,在宏中使用宏禁止动态使用它,不是吗?-我还需要学习“查看”当我必须使用gensym时,或当我不使用gensym时,立即…@Gwang JinKim:我建议您阅读宏知识的完整处理方法。简单地说,有两个问题:“变量捕获”和“多重求值”;如果您愿意,请问一个单独的问题。是的,谢谢,修复了-编译应将此类错误报告为有关未定义变量的警告。
(macroexpand-1
 '(with-open-files ((a b c) ("f" "g" "h") :direction :output :if-exists :supersede)
   (print "a" a)
   (print "b" b)
   (print "c" c)))
==>
(WITH-OPEN-FILE (A "f" :DIRECTION :OUTPUT :IF-EXISTS :SUPERSEDE)
  (WITH-OPEN-FILES ((B C) ("g" "h") :DIRECTION :OUTPUT :IF-EXISTS :SUPERSEDE)
    (PRINT "a" A) (PRINT "b" B) (PRINT "c" C)))

(macroexpand-1
 '(with-open-files ((a) ("f") :direction :output :if-exists :supersede)
   (print "a" a)))
==>
(WITH-OPEN-FILE (A "f" :DIRECTION :OUTPUT :IF-EXISTS :SUPERSEDE)
  (WITH-OPEN-FILES (NIL NIL :DIRECTION :OUTPUT :IF-EXISTS :SUPERSEDE)
    (PRINT "a" A)))

(macroexpand-1
 '(with-open-files (nil nil :direction :output :if-exists :supersede)
   (print nil)))
==>
(PROGN (PRINT NIL))
(defmacro with-open-files-d ((streams file-names &rest options &key &allow-other-keys) &body body)
  (let ((sv (gensym "STREAM-VARIABLES-"))
        (so (gensym "STREAM-OBJECTS-"))
        (ab (gensym "ABORT-"))
        (op (gensym "OPTIONS-")))
    `(let* ((,sv ,streams)
            (,ab t)
            (,op (list ,@options))
            (,so (mapcar (lambda (fn) (apply #'open fn ,op)) ,file-names)))
       (progv ,sv ,so
         (unwind-protect (multiple-value-prog1 (progn ,@body) (setq ,ab nil))
           (dolist (s ,so)
             (when s
               (close s :abort ,ab))))))))

(macroexpand-1
 '(with-open-files-d ('(a b c) '("f" "g" "h")  :direction :output :if-exists :supersede)
   (print "a" a)
   (print "b" b)
   (print "c" c)))
==>
(LET* ((#:STREAM-VARIABLES-372 '(A B C))
       (#:ABORT-374 T)
       (#:OPTIONS-375 (LIST :DIRECTION :OUTPUT :IF-EXISTS :SUPERSEDE))
       (#:STREAM-OBJECTS-373
        (MAPCAR (LAMBDA (FN) (APPLY #'OPEN FN #:OPTIONS-375)) '("f" "g" "h"))))
  (PROGV
      #:STREAM-VARIABLES-372
      #:STREAM-OBJECTS-373
    (UNWIND-PROTECT
        (MULTIPLE-VALUE-PROG1 (PROGN (PRINT "a" A) (PRINT "b" B) (PRINT "c" C))
          (SETQ #:ABORT-374 NIL))
      (DOLIST (S #:STREAM-OBJECTS-373)
        (WHEN S
          (CLOSE S :ABORT #:ABORT-374))))))
(defun process-A-line (line stream)
  do something with line,
  stream is an open output stream)

(defun process-file (input-file processors)
  "Read input-file line by line, calling processors,
which is a list of lists (handler destination ...):
 handler is a function like process-A-line,
 destination is a file name and the rest is open options."
  (with-open-file (inf input-file)
    (let ((proc-fd (mapcar (lambda (p)
                             (cons (first p)
                                   (apply #'open (rest p))))
                           processors))
          (abort-p t))
      (unwind-protect
           (loop for line = (read-line inf nil nil)
             while line
             do (dolist (p-f proc-fd)
                  (funcall (car p-f) line (cdr p-f)))
             finally (setq abort-p nil))
        (dolist (p-f proc-fd)
          (close (cdr p-f) :abort abort-p))))))