Playing with DSLs, a question or two

I am playing around with writing some DSLs to help me get more familier
with how they work and I have a couple of questions. First here's an
example of one I am playing with (based on the classic dungeon adventure
problem)

class Adventure
  def initialize
    @rooms = Hash.new
    @current_room = nil
  end

  def room(reference, &block)
    if @rooms.has_key?(reference)
      puts "Error: Room #{reference} has already been defined"
    else
      @rooms[reference] = {:short => '', :long => '', :exits => Hash.new}
    end

    @current_room = reference

    yield block
  end

  def short(text)
    @rooms[@current_room][:short] = text
  end

  def long(text)
    @rooms[@current_room][:long] = text
  end

  def door(direction, destination, &block)
    if @rooms[@current_room][:exits].has_key?(direction)
      puts "Error: Room #{@current_room} already has an exit defined in
#{direction}"
    else
      @rooms[@current_room][:exits][direction] = {:where => destination,
:condition => block_given? ? block : true}
    end
  end
end

class Object
  def adventure(&block)
    a = Adventure.new

    a.instance_eval(&block)
  end
end

adventure do
  room :cave do
    short "You are in a large cave"
    long "You are in a very large cave"
    door :west, :another_cave
    door :east, :exit do
      if player.has_item(:key)
        true
      else
        false
      end
    end
  end

  room :another_cave do
    short "A cave"
    long "Oh great ... another cave"
    door :east, :cave
  end

  room :exit do
    short "Freedom"
    long "Freeeeeeeeeeedom!!!!!!"
    door :west, :cave
  end

  require 'pp'
  pp @rooms
end

Now this works just fine but a couple of things just don't seem right.
Notice the @current_room variable that is set in the room method and
referenced in short, long and door. This seems clunky - it is basically a
global variable to help tie things together. Is there some better way of
doing this? Perhaps by passing it as a parameter somehow.

Also is the yield at the bottom of the room method the right way to go or
should I be calling instance_eval or similar?

Also how do I stop, for example, the door method being called outside of
the room method? I have a solution where I set @current_room to nil after
the yield and then each method checks to see if it is set and if not it
errors. But again this seems to be quite a pain.

Hey Peter,

How does this look? DSLs · GitHub

By making the Room a separate object, the scoping might be more as you had
hoped e.g. the door method

Regarding the yield vs. instance_eval -- this is a matter of opinion. If
you wanted to be very explicit, you could yield the adventure and room
objects to the blocks given to those methods. This makes your DSL seem a
bit less magic, but maybe that's ok.

I gave a talk on DSLs at Boulder Ruby a while back that you might find
interesting: https://speakerdeck.com/cookrn/dsls-as-teaching-tools

Ryan

···

On Mon, Feb 11, 2013 at 8:18 AM, Peter Hickman < peterhickman386@googlemail.com> wrote:

I am playing around with writing some DSLs to help me get more familier
with how they work and I have a couple of questions. First here's an
example of one I am playing with (based on the classic dungeon adventure
problem)

class Adventure
  def initialize
    @rooms = Hash.new
    @current_room = nil
  end

  def room(reference, &block)
    if @rooms.has_key?(reference)
      puts "Error: Room #{reference} has already been defined"
    else
      @rooms[reference] = {:short => '', :long => '', :exits => Hash.new}
    end

    @current_room = reference

    yield block
  end

  def short(text)
    @rooms[@current_room][:short] = text
  end

  def long(text)
    @rooms[@current_room][:long] = text
  end

  def door(direction, destination, &block)
    if @rooms[@current_room][:exits].has_key?(direction)
      puts "Error: Room #{@current_room} already has an exit defined in
#{direction}"
    else
      @rooms[@current_room][:exits][direction] = {:where => destination,
:condition => block_given? ? block : true}
    end
  end
end

class Object
  def adventure(&block)
    a = Adventure.new

    a.instance_eval(&block)
  end
end

