如何解决评估 Common Lisp 宏的参数 简单的结构通用解释器评估员代码扩展器
我想制作一个宏,它的行为取决于它的一个参数。例如:
(defclass myvar ()
((l :initarg :l
:reader l)))
(defparameter mv1 (make-instance 'myvar :l 10))
(defmacro mac1 (v)
`(progn,@(loop for x upto (eval `(l,v)) collect `(format t "~A~%",x))))
(mac1 mv1)
1
2
3
4
5
6
7
8
9
10
这很好用。 但是如果我想对局部变量做同样的事情:
(let ((mv2 (make-instance 'myvar :l 10)))
(mac1 mv2))
我收到错误:
The variable MV2 is unbound.
It is a local variable not available at compile-time.
有没有办法在宏中评估局部变量?
解决方法
您的目标是将一些代码编译成另一种语言。 几乎不需要宏来做到这一点,您可以采用结构化表达式并生成另一种树,或者直接将外语代码作为字符串发出。
有点令人困惑的是,您的代码看起来像 Lisp,但实际上并不像 Lisp,因为您希望 loop-on
能够检查其词法环境以了解编译时绑定是什么一些符号的地方。
这在标准中没有指定,即使您可以在您的实现中找到一种方法来访问它,例如在宏中使用 &environment
关键字,那么该解决方案也不会是可移植的。
在任何情况下,您都必须指定如何将 Lisp 形式转换为 C,这意味着您必须实现一个解释器(在它的一般含义中,即对代码进行解释)。
如果您有简单的需求,一种简单的方法是为数据创建 make-var
和 loop-on
构造函数。
让我们定义 var
和 loop-on
结构:
(defstruct (var (:constructor make-var (x y))) x y)
(defstruct loop-on var code)
简单的结构
由于我还不知道您想如何利用 body
,让我们按原样存储它。
这部分需要一个宏,但除非您指定如何编译主体,否则这不会很有用。
(defmacro loop-on (v &body body)
`(make-loop-on :var,v :code ',body))
现在,您的代码可以作为 Lisp 代码进行评估:
(let ((v1 (make-var 100 100)))
(loop-on v1 (some-code-here)))
结果值为:
#S(LOOP-ON :VAR #S(VAR :X 100 :Y 100) :CODE ((SOME-CODE-HERE)))
现在,您的编译器可以将此循环扩展为 C 代码,前提是 :CODE
槽满足您想要支持的 Lisp 子集。
通用解释器
对于较大的项目,您希望定义一个知道如何解释代码的代码漫游器。 我们定义6个泛型函数,如下:
(defgeneric interpret-let (interpreter env bindings code))
(defgeneric interpret-body (interpreter env forms))
(defgeneric interpret-loop (interpreter env var forms))
(defgeneric interpert-lisp (interpreter env form))
(defgeneric interpret (interpreter env code)
(:method (i env code)
(optima:ematch code
((list* 'let bindings code)
(interpret-let i env bindings code))
((list 'make-var x y)
(make-var x y))
((list* 'loop-on v code)
(interpret-loop i env v code))
((list 'lisp form)
(interpret-lisp i env form)))))
代码依赖于 optima
进行模式匹配。输入代码与已知语法匹配,行为被委托给通用函数,这些函数根据您正在构建的解释器类型进行调度;
那么 env
环境变量是一个词法环境,它可以保存变量名、函数、类型等的绑定。你也可以在 env
上调度,但这里我们假设这是一个关联列表符号到值。
以下是如何为一组给定绑定增加环境,每个绑定都是一个将符号映射到表单的列表:
(defgeneric augment-env (interpreter env bindings)
(:method (i env bindings)
(nconc (loop for (n v) in bindings
collect (cons n (interpret i env v)))
env)))
评估员
这里我专门介绍了名为 :eval
的解释器的方法。
请注意,我们不匹配类,而是匹配关键字。
对于更复杂的情况,您可以在解释器中存储一些状态。
(defmethod interpret-body ((i (eql :eval)) e (form cons))
(destructuring-bind (form . rest) form
(cond
(rest (interpret i e form)
(interpret-body i e rest))
(t (interpret i e form)))))
(defmethod interpret-let ((i (eql :eval)) env bindings code)
(interpret-body i (augment-env i env bindings) code))
(defmethod interpret-lisp ((i (eql :eval)) env form)
(eval `(let,(loop for (a . b) in env collect (list a b))
(declare (ignorable,@(mapcar #'car env))),form)))
(defmethod interpret-loop ((i (eql :eval)) e v body)
(let ((var (cdr (assoc v e))))
(dotimes (ii (var-x var))
(dotimes (jj (var-y var))
(interpret-body i
(acons 'i ii (acons 'j jj e))
body)))))
有了上面的定义,你可以解释你的代码如下:
(interpret :eval
nil
'(let ((v1 (make-var 10 5)))
(loop-on v1 (lisp (print (list i j))))))
这将执行双循环并将值打印到标准输出。
代码扩展器
现在让我们编写一个代码解释器,将输入表单扩展为另一种语言。 实际上,如果目标语言是 C,我会在这里编写代表 C 代码的代码。 然后可以将生成的代码漂亮地打印为 C(这往往比直接编写 C 更好)。
(defmethod interpret-let ((i (eql :expand)) e bindings code)
(loop for (a b) in bindings
for v = (interpret i e b)
if (typep v 'var)
collect (cons a v) into new-env
else
collect (list a v) into new-bindings
finally
(return
`(c/block
;; remove VAR instances (they are expanded at compile-time)
(c/declare,new-bindings),@(interpret-body i (append new-env e) code)))))
(defmethod interpret-body ((i (eql :expand)) e code)
(loop for f in code collect (interpret i e f)))
(defmethod interpret-loop ((i (eql :expand)) e v body)
(let ((var (cdr (assoc v e))))
(assert var)
`(c/for (i (< i,(var-x var)) (++ i))
(c/for (j (< j,(var-y var)) (++ j)),@(interpret-body i
(augment-env i e `((i i) (j j)))
body)))))
(defmethod interpret ((i (eql :expand)) e (code symbol))
code)
调用 Lisp 的最后一部分可能对您的情况并不重要,但假设我们有一种方法可以在我们的 C 代码中调用 Lisp 解释器,该解释器可以将变量绑定列表作为参数。
(defmethod interpret-lisp ((i (eql :expand)) e form)
`(c/lisp-lexenv-eval,(princ-to-string form),(loop for (a . b) in e
when (symbolp b)
append (list (string a) b))))
使用这个扩展器,结果会有所不同:
(interpret :expand
nil
'(let ((v1 (make-var 10 5)))
(loop-on v1 (lisp (print (list i j))))))
生成的代码为:
(C/BLOCK (C/DECLARE NIL)
(C/FOR (I (< I 10) (++ I))
(C/FOR (J (< J 5) (++ J))
(C/LISP-LEXENV-EVAL "(PRINT (LIST I J))"
("I" I "J" J)))))
有了漂亮的打印机,您就可以发出相应的 C 代码。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。