debuggingcommon-lispdefmacro

Undefined variable: COMMON-LISP-USER::0.25% in macro when I expected the symbol to not be evaluated


I have a macro to define initialization variables to the program. The macro is ...

(defmacro defparm (var value &key (frequency nil) (date nil date-supplied-p))
  "Macro to define starting parameters."
  `(let ((new-value (set-value ,value ,frequency)))
     (cond ((eq ,date-supplied-p t) 
             (set-variation ',var new-value (make-date ,date)))
           (t (setf (slot-value *my-parms* ',var) new-value)))))

This macro depends on macros set-value and set-variation, and functions is-date-p and is-percent-p.

(defun is-percent-p (value)
  (cond ((and (symbolp value) (char= (char (symbol-name value) (1- (length (symbol-name value)))) #\%)))
        ((and (stringp value) (char= (char value (1- (length value))) #\%)))
        (t nil)))

(defmacro set-value (value &optional frequency)
  `(cond ((and (typep ',value 'symbol) (is-percent-p ',value))
           (let ((new-value (string-right-trim "%" (symbol-name ',value))))
             (/ (if (find #\. new-value)
                    (parse-float:parse-float new-value)
                    (parse-integer new-value :junk-allowed nil)) 100.0)))
         (t ,value)))

I haven't included is-date-p because it isn't relevant in this situation, and set-variation can simply be dummied out ...

(defun set-variation (&rest x)
  (declare (ignorable x)))

When I evaluate ...

(defparm interest-rate 5%)

I get this ...

(defparm interest-rate 5%) ; in: DEFPARM INTEREST-RATE ; (SET-VALUE 5% NIL) ; --> IF IF AND IF TYPEP ; ==> ; 1 ; ; caught WARNING: ; undefined variable: COMMON-LISP-USER::5% ; ; compilation unit finished ; Undefined variable: ; 5% ; caught 1 WARNING condition 0.05 0.05

The result is correct, 0.05, but I don't understand why I get the warning.

I have put in (print nn) statements in an attempt to pin down where the warning occurs, where the '5% symbol is evaluated, but I can't find it.

Where in my code is the actual point of the "warning"?!


Solution

  • If we expand your expression fully ("walk"), then we get something like this:

    
    (LET ((NEW-VALUE
           (COND ((IF (TYPEP '5% 'SYMBOL) (IS-PERCENT-P '5%) NIL)
                  (LET ((NEW-VALUE (STRING-RIGHT-TRIM "%" (SYMBOL-NAME '5%))))
                    (/ (IF (FIND #\. NEW-VALUE)
                           (PARSE-FLOAT NEW-VALUE)
                           (PARSE-INTEGER NEW-VALUE :JUNK-ALLOWED NIL))
                       100.0)))
                 (T 5%))))
      (COND ((EQ NIL T) (SET-VARIATION 'INTEREST-RATE NEW-VALUE (MAKE-DATE NIL)))
            (T (CLOS::SET-SLOT-VALUE *MY-PARMS* 'INTEREST-RATE NEW-VALUE))))
    

    You can see that in your first cond there is a clause (t 5%). That's where 5% is a variable.

    Typically I would debug these things by macro expanding the corresponding code snippets (and optionally a compiler would give me undefined variable locations, etc.):

    CL-USER 9 > (pprint (macroexpand-1 '(SET-VALUE 5% NIL)))
    
    (COND ((AND (TYPEP '5% 'SYMBOL) (IS-PERCENT-P '5%))
           (LET ((NEW-VALUE (STRING-RIGHT-TRIM "%" (SYMBOL-NAME '5%))))
             (/ (IF (FIND #\. NEW-VALUE)
                    (PARSE-FLOAT NEW-VALUE)
                  (PARSE-INTEGER NEW-VALUE :JUNK-ALLOWED NIL))
                100.0)))
          (T 5%))