1

Note, this is a follow up to my question here.

I'm trying to parse the following Tcl code:

foo bar {
  biz buzz
}

In Tcl, foo is the method name, bar is the argument, and the rest is a "block" to be processed by eval.

Now here is my current implementation to this:

def self.foo(name, &block)
  puts "Foo --> #{name}"
  if block
    puts "block exists"
  else
    puts "block does not exist"
  end
end

def self.method_missing(meth, *args, &block)
  p meth
  p block
  meth.to_s &block
end

tcl = <<-TCL.gsub(/^\s+/, "").chop
  foo bar {
    biz buzz
  }
TCL

instance_eval(tcl)

Which outputs the following:

:bar
#<Proc:0x9e39c80@(eval):1>
Foo --> bar
block does not exist

In this example, when the block is passed up to the foo method, it does not exist. Yet in method_missing it does exist (at least it appears to exist). What's going on here?

Note, I am aware of ruby's precedence of parentheses and realize this works:

foo (bar) {
  biz buzz
}

However, I want to have the parentheses omitted. So is this possible in ruby (without lexical analysis)?

Community
  • 1
  • 1
elmt
  • 1,604
  • 14
  • 24
  • Note that in Tcl you'd be more likely to process a “block” passed to a procedure with `uplevel`, but that's a concept which doesn't map to Ruby at all; the two languages simply have different concepts of what scoping means. – Donal Fellows Apr 21 '11 at 14:33
  • Donal, I understand that. Fortunately what I have to parse is as simple as this, nothing more. – elmt Apr 22 '11 at 02:58
  • That's partially why I only posted it as a comment. – Donal Fellows Apr 23 '11 at 13:54

3 Answers3

1

This has nothing to do with method_missing. You simply can't omit parentheses when passing block along with some parameters. In your case, Ruby will try to call bar method with block as an argument, and the result of it all will be passed to foo method as a single argument.

You can try this yourself by simplifying the method call (all the metaprogramming just obscures the real problem in your case):

# make a method which would take anything
def a *args, &block
end

# try to call it both with argument and a block:
a 3 {
  4
}
#=>SyntaxError: (irb):16: syntax error, unexpected '{', expecting $end
#   from /usr/bin/irb:12:in `<main>'
Mladen Jablanović
  • 43,461
  • 10
  • 90
  • 113
1

You can do (I marked the lines I changed):

def self.foo args                  # changed
  name, block = *args              # changed
  puts "Foo --> #{name}"
  if block
    puts "block exists"
  else
    puts "block does not exist"
  end
end

def self.method_missing(meth, *args, &block)
  p meth
  p block
  return meth.to_s, block          # changed
end

That way, block will exist.

Sony Santos
  • 5,435
  • 30
  • 41
  • Wow this is great! Thanks for not repeating what I already knew and proving that if there's a will, there's a way. – elmt Apr 22 '11 at 02:57
0

So the best solution I've found is to just gsub the string before processing it.

tcl = <<-TCL.gsub(/^\s+/, "").chop.gsub('{', 'do').gsub('}', 'end')
  foo bar {
    biz buzz
  }
TCL
elmt
  • 1,604
  • 14
  • 24