Tricky DSL, how to do it?

I'd want to write a DSL such that a surface method_missing catches
undefined methods and records the blocks that they define e.g.

  foo do
    puts "foo"
  end

So I would end up with:

  { :foo=>#<Proc ...> }

Presently I have something like this:

···

#
  class Evaluator < BasicObject
    def initialize(&block)
      @__config__ = {}
      instance_eval(&block) if block
    end

    def __config__
      @__config__
    end

    def method_missing(sym, *args, &block)
      @__config__[sym] = block
    end
  end

However when I call on a block I want it to evaluate as if in the defining
context (in this case toplevel), not inside the "DSL" class that evaluated
via method_missing.

  e = Evaluator.new do
    foo do
      puts "foo"
    end
  end

  e.__config__[:foo].call

Instead of what I want, I get a method missing error for #puts.

Any ideas?

You are missing a `super` on the method_missing. first you should ask if is
a method on the class, and if it is, call super with no args.
try:
class Evaluator < BasicObject
    def initialize(&block)
      @__config__ = {}
      instance_eval(&block) if block
    end

    def __config__
      @__config__
    end

    def method_missing(sym, *args, &block)
      respond_to? sym ? super : @__config__[sym] = block
    end
  end

···

On Sun, Nov 6, 2011 at 5:35 PM, Intransition <transfire@gmail.com> wrote

I'd want to write a DSL such that a surface method_missing catches
undefined methods and records the blocks that they define e.g.

  foo do
    puts "foo"
  end

So I would end up with:

  { :foo=>#<Proc ...> }

Presently I have something like this:

  #
  class Evaluator < BasicObject
    def initialize(&block)
      @__config__ = {}
      instance_eval(&block) if block
    end

    def __config__
      @__config__
    end

    def method_missing(sym, *args, &block)
      @__config__[sym] = block
    end
  end

However when I call on a block I want it to evaluate as if in the defining
context (in this case toplevel), not inside the "DSL" class that evaluated
via method_missing.

  e = Evaluator.new do
    foo do
      puts "foo"
    end
  end

  e.__config__[:foo].call

Instead of what I want, I get a method missing error for #puts.

Any ideas?

--
Sincerely,

Isaac Sanders
Section C-4B Vice Chief, Order of the Arrow
Vice Chief of Administration, Tecumseh #65
Eagle Scout

Any ideas?

This might not be the best solution, but if you give initialize
an argument about its surrounding state, then it becomes easier.

class Evaluator < Object
  attr_reader :__config__, :__outside__

  def initialize(outside, &block)
    @__config__ = {}
    @__outside__ = outside
    instance_eval &block
  end

  def method_missing(sym, *args, &block)
    if block_given?
      @__outside__.class.class_eval do
        define_method sym do |*args|
          block.call *args
        end
      end
      @__config__[sym] =
@__outside__.class.instance_method(sym).bind(self)
    else
      super
    end
  end

end

e = Evaluator.new(self) do
  foo do
    puts "foo"
  end
end

e.__config__[:foo].call

I don't know if that's what you were after, but after playing around
for a bit that's what I came up with.

Let me know if it helps,

-Luke

···

--
Posted via http://www.ruby-forum.com/\.

You can't have both (evaluate with self = Evaluator to trigger
Evaluator's method missing and invoking the block in the defining
context) at the same time.

Question is, what happens if a missing method is invoked? In your
implementation the block is returned. I would at least change that
to return nil to avoid nasty side effects. But generally execution
cannot reasonably continue. What about catching the exception and
recording it?

12:54:59 ~$ ./x.rb
{:foo=>#<Proc:0x101b3554@./x.rb:24>}
12:55:02 ~$ cat -n x.rb
     1 #!/bin/env ruby19
     2
     3 class Evaluator < BasicObject
     4 def initialize(&block)
     5 @__config__ = {}
     6
     7 if block
     8 begin
     9 block.call
    10 rescue ::NoMethodError => e
    11 @__config__[e.name] = block
    12 nil
    13 end
    14 end
    15 end
    16
    17 def __config__
    18 @__config__
    19 end
    20
    21 end
    22
    23
    24 e = Evaluator.new do
    25 foo do
    26 puts "foo"
    27 end
    28 end
    29
    30 p e.__config__
    31
12:55:03 ~$

Kind regards

robert

···

On Sun, Nov 6, 2011 at 11:35 PM, Intransition <transfire@gmail.com> wrote:

I'd want to write a DSL such that a surface method_missing catches undefined
methods and records the blocks that they define e.g.
foo do
puts "foo"
end
So I would end up with:
{ :foo=>#<Proc ...> }
Presently I have something like this:

#
class Evaluator < BasicObject
def initialize(&block)
@__config__ = {}
instance_eval(&block) if block
end
def __config__
@__config__
end
def method_missing(sym, *args, &block)
@__config__[sym] = block
end
end
However when I call on a block I want it to evaluate as if in the defining
context (in this case toplevel), not inside the "DSL" class that evaluated
via method_missing.
e = Evaluator.new do
foo do
puts "foo"
end
end

e.__config__[:foo].call
Instead of what I want, I get a method missing error for #puts.
Any ideas?

--
remember.guy do |as, often| as.you_can - without end
http://blog.rubybestpractices.com/

You could capture the calling self by getting it from the binding of
the block and hide the evaluating call behind your own #eval method:

class Evaluator < BasicObject
  def initialize(&block)
    @__config__ = {}
    if block
      @context = block.binding.eval("self")
      instance_eval(&block)
    end
  end

  def __config__
    @__config__
  end

  def method_missing(sym, *args, &block)
    @__config__[sym] = block
  end

  def eval(sym, *args)
    @context.instance_exec(*args, &@__config__[sym])
  end

end

e = Evaluator.new do
  foo do
    puts "foo"
  end

  bar do |txt|
    puts "txt = #{txt}"
  end
end

e.eval(:foo) # => "foo"
e.eval(:bar, "hello") # => "txt = hello"

Regards,
Sean

···

On Sun, Nov 6, 2011 at 10:35 PM, Intransition <transfire@gmail.com> wrote:

I'd want to write a DSL such that a surface method_missing catches undefined
methods and records the blocks that they define e.g.
foo do
puts "foo"
end
So I would end up with:
{ :foo=>#<Proc ...> }
Presently I have something like this:

#
class Evaluator < BasicObject
def initialize(&block)
@__config__ = {}
instance_eval(&block) if block
end
def __config__
@__config__
end
def method_missing(sym, *args, &block)
@__config__[sym] = block
end
end
However when I call on a block I want it to evaluate as if in the defining
context (in this case toplevel), not inside the "DSL" class that evaluated
via method_missing.
e = Evaluator.new do
foo do
puts "foo"
end
end

e.__config__[:foo].call
Instead of what I want, I get a method missing error for #puts.
Any ideas?

This seems very advanced.

I am very confused and also fascinated.

Please continue. :slight_smile:

···

--
Posted via http://www.ruby-forum.com/.

I'd be quite surprised if respond_to? ever evaluated true!

Sorry 'bout the double posting, but it hit me that it's only impossible
to bind a method to an instance that's of the same class. That and the
fact that the whole binding thing was useless in my previous post made
me go back to my computer and give it another shot.

It's still not a great solution, because I couldn't figure out how to
inherit from BasicObject and still have it work.

class Evaluator < Object
  attr_reader :__config__, :__outside__

  def initialize(outside, &block)
    @__config__ = {}
    @__outside__ = outside
    instance_eval &block
  end

  def method_missing(sym, *args, &block)
    if block_given?
      outside = @__outside__
      outside.class.class_eval do
        define_method sym do |*args|
          outside.instance_exec *args, &block
        end
      end
      @__config__[sym] = @__outside__.method(sym)
    else
      super
    end
  end

end

class Test
  def hunt
    puts "DEAD"
  end
end

t = Test.new

e = Evaluator.new(t) do
  foo do
    puts "foo"
  end

  baz do
    hunt
  end
end

e.__config__[:foo].call
e.__config__[:baz].call

-Luke

···

--
Posted via http://www.ruby-forum.com/.

Hi Guys

I am a little confused and need some help. I am a newbie for Ruby but i think i understand Object Oriented programming a little bit so i got confused here.

I have got 2 classes here

1. CEmployee

class CEmployee
def initialize()
puts "inside CEmployee"
end
end

2. CManager

class CManager < CEmployee
def initialize()
puts "inside CManager"
end
end

Now if i create an Object of CManager class (unlike other languages like C++ ) the constructor of it gets called but not the base class "Until i use the super keyword".

objManager = CManager\.new

Can someone please help me here? And even if the this is supposed to be working like this, isn't it something different from the Object Oriented Concept which says base class constructor always gets called first?

I really think that we should all take another look at the Ruby Object
Model. Dave Thomas has a great screencast on it. This question would be
answered if the ROM was analyzed closely.

···

On Mon, Nov 7, 2011 at 6:56 AM, Robert Klemme <shortcutter@googlemail.com>wrote:

On Sun, Nov 6, 2011 at 11:35 PM, Intransition <transfire@gmail.com> wrote:
> I'd want to write a DSL such that a surface method_missing catches
undefined
> methods and records the blocks that they define e.g.
> foo do
> puts "foo"
> end
> So I would end up with:
> { :foo=>#<Proc ...> }
> Presently I have something like this:
>
> #
> class Evaluator < BasicObject
> def initialize(&block)
> @__config__ = {}
> instance_eval(&block) if block
> end
> def __config__
> @__config__
> end
> def method_missing(sym, *args, &block)
> @__config__[sym] = block
> end
> end
> However when I call on a block I want it to evaluate as if in the
defining
> context (in this case toplevel), not inside the "DSL" class that
evaluated
> via method_missing.
> e = Evaluator.new do
> foo do
> puts "foo"
> end
> end
>
> e.__config__[:foo].call
> Instead of what I want, I get a method missing error for #puts.
> Any ideas?

You can't have both (evaluate with self = Evaluator to trigger
Evaluator's method missing and invoking the block in the defining
context) at the same time.

Question is, what happens if a missing method is invoked? In your
implementation the block is returned. I would at least change that
to return nil to avoid nasty side effects. But generally execution
cannot reasonably continue. What about catching the exception and
recording it?

12:54:59 ~$ ./x.rb
{:foo=>#<Proc:0x101b3554@./x.rb:24>}
12:55:02 ~$ cat -n x.rb
    1 #!/bin/env ruby19
    2
    3 class Evaluator < BasicObject
    4 def initialize(&block)
    5 @__config__ = {}
    6
    7 if block
    8 begin
    9 block.call
   10 rescue ::NoMethodError => e
   11 @__config__[e.name] = block
   12 nil
   13 end
   14 end
   15 end
   16
   17 def __config__
   18 @__config__
   19 end
   20
   21 end
   22
   23
   24 e = Evaluator.new do
   25 foo do
   26 puts "foo"
   27 end
   28 end
   29
   30 p e.__config__
   31
12:55:03 ~$

Kind regards

robert

--
remember.guy do |as, often| as.you_can - without end
http://blog.rubybestpractices.com/

--
Sincerely,

Isaac Sanders
Section C-4B Vice Chief, Order of the Arrow
Vice Chief of Administration, Tecumseh #65
Eagle Scout

Sean O'halpin wrote in post #1030618:

class Evaluator < BasicObject
  def initialize(&block)
    @__config__ = {}
    if block
      @context = block.binding.eval("self")
      instance_eval(&block)

That's a nice solution, I like that. I came up with something similar
after I posted last night, but I like this more.

Aside: What's with threads sneaking their way into other ones? I
can't be the only one seeing "Newbie:: Understand Constructor Flow"
alongside
the "Tricky DSL" one. Sorry if this has been discussed elsewhere.

Posted by Robert Klemme
You can't have both (evaluate with self = Evaluator to trigger
Evaluator's method missing and invoking the block in the defining
context) at the same time.

I'm not sure I understand what you're saying. With some solutions being
posted above, at least 'self' is changed in the invoked block. That
seems
to be what Thomas was looking for, at least from my understanding.

Can you elaborate on what you said?

Thanks,

-Luke

···

On Sun, Nov 6, 2011 at 10:35 PM, Intransition <transfire@gmail.com>

--
Posted via http://www.ruby-forum.com/\.

Nicely coded. And spot on. If I didn't know that self would always be
toplevel in my particular case this is the code I would be using.

Actually... this code seems like it might make for a good general use DSL
pattern. I'm going to try to generalize it further and see if makes sense
to make it a reusable library.

What do you think?

···

On Monday, November 7, 2011 9:14:44 AM UTC-5, Sean O'Halpin wrote:

You could capture the calling self by getting it from the binding of
the block and hide the evaluating call behind your own #eval method:

class Evaluator < BasicObject
  def initialize(&block)
    @__config__ = {}
    if block
      @context = block.binding.eval("self")
      instance_eval(&block)
    end
  end

  def __config__
    @__config__
  end

  def method_missing(sym, *args, &block)
    @__config__[sym] = block
  end

  def eval(sym, *args)
    @context.instance_exec(*args, &@__config__[sym])
  end

end

e = Evaluator.new do
  foo do
    puts "foo"
  end

  bar do |txt|
    puts "txt = #{txt}"
  end
end

e.eval(:foo) # => "foo"
e.eval(:bar, "hello") # => "txt = hello"

try it. I am pretty sure it would work.read this if you don't believe me:
http://www.trottercashion.com/2011/02/08/rubys-define_method-method_missing-and-instance_eval.html

···

On Sun, Nov 6, 2011 at 6:38 PM, Intransition <transfire@gmail.com> wrote:

I'd be quite surprised if respond_to? ever evaluated true!

--
Sincerely,

Isaac Sanders
Section C-4B Vice Chief, Order of the Arrow
Vice Chief of Administration, Tecumseh #65
Eagle Scout

Hi Guys

I am a little confused and need some help. I am a newbie for Ruby but i think i understand Object Oriented programming a little bit so i got confused here.

I have got 2 classes here

1. CEmployee

class CEmployee
    def initialize()
        puts "inside CEmployee"
    end
end

2. CManager

class CManager < CEmployee
    def initialize()
        puts "inside CManager"
    end
end

Now if i create an Object of CManager class (unlike other languages like C++ ) the constructor of it gets called but not the base class "Until i use the super keyword".

    objManager = CManager.new

Can someone please help me here? And even if the this is supposed to be working like this, isn't it something different from the Object Oriented Concept which says base class constructor always gets called first?

This is how it is supposed to work indeed, in Ruby there's no notion of default constructor like in C++ (a non-default constructor must be invoked explicitly even there). With a call to super, a subclass can choose how a superclass constructor is invoked in regards to passed parameters and the order of statements. Or not to invoke it at all.

And this is not something unheard of in Object Oriented Concept world. Just look at Objective-C for quick examples, where a subclass is responsible also for its superclass allocation, not only initialization.

Genandy.

···

On Nov 6, 2011, at 7:16 PM, Ankush Ganatra wrote:

Hi Luke,

I think your idea about the "outside" is on the right track. I ended doing
something akin to that, but in my case the "outside" is just toplevel (aka
<main>). Your solution is defining methods on the class of the outside
object. It gets the desired results, but at the expense of polluting the
class's namespace. In my case, using toplevel, that actually might not be a
problem, so yea, this might be a workable solution. But I found using
methods had some other limitations (e.g. problem evaluate it in another
scope). So I ended up doing something a bit different, but at it's core
it's the same approach as yours, just without the method definitions. The
"trick" I came up with was:

      def block.call(*args)
         ::Kernel.eval('self',::TOPLEVEL_BINDING).instance_exec(*args,
&self)
      end

This defines a singleton method on the block overriding the original #call
method, forcing evaluation of the block at the toplevel. Once I had that
working, I refactored so I could get the same result but without have to
monkey patch the Proc object. So far so good.

If you are interested in the actual project code, it is here:

Thanks for the help.

Check it out:

  class ContextDSL < BasicObject

    def initialize(&block)
      @missing_block = block
    end

    def eval!(&block)
      @context = block.binding.eval("self")
      instance_eval(&block)
    end

    def method_missing(sym, *xargs, &block)
      context = @context
      context_block = ::Proc.new do |*args|
        context.instance_exec(*args, &block)
      end
      @missing_block.call(sym, context_block)
    end

  end

Try it out:

  config = {}

  dsl = ContextDSL.new do |name, block|
    config[name] = block
  end

  dsl.eval! do
    foo do
      puts self
    end
  end

  config[:foo].call #=> <main>

Now with this general solution in hand, it's really got me asking why Ruby
just doesn't provide a way to set the context, e.g.

  f = lambda{ self }

  f.context = "Any Object"

  f.call #=> "Any Object"

Ankush, an additional note: super is different than ordinary method
calls with regard to brackets. If you just write "super" the complete
argument list of the current method is copied (including block if
passed); regular methods are invoked with empty argument list in this
case. If you want to explicitly invoke the super class method without
arguments you need to write "super()". Providing specific arguments
works as well of course: "super(1, 'foo')".

Kind regards

robert

···

On Mon, Nov 7, 2011 at 4:32 AM, Gennady Bystritsky <gennady-ruby@bystr.com> wrote:

On Nov 6, 2011, at 7:16 PM, Ankush Ganatra wrote:

I am a little confused and need some help. I am a newbie for Ruby but i think i understand Object Oriented programming a little bit so i got confused here.

I have got 2 classes here

1. CEmployee

class CEmployee
def initialize()
puts "inside CEmployee"
end
end

2. CManager

class CManager < CEmployee
def initialize()
puts "inside CManager"
end
end

Now if i create an Object of CManager class (unlike other languages like C++ ) the constructor of it gets called but not the base class "Until i use the super keyword".

objManager = CManager\.new

Can someone please help me here? And even if the this is supposed to be working like this, isn't it something different from the Object Oriented Concept which says base class constructor always gets called first?

This is how it is supposed to work indeed, in Ruby there's no notion of default constructor like in C++ (a non-default constructor must be invoked explicitly even there). With a call to super, a subclass can choose how a superclass constructor is invoked in regards to passed parameters and the order of statements. Or not to invoke it at all.

--
remember.guy do |as, often| as.you_can - without end
http://blog.rubybestpractices.com/

An init method in Objective-C (at least in Cocoa) only calls its
superclass's init method, not alloc. The class method alloc
automatically allocates enough space for the whole object.

(The strange thing about calling -[super init] in Cocoa is that you
have to assign your self variable to the result, just in case -[super
init] actually returns a whole different object. In that case, the
super initializer will deallocate the original one. That's an area I
don't understand very well.)

···

On Sun, Nov 6, 2011 at 9:32 PM, Gennady Bystritsky <gennady-ruby@bystr.com> wrote:

And this is not something unheard of in Object Oriented Concept world. Just look at Objective-C for quick examples, where a subclass is responsible also for its superclass allocation, not only initialization.