[boo]
DSL-friendly syntax
[ bamboo ] 01:01, Thursday, 7 June 2007

Georges showed me today how he was using a boo DSL to generate HTML:

   1:html() do:
   2:    body() do:
   3:        text("Hello, world!")

The idea is to use nested closures to represent the hierarchy of tags:

   1:        
   2:callable Block()
   3:
   4:def blockTag(tagName as string, block as Block):
   5:    print "<${tagName}>"
   6:    block()
   7:    print "</${tagName}>"
   8:    
   9:def html(block as Block):
  10:    blockTag("html", block)
  11:    
  12:def body(block as Block):
  13:    blockTag("body", block)
  14:
  15:def text(s as string):
  16:    print s

It works pretty well except that all those do keywords and parenthesis kind of get in the way of clarity for that particular use case.

What we really would like to write is something like:

   1:html:
   2:    body:
   3:        text "Hello, world!"

Which is clearer and more to the point.

Boo actually allows one to do such a thing by extending the language with macros.

Boo macros are objects that are invoked by the compiler during the compilation process to expand or transform the AST in a hopefully useful way.

Whenever the Boo parser finds code in the form: name expression_list (block)? it creates a MacroStatement node that will be later handled to a macro object for expansion.

Let's define our html, body and text macros for our DSL:

   1:import Boo.Lang.Compiler
   2:import Boo.Lang.Compiler.Ast
   3:
   4:class AbstractTagMacro(AbstractAstMacro):
   5:"""
   6:Maps a macro to a DSL method invocation.
   7:"""
   8:    override def Expand(node as MacroStatement):
   9:        invocation = MethodInvocationExpression(Target: ReferenceExpression(TagName))
  10:        invocation.Arguments.Add(CallableBlockExpression(Body: node.Block)) 
  11:        return ExpressionStatement(invocation)
  12:        
  13:    abstract TagName as string:
  14:        get:
  15:            pass
  16:
  17:class HtmlMacro(AbstractTagMacro):
  18:    override TagName:
  19:        get: return "html"
  20:        
  21:class BodyMacro(AbstractTagMacro):
  22:    override TagName:
  23:        get: return "body"
  24:        
  25:class TextMacro(AbstractAstMacro):
  26:    override def Expand(node as MacroStatement):
  27:        invocation = MethodInvocationExpression(Target: ReferenceExpression("text"))
  28:        invocation.Arguments = node.Arguments
  29:        return ExpressionStatement(invocation)

Now we can write code as we wanted to in the first place but there are a few issues with macros:

  • writing macros demand a in depth knowledge of the compiler object model (oh, you didn't notice it?)
  • macros cannot be used in the same assembly defining them (the compiler needs to instantiate them after all, right?)

Enter BOO-835. It's now possible to use regular method definitions to create DSLs:

   1:callable Block()
   2:
   3:def blockTag(tagName as string, block as Block):
   4:    print "<${tagName}>"
   5:    block()
   6:    print "</${tagName}>"
   7:    
   8:def html(block as Block):
   9:    blockTag "html", block
  10:    
  11:def body(block as Block):
  12:    blockTag "body", block
  13:    
  14:def text(s as string):
  15:    print s
  16:    
  17:html:
  18:    body:
  19:        text "Hello, world!"

The code landed in the repository just a few minutes ago so feel free to try it while it's hot.

Updated: jira issue code was wrong.

TrackBack
Comments

That is quite a cool feature :-). But I think the biggest thing that could help Boo right now is better docs.. A great start would be adding this feature to the Boo language guide wiki, but longer term it might be helpful to allow anonymous edits to the wiki: this will greatly lower the barrier of entry to those who want to contribute docs.

What do you think?

--Max, June 7, 2007 05:23 AM

Awesome!

This opens a whole world of cool wrist-friendly possibilities.


--cedric, June 7, 2007 06:31 AM

Hi Max,

The boo websites were open once upon the time but we started getting a lot of spam. It's a shame but now we have to rely on login protected editing to counter measure that.

If you want to edit the website please send a message to boolang with your docs.codehaus.org id and we'll be happy to add you to the list of editors.

--bamboo, June 7, 2007 09:05 AM

jquery chainability for generators, TASTY!

--rektide, June 7, 2007 09:06 PM

comments working again, also tasty.

--rektide, June 7, 2007 09:07 PM

Looks like a nice way to write nant scripts. How would attributes be written though ?

nant:
project[name:foo, default:bar]:
target[name:bar]:


perhaps ?

--Ian, June 8, 2007 05:03 AM

Interesting idea. This could also be done in C# (I assume that Callable is the Boo equivalent of a delegate).

It's kind of ugly compared to the Boo version but still not terrible.

public delegate void Block();

public static void BlockTag(string name, Block block) {
Console.WriteLine("", name);
block();
Console.WriteLine("", name);
}

public static void html(Block block) {
BlockTag("html", block);
}

public static void body(Block block) {
BlockTag("body", block);
}

public static void Main() {
html(delegate {
body(delegate {
Console.WriteLine("Hello, vorld"");
})
});
}

--Chris, June 8, 2007 07:12 AM

Note that your blog software butchered the contents of the Console.WriteLine()s in BlockTag(). It's obvious what goes there anyway.

On the other hand, "Hello, vorld!" is entirely my mistake. :)

--Chris, June 8, 2007 07:14 AM
Post a comment









Remember personal info?