我在node.js上实现了自己的Lisp,我可以像这样运行s表达式:
(assert (= 3 (+ 1 2))) (def even? (fn [n] (= 0 (bit-and n 1)))) (assert (even? 4)) (assert (= false (even? 5)))
现在我想添加宏 - defmacro
函数 - 但这就是我被困住的地方。我想知道如何在其他Lisps中实现宏系统,但我找不到很多指针(this和this除外)。
我看过Clojure宏系统 - 我最熟悉的Lisp - 但这看起来太复杂了,我找不到我可以轻易应用的其他线索(Clojure宏最终编译成字节代码而不是不适用于javascript,我也无法理解macroexpand1
函数。)
所以我的问题是:给定一个没有宏但带有AST的Lisp实现,如何添加像Clojure宏系统这样的宏系统?这个宏系统可以在Lisp中实现,还是在宿主语言的实现中需要额外的功能?
还有一句话:我还没有实现quote
('
),因为我无法弄清楚返回列表中应该包含哪种值。它应该包含AST元素还是像Symbol
和Keyword
这样的对象(后者是Clojure的情况)?
答案 0 :(得分:13)
所有宏都会将未评估的表单作为参数并在其正文中执行替换。实现宏系统的技巧是告诉编译器lazy。
换句话说,当编译器遇到函数时,它首先计算其形式参数列表,产生结果并将它们传递给函数。当编译器找到一个宏时,它会将参数 unvaluated 传递给正文,然后执行正文请求的任何计算,最后将其自身替换为结果。
例如,假设您有一个功能:
(defun print-3-f (x) (progn (princ x) (princ x) (princ x)))
和宏:
(defmacro print-3-m (x) `(progn (princ ,x) (princ ,x) (princ ,x)))
然后你可以马上看到差异:
CL-USER> (print-3-f (rand))
* 234
* 234
* 234
CL-USER> (print-3-m (rand))
* 24
* 642
* 85
要理解为什么会这样,你需要以某种方式运行编译器。
当Lisp遇到该函数时,它会构建一个树,其中首先评估(rand)
并将结果传递给函数,该函数将打印所述结果三次。
另一方面,当Lisp遇到宏时,它会将(rand)
未触动的形式传递给正文,该正文返回一个引用列表,其中x
被替换为(rand)
,屈服:
(progn (princ (rand)) (princ (rand)) (princ (rand)))
并替换此新表单的宏调用。
Here你会发现大量有关各种语言宏的文档,包括Lisp。
答案 1 :(得分:3)
这是来自Peter Norvig的Paradigms of Artificial Intelligence Programming - 这是任何LISP程序员书架的必备书。
他假设您正在实施解释语言,并提供了在LISP中运行的Scheme解释器的示例。
The following two examples here显示他如何将宏添加到主eval
函数(interp
)
这是在处理宏之前解释S表达式的函数:
(defun interp (x &optional env)
"Interpret (evaluate) the expression x in the environment env."
(cond
((symbolp x) (get-var x env))
((atom x) x)
((case (first x)
(QUOTE (second x))
(BEGIN (last1 (mapcar #'(lambda (y) (interp y env))
(rest x))))
(SET! (set-var! (second x) (interp (third x) env) env))
(IF (if (interp (second x) env)
(interp (third x) env)
(interp (fourth x) env)))
(LAMBDA (let ((parms (second x))
(code (maybe-add 'begin (rest2 x))))
#'(lambda (&rest args)
(interp code (extend-env parms args env)))))
(t ;; a procedure application
(apply (interp (first x) env)
(mapcar #'(lambda (v) (interp v env))
(rest x))))))))
这是在添加宏评估之后(为了清晰起见,子方法已经在参考链接中
(defun interp (x &optional env)
"Interpret (evaluate) the expression x in the environment env.
This version handles macros."
(cond
((symbolp x) (get-var x env))
((atom x) x)
((scheme-macro (first x))
(interp (scheme-macro-expand x) env))
((case (first x)
(QUOTE (second x))
(BEGIN (last1 (mapcar #'(lambda (y) (interp y env))
(rest x))))
(SET! (set-var! (second x) (interp (third x) env) env))
(IF (if (interp (second x) env)
(interp (third x) env)
(interp (fourth x) env)))
(LAMBDA (let ((parms (second x))
(code (maybe-add 'begin (rest2 x))))
#'(lambda (&rest args)
(interp code (extend-env parms args env)))))
(t ;; a procedure application
(apply (interp (first x) env)
(mapcar #'(lambda (v) (interp v env))
(rest x))))))))
值得注意的是,Christian Queinnec's Lisp In Small Pieces的开篇章节具有非常相似的功能,他称之为eval
。
答案 2 :(得分:2)
您需要在评估链中进行宏扩展阶段:
text-input -> read -> macroexpand -> compile -> load
请注意,宏扩展应该是递归的(宏扩展,直到没有宏扩展)。
您的环境需要能够“保持”宏扩展功能,可以在此阶段按名称查找。请注意,defmacro
是Common Lisp中的一个宏,它设置正确的调用以将该名称与该环境中的宏扩展函数相关联。
答案 3 :(得分:1)
看看this示例。它是一个类似Arc的编译器的玩具实现,具有不错的宏支持。