From 568c5a8193006d12a47befaf493d1bbd76a5a5cd Mon Sep 17 00:00:00 2001 From: ko1 Date: Fri, 9 Aug 2013 09:51:00 +0000 Subject: [PATCH] * proc.c: add Binding#local_variable_get/set/defined? to access local variables which a binding contains. Most part of implementation by nobu. * test/ruby/test_proc.rb: add a tests for above. * vm.c, vm_core.h (rb_binding_add_dynavars): add a new function to add a new environment to create space for new local variables. git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@42464 b2dd03c8-39d4-4d8f-98ff-823fe69b080e --- ChangeLog | 11 +++ proc.c | 161 +++++++++++++++++++++++++++++++++++++++++ test/ruby/test_proc.rb | 34 +++++++++ vm.c | 38 ++++++++++ vm_core.h | 1 + 5 files changed, 245 insertions(+) diff --git a/ChangeLog b/ChangeLog index 7474646a44..edfecb3acc 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,14 @@ +Fri Aug 9 18:48:09 2013 Koichi Sasada + + * proc.c: add Binding#local_variable_get/set/defined? + to access local variables which a binding contains. + Most part of implementation by nobu. + + * test/ruby/test_proc.rb: add a tests for above. + + * vm.c, vm_core.h (rb_binding_add_dynavars): add a new function + to add a new environment to create space for new local variables. + Fri Aug 9 14:02:01 2013 SHIBATA Hiroshi * tool/make-snapshot: Fix order of priority for option parameter. diff --git a/proc.c b/proc.c index d7395df72b..2bac5f96ed 100644 --- a/proc.c +++ b/proc.c @@ -394,6 +394,164 @@ bind_eval(int argc, VALUE *argv, VALUE bindval) return rb_f_eval(argc+1, args, Qnil /* self will be searched in eval */); } +static VALUE * +get_local_variable_ptr(VALUE envval, ID lid) +{ + const rb_env_t *env; + + do { + const rb_iseq_t *iseq; + int i; + + GetEnvPtr(envval, env); + iseq = env->block.iseq; + + for (i=0; ilocal_table_size; i++) { + if (iseq->local_table[i] == lid) { + return &env->env[i]; + } + } + } while ((envval = env->prev_envval) != 0); + + return 0; +} + +/* + * check local variable name. + * returns ID if it's an already interned symbol, or 0 with setting + * local name in String to *namep. + */ +static ID +check_local_id(VALUE bindval, volatile VALUE *pname) +{ + ID lid = rb_check_id(pname); + VALUE name = *pname, sym = name; + + if (lid) { + if (!rb_is_local_id(lid)) { + name = rb_id2str(lid); + wrong: + rb_name_error_str(sym, "wrong local variable name `% "PRIsVALUE"' for %"PRIsVALUE, + name, bindval); + } + } + else { + if (!rb_is_local_name(sym)) goto wrong; + return 0; + } + return lid; +} + +/* + * call-seq: + * binding.local_variable_get(symbol) -> obj + * + * Returns a +value+ of local variable +symbol+. + * + * def foo + * a = 1 + * binding.local_variable_get(:a) #=> 1 + * binding.local_variable_get(:b) #=> NameError + * end + * + * This method is short version of the following code. + * + * binding.eval("#{symbol}") + * + */ +static VALUE +bind_local_variable_get(VALUE bindval, VALUE sym) +{ + ID lid = check_local_id(bindval, &sym); + const rb_binding_t *bind; + const VALUE *ptr; + + if (!lid) goto undefined; + + GetBindingPtr(bindval, bind); + + if ((ptr = get_local_variable_ptr(bind->env, lid)) == NULL) { + undefined: + rb_name_error_str(sym, "local variable `%"PRIsVALUE"' not defined for %"PRIsVALUE, + sym, bindval); + } + + return *ptr; +} + +/* + * call-seq: + * binding.local_variable_set(symbol, obj) -> obj + * + * Set local variable named +symbol+ as +obj+. + * + * def foo + * a = 1 + * b = binding + * b.local_variable_set(:a, 2) # set existing local variable `a' + * b.local_variable_set(:b, 3) # create new local variable `b' + * # `b' exists only in binding. + * b.local_variable_get(:a) #=> 2 + * b.local_variable_get(:b) #=> 3 + * p a #=> 2 + * p b #=> NameError + * end + * + * This method is a similar behavior of the following code + * + * binding.eval("#{symbol} = #{obj}") + * + * if obj can be dumped in Ruby code. + */ +static VALUE +bind_local_variable_set(VALUE bindval, VALUE sym, VALUE val) +{ + ID lid = check_local_id(bindval, &sym); + rb_binding_t *bind; + VALUE *ptr; + + if (!lid) lid = rb_intern_str(sym); + + GetBindingPtr(bindval, bind); + if ((ptr = get_local_variable_ptr(bind->env, lid)) == NULL) { + /* not found. create new env */ + ptr = rb_binding_add_dynavars(bind, 1, &lid); + } + + *ptr = val; + + return val; +} + +/* + * call-seq: + * binding.local_variable_defined?(symbol) -> obj + * + * Returns a +true+ if a local variable +symbol+ exists. + * + * def foo + * a = 1 + * binding.local_variable_defined?(:a) #=> true + * binding.local_variable_defined?(:b) #=> false + * end + * + * This method is short version of the following code. + * + * binding.eval("defined?(#{symbol}) == 'local-variable'") + * + */ +static VALUE +bind_local_variable_defined_p(VALUE bindval, VALUE sym) +{ + ID lid = check_local_id(bindval, &sym); + const rb_binding_t *bind; + + if (!lid) return Qfalse; + + GetBindingPtr(bindval, bind); + return get_local_variable_ptr(bind->env, lid) ? Qtrue : Qfalse; +} + static VALUE proc_new(VALUE klass, int is_lambda) { @@ -2539,6 +2697,9 @@ 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_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); rb_define_global_function("binding", rb_f_binding, 0); } diff --git a/test/ruby/test_proc.rb b/test/ruby/test_proc.rb index 2db9108534..1bc360c9e8 100644 --- a/test/ruby/test_proc.rb +++ b/test/ruby/test_proc.rb @@ -1207,4 +1207,38 @@ class TestProc < Test::Unit::TestCase bug8345 = '[ruby-core:54688] [Bug #8345]' assert_normal_exit('def proc; end; ->{}.curry', bug8345) end + + def get_binding if: 1, case: 2, when: 3, begin: 4, end: 5 + a = 0 + binding + end + + def test_local_variable_get + b = get_binding + assert_equal(0, b.local_variable_get(:a)) + assert_raise(NameError){ b.local_variable_get(:b) } + + # access keyword named local variables + assert_equal(1, b.local_variable_get(:if)) + assert_equal(2, b.local_variable_get(:case)) + assert_equal(3, b.local_variable_get(:when)) + assert_equal(4, b.local_variable_get(:begin)) + assert_equal(5, b.local_variable_get(:end)) + end + + def test_local_variable_set + b = get_binding + b.local_variable_set(:a, 10) + b.local_variable_set(:b, 20) + assert_equal(10, b.local_variable_get(:a)) + assert_equal(20, b.local_variable_get(:b)) + assert_equal(10, b.eval("a")) + assert_equal(20, b.eval("b")) + end + + def test_local_variable_defined? + b = get_binding + assert_equal(true, b.local_variable_defined?(:a)) + assert_equal(false, b.local_variable_defined?(:b)) + end end diff --git a/vm.c b/vm.c index a8246d4c24..8dd71e2b3d 100644 --- a/vm.c +++ b/vm.c @@ -602,6 +602,44 @@ rb_vm_make_proc(rb_thread_t *th, const rb_block_t *block, VALUE klass) return procval; } +VALUE * +rb_binding_add_dynavars(rb_binding_t *bind, int dyncount, const ID *dynvars) +{ + VALUE envval = bind->env, path = bind->path, iseqval; + rb_env_t *env; + rb_block_t *base_block; + rb_thread_t *th = GET_THREAD(); + rb_iseq_t *base_iseq; + NODE *node = 0; + ID minibuf[4], *dyns = minibuf; + VALUE idtmp = 0; + + if (dyncount < 0) return 0; + + GetEnvPtr(envval, env); + + base_block = &env->block; + base_iseq = base_block->iseq; + + if (dyncount >= numberof(minibuf)) dyns = ALLOCV_N(ID, idtmp, dyncount + 1); + + dyns[0] = dyncount; + MEMCPY(dyns + 1, dynvars, ID, dyncount); + node = NEW_NODE(NODE_SCOPE, dyns, 0, 0); + + iseqval = rb_iseq_new(node, base_iseq->location.label, path, path, + base_iseq->self, ISEQ_TYPE_EVAL); + node->u1.tbl = 0; /* reset table */ + ALLOCV_END(idtmp); + + vm_set_eval_stack(th, iseqval, 0, base_block); + bind->env = rb_vm_make_env_object(th, th->cfp); + vm_pop_frame(th); + GetEnvPtr(bind->env, env); + + return env->env; +} + /* C -> Ruby: block */ static inline VALUE diff --git a/vm_core.h b/vm_core.h index 378454de05..bba13168dc 100644 --- a/vm_core.h +++ b/vm_core.h @@ -825,6 +825,7 @@ VALUE rb_vm_invoke_proc(rb_thread_t *th, rb_proc_t *proc, VALUE rb_vm_make_proc(rb_thread_t *th, const rb_block_t *block, VALUE klass); VALUE rb_vm_make_env_object(rb_thread_t *th, rb_control_frame_t *cfp); VALUE rb_binding_new_with_cfp(rb_thread_t *th, const rb_control_frame_t *src_cfp); +VALUE *rb_binding_add_dynavars(rb_binding_t *bind, int dyncount, const ID *dynvars); void rb_vm_inc_const_missing_count(void); void rb_vm_gvl_destroy(rb_vm_t *vm); VALUE rb_vm_call(rb_thread_t *th, VALUE recv, VALUE id, int argc,