Enhancing Numeric#step

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

 現在、 Numeric#step は limit ã‚’å¿…é ˆå¼•æ•°ã¨ã—ã¦ã„ã‚‹ãŸã‚ã€æ‰‹è»½ã«
無限数列を生成することができません。Float::INFINITY ないし 1/0.0
のような値を渡せば可能ではありますが、「1から上限なしでカウント
アップする」のようなよくある要件を満たす方法としては冗長です。

 そこで、上限(下限)なしでループするように limit も省略可能とし、
なおかつ増分のみの指定もできるように疑似キーワード引数を導入して
みました。

1.step {|i| … } # i = 1, 2, 3, …
-1.step(by:-1) {|i| … } # i = -1, -2, -3, …
1.0.step(by: 0.1, to: 2.0).to_a # [1.0, 1.1, …, 2.0] (余談:誤差に注意)
2.step(by:2).take(100) # [2, 4, 6, …, 200]

 キーワードを by: と to: ã«ã—ãŸã®ã§ã€å¾“æ¥ã®ã‚ˆã†ã«é †åºã§æ„å‘³ã‚’
表すより読みやすいと思います。いかがでしょうか。

commit 5835d772b01da4f048834ec33b2ca4399f0051d6
Author: Akinori MUSHA [email protected]
Date: Wed Sep 8 21:11:27 2010 +0900

Enhance Numeric#step.

diff --git a/ChangeLog b/ChangeLog
index 5e9b53d…6227f68 100644
— a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,9 @@
+Wed Sep 8 21:04:21 2010 Akinori MUSHA [email protected]
+

    • numeric.c (num_step): Introduce keyword arguments (by: and to:),
  • allowing user to generate an infinite sequence of numbers
  • without a pain.

Wed Sep 8 06:25:41 2010 Tanaka A. [email protected]

  • ext/pathname/pathname.c (path_setuid_p): Pathname#setuid?
    translated
    diff --git a/numeric.c b/numeric.c
    index 25c9e73…98b03c3 100644
    — a/numeric.c
    +++ b/numeric.c
    @@ -107,6 +107,8 @@ VALUE rb_cFixnum;
    VALUE rb_eZeroDivError;
    VALUE rb_eFloatDomainError;

