Skip to content

Fun with Emacs Macros – Irregular Property Lists

As an extra note: some people on /r/Emacs are interpreting this post as if it suggests use of macro instead of functions. That is certainly not the case. Macro arguments are not evaluated when macro is called, thus macros can not be used as functions in expressions and passed around as functions and function objects can. In this article I use macro for ease of typing, as a convenient tool for fast prototyping and testing. I find it very convenient to code that way. If you would like to have a function which you can use as a library, you will have to rewrite this as a function, which shouldn’t be particularly difficult.

Someone posted on /r/Emacs following list and wondered why he/she does not get correct values from the property list:

(rss :base-directory ./org/posts
     :base-extension org
     :recursive t
     :exclude \(?:\(?:post\|rs\)s\.org\)
     :publishing-function ab/publish-rss
     :publishing-directory ./public
     :rss-extension xml
     :html-link-home http://localhost:8080/
     :html-link-use-abs-url t
     :html-link-org-files-as-html t
     :auto-sitemap t
     :base-url http://localhost:8080/posts/
     :sitemap-filename rss.org
     :sitemap-title Nothing Is Simple
     :sitemap-style list
     :sitemap-sort-files anti-chronologically
     :sitemap-function ab/generate-rss-feed
     :sitemap-format-entry ab/format-rss-entry)

I was a bit late to the party, and someone already posted the obvious answer that this isn’t a property list, since it does not have even number of elements. A property list is an ordinary list with even number of elements. It is rather a convention than a real data type. Property lists, and even association lists, alists in Elisp, are nothing but lists of key-value pairs.

However, we could actually treat any list as containing a key-value pairs, as long as a “property” have a following element we can treat as its value. We could maybe call such a list irregular property list, but I don’t think name is important. Getting value in such list is a simply matter of finding property element and returning value of next element.

