0

我有一个普遍的问题,我应该如何去创建正确的宏扩展函数或宏。

这是我在 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);
        }
    };
}

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

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

我正在测试 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))))

我的 lisp 回报:

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 中编写此函数或宏。

编辑

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

前:

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代码,只需取消引用该对(删除数据标志)。

4

1 回答 1

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,它以与 CL 中类似的方式包装此函数defmacro(但不&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)))

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

(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 的打印机不会专门打印 uninterned,但r上述内容是 uninterned。

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

> (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)

此处提供了此代码的一个版本。

于 2019-05-15T17:01:09.460 回答