Solution To The Emacs Programming Challenge

A few days ago, I posed an Elisp programming challenge that asked you to make the 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!")
    (emessage "java.lang.Exception")
      (class "logtest")
      (method 'main)
      (line 30))))

executable in such a way that it transforms itself into the equivalent XML record when executed.

On the day I posed the challenge, I woke up thinking that making the record transform itself into XML was the proper way to turn it into XML rather than the way I described in Converting S-Expression To XML In Emacs. It seemed like a direct application of the ideas in my More Fun With Log Files Stored As Lisp post and I decided to program it just for fun with no idea of turning it into a post.

A couple of hours later I was still flailing around so maybe the application wasn't as direct as I thought. Actually, the idea is the same but because you're not transforming it back into Lisp it's a little trickier.

In any event, here's how I solved the problem. First define all the tags except record to be functions of the form

(defun date (&rest args) (toxml 'date args))

As I explained in the Exploratory Programming In Emacs post that involved little more than copying the list of tags and a keyboard macro. The toxml function is

(defun toxml (tag args)
    (princ (format "\n<%s>" tag))
    (mapc 'princ args)
    (princ (format "</%s>" tag))))

The idea is that each sexpr returns a string of the arguments wrapped in <tag>…</tag> where tag is the symbol in the function slot of the sexpr. That is, (date "2005-02-21T18:57:39") is transformed to "<date>2005-02-21T18:57:39</date>". If we temporarily alias record to list and executed the record sexpr, we get

<date>2005-02-21T18:57:39</date>" "
<millis>1109041059800</millis>" "
<sequence>1</sequence>" "
<logger>nil</logger>" "
<level>SEVERE</level>" "
<class>java.util.logging.LogManager$RootLogger</class>" "
<method>log</method>" "
<thread>10</thread>" "
<emessage>A very very bad thing has happened!</emessage>" "

which shows what the record function will see when it gets called. Note that each string begins with a newline so the " " at the end of each line is a close quote followed by an open quote.

Finally, we need to define a record function to stitch the strings together and get rid of that annoying nil in <logger>nil</logger>.

(defun record (&rest args)
  (princ "<record>")
  (princ (replace-regexp-in-string ">nil<" "><" (apply 'concat args)))
  (princ "</record>"))

The princ on the third line gets rid of the quote marks that the concat places around the final result of the apply.

Now when we execute the record we get

<emessage>A very very bad thing has happened!</emessage>

Update: Let me mention again that the inspiration for this series of posts and the sample record sexpr are from Steve Yegge's The Emacs Problem. Be sure to give it a read if you haven't already.

This entry was posted in Programming and tagged , . Bookmark the permalink.
  • Alex Botero-Lowry

    Totally seems like a perfect place for a macro! :)

    (defmacro record (&rest data)
    (flet ((toxml (cell)
    (if (consp cell)
    (format "%s" (car cell)
    (mapconcat 'toxml (cdr cell) "")
    (car cell))
    (format "%s" cell))))
    (toxml (append '(record) data))))

    There is an advantage to your approach, which is that malformed records won't pass it, but this approach has the advantage of taking arbitrary records...

  • Brian

    This was fun! My lack of lisp/elisp knowledge made my solution a little more verbose; I needed a separate function for each of the data types.

    I suppose it reads very much like a C/C++ head picking his way through the elisp manual wrote it, because, well, that's what it is. :-) Thanks for an enjoyable exercise.

    (defun string-to-element (s e) (concat "" s "\n"))

    (defun number-to-element (n e) (string-to-element (number-to-string n) e))
    (defun symbol-to-element (s e) (string-to-element (symbol-name s) e))
    (defun list-to-element (l e) (string-to-element (concat "\n" (apply 'concat l)) e))

    (defun date (x) (string-to-element x "date"))
    (defun logger (x) (string-to-element x "logger"))
    (defun class (x) (string-to-element x "class"))
    (defun emessage (x) (string-to-element x "emessage"))

    (defun millis (x) (number-to-element x "millis"))
    (defun sequence (x) (number-to-element x "sequence"))
    (defun thread (x) (number-to-element x "thread"))
    (defun line (x) (number-to-element x "line"))

    (defun level (x) (symbol-to-element x "level"))
    (defun method (x) (symbol-to-element x "method"))

    (defun record (&rest x) (list-to-element x "record"))
    (defun frame (&rest x) (list-to-element x "frame"))
    (defun exception (&rest x) (list-to-element x "exception"))

  • My solution to this was to use fset to bind a function to each symbol you pass in automatically...