Deconstructing DSL Evolution

time to read 3 min | 524 words

You could say that I have an interest in DSL at the moment, which is why I was excited when I saw this post. It talks about how you can create natural English as a BDD specification.

In fact, the final "code" in the post looks like this:

  Scenario: savings account is in credit
    Given my savings account balance is 100
    And my cash account balance is 10
    When I transfer 20
    Then my savings account balance should be 80
    And my cash account balance should be 30

Looking at this, I went absolutely mad. There was no way this code work. I am not a Ruby expert, but I know enough of the syntax to know that there is simply no way that this would be valid Ruby code.

I got the code and started looking at it. It was complicated a bit because I don't have a Ruby environment on my machine, and I am not even sure that "not a Ruby expert" covers my knowledge very well. A Ruby newbie would be a more accurate term. Let us simply says that I know the syntax of a Ruby if statement, and that about sums it up.

After a while, I let down of grep & notepad, and was able to truly appreciate what was going on there.

In order to explain, I am going to go backward a bit and show the code that is valid Ruby syntax:

Scenario "savings account is in credit" do
    Given "my savings account balance is 100" 
    And "my cash account balance is 10" 
    When "I transfer 20" 
    Then "my savings account balance should be 80" 
    And "my cash account balance should be 30" 
end

That is fairly easy, you have methods that accept strings. You configure this using this approach:

define.when("I transfer $amount") do |amount|
    @savings_account.transfer_to(@cash_account, amount.to_f)
end

This is a parser syntax, and a delegate that accepts the extracted parameters from the passed string. The parsing is done with a Regex, and is really easy to follow. The delegate is basic stuff, when you get down to it.

So, when you call the When method, passing "I transfer 20", the delegate (called block by Rubyists) is being called with the string "20", which was extracted from the string. Really elegant way of doing it, I have to say.

But, what about the natural language syntax from the beginning of the post, that is still not valid Ruby code. The answer here is that it really isn't. What happens is that you have a line based parser, and you call the methods based on the first word in the line. For all practical purposes, it comes out the same.

For rspec, this works very well, and it gives you awesome syntax. It does mean that you are using an external DSL, not an internal one. For this scenario, I don't think that it means much, all the processing is done elsewhere.

Really neat approach, I am impressed & awed.