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

ruby-changes:22086

From: mame <ko1@a...>
Date: Mon, 26 Dec 2011 23:20:22 +0900 (JST)
Subject: [ruby-changes:22086] mame:r34136 (trunk): * vm_core.h (struct rb_iseq_struct), compile.c (iseq_set_arguments, iseq_compile_each), vm_insnhelper.c (vm_callee_setup_arg_complex): implement keyword arguments. See The feature is promised to be included in 2.0, but the detail spec is still under discussion; this commit is a springboard for further discussion. Please try it and give us feedback. This commit includes fixes for some problems reported by Benoit Daloze <eregontp AT gmail.com> and Marc-Andre Lafortune <ruby-core-mailing-list AT marc-andre.ca> .

mame	2011-12-26 23:20:09 +0900 (Mon, 26 Dec 2011)

  New Revision: 34136

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

  Log:
    * vm_core.h (struct rb_iseq_struct), compile.c (iseq_set_arguments, iseq_compile_each), vm_insnhelper.c (vm_callee_setup_arg_complex): implement keyword arguments. See [ruby-core:40290] The feature is promised to be included in 2.0, but the detail spec is still under discussion; this commit is a springboard for further discussion. Please try it and give us feedback. This commit includes fixes for some problems reported by Benoit Daloze <eregontp AT gmail.com> [ruby-core:40518] and Marc-Andre Lafortune <ruby-core-mailing-list AT marc-andre.ca> [ruby-core:41772].
    
    * iseq.c (iseq_free, prepare_iseq_build): bookkeeping.
    
    * test/ruby/test_keyword.rb: add tests for keyword arguments.
    
    * test/ripper/dummyparser.rb (class DummyParser): temporal fix for
      ripper test.

  Added files:
    trunk/test/ruby/test_keyword.rb
  Modified files:
    trunk/ChangeLog
    trunk/compile.c
    trunk/iseq.c
    trunk/test/ripper/dummyparser.rb
    trunk/vm_core.h
    trunk/vm_insnhelper.c

Index: ChangeLog
===================================================================
--- ChangeLog	(revision 34135)
+++ ChangeLog	(revision 34136)
@@ -1,3 +1,23 @@
+Mon Dec 26 22:15:27 2011  Yusuke Endoh  <mame@t...>
+
+	* vm_core.h (struct rb_iseq_struct), compile.c (iseq_set_arguments,
+	  iseq_compile_each), vm_insnhelper.c (vm_callee_setup_arg_complex):
+	  implement keyword arguments.  See [ruby-core:40290]
+	  The feature is promised to be included in 2.0, but the detail spec
+	  is still under discussion; this commit is a springboard for further
+	  discussion.  Please try it and give us feedback.
+	  This commit includes fixes for some problems reported by Benoit
+	  Daloze <eregontp AT gmail.com> [ruby-core:40518] and Marc-Andre
+	  Lafortune <ruby-core-mailing-list AT marc-andre.ca>
+	  [ruby-core:41772].
+
+	* iseq.c (iseq_free, prepare_iseq_build): bookkeeping.
+
+	* test/ruby/test_keyword.rb: add tests for keyword arguments.
+
+	* test/ripper/dummyparser.rb (class DummyParser): temporal fix for
+	  ripper test.
+
 Mon Dec 26 22:00:17 2011  Yusuke Endoh  <mame@t...>
 
 	* node.h, node.c, parse.y: implement a parser part for keyword
Index: vm_core.h
===================================================================
--- vm_core.h	(revision 34135)
+++ vm_core.h	(revision 34136)
@@ -220,6 +220,9 @@
     int arg_post_start;
     int arg_size;
     VALUE *arg_opt_table;
+    int arg_keyword;
+    int arg_keywords;
+    ID *arg_keyword_table;
 
     size_t stack_max; /* for stack overflow check */
 
Index: iseq.c
===================================================================
--- iseq.c	(revision 34135)
+++ iseq.c	(revision 34136)
@@ -83,6 +83,7 @@
 	    RUBY_FREE_UNLESS_NULL(iseq->ic_entries);
 	    RUBY_FREE_UNLESS_NULL(iseq->catch_table);
 	    RUBY_FREE_UNLESS_NULL(iseq->arg_opt_table);
+	    RUBY_FREE_UNLESS_NULL(iseq->arg_keyword_table);
 	    compile_data_free(iseq->compile_data);
 	}
 	ruby_xfree(ptr);
