From 82abe79b9fa3b5a648f453076a28ed9566d02ec8 Mon Sep 17 00:00:00 2001 From: knu Date: Fri, 10 Sep 2010 07:51:58 +0000 Subject: * class.c (rb_scan_args): Add support for optional keyword argument hash. * README.EXT, README.EXT.ja: Update documentation accordingly. * dir.c (dir_initialize): Make use of the new rb_scan_args() feature. * io.c (rb_io_s_popen, rb_scan_open_args, rb_io_initialize) (rb_io_s_pipe, open_key_args, io_s_foreach, io_s_readlines) (rb_io_s_read, rb_io_set_encoding): Ditto. * transcode.c (str_transcode, econv_args) (econv_primitive_convert): Ditto. * ext/zlib/zlib.c (rb_gzreader_initialize): Ditto. git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@29214 b2dd03c8-39d4-4d8f-98ff-823fe69b080e --- ChangeLog | 19 +++++++++++++++ README.EXT | 19 ++++++++++++--- README.EXT.ja | 16 ++++++++++--- class.c | 29 ++++++++++++++++++++++- dir.c | 3 +-- ext/zlib/zlib.c | 6 +---- io.c | 72 +++++++++++++++++++++------------------------------------ transcode.c | 49 +++++++++++++++++++++------------------ 8 files changed, 130 insertions(+), 83 deletions(-) diff --git a/ChangeLog b/ChangeLog index 01c46b56c2..15330acac7 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,22 @@ +Fri Sep 10 16:49:20 2010 Akinori MUSHA + + * class.c (rb_scan_args): Add support for optional keyword + argument hash. + + * README.EXT, README.EXT.ja: Update documentation accordingly. + + * dir.c (dir_initialize): Make use of the new rb_scan_args() + feature. + + * io.c (rb_io_s_popen, rb_scan_open_args, rb_io_initialize) + (rb_io_s_pipe, open_key_args, io_s_foreach, io_s_readlines) + (rb_io_s_read, rb_io_set_encoding): Ditto. + + * transcode.c (str_transcode, econv_args) + (econv_primitive_convert): Ditto. + + * ext/zlib/zlib.c (rb_gzreader_initialize): Ditto. + Fri Sep 10 10:33:18 2010 NARUSE, Yui * random.c (rb_genrand_ulong_limited): renamed from diff --git a/README.EXT b/README.EXT index ac0d7cc98d..c2b2d9d52d 100644 --- a/README.EXT +++ b/README.EXT @@ -1105,12 +1105,13 @@ according to the format string. The format can be described in ABNF as follows: -- -scan-arg-spec := param-arg-spec [block-arg-spec] +scan-arg-spec := param-arg-spec [option-hash-arg-spec] [block-arg-spec] param-arg-spec := pre-arg-spec [post-arg-spec] / post-arg-spec / pre-opt-post-arg-spec pre-arg-spec := num-of-leading-mandatory-args [num-of-optional-args] post-arg-spec := sym-for-variable-length-args [num-of-trailing-mandatory-args] pre-opt-post-arg-spec := num-of-leading-mandatory-args num-of-optional-args num-of-trailing-mandatory-args +option-hash-arg-spec := sym-for-option-hash-arg block-arg-spec := sym-for-block-arg num-of-leading-mandatory-args := DIGIT ; The number of leading @@ -1122,6 +1123,18 @@ sym-for-variable-length-args := "*" ; Indicates that variable ; captured as a ruby array num-of-trailing-mandatory-args := DIGIT ; The number of trailing ; mandatory arguments +sym-for-option-hash-arg := ":" ; Indicates that an option + ; hash is captured if the last + ; argument is a hash or can be + ; converted to a hash with + ; #to_hash. When the last + ; argument is nil, it is + ; captured if it is not + ; ambiguous to take it as + ; empty option hash; i.e. '*' + ; is not specified and + ; arguments are given more + ; than sufficient. sym-for-block-arg := "&" ; Indicates that an iterator ; block should be captured if ; given @@ -1134,8 +1147,8 @@ assigned to captured arguments. For omitted arguments, variables are set to Qnil. NULL can be put in place of a variable reference, which means the corresponding captured argument(s) should be just dropped. -The number of given arguments, excluding an iterator block, is -returned. +The number of given arguments, excluding an option hash or iterator +block, is returned. ** Invoking Ruby method diff --git a/README.EXT.ja b/README.EXT.ja index 95c4ee2fb9..54ab449f2b 100644 --- a/README.EXT.ja +++ b/README.EXT.ja @@ -1198,12 +1198,13 @@ rb_scan_args(int argc, VALUE *argv, const char *fmt, ...) トは,ABNFで記述すると以下の通りです. -- -scan-arg-spec := param-arg-spec [block-arg-spec] +scan-arg-spec := param-arg-spec [option-hash-arg-spec] [block-arg-spec] param-arg-spec := pre-arg-spec [post-arg-spec] / post-arg-spec / pre-opt-post-arg-spec pre-arg-spec := num-of-leading-mandatory-args [num-of-optional-args] post-arg-spec := sym-for-variable-length-args [num-of-trailing-mandatory-args] pre-opt-post-arg-spec := num-of-leading-mandatory-args num-of-optional-args num-of-trailing-mandatory-args +option-hash-arg-spec := sym-for-option-hash-arg block-arg-spec := sym-for-block-arg num-of-leading-mandatory-args := DIGIT ; 先頭に置かれる省略不能な引数の数 @@ -1211,6 +1212,15 @@ num-of-optional-args := DIGIT ; 続 sym-for-variable-length-args := "*" ; 続いて置かれる可変長引数を ; Rubyの配列で取得するための指定 num-of-trailing-mandatory-args := DIGIT ; 終端に置かれる省略不能な引数の数 +sym-for-option-hash-arg := ":" ; オプションハッシュを取得する + ; ための指定; 省略不能な引数の + ; 数よりも多くの引数が指定され, + ; 最後の引数がハッシュ(または + ; #to_hashで変換可能)の場合に + ; 取得される.最後の引数がnilの + ; 場合,可変長引数指定がなく, + ; 省略不能引数の数よりも多くの + ; 引数が指定された場合に取得される sym-for-block-arg := "&" ; イテレータブロックを取得するための ; 指定 -- @@ -1223,8 +1233,8 @@ sym-for-block-arg := "&" ; 省略可能引数が省略された時の変数の値はnil(C言語のレベルでは Qnil)になります. - 返り値は与えられた引数の数です.イテレータブロックは数えま - せん. + 返り値は与えられた引数の数です.オプションハッシュおよびイ + テレータブロックは数えません. ** Rubyメソッド呼び出し diff --git a/class.c b/class.c index 9ad8ec8539..1d4695bc6c 100644 --- a/class.c +++ b/class.c @@ -1376,9 +1376,10 @@ rb_scan_args(int argc, const VALUE *argv, const char *fmt, ...) const char *p = fmt; VALUE *var; va_list vargs; - int f_var = 0, f_block = 0; + int f_var = 0, f_hash = 0, f_block = 0; int n_lead = 0, n_opt = 0, n_trail = 0, n_mand; int argi = 0; + VALUE hash = Qnil; if (ISDIGIT(*p)) { n_lead = *p - '0'; @@ -1402,6 +1403,10 @@ rb_scan_args(int argc, const VALUE *argv, const char *fmt, ...) } } block_arg: + if (*p == ':') { + f_hash = 1; + p++; + } if (*p == '&') { f_block = 1; p++; @@ -1416,6 +1421,23 @@ rb_scan_args(int argc, const VALUE *argv, const char *fmt, ...) va_start(vargs, fmt); + /* capture an option hash - phase 1: pop */ + if (f_hash && n_mand < argc) { + VALUE last = argv[argc - 1]; + + if (NIL_P(last)) { + /* nil is taken as an empty option hash only if it is not + ambiguous; i.e. '*' is not specified and arguments are + given more than sufficient */ + if (!f_var && n_mand + n_opt < argc) + argc--; + } + else { + hash = rb_check_convert_type(last, T_HASH, "Hash", "to_hash"); + if (!NIL_P(hash)) + argc--; + } + } /* capture leading mandatory arguments */ for (i = n_lead; i-- > 0; ) { var = va_arg(vargs, VALUE *); @@ -1452,6 +1474,11 @@ rb_scan_args(int argc, const VALUE *argv, const char *fmt, ...) if (var) *var = argv[argi]; argi++; } + /* capture an option hash - phase 2: assignment */ + if (f_hash) { + var = va_arg(vargs, VALUE *); + if (var) *var = hash; + } /* capture iterator block */ if (f_block) { var = va_arg(vargs, VALUE *); diff --git a/dir.c b/dir.c index 483c8beafc..f9867e44d7 100644 --- a/dir.c +++ b/dir.c @@ -390,11 +390,10 @@ dir_initialize(int argc, VALUE *argv, VALUE dir) } fsenc = rb_filesystem_encoding(); - rb_scan_args(argc, argv, "11", &dirname, &opt); + argc = rb_scan_args(argc, argv, "1:", &dirname, &opt); if (!NIL_P(opt)) { VALUE v, enc=Qnil; - opt = rb_convert_type(opt, T_HASH, "Hash", "to_hash"); v = rb_hash_aref(opt, sym_enc); if (!NIL_P(v)) enc = v; diff --git a/ext/zlib/zlib.c b/ext/zlib/zlib.c index 20218a962c..970b85713e 100644 --- a/ext/zlib/zlib.c +++ b/ext/zlib/zlib.c @@ -3067,11 +3067,7 @@ rb_gzreader_initialize(int argc, VALUE *argv, VALUE obj) int err; Data_Get_Struct(obj, struct gzfile, gz); - if (argc > 1) { - opt = rb_check_convert_type(argv[argc-1], T_HASH, "Hash", "to_hash"); - if (!NIL_P(opt)) argc--; - } - rb_scan_args(argc, argv, "1", &io); + rb_scan_args(argc, argv, "1:", &io, &opt); /* this is undocumented feature of zlib */ err = inflateInit2(&gz->z.stream, -MAX_WBITS); diff --git a/io.c b/io.c index 169158b454..7a4be8a447 100644 --- a/io.c +++ b/io.c @@ -5238,21 +5238,6 @@ pipe_open_s(VALUE prog, const char *modestr, int fmode, convconfig_t *convconfig return pipe_open(&earg, prog, modestr, fmode, convconfig); } -static VALUE -pop_last_hash(int *argc_p, VALUE *argv) -{ - VALUE last, tmp; - if (*argc_p == 0) - return Qnil; - last = argv[*argc_p-1]; - if (NIL_P(last)) return Qnil; - tmp = rb_check_convert_type(last, T_HASH, "Hash", "to_hash"); - if (NIL_P(tmp)) - return Qnil; - (*argc_p)--; - return tmp; -} - /* * call-seq: * IO.popen(cmd, mode="r" [, opt]) -> io @@ -5344,8 +5329,7 @@ rb_io_s_popen(int argc, VALUE *argv, VALUE klass) int oflags, fmode; convconfig_t convconfig; - opt = pop_last_hash(&argc, argv); - rb_scan_args(argc, argv, "11", &pname, &pmode); + argc = rb_scan_args(argc, argv, "11:", &pname, &pmode, &opt); rb_io_extract_modeenc(&pmode, 0, opt, &oflags, &fmode, &convconfig); modestr = rb_io_oflags_modestr(oflags); @@ -5389,12 +5373,11 @@ rb_scan_open_args(int argc, VALUE *argv, VALUE *fname_p, int *oflags_p, int *fmode_p, convconfig_t *convconfig_p, mode_t *perm_p) { - VALUE opt=Qnil, fname, vmode, vperm; + VALUE opt, fname, vmode, vperm; int oflags, fmode; mode_t perm; - opt = pop_last_hash(&argc, argv); - rb_scan_args(argc, argv, "12", &fname, &vmode, &vperm); + argc = rb_scan_args(argc, argv, "12:", &fname, &vmode, &vperm, &opt); FilePathValue(fname); rb_io_extract_modeenc(&vmode, &vperm, opt, &oflags, &fmode, convconfig_p); @@ -6462,8 +6445,7 @@ rb_io_initialize(int argc, VALUE *argv, VALUE io) rb_secure(4); - opt = pop_last_hash(&argc, argv); - rb_scan_args(argc, argv, "11", &fnum, &vmode); + argc = rb_scan_args(argc, argv, "11:", &fnum, &vmode, &opt); rb_io_extract_modeenc(&vmode, 0, opt, &oflags, &fmode, &convconfig); fd = NUM2INT(fnum); @@ -7776,8 +7758,7 @@ rb_io_s_pipe(int argc, VALUE *argv, VALUE klass) int fmode = 0; VALUE ret; - opt = pop_last_hash(&argc, argv); - rb_scan_args(argc, argv, "02", &v1, &v2); + argc = rb_scan_args(argc, argv, "02:", &v1, &v2, &opt); if (rb_pipe(pipes) == -1) rb_sys_fail(0); @@ -7824,22 +7805,20 @@ struct foreach_arg { }; static void -open_key_args(int argc, VALUE *argv, struct foreach_arg *arg) +open_key_args(int argc, VALUE *argv, VALUE opt, struct foreach_arg *arg) { - VALUE opt, v; + VALUE path, v; - FilePathValue(argv[0]); + path = *argv++; + argc--; + FilePathValue(path); arg->io = 0; - arg->argc = argc - 1; - arg->argv = argv + 1; - if (argc == 1) { - no_key: - arg->io = rb_io_open(argv[0], INT2NUM(O_RDONLY), INT2FIX(0666), Qnil); + arg->argc = argc; + arg->argv = argv; + if (NIL_P(opt)) { + arg->io = rb_io_open(path, INT2NUM(O_RDONLY), INT2FIX(0666), Qnil); return; } - opt = pop_last_hash(&arg->argc, arg->argv); - if (NIL_P(opt)) goto no_key; - v = rb_hash_aref(opt, sym_open_args); if (!NIL_P(v)) { VALUE args; @@ -7853,13 +7832,13 @@ open_key_args(int argc, VALUE *argv, struct foreach_arg *arg) } #endif args = rb_ary_tmp_new(n); - rb_ary_push(args, argv[0]); + rb_ary_push(args, path); rb_ary_concat(args, v); arg->io = rb_io_open_with_args((int)n, RARRAY_PTR(args)); rb_ary_clear(args); /* prevent from GC */ return; } - arg->io = rb_io_open(argv[0], Qnil, Qnil, opt); + arg->io = rb_io_open(path, Qnil, Qnil, opt); } static VALUE @@ -7902,11 +7881,12 @@ io_s_foreach(struct foreach_arg *arg) static VALUE rb_io_s_foreach(int argc, VALUE *argv, VALUE self) { + VALUE opt; struct foreach_arg arg; - rb_scan_args(argc, argv, "13", NULL, NULL, NULL, NULL); + argc = rb_scan_args(argc, argv, "13:", NULL, NULL, NULL, NULL, &opt); RETURN_ENUMERATOR(self, argc, argv); - open_key_args(argc, argv, &arg); + open_key_args(argc, argv, opt, &arg); if (NIL_P(arg.io)) return Qnil; return rb_ensure(io_s_foreach, (VALUE)&arg, rb_io_close, arg.io); } @@ -7938,10 +7918,11 @@ io_s_readlines(struct foreach_arg *arg) static VALUE rb_io_s_readlines(int argc, VALUE *argv, VALUE io) { + VALUE opt; struct foreach_arg arg; - rb_scan_args(argc, argv, "13", NULL, NULL, NULL, NULL); - open_key_args(argc, argv, &arg); + argc = rb_scan_args(argc, argv, "13:", NULL, NULL, NULL, NULL, &opt); + open_key_args(argc, argv, opt, &arg); if (NIL_P(arg.io)) return Qnil; return rb_ensure(io_s_readlines, (VALUE)&arg, rb_io_close, arg.io); } @@ -8001,11 +7982,11 @@ seek_before_access(VALUE argp) static VALUE rb_io_s_read(int argc, VALUE *argv, VALUE io) { - VALUE offset; + VALUE opt, offset; struct foreach_arg arg; - rb_scan_args(argc, argv, "13", NULL, NULL, &offset, NULL); - open_key_args(argc, argv, &arg); + argc = rb_scan_args(argc, argv, "13:", NULL, NULL, &offset, NULL, &opt); + open_key_args(argc, argv, opt, &arg); if (NIL_P(arg.io)) return Qnil; if (!NIL_P(offset)) { struct seek_arg sarg; @@ -8684,8 +8665,7 @@ rb_io_set_encoding(int argc, VALUE *argv, VALUE io) rb_io_t *fptr; VALUE v1, v2, opt; - opt = pop_last_hash(&argc, argv); - rb_scan_args(argc, argv, "11", &v1, &v2); + argc = rb_scan_args(argc, argv, "11:", &v1, &v2, &opt); GetOpenFile(io, fptr); io_encoding_set(fptr, v1, v2, opt); return io; diff --git a/transcode.c b/transcode.c index c718182a14..17bad37e27 100644 --- a/transcode.c +++ b/transcode.c @@ -2666,12 +2666,9 @@ str_transcode(int argc, VALUE *argv, VALUE *self) int ecflags = 0; VALUE ecopts = Qnil; - if (0 < argc) { - opt = rb_check_convert_type(argv[argc-1], T_HASH, "Hash", "to_hash"); - if (!NIL_P(opt)) { - argc--; - ecflags = rb_econv_prepare_opts(opt, &ecopts); - } + argc = rb_scan_args(argc, argv, "02:", NULL, NULL, &opt); + if (!NIL_P(opt)) { + ecflags = rb_econv_prepare_opts(opt, &ecopts); } return str_transcode0(argc, argv, self, ecflags, ecopts); } @@ -2908,25 +2905,28 @@ econv_args(int argc, VALUE *argv, int *ecflags_p, VALUE *ecopts_p) { - VALUE opt, opthash, flags_v, ecopts; + VALUE opt, flags_v, ecopts; int sidx, didx; const char *sname, *dname; rb_encoding *senc, *denc; int ecflags; - rb_scan_args(argc, argv, "21", snamev_p, dnamev_p, &opt); + argc = rb_scan_args(argc, argv, "21:", snamev_p, dnamev_p, &flags_v, &opt); - if (NIL_P(opt)) { - ecflags = 0; + if (!NIL_P(flags_v)) { + if (!NIL_P(opt)) { + rb_raise(rb_eArgError, "wrong number of arguments (%d for 2..3)", + argc + 1); + } + ecflags = NUM2INT(rb_to_int(flags_v)); ecopts = Qnil; } - else if (!NIL_P(flags_v = rb_check_to_integer(opt, "to_int"))) { - ecflags = NUM2INT(flags_v); - ecopts = Qnil; + else if (!NIL_P(opt)) { + ecflags = rb_econv_prepare_opts(opt, &ecopts); } else { - opthash = rb_convert_type(opt, T_HASH, "Hash", "to_hash"); - ecflags = rb_econv_prepare_opts(opthash, &ecopts); + ecflags = 0; + ecopts = Qnil; } senc = NULL; @@ -3543,7 +3543,7 @@ econv_primitive_convert(int argc, VALUE *argv, VALUE self) unsigned long output_byteend; int flags; - rb_scan_args(argc, argv, "23", &input, &output, &output_byteoffset_v, &output_bytesize_v, &opt); + argc = rb_scan_args(argc, argv, "23:", &input, &output, &output_byteoffset_v, &output_bytesize_v, &flags_v, &opt); if (NIL_P(output_byteoffset_v)) output_byteoffset = 0; /* dummy */ @@ -3555,15 +3555,15 @@ econv_primitive_convert(int argc, VALUE *argv, VALUE self) else output_bytesize = NUM2LONG(output_bytesize_v); - if (NIL_P(opt)) { - flags = 0; - } - else if (!NIL_P(flags_v = rb_check_to_integer(opt, "to_int"))) { - flags = NUM2INT(flags_v); + if (!NIL_P(flags_v)) { + if (!NIL_P(opt)) { + rb_raise(rb_eArgError, "wrong number of arguments (%d for 2..5)", + argc + 1); + } + flags = NUM2INT(rb_to_int(flags_v)); } - else { + else if (!NIL_P(opt)) { VALUE v; - opt = rb_convert_type(opt, T_HASH, "Hash", "to_hash"); flags = 0; v = rb_hash_aref(opt, sym_partial_input); if (RTEST(v)) @@ -3572,6 +3572,9 @@ econv_primitive_convert(int argc, VALUE *argv, VALUE self) if (RTEST(v)) flags |= ECONV_AFTER_OUTPUT; } + else { + flags = 0; + } StringValue(output); if (!NIL_P(input)) -- cgit v1.2.1