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/