EINVAL: Valid solutions for invalid problems

Emacs Skeletons: The Missing Manual

The venerable Emacs skeleton system, while powerful, lacks documentation in some areas. For example the topic of subskeletons is only touched on with little to no examples. This post attempts to alleviate it to some degree. A basic Elisp familiarity is assumed.

Skeleton syntax reminder

As a quick reminder, this is the basic skeleton syntax:

(INTERACTOR ELEMENT...)

A trivial skeleton producing "foobar" looks like this:

(nil "foo" "bar")

The interactor can be:

  • nil (for non-interactive skeletons)
  • a string (an interactive prompt text)
  • a Lisp expression (an arbitrary prompt, interactive or not)

The result of a non-nil interactor is bound to str which can be used as one of the elements:

("Name: "
 "Hello, my name is " str ".  Nice to meet you!")

As seen above, an element can be many things (see the documentation), but for simplicity’s sake, let’s say it can be:

  • a string (which is inserted as is)
  • str (invoke the interactor and insert the result)
  • a list (see below)

A skeleton element that’s a list is either a Lisp expression or a subskeleton. This is differentiated by its first element. If the first list element is a list or a string, the skeleton element is a subskeleton. When it’s a symbol, it’s assumed to be a Lisp function call. As of Emacs 29.4, all the other cases are invalid (technically assumed to be function calls but they are invalid as function calls).

The subskeleton syntax is almost the same as the top-level skeleton’s. In addition to previous the possible interactor values, it can also be a list of strings to iterate over. Subskeletons are almost always a loop. For example a prompt-based subskeleton will get inserted as many times, as many times we provide it with a non-empty string, each time with the new input assigned to str.

An element that’s a Lisp expression is evaluated and the result is interpreted as a skeleton element again.

Sounds simple but it can be confusing in practice, so let’s move on to some examples.

Examples

All the examples below will be in the following form so they can be easily evaluated to try them out. Consider reading this post in eww so that C-x C-e is readily available.

(with-temp-buffer
  (skeleton-insert
   'SKELETON)
  (buffer-string))
Example
(with-temp-buffer
  (skeleton-insert
   '(nil "foo" "bar"))
  (buffer-string))

The same SKELETON can be used with define-skeleton (just without the outer parentheses).

A Lisp expression

(with-temp-buffer
  (skeleton-insert
   '(nil
     (progn "foo")
     "bar" "baz"
     (progn '(progn "qux"))))
  (buffer-string))
"foobarbazqux"

As mentioned before, each list inside a skeleton that starts with a symbol (here: progn), gets evaluated as Elisp and the result is considered as a skeleton element again. Possibly recursively, as shown with the second progn returning a yet another progn expression.

Quoted Lisp expression

There is also a quoted variant of the Lisp expression skeleton element. Its result is always discarded, it’s evaluated purely for its side-effects.

(with-temp-buffer
  (skeleton-insert
   '(nil
     '(setq v1 nil)
     "foo" "bar"))
  (buffer-string))
"foobar"

Note the v1 variable. It’s one of the helper variables always available inside skeletons (no let expressions necessary). We’ll be using it in some examples later.

A trivial subskeleton with a nil interactor

(with-temp-buffer
  (skeleton-insert
   '(nil
     "foo{"
     (nil "aaa" "bbb")
     "}bar"))
  (buffer-string))
"foo{aaabbb}bar"

This is the only subskeleton form that isn’t a de facto loop and doesn’t need to contain the str element.

It’s also superficially identical to using concat inside skeleton, though this one lacks any additional skeleton-specific functionality.

a concat example
(with-temp-buffer
  (skeleton-insert
   '(nil
     "foo{"
     (concat "aaa" "bbb")
     "}bar"))
  (buffer-string))
"foo{aaabbb}bar"

This syntax isn’t very useful alone, but can be used for example to return more than one skeleton element from a Lisp expression, like this:

(with-temp-buffer
  (skeleton-insert
   '(nil
     "foo{"
     (when (y-or-n-p "Hello?")
       '(nil "aaa" "bbb"))
     "}bar"))
  (buffer-string))

Note that if a subskeleton with a nil interactor actually does contain str, that whole subskeleton gets discarded. This is useful if we do intend to iterate over a list passed as an interactor, but that list happens to be empty (i.e. nil).

A subskeleton with a string interactor (prompt)

(with-temp-buffer
  (skeleton-insert
   '(nil
     "foo{"
     ("Input: " "<" str ">")
     "}bar"))
  (buffer-string))
"foo{<input1><input2>...}bar"

The interactor is treated as the prompt for the interactive read of the value that gets assigned to str. To exit the loop, enter an empty string.

A subskeleton with a Lisp expression interactor

(with-temp-buffer
  (skeleton-insert
   '(nil
     '(setq v1 '("aaa" "bbb" "ccc"))
     "foo{"
     ((completing-read "Input: " v1) "<" str ">")
     "}bar"))
  (buffer-string))

This syntax allows to use an arbitrary Lisp expression to get the str value. Often a call to completing-read or a similar function. The loop exits when the expression returns nil or an empty string.

A subskeleton with a list of strings interactor

(with-temp-buffer
  (skeleton-insert
   '(nil
     "foo{"
     (("aaa" "bbb" "ccc") "<" str ">")
     "}bar"))
  (buffer-string))
"foo{<aaa><bbb><ccc>}bar"

This construct allows non-interactive looping over a list of strings, with each one being assigned to str in turn.

A subskeleton with a non-trivial list of strings interactor

When I started working on more complex skeletons, it quickly dawned on me that there seemingly is no syntax for a subskeleton with a list of strings interactor with the list being provided either by a variable, or some expression. The list of strings can only ever be a list literal. The following skeletons do NOT work.

This tries to call v1 as a function:

(with-temp-buffer
  (skeleton-insert
   '(nil
     '(setq v1 '("aaa" "bbb" "ccc"))
     "foo{"
     (v1 "<" str ">")
     "}bar"))
  (buffer-string))
Symbol’s function definition is void: v1

This uses the whole return value of (reverse '("aaa" "bbb" "ccc")) as str which firstly isn’t a string, and secondly wouldn’t ever become nil to end the loop:

(with-temp-buffer
  (skeleton-insert
   '(nil
     "foo{"
     ((reverse '("aaa" "bbb" "ccc")) "<" str ">")
     "}bar"))
  (buffer-string))
Wrong type argument: stringp, ("ccc" "bbb" "aaa")

Backquotes!

It turns out there is a solution but as far as I’m aware it is not documented anywhere! The intended effect can be achieved using backquotes (aka quasiquotes).

(with-temp-buffer
  (skeleton-insert
   '(nil
     '(setq v1 '("aaa" "bbb" "ccc"))
     "foo{"
     `(,v1 "<" str ">")
     "}bar"))
  (buffer-string))
"foo{<aaa><bbb><ccc>}bar"
  (with-temp-buffer
    (skeleton-insert
     '(nil
       "foo{"
       `(,(reverse '("aaa" "bbb" "ccc")) "<" str ">")
       "}bar"))
    (buffer-string))
"foo{<ccc><bbb><aaa>}bar"

Post-scriptum

In some indefinite future I intend to improve the skeleton section of the actual GNU Emacs documentation. In the meanwhile this blog post is surely better than nothing.