[前][次][番号順一覧][スレッド一覧]

ruby-changes:55215

From: mame <ko1@a...>
Date: Wed, 3 Apr 2019 17:11:48 +0900 (JST)
Subject: [ruby-changes:55215] mame:r67422 (trunk): Introduce beginless range [Feature#14799]

mame	2019-04-03 17:11:41 +0900 (Wed, 03 Apr 2019)

  New Revision: 67422

  https://svn.ruby-lang.org/cgi-bin/viewvc.cgi?view=revision&revision=67422

  Log:
    Introduce beginless range [Feature#14799]

  Modified files:
    trunk/NEWS
    trunk/defs/id.def
    trunk/doc/syntax/literals.rdoc
    trunk/parse.y
    trunk/range.c
    trunk/spec/ruby/core/range/new_spec.rb
    trunk/test/ruby/test_array.rb
    trunk/test/ruby/test_range.rb
Index: NEWS
===================================================================
--- NEWS	(revision 67421)
+++ NEWS	(revision 67422)
@@ -28,6 +28,12 @@ sufficient information, see the ChangeLo https://github.com/ruby/ruby/blob/trunk/NEWS#L28
 * Numbered parameter as the default block parameter is introduced as an
   experimental feature.  [Feature #4475]
 
+* A beginless range is experimentally introduced.  It might not be as useful
+  as an endless range, but would be good for DSL purpose.
+
+     ary[..3]  # identical to ary[0..3]
+     where(sales: ..100)
+
 === Core classes updates (outstanding ones only)
 
 Enumerable::
Index: range.c
===================================================================
--- range.c	(revision 67421)
+++ range.c	(revision 67422)
@@ -35,7 +35,7 @@ static VALUE r_cover_p(VALUE, VALUE, VAL https://github.com/ruby/ruby/blob/trunk/range.c#L35
 static void
 range_init(VALUE range, VALUE beg, VALUE end, VALUE exclude_end)
 {
-    if ((!FIXNUM_P(beg) || !FIXNUM_P(end)) && !NIL_P(end)) {
+    if ((!FIXNUM_P(beg) || !FIXNUM_P(end)) && !NIL_P(beg) && !NIL_P(end)) {
 	VALUE v;
 
 	v = rb_funcall(beg, id_cmp, 1, end);
@@ -704,8 +704,8 @@ range_bsearch(VALUE range) https://github.com/ruby/ruby/blob/trunk/range.c#L704
     }
 #if SIZEOF_DOUBLE == 8 && defined(HAVE_INT64_T)
     else if (RB_TYPE_P(beg, T_FLOAT) || RB_TYPE_P(end, T_FLOAT)) {
-	int64_t low  = double_as_int64(RFLOAT_VALUE(rb_Float(beg)));
-	int64_t high = double_as_int64(NIL_P(end) ? HUGE_VAL : RFLOAT_VALUE(rb_Float(end)));
+	int64_t low  = double_as_int64(NIL_P(beg) ? -HUGE_VAL : RFLOAT_VALUE(rb_Float(beg)));
+	int64_t high = double_as_int64(NIL_P(end) ?  HUGE_VAL : RFLOAT_VALUE(rb_Float(end)));
 	int64_t mid, org_high;
 	BSEARCH(int64_as_double_to_num);
     }
@@ -726,6 +726,18 @@ range_bsearch(VALUE range) https://github.com/ruby/ruby/blob/trunk/range.c#L726
 	    diff = rb_funcall(diff, '*', 1, LONG2FIX(2));
 	}
     }
+    else if (NIL_P(beg) && is_integer_p(end)) {
+	VALUE diff = LONG2FIX(-1);
+	RETURN_ENUMERATOR(range, 0, 0);
+	while (1) {
+	    VALUE mid = rb_funcall(end, '+', 1, diff);
+	    BSEARCH_CHECK(mid);
+	    if (!smaller) {
+		return bsearch_integer_range(mid, end, 0);
+	    }
+	    diff = rb_funcall(diff, '*', 1, LONG2FIX(2));
+	}
+    }
     else {
 	rb_raise(rb_eTypeError, "can't do binary search for %s", rb_obj_classname(beg));
     }
@@ -770,6 +782,9 @@ range_size(VALUE range) https://github.com/ruby/ruby/blob/trunk/range.c#L782
             return DBL2NUM(HUGE_VAL);
         }
     }
+    else if (NIL_P(b)) {
+        return DBL2NUM(HUGE_VAL);
+    }
 
     return Qnil;
 }
@@ -1230,7 +1245,7 @@ rb_range_beg_len(VALUE range, long *begp https://github.com/ruby/ruby/blob/trunk/range.c#L1245
 
     if (!rb_range_values(range, &b, &e, &excl))
 	return Qfalse;
-    beg = NUM2LONG(b);
+    beg = NIL_P(b) ? 0 : NUM2LONG(b);
     end = NIL_P(e) ? -1 : NUM2LONG(e);
     if (NIL_P(e)) excl = 0;
     origbeg = beg;
@@ -1392,6 +1407,12 @@ range_include_internal(VALUE range, VALU https://github.com/ruby/ruby/blob/trunk/range.c#L1407
 	    VALUE rb_str_include_range_p(VALUE beg, VALUE end, VALUE val, VALUE exclusive);
 	    return rb_str_include_range_p(beg, end, val, RANGE_EXCL(range));
 	}
+        else if (NIL_P(beg)) {
+	    VALUE r = rb_funcall(val, id_cmp, 1, end);
+	    if (NIL_P(r)) return Qfalse;
+	    if (rb_cmpint(r, val, end) <= 0) return Qtrue;
+	    return Qfalse;
+        }
 	else if (NIL_P(end)) {
 	    VALUE r = rb_funcall(beg, id_cmp, 1, val);
 	    if (NIL_P(r)) return Qfalse;
@@ -1487,7 +1508,7 @@ r_cover_range_p(VALUE range, VALUE beg, https://github.com/ruby/ruby/blob/trunk/range.c#L1508
 static VALUE
 r_cover_p(VALUE range, VALUE beg, VALUE end, VALUE val)
 {
-    if (r_less(beg, val) <= 0) {
+    if (NIL_P(beg) || r_less(beg, val) <= 0) {
 	int excl = EXCL(range);
 	if (NIL_P(end) || r_less(val, end) <= -excl)
 	    return Qtrue;
@@ -1550,9 +1571,15 @@ range_alloc(VALUE klass) https://github.com/ruby/ruby/blob/trunk/range.c#L1571
  *     ('a'..'e').to_a    #=> ["a", "b", "c", "d", "e"]
  *     ('a'...'e').to_a   #=> ["a", "b", "c", "d"]
  *
- *  == Endless Ranges
+ *  == Beginless/Endless Ranges
+ *
+ *  A "beginless range" and "endless range" represents a semi-infinite
+ *  range.  Literal notation for a beginless range is:
+ *
+ *     (..1)
+ *     # or
+ *     (...1)
  *
- *  An "endless range" represents a semi-infinite range.
  *  Literal notation for an endless range is:
  *
  *     (1..)
@@ -1564,14 +1591,16 @@ range_alloc(VALUE klass) https://github.com/ruby/ruby/blob/trunk/range.c#L1591
  *     (1..nil)  # or similarly (1...nil)
  *     Range.new(1, nil) # or Range.new(1, nil, true)
  *
- *  Endless ranges are useful, for example, for idiomatic slicing of
- *  arrays:
+ *  Beginless/endless ranges are useful, for example, for idiomatic
+ *  slicing of arrays:
  *
+ *    [1, 2, 3, 4, 5][...2]   # => [1, 2]
  *    [1, 2, 3, 4, 5][2...]   # => [3, 4, 5]
  *
  *  Some implementation details:
  *
- *  * +end+ of endless range is +nil+;
+ *  * +begin+ of beginless range and +end+ of endless range are +nil+;
+ *  * +each+ of beginless range raises an exception;
  *  * +each+ of endless range enumerates infinite sequence (may be
  *    useful in combination with Enumerable#take_while or similar
  *    methods);
Index: test/ruby/test_array.rb
===================================================================
--- test/ruby/test_array.rb	(revision 67421)
+++ test/ruby/test_array.rb	(revision 67422)
@@ -42,6 +42,8 @@ class TestArray < Test::Unit::TestCase https://github.com/ruby/ruby/blob/trunk/test/ruby/test_array.rb#L42
     assert_equal([1, 2, 3], x[1..3])
     assert_equal([1, 2, 3], x[1,3])
     assert_equal([3, 4, 5], x[3..])
+    assert_equal([0, 1, 2], x[..2])
+    assert_equal([0, 1], x[...2])
 
     x[0, 2] = 10
     assert_equal([10, 2, 3, 4, 5], x)
@@ -375,8 +377,11 @@ class TestArray < Test::Unit::TestCase https://github.com/ruby/ruby/blob/trunk/test/ruby/test_array.rb#L377
 
     assert_equal(@cls[10, 11, 12], a[9..11])
     assert_equal(@cls[98, 99, 100], a[97..])
+    assert_equal(@cls[1, 2, 3], a[..2])
+    assert_equal(@cls[1, 2], a[...2])
     assert_equal(@cls[10, 11, 12], a[-91..-89])
-    assert_equal(@cls[98, 99, 100], a[-3..])
+    assert_equal(@cls[1, 2, 3], a[..-98])
+    assert_equal(@cls[1, 2], a[...-98])
 
     assert_nil(a[10, -3])
     assert_equal [], a[10..7]
@@ -462,6 +467,14 @@ class TestArray < Test::Unit::TestCase https://github.com/ruby/ruby/blob/trunk/test/ruby/test_array.rb#L467
     assert_equal(nil, a[10..] = nil)
     assert_equal(@cls[*(0..9).to_a] + @cls[nil], a)
 
+    a = @cls[*(0..99).to_a]
+    assert_equal(nil, a[..10] = nil)
+    assert_equal(@cls[nil] + @cls[*(11..99).to_a], a)
+
+    a = @cls[*(0..99).to_a]
+    assert_equal(nil, a[...10] = nil)
+    assert_equal(@cls[nil] + @cls[*(10..99).to_a], a)
+
     a = @cls[1, 2, 3]
     a[1, 0] = a
     assert_equal([1, 1, 2, 3, 2, 3], a)
Index: test/ruby/test_range.rb
===================================================================
--- test/ruby/test_range.rb	(revision 67421)
+++ test/ruby/test_range.rb	(revision 67422)
@@ -691,6 +691,8 @@ class TestRange < Test::Unit::TestCase https://github.com/ruby/ruby/blob/trunk/test/ruby/test_range.rb#L691
 
     assert_equal Float::INFINITY, (1...).size
     assert_equal Float::INFINITY, (1.0...).size
+    assert_equal Float::INFINITY, (...1).size
+    assert_equal Float::INFINITY, (...1.0).size
     assert_nil ("a"...).size
   end
 
@@ -735,6 +737,7 @@ class TestRange < Test::Unit::TestCase https://github.com/ruby/ruby/blob/trunk/test/ruby/test_range.rb#L737
     assert_equal(1, (0...ary.size).bsearch {|i| ary[i] >= 100 })
 
     assert_equal(1_000_001, (0...).bsearch {|i| i > 1_000_000 })
+    assert_equal( -999_999, (...0).bsearch {|i| i > -1_000_000 })
   end
 
   def test_bsearch_for_float
@@ -787,7 +790,8 @@ class TestRange < Test::Unit::TestCase https://github.com/ruby/ruby/blob/trunk/test/ruby/test_range.rb#L790
     assert_in_delta(1.0, (0.0..inf).bsearch {|x| Math.log(x) >= 0 })
     assert_in_delta(7.0, (0.0..10).bsearch {|x| 7.0 - x })
 
-    assert_equal(1_000_000.0.next_float, (0.0..).bsearch {|x| x > 1_000_000 })
+    assert_equal( 1_000_000.0.next_float, (0.0..).bsearch {|x| x > 1_000_000 })
+    assert_equal(-1_000_000.0.next_float, (..0.0).bsearch {|x| x > -1_000_000 })
   end
 
   def check_bsearch_values(range, search, a)
@@ -890,6 +894,7 @@ class TestRange < Test::Unit::TestCase https://github.com/ruby/ruby/blob/trunk/test/ruby/test_range.rb#L894
     assert_equal(bignum + 0, (bignum...bignum+ary.size).bsearch {|i| true })
     assert_equal(nil, (bignum...bignum+ary.size).bsearch {|i| false })
     assert_equal(bignum * 2 + 1, (bignum...).bsearch {|i| i > bignum * 2 })
+    assert_equal(-bignum * 2 + 1, (...-bignum).bsearch {|i| i > -bignum * 2 })
 
     assert_raise(TypeError) { ("a".."z").bsearch {} }
   end
@@ -907,4 +912,8 @@ class TestRange < Test::Unit::TestCase https://github.com/ruby/ruby/blob/trunk/test/ruby/test_range.rb#L912
     assert_equal([1,2,3,4], (1...5).to_a)
     assert_raise(RangeError) { (1..).to_a }
   end
+
+  def test_beginless_range_iteration
+    assert_raise(TypeError) { (..1).each { } }
+  end
 end
Index: parse.y
===================================================================
--- parse.y	(revision 67421)
+++ parse.y	(revision 67422)
@@ -922,6 +922,8 @@ static void token_info_warn(struct parse https://github.com/ruby/ruby/blob/trunk/parse.y#L922
 %token tNMATCH		RUBY_TOKEN(NMATCH) "!~"
 %token tDOT2		RUBY_TOKEN(DOT2)   ".."
 %token tDOT3		RUBY_TOKEN(DOT3)   "..."
+%token tBDOT2		RUBY_TOKEN(BDOT2)   "(.."
+%token tBDOT3		RUBY_TOKEN(BDOT3)   "(..."
 %token tAREF		RUBY_TOKEN(AREF)   "[]"
 %token tASET		RUBY_TOKEN(ASET)   "[]="
 %token tLSHFT		RUBY_TOKEN(LSHFT)  "<<"
@@ -968,7 +970,7 @@ static void token_info_warn(struct parse https://github.com/ruby/ruby/blob/trunk/parse.y#L970
 %right '=' tOP_ASGN
 %left modifier_rescue
 %right '?' ':'
-%nonassoc tDOT2 tDOT3
+%nonassoc tDOT2 tDOT3 tBDOT2 tBDOT3
 %left  tOROP
 %left  tANDOP
 %nonassoc  tCMP tEQ tEQQ tNEQ tMATCH tNMATCH
@@ -1996,6 +1998,30 @@ arg		: lhs '=' arg_rhs https://github.com/ruby/ruby/blob/trunk/parse.y#L1998
 		    /*% %*/
 		    /*% ripper: dot3!($1, Qnil) %*/
 		    }
+		| tBDOT2 arg
+		    {
+		    /*%%%*/
+                        YYLTYPE loc;
+                        loc.beg_pos = @1.beg_pos;
+                        loc.end_pos = @1.beg_pos;
+
+			value_expr($2);
+			$$ = NEW_DOT2(new_nil(&loc), $2, &@$);
+		    /*% %*/
+		    /*% ripper: dot2!(Qnil, $2) %*/
+		    }
+		| tBDOT3 arg
+		    {
+		    /*%%%*/
+                        YYLTYPE loc;
+                        loc.beg_pos = @1.beg_pos;
+                        loc.end_pos = @1.beg_pos;
+
+			value_expr($2);
+			$$ = NEW_DOT3(new_nil(&loc), $2, &@$);
+		    /*% %*/
+		    /*% ripper: dot3!(Qnil, $2) %*/
+		    }
 		| arg '+' arg
 		    {
 			$$ = call_bin_op(p, $1, '+', $3, &@2, &@$);
@@ -8241,15 +8267,16 @@ parser_yylex(struct parser_params *p) https://github.com/ruby/ruby/blob/trunk/parse.y#L8267
 	pushback(p, c);
 	return warn_balanced('-', "-", "unary operator");
 
-      case '.':
+      case '.': {
+        int is_beg = IS_BEG();
 	SET_LEX_STATE(EXPR_BEG);
 	switch (c = nextc(p)) {
 	  case '.':
 	    if ((c = nextc(p)) == '.') {
-		return tDOT3;
+		return is_beg ? tBDOT3 : tDOT3;
 	    }
 	    pushback(p, c);
-	    return tDOT2;
+	    return is_beg ? tBDOT2 : tDOT2;
 	  case ':':
 	    switch (c = nextc(p)) {
 	      default:
@@ -8275,6 +8302,7 @@ parser_yylex(struct parser_params *p) https://github.com/ruby/ruby/blob/trunk/parse.y#L8302
 	set_yylval_id('.');
 	SET_LEX_STATE(EXPR_DOT);
 	return '.';
+      }
 
       case '0': case '1': case '2': case '3': case '4':
       case '5': case '6': case '7': case '8': case '9':
Index: doc/syntax/literals.rdoc
===================================================================
--- doc/syntax/literals.rdoc	(revision 67421)
+++ doc/syntax/literals.rdoc	(revision 67422)
@@ -334,6 +334,7 @@ its ending value. https://github.com/ruby/ruby/blob/trunk/doc/syntax/literals.rdoc#L334
   (1..2)  # includes its ending value
   (1...2) # excludes its ending value
   (1..)   # endless range, representing infinite sequence from 1 to Infinity
+  (..1)   # beginless range, representing infinite sequence from -Infinity to 1
 
 You may create a range of any object.  See the Range documentation for details
 on the methods you need to implement.
Index: defs/id.def
===================================================================
--- defs/id.def	(revision 67421)
+++ defs/id.def	(revision 67422)
@@ -76,6 +76,8 @@ firstline, predefined = __LINE__+1, %[\ https://github.com/ruby/ruby/blob/trunk/defs/id.def#L76
 token_ops = %[\
   Dot2          ..      DOT2
   Dot3          ...     DOT3
+  BDot2         ..      BDOT2
+  BDot3         ...     BDOT3
   UPlus         +@      UPLUS
   UMinus        -@      UMINUS
   Pow           **      POW
Index: spec/ruby/core/range/new_spec.rb
===================================================================
--- spec/ruby/core/range/new_spec.rb	(revision 67421)
+++ spec/ruby/core/range/new_spec.rb	(revision 67422)
@@ -43,9 +43,25 @@ describe "Range.new" do https://github.com/ruby/ruby/blob/trunk/spec/ruby/core/range/new_spec.rb#L43
     end
   end
 
-  describe "endless range" do
-    it "does not allow range without left boundary" do
-      -> { Range.new(nil, 1) }.should raise_error(ArgumentError, /bad value for range/)
+  describe "beginless/endless range" do
+    ruby_version_is ""..."2.7" do
+      it "does not allow range without left boundary" do
+        -> { Range.new(nil, 1) }.should raise_error(ArgumentError, /bad value for range/)
+      end
+    end
+
+    ruby_version_is "2.7" do
+      it "allows beginless left boundary" do
+        range = Range.new(nil, 1)
+        range.begin.should == nil
+      end
+
+      it "distinguishes ranges with included and excluded right boundary" do
+        range_exclude = Range.new(nil, 1, true)
+        range_include = Range.new(nil, 1, false)
+
+        range_exclude.should_not == range_include
+      end
     end
 
     ruby_version_is ""..."2.6" do

--
ML: ruby-changes@q...
Info: http://www.atdot.net/~ko1/quickml/

[前][次][番号順一覧][スレッド一覧]