IronShay

Ironing code, geek t-shirts and even presentations!

NAVIGATION - SEARCH

IronRuby Sample #3: Creating a DSL

[ This is part 3 of my IronRuby samples series. You can read the first post (Hello World) and the second post (C# Recorder using IronRuby) as well. ]

The Ruby language is very powerful in general, and in its metaprogramming abilities in particular. This time I’m going to demonstrate how you can take advantage of these abilities along with Ruby’s syntax capabilities in order to create a custom DSL (Domain Specific Language) in a matter of minutes. Pay attention that these features are not specific to IronRuby, they are a part of every implementation of the Ruby language.

Let’s start from the end this time. This is the code you will be able to write with my little DSL:

list = ShoppingList.new
list.buy 3.kilograms.of(Peanuts)
list.buy 100.grams.of(Cheese)
list.buy 2.kilograms.of(Snacks)
list.print

Running this will result in the next output to the console:

Please buy:
- 3000 grams of peanuts
- 100 grams of cheese
- 2000 grams of snacks

It’s a nice little DSL to manage your shopping lists with. But as much nice as it is, the more important fact is that you can write your custom DSLs in Ruby (and IronRuby of course) with ease and make your coding experience much more fluent.

How is it Written?

The code for this DSL contains 49 lines of code, including lots of comments… Again, writing DSLs in Ruby (and IronRuby) is very easy. All I needed for this DSL were two “special” techniques of Ruby – const_missing and monkey patching. These two together makes about all the magic of this DSL.

I’m not going to go through the code line by line. I added comments inside the code so I think it will pretty straight forward to read. Do not hesitate to comment or contact me directly if something is not clear enough.

the DSL code:

# ShoppingList class
class ShoppingList
  # Ruby's class constructor - initializes the shopping list array.
  def initialize
    @list = []
  end
  
  # The main method - gets an item and adds it to the array.
  def buy(item)
    @list << item
  end
  
  # Prints all items in the list in a user-friendly way.
  def print
    puts "Please buy:"
    @list.each do |item|
      puts "- #{item[:grams]} grams of #{item[:product].downcase}"
    end
  end
end

# Every call to an undefined constant will reach here.
# This allows to use Peanuts inside the DSL code without defining it
# so we can write 5.grams.of(Peanuts) instead of 5.grams.of("Peanuts")
def Object.const_missing(name)
  # Return the constant as a string
  name.to_s
end

# For the DSL, we need to add grams and kilograms methods to all integers in the application.
# In order to do that, I open the Fixnum class (which is Ruby's equivalent to C# Int32) and add
# the needed methods
class Fixnum
  # grams method will just return itself
  def grams
    self
  end
  
  # Because we save everything in grams, when the kilograms method is used, I'll return 
  # the current value * 1000.
  def kilograms
    self*1000
  end
  
  # The of method retrieves an object and returns a hash of the object and its gram amount.
  def of(product)
    {:product=>product, :grams=>self}
  end
end


All the best,
Shay.



Pingbacks and trackbacks (3)+

Add comment