Monday, December 26, 2011

Making a emacs lisp expression expand itself to XML

My response to the blog article An Emacs Programming Challenge

The goal is to make the emacs lisp record below execute itself and produce XML. My lisp has gotten rusty so it took me a couple of hours to get this working, but I think I got there...

You can see it properly formatted and coloured here

It's fairly straightforward. The only complexity is the use of lexical-let. emacs lisp is dynamically scoped, so in the function I create for each keyword, where it prints the symbol name is a free variable at runtime. So if sym-name is not defined you'll get an error, otherwise you'll get whatever it is defined as instead of the value you wanted.

By using lexical-let any variables are bound lexically, that is stored in an environment that stays with the function we create when it is executed (a closure).
(require 'cl) ;; uses lexical-let

(defun make-xml-izer(name)
"Given a symbol NAME this makes a function that outputs an xml string for that
symbol and using fset binds it to the same symbol so it becomes executable.
This pollutes the global function namespace so be careful with which names you
pass in"
(lexical-let ((sym-name name))
(fset (intern name)
(lambda(&rest input)
"returns a string representing the xml encoding of the input sexp"
(let ((res (format "<%s>" sym-name)))
(dolist (item input)
(if (listp item)
(setf res (concat res (eval item)))
(setf res (format "%s%s" res item))))
(setf res (format "%s" res sym-name))
res)))))

;; executing this makes all the symbols in the list below executable
(mapcar (lambda(s)
(make-xml-izer
(symbol-name s)))
'(record date millis sequence logger level class method thread emessage exception frame line))

;; the sample record
(record
(date "2005-02-21T18:57:39")
(millis 1109041059800)
(sequence 1)
(logger nil)
(level 'SEVERE)
(class "java.util.logging.LogManager$RootLogger")
(method 'log)
(thread 10)
(emessage "A very very bad thing has happened!")
(exception
(emessage "java.lang.Exception")
(frame
(class "logtest")
(method 'main)
(line 30))))