我有一个普遍的疑问,我应该如何去创建适当的宏展开函数或宏。
这是我在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代码,而只需取消引用对(删除数据标志)。
答案 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))
关于此的注意事项:
((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。