关于宏:如何用另一个 s-expression package和执行一个 lisp s-expression?

How to wrap and execute a lisp s-expression by another s-expression?

我试图用另一个 lisp 表达式package一个 lisp 表达式。我想,一个宏应该做到这一点,但我不明白这一点。谁能帮帮我,谁知道怎么做?

我的实际目标是编写一个宏,将一批 with-open-file 表达式package在一些宏体代码周围。

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

由于 with-open-file 需要输入或输出流的符号(处理程序)和输出(或输入)文件的路径变量,以及一些附加信息(文件的方向等),我想把将它们放入列表中。

1
2
3
4
5
6
7
8
9
10
;; 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)

我希望如何调用宏:

1
2
3
4
5
6
7
8
9
10
(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))

宏调用应该扩展的内容:

1
2
3
4
5
6
(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 表达式package另一个 s 表达式。

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

1
2
3
4
5
6
7
8
9
10
11
(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)))

给出:

1
2
3
4
5
(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 函数来评估构建的代码......(但不知何故我知道这不应该这样做。而且我真的不明白如何编写宏做这样的事情......实际上,宏是用来解决这些问题的......)

执行死刑后,我遇到了大麻烦。而且由于不能在宏(而不是函数名)上调用 funcallapply 我没有看到明显的解决方案。有人遇到过这种情况吗?

当完成用另一个s-expression将s-expressionpackage在一个宏中并让它被评估时,下一个问题是,如何处理列表以让代码扩展为所需的代码然后被评估?我只是尝试了几个小时并没有走远。

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


请注意,在 Lisp 中,"处理程序"通常是一个函数,而不是一个符号。您的命名令人困惑。

静止的

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

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

1
2
3
4
5
6
(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)))

测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
(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))

动态的

如果您在编译时不知道流和文件是什么,例如,
它们存储在 *handler* 变量中,您不能使用简单的
上面的宏 - 你必须自己使用
progv 用于绑定和
gensym 避免变量
捕获。注意 let 里面的反引号如何避免多个
评估(即,参数 streamsfile-namesoptions
被评估一次,而不是多次):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
(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))))))

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

重要的

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