summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ChangeLog20
-rw-r--r--compile.c70
-rw-r--r--iseq.c2
-rw-r--r--test/ripper/dummyparser.rb2
-rw-r--r--test/ruby/test_keyword.rb105
-rw-r--r--vm_core.h3
-rw-r--r--vm_insnhelper.c39
7 files changed, 238 insertions, 3 deletions
diff --git a/ChangeLog b/ChangeLog
index 58d1e9842c..8ec1bcd6c4 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,23 @@
+Mon Dec 26 22:15:27 2011 Yusuke Endoh <mame@tsg.ne.jp>
+
+ * 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@tsg.ne.jp>
* node.h, node.c, parse.y: implement a parser part for keyword
diff --git a/compile.c b/compile.c
index 38139f110b..31d9ad2d9b 100644
--- a/compile.c
+++ b/compile.c
@@ -1127,6 +1127,36 @@ iseq_set_arguments(rb_iseq_t *iseq, LINK_ANCHOR *optargs, NODE *node_args)
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 @@ iseq_set_arguments(rb_iseq_t *iseq, LINK_ANCHOR *optargs, NODE *node_args)
}
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 @@ iseq_compile_each(rb_iseq_t *iseq, LINK_ANCHOR *ret, NODE * node, int poped)
}
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) {
diff --git a/iseq.c b/iseq.c
index e694567f9f..279386e1a7 100644
--- a/iseq.c
+++ b/iseq.c
@@ -83,6 +83,7 @@ iseq_free(void *ptr)
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 @@ prepare_iseq_build(rb_iseq_t *iseq,
iseq->type = type;
iseq->arg_rest = -1;
iseq->arg_block = -1;
+ iseq->arg_keyword = -1;
iseq->klass = 0;
/*
diff --git a/test/ripper/dummyparser.rb b/test/ripper/dummyparser.rb
index 8a706b558a..1403203dae 100644
--- a/test/ripper/dummyparser.rb
+++ b/test/ripper/dummyparser.rb
@@ -151,7 +151,7 @@ class DummyParser < Ripper
"&#{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|
diff --git a/test/ruby/test_keyword.rb b/test/ruby/test_keyword.rb
new file mode 100644
index 0000000000..27edc6b8fd
--- /dev/null
+++ b/test/ruby/test_keyword.rb
@@ -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
diff --git a/vm_core.h b/vm_core.h
index d44bd25298..df186a0b58 100644
--- a/vm_core.h
+++ b/vm_core.h
@@ -220,6 +220,9 @@ struct rb_iseq_struct {
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 */
diff --git a/vm_insnhelper.c b/vm_insnhelper.c
index 85b5e6ee78..9a22741cc3 100644
--- a/vm_insnhelper.c
+++ b/vm_insnhelper.c
@@ -132,6 +132,15 @@ argument_error(const rb_iseq_t *iseq, int miss_argc, int correct_argc)
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 @@ vm_callee_setup_arg_complex(rb_thread_t *th, const rb_iseq_t * iseq,
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 @@ vm_callee_setup_arg_complex(rb_thread_t *th, const rb_iseq_t * iseq,
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 @@ vm_callee_setup_arg_complex(rb_thread_t *th, const rb_iseq_t * iseq,
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;
}