评估Common Lisp宏的参数 [英] evaluate an argument of Common Lisp macro

查看:52
本文介绍了评估Common Lisp宏的参数的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想创建一个宏,该宏的行为将取决于其参数之一.例如:

 (defclass myvar()((l:initarg:l:读者l)))(defparameter mv1(make-instance'myvar:l 10))(defmacro mac1(v)`(progn,@(x循环直到(eval`(l,v))收集`(格式t〜A〜%",x))))(mac1 mv1)1个2个345678910 

这很好.但是,如果我尝试对局部变量执行相同的操作:

 (让((mv2(make-instance'myvar:l 10)))(mac1 mv2)) 

我遇到了错误:

 变量MV2是未绑定的.它是一个局部变量,在编译时不可用. 

有没有一种方法可以评估宏中的局部变量?

解决方案

您的目标是将一些代码编译成另一种语言.几乎不需要宏,您可以使用结构化表达式并生成另一种树,也可以直接将外语代码作为字符串发出.

有点令人困惑的是,您的代码看起来像Lisp,但实际上却不像Lisp,因为您希望 loop-on 能够检查其词法环境以了解编译的内容-时间绑定对于某些符号而言是适当的.标准中未对此进行指定,即使您在实现中找到一种方法来访问该方法,例如使用宏中的& environment 关键字,该解决方案也无法移植./p>

在任何情况下,您都必须指定如何将Lisp形式转换为C,这意味着您必须实现解释器(按其一般含义,即对代码进行解释).

如果有简单的要求,一种简单的方法是制作 make-var loop-on 数据构造函数.

让我们定义 var loop-on 结构:

 (defstruct(var(:constructor make-var(x y)))x y)(解构循环上的var代码) 

简单的结构

由于我还不知道您想如何利用 body ,所以让我们按原样存储它.那部分需要一个宏,但是在您指定如何编译主体之前,这将不是很有用.

 (defmacro循环(v& body body)`(make-loop-on:var,v:code',body)) 

现在,您的代码可以被评估为Lisp代码:

 (让((v1(make-var 100100)))(在v1上循环(此处为某些代码))) 

结果为:

  #S(LOOP-ON:VAR #S(VAR:X 100:Y 100):CODE((SOME-CODE-HERE))) 

现在,只要:CODE 插槽满足您要支持的Lisp子集,您的编译器就可以将此循环扩展为C代码.

通用解释器

对于较大的项目,您想定义一个知道如何解释代码的代码遍历器.让我们定义6个通用函数,如下所示:

 (defgeneric解释let(解释器环境绑定代码))(defgeneric解释体(解释器环境形式))(defgeneric解释循环(解释器环境变量形式))(defgeneric interpert-lisp(解释器环境表格))(非通用解释器(解释器环境代码)(:方法(我的环境代码)(最佳:ematch代码((list *'让绑定代码)(解释,让我我的环境绑定代码)((列出'make-var x y)(make-var x y))((list *'循环上的v代码)(解释循环我的环境v代码)((列出'lisp表格)(enquire-lisp我的环境表格)))) 

代码依赖于 optima 进行模式匹配.输入代码与已知语法匹配,并且行为被委派给泛型函数,这些泛型函数将根据您正在构建的解释器的类型进行分派;

然后, env 环境变量是一个词法环境,它可以容纳变量名称,函数,类型等的绑定.您也可以在 env 上进行分派,但是这里我们假设这是从符号到值的关联列表.这是为一组给定的绑定扩充环境的方法,每个绑定都是将符号映射到表单的列表:

 (defgeneric扩充环境(解释器环境绑定)(:方法(i env绑定)(nconc(绑定中(n v)的循环收集(cons n(解释i env v)))env))) 

评估者

在这里,我专门介绍名为:eval 的解释器的方法.请注意,我们不是针对类,而是针对关键字.对于更复杂的情况,您可以在解释器中存储一些状态.

 (defmethod解释体((i(eql:eval))e(form缺点))(解构绑定(表单.休息)表单(条件(休息(解释一下表格)(我要休息的身体))(t(解释表格)))))(defmethod解释-let [[i(eql:eval))env绑定代码)(解释主体i(增强环境env绑定)代码))(defmethod interpret-lisp((i(eql:eval))env格式)(eval`(let,(env collect(list a b)中的(a.b)的循环))(声明(ignorable,@(mapcar#'car env))),形式)))(defmethod解释循环((i(eql:eval))e v主体)(让[[var(cdr(assoc v e)))))(执行时间(ii(var-x var))(dotimes(jj(var-y var))(解释我(acons'i ii(acons'j jj e))身体))))) 

使用上述定义,您可以按以下方式解释代码:

 (解释:eval零'(让((v1(make-var 10 5)))(循环v1(lisp(打印(列表i j)))))) 

这将执行双循环并将值打印到标准输出.

代码扩展器

现在让我们编写一个代码解释器,将输入形式扩展为另一种语言.实际上,如果目标语言是C,我将在此处编写表示C代码的代码.然后,可以将生成的代码漂亮地打印为C(这往往比直接编写C更好).

 (defmethod解释-let [[i(eql:expand))e绑定代码)(绑定中的(a b)的循环对于v =(解释i e b)如果(typep v'var)收集(cons v)到new-env别的收集(列出v)到new-bindings中最后(返回`(c/块;;删除VAR实例(它们在编译时进行了扩展)(c/declare,new-bindings),@(解释主体i(附加新环境代码))))))(defmethod解释主体((i(eql:expand))e代码)(代码中f的循环收集(解释e)))(defmethod解释循环((i(eql:expand))e v主体)(让[[var(cdr(assoc v e)))))(断言var)`(c/for(i(< i,(var-x var))(++ i))(c/for(j(< j,(var-y var))(++ j)),@(injection-body i(augment-env i e`(((i i)(j j))))身体)))))(defmethod解释[[i(eql:expand))e(代码符号))代码) 

最后一个调用Lisp的部分对于您的情况可能并不重要,但可以说,我们有一种方法可以在C代码中调用Lisp解释器,该方法可以将变量绑定列表作为参数.

 (defmethod interpret-lisp((i(eql:expand))e形式)`(c/lisp-lexenv-eval,(从字符串到字符串的形式),(在e中的(a.b)循环何时(符号b)追加(列表(字符串a)b)))) 

使用此扩展器,结果将有所不同:

 (解释:expand零'(让((v1(make-var 10 5)))(循环v1(lisp(打印(列表i j)))))) 

生成的代码是:

 (C/BLOCK(C/DECLARE NIL)(C/FOR(我(< I 10)(++ I))(C/FOR(J(< J 5)(++ J))(C/LISP-LEXENV-EVAL(PRINT(LIST I J))")("I","J",J))))) 

有了漂亮的打印机,您就可以发出相应的C代码.

I want to make a macro, which behaviour will depend from one of it's arguments. For example:

(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

This works fine. But if I'm trying to do the same with local variable:

(let ((mv2 (make-instance 'myvar :l 10)))
  (mac1 mv2))

I'm getting the error:

 The variable MV2 is unbound.
 It is a local variable not available at compile-time.

Is there a way to evaluate local variable in macro?

解决方案

Your goal is to compile some code into another language. There is hardly any need for macro to do that, you take a structured expression and produces another kind of tree, or directly emit code in a foreign language as string.

What is a bit confusing is that your code looks like Lisp, but does not really work like Lisp, since you want to have loop-on be able to inspect its lexical environment to know what compile-time binding is in place for some symbol. This is not specified in the standard, and even if you can find a way in your implementation to access that, using for example the &environment keyword in macros, then the solution would not be portable.

In any case, you'll have to specify how to translate Lisp forms as C, which means you have to implement an interpreter (in the general meaning of it, i.e. give an interpretation to code).

One simple way, if you have simple requirements, is to make make-var and loop-on constructors for data.

Let's define var and loop-on structs:

 (defstruct (var (:constructor make-var (x y))) x y)
 (defstruct loop-on var code)

Simple structures

As I don't know yet how you want to exploit the body, let's store it as-is. That part necessitates a macro, but until you specify how you compile the body, this won't be very useful.

(defmacro loop-on (v &body body)
  `(make-loop-on :var ,v :code ',body))

Now, your code can be evaluated as Lisp code:

(let ((v1 (make-var 100 100)))
  (loop-on v1 (some-code-here)))

And the resulting value is:

#S(LOOP-ON :VAR #S(VAR :X 100 :Y 100) :CODE ((SOME-CODE-HERE)))

Now, your compiler can expand this loops as C code, provided the :CODE slot satisfies the subset of Lisp you want to support.

A generic interpreter

For larger projects, you want to define a code walker that knows how to interpret the code. Let's define 6 generic functions, as follows:

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

The code depends on optima for pattern matching. The input code is matched against known syntax and the behavior is delegated to generic functions, which are dispatched according the to type of interpreter you are building;

Then env environment variable is a lexical environment, it can hold bindings for variable names, functions, types, etc. You could dispatch on env too but here we assume this is an association list from symbols to values. Here is how you augment the environment for a set of given bindings, each of them being a list mapping a symbol to a form:

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

An evaluator

Here I specialize the methods for an interpreter named :eval. Notice that we don't match against a class, but against a keyword. For more sophisticated cases you can store some state in the interpreter.

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

With the above definitions, you can interpret your code as follows:

(interpret :eval
           nil
           '(let ((v1 (make-var 10 5)))
             (loop-on v1 (lisp (print (list i j))))))

This performs the double loop and print values to the standard output.

A code expander

Let's now write a code interpreter that expands the input form as a different kind of language. In practice I would write here code that represents C code, if the target language is C. Then the resulting code could be pretty-printed as C (this tends to be better than directly writing 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)

The last part that invokes Lisp is probably not important for your case, but let's say that we have a way to call a Lisp interpreter in our C code that can take an list of variable bindings as a parameter.

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

With this expander, the result would be different:

(interpret :expand
           nil
           '(let ((v1 (make-var 10 5)))
             (loop-on v1 (lisp (print (list i j))))))

The generated code is:

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

With a pretty printer, you could then emit the corresponding C code.

这篇关于评估Common Lisp宏的参数的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