From 0dc74b94c2bea4285e4e63e7a1a03f75582a6630 Mon Sep 17 00:00:00 2001 From: mrkn Date: Thu, 15 Mar 2018 07:19:46 +0000 Subject: [PATCH] Add `exception:` keyword in Kernel#Rational() Support `exception:` keyword argument in `Kernel#Rational()`. If `exception:` is `false`, `Kernel#Rational()` returns `nil` if the given value cannot be interpreted as a rational value. The default value of `exception:` is `true`. This is part of [Feature #12732]. git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@62759 b2dd03c8-39d4-4d8f-98ff-823fe69b080e --- rational.c | 201 +++++++++++++++++++++++++------------ test/ruby/test_rational.rb | 24 +++++ 2 files changed, 161 insertions(+), 64 deletions(-) diff --git a/rational.c b/rational.c index d782143695..d88f50f886 100644 --- a/rational.c +++ b/rational.c @@ -525,7 +525,9 @@ f_rational_new_no_reduce2(VALUE klass, VALUE x, VALUE y) return nurat_s_canonicalize_internal_no_reduce(klass, x, y); } +static VALUE nurat_convert(VALUE klass, VALUE numv, VALUE denv, int raise); static VALUE nurat_s_convert(int argc, VALUE *argv, VALUE klass); + /* * call-seq: * Rational(x, y) -> rational @@ -564,7 +566,22 @@ static VALUE nurat_s_convert(int argc, VALUE *argv, VALUE klass); static VALUE nurat_f_rational(int argc, VALUE *argv, VALUE klass) { - return nurat_s_convert(argc, argv, rb_cRational); + VALUE a1, a2, opts = Qnil; + int raise = TRUE; + + if (rb_scan_args(argc, argv, "11:", &a1, &a2, &opts) == 1) { + a2 = Qundef; + } + if (!NIL_P(opts)) { + static ID kwds[1]; + VALUE exception; + if (!kwds[0]) { + kwds[0] = rb_intern_const("exception"); + } + rb_get_kwargs(opts, kwds, 0, 1, &exception); + raise = (exception != Qfalse); + } + return nurat_convert(rb_cRational, a1, a2, raise); } /* @@ -2363,7 +2380,7 @@ skip_ws(const char *s, const char *e) } static VALUE -parse_rat(const char *s, const char *const e, int strict) +parse_rat(const char *s, const char *const e, int strict, int raise) { int sign; VALUE num, den, ndiv, ddiv; @@ -2384,6 +2401,7 @@ parse_rat(const char *s, const char *const e, int strict) den = ndiv; } else if (den == ZERO) { + if (!raise) return Qnil; rb_num_zerodiv(); } else if (strict && skip_ws(s, e) != e) { @@ -2411,20 +2429,23 @@ parse_rat(const char *s, const char *const e, int strict) } static VALUE -string_to_r_strict(VALUE self) +string_to_r_strict(VALUE self, int raise) { VALUE num; rb_must_asciicompat(self); - num = parse_rat(RSTRING_PTR(self), RSTRING_END(self), 1); + num = parse_rat(RSTRING_PTR(self), RSTRING_END(self), 1, raise); if (NIL_P(num)) { - rb_raise(rb_eArgError, "invalid value for convert(): %+"PRIsVALUE, - self); + if (!raise) return Qnil; + rb_raise(rb_eArgError, "invalid value for convert(): %+"PRIsVALUE, + self); } - if (RB_FLOAT_TYPE_P(num)) - rb_raise(rb_eFloatDomainError, "Infinity"); + if (RB_FLOAT_TYPE_P(num)) { + if (!raise) return Qnil; + rb_raise(rb_eFloatDomainError, "Infinity"); + } return num; } @@ -2463,7 +2484,7 @@ string_to_r(VALUE self) rb_must_asciicompat(self); - num = parse_rat(RSTRING_PTR(self), RSTRING_END(self), 0); + num = parse_rat(RSTRING_PTR(self), RSTRING_END(self), 0, TRUE); if (RB_FLOAT_TYPE_P(num)) rb_raise(rb_eFloatDomainError, "Infinity"); @@ -2475,74 +2496,126 @@ rb_cstr_to_rat(const char *s, int strict) /* for complex's internal */ { VALUE num; - num = parse_rat(s, s + strlen(s), strict); + num = parse_rat(s, s + strlen(s), strict, TRUE); if (RB_FLOAT_TYPE_P(num)) rb_raise(rb_eFloatDomainError, "Infinity"); return num; } +static VALUE +to_rational(VALUE val) +{ + return rb_convert_type_with_id(val, T_RATIONAL, "Rational", idTo_r); +} + +static VALUE +nurat_convert(VALUE klass, VALUE numv, VALUE denv, int raise) +{ + VALUE a1 = numv, a2 = denv; + int state; + + if (NIL_P(a1) || NIL_P(a2)) + rb_raise(rb_eTypeError, "can't convert nil into Rational"); + + if (RB_TYPE_P(a1, T_COMPLEX)) { + if (k_exact_zero_p(RCOMPLEX(a1)->imag)) + a1 = RCOMPLEX(a1)->real; + } + + if (RB_TYPE_P(a2, T_COMPLEX)) { + if (k_exact_zero_p(RCOMPLEX(a2)->imag)) + a2 = RCOMPLEX(a2)->real; + } + + if (RB_FLOAT_TYPE_P(a1)) { + a1 = float_to_r(a1); + } + else if (RB_TYPE_P(a1, T_STRING)) { + a1 = string_to_r_strict(a1, raise); + if (!raise && NIL_P(a1)) return Qnil; + } + + if (RB_FLOAT_TYPE_P(a2)) { + a2 = float_to_r(a2); + } + else if (RB_TYPE_P(a2, T_STRING)) { + a2 = string_to_r_strict(a2, raise); + if (!raise && NIL_P(a2)) return Qnil; + } + + if (RB_TYPE_P(a1, T_RATIONAL)) { + if (a2 == Qundef || (k_exact_one_p(a2))) + return a1; + } + + if (a2 == Qundef) { + if (!k_integer_p(a1)) { + if (!raise) { + VALUE result = rb_protect(to_rational, a1, NULL); + rb_set_errinfo(Qnil); + return result; + } + return to_rational(a1); + } + } + else { + if (!k_numeric_p(a1)) { + if (!raise) { + a1 = rb_protect(to_rational, a1, &state); + if (state) { + rb_set_errinfo(Qnil); + return Qnil; + } + } + else { + a1 = rb_check_convert_type_with_id(a1, T_RATIONAL, "Rational", idTo_r); + } + } + if (!k_numeric_p(a2)) { + if (!raise) { + a2 = rb_protect(to_rational, a2, &state); + if (state) { + rb_set_errinfo(Qnil); + return Qnil; + } + } + else { + a2 = rb_check_convert_type_with_id(a2, T_RATIONAL, "Rational", idTo_r); + } + } + if ((k_numeric_p(a1) && k_numeric_p(a2)) && + (!f_integer_p(a1) || !f_integer_p(a2))) + return f_div(a1, a2); + } + + { + int argc; + VALUE argv2[2]; + argv2[0] = a1; + if (a2 == Qundef) { + argv2[1] = Qnil; + argc = 1; + } + else { + if (!k_integer_p(a2) && !raise) return Qnil; + argv2[1] = a2; + argc = 2; + } + return nurat_s_new(argc, argv2, klass); + } +} + static VALUE nurat_s_convert(int argc, VALUE *argv, VALUE klass) { VALUE a1, a2; - rb_scan_args(argc, argv, "11", &a1, &a2); - - if (NIL_P(a1) || (argc == 2 && NIL_P(a2))) - rb_raise(rb_eTypeError, "can't convert nil into Rational"); - - if (RB_TYPE_P(a1, T_COMPLEX)) { - if (k_exact_zero_p(RCOMPLEX(a1)->imag)) - a1 = RCOMPLEX(a1)->real; + if (rb_scan_args(argc, argv, "11", &a1, &a2) == 1) { + a2 = Qundef; } - if (RB_TYPE_P(a2, T_COMPLEX)) { - if (k_exact_zero_p(RCOMPLEX(a2)->imag)) - a2 = RCOMPLEX(a2)->real; - } - - if (RB_FLOAT_TYPE_P(a1)) { - a1 = float_to_r(a1); - } - else if (RB_TYPE_P(a1, T_STRING)) { - a1 = string_to_r_strict(a1); - } - - if (RB_FLOAT_TYPE_P(a2)) { - a2 = float_to_r(a2); - } - else if (RB_TYPE_P(a2, T_STRING)) { - a2 = string_to_r_strict(a2); - } - - if (RB_TYPE_P(a1, T_RATIONAL)) { - if (argc == 1 || (k_exact_one_p(a2))) - return a1; - } - - if (argc == 1) { - if (!k_integer_p(a1)) - return rb_convert_type_with_id(a1, T_RATIONAL, "Rational", idTo_r); - } - else { - if (!k_numeric_p(a1)) { - a1 = rb_check_convert_type_with_id(a1, T_RATIONAL, "Rational", idTo_r); - } - if (!k_numeric_p(a2)) { - a2 = rb_check_convert_type_with_id(a2, T_RATIONAL, "Rational", idTo_r); - } - if ((k_numeric_p(a1) && k_numeric_p(a2)) && - (!f_integer_p(a1) || !f_integer_p(a2))) - return f_div(a1, a2); - } - - { - VALUE argv2[2]; - argv2[0] = a1; - argv2[1] = a2; - return nurat_s_new(argc, argv2, klass); - } + return nurat_convert(klass, a1, a2, TRUE); } /* diff --git a/test/ruby/test_rational.rb b/test/ruby/test_rational.rb index 4cdcb419cb..ae253d89fc 100644 --- a/test/ruby/test_rational.rb +++ b/test/ruby/test_rational.rb @@ -805,6 +805,30 @@ class Rational_Test < Test::Unit::TestCase assert_raise(ZeroDivisionError) {Rational("1/0")} end + def test_Rational_without_exception + assert_nothing_raised(ArgumentError) { + assert_equal(nil, Rational("5/3x", exception: false)) + } + assert_nothing_raised(ZeroDivisionError) { + assert_equal(nil, Rational("1/0", exception: false)) + } + assert_nothing_raised(TypeError) { + assert_equal(nil, Rational(Object.new, exception: false)) + } + assert_nothing_raised(TypeError) { + assert_equal(nil, Rational(1, Object.new, exception: false)) + } + + o = Object.new; + def o.to_r; raise; end + assert_nothing_raised(RuntimeError) { + assert_equal(nil, Rational(o, exception: false)) + } + assert_nothing_raised(TypeError) { + assert_equal(nil, Rational(1, o, exception: false)) + } + end + def test_to_i assert_equal(1, Rational(3,2).to_i) assert_equal(1, Integer(Rational(3,2)))