adventure do
  room :cave do
    short "You are in a large cave"
    long "You are in a very large cave"
    door :west, :another_cave
    door :east, :exit do
      if player.has_item(:key)
        true
      else
        false
      end
    end
  end

  room :another_cave do
    short "A cave"
    long "Oh great ... another cave"
    door :east, :cave
  end

  room :exit do
    short "Freedom"
    long "Freeeeeeeeeeedom!!!!!!"
    door :west, :cave
  end

  require 'pp'
  pp @rooms
end

Now this works just fine but a couple of things just don't seem right.
Notice the @current_room variable that is set in the room method and
referenced in short, long and door. This seems clunky - it is basically a
global variable to help tie things together. Is there some better way of
doing this? Perhaps by passing it as a parameter somehow.

Also is the yield at the bottom of the room method the right way to go or
should I be calling instance_eval or similar?

Also how do I stop, for example, the door method being called outside of
the room method? I have a solution where I set @current_room to nil after
the yield and then each method checks to see if it is set and if not it
errors. But again this seems to be quite a pain.

--
Ryan Cook
720.319.7660

Thanks Ryan.

That was just the sort of thing I was looking for.

Thanks for that.

···

On 11 February 2013 17:18, Ryan Cook <cookrn@gmail.com> wrote:

Hey Peter,

How does this look? DSLs · GitHub

By making the Room a separate object, the scoping might be more as you had
hoped e.g. the door method

Regarding the yield vs. instance_eval -- this is a matter of opinion. If
you wanted to be very explicit, you could yield the adventure and room
objects to the blocks given to those methods. This makes your DSL seem a
bit less magic, but maybe that's ok.

I gave a talk on DSLs at Boulder Ruby a while back that you might find
interesting: https://speakerdeck.com/cookrn/dsls-as-teaching-tools

Ryan

On Mon, Feb 11, 2013 at 8:18 AM, Peter Hickman < > peterhickman386@googlemail.com> wrote:

I am playing around with writing some DSLs to help me get more familier
with how they work and I have a couple of questions. First here's an
example of one I am playing with (based on the classic dungeon adventure
problem)

class Adventure
  def initialize
    @rooms = Hash.new
    @current_room = nil
  end

  def room(reference, &block)
    if @rooms.has_key?(reference)
      puts "Error: Room #{reference} has already been defined"
    else
      @rooms[reference] = {:short => '', :long => '', :exits => Hash.new}
    end

    @current_room = reference

    yield block
  end

  def short(text)
    @rooms[@current_room][:short] = text
  end

  def long(text)
    @rooms[@current_room][:long] = text
  end

  def door(direction, destination, &block)
    if @rooms[@current_room][:exits].has_key?(direction)
      puts "Error: Room #{@current_room} already has an exit defined in
#{direction}"
    else
      @rooms[@current_room][:exits][direction] = {:where => destination,
:condition => block_given? ? block : true}
    end
  end
end

class Object
  def adventure(&block)
    a = Adventure.new

    a.instance_eval(&block)
  end
end

adventure do
  room :cave do
    short "You are in a large cave"
    long "You are in a very large cave"
    door :west, :another_cave
    door :east, :exit do
      if player.has_item(:key)
        true
      else
        false
      end
    end
  end

  room :another_cave do
    short "A cave"
    long "Oh great ... another cave"
    door :east, :cave
  end

  room :exit do
    short "Freedom"
    long "Freeeeeeeeeeedom!!!!!!"
    door :west, :cave
  end

  require 'pp'
  pp @rooms
end

Now this works just fine but a couple of things just don't seem right.
Notice the @current_room variable that is set in the room method and
referenced in short, long and door. This seems clunky - it is basically a
global variable to help tie things together. Is there some better way of
doing this? Perhaps by passing it as a parameter somehow.

Also is the yield at the bottom of the room method the right way to go or
should I be calling instance_eval or similar?

