【发布时间】:2023-03-12 14:02:03
【问题描述】:
我有一个普遍的问题,我应该如何着手并创建适当的宏扩展函数或宏。
这是我在 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 设置为可扩展,它也不会扩展 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 代码,只需取消引用该对(删除数据标志)。
【问题讨论】:
-
至少在 Common Lisp 中,MACROEXPAND 多次调用自身,直到表单不再是宏表单。例如,在某些 Lisp 中,LET 可能是宏,然后扩展为 ((lambda (vars...) ...) values...) 形式。 Common Lisp 也有 MACROEXPAND-1,它只做一次宏扩展。
-
如果准引用在读取时被扩展(!),那么它可能会扩展为带有 CONS、LIST、QUOTE 等的形式。它可能没有明确的 QUASIQUOTE 形式扩展到。许多 Common Lisp 实现将使用一些特定于实现的运算符,以便稍后知道这是一个 quasiquote 形式,例如然后相应地打印。
-
您将问题与问题不必要的异步/等待逻辑混淆了。首先编写一个工作宏扩展器;然后,如果它通过了所有测试,请考虑在保持测试运行的小型重构测试中“异步”它。
-
宏扩展器不能只是盲目地遍历树结构并在任何地方识别宏调用。它必须了解 Lisp 方言的所有特殊形式。例如,如果
m是一个宏,那么它不应该在(quote (m))中被识别。宏扩展器必须知道quote运算符不评估其参数,因此也不扩展该参数。同样,如果你有(let ((m 42)) ..),m也不能被识别为那里的宏调用。 -
再次检查宏的展开结果,看是否为宏。它必须反复扩展,直到它被缩减为不再是宏调用的形式。然后必须遍历该表单以在其中进行更多宏扩展。
标签: javascript macros lisp lisp-macros