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:
parent
a62d6e64bf
commit
568c5a8193
5 changed files with 245 additions and 0 deletions
11
ChangeLog
11
ChangeLog
|
@ -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
161
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; 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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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
38
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
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Reference in a new issue