Starbucks-fueled Developer

Thursday, June 28, 2007

DSL Support in Brail, Part II

So continuing from Part I, here is our example view:

<?brail
dsl Html:
html:
head:
title:
text "Untitled page"
end
end
body:
p:
text "Hello World"
end
end
end
end
?>
As covered in part one, the initial expansion of the "dsl" macro yields us the following code in Boo:

dsl = Castle.MonoRail.Views.Brail.DslProvider(self)
dsl.Register(Castle.MonoRail.Views.Brail.HtmlExtension(OutputStream))

or, in C#:

DslProvider dsl = new Castle.MonoRail.Views.Brail.DslProvider(this);
dsl.Register(new Castle.MonoRail.Views.Brail.HtmlExtension(OutputStream));


The "this" and "OutputStream" references are what we're going tackle now. The Brail view engine for MonoRail functions loosely similar to traditional ASP.NET applications in that all ASPX inherit from System.Web.UI.Page; in Brail, all views inherit Castle.MonoRail.Views.Brail.BrailBase. Similarly to ASP.NET, whenever a view is rendered by MonoRail, the Brail view engine parses the view and generates a new assembly that contains a new object that derives from BrailBase. So, if our view were named "default.brail", our runtime-generated assembly would contain a class called "default_brail" that inherits BrailBase. When an MonoRail request is made and once MonoRail has determined what view(s) to render, the Run() method on the BrailBase, which is an abstract/must-override method and that is where the code in your View files is placed for execution. This is where the "dsl" macro is expanded.

Back to the "this" and "OutputStream" references...DSL support was designed to work similarly to VB's with block, allowing short-hand to invoke many methods on an object but only reference object by name once (for a laugh, read the documentation on the with...end with statement. Performance my butt...). This turns out to be quite a trick to accomplish, from the perspective of the compiler/parser; take this for example:

<?brail
dsl Html:
for name in names:
p:
text "Hello ${name}"
end
end
end
?>
What's the context of the name and names variables? Remember, this code gets stuffed into the view's Run() method. Here's the resulting code created by the compiler within the Run() method:

dsl = Castle.MonoRail.Views.Brail.DslProvider(self)
dsl.Register(Castle.MonoRail.Views.Brail.HtmlExtension(OutputStream))
for dsl.name in dsl.names:
dsl.p( { dsl.text("Hello ${name}") } )
end
Done double-taking? You probably said, "why the hell does it look like its trying to get the value of properties called "name" and "names" off the dsl object?! That's gotta be a bug..." And you'd be right.

Partially. "dsl.name" is a bug (found by Ayende) and has since been fixed, as you're actually declaring and referencing a new variable there all at one time. But, as you can also see, the DSL macro went thru the code and replaced all the other tag macros (like "p" to indicate a paragraph tag) as being methods on the dsl instance. The reason for that is duck-typing.

Under the covers, DslProvider implements Boo's IQuackFu interface allowing other .NET languages to work with the duck-typing semantics of the Boo language. Here, when a method is invoked on the DslProvider instance, Boo sees that DslProvider implements IQuackFu and invokes QuackInvoke with the method name and parameters that was called; in the case of "dsl.p(...)", "p" and the block of code are the parameters. The DslProvider keeps an IDictionary of all the methods that the referenced language implementation (HtmlExtension, in our case). When QuackInvoke is called on DslProvider, it tries to find a match give then the method name and number of parameters on the HtmlExtension object and then forwards the call onto it.

So what about the "dsl.names" reference? Simple answer: more duck-typing. Let's say your controller is written as so:

public class HomeController : SmartDispatcherController
{
public void Default()
{
PropertyBag["names"] = new string[] { "Ayende", "Harris" };
}
}
Without going into crazy detail, the Brail view engine essentially makes the items in the PropertyBag, Request, and other common and useful System.Web objects and collections class-level instances on the dynamically-generated BrailBase instance (that is a much-simplified explanation, but it does the job here). Duck-typing comes into play here because these "variables" are being treated as properties on the DslProvider instance. This time, QuackGet is called on DslProvider and a similar lookup is done, except this time against the BrailBase instance instead of the the language implementation.

