首页 文章

如何实现Lisp宏系统?

提问于
浏览
16

我已经在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中实现宏系统但我找不到很多指针(除了thisthis) .

我最熟悉 - 但这看起来太复杂了我无法应用于javascript,我也无法理解 macroexpand1 函数 . )

所以我的问题是:给定一个没有宏但有AST的Lisp实现,如何添加像Clojure宏系统这样的宏系统?这个宏系统可以在Lisp中实现,还是在宿主语言的实现中需要额外的功能?

还有一句话:我还没有实现 quote' ),因为我无法弄清楚返回列表中应该包含哪种值 . 它应该包含AST元素或对象,如 SymbolKeyword (后者是Clojure的情况)?

4 回答

  • 3

    宏所做的就是将未评估的表单作为参数并在其主体上执行替换 . 实现宏系统的技巧是告诉编译器lazy .

    换句话说,当编译器遇到函数时,它首先计算其形式参数列表,产生结果并将它们传递给函数 . 当编译器找到一个宏时,它会将未评估的参数传递给正文,然后执行正文请求的任何计算,最后将其自身替换为结果 .

    例如,假设您有一个功能:

    (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) 原样传递给body,返回一个带引号的列表,其中 x(rand) 替换,产生:

    (progn (princ (rand)) (princ (rand)) (princ (rand)))
    

    并替换此新表单的宏调用 .

    Here你会发现大量有关各种语言宏的文档,包括Lisp .

  • 1

    这是来自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

    看一下this的例子 . 它是一个类似Arc的编译器的玩具实现,具有不错的宏支持 .

  • 13

    您需要在评估链中进行宏扩展阶段:

    text-input -> read -> macroexpand -> compile -> load
    

    请注意,宏扩展应该是递归的(宏扩展,直到没有遗留任何宏扩展) .

    您的环境需要具有"hold"宏扩展功能,可以在此阶段按名称查找 . 请注意 defmacro 是Common Lisp中的一个宏,它设置正确的调用以将该名称与该环境中的宏扩展函数相关联 .

相关问题