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 = %(<table border="0" cellspacing="5" cellpadding="5"><caption>%%CAPTION%%</caption>%%ITEMS%%</table>)
	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(<tr><td>#{name}</td><td><div style="width:#{norm_values[i]}px;background-color:#{ChartDSL::Background_colors[i]}">&nbsp;</div></td><td>#{value}</td></tr>))
		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:

<table border="0" cellspacing="5" cellpadding="5">
<caption>Score</caption>
 <tr>
  <td>Google Chrome</td>
  <td><div style="width:100.0px;background-color:red">&nbsp;</div></td>
  <td>1589</td>
 </tr>
 <tr>
  <td>Safari 3.1.2</td>
  <td><div style="width:10.3209565764632px;background-color:blue">&nbsp;</div</td>  <td>164</td></tr>
 <tr>
  <td>Firefox 3.0.1</td>
  <td><div style="width:9.81749528005034px;background-color:green">&nbsp;</div</td> <td>156</td>
</tr>
<tr>
 <td>Shiretoko</td>
 <td><div style="width:9.12523599748269px;background-color:yellow">&nbsp;</div</td><td>145</td>
</tr>
</table>

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)


Rss Comments

5 Comments

  1. you call that a DSL?

    #1 lolbob
  2. I really appreciate that you took the time to write this down, good job! ;)

    #2 voldern
  3. @lolbob: I did not claim this piece of code to be a great invention but it does what I wanted to do. After having a glimpse of others’ works/tutorials I thought DSL is an adequate term for this type of code. Could you state why it is not a DSL?

    #3 Balint Erdi
  4. Just a suggestion, but you might want to try making your DSL be a language that outputs a URL to the Google Charts API in the end. It’s considerably more flexible and produces some nice looking results for very little effort.

    http://chart.apis.google.com/chart?cht=bhg&chs=200×125&chd=s:el,or&chco=4d89f9,c6d9fd

    #4 John Munsch
  5. @John That’s a very good idea! I’ll see to that when I get a chance.

    #5 Balint Erdi

Leave a comment