(defmacro pget (property proplist)
  `(let (found
         (plist ,proplist)
         (p (quote ,proplist)))
     (catch 'done
       (dolist (prop plist)
         (when (equal prop p)
           (setq found t))
         (when found
           (unless (equal prop )
             (setq found prop)
             (throw 'done t)))))
     found))

For tests, I’ll modify above list with a property test that does not get any value, and assign the list to a variable for easier test:

(setq tl '(rss :base-directory ./org/posts
               :base-extension org
               :recursive t
               :exclude \(?:\(?:post\|rs\)s\.org\)
               :publishing-function ab/publish-rss
               :publishing-directory ./public
               :rss-extension xml
               :html-link-home http://localhost:8080/
               :html-link-use-abs-url t
               :html-link-org-files-as-html t
               :auto-sitemap t
               :base-url http://localhost:8080/posts/
               :sitemap-filename rss.org
               :sitemap-title Nothing Is Simple
               :sitemap-style list
               :sitemap-sort-files anti-chronologically
               :sitemap-function ab/generate-rss-feed
               :sitemap-format-entry ab/format-rss-entry
               :test))

(pget :base-directory tl)
(pget :test tl)
(pget :tests tl)

Now there are some bad things with this approach. For example, there is no way for us to know if the returned value is truly the value in a given key-value pair, or if a programmer is making a mistake. For example: (pget ‘rss tl) returns string :base-directory, which obviously isn’t what we are thinking of. With other words, we can’t really assert if a key-value pair is really a key and a value pair, or just two elements to be following each other. For that reason I don’t think we should use lists in this way in general. I wonder if there is much one can assert in “normal” plist either, but it is not a question here. Anyway, we can do a little better here and at least we can return if the property is a member of the list, and if there is an element after it.

Another thing is how we deal with missing value or a missing property. As a side result of how throw-catch mechanism works in Emacs we can pass it a value that gets evaluated when we “throw” something. Here we have used ‘t which gets thrown when we have a property without a value, otherwise we get the value. If property is not found we get a nil since that throw never happens.

We can do better and actually use throw to return the value itself, as well as to return some value which indicates not-found cases:

(defmacro pget (property proplist)
  `(let (found-property
         (plist ,proplist)
         (p (quote ,property)))
    (catch 'done
      (dolist (prop plist)
        (when (equal prop p)
          (setq found-property t))
        (when found-property
          (unless (equal prop p)
            (when prop
              (throw 'done prop)))))
      (if found-property
          'value-not-found
        'property-not-found))))

(pget :base-extension tl)
(pget :base-directory tl)
(pget :test tl)
(pget :tests tl)

With above version we can at least detect if there are such elements as property and some value after it. Here we don’t need to return some hard coded values. Instead, we can optionally set which values are returned in case when property or its value are not found:

(defmacro pget (property proplist &optional property-not-found value-not-found)
  `(let (found-property
         (p (quote ,property))
         (plist ,proplist)
         (prop-not-found (if ,property-not-found
                             ,property-not-found
                           'property-not-found))
         (val-not-found (if ,value-not-found
                            ,value-not-found
                          'value-not-found)))
     (catch 'done
       (dolist (prop p)
         (when (equal prop p)
           (setq found-property t))
         (when found-property
           (unless (equal prop p)
             (when prop
               (throw 'done prop)))))
       (if found-property
           val-not-found
         prop-not-found))))

(pget :base-extension tl)
(pget :base-directory tl)
(pget :test tl)
(pget :tests tl)

(defun my-value-not-found-fn (property)
  (message "There is nothing for property: %s" property))

(defun my-property-not-found-fn (property)
  (message "There is not such member: %s." property))

(setq returned-callback
      (pget :test tl 'my-property-not-found-fn 'my-value-not-found-fn))

(funcall returned-callback ':tests)

(setq returned-callback
      (pget :tests tl 'my-property-not-found-fn 'my-value-not-found-fn))

We don’t need to pass in function names, we could pass any value to be returned:

(pget :test tl 1 2)
(pget :tests tl 1 2)

(pget :test tl nil 'no-value)
(pget :tests tl "no tests property")

This could be improved more in this direction, but I am leaving it there.

Instead, let’s take a look at another mistake, a forgotten quote around a string: :sitemap-title Nothing Is Simple, resulting in more list elements than what the poster probably meant.

We could exploit another convention we usually see when using property lists to return a list of values for a given property. By onvention, in plists, property names are usually preceded with a “:” before the property name. Thus, we can consider any list with a pair :name value as a sort of property list. In such a list we return a sublist in interval

(property-name, next-property-name)

where I mean interval in mathematical sense where ‘()’ means an open interval where limits are excluded:

(defmacro pget (property propertylist &optional delimiter)
  `(let (stack
         found-property
         (p (quote ,property))
         (delim ,delimiter)
         (plist ,propertylist))
     (catch 'done
       (dolist (prop plist)
         (when (equal prop p)
           (setq found-property t))
         (when found-property
           (if delim
               (when (equal prop delim)
                 (throw 'done t)))
           (unless (equal prop p)
             (unless delim
               (when (string-prefix-p ":" (symbol-name prop))
                 (throw 'done t)))
             (push prop stack)))))
     (nreverse stack)))

(pget :base-extension tl)
(pget :base-directory tl)
(pget :sitemap-title tl)
(pget :test tl)
(pget :tests tl)

The optional delimiter let us return a sublist between any pair of elements, not just those ended with an element prefixed with a “:”.

Now I am a bit lazy and bored here, so I will wrap it up for this blog post. It would be otherwise possible to do similar as for above versions of the macro to return optional values when a property or any values are not found. Anyway, I don’t think this is something one would use in real code, it was just a small exercise for the fun of it. By the way, the above macro is based on idea of operator precedence algorithm which I think is a great tool for parsing expressions with Emacs macros, or probably any Lisp, since it is so easy to use lists for stacks and the entire language is string processing in disguise.

Post a Comment

Your email is never published nor shared. Required fields are marked *