您的目标是将一些代码编译成另一种语言。
几乎不需要宏来做到这一点,您可以采用结构化表达式并生成另一种树,或者直接将外语代码作为字符串发出。
有点令人困惑的是,你的代码看起来像 Lisp,但实际上并不像 Lisp 那样工作,因为你想要loop-on
能够检查其词法环境以了解某些符号的编译时绑定。
标准中没有指定这一点,即使您可以在实现中找到一种方法来访问它,例如使用&environment
宏中的关键字,那么该解决方案将不可移植。
无论如何,您都必须指定如何将 Lisp 形式转换为 C,这意味着您必须实现一个解释器(就其一般含义而言,即对代码进行解释)。
如果您有简单的要求,一种简单的方法是make-var
and loop-on
数据的构造函数。
让我们定义一下var
and 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
slot 满足你想要支持的 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
用于模式匹配。输入代码与已知语法进行匹配,并将行为委托给通用函数,这些函数根据您正在构建的解释器的类型进行调度;
Then 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 代码。