# DSL implementation question

**URL:** <https://www.ruby-forum.com/t/dsl-implementation-question/133836>\
**Category:** Ruby\
**Created:** [April 4, 2008, 5:49am UTC](https://www.ruby-forum.com/t/dsl-implementation-question/133836 "2008-04-04T05:49:44Z")\
**Posts on this page:** 3\
**Page:** 1

<div class="post-metadata">

**Author:** ![tender\_flake](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/t/b9bd4f/32.png) [@tender\_flake](https://www.ruby-forum.com/u/tender_flake)\
**Post date:** [April 4, 2008, 5:49am UTC](https://www.ruby-forum.com/t/dsl-implementation-question/133836/1 "2008-04-04T05:49:44Z")

</div>

Hello,

I’m trying to write a simple DSL to define memory maps of hardware  
devices.  
Something like:

cfg\_block “top\_device”, :depth =\> 16 do  
reg “ctrl\_reg”, :offset =\> 0, :mode =\> “RW”  
reg “status\_reg”, :offset =\> 1, :mode =\> “R”  
end

I’ve implemented something but I am not satisfied with it.

The problem I have is about scoping. How can I make the reg command  
in my  
small DSL exemple above execute in the context of the object created  
by the  
cfg\_block method? The way I implemented this is by passing the  
created  
cfg\_block object as a block parameter and then passing it to every reg  
command. E.g:

cfg\_block “top\_device”, :depth =\> 16 do | **dev** |  
reg “ctrl\_reg”, **dev** , :offset…  
end

I consider this redundant since I’d like the context to provide the  
cfg\_block reference. I tried multiple things but haven’t figured it  
out  
using blocks. Is there a way to get rid of **dev**?

Suggestions would be greatly appreciated. Thanks in advance.

My implementation follows below:

module Register\_defs

class Cfg\_Block \< Hash

```
  def initialize(name, params={})
     self[:name] = name
     self[:regs] = {}
     self.merge!(params)
  end

  def add_reg(reg)
     self[:regs][reg[:name]] = reg
  end

```

end

class Register \< Hash  
def initialize(name, cfg\_block, params={})  
self[:name] = name  
self.merge!(params)  
cfg\_block.add\_reg(self)  
end

end

def cfg\_block(name, params={})  
cfg\_block = Cfg\_Block.new(name, params)  
yield(cfg\_block)  
cfg\_block  
end

def reg(name, cfg\_block, params={})  
reg = Register.new(name, cfg\_block, params)  
yield(reg)  
reg  
end

end

include Register\_defs

modules = cfg\_block “top\_block” do |cb|

reg “reg1”, cb, :addr =\> 0 do  
# do something  
end

reg “reg2”, cb, :addr =\> 0 do  
# do something else  
end

end

p modules

---

<div class="post-metadata">

**Author:** ![tender\_flake](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/t/b9bd4f/32.png) [@tender\_flake](https://www.ruby-forum.com/u/tender_flake)\
**Post date:** [April 4, 2008, 6:15am UTC](https://www.ruby-forum.com/t/dsl-implementation-question/133836/2 "2008-04-04T06:15:30Z")

</div>

You’d do something like this:

def cfg\_block(name, params={}, &block)

# Other stuff

my\_obj.instance\_eval &block  
end

…where my\_obj is the object that you were previously yielding. 🙂

–Jeremy

On Thu, Apr 3, 2008 at 11:49 PM, tender flake  
[removed\_email\_address@domain.invalid](mailto:removed_email_address@domain.invalid) wrote:

> ```
> end
> self.merge!(params)
> 
> ```
> 
> modules = cfg\_block “top\_block” do |cb|
> 
> p modules

–  
[http://jeremymcanally.com/](http://jeremymcanally.com/)  
[http://entp.com](http://entp.com)

Read my books:  
Ruby in Practice ([Ruby in Practice](http://manning.com/mcanally/))  
My free Ruby e-book ([http://humblelittlerubybook.com/](http://humblelittlerubybook.com/))

Or, my blogs:

> **[Site not found · DreamHost](http://mrneighborly.com)**
>
> The owner of this domain has not yet uploaded their website.

[http://rubyinpractice.com](http://rubyinpractice.com)

---

<div class="post-metadata">

**Author:** ![tender\_flake](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/t/b9bd4f/32.png) [@tender\_flake](https://www.ruby-forum.com/u/tender_flake)\
**Post date:** [April 5, 2008, 1:55am UTC](https://www.ruby-forum.com/t/dsl-implementation-question/133836/3 "2008-04-05T01:55:06Z")

</div>

On Fri, Apr 4, 2008 at 4:49 AM, tender flake  
[removed\_email\_address@domain.invalid](mailto:removed_email_address@domain.invalid) wrote:

> I’ve implemented something but I am not satisfied with it.

Hi,

I wrote Doodle to do this kind of thing (gem install doodle):

require ‘doodle’  
require ‘pp’

class Register \< Doodle::Base  
has :name  
has :offset  
has :mode  
end

class ConfigBlock \< Doodle::Base  
has :name  
has :depth  
has :registers, :init =\> [], :collect =\> { :reg =\> Register }  
end

def cfg\_block(\*args, &block)  
ConfigBlock(\*args, &block)  
end

modules = cfg\_block “top\_device”, :depth =\> 16 do  
reg “ctrl\_reg”, :offset =\> 0, :mode =\> “RW”  
reg “status\_reg”, :offset =\> 1, :mode =\> “R”  
end

pp modules

Output:

#\<ConfigBlock:0xb7b9b5c4  
@depth=16,  
@name=“top\_device”,  
@registers=  
[#\<Register:0xb7b9582c @mode=“RW”, @name=“ctrl\_reg”, @offset=0\>,  
#\<Register:0xb7b8ecfc @mode=“R”, @name=“status\_reg”, @offset=1\>]\>

If you wanted validation on the parameters, you could do something like:

require ‘doodle’

class Named \< Doodle::Base  
has :name, :kind =\> String do  
from Symbol do |sym|  
sym.to\_s  
end  
end  
end

class Register \< Named  
has :offset, :kind =\> Integer do  
must “be in range 0-15” do |offset|  
(0…15).include?(offset)  
end  
end  
has :mode, :kind =\> String do |mode|  
valid\_modes = %w[R W RW]  
must “be one of #{valid\_modes.join(', ')}” do |mode|  
valid\_modes.include?(mode)  
end  
end  
end

class ConfigBlock \< Named  
has :depth, :kind =\> Integer  
has :registers, :init =\> [], :collect =\> { :reg =\> Register }  
end

def cfg\_block(\*args, &block)  
ConfigBlock(\*args, &block)  
end

modules = cfg\_block :top\_device, :depth =\> 16 do  
reg :ctrl\_reg, :offset =\> 0, :mode =\> “RW”  
reg :status\_reg, :offset =\> 1, :mode =\> “WR”  
end

#=\> memory-map2.rb:35: mode must be one of R, W, RW - got String(“WR”)  
(Doodle::ValidationError)

Regards,  
Sean
