From: Peter Hickman Date: 2013-02-12T05:42:06+09:00 Subject: Re: Playing with DSLs, a question or two --047d7b621f2ac03d0804d578f0d4 Content-Type: text/plain; charset=ISO-8859-1 Thanks Ryan. That was just the sort of thing I was looking for. Thanks for that. On 11 February 2013 17:18, Ryan Cook 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 > --047d7b621f2ac03d0804d578f0d4 Content-Type: text/html; charset=ISO-8859-1 Content-Transfer-Encoding: quoted-printable
Thanks Ryan.

That was just the so= rt of thing I was looking for.

Thanks = for that.


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


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 matt= er of opinion. If you wanted to be very explicit, you could yield the adven= ture 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:=A0https://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 wr= iting 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
=A0 def initialize
= =A0 =A0 @rooms =3D Hash.new
=A0 =A0 @current_room =3D nil
=A0 end

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

=A0 =A0 @current_room =3D reference

=A0 =A0 yield block
=A0 end

=A0 de= f short(text)
=A0 =A0 @rooms[@current_room][:short] =3D text
=A0 end

=A0 def long(text)
=A0 =A0 @rooms[@current_ro= om][:long] =3D text
=A0 end

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

c= lass Object
=A0 def adventure(&block)
=A0 =A0 a =3D= Adventure.new

=A0 =A0 a.instance_eval(&block)=
=A0 end
end

adventure do
=A0 room :cave do<= /div>
=A0 =A0 short "You are in a large cave"
=A0 = =A0 long "You are in a very large cave"
=A0 =A0 door :w= est, :another_cave
=A0 =A0 door :east, :exit do
=A0 =A0 =A0 if player.has_item(= :key)
=A0 =A0 =A0 =A0 true
=A0 =A0 =A0 else
= =A0 =A0 =A0 =A0 false
=A0 =A0 =A0 end
=A0 =A0 end
=
=A0 end

=A0 room :another_cave do
=A0 =A0 short "A cave"
=A0 =A0 long "Oh great= ... another cave"
=A0 =A0 door :east, :cave
=A0 e= nd

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

=A0 require 'p= p'
=A0 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 refer= enced in short, long and door. This seems clunky - it is basically a global= =A0variable=A0to help tie things together. Is there some better way of doin= g 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 calle= d outside of the room method? I have a solution where I set @current_room t= o nil after the yield and then each method checks to see if it is set and i= f not it errors. But again this seems to be quite a pain.




--
Ryan Cook
720.319.7660

--047d7b621f2ac03d0804d578f0d4--