Also how do I stop, for example, the door method being called outside of
the room method? I have a solution where I set @current_room to nil after
the yield and then each method checks to see if it is set and if not it
errors. But again this seems to be quite a pain.

--
Ryan Cook
720.319.7660

Hi,

Dwemthy's Array by Why can also be helpful
http://mislav.uniqpath.com/poignant-guide/dwemthy/

ati

···

On 2/11/13, Peter Hickman <peterhickman386@googlemail.com> wrote:

Thanks Ryan.

That was just the sort of thing I was looking for.

Thanks for that.

On 11 February 2013 17:18, Ryan Cook <cookrn@gmail.com> wrote:

Hey Peter,

How does this look? https://gist.github.com/cookrn/4755773

By making the Room a separate object, the scoping might be more as you
had
hoped e.g. the door method

Regarding the yield vs. instance_eval -- this is a matter of opinion. If
you wanted to be very explicit, you could yield the adventure and room
objects to the blocks given to those methods. This makes your DSL seem a
bit less magic, but maybe that's ok.

I gave a talk on DSLs at Boulder Ruby a while back that you might find
interesting: https://speakerdeck.com/cookrn/dsls-as-teaching-tools

Ryan

On Mon, Feb 11, 2013 at 8:18 AM, Peter Hickman < >> peterhickman386@googlemail.com> wrote:

I am playing around with writing some DSLs to help me get more familier
with how they work and I have a couple of questions. First here's an
example of one I am playing with (based on the classic dungeon adventure
problem)

class Adventure
  def initialize
    @rooms = Hash.new
    @current_room = nil
  end

  def room(reference, &block)
    if @rooms.has_key?(reference)
      puts "Error: Room #{reference} has already been defined"
    else
      @rooms[reference] = {:short => '', :long => '', :exits =>
Hash.new}
    end

    @current_room = reference

    yield block
  end

  def short(text)
    @rooms[@current_room][:short] = text
  end

  def long(text)
    @rooms[@current_room][:long] = text
  end

  def door(direction, destination, &block)
    if @rooms[@current_room][:exits].has_key?(direction)
      puts "Error: Room #{@current_room} already has an exit defined in
#{direction}"
    else
      @rooms[@current_room][:exits][direction] = {:where => destination,
:condition => block_given? ? block : true}
    end
  end
end

class Object
  def adventure(&block)
    a = Adventure.new

    a.instance_eval(&block)
  end
end

adventure do
  room :cave do
    short "You are in a large cave"
    long "You are in a very large cave"
    door :west, :another_cave
    door :east, :exit do
      if player.has_item(:key)
        true
      else
        false
      end
    end
  end

  room :another_cave do
    short "A cave"
    long "Oh great ... another cave"
    door :east, :cave
  end

  room :exit do
    short "Freedom"
    long "Freeeeeeeeeeedom!!!!!!"
    door :west, :cave
  end

  require 'pp'
  pp @rooms
end

Now this works just fine but a couple of things just don't seem right.
Notice the @current_room variable that is set in the room method and
referenced in short, long and door. This seems clunky - it is basically
a
global variable to help tie things together. Is there some better way of
doing this? Perhaps by passing it as a parameter somehow.

Also is the yield at the bottom of the room method the right way to go
or
should I be calling instance_eval or similar?

Also how do I stop, for example, the door method being called outside of
the room method? I have a solution where I set @current_room to nil
after
the yield and then each method checks to see if it is set and if not it
errors. But again this seems to be quite a pain.

--
Ryan Cook
720.319.7660

Gulyás

Thanks for that. It woud seem that picking an adventure game was the best
choice for learning DSLs. At least I understand the domain :slight_smile:

Peter

···

On 12 February 2013 10:50, Gulyás Attila <toraritte@gmail.com> wrote:

Hi,

Dwemthy's Array by Why can also be helpful
http://mislav.uniqpath.com/poignant-guide/dwemthy/

ati

