Listing 1 will refresh your memory about what the Scheduling DSL looks like.
Listing 1. Sample code from the Scheduling DSL
task "warn if website is down":
every 3.Minutes()
starting now
when WebSite("http://example.org").IsNotResponding
then:
notify "admin@example.org", "server down!"
|
It doesn’t look much like code, right? But take a look at the class diagram in figure 1.
This is the implicit base class for the Scheduling DSL. An implicit base class is one of the more common ways to define and work with a DSL.
For now, please assume that the DSL code you see is being magically placed in the Prepare() method of a derived class. This means that you have full access to all the methods that the BaseScheduler exposes, because those are exposed by the base class.
What this means, in turn, is
that you can now look at the DSL and the class diagram and suddenly
understand that most of what goes on here involves plain old method
calls. Nothing fancy or hard to understand—we’re merely using a slightly
different syntax to call them than you usually do.
We’re adding a minor extension to the language here. Two methods in the BaseScheduler aren’t part of the API, but rather are part of the language extension:
Minutes()—This is a simple extension method that allows us to specify 3.Minutes(), which reads better than TimeSpan.FromMinutes(3), which is how we would usually perform the same task.
when(Expression)—This
is a meta-method, which is a method that can modify the language. It
specifies that the expression that’s passed to it will be wrapped in a
delegate and stored in an instance variable.
That doesn’t make much
sense right now, I know, so let’s start taking this DSL apart. We’ll use
the exact opposite approach from what we do when we’re building the
DSL. We’ll add the programming concepts to the existing DSL until we
fully understand how this works.
Let’s start by adding parentheses and removing some compiler syntactic sugar. Listing 2 shows the results of that.
Listing 2. The Scheduling DSL after removing most of the syntactic sugar
task("warn if website is down", do() :
self.every( self.Minutes(3) )
self.starting ( self.now )
self.when( WebSite("http://example.org").IsNotResponding)
self.then( do():
notify( "admin@example.org", "server down!")
)
)
|
A couple of notes about this before we continue:
That looks a lot more like code
now (and a lot less like a normal language). But we’re not done yet. We
still need to resolve the when meta-method. When we run that, we’ll get the result shown in listing 3.
Listing 3. The Scheduling DSL after resolving the when meta-method
task("warn if website is down", do() :
self.every( self.Minutes(3) )
starting ( self.now )
condition = do():
return WebSite("http://example.org").IsNotResponding
then( do():
notify( "admin@example.org", "server down!")
)
)
|
As you can see, we completely removed the when
method, replacing it with an assignment of an anonymous delegate for
the instance variable. This is the only piece of compiler magic we’ve
performed. Everything else is already in the Boo language.
Take a look at the when and then methods. Both of them end up with a similar syntax, but they’re implemented in drastically different ways. The when method is a meta-method. It changes the code at compilation time. The then method uses an anonymous block as a way to pass the delegate to execute.
The reason we have two
different approaches that end up with nearly the same end result
(passing a delegate to a method) has to do with the syntax we want to
achieve.
With the when method, we want to achieve a keyword-like behavior, so the when method accepts an expression and transforms that to a delegate. The then keyword has a different syntax that accepts a block of code, so we use Boo’s anonymous blocks to help us out there. |
Now we can take the code in listing 3.13 and make a direct translation to C#, which will give us the code in listing 4.
Listing 4. The Scheduling DSL code, translated to C#
task("warn if website is down", delegate
{
this.every( this.Minutes(3) );
this.starting ( this.now );
this.condition = delegate
{
return new WebSite("http://example.org"). IsNotResponding;
};
this.then( delegate
{
this.notify( "admin@example.org", "server down!");
});
});
|
Take a look back at the original DSL text in listing 3.11, and compare it to listing 4.
In terms of functionality, they’re the same, but the syntactic
differences between them are huge, and we want a good syntax for our
DSL.
We’ve skipped one important
part; we haven’t talked yet about what the implicit base class will do.
The result of the implicit base class resolving its base class is shown
in listing 5.
Listing 5. The full class that was generated using the implicit base class
public class MyDemoTask ( BaseScheduler ):
def override Prepare():
task("warn if website is down"), def():
# the rest of the code
|
Now that we have a firm grasp of what code we’re getting out of the DSL, we need to get to grips with how we can run this code.