So what's "OutputStream"? OutputStream is a property on BrailBase. Go ahead. Ask. Why isn't the generated code:

dsl = Castle.MonoRail.Views.Brail.DslProvider(self)
dsl.Register(Castle.MonoRail.Views.Brail.HtmlExtension(dsl.OutputStream))

Because, at this point, the macro is generating code just before the "with...end with", so we don't need to resolve the "OutputStream" reference using duck-typing. The "OutputStream" referenced is passed to the HtmlExtension object because the HtmlExtension object is what's actually responsible for "Response.Write" calls to give you your precious markup.

The idea for using the "with...end with" style seemed appropriate here as it provides, IMHO, more readable code instead of a lot of nested dsl.somethings( { dsl.moresomethings( { dsl.even_more_somethings( {...} ) } ) } ) when needing to produce an extensive amount of markup. The implementation of the "with...end with" design was taken by an example on the Boo project of how to do just that and can be found in the project's repository.

All that for pretty syntax.

Next time, we'll cover current ideas, status, and any other features that may/not be on the horizon.

Tuesday, June 26, 2007

DSL Support in Brail, Part I

Ayende mentioned on his blog about the work I've done to incorporate support for DSLs in Brail views within MonoRail. While he did a great job giving an overview of how to make use of the functionality, I figured I'd dive into the actual implementation and give some details of how this is achieved and where this can be taken -- at least my "vision".

The Beginning

First, none of this is even possible without mentioning what the Boo project team has done and giving a bit of a background - to my understanding - of what's at the core. With the 0.7.8 release of Boo, the team enabled the ability to invoke methods of an object via Boo's macro syntax. What's that mean; and what's a macro? C# developers have been using -- no, well, ok, pun intended -- since C# was introduced. In C#, using() is or can be thought of as a macro as when the C# compiler finds a using() block, it expands it into a try/catch/finally block calling dispose on the instance referenced in the using statement. Here's an example; this code:

using(SqlConnection c = new SqlConnection())
{
// do stuff
}

is rewritten/transformed by the compiler to:

SqlConnection c = null;
try
{
c= new SqlConnection()
}
finally
{
if(c != null)
{
c.Dispose()
}
}

which is why the using statement only accepts types that implement IDisposable, as it ensures Dispose() is always called. So, basically, macros are shorthand for developers.

Back to Boo. The 0.7.8 release allows developers to invoke methods using the macro syntax. So if I had a method called text that took a string as a parameter:

def text(value as string):
print value

in Boo 0.7.8, I can call text like this:

text "hello world"

Well, that's all well and good, isn't it? Nothing really "magical" there; how does this enable DSLs in Brail?

The Awakening

One of the major advantages that Boo offers developers is the ability to extend the language in numerous ways and macros is just one, "simple" option developers have at their disposal. The only caviaet is that your macros, obviously, need to produce valid, compilable code. So, from the example above, when the compiler parses:

text "hello world"

it rewrites/transforms the source to:

text("hello world")

So, how, or rather what, does a MonoRail view in Brail that's something like this:

<?brail
dsl Html:
html:
head:
title:
text "Untitled page"
end
end
body:
p:
text "Hello World"
end
end
end
end
?>

Give you the output you expect?

The Implementation

As you might guess, "dsl" is a macro that takes one parameter: the language you intend to use. So what does "dsl Html" generate? This, in Boo:

dsl = Castle.MonoRail.Views.Brail.DslProvider(self)
dsl.Register(Castle.MonoRail.Views.Brail.HtmlExtension(OutputStream))

or, in C#:

DslProvider dsl = new Castle.MonoRail.Views.Brail.DslProvider(this);
dsl.Register(new Castle.MonoRail.Views.Brail.HtmlExtension(OutputStream));

Okay, that might be a lot to swallow. "dsl Html" starts off by creating a new instance of a DslProvider object. The DslProvider object acts as a registry of languages to use. In this case, the second line tells the DslProvider that we're specifically interested in working with the HtmlExtension.

What's up with "this" and "OutputStream"? Here's where we get into the real nitty gritty, and for that, I leave you to Part II in this series. Hope this is as interesting for you as it is for me!!