Examples of DSLs in everyday programming
As I noted at the beginning of this discussion, DSLs are very common. Chances are good that you've already used quite a few of them as both a programmer and a user. One nearly omnipresent example of a DSL is Cascading Style Sheets (CSS), which allows you to add style to Web pages and documents. Listing 1 is an excerpt from a CSS file. It specifies the style for a hyperlink (the <A>
tag) -- namely how its color should change when you mouse over it.
Listing 1. CSS is a DSL
A:hover { color: #FF0000; text-decoration: none; }
Another example of an external DSL is the stuff you write in a makefile for the make
utility. The domain in this case is build -- the source code that you want to compile and build into a library or executable, or the files that you want to process for generating documentation or the like. Listing 2 contains an example makefile.
Listing 2. So is this makefile
GCC=cxx -I.
all : clean compile
compile : myprog
myprog: MyProg.cc Util.o
$(GCC) -o myprog MyProg.cc Util.o
Util.o : Util.h Util.cc
$(GCC) -c Util.cc
clean :
/bin/rm -f myprog Util.o
The makefile expresses a set of dependencies, and the commands (the indented statements) are executed based on the dependencies. For example, Util.cc
is compiled into Util.o
if Util.o
does not exist, or if Util.h
or Util.cc
is modified after Util.o
was created. Once you learn a few rules (and idiosyncrasies), the makefile is a pretty lightweight way to express build dependencies.
The Java equivalent of make
is the popular Ant build file, an example of which you can see in Listing 3.
Listing 3. An Ant build file
<project name="AnExampleProject" default="jarit" basedir=".">
<property name="src" location="src"/>
<property name="build" location="build"/>
<property name="distrib" location="distrib"/>
<target name="compile" description="compile your Java code from src into build" >
<javac srcdir="${src}" destdir="${build}"/>
</target>
<target name="jarit" depends="compile" description="jar it up" >
<jar jarfile="${distrib}/AnExampleProject.jar" basedir="${build}"/>
</target>
</project>
Both makefiles and Ant build files are external DSLs. In the case of Ant, the XML representation is processed by the ant
utility using an XML parser. Ant's vocabulary contains various terms, such as target
and properties
, that are valid in the domain and context of compiling and bundling code.
In the Ruby community, the equivalent of make
and Ant is Rake. Rake is also an example of a DSL; however, it is written using Ruby itself, so it is an internal or embedded DSL. Listing 4 contains an example of a Rake file.
Listing 4. An example Rake file
ORIGINAL = 'input.dat'
BACKUP = 'input.dat.bak'
task :default => BACKUP
file BACKUP => ORIGINAL do |task|
cp task.prerequisites[0], task.name
end
In this simple example, the file input.dat
is copied (or backed up) to input.dat.bak
only if the timestamp of the dat
file is later than the bak
file, or if the bak
file does not exist. Rake skillfully takes advantage of the flexibility of Ruby to provide an elegant syntax for expressing tasks and their dependencies. For instance, in the context of Rake task
is simply a method that takes a hash
as a parameter.
Gant is an internal DSL that is similar to Rake; it is written using Groovy, and serves as a wrapper around Ant. If you use Gant, you don't have to endure XML coding, and can use a lightweight syntax to express your builds. Listing 5 is an example.
Listing 5. A sample Gant file
#slightly modified version of example from http://gant.codehaus.org/
includeTargets << gant.targets.Clean
cleanPattern << [ '**/*~' , '**/*.bak' ]
cleanDirectory << 'build'
target (stuff : 'A target to do some stuff') {
println 'Stuff'
depends clean
echo message : 'A default message from Ant'
otherStuff()
}
target (otherStuff : 'A target to do some other stuff') {
println 'OtherStuff'
echo message : 'Another message from Ant'
clean()
}
setDefaultTarget stuff