Macro-writing macro - interpreting dynamic keyword arguments for use in 2nd-level macro - lisp

This is a discussion on Macro-writing macro - interpreting dynamic keyword arguments for use in 2nd-level macro - lisp ; Hi, I'm working on a macro that does substantial lifting. I've hit a snag, however, and even though I can see where the issue is I know that I don't fully understand the computational reason for the failure, or else ...

+ Reply to Thread
Results 1 to 3 of 3

Macro-writing macro - interpreting dynamic keyword arguments for use in 2nd-level macro

  1. Default Macro-writing macro - interpreting dynamic keyword arguments for use in 2nd-level macro

    Hi,

    I'm working on a macro that does substantial lifting. I've hit a snag,
    however, and even though I can see where the issue is I know that I
    don't fully understand the computational reason for the failure, or
    else I would know either (a) I'm trying the impossible, or (b) how to
    overcome the problem.

    Anyway, introduction aside, here's the issue.

    I have a macro (MKINSTANCER) that can inspect a lists (one might be
    called OB1) expressed in my own grammar, and based on it will
    construct another macro (e.g. OB1-INSTANCER) with keyword arguments
    based on the slots defined by OB1 (basically constructing an object
    and setting slot values according to the keyword arguments passed to
    OB1-INSTANCER).
    To enhance MACX, I want it to insert logic into resulting macros that
    it writes, to only process keyword arguments if the user supplied
    values.

    The problem is that the expressions I've written don't evaluate to the
    values supplied as keyword values, but instead try to evaluate unbound
    variables with the same names (symbols?) you would usually use to pick
    up the values of the keyword arguments.

    Here is a version of MKINSTANCER that highlights the problem:

    (defmacro mkinstancer (instancer-name)
    (let* ((keyw-args `((HEIGHT NIL HEIGHT-SUPPLIED-P) (WIDTH NIL WIDTH-
    SUPPLIED-P)))
    (keyw-args-q (mapcar #'(lambda (x) (list 'quote x)) keyw-args)))
    `(defmacro ,instancer-name (&key ,@keyw-args)
    `(progn
    ,@(mapcar #'(lambda (val)
    `(princ ,(first (second val))))
    (quote ,keyw-args-q))))))
    ...............................................................................

    What this would generate when called is shown by the following
    macroexpansion:

    (macroexpand-1 `(mkinstancer instancer1))
    (DEFMACRO INSTANCER1
    (&KEY (HEIGHT () HEIGHT-SUPPLIED-P) (WIDTH () WIDTH-SUPPLIED-
    P))
    `(PROGN
    ,@(MAPCAR #'(LAMBDA (VAL) `(PRINC ,(FIRST (SECOND VAL))))
    '('(HEIGHT NIL HEIGHT-SUPPLIED-P)
    '(WIDTH NIL WIDTH-SUPPLIED-P)))))

    ...............................................................................

    I then create my "2nd-level" macro like so:

    (mkinstancer instancer1)

    And this has the following sample macroexpansion:
    (macroexpand-1 `(instancer1 :height 45 :width 90))
    (PROGN (PRINC HEIGHT) (PRINC WIDTH))

    ...............................................................................

    I am trying to get this to be instead: "(PROGN (PRINC 45) (PRINC
    90))".

    Why I actually call the macro "instancer1", I get the following
    backtrace (showing I'm referencing unbound variable HEIGHT):

    The variable HEIGHT is unbound.
    [Condition of type UNBOUND-VARIABLE]

    Restarts:
    0: [ABORT] Return to SLIME's top level.
    1: [TERMINATE-THREAD] Terminate this thread (#<THREAD "repl-
    thread" {AF8C939}>)

    Backtrace:
    0: (SB-INT:SIMPLE-EVAL-IN-LEXENV HEIGHT #<NULL-LEXENV>)
    1: (SB-INT:SIMPLE-EVAL-IN-LEXENV (PRINC HEIGHT) #<NULL-LEXENV>)
    2: (SB-IMPL::SIMPLE-EVAL-PROGN-BODY
    ((PRINC HEIGHT) (PRINC WIDTH))
    #<NULL-LEXENV>)
    3: (SB-INT:SIMPLE-EVAL-IN-LEXENV
    (INSTANCER1 :HEIGHT 45 :WIDTH 90)
    #<NULL-LEXENV>)
    --more--

    ========================

    I know this has been a long post but I hope I've expressed the
    situation clearly enough for someone to suggest a path to
    illumination.

    I'm using SBCL 1.0.7 on Linux (Ubuntu Gutsy Gibbon).

    Joubert

    PS: In my wanderings I've experimented with different combinations of
    symbol-value, find-symbol, etc. in the (mapcar) expression because I
    thought the problem is a symbol lookup mismatch.


  2. Default Re: Macro-writing macro - interpreting dynamic keyword arguments for use in 2nd-level macro

    In article <1193713558.885107.248830@v3g2000hsg.googlegroups.com>,
    Joubert Nel <joubert.nel@gmail.com> wrote:

    > Hi,
    >
    > I'm working on a macro that does substantial lifting. I've hit a snag,
    > however, and even though I can see where the issue is I know that I
    > don't fully understand the computational reason for the failure, or
    > else I would know either (a) I'm trying the impossible, or (b) how to
    > overcome the problem.
    >
    > Anyway, introduction aside, here's the issue.
    >
    > I have a macro (MKINSTANCER) that can inspect a lists (one might be
    > called OB1) expressed in my own grammar, and based on it will
    > construct another macro (e.g. OB1-INSTANCER) with keyword arguments
    > based on the slots defined by OB1 (basically constructing an object
    > and setting slot values according to the keyword arguments passed to
    > OB1-INSTANCER).
    > To enhance MACX, I want it to insert logic into resulting macros that
    > it writes, to only process keyword arguments if the user supplied
    > values.
    >
    > The problem is that the expressions I've written don't evaluate to the
    > values supplied as keyword values, but instead try to evaluate unbound
    > variables with the same names (symbols?) you would usually use to pick
    > up the values of the keyword arguments.
    >
    > Here is a version of MKINSTANCER that highlights the problem:
    >
    > (defmacro mkinstancer (instancer-name)
    > (let* ((keyw-args `((HEIGHT NIL HEIGHT-SUPPLIED-P) (WIDTH NIL WIDTH-
    > SUPPLIED-P)))
    > (keyw-args-q (mapcar #'(lambda (x) (list 'quote x)) keyw-args)))
    > `(defmacro ,instancer-name (&key ,@keyw-args)
    > `(progn
    > ,@(mapcar #'(lambda (val)
    > `(princ ,(first (second val))))
    > (quote ,keyw-args-q))))))
    > ..............................................................................
    >
    > What this would generate when called is shown by the following
    > macroexpansion:
    >
    > (macroexpand-1 `(mkinstancer instancer1))
    > (DEFMACRO INSTANCER1
    > (&KEY (HEIGHT () HEIGHT-SUPPLIED-P) (WIDTH () WIDTH-SUPPLIED-
    > P))
    > `(PROGN
    > ,@(MAPCAR #'(LAMBDA (VAL) `(PRINC ,(FIRST (SECOND VAL))))
    > '('(HEIGHT NIL HEIGHT-SUPPLIED-P)
    > '(WIDTH NIL WIDTH-SUPPLIED-P)))))
    >



    You may want to expand that into this version:

    (DEFMACRO INSTANCER1 (&KEY (HEIGHT () HEIGHT-SUPPLIED-P) (WIDTH () WIDTH-SUPPLIED-P))
    `(PROGN
    ,@(MAPCAR #'(LAMBDA (VAL) `(PRINC ,(FIRST VAL)))
    `((,HEIGHT NIL ,HEIGHT-SUPPLIED-P)
    (,WIDTH NIL ,HEIGHT-SUPPLIED-P)))))

    Your code does not reference the macro's variables. The
    symbols HEIGHT, WIDTH, HEIGHT-SUPPLIED-P and HEIGHT-SUPPLIED-P
    are inside literal lists.

    (defmacro mkinstancer (instancer-name)
    (let* ((keyw-args '((HEIGHT NIL HEIGHT-SUPPLIED-P) (WIDTH NIL WIDTH-SUPPLIED-P)))
    (keyw-args-list (loop for (var default supplied-p) in keyw-args
    collect `(list ,var ,default ,supplied-p))))
    `(defmacro ,instancer-name (&key ,@keyw-args)
    `(progn
    ,@(mapcar #'(lambda (val)
    `(princ ,(first val)))
    (list ,@keyw-args-list))))))

    Check if this does what you want...

    > ..............................................................................
    >
    > I then create my "2nd-level" macro like so:
    >
    > (mkinstancer instancer1)
    >
    > And this has the following sample macroexpansion:
    > (macroexpand-1 `(instancer1 :height 45 :width 90))
    > (PROGN (PRINC HEIGHT) (PRINC WIDTH))
    >
    > ..............................................................................
    >
    > I am trying to get this to be instead: "(PROGN (PRINC 45) (PRINC
    > 90))".
    >
    > Why I actually call the macro "instancer1", I get the following
    > backtrace (showing I'm referencing unbound variable HEIGHT):
    >
    > The variable HEIGHT is unbound.
    > [Condition of type UNBOUND-VARIABLE]
    >
    > Restarts:
    > 0: [ABORT] Return to SLIME's top level.
    > 1: [TERMINATE-THREAD] Terminate this thread (#<THREAD "repl-
    > thread" {AF8C939}>)
    >
    > Backtrace:
    > 0: (SB-INT:SIMPLE-EVAL-IN-LEXENV HEIGHT #<NULL-LEXENV>)
    > 1: (SB-INT:SIMPLE-EVAL-IN-LEXENV (PRINC HEIGHT) #<NULL-LEXENV>)
    > 2: (SB-IMPL::SIMPLE-EVAL-PROGN-BODY
    > ((PRINC HEIGHT) (PRINC WIDTH))
    > #<NULL-LEXENV>)
    > 3: (SB-INT:SIMPLE-EVAL-IN-LEXENV
    > (INSTANCER1 :HEIGHT 45 :WIDTH 90)
    > #<NULL-LEXENV>)
    > --more--
    >
    > ========================
    >
    > I know this has been a long post but I hope I've expressed the
    > situation clearly enough for someone to suggest a path to
    > illumination.
    >
    > I'm using SBCL 1.0.7 on Linux (Ubuntu Gutsy Gibbon).
    >
    > Joubert
    >
    > PS: In my wanderings I've experimented with different combinations of
    > symbol-value, find-symbol, etc. in the (mapcar) expression because I
    > thought the problem is a symbol lookup mismatch.


  3. Default Re: Macro-writing macro - interpreting dynamic keyword arguments for use in 2nd-level macro

    Joubert Nel <joubert.nel@gmail.com> writes:

    > I have a macro (MKINSTANCER) that can inspect a lists (one might be
    > called OB1) expressed in my own grammar, and based on it will
    > construct another macro (e.g. OB1-INSTANCER) with keyword arguments
    > based on the slots defined by OB1 (basically constructing an object
    > and setting slot values according to the keyword arguments passed to
    > OB1-INSTANCER).


    OK, the first question that comes up is why you are constructing a
    second macro instead of having the first macro construct functions.
    What is it that you need a macro for in the second case? It may be you
    can solve your real problem more simply with a function.

    > To enhance MACX, I want it to insert logic into resulting macros that
    > it writes, to only process keyword arguments if the user supplied
    > values.


    OK. But this may not require a macro. If you really do need to wait
    for run-time values (rather than macro-expand time values), you would
    need a function anyway.

    > The problem is that the expressions I've written don't evaluate to the
    > values supplied as keyword values, but instead try to evaluate unbound
    > variables with the same names (symbols?) you would usually use to pick
    > up the values of the keyword arguments.
    >
    > Here is a version of MKINSTANCER that highlights the problem:
    >
    > (defmacro mkinstancer (instancer-name)
    > (let* ((keyw-args `((HEIGHT NIL HEIGHT-SUPPLIED-P) (WIDTH NIL WIDTH-
    > SUPPLIED-P)))
    > (keyw-args-q (mapcar #'(lambda (x) (list 'quote x)) keyw-args)))
    > `(defmacro ,instancer-name (&key ,@keyw-args)
    > `(progn
    > ,@(mapcar #'(lambda (val)
    > `(princ ,(first (second val))))
    > (quote ,keyw-args-q))))))


    I'm not sure why you create the quoted version of the keyword arguments
    list. It just seems to get in your way, since the (second val) is just
    used to extract the value from the second level of quoting that you
    introduced. What about this:

    (defmacro mkinstancer (instancer-name)
    (let ((keyw-args '((HEIGHT NIL HEIGHT-SUPPLIED-P)
    (WIDTH NIL WIDTH- SUPPLIED-P))))
    `(defun ,instancer-name (&key ,@keyw-args)
    ,@(mapcar #'(lambda (val) `(princ ,(first val)))
    keyw-args))))


    If you do end up going the route of having nested backquotes (like you
    do in your defmacro form), you will need to think carefully about the
    proper order of evaluation. You will often see constructs like ,, or
    ,', in the inside of the inner-most backquoted form to assure proper
    evaluation or non-evaluation. That part of the specification needs to
    be looked at carefully (or you can use trial and error with macroexpand).


    > What this would generate when called is shown by the following
    > macroexpansion:
    >
    > (macroexpand-1 `(mkinstancer instancer1))
    > (DEFMACRO INSTANCER1
    > (&KEY (HEIGHT () HEIGHT-SUPPLIED-P) (WIDTH () WIDTH-SUPPLIED-
    > P))
    > `(PROGN
    > ,@(MAPCAR #'(LAMBDA (VAL) `(PRINC ,(FIRST (SECOND VAL))))
    > '('(HEIGHT NIL HEIGHT-SUPPLIED-P)
    > '(WIDTH NIL WIDTH-SUPPLIED-P)))))


    Your problem here is that you want the MAPCAR to have occurred during
    the expansion process that producs this macro, instead of happening
    after the expansion. That is caused by not enough evaluation when
    building the template. If you really need it to be a macro, then you
    would need to arrange to evaluate the MAPCAR form earlier.

    > And this has the following sample macroexpansion:
    > (macroexpand-1 `(instancer1 :height 45 :width 90))
    > (PROGN (PRINC HEIGHT) (PRINC WIDTH))
    >
    > I am trying to get this to be instead: "(PROGN (PRINC 45) (PRINC

    90))".

    The best approach to building this, is to take a bottom-up approach.
    First figure out what your inner macro wants to look like:

    (defmacro instancer1 (&key ...)
    `(progn (princ ,height) (princ ,width)))

    Then you need to figure out how to produce that body. I find it easier
    to do the construction outside of the backquote. Multiple levels of
    backquoting are tough enough to figure out without adding additional
    things that need to be computed. So pull the MAPCAR out of the
    backquote and put it in the part of the first macro that executes.
    This in and of itself needs two levels of backquote, so it would get
    really difficult to understand if inside the other body:

    (defmacro mkinstancer (instancer-name)
    (let* ((keyw-args '((HEIGHT NIL HEIGHT-SUPPLIED-P)
    (WIDTH NIL WIDTH- SUPPLIED-P)))
    (body (mapcar #'(lambda (val) ``(princ ,,(first val)))
    keyw-args)))
    `(defmacro ,instancer-name (&key ,@keyw-args)
    `(progn ,,@body))))

    But as I said at the start, it isn't clear that you really want a macro
    here. I think a function would do just as well. Even better, in fact,
    since I think you really do want to have the keyword values evaluated.

    So, try the version with a function first and only go to this level of
    complication if you really need it.


    > PS: In my wanderings I've experimented with different combinations of
    > symbol-value, find-symbol, etc. in the (mapcar) expression because I
    > thought the problem is a symbol lookup mismatch.


    It's not really a symbol lookup mismatch, but rather a problem with
    evaluation and on-evaluation of the forms. You need to arrange to have
    the proper form available when you want things to happen.


    --
    Thomas A. Russ, USC/Information Sciences Institute

+ Reply to Thread

Similar Threads

  1. Replies: 2
    Last Post: 10-09-2007, 03:25 AM
  2. EXCEL - writing a macro
    By Application Development in forum DOTNET
    Replies: 0
    Last Post: 03-28-2007, 09:56 AM
  3. how to extract LaTeX macro arguments?
    By Application Development in forum Perl
    Replies: 4
    Last Post: 12-25-2003, 10:42 PM