[前][次][番号順一覧][スレッド一覧]

ruby-changes:30385

From: ko1 <ko1@a...>
Date: Fri, 9 Aug 2013 18:51:06 +0900 (JST)
Subject: [ruby-changes:30385] ko1:r42464 (trunk): * proc.c: add Binding#local_variable_get/set/defined?

ko1	2013-08-09 18:51:00 +0900 (Fri, 09 Aug 2013)

  New Revision: 42464

  http://svn.ruby-lang.org/cgi-bin/viewvc.cgi?view=rev&revision=42464

  Log:
    * 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.

  Modified files:
    trunk/ChangeLog
    trunk/proc.c
    trunk/test/ruby/test_proc.rb
    trunk/vm.c
    trunk/vm_core.h
Index: ChangeLog
===================================================================
--- ChangeLog	(revision 42463)
+++ ChangeLog	(revision 42464)
@@ -1,3 +1,14 @@ https://github.com/ruby/ruby/blob/trunk/ChangeLog#L1
+Fri Aug  9 18:48:09 2013  Koichi Sasada  <ko1@a...>
+
+	* 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@g...>
 
 	* tool/make-snapshot: Fix order of priority for option parameter.
Index: vm_core.h
===================================================================
--- vm_core.h	(revision 42463)
+++ vm_core.h	(revision 42464)
@@ -825,6 +825,7 @@ VALUE rb_vm_invoke_proc(rb_thread_t *th, https://github.com/ruby/ruby/blob/trunk/vm_core.h#L825
 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,
Index: proc.c
===================================================================
--- proc.c	(revision 42463)
+++ proc.c	(revision 42464)
@@ -394,6 +394,164 @@ bind_eval(int argc, VALUE *argv, VALUE b https://github.com/ruby/ruby/blob/trunk/proc.c#L394
     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) https://github.com/ruby/ruby/blob/trunk/proc.c#L2697
     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);
 }
 
Index: vm.c
===================================================================
--- vm.c	(revision 42463)
+++ vm.c	(revision 42464)
@@ -602,6 +602,44 @@ rb_vm_make_proc(rb_thread_t *th, const r https://github.com/ruby/ruby/blob/trunk/vm.c#L602
     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
Index: test/ruby/test_proc.rb
===================================================================
--- test/ruby/test_proc.rb	(revision 42463)
+++ test/ruby/test_proc.rb	(revision 42464)
@@ -1207,4 +1207,38 @@ class TestProc < Test::Unit::TestCase https://github.com/ruby/ruby/blob/trunk/test/ruby/test_proc.rb#L1207
     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

--
ML: ruby-changes@q...
Info: http://www.atdot.net/~ko1/quickml/

[前][次][番号順一覧][スレッド一覧]