# DSL challenge. Do you guys have any elegant ideas?

**URL:** <https://www.ruby-forum.com/t/dsl-challenge-do-you-guys-have-any-elegant-ideas/145459>\
**Category:** Ruby\
**Created:** [August 10, 2008, 1:48am UTC](https://www.ruby-forum.com/t/dsl-challenge-do-you-guys-have-any-elegant-ideas/145459 "2008-08-10T01:48:10Z")\
**Posts on this page:** 5\
**Page:** 1

<div class="post-metadata">

**Author:** ![lynchnco](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/l/c77e96/32.png) [@lynchnco](https://www.ruby-forum.com/u/lynchnco)\
**Post date:** [August 10, 2008, 1:48am UTC](https://www.ruby-forum.com/t/dsl-challenge-do-you-guys-have-any-elegant-ideas/145459/1 "2008-08-10T01:48:10Z")

</div>

Hi,  
I’m writing a DSL for some parsing. And would like the following  
functionality. I was wondering if there’s some metaprogramming experts  
in here that can share a bit of wisdom.

Here’s the ideal functionality:

string = Farm.create do  
barn do  
animal “dog”  
animal “cat”  
end  
pond do  
animal “whale”  
animal “shark”  
end  
end

The string should print:  
Farm contains  
[  
Barn contains  
[  
dog  
cat  
]  
Pond contains  
[  
whale  
shark  
]  
]

It would be really really nice to have this also:  
Farm.create do  
animal “whale”  
end

#throws "IllegalMethodError: method animal() can only be called under  
barn()

I currently have a rather inelegant hack using instance\_eval, which  
messes up a lot of other things.  
Thanks a lot for your help.  
-Patrick

---

<div class="post-metadata">

**Author:** ![lynchnco](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/l/c77e96/32.png) [@lynchnco](https://www.ruby-forum.com/u/lynchnco)\
**Post date:** [August 10, 2008, 3:06am UTC](https://www.ruby-forum.com/t/dsl-challenge-do-you-guys-have-any-elegant-ideas/145459/2 "2008-08-10T03:06:40Z")

</div>

Patrick Li wrote:

> I currently have a rather inelegant hack using instance\_eval, which  
> messes up a lot of other things.

If you want to avoid instance\_eval (and that’s a good idea for DSL  
syntax in many cases, IMO), one alternative is to use yield to get  
syntax like:

string = Farm.create do |farm|  
farm.barn do |barn|  
barn.animal “dog”  
barn.animal “cat”  
end  
farm.pond do |pond|  
pond.animal “whale”  
pond.animal “shark”  
end  
end

A little less concise, but you avoid the scope changes that come with  
instance\_eval.

---

<div class="post-metadata">

**Author:** ![lynchnco](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/l/c77e96/32.png) [@lynchnco](https://www.ruby-forum.com/u/lynchnco)\
**Post date:** [August 10, 2008, 6:10am UTC](https://www.ruby-forum.com/t/dsl-challenge-do-you-guys-have-any-elegant-ideas/145459/3 "2008-08-10T06:10:14Z")

</div>

On Aug 9, 2008, at 5:46 PM, Patrick Li wrote:

> whale  
> shark  
> ]  
> ]
> 
> It would be really really nice to have this also:  
> Farm.create do  
> animal “whale”  
> end

cfp: ~\> cat a.rb

# following is an example dsl built from my current idea of dsl best

# practices, which can be found @

# 

[http://drawohara.com/post/39582749/ruby-the-best-way-to-build-ruby-dsls](http://drawohara.com/post/39582749/ruby-the-best-way-to-build-ruby-dsls)

# 

f =  
farm {  
barn {  
animal :dog  
animal :cat  
}

```
   pond {
     animal :whale
     animal :shark
   }
 }

```

p f

#=\> #\<Farm:0x250d0 @pond=#\<Farm::Pond:0x24e78 @animals=[#\<Whale:  
0x24d74\>, #Shark:0x24d38]\>, @barn=#\<Farm::Barn:0x24fe0  
@animals=[#Dog:0x24edc, #Cat:0x24ea0]\>\>

BEGIN {

# these classe are unimportant

# 

```
 class Animal; end
 class Dog < Animal; end
 class Cat < Animal; end
 class Whale < Animal; end
 class Shark < Animal; end

```

# this is important, note how it _wraps_ a class so the

instance\_eval is the

# _dsl_ instance\_eval, note that of the wrapped object

# 

```
 module Dsl
   module ClassMethods
     def dsl &block
       unless @dsl
         name = self.name.downcase.split(%r/::/).last
         @dsl = (
           Class.new do
             attr name
             const_set :Name, name
             def initialize object, &block
               ivar = "@#{ self.class.const_get(:Name) }"
               instance_variable_set ivar, object
               instance_eval &block if block
             end
           end
         )
       end
       @dsl.module_eval &block if block
       @dsl
     end
   end

   module InstanceMethods
     def dsl &block
       self.class.dsl.new(self, &block)
     end
   end

   def Dsl.included other
     other.send :extend, ClassMethods
     other.send :include, InstanceMethods
   end
 end

```

# again this is mostly unimportant, just note how they make use of

the module

# for declaring the dsl class, and how they use it in intiialize

# 

```
 class Farm
   include Dsl

   attr_accessor 'barn'
   attr_accessor 'pond'

   def initialize &block
     dsl &block
   end

   class Barn
     include Dsl

     attr_accessor 'animals'

     def initialize &block
       @animals = []
       dsl &block
     end

     dsl {
       def animal name
         barn.animals << Object.const_get(name.to_s.capitalize).new
       end
     }
   end

   class Pond
     include Dsl

     attr_accessor 'animals'

     def initialize &block
       @animals = []
       dsl &block
     end

     dsl {
       def animal name
         pond.animals << Object.const_get(name.to_s.capitalize).new
       end
     }
   end

   dsl {
     def barn *a, &b
       farm.barn = Barn.new(*a, &b)
     end

     def pond *a, &b
       farm.pond = Pond.new(*a, &b)
     end
   }
 end

```

# this is just the top level hook

# 

```
 def farm(*a, &b) Farm.new(*a, &b) end

```

}

a @ [http://codeforpeople.com/](http://codeforpeople.com/)

---

<div class="post-metadata">

**Author:** ![lynchnco](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/l/c77e96/32.png) [@lynchnco](https://www.ruby-forum.com/u/lynchnco)\
**Post date:** [August 10, 2008, 4:56pm UTC](https://www.ruby-forum.com/t/dsl-challenge-do-you-guys-have-any-elegant-ideas/145459/4 "2008-08-10T16:56:39Z")

</div>

Hi Patrick,

Here’s another solution. You didn’t specify the problems you were  
having, so I can’t be certain whether this solution avoids them.

Eric

====

Ruby training and Rails training available at [http://LearnRuby.com](http://LearnRuby.com) .

====

module Farm  
class \<\< self  
def create(&block)  
@scope\_stack = []  
push\_frame(:farm)  
class\_eval(&block)

```
  "Farm contains\n[\n" + indent(pop_frame) + "\n]\n"
end

def barn(&block)
  push_frame(:place)
  class_eval(&block)

  append_to_frame "Barn contains\n[\n" + indent(pop_frame) +
    "\n]\n"
end

def pond(&block)
  push_frame(:place)
  class_eval(&block)

  append_to_frame "Pond contains\n[\n" + indent(pop_frame) +
    "\n]\n"
end

def animal(name)
  raise "IllegalMethodError -- animal can only be called " \
        "under a place" unless
    top_frame[0] == :place
  append_to_frame name + "\n"
end

private

def push_frame(description)
  @scope_stack.push [description, ""]
end

def pop_frame
  @scope_stack.pop[1]
end

def top_frame
  @scope_stack[-1]
end

def append_to_frame(str)
  top_frame[1] << str
end

def indent(str, level = 2)
  str.split("\n").map { |s| " "*level + s }.join("\n")
end

```

end  
end

string = Farm.create do  
barn do  
animal “dog”  
animal “cat”  
end  
pond do  
animal “whale”  
animal “shark”  
end  
end

puts string

---

<div class="post-metadata">

**Author:** ![lynchnco](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/l/c77e96/32.png) [@lynchnco](https://www.ruby-forum.com/u/lynchnco)\
**Post date:** [August 10, 2008, 7:55pm UTC](https://www.ruby-forum.com/t/dsl-challenge-do-you-guys-have-any-elegant-ideas/145459/5 "2008-08-10T19:55:24Z")

</div>

Thanks for all the solutions.  
I’ve looked through every one of them and here’s my thoughts.

Joel VanderWerf: This is the ideal functionality. It’s easily  
extendable. Very nicely object-oriented. And it was the first design  
that I actually used. Unfortunately, having to explicitly call the  
receiver object every time is not very elegant. Which lead to me using  
instance\_eval to get around it.

The problem I was having with instance\_eval is this:  
instance\_eval messes up your scope. Which is counterintuitive from the  
users point of view.

i.e. the user cannot call private helper methods from within Farm.create

def barnCreator  
barn do  
animal “Dog”  
end  
end

Farm.create do  
barnCreator #throws illegalmethoderror  
end

Ara Howard: I think your solution solves the scope problems really  
nicely. Your code library is pretty advanced though. I’m going to need  
to understand it a little more fully before I can make a judgement.

Eric I: I notice that your manually controlling the “stack frame” to  
restrict access to certain methods. But you’re using class\_eval also,  
whose scope changes will run into the same problems as instance\_eval.  
I’m going to need to read through your code more carefully also to  
understand it.

So I guess the cleanest solution is still Joel’s. But I just need a way  
to implicitly specify the receiver without changing scope and killing  
all my closures.

Thanks very much for your help guys. I definately learned a lot from  
your solutions.  
-Patrick
