是否有刚刚分配的变量的ruby钩子?

时间:2011-10-15 02:04:49

标签: ruby

这是我理想的样子。用户:

a="hello"

,输出为

You just allocated "a" !
 => "Hello" 

只要我能发出消息,订单就无关紧要了。

2 个答案:

答案 0 :(得分:7)

不,没有直接的方法来实现这一点,因为在执行代码之前,Ruby字节码编译器会丢弃局部变量名。

唯一的指令YARV(MRI 1.9.2中使用的Ruby VM)提供的局部变量是getlocalsetlocal,它们都是对整数指标而不是变量名进行操作。以下是1.9.2来源中insns.def的摘录:

/**********************************************************/
/* deal with variables                                    */
/**********************************************************/

/**
  @c variable
  @e get local variable value (which is pointed by idx).
  @j idx 
 */
DEFINE_INSN
getlocal
(lindex_t idx)
()
(VALUE val)
{
    val = *(GET_LFP() - idx);
}

/**
  @c variable
  @e set local variable value (which is pointed by idx) as val.
  @j idx 
 */
DEFINE_INSN
setlocal
(lindex_t idx)
(VALUE val)
()
{
    (*(GET_LFP() - idx)) = val;
}

有可能破解MRI源(或使用set_trace_func并潜入Binding对象 - 请参阅sarnold的答案),以便在设置局部变量时通知您,但是没有这样做的任何高级方式都可能无法在不进入解释器内部的情况下检索这些局部变量的名称。

答案 1 :(得分:4)

我想出了一个基于set_trace_func的解决方案。我实际上无法反击limitations Charlie points out,但我相信我所写的内容应该或多或少地与你所描述的一样:

#!/usr/bin/ruby

def hash_from_binding(bin)
    h = Hash.new
    bin.eval("local_variables").each do |i|
        v = bin.eval(i)
        v && h[i]=bin.eval(i)
    end
    bin.eval("instance_variables").each do |i|
        v = bin.eval(i)
        v && h[i]=bin.eval(i)
    end
    h
end


$old_binding = hash_from_binding(binding)
$new_binding = hash_from_binding(binding)

set_trace_func lambda {|event, file, line, id, bin, classname|
  $old_binding = $new_binding
  $new_binding = hash_from_binding(bin)

  diff = $new_binding.reject {|k, v| $old_binding[k] == $new_binding[k]}

  printf("%d:\n", line)
#  $old_binding.each do |k,v|
#     printf("%8s: %s\n", k, v)
#  end
#  $new_binding.each do |k,v|
#     printf("%8s: %s\n", k, v)
#  end
  diff.each do |k,v|
      printf("%8s: %s\n", k, v)
  end
}

a = "hello"
b = "world"
c = "there"
d = nil
e = false
@a = "HELLO"
@b = "WORLD"
A="Hello"
B="World"

def foo
    foo_a = "foo"
    @foo_b = "foo"
end

foo

hash_from_binding(bin)会将Binding对象转换为Hash。如果您不想要,可以删除instance_variables部分。如果您不想要,可以删除local_variables部分。 v && h[i]=bin.eval(i)的复杂性是由于Binding对象中的奇怪 - 即使跟踪函数还没有“解析”所有内容,传递给跟踪函数的Binding对象 知道将在范围中定义的所有变量。这很尴尬。这至少会过滤掉尚未赋值的变量。因此,它还会过滤掉分配值nilfalse的变量。您可能对跟踪功能中的reject操作感到满意,可以为您执行过滤工作。

set_trace_func API将为每个解析的源代码行调用跟踪方法。 (面对不同的执行环境,这可能是一个极大的限制。)所以我编写了一个跟踪函数,将绑定对象与 new 绑定对象和报告进行比较已更改的变量定义。您还可以报告 new 的变量定义,但这会错过以下情况:

a = 1
a = 2

一个有趣的结果是,报告的绑定在函数调用中发生了巨大变化,因为新变量已经生效,旧变量从环境中删除。这可能会过度混淆输出,但是event参数可能用于确定是否打印新的变量值。 (由于函数调用可能会修改“返回者”代码范围内的变量值,因此打印它们似乎都是安全的方法。)

当工具自行运行时,它会输出以下内容:

$ ./binding.rb 
38:
39:
       a: hello
40:
       b: world
41:
       c: there
42:
43:
44:
      @a: HELLO
45:
      @b: WORLD
46:
48:
48:
48:
53:
      @a: HELLO
      @b: WORLD
48:
49:
50:
   foo_a: foo
50:
  @foo_b: foo
$ 

这是我运行此工具时最复杂的Ruby代码,所以它可能会破坏一些非常重要的东西。