Search code examples
functional-programmingprogramming-languagescommon-lispmetaprogramming

Programming language/platform with runtime access to the AST


I'm looking to implement some proof-of-concept demo for a short presentation in which the running code is aware of a hashed 'value' of the currently executing block of code. For example:

function BBB(a) {
  a = 2 * a;
  print me.hash;          --> "xxxxxxx" (value of BBB-syntax represenation)
  return a;                              
}

function AAA(a, b, c) {
  d = BBB(a);
  print me.hash;          --> "yyyyyyy" (value of AAA-Syntax representation, possibly dependant on value of BBB, but not necessary)
  return d;
}

I instinctively turned to LISPish languages, but had no success with Scheme yet. And I've not been in touch with Common LISP for a long time, which I suspect might be able to do it (hints appreciated). It doesn't have to be fast, or a popular platform, can be the most academic and weird platform available. It's just a demo.

Does anybody know a language/platform that can do this pretty much out of the box or with relatively little tinkering? I'd prefer some kind of parsed/treeish thing to work with, not actual source code.


Solution

  • You guessed it. Common Lisp can do it pretty easily:

    (defmacro reflective-defun (name args &body body)
      (let ((source-form `(reflective-defun ,name ,args ,@body)))
        `(let ((me ',source-form))
           (defun ,@(cdr source-form)))))
    
    (reflective-defun bbb (a)
      (setf a (* 2 a))
      (print me)
      a)
    
    (reflective-defun aaa (a b c)
      (let ((d (bbb a)))
        (print me)
        d))
    
    (aaa 12 :x :y)
    

    Output:

    (REFLECTIVE-DEFUN BBB
        (A)
      (SETF A (* 2 A))
      (PRINT ME)
      A) 
    (REFLECTIVE-DEFUN AAA
        (A B C)
      (LET ((D (BBB A)))
        (PRINT ME)
        D)) 
    24
    

    Here's how you can write a self-redefining function:

    (defun recursive-replace (tree what with)
      "Walks down the TREE and replaces anything that is EQUALP to WHAT with WITH."
      (cond ((equalp tree what)
             with)
            ((listp tree)
             (loop for item in tree
                  collect (recursive-replace item what with)))
            (t tree)))
    
    (reflective-defun ccc (a b c)
      (let ((d (bbb a)))
        (print me)
        (if (eql b :use-me-from-now-on)
            (eval (recursive-replace me '(bbb a) '(bbb b))))
        d))
    

    Incidentally, Scheme (and any language where the macros are hygienic) will fight you tooth and nail to prevent you from creating an identifier called me that can be referenced by source code passed to a macro.