On 2/11/13, Peter Hickman <peterhickman386@googlemail.com> wrote:
> Thanks Ryan.
>
> That was just the sort of thing I was looking for.
>
> Thanks for that.
>
>
> On 11 February 2013 17:18, Ryan Cook <cookrn@gmail.com> wrote:
>
>> Hey Peter,
>>
>> How does this look? DSLs · GitHub
>>
>> By making the Room a separate object, the scoping might be more as you
>> had
>> hoped e.g. the door method
>>
>> Regarding the yield vs. instance_eval -- this is a matter of opinion. If
>> you wanted to be very explicit, you could yield the adventure and room
>> objects to the blocks given to those methods. This makes your DSL seem a
>> bit less magic, but maybe that's ok.
>>
>> I gave a talk on DSLs at Boulder Ruby a while back that you might find
>> interesting: https://speakerdeck.com/cookrn/dsls-as-teaching-tools
>>
>> Ryan
>>
>>
>> On Mon, Feb 11, 2013 at 8:18 AM, Peter Hickman < > >> peterhickman386@googlemail.com> wrote:
>>
>>> I am playing around with writing some DSLs to help me get more familier
>>> with how they work and I have a couple of questions. First here's an
>>> example of one I am playing with (based on the classic dungeon
adventure
>>> problem)
>>>
>>> class Adventure
>>> def initialize
>>> @rooms = Hash.new
>>> @current_room = nil
>>> end
>>>
>>> def room(reference, &block)
>>> if @rooms.has_key?(reference)
>>> puts "Error: Room #{reference} has already been defined"
>>> else
>>> @rooms[reference] = {:short => '', :long => '', :exits =>
>>> Hash.new}
>>> end
>>>
>>> @current_room = reference
>>>
>>> yield block
>>> end
>>>
>>> def short(text)
>>> @rooms[@current_room][:short] = text
>>> end
>>>
>>> def long(text)
>>> @rooms[@current_room][:long] = text
>>> end
>>>
>>> def door(direction, destination, &block)
>>> if @rooms[@current_room][:exits].has_key?(direction)
>>> puts "Error: Room #{@current_room} already has an exit defined in
>>> #{direction}"
>>> else
>>> @rooms[@current_room][:exits][direction] = {:where =>
destination,
>>> :condition => block_given? ? block : true}
>>> end
>>> end
>>> end
>>>
>>> class Object
>>> def adventure(&block)
>>> a = Adventure.new
>>>
>>> a.instance_eval(&block)
>>> end
>>> end
>>>
>>> adventure do
>>> room :cave do
>>> short "You are in a large cave"
>>> long "You are in a very large cave"
>>> door :west, :another_cave
>>> door :east, :exit do
>>> if player.has_item(:key)
>>> true
>>> else
>>> false
>>> end
>>> end
>>> end
>>>
>>> room :another_cave do
>>> short "A cave"
>>> long "Oh great ... another cave"
>>> door :east, :cave
>>> end
>>>
>>> room :exit do
>>> short "Freedom"
>>> long "Freeeeeeeeeeedom!!!!!!"
>>> door :west, :cave
>>> end
>>>
>>> require 'pp'
>>> pp @rooms
>>> end
>>>
>>> Now this works just fine but a couple of things just don't seem right.
>>> Notice the @current_room variable that is set in the room method and
>>> referenced in short, long and door. This seems clunky - it is basically
>>> a
>>> global variable to help tie things together. Is there some better way
of
>>> doing this? Perhaps by passing it as a parameter somehow.
>>>
>>> Also is the yield at the bottom of the room method the right way to go
>>> or
>>> should I be calling instance_eval or similar?
>>>
>>> Also how do I stop, for example, the door method being called outside
of
>>> the room method? I have a solution where I set @current_room to nil
>>> after
>>> the yield and then each method checks to see if it is set and if not it
>>> errors. But again this seems to be quite a pain.
>>>
>>>
>>
>>
>> --
>> Ryan Cook
>> 720.319.7660
>>
>