@@ -239,6 +240,7 @@
     iseq->type = type;
     iseq->arg_rest = -1;
     iseq->arg_block = -1;
+    iseq->arg_keyword = -1;
     iseq->klass = 0;
 
     /*
Index: compile.c
===================================================================
--- compile.c	(revision 34135)
+++ compile.c	(revision 34136)
@@ -1127,6 +1127,36 @@
 	    iseq->arg_opts = 0;
 	}
 
+	if (args->kw_args) {
+	    NODE *node = args->kw_args;
+	    VALUE keywords = rb_ary_tmp_new(1);
+	    int i = 0, j;
+
+	    iseq->arg_keyword = get_dyna_var_idx_at_raw(iseq, args->kw_rest_arg->nd_vid);
+	    COMPILE(optargs, "kwarg", args->kw_rest_arg);
+	    while (node) {
+		rb_ary_push(keywords, INT2FIX(node->nd_body->nd_vid));
+		COMPILE_POPED(optargs, "kwarg", node); /* nd_type(node) == NODE_KW_ARG */
+		node = node->nd_next;
+		i += 1;
+	    }
+	    if ((args->kw_rest_arg->nd_vid & ID_SCOPE_MASK) == ID_JUNK) {
+		iseq->arg_keywords = i;
+		iseq->arg_keyword_table = ALLOC_N(ID, i);
+		for (j = 0; j < i; j++) {
+		    iseq->arg_keyword_table[j] = FIX2INT(RARRAY_PTR(keywords)[j]);
+		}
+	    }
+	    else {
+		iseq->arg_keywords = 0;
+		iseq->arg_keyword_table = 0;
+	    }
+	    ADD_INSN(optargs, nd_line(args->kw_args), pop);
+	}
+	else {
+	    iseq->arg_keyword = -1;
+	}
+
 	if (args->pre_init) { /* m_init */
 	    COMPILE_POPED(optargs, "init arguments (m)", args->pre_init);
 	}
