From: naruse@... Date: 2016-11-25T09:34:58+00:00 Subject: [ruby-core:78347] [CommonRuby Feature#12732] An option to pass to `Integer`, `Float`, to return `nil` instead of raise an exception Issue #12732 has been updated by Yui NARUSE. Below is PoC; it may have a path which raises an exception. ```diff diff --git a/object.c b/object.c index 05bef4d..5d63803 100644 --- a/object.c +++ b/object.c @@ -2750,17 +2750,60 @@ static VALUE rb_f_integer(int argc, VALUE *argv, VALUE obj) { VALUE arg = Qnil; + VALUE opts = Qnil; + VALUE exception = Qnil; + VALUE vbase = Qundef; int base = 0; + static ID int_kwds[1]; - switch (argc) { - case 2: - base = NUM2INT(argv[1]); - case 1: - arg = argv[0]; - break; - default: - /* should cause ArgumentError */ - rb_scan_args(argc, argv, "11", NULL, NULL); + rb_scan_args(argc, argv, "11:", &arg, &vbase, &opts); + if (!NIL_P(vbase)) { + base = NUM2INT(vbase); + } + if (!NIL_P(opts)) { + if (!int_kwds[0]) { + int_kwds[0] = rb_intern_const("exception"); + } + if (rb_get_kwargs(opts, int_kwds, 0, 1, &exception)) { + VALUE tmp; + if (RB_FLOAT_TYPE_P(arg)) { + double f; + if (base != 0) goto arg_error; + f = RFLOAT_VALUE(arg); + if (FIXABLE(f)) return LONG2FIX((long)f); + return rb_dbl2big(f); + } + else if (RB_INTEGER_TYPE_P(arg)) { + if (base != 0) goto arg_error; + return arg; + } + else if (RB_TYPE_P(arg, T_STRING)) { + const char *s; + long len; + rb_must_asciicompat(arg); + RSTRING_GETMEM(arg, s, len); + tmp = rb_cstr_parse_inum(s, len, NULL, base); + if (NIL_P(tmp)) { + return exception; + } + return tmp; + } + else if (NIL_P(arg)) { + if (base != 0) goto arg_error; + return exception; + } + if (base != 0) { + tmp = rb_check_string_type(arg); + if (!NIL_P(tmp)) return rb_str_to_inum(tmp, base, TRUE); +arg_error: + rb_raise(rb_eArgError, "base specified for non string value"); + } + tmp = convert_type(arg, "Integer", "to_int", FALSE); + if (NIL_P(tmp)) { + return rb_to_integer(arg, "to_i"); + } + return tmp; + } } return rb_convert_to_integer(arg, base); } ``` ```ruby def assert(a, b) if a != b raise "'#{a}' != '#{b}'" end end def assert_raise(ex) begin yield raise "#{ex} is expected but not raised" rescue ex # correct rescue raise "#{ex} is expected but #{$!.inspect}" end end o = Object.new assert 123, Integer("123") assert 50, Integer("32", 16) assert 16, Integer("10", 16, exception: o) assert o, Integer("x", exception: o) assert o, Integer("x", 16, exception: o) assert_raise(ArgumentError){ Integer("x") } assert_raise(ArgumentError){ Integer("x", 16) } ``` ```ruby require'benchmark/ips' Benchmark.ips{|x| x.report("rescue") { Integer('foo') rescue nil } x.report("kwarg") { Integer('foo', exception: nil) } } ``` ``` Warming up -------------------------------------- rescue 36.258k i/100ms kwarg 64.004k i/100ms Calculating ------------------------------------- rescue 392.926k (�� 8.9%) i/s - 1.958M in 5.025204s kwarg 844.563k (��14.9%) i/s - 4.096M in 5.017539s ``` ---------------------------------------- Feature #12732: An option to pass to `Integer`, `Float`, to return `nil` instead of raise an exception https://bugs.ruby-lang.org/issues/12732#change-61713 * Author: Aaron Patterson * Status: Feedback * Priority: Normal * Assignee: Yukihiro Matsumoto ---------------------------------------- I would like to be able to pass an option to `Integer()` and `Float()` so that they don't raise an exception, but return `nil` instead. For example: ~~~ Integer(string, exception: false) ~~~ The reason I want this function is so that I can convert strings from YAML or JSON to integers if they parse correctly, or just return strings if they can't be parsed. ---Files-------------------------------- integer-parse.pdf (29 KB) -- https://bugs.ruby-lang.org/ Unsubscribe: