如何在JavaScript中的自定义LISP中编写宏扩展

时间:2019-05-13 12:41:14

标签: javascript macros lisp lisp-macros

我有一个普遍的疑问,我应该如何去创建适当的宏展开函数或宏。

这是我在LIPS解释器中定义的宏(您可以在https://jcubic.github.io/lips/进行测试)

function macro_expand(single) {
    return async function(code, args) {
        var env = args['env'] = this;
        async function traverse(node) {
            if (node instanceof Pair && node.car instanceof Symbol) {
                try {
                    var value = env.get(node.car);
                    if (value instanceof Macro && value.defmacro) {
                        var result = await value.invoke(node.cdr, args, true);
                        if (result instanceof Pair) {
                            return result;
                        }
                    }
                } catch (e) {
                    // ignore variables
                }
            }
            var car = node.car;
            if (car instanceof Pair) {
                car = await traverse(car);
            }
            var cdr = node.cdr;
            if (cdr instanceof Pair) {
                cdr = await traverse(cdr);
            }
            var pair = new Pair(car, cdr);
            return pair;
        }
        var new_code = code;
        if (single) {
            return quote((await traverse(code)).car);
        } else {
            while (true) {
                new_code = await traverse(code);
                if (code.toString() === new_code.toString()) {
                    break;
                }
                code = new_code;
            }
            return quote(new_code.car);
        }
    };
}

问题在于,这是虚拟的宏扩展,并且忽略了有关变量的错误,因此它无法评估宏准引用,因为它抛出了无法找到变量的异常。因此,我最终在扩展列表内添加了准引用(注意:最新版本的代码甚至都没有尝试扩展准引用,因为它被标记为不可扩展)。

编写宏扩展的方法是什么?使用宏扩展功能时,我应该扩展评估功能以使其工作不同吗?

我正在测试biwascheme如何创建此功能https://www.biwascheme.org/,但也无法正常运行,因为我希望此功能可以工作:

它展开:

biwascheme> (define-macro (foo name . body) `(let ((x ,(symbol->string name))) `(print ,x)))
biwascheme> (macroexpand '(foo bar))
=> ((lambda (x) (cons (quote print) (cons x (quote ())))) "bar")
biwascheme> 

我希望它可以扩展为:

(let ((x "bar")) (quasiquote (print (unquote x))))

我轻快的回报:

lips> (define-macro (foo name . body)
          `(let ((x ,(symbol->string name))) `(print ,x)))
;; macroexpand is a macro
lips> (macroexpand (foo bar))
(quasiquote (let ((x (unquote (symbol->string name))))
              (quasiquote (print (unquote x)))))

即使我将quasiquote设置为可扩展,它也不会扩展准引用,因为它找不到名称,因此会抛出被宏扩展忽略的异常。

任何代码甚至伪代码都将有助于在我的LISP中编写此函数或宏。

编辑

我已经开始更新代码,以将宏扩展合并到评估函数中,并对define-macro宏进行了一次更改。调用宏扩展时,它不是第一次调用代码,这就是问题所在。

之前:

var rest = __doc__ ? macro.cdr.cdr : macro.cdr;
if (macro_expand) {
    return rest.car;
}
var pair = rest.reduce(function(result, node) {
    return evaluate(node, { env, dynamic_scope, error });
});

之后:

var rest = __doc__ ? macro.cdr.cdr : macro.cdr;
var pair = rest.reduce(function(result, node) {
    return evaluate(node, eval_args);
});
if (macro_expand) {
    return quote(pair);
}

现在它可以正常工作了,所以我的expand_macro宏正常工作,这就是应该编写macro_expand的方式。

EDIT2 :我进一步重构了代码,事实证明,我不需要在define-macro宏内使用macro_exapnd代码,而只需取消引用对(删除数据标志)。

1 个答案:

答案 0 :(得分:0)

这里是用Racket编写的玩具宏扩展器,用于处理CL样式的宏。在编写此代码的过程中,我已经使用了Racket宏和其他工具,因此就其本身而言,它不会被引导。显然,可以做到这一点,但是这样做会更加费力。

这样做的目的仅仅是演示一个简单的宏扩展器是如何工作的:从任何意义上说,它都不是适合于实际使用的东西。

特殊形式

首先,我们需要处理特殊形式。特殊形式是具有魔术语义的事物。这个扩展器对它们的工作原理非常简单:

  • 特殊形式是复合形式,其第一个元素是特殊运算符;
  • 表格其余部分的每个元素要么是某种特殊的东西,要么不会扩展,要么会正常扩展,这是通过在定义中说出expr来完成的。
  • 完成此操作的方式是通过相当简单的模式匹配器完成的,这可能仅是因为扩展器知道的特殊格式数量很少。

这是特殊形式的定义方式,以及其中三个的定义:

(define special-patterns (make-hasheqv))

(define (special-pattern? op)
  (and (symbol? op)
       (hash-has-key? special-patterns op)))

(define (special-pattern op)
  (hash-ref special-patterns op))

(define-syntax-rule (define-special-pattern (op spec ...))
  (hash-set! special-patterns 'op '(op spec ...)))

(define-special-pattern (quote thing))
(define-special-pattern (lambda args expr ...))
(define-special-pattern (define thing expr ...))
(define-special-pattern (set! thing expr))

现在,我们可以询问某物是否是一种特殊形式(代码中的特殊模式)并检索其模式:

> (special-pattern? 'lambda)
#t
> (special-pattern 'lambda)
'(lambda args expr ...)

请注意,if之类的东西并不是宏扩展器的特殊运算符,尽管它们实际上是特殊的:以(if test then else)之类的形式应扩展所有子表单,因此没有理由让宏扩展器了解它们。像lambda这样的东西,宏扩展程序需要知道的某些子表单不应该扩展。

宏定义

宏是复合形式,其第一个元素被认为是命名宏。对于每个这样的宏,都有一个宏扩展器函数将负责扩展表单:该函数将传递整个表单。有一点语法,就是define-macro,它以类似于defmacro在CL中的方式包装了此函数(但没有&whole支持,也没有支持arglist解构或任何一个。

(define macros (make-hasheqv))

(define (macro? op)
  (and (symbol? op)
       (hash-has-key? macros op)))

(define (macro op)
  (hash-ref macros op))

(define-syntax-rule (define-macro (m arg ... . tail) form ...)
  (hash-set! macros 'm (lambda (whole)
                         (apply (lambda (arg ... . tail) form ...)
                                (rest whole)))))

我们可以定义一个简单的宏:这是let的四个定义。

首先,这是最基本的方法:它甚至不使用define-macro,而是它变成的:外部函数获取整个形式,然后在其内部调用内部形式这不是宏名称。然后,内部函数会费力地将(let ((x y) ...) ...)变成((lambda (x ...) ...) y ...),这是let的正确扩展。 (请注意,这些都不涉及CL的(let (x) ...))。

(hash-set! macros 'let
           ;; this is what define-macro turns into
           (lambda (whole)
             (apply (lambda (bindings . body)
                      (cons (cons 'lambda
                                  (cons (map first bindings) body))
                            (map second bindings)))
                    (rest whole))))

现在是这样,但是使用define-macro可以减轻痛苦:

(define-macro (let bindings . body)
  ;; Really primitive version
  (cons (cons 'lambda (cons (map first bindings) body))
        (map second bindings)))

另一个使用list*的版本使事情变得不太可怕了:

(define-macro (let bindings . body)
  ;; without backquote, but usung list* to make it a bit
  ;; less painful
  (list* (list* 'lambda (map first bindings) body)
         (map second bindings)))

最后是使用反引号( aka 准引用)的版本。

(define-macro (let bindings . body)
  ;; with backquote
  `((lambda ,(map first bindings) ,@body)
    ,@(map second bindings)))

这是prog1的宏定义的一个版本,由于卫生故障而被破坏:

(define-macro (prog1 form . forms)
  ;; Broken
  `(let ([r ,form])
     ,@forms
     r))

这是您需要编写得更卫生的方法(尽管按Scheme的某些极端标准来说,它仍然不卫生):

(define-macro (prog1 form . forms)
  ;; Working
  (let ([rn (string->uninterned-symbol "r")])
    `(let ([,rn ,form])
       ,@forms
       ,rn)))

请注意,此宏变成另一个宏:它扩展为let:扩展器需要处理(并且确实如此)。

宏扩展器

宏扩展器由两个功能组成:expand-macros是实际执行扩展的东西,它以特殊形式分派给expand-special

这里是expand-macros

(define (expand-macros form)
  ;; expanding a form
  (if (cons? form)
      ;; only compound forms are even considered
      (let ([op (first form)])
        (cond [(macro? op)
               ;; it's a macro: call the macro function & recurse on the result
               (expand-macros ((macro op) form))]
              [(special-pattern? op)
               ;; it's special: use the special expander
               (expand-special form)]
              [else
               ;; just expand every element.
               (map expand-macros form)]))
      form))

关于此的注意事项:

  • 只有复合形式可以是宏形式;
  • 这是一个lisp-1,因此可以完全正常地评估复合形式的汽车,并且可以是宏观形式:((let (...) ...) ...)很好;
  • 宏将递归扩展,直到无所事事为止。

这里是expand-special:比expand-macro还要花哨,而且可能有错误:要尝试的是将特殊形式的定义与给出的形式相匹配。

(define (expand-special form)
  ;; expand a special thing based on a pattern.
  (match-let* ([(cons op body) form]
               [(cons pop pbody) (special-pattern op)])
    (unless (eqv? op pop)
      (error 'expand-special "~s is not ~s" pop op))
    (let pattern-loop ([accum (list op)]
                       [tail body]
                       [ptail pbody]
                       [context 'expr])
      (cond [(null? tail)
             (unless (or (null? ptail)
                         (eqv? (first ptail) '...))
               (error 'expand-special "~s is not enough forms for ~s"
                      body op))
             (reverse accum)]
            [(null? ptail)
             (error 'expand-special "~s is too many forms for ~s"
                    body op)]
            [else
             (match-let* ([(cons btf btr) tail]
                          [(cons ptf ptr) ptail]
                          [ellipsis? (eqv? ptf '...)]
                          [ctx (if ellipsis? context ptf)]
                          [ptt (if ellipsis? ptail ptr)])
               (pattern-loop (cons (if (eqv? ctx 'expr)
                                       (expand-macros btf)
                                       btf)
                                   accum)
                             btr ptt ctx))]))))

这里有点奇怪的是省略号(...)的处理,该省略号在匹配器中用于指示“此处有更多内容”:我不记得它是否可以处理不是最后的省略号以一种模式,但我强烈怀疑没有。请注意,尽管底层的宏系统也使用省略号,但它们是无关的:这仅是基于...是合法符号名这一事实。

请注意,当然,这会在需要时递归回到expand-macros

基于这些定义,我们现在可以扩展一些宏:

> (expand-macros '(let ((x y)) x))
'((lambda (x) x) y)
> (expand-macros '(prog1 a b))
'((lambda (r) b r) a)

请注意,Racket的打印机不是专门进行未联网打印,但是上面的r是未联网的。

使用简单的跟踪实用程序,您可以定义宏扩展程序的跟踪版本:

> (expand-macros '(let ([x 1]) (prog1 x (display "1"))))
[expand-macros (let ((x 1)) (prog1 x (display "1")))
 [expand-macros ((lambda (x) (prog1 x (display "1"))) 1)
  [expand-macros (lambda (x) (prog1 x (display "1")))
   [expand-special (lambda (x) (prog1 x (display "1")))
    [expand-macros (prog1 x (display "1"))
     [expand-macros (let ((r x)) (display "1") r)
      [expand-macros ((lambda (r) (display "1") r) x)
       [expand-macros (lambda (r) (display "1") r)
        [expand-special (lambda (r) (display "1") r)
         [expand-macros (display "1")
          [expand-macros display
           -> display]
          [expand-macros "1"
           -> "1"]
          -> (display "1")]
         [expand-macros r
          -> r]
         -> (lambda (r) (display "1") r)]
        -> (lambda (r) (display "1") r)]
       [expand-macros x
        -> x]
       -> ((lambda (r) (display "1") r) x)]
      -> ((lambda (r) (display "1") r) x)]
     -> ((lambda (r) (display "1") r) x)]
    -> (lambda (x) ((lambda (r) (display "1") r) x))]
   -> (lambda (x) ((lambda (r) (display "1") r) x))]
  [expand-macros 1
   -> 1]
  -> ((lambda (x) ((lambda (r) (display "1") r) x)) 1)]
 -> ((lambda (x) ((lambda (r) (display "1") r) x)) 1)]
'((lambda (x) ((lambda (r) (display "1") r) x)) 1)

此代码的版本可用here