@@ -1151,11 +1181,15 @@
 	}
 
 	if (iseq->arg_opts != 0 || iseq->arg_post_len != 0 ||
-	    iseq->arg_rest != -1 || iseq->arg_block != -1) {
+	    iseq->arg_rest != -1 || iseq->arg_block != -1 ||
+	    iseq->arg_keyword != -1) {
 	    iseq->arg_simple = 0;
 
 	    /* set arg_size: size of arguments */
-	    if (iseq->arg_block != -1) {
+	    if (iseq->arg_keyword != -1) {
+		iseq->arg_size = iseq->arg_keyword + 1;
+	    }
+	    else if (iseq->arg_block != -1) {
 		iseq->arg_size = iseq->arg_block + 1;
 	    }
 	    else if (iseq->arg_post_len) {
@@ -4919,6 +4953,38 @@
 	}
 	break;
       }
+      case NODE_KW_ARG:{
+	LABEL *default_label = NEW_LABEL(nd_line(node));
+	LABEL *end_label = NEW_LABEL(nd_line(node));
+	int idx, lv, ls;
+	ID id = node->nd_body->nd_vid;
+
+	ADD_INSN(ret, nd_line(node), dup);
+	ADD_INSN1(ret, nd_line(node), putobject, ID2SYM(id));
+	ADD_SEND(ret, nd_line(node), ID2SYM(rb_intern("key?")), INT2FIX(1));
+	ADD_INSNL(ret, nd_line(node), branchunless, default_label);
+	ADD_INSN(ret, nd_line(node), dup);
+	ADD_INSN1(ret, nd_line(node), putobject, ID2SYM(id));
+	ADD_SEND(ret, nd_line(node), ID2SYM(rb_intern("delete")), INT2FIX(1));
+	switch (nd_type(node->nd_body)) {
+	    case NODE_LASGN:
+		idx = iseq->local_iseq->local_size - get_local_var_idx(iseq, id);
+		ADD_INSN1(ret, nd_line(node), setlocal, INT2FIX(idx));
+		break;
+	    case NODE_DASGN:
+	    case NODE_DASGN_CURR:
+		idx = get_dyna_var_idx(iseq, id, &lv, &ls);
+		ADD_INSN2(ret, nd_line(node), setdynamic, INT2FIX(ls - idx), INT2FIX(lv));
+		break;
+	    default:
+		rb_bug("iseq_compile_each (NODE_KW_ARG): unknown node: %s", ruby_node_name(nd_type(node->nd_body)));
+	}
+	ADD_INSNL(ret, nd_line(node), jump, end_label);
+	ADD_LABEL(ret, default_label);
+	COMPILE_POPED(ret, "keyword default argument", node->nd_body);
+	ADD_LABEL(ret, end_label);
+	break;
+      }
       case NODE_DSYM:{
 	compile_dstr(iseq, ret, node);
 	if (!poped) {
Index: vm_insnhelper.c
===================================================================
--- vm_insnhelper.c	(revision 34135)
+++ vm_insnhelper.c	(revision 34136)
@@ -132,6 +132,15 @@
     rb_exc_raise(exc);
 }
 
+NORETURN(static void unknown_keyword_error(const rb_iseq_t *iseq, VALUE hash));
+static void
+unknown_keyword_error(const rb_iseq_t *iseq, VALUE hash)
+{
+    (void) iseq;
+    (void) hash;
+    rb_raise(rb_eArgError, "unknown keyword");
+}
+
 #define VM_CALLEE_SETUP_ARG(ret, th, iseq, orig_argc, orig_argv, block) \
     if (LIKELY((iseq)->arg_simple & 0x01)) { \
 	/* simple check */ \
@@ -153,9 +162,30 @@
     int argc = orig_argc;
     VALUE *argv = orig_argv;
     rb_num_t opt_pc = 0;
+    VALUE keyword_hash = Qnil;
 
     th->mark_stack_len = argc + iseq->arg_size;
 
+    if (iseq->arg_keyword != -1) {
+	int i, j;
+	if (argc > 0) keyword_hash = rb_check_convert_type(argv[argc-1], T_HASH, "Hash", "to_hash");
+	if (!NIL_P(keyword_hash)) {
+	    argc--;
+	    keyword_hash = rb_hash_dup(keyword_hash);
+	    if (iseq->arg_keywords) {
+		for (i = j = 0; i < iseq->arg_keywords; i++) {
+		    if (st_lookup(RHASH_TBL(keyword_hash), ID2SYM(iseq->arg_keyword_table[i]), 0)) j++;
+		}
+		if (RHASH_TBL(keyword_hash)->num_entries > (unsigned int) j) {
+		    unknown_keyword_error(iseq, keyword_hash);
+		}
+	    }
+	}
+	else {
+	    keyword_hash = rb_hash_new();
+	}
+    }
+
     /* mandatory */
     if (argc < (m + iseq->arg_post_len)) { /* check with post arg */
 	argument_error(iseq, argc, m + iseq->arg_post_len);
@@ -205,6 +235,11 @@
 	argc = 0;
     }
 
+    /* keyword argument */
+    if (iseq->arg_keyword != -1) {
+	orig_argv[iseq->arg_keyword] = keyword_hash;
+    }
+
     /* block arguments */
     if (block && iseq->arg_block != -1) {
 	VALUE blockval = Qnil;
@@ -230,6 +265,10 @@
 	orig_argv[iseq->arg_block] = blockval; /* Proc or nil */
     }
 
+    if (iseq->arg_keyword && argc != 0) {
+	argument_error(iseq, orig_argc, m + iseq->arg_post_len);
+    }
+
     th->mark_stack_len = 0;
     return (int)opt_pc;
 }
Index: test/ruby/test_keyword.rb
===================================================================
--- test/ruby/test_keyword.rb	(revision 0)
+++ test/ruby/test_keyword.rb	(revision 34136)
@@ -0,0 +1,105 @@
+require 'test/unit'
+
+class TestKeywordArguments < Test::Unit::TestCase
+  def f1(str: "foo", num: 424242)
+    [str, num]
+  end
+
+  def test_f1
+    assert_equal(["foo", 424242], f1)
+    assert_equal(["bar", 424242], f1(str: "bar"))
+    assert_equal(["foo", 111111], f1(num: 111111))
+    assert_equal(["bar", 111111], f1(str: "bar", num: 111111))
+    assert_raise(ArgumentError) { f1(str: "bar", check: true) }
+    assert_raise(ArgumentError) { f1("string") }
+  end
+
+
+  def f2(x, str: "foo", num: 424242)
+    [x, str, num]
+  end
+
+  def test_f2
+    assert_equal([:xyz, "foo", 424242], f2(:xyz))
+  end
+
+
+  def f3(str: "foo", num: 424242, **h)
+    [str, num, h]
+  end
+
+  def test_f3
+    assert_equal(["foo", 424242, {}], f3)
+    assert_equal(["bar", 424242, {}], f3(str: "bar"))
+    assert_equal(["foo", 111111, {}], f3(num: 111111))
+    assert_equal(["bar", 111111, {}], f3(str: "bar", num: 111111))
+    assert_equal(["bar", 424242, {:check=>true}], f3(str: "bar", check: true))
+    assert_raise(ArgumentError) { f3("string") }
+  end
+
+
+  define_method(:f4) {|str: "foo", num: 424242| [str, num] }
+
+  def test_f4
+    assert_equal(["foo", 424242], f4)
+    assert_equal(["bar", 424242], f4(str: "bar"))
+    assert_equal(["foo", 111111], f4(num: 111111))
+    assert_equal(["bar", 111111], f4(str: "bar", num: 111111))
+    assert_raise(ArgumentError) { f4(str: "bar", check: true) }
+    assert_raise(ArgumentError) { f4("string") }
+  end
+
+
+  define_method(:f5) {|str: "foo", num: 424242, **h| [str, num, h] }
+
+  def test_f5
+    assert_equal(["foo", 424242, {}], f5)
+    assert_equal(["bar", 424242, {}], f5(str: "bar"))
+    assert_equal(["foo", 111111, {}], f5(num: 111111))
+    assert_equal(["bar", 111111, {}], f5(str: "bar", num: 111111))
+    assert_equal(["bar", 424242, {:check=>true}], f5(str: "bar", check: true))
+    assert_raise(ArgumentError) { f5("string") }
+  end
+
+
+  def f6(str: "foo", num: 424242, **h, &blk)
+    [str, num, h, blk]
+  end
+
+  def test_f6 # [ruby-core:40518]
+    assert_equal(["foo", 424242, {}, nil], f6)
+    assert_equal(["bar", 424242, {}, nil], f6(str: "bar"))
+    assert_equal(["foo", 111111, {}, nil], f6(num: 111111))
+    assert_equal(["bar", 111111, {}, nil], f6(str: "bar", num: 111111))
+    assert_equal(["bar", 424242, {:check=>true}, nil], f6(str: "bar", check: true))
+    a = f6 {|x| x + 42 }
+    assert_equal(["foo", 424242, {}], a[0, 3])
+    assert_equal(43, a.last.call(1))
+  end
+
+  def f7(*r, str: "foo", num: 424242, **h)
+    [r, str, num, h]
+  end
+
+  def test_f7 # [ruby-core:41772]
+    assert_equal([[], "foo", 424242, {}], f7)
+    assert_equal([[], "bar", 424242, {}], f7(str: "bar"))
+    assert_equal([[], "foo", 111111, {}], f7(num: 111111))
+    assert_equal([[], "bar", 111111, {}], f7(str: "bar", num: 111111))
+    assert_equal([[1], "foo", 424242, {}], f7(1))
+    assert_equal([[1, 2], "foo", 424242, {}], f7(1, 2))
+    assert_equal([[1, 2, 3], "foo", 424242, {}], f7(1, 2, 3))
+    assert_equal([[1], "bar", 424242, {}], f7(1, str: "bar"))
+    assert_equal([[1, 2], "bar", 424242, {}], f7(1, 2, str: "bar"))
+    assert_equal([[1, 2, 3], "bar", 424242, {}], f7(1, 2, 3, str: "bar"))
+  end
+
+
+  def test_lambda
+    f = ->(str: "foo", num: 424242) { [str, num] }
+    assert_equal(["foo", 424242], f[])
+    assert_equal(["bar", 424242], f[str: "bar"])
+    assert_equal(["foo", 111111], f[num: 111111])
+    assert_equal(["bar", 111111], f[str: "bar", num: 111111])
+  end
+end
Index: test/ripper/dummyparser.rb
===================================================================
--- test/ripper/dummyparser.rb	(revision 34135)
+++ test/ripper/dummyparser.rb	(revision 34136)
@@ -151,7 +151,7 @@
     "&#{var}"
   end
 
-  def on_params(required, optional, rest, more, block)
+  def on_params(required, optional, rest, more, keyword, keyword_rest, block)
     args = NodeList.new
 
     required.each do |req|

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

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