Posterous theme by Cory Watilo

Filed under: hacking

DSLs using Unix shells

Back when I was in graduate school writing lots of Lisp code (and I do miss writing Lisp code) I frequently made use of macros to create small domain specific languages (DSLs). Now that I am working on a team writing C/C++ and shell scripts, breaking out the macro-esque code is a little more difficult.

That said, it’s not impossible. I haven’t written enough C/C++ to do much with preprocessor pseudo-macros, but I have written a lot of shell scripts. And I’ve found you can get a good taste of Lisp-style macros in some circumstances.

One situation that I like to employ them is with configuration files. More specifically, these are configuration files that will be sourced by a running shell (using the source or . command). While writing shell-based infrastructures, I’ve often found that you end up putting lots of things in configuration files. You might end up with something like this:

FOO_args=aaa
BAR_args=bbb
BAR_elements=ccc

This can get out of hand quickly; parameter names on the order of 25 to 40 characters is not unheard of. I find this hard to read and error prone when editing it on a live system (especially embedded ones where you have a limited set of tools).

If find the following a little easier to work with:

foo
{
    define args=aaa
}

bar
{
    define args=bbb elements=ccc
}

Sure, it’s more lines, but it immediately tells you quite a bit about what the configuration settings are meant to do. In short, it says it better.

The question is, how can this be turned into something the shell can evaluate that will result in the definitions being accessible to the rest of the code?

Consider the following:

#!/bin/bash

SCOPE=

function configure
{
    unset SCOPE
    SCOPE=$(echo ${1} | tr '[[:lower:]]' '[[:upper:]]')
}

function define
{
    for def in ${@}; do eval "${SCOPE}_${def}"; done
}

With a slight modification to the idealized configuration code above, the objective of defining the same parameters with a different syntax is achieved.

#!/bin/bash

. ./function-defs.sh

configure foo
{
    define args=aaa
}

configure bar
{
    define args=bbb elements=ccc
}

echo "FOO_args is ${FOO_args}"
echo "BAR_args is ${BAR_args}"
echo "BAR_elements is ${BAR_elements}"

Of course, this example is for demonstration purposes and things can get more complicated; consider the case of configure only working in certain circumstances and thus, making define a no-op.

All in all, this technique has made some things a lot simpler for me in some cases.