+static VALUE sym_to, sym_by;
+
void
rb_num_zerodiv(void)
{
@@ -1605,18 +1607,26 @@ ruby_float_step(VALUE from, VALUE to, VALUE
step, int excl)

/*

  • call-seq:
    • num.step(limit[, step]) {|i| block }  ->  self
      
    • num.step(limit[, step])               ->  an_enumerator
      
    • num.step(by: step, to: limit]) {|i| block }  ->  self
      
    • num.step(by: step, to: limit])               ->  an_enumerator
      
    • num.step(limit, step=1) {|i| block }         ->  self
      
    • num.step(limit, step=1)                      ->  an_enumerator
      
    • Invokes block with the sequence of numbers starting at
    • num, incremented by step (default 1) on each
    • call. The loop finishes when the value to be passed to the block
    • is greater than limit (if step is positive) or less
    • than limit (if step is negative). If all the
    • arguments are integers, the loop operates using an integer
    • counter. If any of the arguments are floating point numbers, all
    • are converted to floats, and the loop is executed floor(n +
    • n*epsilon)+ 1 times, where n = (limit -
    • than limit (if step is negative), where limit
    • is defaulted to infinity. In the keyword argument style
    • (recommended), any of step and limit (default
    • infinity) can be omitted. In the fixed position argument style,
    • integer zero as a step (i.e. num.step(limit, 0)) is not allowed
    • for historical reasons.
    • If all the arguments are integers, the loop operates using an
    • integer counter. If any of the arguments are floating point
    • numbers, all are converted to floats, and the loop is executed
    • floor(n + n*epsilon)+ 1 times, where n = (limit -
    • num)/step. Otherwise, the loop starts at num, uses
    • either the < or > operator to compare
    • the counter against limit, and increments itself using the
      @@ -1624,11 +1634,17 @@ ruby_float_step(VALUE from, VALUE to, VALUE
      step, int excl)
    • If no block is given, an enumerator is returned instead.
    • p 1.step.take(4)
      
    • p 10.step(by: -1).take(4)
      
    • 3.step(to: 5) { |i| print i, " " }
      
    • 1.step(10, 2) { |i| print i, " " }
      
    • Math::E.step(Math::PI, 0.2) { |f| print f, " " }
      
    • Math::E.step(to: Math::PI, by: 0.2) { |f| print f, " " }
      
    • produces:
    • [1, 2, 3, 4]
      
    • [10, 9, 8, 7]
      
    • 3 4 5
      
    • 1 3 5 7 9
      
    • 2.71828182845905 2.91828182845905 3.11828182845905
      
    */
    @@ -1636,43 +1652,75 @@ ruby_float_step(VALUE from, VALUE to, VALUE
    step, int excl)
    static VALUE
    num_step(int argc, VALUE *argv, VALUE from)
    {
  • VALUE to, step;
  • VALUE to, step, hash;

  • int inf = 0;

    RETURN_ENUMERATOR(from, argc, argv);

  • if (argc == 1) {
  • switch (argc) {
  •  case 0:
    
  • to = DBL2NUM(INFINITY);
  • step = INT2FIX(1);
  • break;
  •  case 1:
    
    to = argv[0];
    step = INT2FIX(1);
  • }
  • else {
  • if (argc == 2) {
  •  to = argv[0];
    
  •  step = argv[1];
    
  • }
  • else {
  •  rb_raise(rb_eArgError, "wrong number of arguments (%d for 1..2)", 
    

argc);

  • switch (TYPE(to)) {
  • case T_FIXNUM:
  • case T_BIGNUM:
  • case T_FLOAT:
  • case T_RATIONAL:
  •  break;
    
  • default:
  •  hash = rb_check_convert_type(to, T_HASH, "Hash", "to_hash");
    
  •  if (!NIL_P(hash)) {
    
  • step = rb_hash_aref(hash, sym_by);
  • if (NIL_P(step)) step = INT2FIX(1);
  • to = rb_hash_aref(hash, sym_to);
  • if (NIL_P(to)) to = DBL2NUM(INFINITY);
  •  }
    
    }
  • break;
  •  case 2:
    
  • to = argv[0];
  • step = argv[1];
    if (rb_equal(step, INT2FIX(0))) {
    rb_raise(rb_eArgError, “step can’t be 0”);
    }
  • break;
  •  default:
    
  • rb_raise(rb_eArgError, “wrong number of arguments (%d for 0…2)”,
    argc);
    }
  • if (FIXNUM_P(from) && FIXNUM_P(to) && FIXNUM_P(step)) {
  • if (TYPE(to) == T_FLOAT && isinf(RFLOAT_VALUE(to)))
  • inf = 1;
  • if (FIXNUM_P(from) && (inf || FIXNUM_P(to)) && FIXNUM_P(step)) {
    long i, end, diff;
  • i = FIX2LONG(from);
  • end = FIX2LONG(to);
  • diff = FIX2LONG(step);
  • if (inf) {
  •  i = FIX2LONG(from);
    
  •  diff = FIX2LONG(step);
    
  • if (diff > 0) {
  •  while (i <= end) {
    
  •  for (;;) {
    
    rb_yield(LONG2FIX(i));
    i += diff;
    }
  • }
  • else {
  •  while (i >= end) {
    
  • rb_yield(LONG2FIX(i));
  • i += diff;
  • } else {

  •  i = FIX2LONG(from);
    
  •  end = FIX2LONG(to);
    
  •  diff = FIX2LONG(step);
    
  •  if (diff > 0) {
    
  • while (i <= end) {

  •    rb_yield(LONG2FIX(i));
    
  •    i += diff;
    
  • }

  •  }
    
  •  else {
    
  • while (i >= end) {

  •    rb_yield(LONG2FIX(i));
    
  •    i += diff;
    
  • }
    }
    }
    }
    @@ -3491,4 +3539,7 @@ Init_Numeric(void)
    rb_define_method(rb_cFloat, “nan?”, flo_is_nan_p, 0);
    rb_define_method(rb_cFloat, “infinite?”, flo_is_infinite_p, 0);
    rb_define_method(rb_cFloat, “finite?”, flo_is_finite_p, 0);

  • sym_to = ID2SYM(rb_intern(“to”));

  • sym_by = ID2SYM(rb_intern(“by”));
    }
    diff --git a/test/ruby/test_numeric.rb b/test/ruby/test_numeric.rb
    index f4fbea4…5525634 100644
    — a/test/ruby/test_numeric.rb
    +++ b/test/ruby/test_numeric.rb
    @@ -181,27 +181,58 @@ class TestNumeric < Test::Unit::TestCase
    assert_equal([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], a)

    a = []

  • 1.step(to: 10) {|x| a << x }

  • assert_equal([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], a)

  • a = []
    1.step(10, 2) {|x| a << x }
    assert_equal([1, 3, 5, 7, 9], a)

  • a = []

  • 1.step(to: 10, by: 2) {|x| a << x }

  • assert_equal([1, 3, 5, 7, 9], a)

  • assert_raise(ArgumentError) { 1.step(10, 1, 0) { } }
    assert_raise(ArgumentError) { 1.step(10, 0) { } }

  • assert_nothing_raised { 1.step(by: 0) }

    a = []
    10.step(1, -2) {|x| a << x }
    assert_equal([10, 8, 6, 4, 2], a)

    a = []

  • 10.step(to: 1, by: -2) {|x| a << x }

  • assert_equal([10, 8, 6, 4, 2], a)

  • a = []
    1.0.step(10.0, 2.0) {|x| a << x }
    assert_equal([1.0, 3.0, 5.0, 7.0, 9.0], a)

    a = []

  • 1.0.step(to: 10.0, by: 2.0) {|x| a << x }

  • assert_equal([1.0, 3.0, 5.0, 7.0, 9.0], a)

  • a = []
    1.step(10, 2**32) {|x| a << x }
    assert_equal([1], a)

    a = []

  • 1.step(to: 10, by: 2**32) {|x| a << x }

  • assert_equal([1], a)

  • a = []
    10.step(1, -(2**32)) {|x| a << x }
    assert_equal([10], a)

  • a = []

  • 10.step(to: 1, by: -(2**32)) {|x| a << x }

  • assert_equal([10], a)

  • a = 10.step.take(4)

  • assert_equal([10, 11, 12, 13], a)

  • a = 10.step(by: -1).take(4)

  • assert_equal([10, 9, 8, 7], a)
    end

def test_num2long

e$B$^$D$b$He(B e$B$f$-$R$m$G$9e(B

In message “Re: [ruby-dev:42194] Enhancing Numeric#step”
on Wed, 8 Sep 2010 21:58:11 +0900, “Akinori MUSHA”
[email protected] writes:

|e$B!!8=:!“e(B Numeric#step e$B$Oe(B limit e$B$rI,?\0z?t$H$7$F$$$k$?$a!”<j7Z$Ke(B
|e$BL58B?tNs$r@8@.$9$k$3$H$,$G$-$^$;$s!#e(BFloat::INFINITY e$B$J$$$7e(B 1/0.0
|e$B$N$h$&$JCM$rEO$;$P2DG=$G$O$“$j$^$9$,!”!Ve(B1e$B$+$i>e8B$J$7$G%+%&%s%He(B
|e$B%“%C%W$9$k!W$N$h$&$J$h$/$”$kMW7o$rK~$?$9J}K!$H$7$F$O>iD9$G$9!#e(B
|
|e$B!!$=$3$G!">e8Be(B(e$B2<8Be(B)e$B$J$7$G%k!<%W$9$k$h$&$Ke(B limit e$B$b>JN,2DG=$H$7!"e(B
|e$B$J$*$+$DA}J,$N$
$N;XDj$b$G$-$k$h$&$K5?;w%-!<%o!<%I0z?t$rF3F~$7$Fe(B
|e$B$$^$7$?!#e(B
|
|1.step {|i| … } # i = 1, 2, 3, …
|-1.step(by:-1) {|i| … } # i = -1, -2, -3, …
|1.0.step(by: 0.1, to: 2.0).to_a # [1.0, 1.1, …, 2.0] (e$BM>CLe(B:e$B8m:9$KCm0Ue(B)
|2.step(by:2).take(100) # [2, 4, 6, …, 200]
|
|e$B!!%-!<%o!<%I$re(B by: e$B$He(B to: e$B$K$7$?$N$G!"=>Mh$N$h$&$K=g=x$G0UL#$re(B
|e$BI=$9$h$jFI$
$d$9$$$H;W$$$^$9!#$$$+$,$G$7$g$&$+!#e(B

e$B%-!<%o!<%I0z?t$rIU$1$k$J$i!“e(Bstepe$B$h$j$Oe(Btoe$B$NJ}$,8~$$$F$$$k%1!<e(B
e$B%9$b$”$j$=$&$J5$$,$7$^$9!#7k6Ie(Bstepe$B$H$$$&L>A0$r<u$1F~$l$i$l$ke(B
e$B$+$I$&$+$,%]%$%s%H$G$9$M!#e(B

e$B8D?ME*$K$O0cOB46$"$j$J$s$G$9$,!"e(Bstepe$B$r!V?J$`!W$HF0;l$G$H$i$(e(B
e$B$k$J$i5vMF$G$-$k$N$+$b!#e(B

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

|みました。
スもありそうな気がします。結局stepという名前を受け入れられる
かどうかがポイントですね。

 「stepよりはtoの方が向いているケースもありそう」の意味が取れ
ませんでした。逆かな?(でも 1.step(step: 2) は重言すぎる…)

個人的には違和感ありなんですが、stepを「進む」と動詞でとらえ
るなら許容できるのかも。

 私はもともとその意味で捉えていました。増分指定は第二引数で、
しかも省略可能なので step を増分を意味する名詞と捉えるのは少々
不自然です。上限を指定する 1.step(10) が基本用法ですが、これは
「(1から)10まで進める(step to 10)」と読まれるでしょう。

 上限を指定しなかったらずっと繰り返すのは自然だと思いますし、
æ—¥é ƒã‹ã‚‰ 1.step(10, 2) という表現を読みづらく感じていたので、
今回の提案に至りました。

e$B$^$D$b$He(B e$B$f$-$R$m$G$9e(B

In message “Re: [ruby-dev:42200] Re: Enhancing Numeric#step”
on Thu, 9 Sep 2010 00:09:52 +0900, “Akinori MUSHA”
[email protected] writes:
|> |e$B$J$*$+$DA}J,$N$$N;XDj$b$G$-$k$h$&$K5?;w%-!<%o!<%I0z?t$rF3F~$7$Fe(B
|> |e$B$
$^$7$?!#e(B
|> |
|> |1.step {|i| … } # i = 1, 2, 3, …
|> |-1.step(by:-1) {|i| … } # i = -1, -2, -3, …
|> |1.0.step(by: 0.1, to: 2.0).to_a # [1.0, 1.1, …, 2.0] (e$BM>CLe(B:e$B8m:9$KCm0Ue(B)
|> |2.step(by:2).take(100) # [2, 4, 6, …, 200]
|> |
|> |e$B!!%-!<%o!<%I$re(B by: e$B$He(B to: e$B$K$7$?$N$G!“=>Mh$N$h$&$K=g=x$G0UL#$re(B
|> |e$BI=$9$h$jFI$_$d$9$$$H;W$$$^$9!#$$$+$,$G$7$g$&$+!#e(B
|>
|> e$B%-!<%o!<%I0z?t$rIU$1$k$J$i!“e(Bstepe$B$h$j$Oe(Btoe$B$NJ}$,8~$$$F$$$k%1!<e(B
|> e$B%9$b$”$j$=$&$J5$$,$7$^$9!#7k6Ie(Bstepe$B$H$$$&L>A0$r<u$1F~$l$i$l$ke(B
|> e$B$+$I$&$+$,%]%$%s%H$G$9$M!#e(B
|
|e$B!!!Ve(Bstepe$B$h$j$Oe(Btoe$B$NJ}$,8~$$$F$$$k%1!<%9$b$”$j$=$&!W$N0UL#$,<h$le(B
|e$B$^$;$s$G$7$?!#5U$+$J!)!J$G$be(B 1.step(step: 2) e$B$O=E8@$9$.$k!D!Ke(B

e$B$?$H$($Pe(B

1.0.step(by: 0.1, to: 2.0).to_a # [1.0, 1.1, …, 2.0]
(e$BM>CLe(B:e$B8m:9$KCm0Ue(B)

e$B$Oe(B

1.0.to(2.0, by: 0.1).to_a

e$B$NJ}$,$h$jFI$_$d$9$$5-K!$N$h$&$J5$$,$7$?!"$H$$$&0UL#$G$7$?!#e(B

|e$B!!>e8B$r;XDj$7$J$+$C$?$i$:$C$H7+$jJV$9$N$O<+A3$@$H;W$$$^$9$7!“e(B
|e$BF|:”$+$ie(B 1.step(10, 2) e$B$H$$$&I=8=$rFI$_$E$i$/46$8$F$$$?$N$G!"e(B
|e$B:#2s$NDs0F$K;j$j$^$7$?!#e(B

e$B0lHU9M$($F!“e(Bstepe$B%a%=%C%I$X$N3HD%$=$N$b$N$K$O;?@.$7$h$&$H;W$$e(B
e$B$^$7$?!#$?$@!”:#8e%-!<%o!<%I0z?t$r<h$k%a%=%C%I$,A}2C$9$k$3$He(B
e$B$,MF0W$KA[A|$G$-$^$9$N$G!"e(BC APIe$B$G$b%-!<%o!<%I0z?t$KBP1~$7$?e(B
e$B$$$H;W$$$^$9!#e(B

e$B$?$H$($P!"e(B

rb_scan_args(argc, argv, “02:”, &limit, &step, &kw);

e$B$G%-!<%o!<%I<-=q$r<h$l$?$j!"e(B

rb_scan_keywords(kw, “by”, &step, “to”, &limit, NULL);

e$B$G!"%-!<%o!<%I<-=q$rJ,2r$7$?$j$9$k$h$&$Je(BAPIe$B$O$I$&$@$m$&$+$He(B
e$B9M$($F$$$^$9!#e(B

e$B$3$N$h$&$Je(BAPIe$B$r%Y!<%9$Ke(Bstepe$B3HD%$r<h$j9~$$?$$$N$G$9$,!"$I$Ne(B
e$B$h$&$J<j=g$G9T$$$^$7$g$&$+!#;d$,$^$:e(BC APIe$B$r:n$C$F!"Ip<T$5$se(B
e$B$,$=$l$KBP1~$7$?e(Bstepe$B%Q%C%A$r%3%
%C%H$9$k$H$+$G$9$+$M!#e(B

e$B$$$D;d$N;~4V$,$H$l$k$+$,2]Bj$@$1$I!#e(B

                            e$B$^$D$b$He(B e$B$f$-$R$me(B /:|)

e$B$^$D$b$He(B e$B$f$-$R$m$G$9e(B

In message “Re: [ruby-dev:42205] Re: Enhancing Numeric#step”
on Thu, 9 Sep 2010 10:40:39 +0900, “NARUSE, Yui” [email protected]
writes:

|> # e$B$$$D;d$N;~4V$,$H$l$k$+$,2]Bj$@$1$I!#e(B
|
|e$B;EMM$5$(7h$a$FD:$1$l$P!“<BAu$O$J$+$@$5$s$,$d$C$F$/$@$5$k!”$H$+!#e(B

e$B!V:n$i$:$K;EMM$@$17h$a$k!W$J$s$F!“;d$,0lHV7ZJN$9$kBVEY$G$O$Je(B
e$B$$$G$9$+!#$C$F$+!”:n$C$F$_$J$$$H;EMM$,7h$a$i$l$J$$%?%$%W$J$se(B
e$B$G$9!";d$O!#e(B

e$B@.@%$G$9!#e(B

2010e$BG/e(B9e$B7ne(B9e$BF|e(B9:59 Yukihiro M.
[email protected]:

rb_scan_keywords(kw, “by”, &step, “to”, &limit, NULL);

e$B$G!"%-!<%o!<%I<-=q$rJ,2r$7$?$j$9$k$h$&$Je(BAPIe$B$O$I$&$@$m$&$+$He(B
e$B9M$($F$$$^$9!#e(B

e$B$3$N$h$&$Je(BAPIe$B$r%Y!<%9$Ke(Bstepe$B3HD%$r<h$j9~$$?$$$N$G$9$,!"$I$Ne(B
e$B$h$&$J<j=g$G9T$$$^$7$g$&$+!#;d$,$^$:e(BC APIe$B$r:n$C$F!"Ip<T$5$se(B
e$B$,$=$l$KBP1~$7$?e(Bstepe$B%Q%C%A$r%3%
%C%H$9$k$H$+$G$9$+$M!#e(B

e$B$$$D;d$N;~4V$,$H$l$k$+$,2]Bj$@$1$I!#e(B

e$B;EMM$5$(7h$a$FD:$1$l$P!“<BAu$O$J$+$@$5$s$,$d$C$F$/$@$5$k!”$H$+!#e(B

At Thu, 9 Sep 2010 09:59:45 +0900,
matz wrote:

| 上限を指定しなかったらずっと繰り返すのは自然だと思いますし、
|æ—¥é ƒã‹ã‚‰ 1.step(10, 2) という表現を読みづらく感じていたので、
|今回の提案に至りました。

一晩考えて、stepメソッドへの拡張そのものには賛成しようと思い
ã¾ã—ãŸã€‚ãŸã ã€ä»Šå¾Œã‚­ãƒ¼ãƒ¯ãƒ¼ãƒ‰å¼•æ•°ã‚’å–ã‚‹ãƒ¡ã‚½ãƒƒãƒ‰ãŒå¢—åŠ ã™ã‚‹ã“ã¨
が容易に想像できますので、C APIでもキーワード引数に対応した
いと思います。

 ありがとうございます。

たとえば、

rb_scan_args(argc, argv, “02:”, &limit, &step, &kw);

 若干記法は違いますが、前に提案しました。 [ruby-dev:38048]

でキーワード辞書を取れたり、

rb_scan_keywords(kw, “by”, &step, “to”, &limit, NULL);

で、キーワード辞書を分解したりするようなAPIはどうだろうかと
考えています。

 なるほど。確かにあると便利かも。

このようなAPIをベースにstep拡張を取り込みたいのですが、どの
ã‚ˆã†ãªæ‰‹é †ã§è¡Œã„ã¾ã—ã‚‡ã†ã‹ã€‚ç§ãŒã¾ãšC APIを作って、武者さん
がそれに対応したstepパッチをコミットするとかですかね。

 JRubyやRubiniusと共通のC APIを設けるという話とかぶらないなら、
上記はすぐにも実装しますよ。まあ、書き換えの手間はあまりないので
é †åºã¯ã©ã¡ã‚‰ã§ã‚‚ã„ã„ã§ã™ã‘ã©ã­ã€‚

e$B?\F#$G$9!#e(B

In [email protected]
“[ruby-dev:42204] Re: Enhancing Numeric#step” on Thu, 9 Sep 2010
09:59:45 +0900,
Yukihiro M. [email protected] wrote:

rb_scan_keywords(kw, “by”, &step, “to”, &limit, NULL);

e$B$G!"%-!<%o!<%I<-=q$rJ,2r$7$?$j$9$k$h$&$Je(BAPIe$B$O$I$&$@$m$&$+$He(B
e$B9M$($F$$$^$9!#e(B

e$B$3$N$h$&$Je(BAPIe$B$r%Y!<%9$Ke(Bstepe$B3HD%$r<h$j9~$$?$$$N$G$9$,!"$I$Ne(B
e$B$h$&$J<j=g$G9T$$$^$7$g$&$+!#;d$,$^$:e(BC APIe$B$r:n$C$F!"Ip<T$5$se(B
e$B$,$=$l$KBP1~$7$?e(Bstepe$B%Q%C%A$r%3%
%C%H$9$k$H$+$G$9$+$M!#e(B

rroongae$B$H$$$&3HD%%i%$%V%i%j$r:n$C$F$$$k$N$G$9$,!"$=$NCf$GF1e(B
e$B$8%7%0%M%A%c$r;}$D4X?t$rDj5A$7$F;H$C$F$$$^$9!#e(B
rroonga/ext/groonga/rb-grn-utils.c at master · ranguba/rroonga · GitHub

e$B<B:]$Ke(Brroongae$B$NCf$N$$$m$s$J$H$3$m$G;H$C$F$$$^$9$,!“JXMx$J$Ne(B
e$B$G$$$D$+e(BRubye$BK\BN$Ne(BC
APIe$B$KDI2C$7$F$O$I$&$+$HDs0F$9$k$D$b$j$Ge(B
e$B$7$?!#$J$N$G!”%7%0%M%A%c$O$h$$$H;W$$$^$9!#e(B

rroongae$B$GDj5A$7$F$$$k4X?t$G$O!"e(BC APIe$B$G;XDj$7$F$$$J$$%-!<%o!<e(B
e$B%I$r;XDj$7$?>l9g$Oe(BArgumentErrore$B$rJV$9$h$&$K$7$F$$$^$9!#!J>ee(B
e$B$NNc$@$He(B:bye$B$He(B:toe$B0J30$N$b$N$r;XDj$7$?$iNc30!Ke(B

e$B4|BT$7$F$$$J$$%-!<%o!<%I$ONc30$K$7$?J}$,LdBj$K$9$0$K5$$E$1$ke(B
e$B$N$G!“$3$N5sF0$O5$$KF~$C$F$$$^$9$,!“8_49@-$r5$$K$9$k$J$iC1$Ke(B
e$BL5;k$9$k$N$b%”%j$+$b$7$l$^$;$s!#$?$@!“L5;k$9$k>l9g$O!”$J$s$ie(B
e$B$+$NJ}K!$G8F=PB&$KDLCN$9$k;EAH$_$,$”$k$H4r$7$$$H;W$$$^$9!#e(B

e$B@.@%$G$9!#e(B

2010e$BG/e(B9e$B7ne(B9e$BF|e(B11:02 Yukihiro M.
[email protected]:

e$B$$$G$9$+!#$C$F$+!“:n$C$F$_$J$$$H;EMM$,7h$a$i$l$J$$%?%$%W$J$se(B
e$B$G$9!”;d$O!#e(B

e$B$^$!!"$=$&6D$k$H$O;W$$$^$7$?!JFC$K8e<T!K!#e(B

e$B$7$+$7!“<B:]$N$H$3$m!”!V:n$i$:$K;EMM$@$17h$a$k!W$H$$$&$h$j$O!“e(B
e$B4{$K:n$C$F$”$k$N$G;EMM$@$17h$a$k$K$J$j$=$&$J5$$,$9$k$N$,$J$+$@$5$s!#e(B

e$B?\F#$G$9!#e(B

In [email protected]
“[ruby-dev:42210] Re: Enhancing Numeric#step” on Thu, 9 Sep 2010
13:32:41 +0900,
Yukihiro M. [email protected] wrote:

def step(to: limit, by: step, **kw)

end

e$B$O!"$I$N%-!<%o!<%I$KBP$7$F$bNc30$rH/@8$5$;$^$;$s!#$=$N$3$H$re(B
e$B9M$($k$H!“e(Brb_scan_options()e$B$bNc30$rH/@8$5$;$k$+$I$&$+$r@Z$je(B
e$BBX$($k<jCJ$,$”$k$Y$-$G$7$g$&!#$=$l$,%U%i%0$J$N$+!"JL4X?t$K$9e(B
e$B$k$N$+$OJ,$+$j$^$;$s$,!#e(B

e$B$G$“$l$P!”$3$s$J;H$$J}$K$9$k$N$O$I$&$G$7$g$&$+!)e(B

/* toe$B$+e(Bbye$B0J30$,$"$l$PNc30e(B */
rb_scan_options(options,
“to”, &limit,
“by”, &step,
NULL);

/* toe$B$+e(Bbye$B0J30$,$"$C$?$iA4Ite(Bkwe$B$KF~$ke(B */
rb_scan_options(options,
“to”, &limit,
“by”, &step,
“**”, &kw,
NULL);

e$B$G$b!"$3$l$@$He(B:**e$B$,;H$($J$$$N$G!"e(B

/* toe$B$+e(Bbye$B0J30$,$"$C$?$iA4Ite(Bkwe$B$KF~$ke(B */
rb_scan_options(options,
“to”, &limit,
“by”, &step,
“”, &kw,
NULL);

e$B$H$+!#e(B
e$B$&!<$s!“$G$b!”$3$l$O$"$s$^$j%-%l%$$8$c$J$$$G$9$M!#!#!#e(B

e$B$^$D$b$He(B e$B$f$-$R$m$G$9e(B

In message “Re: [ruby-dev:42209] Re: Enhancing Numeric#step”
on Thu, 9 Sep 2010 12:44:07 +0900, Kouhei S. [email protected]
writes:

|rroongae$B$H$$$&3HD%%i%$%V%i%j$r:n$C$F$$$k$N$G$9$,!"$=$NCf$GF1e(B
|e$B$8%7%0%M%A%c$r;}$D4X?t$rDj5A$7$F;H$C$F$$$^$9!#e(B
| rroonga/ext/groonga/rb-grn-utils.c at master · ranguba/rroonga · GitHub
|
|e$B<B:]$Ke(Brroongae$B$NCf$N$$$m$s$J$H$3$m$G;H$C$F$$$^$9$,!“JXMx$J$Ne(B
|e$B$G$$$D$+e(BRubye$BK\BN$Ne(BC APIe$B$KDI2C$7$F$O$I$&$+$HDs0F$9$k$D$b$j$Ge(B
|e$B$7$?!#$J$N$G!”%7%0%M%A%c$O$h$$$H;W$$$^$9!#e(B

e$B$"$j$,$H$&$4$6$$$^$9!#;29M$K$7$^$9!#e(B

|rroongae$B$GDj5A$7$F$$$k4X?t$G$O!"e(BC APIe$B$G;XDj$7$F$$$J$$%-!<%o!<e(B
|e$B%I$r;XDj$7$?>l9g$Oe(BArgumentErrore$B$rJV$9$h$&$K$7$F$$$^$9!#!J>ee(B
|e$B$NNc$@$He(B:bye$B$He(B:toe$B0J30$N$b$N$r;XDj$7$?$iNc30!Ke(B

e$B<BAu$O<j$D$+$:$Ne(BRubye$B%l%Y%k$G$N%-!<%o!<%I0z?t$Oe(B

def step(to: limit, by: step)

end

e$B$N$h$&$JJ8K!$K$7$h$&$H;W$C$F$$$k$N$G$9$,!“%-!<%o!<%I<-=q$rL@e(B
e$B<(E*$K<h$j=P$5$J$+$C$?>l9g$K$O!”;XDj$7$F$$$J$+$$%-!<%o!<%I$Ke(B
e$BBP$7$FNc30$r=P$=$&$H9M$($F$$$^$9!#$D$^$j!"e(B

def step(to: limit, by: step)

end

e$B$O!"e(B:toe$B$He(B:bye$B0J30$N%-!<%o!<%I$KNc30$rH/@8$5$;$^$9$,!"e(B

def step(to: limit, by: step, **kw)

end

e$B$O!"$I$N%-!<%o!<%I$KBP$7$F$bNc30$rH/@8$5$;$^$;$s!#$=$N$3$H$re(B
e$B9M$($k$H!“e(Brb_scan_options()e$B$bNc30$rH/@8$5$;$k$+$I$&$+$r@Z$je(B
e$BBX$($k<jCJ$,$”$k$Y$-$G$7$g$&!#$=$l$,%U%i%0$J$N$+!"JL4X?t$K$9e(B
e$B$k$N$+$OJ,$+$j$^$;$s$,!#e(B

                            e$B$^$D$b$He(B e$B$f$-$R$me(B /:|)

e$B$J$+$@$G$9!#e(B

At Thu, 9 Sep 2010 09:59:45 +0900,
Yukihiro M. wrote in [ruby-dev:42204]:

e$B$?$H$($P!"e(B

rb_scan_args(argc, argv, “02:”, &limit, &step, &kw);

e$B$G%-!<%o!<%I<-=q$r<h$l$?$j!"e(B

rb_scan_keywords(kw, “by”, &step, “to”, &limit, NULL);

e$B$G!"%-!<%o!<%I<-=q$rJ,2r$7$?$j$9$k$h$&$Je(BAPIe$B$O$I$&$@$m$&$+$He(B
e$B9M$($F$$$^$9!#e(B

rb_scan_argse$B$He(Brb_scan_keywordse$B$G=EJ#$7$?$b$N$,$"$C$?>l9g$O$I$&$Je(B
e$B$k$N$G$7$g$&$+!#$D$^$j!"$3$NNc$G8@$&$He(B

0.step(2, 1, to: 10, by: 2)

e$B$J$I$N$h$&$K8F$P$l$?>l9g$G$9!#Nc30$,5/$-$?$[$&$,$$$$$h$&$J5$$,$9e(B
e$B$k$N$G$9$,!"$=$&9M$($k$H=hM}$,J,$+$l$F$$$k$HLLE]$J$3$H$K$J$i$J$$e(B
e$B$G$7$g$&$+!#e(B

e$B$^$D$b$He(B e$B$f$-$R$m$G$9e(B

In message “Re: [ruby-dev:42215] Re: Enhancing Numeric#step”
on Thu, 9 Sep 2010 22:33:00 +0900, Nobuyoshi N.
[email protected] writes:

|rb_scan_argse$B$He(Brb_scan_keywordse$B$G=EJ#$7$?$b$N$,$“$C$?>l9g$O$I$&$Je(B
|e$B$k$N$G$7$g$&$+!#$D$^$j!”$3$NNc$G8@$&$He(B
|
| 0.step(2, 1, to: 10, by: 2)
|
|e$B$J$I$N$h$&$K8F$P$l$?>l9g$G$9!#Nc30$,5/$-$?$[$&$,$$$$$h$&$J5$$,$9e(B
|e$B$k$N$G$9$,!"$=$&9M$($k$H=hM}$,J,$+$l$F$$$k$HLLE]$J$3$H$K$J$i$J$$e(B
|e$B$G$7$g$&$+!#e(B

e$B$7$+$7!“$=$NE@$K$D$$$F$Oe(BRubye$B%l%Y%k$G$b<+A0$G%A%'%C%/$9$kI,MWe(B
e$B$,$”$k$N$G!“F1Ey$G$O$J$$$+$H;W$$$^$9!#$H$O$$$(!“ITJX$J$3$H$Ke(B
e$B$OJQ$o$j$J$$$N$G!”$h$jNI$$%”%$%G%#%“$,$”$l$PEvA38!F$$7$^$9!#e(B

e$B$^$D$b$He(B e$B$f$-$R$m$G$9e(B

In message “Re: [ruby-dev:42221] Re: Enhancing Numeric#step”
on Fri, 10 Sep 2010 10:24:12 +0900, “Akinori MUSHA”
[email protected] writes:

|> > rb_scan_args(argc, argv, “02:”, &limit, &step, &kw);
|>
|> e$B!!<c435-K!$O0c$$$^$9$,!"A0$KDs0F$7$^$7$?!#e(B [ruby-dev:38048]
|>
|> > e$B$G%-!<%o!<%I<-=q$r<h$l$?$j!"e(B
|
|e$B!!%Q%C%A$rE:IU$7$^$9!#e(B’:‘e$B$rKvHx!Je(B’&'e$B$rCV$/>l9g$O$=$ND>A0!K$KCV$/e(B
|e$B7A$K2~$a$^$7$?!#$$$+$,$G$7$g$&$+!#e(B

e$B$9$P$i$7$$!#K;$7$/$7$F$$$k4V$K$I$s$I$s<BAu$,?J$s$G$$$/!#e(B
e$B$A$g$C$H<d$7$$$1$I!#e(B

e$B%3%_%C%H$7$F$$$?$@$1$^$;$s$+!)e(B

At Fri, 10 Sep 2010 10:24:12 +0900,
I wrote:

 パッチを添付します。':‘を末尾(’&'ã‚’ç½®ãå ´åˆã¯ãã®ç›´å‰ï¼‰ã«ç½®ã
形に改めました。いかがでしょうか。

 Numeric#step の拡張パッチも更新しました。無限ループの最適化も
問題があったので直しました。


Akinori MUSHA / http://akinori.org/

diff --git a/ChangeLog b/ChangeLog
index 5e9b53d…1a90dca 100644
— a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,14 @@
+Fri Sep 10 10:46:39 2010 Akinori MUSHA [email protected]
+

    • numeric.c (num_step): Introduce keyword arguments (by: and to:),
  • allowing user to generate an infinite sequence of numbers
  • without a pain.
    • numeric.c (num_step): Make use of rb_scan_args() to extract an
  • option hash.
    • numeric.c (num_step): Optimize the infinite loop cases.

Wed Sep 8 06:25:41 2010 Tanaka A. [email protected]

  • ext/pathname/pathname.c (path_setuid_p): Pathname#setuid?
    translated
    diff --git a/NEWS b/NEWS
    index 53b904b…f528410 100644
    — a/NEWS
    +++ b/NEWS
    @@ -22,6 +22,11 @@ with all sufficient information, see the ChangeLog
    file.
    • File::NULL
      name of NULL device.
    • Numeric
    • Numeric#step now takes keyword arguments :to and :by, and the
  •  limit is optional.  An infinite sequence of numbers is generated
    
  •  when no limit is specified.
    
    • String
      • new methods:
        • String#prepend
          diff --git a/numeric.c b/numeric.c
          index 25c9e73…c53b537 100644
          — a/numeric.c
          +++ b/numeric.c
          @@ -107,6 +107,8 @@ VALUE rb_cFixnum;
          VALUE rb_eZeroDivError;
          VALUE rb_eFloatDomainError;

+static VALUE sym_to, sym_by;
+
void
rb_num_zerodiv(void)
{
@@ -1605,18 +1607,26 @@ ruby_float_step(VALUE from, VALUE to, VALUE
step, int excl)

/*

  • call-seq:
    • num.step(limit[, step]) {|i| block }  ->  self
      
    • num.step(limit[, step])               ->  an_enumerator
      
    • num.step(by: step, to: limit]) {|i| block }  ->  self
      
    • num.step(by: step, to: limit])               ->  an_enumerator
      
    • num.step(limit, step=1) {|i| block }         ->  self
      
    • num.step(limit, step=1)                      ->  an_enumerator
      
    • Invokes block with the sequence of numbers starting at
    • num, incremented by step (default 1) on each
    • call. The loop finishes when the value to be passed to the block
    • is greater than limit (if step is positive) or less
    • than limit (if step is negative). If all the
    • arguments are integers, the loop operates using an integer
    • counter. If any of the arguments are floating point numbers, all
    • are converted to floats, and the loop is executed floor(n +
    • n*epsilon)+ 1 times, where n = (limit -
    • than limit (if step is negative), where limit
    • is defaulted to infinity. In the keyword argument style
    • (recommended), any of step and limit (default
    • infinity) can be omitted. In the fixed position argument style,
    • integer zero as a step (i.e. num.step(limit, 0)) is not allowed
    • for historical reasons.
    • If all the arguments are integers, the loop operates using an
    • integer counter. If any of the arguments are floating point
    • numbers, all are converted to floats, and the loop is executed
    • floor(n + n*epsilon)+ 1 times, where n = (limit -
    • num)/step. Otherwise, the loop starts at num, uses
    • either the < or > operator to compare
    • the counter against limit, and increments itself using the
      @@ -1624,11 +1634,17 @@ ruby_float_step(VALUE from, VALUE to, VALUE
      step, int excl)
    • If no block is given, an enumerator is returned instead.
    • p 1.step.take(4)
      
    • p 10.step(by: -1).take(4)
      
    • 3.step(to: 5) { |i| print i, " " }
      
    • 1.step(10, 2) { |i| print i, " " }
      
    • Math::E.step(Math::PI, 0.2) { |f| print f, " " }
      
    • Math::E.step(to: Math::PI, by: 0.2) { |f| print f, " " }
      
    • produces:
    • [1, 2, 3, 4]
      
    • [10, 9, 8, 7]
      
    • 3 4 5
      
    • 1 3 5 7 9
      
    • 2.71828182845905 2.91828182845905 3.11828182845905
      
    */
    @@ -1636,60 +1652,65 @@ ruby_float_step(VALUE from, VALUE to, VALUE
    step, int excl)
    static VALUE
    num_step(int argc, VALUE *argv, VALUE from)
    {
  • VALUE to, step;
  • VALUE to, step, hash;

  • int desc, inf = 0;

    RETURN_ENUMERATOR(from, argc, argv);

  • if (argc == 1) {
  • to = argv[0];
  • step = INT2FIX(1);
  • argc = rb_scan_args(argc, argv, “02:”, &to, &step, &hash);
  • if (!NIL_P(hash)) {
  • step = rb_hash_aref(hash, sym_by);
  • to = rb_hash_aref(hash, sym_to);
    }
    else {
  • if (argc == 2) {
  •  to = argv[0];
    
  •  step = argv[1];
    
  • }
  • else {
  •  rb_raise(rb_eArgError, "wrong number of arguments (%d for 1..2)", 
    

argc);

  • }
  • /* compatibility */
    if (rb_equal(step, INT2FIX(0))) {
    rb_raise(rb_eArgError, “step can’t be 0”);
    }
    }
  • if (NIL_P(step)) step = INT2FIX(1);
  • desc = FIXNUM_P(step) ? FIX2LONG(step) < 0 : RTEST(rb_funcall(step,
    ‘<’, 1, INT2FIX(0)));
  • if (NIL_P(to)) to = desc ? DBL2NUM(-INFINITY) : DBL2NUM(INFINITY);
  • if (TYPE(to) == T_FLOAT) {
  • double f = RFLOAT_VALUE(to);
  • if (FIXNUM_P(from) && FIXNUM_P(to) && FIXNUM_P(step)) {
  • long i, end, diff;
  • inf = isinf(f) && (signbit(f) ? desc : !desc);
  • }
  • i = FIX2LONG(from);
  • end = FIX2LONG(to);
  • diff = FIX2LONG(step);
  • if (FIXNUM_P(from) && (inf || FIXNUM_P(to)) && FIXNUM_P(step)) {
  • long i = FIX2LONG(from);
  • long diff = FIX2LONG(step);
  • if (diff > 0) {
  •  while (i <= end) {
    
  • if (inf) {
  •  for (;; i += diff)
    
    rb_yield(LONG2FIX(i));
  • i += diff;
  •  }
    
    }
    else {
  •  while (i >= end) {
    
  • rb_yield(LONG2FIX(i));
  • i += diff;
  •  long end = FIX2LONG(to);
    
  •  if (desc) {
    
  • for (; i >= end; i += diff)
  •    rb_yield(LONG2FIX(i));
    
  •  }
    
  •  else {
    
  • for (; i <= end; i += diff)
  •    rb_yield(LONG2FIX(i));
     }
    
    }
    }
    else if (!ruby_float_step(from, to, step, FALSE)) {
    VALUE i = from;
  • ID cmp;

  • if (RTEST(rb_funcall(step, ‘>’, 1, INT2FIX(0)))) {

  •  cmp = '>';
    
  • if (inf) {
  •  for (;; i = rb_funcall(i, '+', 1, step))
    
  • rb_yield(i);
    }
    else {
  •  cmp = '<';
    
  • }
  • for (;:wink: {
  •  if (RTEST(rb_funcall(i, cmp, 1, to))) break;
    
  •  rb_yield(i);
    
  •  i = rb_funcall(i, '+', 1, step);
    
  •  ID cmp = desc ? '<' : '>';
    
  •  for (; !RTEST(rb_funcall(i, cmp, 1, to)); i = rb_funcall(i, '+', 
    

1, step))

  • rb_yield(i);
    }
    }
    return from;
    @@ -3491,4 +3512,7 @@ Init_Numeric(void)
    rb_define_method(rb_cFloat, “nan?”, flo_is_nan_p, 0);
    rb_define_method(rb_cFloat, “infinite?”, flo_is_infinite_p, 0);
    rb_define_method(rb_cFloat, “finite?”, flo_is_finite_p, 0);

  • sym_to = ID2SYM(rb_intern(“to”));

  • sym_by = ID2SYM(rb_intern(“by”));
    }
    diff --git a/test/ruby/test_numeric.rb b/test/ruby/test_numeric.rb
    index f4fbea4…5525634 100644
    — a/test/ruby/test_numeric.rb
    +++ b/test/ruby/test_numeric.rb
    @@ -181,27 +181,58 @@ class TestNumeric < Test::Unit::TestCase
    assert_equal([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], a)

    a = []

  • 1.step(to: 10) {|x| a << x }

  • assert_equal([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], a)

  • a = []
    1.step(10, 2) {|x| a << x }
    assert_equal([1, 3, 5, 7, 9], a)

  • a = []

  • 1.step(to: 10, by: 2) {|x| a << x }

  • assert_equal([1, 3, 5, 7, 9], a)

  • assert_raise(ArgumentError) { 1.step(10, 1, 0) { } }
    assert_raise(ArgumentError) { 1.step(10, 0) { } }

  • assert_nothing_raised { 1.step(by: 0) }

    a = []
    10.step(1, -2) {|x| a << x }
    assert_equal([10, 8, 6, 4, 2], a)

    a = []

  • 10.step(to: 1, by: -2) {|x| a << x }

  • assert_equal([10, 8, 6, 4, 2], a)

  • a = []
    1.0.step(10.0, 2.0) {|x| a << x }
    assert_equal([1.0, 3.0, 5.0, 7.0, 9.0], a)

    a = []

  • 1.0.step(to: 10.0, by: 2.0) {|x| a << x }

  • assert_equal([1.0, 3.0, 5.0, 7.0, 9.0], a)

  • a = []
    1.step(10, 2**32) {|x| a << x }
    assert_equal([1], a)

    a = []

  • 1.step(to: 10, by: 2**32) {|x| a << x }

  • assert_equal([1], a)

  • a = []
    10.step(1, -(2**32)) {|x| a << x }
    assert_equal([10], a)

  • a = []

  • 10.step(to: 1, by: -(2**32)) {|x| a << x }

  • assert_equal([10], a)

  • a = 10.step.take(4)

  • assert_equal([10, 11, 12, 13], a)

  • a = 10.step(by: -1).take(4)

  • assert_equal([10, 9, 8, 7], a)
    end

def test_num2long

At Fri, 10 Sep 2010 13:57:48 +0900,
matz wrote:

|形に改めました。いかがでしょうか。

すばらしい。忙しくしている間にどんどん実装が進んでいく。
ちょっと寂しいけど。

 まあ、一年前のパッチの焼き直しなので。
 
 最近、散らばっていたパッチをまとめて git svn の袋に入れたら楽に
なりました。MLに投げてしばらくして消してしまったものも多いですが。

コミットしていただけませんか?

 入れました。Numeric#step も先の通り準備済みです。

At Thu, 9 Sep 2010 11:04:19 +0900,
I wrote:

At Thu, 9 Sep 2010 09:59:45 +0900,
matz wrote:

たとえば、

rb_scan_args(argc, argv, “02:”, &limit, &step, &kw);

 若干記法は違いますが、前に提案しました。 [ruby-dev:38048]

でキーワード辞書を取れたり、

 パッチを添付します。':‘を末尾(’&'ã‚’ç½®ãå ´åˆã¯ãã®ç›´å‰ï¼‰ã«ç½®ã
形に改めました。いかがでしょうか。


Akinori MUSHA / http://akinori.org/

diff --git a/README.EXT b/README.EXT
index ac0d7cc…c2b2d9d 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 95c4ee2…54ab449 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 9ad8ec8…1d4695b 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 483c8be…f9867e4 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 20218a9…970b857 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 169158b…7a4be8a 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 c718182…17bad37 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))

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 and are mandatory, and 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 and are mandatory, and 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 , , and 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
    
  •                                    ; モード時のみ)を出力せよという指定
    
  • 以下のように使います.
  • /*
    • キーワード引数 と ã¯å¿…é ˆï¼Œ は任意とする;
    • それ以外のキーワード引数を指定すると ArgumentError が発生する.
  • */
  • rb_scan_keyword_args(opt, “2!”, “x”, &x, “y”, &y, “color”, &color,
    NULL);
  • /*
    • キーワード引数 と ã¯å¿…é ˆï¼Œ は任意とする;
    • それ以外に指定されたキーワード引数はハッシュとして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);
  • /*
    • キーワード引数 , , はすべて任意;
    • それ以外のキーワード引数を指定すると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);

At Mon, 13 Sep 2010 12:48:37 +0900,
I wrote:

ようにしました。(パッチは省きます)
 もうだいぶ経つので後ほど入れようと思います。以下のようなAPIです。

=begin

rb_scan_keyword_args(VALUE hash, const char *fmt, …)

Retrieve keyword arguments from hash (Qnil is allowed and treated the
same as empty hash) according to the format string and an arbitrary
number of pairs of a keyword’s ID and a VALUE reference that follow,
terminated by a single occurrence of NULL. Another VALUE reference
may be put after the NULL, to which unlisted keyword arguments are
captured in 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 and are mandatory, and is optional;
  • If any other keyword arguments are given, an ArgumentError is
    raised.
    */
    rb_scan_keyword_args(opt, “2!”,
    rb_intern(“x”), &x, rb_intern(“y”), &y,
    rb_intern(“color”), &color, NULL);

/*

  • The keywords and are mandatory, and 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
    ",
    rb_intern(“x”), &x, rb_intern(“y”), &y,
    rb_intern(“color”), &color, NULL, &rest);

/* Unknown parameters can just be dropped by passing a NULL. /
rb_scan_keyword_args(opt, "2
",
rb_intern(“x”), &x, rb_intern(“y”), &y,
rb_intern(“color”), &color, NULL, NULL);

/*

  • The keywords , , and 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”,
    rb_intern(“x”), &x, rb_intern(“y”), &y,
    rb_intern(“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.

=end

At Sun, 12 Sep 2010 19:51:21 +0900,
I wrote:

してみました。仕様は以下の差分中に(日本語も)あるので見てみて
ください。

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

 rb_intern() 程度の手間は強いてもいいかなと思ったので、IDを取る
ようにしました。(パッチは省きます)

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

 想定されるRubyのAPIと対比してみると、こういう感じです。

# 省略可能キーワード
argc = rb_scan_keyword_args(argc, argv, "2:", &x, &y, &opt);
rb_scan_keyword_args(opt, "*", rb_intern("color"), &color, 0, 

&rest);

def point(x, y, color: color = :DEFAULT, **rest)
end

# å¿…é ˆã‚­ãƒ¼ãƒ¯ãƒ¼ãƒ‰
argc = rb_scan_keyword_args(argc, argv, "2:", &x, &y, &opt);
rb_scan_keyword_args(opt, "1*", rb_intern("color"), &color, 0, 

&rest);

def point(x, y, color: color, **rest)
end

# 残りを読み捨て
argc = rb_scan_keyword_args(argc, argv, "2:", &x, &y, &opt);
rb_scan_keyword_args(opt, "1*", rb_intern("color"), &color, 0, 

NULL);

def point(x, y, color: color, **rest)
end

# 知らないキーワードはwarning
argc = rb_scan_keyword_args(argc, argv, "2:", &x, &y, &opt);
rb_scan_keyword_args(opt, "1", rb_intern("color"), &color, 0);

def point(x, y, color: color, **rest)
  warn "unknown keywords: #{rest.keys.inspect}" if rest && $VERBOSE
end

# 知らないキーワードはArgumentError
argc = rb_scan_keyword_args(argc, argv, "2:", &x, &y, &opt);
rb_scan_keyword_args(opt, "1!", rb_intern("color"), &color, 0);

def point(x, y, color: color)
end