How to write DSLs with Ruby

Posted by MSch Sat, 01 Apr 2006 20:51:00 GMT

Recently I needed a DSL like the one in Rake for one of my projects. At first I just googled around a bit hoping to find a comprehensive tutorial. I did gain some more or less useful insights, but what I wanted was something as clean and coherent as a Rakefile:

  namespace "samples" do
    task :build do
      # Build the sample programs
    end
  end

After reading lots of tutorials and gaining come clues all I managed to code was:

  namespace do |n|
    n.task do |t|
      # Build the sample programs
    end
  end

I felt terrible and started reading the Pickaxe, desperate for a solution. Proc, Binding, Object, Kernel. I was looking everywhere for some clues on how to make my DSL as great as Rake’s. Finally I found what I was looking for: Object#instance_eval!

I’ll spare you any further explainations and present you the source (abridged, download full version) to my DSL implementation:

class PseudoRakefile
  def evaluate(&block)
    Tasks.new({}, {}, self, &block)
  end

  protected
  class Tasks
    attr_reader :parent
    def initialize(args, options, parent, &block)
      # snip
      instance_eval &block
    end

    def task(task, args = {}, &block)
     Tasks.new(@args.merge(args), @options.merge(:task => task), self, &block)
    end
    def namespace(name, &block)
      Tasks.new(@args, @options.merge(:namespace=>name), self, &block)
    end

    # snip
    def puts(msg)
      Kernel.puts "#{@options[:namespace]}:#{@options[:task]} #{msg}"
    end
  end
end

rakefile = PseudoRakefile.new

rakefile.evaluate do
  namespace "samples" do
    task :build do
      develop('deal', :do_it => :great)
      sleep 2
      develop('solution', :do_it => :sloppy)
    end
    task :destroy, :probability => 1 do
      puts "Tear everything down like a little kid"
      rm("Schroedinger's Cat", :probability => 0.5)
      rm("some unimportant file")
    end
  end
end

It’s free to use for anyone, but you’d make this boy very happy by leaving a comment, especially if you have improved the core idea or actually use my code in your projects.

Tags ,  | 1 comment | 1 trackback