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.