Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
226 views
in Technique[技术] by (71.8m points)

evaluate an argument of Common Lisp macro

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?

question from:https://stackoverflow.com/questions/66049149/evaluate-an-argument-of-common-lisp-macro

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...