Thursday 4 June 2009

The Horn DSL Redux - Part I - The Spike

A couple of months ago while preparing for my talk at Dsl DevCon I was hit by a crushing fear or possibly a rush of reality. After seeing my name amongst the list of notable speakers I panicked. I felt I needed something pretty damn good to legitamise my place amongst such notable Dsl intelligencia.

After much soul searching, I decided to rewrite the horn Dsl in ironruby. I would give two renditions of the same story, one in boo and the other in ironruby. Like many, I have been swayed by the gushing reports of increased productivity and superlative elegance that many proclaim to have achieved through ruby. I had recently upgraded my Nant scripts to the ruby make equivalent rake. This also seemed like quite a fun thing to do.

I am not going to give an introduction to ironruby as much has been written on the subject. Suffice to say that this is Microsoft's ruby implementation. At the time of writing I was unsure of both the stability and the maturity of ironruby. The only way to gain some confience was to spike some initial requirements to ensure that this was worth my valuable time. The goal of the ironruby Dsl is exactly the same as the horn boo dsl which is namely to parse the Dsl into the horn metamodel. A previous posting outlines the objectives of the Dsl.

My first spike was to see whether I could parse an ironruby script from C# code that created a CLR object and returned it to the calling code.

Unfortunately the code I am about to narrate is a couple of months old and I have forgotten the vast quantities of profanity that went into making the following unit test pass.

First up, let us look at the very uncontroversial ironruby code that will return a CLR type. You will just have to take my word for it that I wrote this as the good lord intended, namely test first.


    1 class MetaDataFactory
    2     def return_meta_data()
    3         meta = Horn::Core::Dsl::BuildMetaData.new
    4         meta.Description = "A description of sorts"
    5         meta
    6     end
    7 end

The only line of any interest here is line 3 that creates an instance of the BuildMetaData class that represents the horn DSL. Line 5 illustrates the return symantics of ruby functions, namely without a return statement.

Below is the passing test that I finally ended up with:


   10 public class When_creating_a_clr_object_in_ironruby : Specification
   11 {
   12     private string buildFile;
   13 
   14     protected override void Because()
   15     {
   16         buildFile = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "rubyspike.rb");
   17     }
   18 
   19     [Fact]
   20     public void Then_the_object_should_be_accessible_in_csharp()
   21     {                      
   22         var engine = Ruby.CreateEngine();
   23 
   24         engine.Runtime.LoadAssembly(typeof (BuildMetaData).Assembly);
   25 
   26         engine.ExecuteFile(buildFile);
   27 
   28         var klass = engine.Runtime.Globals.GetVariable("MetaDataFactory");
   29 
   30         var instance = (RubyObject)engine.Operations.CreateInstance(klass);
   31 
   32         var metaData = (BuildMetaData)engine.Operations.InvokeMember(instance, "return_meta_data");
   33 
   34         Assert.Equal(metaData.Description, "A description of sorts");
   35     }
   36 }

I hope this syntax gets a little friendlier in .NET 4.0 but at the time of writing, this was my interpretation of the API. Here is a brief synopsis of the code:


  • Line 22 creates an instance of the ironruby script engine.

  • Line 24 loads the assembly that contains the BuildMetaData class definition into the Ruby script engine's AppDomain. This will allow the instantiation of any type defined in this assembly.

  • Line 26 quite obviously parses the script.

  • Line 28 and 30 contains the code I eventually stumbled across to create an instance of the class that I defined in the ruby code I outlined earlier. The passing of time has eased the pain of how difficult this discovery was for me.

  • Line 32 invokes a member of the newly intantiated instance variable.


With this passing test, I had a modicum of confidence that it was worth my while to pursue my quest further.

If any of this is of interest to you then please join the Horn user group for updates or check out the source here.

No comments:

Post a Comment