Implementing a DSL

time to read 4 min | 642 words

Tim Wilde asked how I would build a DSL, given the example that I gave:

task "warn if website is not alive":
	every 3.Minutes() 
	starting now
	when WebSite("http://example.org").IsAlive == false
	then:
		notify "admin@example.org", "server down!"

Now, I have a small confession to make, I didn't build the DSL for the post, I just thought it up and posted it. Tim has spiked a somewhat nicer implementation of the C# fluent interface, and it was about 250 lines of code, in 8 classes, 6 interfaces.

Face with that, I felt that I had no other choice than to go on and build a DSL implementation. If fact, I think that this is a good exercise of DSL building. We have started from the required syntax, and then we see how we can implement it.

I had to make two modifications to the DSL in order to make it really work, I marked them in red. One was that I wanted to implement the DSL in C#, and it has no way to write an indexed property, which I need in order to create extension properties in Boo, something that Boo completely supports. It was an interesting insight. The second was that the is operator is for reference types, not for values types, so I changed the IsAlive is false to IsAlive == false.

Most of my DSL use the Anonymous Base Class pattern. This means that you need to imagine a base class that wraps the code in this class. This is a really simple DSL, and I wanted to introduce as little complexity as possible, so we just use the basic capabilities of the language.

There are several interesting things that are going on in the DSL. For a start, we are using Boo's ability to drop parenthesizes if needed, which gives us a more "keyword" feeling. Another interesting approach is that when the last parameter of a method is a delegate, we can use just create a block and use that as a way to create the delegate.

So this code:

task "foo":
	print "bar"	

Is the equivalent of this C# code:

task("foo", delegate
{
	Console.WriteLine("bar");
});

This is how "task" and "then" keywords works.

public void task(string name, ActionDelegate taskDelegate)
{
	this.taskName = name;
	taskDelegate();
}
public void then(ActionDelegate actionDelegate)
{
	this.action = actionDelegate;
}

The "when" keyword is a lot more interesting. We want to do late evaluation of the expression that we pass to the when keyword, but the language doesn't allow us to do so. This is where the real power of Boo comes into place, we simply change the language.

[Meta]
public static Expression when(Expression expression)
{
	BlockExpression right = new BlockExpression();
	right.Body.Add(new ReturnStatement(expression));
	return new BinaryExpression(
		BinaryOperatorType.Assign,
		new ReferenceExpression("condition"),
		right
	);
}

This code (which looks much better when it is in Boo, by the way) means that whenever the compiler encounters the result of this statement:

when WebSite("http://example.org").IsAlive == false

Is actually this code:

condition = { return (WebSite("http://example.org").IsAlive == false) }

To my knowledge, this type of transformations is not available in other languages. In fact, while I am certainly not a Ruby expert, I do not believe that this is possible in Ruby. (Let us see if this brings the mob out of the woodworks, shall we?)

For the compiler, the code that we have above will be translated to:

public class validateWebSiteUp(Rhino.DSL.Tests.SchedulingDSL.BaseScheduler):

	public override def Prepare() as void:
		self.task('warn if website is not alive', def ():
			self.every(Rhino.DSL.Tests.SchedulingDSL.BaseScheduler.Minutes(3))
			self.starting(self.now)
			self.condition = { return (WebSite("http://example.org").IsAlive == false) }
			self.then({ self.notify('admin@example.org', 'server down!') })

You can get the source for the DSL here:

https://rhino-tools.svn.sourceforge.net/svnroot/rhino-tools/trunk/rhino-dsl/Rhino.DSL.Tests/SchedulingDSL/BaseScheduler.cs

A sample DSL is here:

https://rhino-tools.svn.sourceforge.net/svnroot/rhino-tools/trunk/rhino-dsl/Rhino.DSL.Tests/SchedulingDSL/ValidateWebSiteUp.boo

Tests:

https://rhino-tools.svn.sourceforge.net/svnroot/rhino-tools/trunk/rhino-dsl/Rhino.DSL.Tests/SchedulingDSL/SchedulingDSLFixture.cs