Рубиновые блоки с method_missing

Обратите внимание, это продолжение моего вопроса здесь.

Я пытаюсь разобрать следующий код Tcl:

foo bar {
  biz buzz
}

В Tcl foo это имя метода, bar является аргументом, а остальное является "блоком" для обработки eval,

Теперь вот моя текущая реализация этого:

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)

Который выводит следующее:

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

В этом примере, когда блок передается до foo Метод, он не существует. Еще в method_missing оно существует (по крайней мере, оно существует). Что тут происходит?

Обратите внимание, я знаю приоритет скобок в Ruby и понимаю, что это работает:

foo (bar) {
  biz buzz
}

Тем не менее, я хочу опустить скобки. Так возможно ли это в рубине (без лексического анализа)?

3 ответа

Решение

Вы можете сделать (я пометил строки, которые я изменил):

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

Таким образом, блок будет существовать.

Это не имеет ничего общего с method_missing, Вы просто не можете опустить скобки при передаче блока вместе с некоторыми параметрами. В вашем случае Руби попробует позвонить bar метод с блоком в качестве аргумента, и результат всего этого будет передан foo метод в качестве одного аргумента.

Вы можете попробовать это сами, упростив вызов метода (все метапрограммирование просто скрывает реальную проблему в вашем случае):

# 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>'

Так что лучшее решение, которое я нашел, это просто gsub строка перед обработкой.

tcl = <<-TCL.gsub(/^\s+/, "").chop.gsub('{', 'do').gsub('}', 'end')
  foo bar {
    biz buzz
  }
TCL
Другие вопросы по тегам