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
> contextin 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 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