A couple of days ago, I wrote about Converting S-Expressions To XML in Emacs. That post was occasioned by my recent rereading of Steve Yegge’s The Emacs Problem in which he talks about, among other things, the utility of storing data as a Lisp program. His thesis was that doing so allows you to easily perform all sorts of useful manipulations on your data.
Certainly, as my post on transforming sexprs to XML shows, it’s trivial to convert a log file stored as a Lisp program to XML. But that post didn’t really leverage the fact that the log record from Yegge’s example is (or can be made) executable.
If you look at it again closely, you’ll notice that it’s just a complicated sexpr and therefore executable as long as the “tags” in the function slots are defined as functions.
(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) (message "A very very bad thing has happened!") (exception (message "java.lang.Exception") (frame (class "logtest") (method 'main) (line 30))))
To show how we can make the record transform itself, let’s start by defining the tags to just reproduce the sexpr that they head. The idea is simple. We could, for example, define record
as
(defun record (&rest args) (cons 'record args))
With this definition, an expression such as
(record "abc" 123)
would evaluate to itself. In order to evaluate the full record from Yegge, we need to define functions for each of the function tags. To make that a bit easier we define a helper macro, prself
.
(defmacro prself (f) `(defun ,f (&rest args) (self-print ',f args)))
so that
(prself record)
get transformed to
(defun record (&rest args) (self-print 'record args))
Unfortunately, self-print
is a bit more complicated than our initial example because we have to quote symbols. That is, we want the sexpr
(level 'SEVERE)
to return an sexpr with SEVERE quoted instead of
(level SEVERE)
To do that, we need to quote all the symbols except nil
and t
.
(defun self-print (f args) (cons f (mapcar (lambda (a) (if (and (symbolp a) (not (eq a nil)) (not (eq a t))) `(quote ,a) a)) args)))
Next we turn record
, date
, millis
, …, line
into functions with (prself record)
, (prself date)
, (prself millis)
, …, (prself line)
. At this point, the record is executable, and if we execute it directly we get
(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) (MESSAGE "A very very bad thing has happened!") (EXCEPTION (MESSAGE "java.lang.Exception") (FRAME (CLASS "logtest") (METHOD 'MAIN) (LINE 30))))
That’s nice, but not very interesting. But suppose we want to transform the milliseconds field (millis
) into seconds. We can do that by redefining millis
as
(defun millis (m) `(seconds ,(/ m 1000.0)));
and if we execute record
again, we get
(RECORD (DATE "2005-02-21T18:57:39") (SECONDS 1.109041E+9) (SEQUENCE 1) (LOGGER NIL) (LEVEL 'SEVERE) (CLASS "java.util.logging.LogManager$RootLogger") (METHOD 'LOG) (THREAD 10) (MESSAGE "A very very bad thing has happened!") (EXCEPTION (MESSAGE "java.lang.Exception") (FRAME (CLASS "logtest") (METHOD 'MAIN) (LINE 30))))
Again, that’s a trivial transformation, but it shows how easy it is to transform fields. Also notice that if we had a whole log of the form
(log (record (date...)...) (record (date...)...) ... (record (date...)...))
and ran prself
on log
, then we could transform the whole log at once without worrying about writing any looping code or anything else. Just execute the (log ...)
sexpr and you’re done.
This is a long post but it shows the power of having your data be executable. Incidentally, I wrote this code using Common Lisp but the same functions would work just fine in Emacs Lisp.