From 6174d6ae1beb0d134f669f52441be7bd7e53e0f1 Mon Sep 17 00:00:00 2001 From: Jack Danger Canty Date: Sat, 10 Aug 2013 22:59:12 -0700 Subject: [PATCH] Allowing binding to list its local variables Before this patch: def get_all_local_variables(bind) lvars = bind.send(:local_variables) # `lvars` should equal [:x, :y], but equals [:bind, :lvars] lvars.map {|name| bind.local_variable_get name } end x = 1 y = 2 get_all_local_variables(binding) # NameError: local variable `bind' not defined for # After this patch: def get_all_local_variables(bind) lvars = bind.local_variables # `lvars` equals [:x, :y] lvars.map {|name| bind.local_variable_get name } end x = 1 y = 2 set_everything_but_x_to_5(binding) # => [1, 1] bind.send(:local_variables) was a global method that used the current stack frame regardless of binding. Now that we have `Binding#local_variable_get` and `Binding#local_variable_set` the API feels very strange without `Binding#local_variables` working similarly. --- proc.c | 53 ++++++++++++++++++++++++++++++++++++++++++++++ test/ruby/test_variable.rb | 9 ++++++++ 2 files changed, 62 insertions(+) diff --git a/proc.c b/proc.c index 6fd3981..da9b7ac 100644 --- a/proc.c +++ b/proc.c @@ -444,6 +444,58 @@ check_local_id(VALUE bindval, volatile VALUE *pname) /* * call-seq: + * binding.local_variables -> Array + * + * Returns the +symbol+ names of the binding's local variables + * + * def foo + * a = 1 + * 2.times do |n| + * binding.local_variables #=> [:a, :n] + * end + * end + * + * This method is short version of the following code. + * + * binding.eval("local_variables") + * + */ +static VALUE +bind_local_variables(VALUE bindval) +{ + VALUE ary = rb_ary_new(); + + const rb_binding_t *bind; + const rb_env_t *env; + VALUE envval; + + GetBindingPtr(bindval, bind); + + envval = bind->env; + GetEnvPtr(envval, env); + + do { + const rb_iseq_t *iseq; + int i; + ID id; + iseq = env->block.iseq; + + for (i=0; ilocal_table_size; i++) { + id = iseq->local_table[i]; + if (id) { + const char *vname = rb_id2name(id); + if (vname) { + rb_ary_push(ary, ID2SYM(id)); + } + } + } + } while ((envval = env->prev_envval) != 0); + + return ary; +} + +/* + * call-seq: * binding.local_variable_get(symbol) -> obj * * Returns a +value+ of local variable +symbol+. @@ -2697,6 +2749,7 @@ Init_Binding(void) rb_define_method(rb_cBinding, "clone", binding_clone, 0); rb_define_method(rb_cBinding, "dup", binding_dup, 0); rb_define_method(rb_cBinding, "eval", bind_eval, -1); + rb_define_method(rb_cBinding, "local_variables", bind_local_variables, 0); rb_define_method(rb_cBinding, "local_variable_get", bind_local_variable_get, 1); rb_define_method(rb_cBinding, "local_variable_set", bind_local_variable_set, 2); rb_define_method(rb_cBinding, "local_variable_defined?", bind_local_variable_defined_p, 1); diff --git a/test/ruby/test_variable.rb b/test/ruby/test_variable.rb index 32b3d61..9752ae3 100644 --- a/test/ruby/test_variable.rb +++ b/test/ruby/test_variable.rb @@ -83,6 +83,15 @@ class TestVariable < Test::Unit::TestCase end.call end + def _new_binding(bind) + this_should_not_be_in_bind = 2 + assert_equal([:x], bind.local_variables) + end + def test_local_variables_from_other_method_binding + x = 1 + _new_binding(binding) + end + def test_global_variable_0 assert_in_out_err(["-e", "$0='t'*1000;print $0"], "", /\At+\z/, []) end -- 1.8.1.3