1
0
Fork 0
mirror of https://github.com/ruby/ruby.git synced 2022-11-09 12:17:21 -05:00

* 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
This commit is contained in:
ko1 2013-08-09 09:51:00 +00:00
parent a62d6e64bf
commit 568c5a8193
5 changed files with 245 additions and 0 deletions

View file

@ -1,3 +1,14 @@
Fri Aug 9 18:48:09 2013 Koichi Sasada <ko1@atdot.net>
* 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 <shibata.hiroshi@gmail.com>
* tool/make-snapshot: Fix order of priority for option parameter.

161
proc.c
View file

@ -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; i<iseq->local_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);
}

View file

@ -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

38
vm.c
View file

@ -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

View file

@ -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,