summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ChangeLog11
-rw-r--r--proc.c161
-rw-r--r--test/ruby/test_proc.rb34
-rw-r--r--vm.c38
-rw-r--r--vm_core.h1
5 files changed, 245 insertions, 0 deletions
diff --git a/ChangeLog b/ChangeLog
index 7474646..edfecb3 100644
--- a/ChangeLog
+++ b/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.
diff --git a/proc.c b/proc.c
index d7395df..2bac5f9 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; 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);
}
diff --git a/test/ruby/test_proc.rb b/test/ruby/test_proc.rb
index 2db9108..1bc360c 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 a8246d4..8dd71e2 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 378454d..bba1316 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,