My first DSL in Ruby
by balint- Published:September 21st, 2008
- Comments:5 Comments
- Category: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:
- The DSL file which defines the charts
- The interpreter which understands the definitions in the DSL file
- 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]}"> </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"> </div></td> <td>1589</td> </tr> <tr> <td>Safari 3.1.2</td> <td><div style="width:10.3209565764632px;background-color:blue"> </div</td> <td>164</td></tr> <tr> <td>Firefox 3.0.1</td> <td><div style="width:9.81749528005034px;background-color:green"> </div</td> <td>156</td> </tr> <tr> <td>Shiretoko</td> <td><div style="width:9.12523599748269px;background-color:yellow"> </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)



5 Comments
you call that a DSL?
I really appreciate that you took the time to write this down, good job! ;)
@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?
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
@John That’s a very good idea! I’ll see to that when I get a chance.