I'm all about Ember.js recently

My First DSL in Ruby

I read a few posts about how good fit Ruby is for building DSLs, Domain Specific Languages. Ever the curious I have been waiting for the opportunity to build a very simple one for a particular problem.

Well, I did not have to wait very long (when you have a hammer everything looks like a nail). I needed a quick method which generates nice html graphs for my post about browser javascript engine benchmarking. After spending a few minutes searching for a free tool (I did not want anything fluffy, just something very basic) I hit the nail in the head with my hammer. Only the nail was the generated HTML charts for my post and the hammer, (my desire to build) a DSL in Ruby.

I would like to say it was difficult but in fact it was a piece of cake with Ruby (and armed with the knowledge of previous DSL builders). The solution is composed of the following three parts:

  1. The DSL file which defines the charts
  2. The interpreter which understands the definitions in the DSL file
  3. The “controller” which just passes the data contained in the DSL file to the interpreter

So let’s see each part separately:

browser_js_benchmarks.dsl (the DSL)

chart "Score" do |c|
    c.add "Safari 3.1.2" => 164
    c.add "Firefox 3.0.1" => 156
    c.add "Shiretoko" => 145
    c.add "Google Chrome" => 1589
end

chart.rb (the interpreter)

require "math_aux"

class ChartDSL
    
    Template = %(%%ITEMS%%
%%CAPTION%%
) Background_colors = %w(red blue green yellow grey) attr_reader :values def initialize @charts = Array.new @values = Hash.new end def chart(name) @name = name yield self @charts.push(make_html) end def add(name_and_value) @values ||= Hash.new @values.merge!(name_and_value) end def load(filename) # c = new instance_eval(File.read(filename), filename) write_output end def make_html sorted_pairs = @values.sort_by { |v| - v[1] } vals = sorted_pairs.map { |p| p[1] } norm_values = MathAux::normalize(vals, 100.0) html = Array.new sorted_pairs.each_with_index do |pair, i| name, value = pair html.push(%Q(#{name}
 
#{value})) end filled_chart = ChartDSL::Template.sub("%%CAPTION%%", @name) filled_chart.sub("%%ITEMS%%", html.join) end def write_output File.open("generated_charts.html", "w") do |f| f.write(@charts) end end end

chart_loader.rb (the controller)

require "chart"
class ChartLoader
    def self.load_chart(dsl_filename)
        c = ChartDSL.new
        c.load(dsl_filename)
    end
end

if __FILE__ == $0
    ChartLoader.load_chart(ARGV[0])
end

For the sake of completeness here is math_aux.rb:

module MathAux
    def self.normalize(numbers, to=1.0)
        norm_rat = to / numbers.max
        numbers.map { |n| n * norm_rat }
    end
end

To generate the charts, one only has to run “the interpreter” with a dsl file as the first parameter:

ruby chart_loader.rb browser_js_benchmarks.dsl

The output is called generated_charts.html and contains the following:

Score
Google Chrome
 
1589
Safari 3.1.2
 
164
Firefox 3.0.1
 
156
Shiretoko
 
145

Note that in ~30-40 code lines we have a “language interpreter”, one that understands the chart DSL and spits out some HTML code that represents the charts. Of course there is plenty of room for improvement, like having the same color denote the same actor between charts (Firefox 3.0.1 should always be the blue bar, for example, unlike in my post), using a better solution for the template strings, adding the possibility of pie charts (although “standard” HTML is not very flexible on different chart forms, one would probably have to use a <canvas> ), and so on.

The essential thing is that it works and it does what the particular situation demanded. It took me a couple of interrupted hours plus the time to read through two related posts which is not that much given that I now have a “tool” (once again, I feel a bit conceited to call it that) which I can use for my future posts whenever the need arises. A custom hammer for my custom nail.

(If you care, feel free to download, use and modify the source code located here)