Is there a ruby ​​hook for a variable that was just highlighted?

That's what I would like ideally. User:

a="hello" 

and the output will be

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

The order does not matter if I can make this message.

+4
source share
2 answers

No, there is no direct way to make local variable names be discarded by the Ruby bytecode compiler before your code is executed.

The only YARV instructions (Ruby VM used in MRI 1.9.2) are to provide local variables, getlocal and setlocal , that work with both integer characters and variable names. Here is an excerpt from insns.def in source 1.9.2:

 /**********************************************************/ /* 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; } 

It may be possible to hack into the MRI source (or use set_trace_func and dive into the Binding object - see sarnold's answer) to tell you when a local variable is set, but there is no high-level way to do this, and you probably won't be able to Get the names of these local variables without diving into the internal interpreters.

+7
source

I came up with a solution based on set_trace_func . I can't resist the restrictions Charlie points out , but I believe that what I wrote should work more or less, as you described:

 #!/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) will turn the Binding object into a Hash . You can remove the instance_variables part if you do not want to. You can remove the local_variables part if you don't want to. The complication of v && h[i]=bin.eval(i) is due to the oddness in Binding objects - even though the trace function has not yet β€œfigured out” the whole content, the Binding object passed to the trace function knows about all the variables that will be identified in the area. It is not comfortable. This at least filters out variables that are not assigned a value. Because of this, it also filters out variables assigned to nil or false . You may be happy with the reject action in the trace function to filter for you.

The set_trace_func API set_trace_func called by the trace method for each source string that is parsed. (This can be a serious limitation to different runtimes.) Therefore, I wrote a trace function that will compare the old binding object with the new binding object and report changed variable definitions. You can also provide new variable definitions, but this will skip cases like:

 a = 1 a = 2 

One funny consequence is that binding messages change dramatically on function calls, as new variables come to life and old variables are removed from the environment. This may be too confusing for the output, but perhaps the event parameter may be useful in determining whether to print new variable values. (Since calling a function can change the values ​​of variables in the "return" code area, printing them seems safe.)

When the tool starts by itself, it displays the following:

 $ ./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 $ 

This is the most difficult piece of Ruby code I ran this tool on, so it can break something non-trivial.

+3
source

All Articles