A Domain-specific language(DSL) is a computer language that’s targeted to a particular kind of problem, rather than a general purpose language that’s aimed at any kind of software problem. Domain specific languages have been talked about, and used for almost as long as computing has been done. Regular expressions and CSS are 2 examples of DSLs.
Any software language needs a parser and an interpreter(or compiler or a mix), but in DSLs, we have 2 types: external ones which need parsers and interpreters, and internal ones which rely on the hosting language power to give the feel of a particular language, and thus they don’t require their own parsers.
Ruby is a very convenient language for writing internal DSLs, it has several powerful techniques that enables you easily to write internal DSLs, and many famous products that we use are nothing but internal DSLs: Haml, Builder and Rake .
Lemme show you a very simple example on how an internal DSL might look like using ruby:
RobotTasksExecuter.start do
stack :boxes => 5 do
fetch do
rotate :rectangle => 60
pick :speed => 'slow',:height => 15
rotate :rectangle => -60
free :speed => 'slow'
end
package do
lock
seal
end
end
endAs you can tell, this is a very basic internal DSL, written to describe basic tasks for a robot.
There is one task at the moment called ’stack’ where the robot should do 2 things: fetch the box, and then package it.
Several ruby techniques are used to bring this basic DSL:
1- Blocks: everything between ‘do .. end’ keywords.
2- Parenthesesless methods: like ‘lock’ and ’seal’.
3- Passing hash values as method arguments without the need of using curly braces: like doing ‘pick :speed => ’slow’,:height => 15′.
Now all i need is a simple functionality to execute this simple internal DSL:
# A dummy task class class RobotTask # A dummy run task # type of task, a margin to indent the output, and some other attributes are passed def run(type,margin,attrs={}) output = "#{margin} Running task '#{type}'" output+=", with attributes:" if !attrs.empty? puts output attrs.each{|key,value| puts "#{margin} --#{key} = #{value}"} end end # A simple tasks executer # This code is very basic and could be far optimized class RobotTasksExecuter def initialize puts " **** Robot is working now ****" # This is used to indent the output @margin = "" end # Start the executer work def self.start(&block) # Instantiate an executer object and evaluate the DSL code block new.instance_eval(&block) end # All undefined methods will call method_missing # Let's use 'execute' as an alias for method_missing alias method_missing execute # All undefined methods will call this method, because of the aliasing taking place over. def execute(type,attrs={}) @margin += " " RobotTask.new.run(type,@margin,attrs) yield if block_given? @margin = @margin[0,@margin.length-2] end end RobotTasksExecuter.start do stack :boxes => 5 do fetch do rotate :rectangle => 60 pick :speed => 'slow',:height => 15 rotate :rectangle => -60 free :speed => 'slow' end package do lock seal end end end
And the output:
**** Robot is working now ****
Running task 'stack', with attributes:
--boxes = 5
Running task 'fetch'
Running task 'rotate', with attributes:
--rectangle = 60
Running task 'pick', with attributes:
--speed = slow
--height = 15
Running task 'rotate', with attributes:
--rectangle = -60
Running task 'free', with attributes:
--speed = slow
Running task 'package'
Running task 'lock'
Running task 'seal'In addition to the 3 points we mentioned earlier, another 2 ones should be added, as they are the heart of the above executer:
4- Reflection techniques: the one we used inside the class method ’start’, exactly this line “new.instance_eval(&block)”.
5- method_missing: all undefined methods are received by ‘execute’, the alias of method_missing.
As i mentioned earlier, this is a very basic internal DSL, if you are looking for an advanced article covering a more advanced one, then don’t hesitate to check this rich one by Daniel Spiewak.
Ruby and Functional Programming - Khaled alHabache’s official blog | May 24th, 2009 at 4:28 am #
[...] Internal DSLs is also one of the nice things that you can achieve with block syntax in Ruby. [...]
Bill Bartmann | September 5th, 2009 at 10:54 am #
I’m so glad I found this site…Keep up the good work
Chris Burke | March 10th, 2010 at 12:43 pm #
You need to alias method_missing execute AFTER execute is defined.
internal programming | April 3rd, 2010 at 2:18 pm #
[...] links appear in Google Results | Web SEO …Internal Anchor links appear in Google's Rich Snippets.Ruby and Internal DSLs – Khaled alHabache's official blogName (required) Mail (will not be published) (required) Website. XHTML: You can use these tags: [...]