4. WHAT IS A DSL?
• Domain-Specific Language
Kevin W. Gisi | http://www.kevingisi.com | kevin@kevingisi.com | @gisikw
5. CUCUMBER IS A DSL
Feature: Password recovery
As a forgetful user
I want to be able to recover my password
So that I can get back into the site when I lose my password!
Scenario: Recovering password
Given I have signed up as a user
When I go to the home page
And I click "Recover your password"
And I fill in "Email" with "user@example.com"
And my email address is "user@example.com"
And I press "Reset my password"
Then I should receive an email
When I open the email
And I click the first link in the email
And I fill in "Password" with "simpsonsrock!"
And I fill in "Password Confirmation" with "simpsonsrock!"
And I press "Update password"
Then I should be on the dashboard page
And I should see "Your password has been reset"
Kevin W. Gisi | http://www.kevingisi.com | kevin@kevingisi.com | @gisikw
6. REAL LANGUAGES
• Rule-based
• Pattern-matching
• Very dull to read
Kevin W. Gisi | http://www.kevingisi.com | kevin@kevingisi.com | @gisikw
7. DSL EVALUATION (RUBY)
• Example: turn the monitor background color green
Kevin W. Gisi | http://www.kevingisi.com | kevin@kevingisi.com | @gisikw
10. DSL EVALUATION (RUBY)
• English: turn the monitor background color green
• Ruby: turn(“monitor”, “background”, “#00FF00”)
Kevin W. Gisi | http://www.kevingisi.com | kevin@kevingisi.com | @gisikw
11. DSL EVALUATION (RUBY)
• Example: turn the monitor background color green
• Ruby: turn( the( monitor( background( color( green) ) ) ) )
Kevin W. Gisi | http://www.kevingisi.com | kevin@kevingisi.com | @gisikw
12. DSL EVALUATION
turn the monitor background color green
class DSLObject
def method_missing(name,*args)
return name.to_s
end
end
# @dsl_object.green => "green"
Kevin W. Gisi | http://www.kevingisi.com | kevin@kevingisi.com | @gisikw
13. DSL EVALUATION
turn the monitor background color “green”
class DSLObject
def color(name)
case name
when "green" : "#00FF00"
when "blue" : "#0000FF"
when "red" : "#FF0000"
end
end
def method_missing(name,*args)
return name.to_s
end
end
# @dsl_object.color green => NameError: undefined local variable
or method `green' for #<Object:0x1001c8288>
Kevin W. Gisi | http://www.kevingisi.com | kevin@kevingisi.com | @gisikw
14. PROBLEM!
• How do we execute the code inside this object?
• @dsl_object.color green
• “green” is evaluated within the current scope, so unless it’s a
variable, it doesn’t exist
• (what we really mean is @dsl_object.color @dsl_object.green)
Kevin W. Gisi | http://www.kevingisi.com | kevin@kevingisi.com | @gisikw
21. RUBY HAS DYNAMIC DISPATCH
• Ruby uses message-passing
• @dsl_object.send(“col
or green”)
• Hey, some guy named Irb
says “color green”
Kevin W. Gisi | http://www.kevingisi.com | kevin@kevingisi.com | @gisikw
22. WRONG AGAIN!
• Hmmm, do I have a method
named “color green”? Nope
• Do my ancestors have a
method “color green”?
Nope
• Ooh! I’ll call
method_missing(“color
green”)
• @dsl_object.send(“col
or green”) #=> “color
Kevin W. Gisi | http://www.kevingisi.com | kevin@kevingisi.com | @gisikw
23. CHANGING SCOPE
@dsl_object.instance_eval do
# Everything in this block
# will treat @dsl_object
# as self
color green
end
# This is equivalent
@dsl_object.instance_eval("color green")
Kevin W. Gisi | http://www.kevingisi.com | kevin@kevingisi.com | @gisikw
24. DSL EVALUATION
turn the monitor background “#00FF00”
class DSLObject
def color(*args)
case args[0]
when "green" then "#00FF00"
when "blue" then "#0000FF"
when "red" then "#FF0000"
end
end
def method_missing(name,*args)
return *args.flatten.unshift(name.to_s)
end
end
# @dsl_object.instance_eval("background color green") =>
["background","#00FF00"]
Kevin W. Gisi | http://www.kevingisi.com | kevin@kevingisi.com | @gisikw
25. DSL EVALUATION
turn the monitor [“background”, “#00FF00”]
class DSLObject
def color(*args)
case args[0]
when "green" then "#00FF00"
when "blue" then "#0000FF"
when "red" then "#FF0000"
end
end
def method_missing(name,*args)
return *args.flatten.unshift(name.to_s)
end
end
# @dsl_object.instance_eval("monitor background color green") =>
["monitor","background","#00FF00"]
Kevin W. Gisi | http://www.kevingisi.com | kevin@kevingisi.com | @gisikw
26. DSL EVALUATION
turn the [“monitor”,“background”, “#00FF00”]
class DSLObject
def color(*args)
case args[0]
when "green" then "#00FF00"
when "blue" then "#0000FF"
when "red" then "#FF0000"
end
end
def the(*args)
return *args
end
def method_missing(name,*args)
return *args.flatten.unshift(name.to_s)
end
end
# @dsl_object.instance_eval("the monitor background color green") =>
["monitor","background","#00FF00"]
Kevin W. Gisi | http://www.kevingisi.com | kevin@kevingisi.com | @gisikw
27. DSL EVALUATION
turn [“monitor”,“background”, “#00FF00”]
Now we’re in normal programming territory
Kevin W. Gisi | http://www.kevingisi.com | kevin@kevingisi.com | @gisikw
28. CONTRIVED EXAMPLES
• Example: turn the beat around
• With our DSL #=> turn [“beat”,“around”]
• Valid syntax, bad semantics
Kevin W. Gisi | http://www.kevingisi.com | kevin@kevingisi.com | @gisikw
29. SYNTAX LIMITATIONS
• Example: Set course for the Hoth system
• Can this be valid syntax?
Kevin W. Gisi | http://www.kevingisi.com | kevin@kevingisi.com | @gisikw
30. SYNTAX LIMITATIONS
• Example: Set course for the Hoth system
• Set, Hoth => Constant undefined
• for => reserved word
• system => already defined (inherited from Object)
Kevin W. Gisi | http://www.kevingisi.com | kevin@kevingisi.com | @gisikw
31. BLANK SLATE
class DSLObject < BasicObject
def method_missing(name,*args)
return *args.flatten.unshift(name.to_s)
end
def const_missing(name)
eval(name.to_s_downcase)
end
end
# @dsl_object.instance_eval("Set course fer the Hoth system")
# => ["Set","course","fer","the","Hoth","system"]
Kevin W. Gisi | http://www.kevingisi.com | kevin@kevingisi.com | @gisikw
35. ENVIRONMENTS ARE HARD
game = TextAdventureGame.new("My First Text Adventure")
hallway = Location.new("A dark and quiet hallway")
kitchen = Location.new("An abandoned kitchen")
hall = Location.new("A banquet hall...strangely
deserted")
game.rooms << hallway
game.rooms << kitchen
game.rooms << hall
hallway.north = kitchen
kitchen.south = hallway
kitchen.north = hall
hall.south = kitchen
Kevin W. Gisi | http://www.kevingisi.com | kevin@kevingisi.com | @gisikw
36. TEXT ADVENTURE WITH BLOCKS
World.new("My First Text Adventure") do
location "hallway" do
description "A dark and quiet hallway"
north "kitchen"
end
location "kitchen" do
description "An abandoned kitchen"
south "hallway"
end
start "hallway"
end
Kevin W. Gisi | http://www.kevingisi.com | kevin@kevingisi.com | @gisikw
37. PYTHONY TEXT ADVENTURE
world "My First Text Adventure"
location "hallway"
description "A dark and quiet hallway"
north "kitchen"
location "kitchen"
description "An abandoned kitchen"
south "hallway"
start "hallway"
Kevin W. Gisi | http://www.kevingisi.com | kevin@kevingisi.com | @gisikw
39. BLOCKS
• do..end
• Delayed execution
• Changes the execution scope
• Is a real argument
Kevin W. Gisi | http://www.kevingisi.com | kevin@kevingisi.com | @gisikw
40. CONTEXTS
• instance_eval
• class_eval
• eval
Kevin W. Gisi | http://www.kevingisi.com | kevin@kevingisi.com | @gisikw
41. WORLD
require 'game'
class World
attr_accessor :name, :locations, :player
def initialize(name,&block)
@name = name
@locations = {}
@player = Player.new
instance_eval &block
end
def location(name,&block)
@locations[name] = Location.new(name,&block)
end
def start(location)
@player.location = @locations[location]
end
end
Kevin W. Gisi | http://www.kevingisi.com | kevin@kevingisi.com | @gisikw
42. LOCATION
class Location
attr_accessor :name, :description, :exits
def initialize(name,&block)
@name = name
@exits = {}
instance_eval &block
end
def description(prose)
@description = prose
end
["north","south","east","west"].each do |direction|
class_eval <<-END
def #{direction}(location)
@exits["#{direction}"] = location
end
END
end
end
Kevin W. Gisi | http://www.kevingisi.com | kevin@kevingisi.com | @gisikw
43. PLAYER
class Player
attr_accessor :location
end
Kevin W. Gisi | http://www.kevingisi.com | kevin@kevingisi.com | @gisikw
44. HANDLING USER INPUT
• Preprocess the input
• Provide a context to execute
• Manipulate the environment
Kevin W. Gisi | http://www.kevingisi.com | kevin@kevingisi.com | @gisikw
45. PREPROCESS THE INPUT
require 'world'
require 'location'
require 'player'
require 'runner'
class Game
def self.run(world)
runner = Runner.new(world)
puts "Welcome to the text adventure game!"
print "> "
until (input = gets.chomp) == "exit"
runner.__execute(input.downcase)
print "> "
end
puts "Thanks for playing"
end
end
Kevin W. Gisi | http://www.kevingisi.com | kevin@kevingisi.com | @gisikw
46. PROVIDE AN EXECUTION CONTEXT
class Runner < BasicObject
attr_accessor :__world, :__handled
def initialize(world)
@__world = world
end
def __handle!
@__handled = true
end
def __execute(string)
@__handled = false
instance_eval(string)
__puts "I don't understand" unless @__handled
end
def __puts(message)
$stdout.puts(message)
end
def method_missing(name,*args)
return *args.flatten.unshift(name.to_s)
end
def look(*args)
__puts @__world.player.location.instance_eval(:@description)
__handle!
end
end
Kevin W. Gisi | http://www.kevingisi.com | kevin@kevingisi.com | @gisikw
47. METAPROGRAMMING
• method_missing
• dynamically writing code
• evaluating code in different contexts
Kevin W. Gisi | http://www.kevingisi.com | kevin@kevingisi.com | @gisikw