[#42194] Enhancing Numeric#step — "Akinori MUSHA" <knu@...>

 Numeric#step の仕様の拡張を提案します。

26 messages 2010/09/08
[#42196] Re: Enhancing Numeric#step — Yukihiro Matsumoto <matz@...> 2010/09/08

まつもと ゆきひろです

[#42200] Re: Enhancing Numeric#step — "Akinori MUSHA" <knu@...> 2010/09/08

At Wed, 8 Sep 2010 22:46:57 +0900,

[#42204] Re: Enhancing Numeric#step — Yukihiro Matsumoto <matz@...> 2010/09/09

まつもと ゆきひろです

[#42232] 1.9.2 readline can't handle cursorkeys, mbcs chars etc (msvcrt) — arton <artonx@...>

artonです。

11 messages 2010/09/10

[#42269] [Ruby 1.9-Bug#3836] Kernel.system, spawnがスペースを含むパスで動作しない — Hiroki Najima <redmine@...>

チケット #3836 が更新されました。 (by Hiroki Najima)

12 messages 2010/09/16
[#42270] WindowsでのKernel.systemの挙動、一貫性について — NAJIMA Hiroki <h.najima@...> 2010/09/16

名島(Nazy)と申します。

[#42310] ビジースレッドがあるとコンテキストスイッチが起きづらくなる — kuwamoto shintaro <beuniv@...>

こんにちは。

9 messages 2010/09/29
[#42315] [bug:trunk] ビジースレッドがあるとコンテキストスイッチが起きづらくなる — "U.Nakamura" <usa@...> 2010/09/30

こんにちは、なかむら(う)です。

[ruby-dev:42237] Introducing "rb_scan_keyword_args()" (was Re: Re: Enhancing Numeric#step)

From: "Akinori MUSHA" <knu@...>
Date: 2010-09-12 10:51:21 UTC
List: ruby-dev #42237
At Thu, 9 Sep 2010 09:59:45 +0900,
matz wrote:
> でキーワード辞書を取れたり、
>
>   rb_scan_keywords(kw, "by", &step, "to", &limit, NULL);
>
> で、キーワード辞書を分解したりするようなAPIはどうだろうかと
> 考えています。

 少し考えてから、 rb_scan_keyword_args() というものを設計・実装
してみました。仕様は以下の差分中に(日本語も)あるので見てみて
ください。

 改良点として、 fmt の頭に ':' を置くと、キーワード名として
const char * でなく ID を取るという機能を付けると、何度も呼び
出されることを考慮して効率のために rb_intern() の結果を取って
おくようなところでも使えるのかなと思っています。

 まあ、そこはAPI仕様としては枝葉として、幹としての機能はこれで
いかがでしょうか。

diff --git a/README.EXT b/README.EXT
index c2b2d9d..2410595 100644
--- a/README.EXT
+++ b/README.EXT
@@ -1150,6 +1150,75 @@ means the corresponding captured argument(s) should be just dropped.
 The number of given arguments, excluding an option hash or iterator
 block, is returned.

+ rb_scan_keyword_args(VALUE hash, const char *fmt, ...)
+
+Retrieve keyword arguments from hash (Qnil is allowed and treated as
+empty hash) according to the format string and an arbitrary number of
+pairs of a keyword and a VALUE reference that follows, terminated by a
+single occurrence of NULL.  Another VALUE reference may follow to
+capture unlisted keyword arguments as a hash.
+
+The format can be described in ABNF as follows:
+
+--
+scan-kw-arg-spec  := [num-of-mandatory-args] how-to-deal-with-unlisted-spec
+
+how-to-deal-with-unlisted-spec := sym-for-capture / sym-for-raise / sym-for-warn
+
+num-of-mandatory-args          := DIGIT ; The number of mandatory
+                                        ; keywords
+sym-for-capture                := "*"   ; Indicates that all the
+                                        ; keywords that are not on the
+                                        ; list should be captured as a
+                                        ; ruby hash.
+sym-for-raise                  := "!"   ; Indicates that an
+                                        ; ArgumentError should be
+                                        ; raised if any keyword that
+                                        ; are not on the list is given.
+sym-for-warning                := ""    ; Indicates that a soft warning
+                                        ; should be emitted raised if
+                                        ; any keyword that are not on
+                                        ; the list is given.
+
+Typical usage of this function is as follows:
+
+  /*
+   * The keywords <x> and <y> are mandatory, and <color> is optional;
+   * If any other keyword arguments are given, an ArgumentError is raised.
+   */
+  rb_scan_keyword_args(opt, "2!", "x", &x, "y", &y, "color", &color, NULL);
+
+  /*
+   * The keywords <x> and <y> are mandatory, and <color> is optional;
+   * If other keyword arguments are given, they are packed into a hash
+   * and assigned to the variable rest.
+   */
+  rb_scan_keyword_args(opt, "2*", "x", &x, "y", &y, "color", &color, NULL, &rest);
+
+  /* Unknown parameters can just be dropped by passing a NULL. */
+  rb_scan_keyword_args(opt, "2*", "x", &x, "y", &y, "color", &color, NULL, NULL);
+
+  /*
+   * The keywords <x>, <y>, and <color> are all optional;
+   * If any other keyword arguments are given, a soft warning (only
+   * reported in verbose mode) is emitted.
+   */
+  rb_scan_keyword_args(opt, "0", "x", &x, "y", &y, "color", &color, NULL);
+
+For each optional keyword arguments that are not given, the
+corresponding VALUE reference is set to Qundef unlike rb_scan_args()
+which sets Qnil.  This is because in rb_scan_args() distinction
+between nil and unspecified can be made by checking argc but in
+rb_scan_keyword_args() there is no concept of argc where hash is
+unordered.
+
+If you want to settle more than nine mandatory keyword arguments,
+split up the retrieval into several steps, i.e. retrieve the first
+nine arguments with "9*", apply "9*" to the captured rest to retrieve
+the next nine, and so on.
+
+--
+
 ** Invoking Ruby method

  VALUE rb_funcall(VALUE recv, ID mid, int narg, ...)
diff --git a/README.EXT.ja b/README.EXT.ja
index 54ab449..4592c2a 100644
--- a/README.EXT.ja
+++ b/README.EXT.ja
@@ -1236,6 +1236,66 @@ sym-for-block-arg              := "&"   ;
   返り値は与えられた引数の数です.オプションハッシュおよびイ
   テレータブロックは数えません.

+rb_scan_keyword_args(VALUE hash, const char *fmt, ...)
+
+  ハッシュ値hash(Qnilも可; 空のハッシュと見なされる)からキー
+  ワード引数を指定されたフォーマットおよび以下に続くキーワー
+  ドとVALUEへの参照の組に従って取り出します.キーワードと
+  VALUEの組は任意個指定でき,単一のNULLで終端されます.最後
+  に,VALUEへの参照を1つ置くことができ,そこに列挙されていな
+  いキーワード引数のハッシュが取得されます.
+
+  このフォーマットは,ABNFで記述すると以下の通りです.
+
+--
+scan-kw-arg-spec  := [num-of-mandatory-args] how-to-deal-with-unlisted-spec
+
+how-to-deal-with-unlisted-spec := sym-for-capture / sym-for-raise / sym-for-warn
+
+num-of-mandatory-args          := DIGIT ; 必須キーワード引数の数
+sym-for-capture                := "*"   ; 列挙されていないキーワード引数は
+                                        ; ハッシュとして取得せよという指定
+sym-for-raise                  := "!"   ; 列挙されていないキーワード引数が
+                                        ; 指定された場合にはArgumentErrorを
+                                        ; 発生せよという指定
+sym-for-warning                := ""    ; 列挙されていないキーワード引数が
+                                        ; 指定された場合にはwarning(verbose
+                                        ; モード時のみ)を出力せよという指定
+
+  以下のように使います.
+
+  /*
+   * キーワード引数 <x> と <y> は必須,<color> は任意とする;
+   * それ以外のキーワード引数を指定すると ArgumentError が発生する.
+   */
+  rb_scan_keyword_args(opt, "2!", "x", &x, "y", &y, "color", &color, NULL);
+
+  /*
+   * キーワード引数 <x> と <y> は必須,<color> は任意とする;
+   * それ以外に指定されたキーワード引数はハッシュとしてrestに格納する.
+   */
+  rb_scan_keyword_args(opt, "2*", "x", &x, "y", &y, "color", &color, NULL, &rest);
+
+  /* &restの代わりにNULLを指定すれば,単に読み捨てることもできる. */
+  rb_scan_keyword_args(opt, "2*", "x", &x, "y", &y, "color", &color, NULL, NULL);
+
+  /*
+   * キーワード引数 <x>, <y>, <color> はすべて任意;
+   * それ以外のキーワード引数を指定するとwarningが出力される.
+   * (verboseモードのみ)
+   */
+  rb_scan_keyword_args(opt, "0", "x", &x, "y", &y, "color", &color, NULL);
+
+  指定されなかった任意キーワード引数については,対応する変数
+  にはQundefがセットされる(Qnilがセットされるrb_scan_args()と
+  の違いに注意).これは,rb_scan_args()ではnilと無指定の区別
+  をargcから知ることができるが,ハッシュを扱う
+  rb_scan_keyword_args()ではそれができないためである.
+
+  なお、必須引数を10個以上設定した場合は,工程を複数回に分け
+  る.つまり,まず "9*" で最初の9個を取り出し,得られた「残り」
+  に対して "9*" を適用して次の9個を取り出し,といった具合.
+
 ** Rubyメソッド呼び出し

 VALUE rb_funcall(VALUE recv, ID mid, int narg, ...)
diff --git a/dir.c b/dir.c
index f9867e4..fe4279b 100644
--- a/dir.c
+++ b/dir.c
@@ -382,26 +382,15 @@ dir_initialize(int argc, VALUE *argv, VALUE dir)
 {
     struct dir_data *dp;
     rb_encoding  *fsenc;
-    VALUE dirname, opt;
-    static VALUE sym_enc;
+    VALUE dirname, opt, enc;

-    if (!sym_enc) {
-	sym_enc = ID2SYM(rb_intern("encoding"));
-    }
     fsenc = rb_filesystem_encoding();

     argc = rb_scan_args(argc, argv, "1:", &dirname, &opt);
+    rb_scan_keyword_args(opt, "0", "encoding", &enc, NULL);

-    if (!NIL_P(opt)) {
-        VALUE v, enc=Qnil;
-
-        v = rb_hash_aref(opt, sym_enc);
-        if (!NIL_P(v)) enc = v;
-
-	if (!NIL_P(enc)) {
-	    fsenc = rb_to_encoding(enc);
-	}
-    }
+    if (enc != Qundef && enc != Qnil)
+	fsenc = rb_to_encoding(enc);

     GlobPathValue(dirname, FALSE);

diff --git a/hash.c b/hash.c
index 73f9012..fb7509d 100644
--- a/hash.c
+++ b/hash.c
@@ -1949,6 +1949,153 @@ rb_hash_compare_by_id_p(VALUE hash)
     return Qfalse;
 }

+static const char *
+keyword_inspect(VALUE key)
+{
+    if (TYPE(key) == T_SYMBOL)
+	return rb_id2name(SYM2ID(key));
+
+    return RSTRING_PTR(rb_inspect(key));
+}
+
+void
+rb_scan_keyword_args(VALUE hash, const char *fmt, ...)
+{
+    const char *p = fmt;
+    const char *key;
+    va_list vargs;
+    int rest = 0;
+    int n_mand = 0, n_missing;
+    int i;
+    VALUE mhash;
+
+    if (ISDIGIT(*p)) {
+	n_mand = *p - '0';
+	p++;
+    }
+    switch (*p) {
+      case '*':
+      case '!':
+	rest = *p;
+	p++;
+    }
+    if (*p != '\0')
+	rb_fatal("bad format: %s", fmt);
+
+    if (NIL_P(hash)) {
+	va_start(vargs, fmt);
+
+	if (n_mand > 0) {
+	    key = va_arg(vargs, const char *);
+	    n_missing = n_mand;
+	  missing:
+	    if (n_missing == 1) {
+		va_end(vargs);
+		rb_raise(rb_eArgError, "missing keyword argument: <%s>", key);
+	    }
+	    else {
+		VALUE msg = rb_str_new2("missing keyword arguments:");
+		int i;
+
+		for (i = 0; i < n_missing; i++) {
+		    if (i > 0) rb_str_buf_cat2(msg, ",");
+		    rb_str_buf_cat2(msg, " <");
+		    rb_str_buf_cat2(msg, key);
+		    rb_str_buf_cat2(msg, ">");
+
+		    (void)va_arg(vargs, VALUE *);
+		    key = va_arg(vargs, const char *);
+		}
+
+		rb_raise(rb_eArgError, "%s", RSTRING_PTR(msg));
+	    }
+	}
+	else {
+	    int i;
+
+	    for (i = 0; ; i++) {
+		VALUE *var;
+
+		key = va_arg(vargs, const char *);
+		if (!key) break;
+
+		var = va_arg(vargs, VALUE *);
+		if (var) *var = Qundef;
+	    }
+	}
+	va_end(vargs);
+	return;
+    }
+
+    if (TYPE(hash) != T_HASH)
+	rb_fatal("invalid object given");
+
+    mhash = rb_hash_dup(hash);
+    va_start(vargs, fmt);
+
+    for (i = 0; ; i++) {
+	VALUE val, *var;
+
+	key = va_arg(vargs, const char *);
+
+	if (!key) {
+	    if (i < n_mand) {
+		va_end(vargs);
+		rb_fatal("not enough keys given (%d for %d)", i, n_mand);
+	    }
+	    break;
+	}
+
+	val = rb_hash_delete_key(mhash, ID2SYM(rb_intern(key)));
+
+	if (val == Qundef && i < n_mand) {
+	    n_missing = n_mand - i;
+	    goto missing;
+	}
+
+	var = va_arg(vargs, VALUE *);
+	if (var) *var = val;
+    }
+
+    if (RHASH_EMPTY_P(mhash))
+	mhash = Qnil;
+
+    if (rest == '*') {
+	VALUE *var = va_arg(vargs, VALUE *);
+	if (var) *var = mhash;
+    }
+    else if (!NIL_P(mhash)) {
+	VALUE keys = rb_hash_keys(mhash);
+	VALUE msg;
+
+	va_end(vargs);
+
+	if (RARRAY_LEN(keys) == 1) {
+	    msg = rb_sprintf("unknown keyword given: <%s>", keyword_inspect(RARRAY_PTR(keys)[0]));
+	}
+	else {
+	    int i;
+
+	    msg = rb_str_new2("unknown keywords given:");
+
+	    for (i = 0; i < RARRAY_LEN(keys); i++) {
+		if (i > 0) rb_str_buf_cat2(msg, ",");
+		rb_str_buf_cat2(msg, " <");
+		rb_str_buf_cat2(msg, keyword_inspect(RARRAY_PTR(keys)[i]));
+		rb_str_buf_cat2(msg, ">");
+	    }
+	}
+
+	if (rest == '!')
+	    rb_raise(rb_eArgError, "%s", RSTRING_PTR(msg));
+	else
+	    rb_warning("%s", RSTRING_PTR(msg));
+    }
+
+    va_end(vargs);
+}
+
+
 static int path_tainted = -1;

 static char **origenviron;
diff --git a/include/ruby/ruby.h b/include/ruby/ruby.h
index 028f4e9..5199879 100644
--- a/include/ruby/ruby.h
+++ b/include/ruby/ruby.h
@@ -1124,6 +1124,7 @@ VALUE rb_funcall(VALUE, ID, int, ...);
 VALUE rb_funcall2(VALUE, ID, int, const VALUE*);
 VALUE rb_funcall3(VALUE, ID, int, const VALUE*);
 int rb_scan_args(int, const VALUE*, const char*, ...);
+void rb_scan_keyword_args(VALUE hash, const char *fmt, ...);
 VALUE rb_call_super(int, const VALUE*);

 VALUE rb_gv_set(const char*, VALUE);


--
Akinori MUSHA / http://akinori.org/

In This Thread