首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >宏参数未被替换。

宏参数未被替换。
EN

Stack Overflow用户
提问于 2022-02-25 04:26:03
回答 2查看 102关注 0票数 1

我试图充分理解编译时宏的局限性。

下面是一个宏(我完全知道这不是一个最佳实践宏):

代码语言:javascript
复制
(defmacro emit (language file &body body)
  (print language)
  (print file)
  (print body)
  (with-open-file (str file :direction :output :if-exists :supersede)
    (princ (cond ((eq language 'html)
                  (cl-who:with-html-output-to-string (s nil :prologue t :indent t) body))
                 ((eq language 'javascript)
                  (parenscript:ps body))
                 ((eq language 'json)
                  (remove #\; (parenscript:ps body))))
           str)))

我编译了宏:

代码语言:javascript
复制
; processing (DEFMACRO EMIT ...)
PROGRAM> 

我编制这份表格:

代码语言:javascript
复制
PROGRAM> (compile nil (lambda () (emit json "~/file" (ps:create "hi" "hello") (ps:create "yo" "howdy"))))

JSON 
"~/file" 
((PARENSCRIPT:CREATE "hi" "hello") (PARENSCRIPT:CREATE "yo" "howdy")) 
#<FUNCTION (LAMBDA ()) {5367482B}>
NIL
NIL
PROGRAM> 

编译时print输出正是我所期望的。

但是,如果我看一下~/file

代码语言:javascript
复制
body

似乎((PARENSCRIPT:CREATE "hi" "hello") (PARENSCRIPT:CREATE "yo" "howdy"))从来没有代替参数body,因此也从未被处理过。

为什么会这样呢?

在这个问题上,最好的读物是什么?

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2022-02-25 16:43:55

为什么要用它代替?你从来没有取代过任何东西。

宏定义一个宏替换函数,该函数应用于代码中的实际表单,以生成另一个表单,然后进行编译。当您将宏定义应用于这些参数时,它将在宏展开时完成各种事情(编写文件等)。在返回princ返回的内容之前,这正是它的第一个参数,然后编译这个返回的表单。我不认为这是你想要的。

似乎你真正想要做的是扩展到一种形式,以一种不同的方式来解释身体,用第一个论点来表示。

您需要做的是返回新表单,以便

代码语言:javascript
复制
(emit 'html "foo.html"
  (:html (:head) (:body "whatever")))

扩展到

代码语言:javascript
复制
(with-open-file (str "foo.html" :direction :output :etc :etc)
  (cl-who:with-html-output (str)
    (:html (:head) (:body "whatever")))

为此,我们有一个模板语法:回勾。

代码语言:javascript
复制
`(foo ,bar baz)

意思是和

代码语言:javascript
复制
(list 'foo bar 'baz)

但使转换代码的结构更加清晰。还有,@可以将事情拼接到一个列表中。

代码语言:javascript
复制
`(foo ,@bar)

意思是和

代码语言:javascript
复制
(list* 'foo bar)

bar的内容,当它们是一个列表时,被拼接到列表中。这对于身体特别有用,比如在宏中。

代码语言:javascript
复制
(defmacro emit (language file &body body)
  `(with-open-file (str ,file :direction :output :if-exists :supersede)
     (princ (cond ((eq ,language 'html)
                   (cl-who:with-html-output-to-string (s nil :prologue t :indent t)
                     ,@body))
                  ((eq ,language 'javascript)
                   (parenscript:ps ,@body))
                  ((eq ,language 'json)
                   (remove #\; (parenscript:ps ,@body))))
            str)))

注意,我引入了反勾符以创建一个模板,并引入逗号将外部参数放入其中。还请注意,参数是表单。

这有几个问题:宏的用户无法知道硬编码的符号。在一种情况下(str),他们必须注意不要隐藏它,在另一种情况下(s),他们必须了解它才能给它写信。为此,我们要么使用生成的符号(对于str,这样就不会发生冲突),要么让用户说出他们想要命名的名称(对于s)。而且,这个cond也可以简化为一个case

代码语言:javascript
复制
(defmacro emit (language file var &body body)
  (let ((str (gensym "str")))
    `(with-open-file (,str ,file
                      :direction :output
                      :if-exists :supersede)
       (princ (case ,language
                ('html
                 (cl-who:with-html-output-to-string (,var nil
                                                     :prologue t
                                                     :indent t)
                   ,@body))
                ('javascript
                 (parenscript:ps ,@body))
                ('json
                 (remove #\; (parenscript:ps ,@body))))
              ,str)))

但是,您可能希望在宏展开时确定输出代码。

代码语言:javascript
复制
(defmacro emit (language file var &body body)
  (let ((str (gensym "str")))
    `(with-open-file (,str ,file
                      :direction :output
                      :if-exists :supersede)
       (princ ,(case language
                 ('html
                  `(cl-who:with-html-output-to-string (,var nil
                                                       :prologue t
                                                       :indent t)
                     ,@body))
                 ('javascript
                  `(parenscript:ps ,@body))
                 ('json
                  `(remove #\; (parenscript:ps ,@body))))
              ,str)))

在这里,您可以看到case表单已经在宏展开时进行了计算,然后使用内部模板来创建内部表单。

这都是完全未经测试的,因此将删除这些小错误保留为练习^^。

保罗·格雷厄姆( Paul )的“关于Lisp”是一本关于宏写作的书。彼得·塞贝尔( Peter )的“实用的通用Lisp”也有一个章节,还有一些菜谱在Edi的“公共Lisp”中。

票数 1
EN

Stack Overflow用户

发布于 2022-02-25 13:33:18

parenscript:ps是一个宏,而不是一个函数:它的主体是文字文本,从parenscript到JavaSctipt,它不是计算而是编译的。这一点很容易查证:

代码语言:javascript
复制
> (parenscript:ps body)
"body;"

对于你应该读什么,我没有任何建议:这个宏看起来非常混乱,我无法真正理解它的根本意图是什么。CL中的宏是一个函数,其参数是某些语言L1中的源代码,并返回某些语言L2中的源代码,其中L2通常是L1的一个子集。但是,如果这只是一个人认为他们需要一个函数时需要宏的正常情况,或者是其他一些混乱的情况,我是无法解决的。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/71261052

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档