mirror of
https://github.com/ruby/ruby.git
synced 2022-11-09 12:17:21 -05:00
* 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. git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@34136 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
This commit is contained in:
parent
1ab3974b0e
commit
a0a2c144b8
7 changed files with 238 additions and 3 deletions
20
ChangeLog
20
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
|
||||
|
|
70
compile.c
70
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) {
|
||||
|
|
2
iseq.c
2
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;
|
||||
|
||||
/*
|
||||
|
|
|
@ -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|
|
||||
|
|
105
test/ruby/test_keyword.rb
Normal file
105
test/ruby/test_keyword.rb
Normal file
|
@ -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
|
|
@ -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 */
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue