Page 1 of 3

Replacing a function in an expression

Posted: Sun Nov 11, 2012 3:34 am
by stackman
I am trying to understand the "code-is-data" motto in common lisp.
So I have an expression and I want to replace all occurrences of the function list by my-list, as follows.

Code: Select all

(defparameter *expr* '(let ((list (quote (1 2 3)))) ;; comment list
                         (append list (list 4 5 'list "list, 'list, LIST"))))
(do-replacement  *expr*)
=>(LET ((LIST '(1 2 3)))
  (APPEND LIST (MY-LIST 4 5 'LIST "list, 'list, LIST")))
It seems that using the subst or subst-if function is a good idea,
but how do I check if the symbol list is used as a function in the expression?

Re: Replacing a function in an expression

Posted: Sun Nov 11, 2012 4:57 am
by Konfusius

Code: Select all

(defun do-replacement (expr)
  (setf expr (macroexpand expr))
  (cond ((atom expr) expr)
		((eq 'list (car expr)) (cons 'my-list (mapcar #'do-replacement (cdr expr))))
		((special-operator-p (car expr))
        (case (car expr)
         ; special forms need special handling
         (quote expr)
         (let (list* 'let
                  (mapcar
                    (lambda (bind)
                    (if (symbolp bind)
                     bind
                     (list (car bind) (do-replacement (cadr bind)))))
                  (cadr expr))
                 (mapcar #'do-replacement (cddr expr))))
         (t (error "special operator ~s not supported" (car expr)))))
      (t (mapcar #'do-replacement expr))))

(do-replacement
  '(let ((list '(list 1 2 3)))
   (append list (list 4 5 'list "list, 'list, LIST"))))
Unfortunately there is no implementation independent way to do this since every implementations has it's own special forms. But usually the number of special forms is small wich makes it feasible to write different versions for different implementations.

Re: Replacing a function in an expression

Posted: Sun Nov 11, 2012 5:54 am
by Goheeca
I think in a real situation macrolet is more appropriate and if we want to implement do-replacement I'd do simply this:

Code: Select all

(defun do-replacement (expr)
  `(macrolet ((list (&rest args) `(my-list ,@args))) ,expr))
Yeah, if expr contains shadowing of list, it isn't replaced, but it's almost always the right thing.
You can also use macroexpand-dammit (it's available via quicklisp) to see what it's doing:

Code: Select all

(defun do-replacement (expr)
  (macroexpand-dammit:macroexpand-dammit
    `(macrolet ((list (&rest args) `(my-list ,@args))) ,expr)))
Konfusius your solution doesn't work for me, I get an output the same as an input.

Re: Replacing a function in an expression

Posted: Sun Nov 11, 2012 1:21 pm
by Konfusius
Goheeca wrote:Konfusius your solution doesn't work for me, I get an output the same as an input.
I've corrected the bug. It should work, now. But your solution is better.

Re: Replacing a function in an expression

Posted: Sun Nov 11, 2012 2:02 pm
by stackman
Unfortunately, the code provided by Goheeca does not work for me.
I never used the macrolet operator before, maybe someone can point to a beginner friendly
introduction to macrolet? I have used macros before, but I don't understand the macrolet section
of the hyperspec at the moment.

Code: Select all

CL-USER>  (defun do-replacement (expr)
                   `(macrolet ((list (&rest args) `(my-list ,@args))) ,expr))
(do-replacement  '(let ((list (quote (1 2 3)))) ;; comment list
                         (append list (list 4 5 'list "list, 'list, LIST"))))
=> (MACROLET ((LIST (&REST ARGS)
             `(MY-LIST ,@ARGS)))
  (LET ((LIST '(1 2 3)))
    (APPEND LIST (LIST 4 5 'LIST "list, 'list, LIST"))))

Re: Replacing a function in an expression

Posted: Sun Nov 11, 2012 2:15 pm
by Goheeca
As let is for lexical variables, as flet is for functions, the macrolet is for macros.
An example:

Code: Select all

(defun add (&rest args) (apply #'+ args))
(defun multiply (&rest args) (apply #'* args))
(add 2 3)
(macrolet ((add (&rest args) `(multiply ,@args))) (add 2 3))
// If I used + instead of add, it would complain about a package lock.

Re: Replacing a function in an expression

Posted: Sun Nov 11, 2012 2:26 pm
by Paul
Konfusius wrote:Unfortunately there is no implementation independent way to do this since every implementations has it's own special forms. But usually the number of special forms is small wich makes it feasible to write different versions for different implementations.
No. The special forms/operators are defined in the standard. Implementations are permitted to implement other macros as special operators, but must also supply macro definitions, so a code-walker doesn't have to know about them.

Re: Replacing a function in an expression

Posted: Sun Nov 11, 2012 2:32 pm
by stackman
Thanks for the answer, Goheeca, still I fail to see how it addresses the question.
macrolet does expansion then evaluation, doesn't it?
I want to transform the source code without evaluating it, in order to modify programmatically source files.

Re: Replacing a function in an expression

Posted: Sun Nov 11, 2012 2:39 pm
by stackman
@ Paul
Are there implementation independant common lisp code walkers?
A quick google search suggests that there are a few edge cases which are not so easy to handle.

I am using sbcl, so maybe there is a way to leverage some sbcl compatible code walker
to deal with code substitution, without having to roll my own?

Re: Replacing a function in an expression

Posted: Sun Nov 11, 2012 3:47 pm
by Goheeca
Check the version with macroexpand-dammit out, but it also expands into the implementation-dependent code because of compiler macros (for example append with two args into sb-impl::append2 under SBCL). Certainly you can be inspired by the source code of macroexpand-dammit for your version without compiler-macro expanding - I won't help you with this.
Moreover, I'd ask is it an ad hoc issue or you just need it?
In the first case I'd still let the wrapping macrolet in a source code.