diff --git a/Src/IronRuby.Tests/RubyTests.cs b/Src/IronRuby.Tests/RubyTests.cs
index f17f6c8d..be37b054 100644
--- a/Src/IronRuby.Tests/RubyTests.cs
+++ b/Src/IronRuby.Tests/RubyTests.cs
@@ -868,6 +868,8 @@ public Tests(Driver/*!*/ driver) {
Io_BuiltinReaders1,
Io_LineReaders2,
Io_LineReaders3,
+ StringIO_LineReaders1,
+ Kernel_ConversionsInjectZip1,
Method_LightFrame1,
Method_LightFrame2,
Hash_SelfKey1,
diff --git a/Src/IronRuby.Tests/Runtime/MiscTests.cs b/Src/IronRuby.Tests/Runtime/MiscTests.cs
index 71fe5e37..34ad997f 100644
--- a/Src/IronRuby.Tests/Runtime/MiscTests.cs
+++ b/Src/IronRuby.Tests/Runtime/MiscTests.cs
@@ -3147,6 +3147,188 @@ private static bool CanCreateSymbolicLinks() {
}
}
+ ///
+ /// Kernel#Integer and #Float with exception: false answer nil from the C# parser instead of
+ /// raising and rescuing; a String in an ASCII-incompatible encoding is refused either way,
+ /// and Float names a NUL. Array#inject over Fixnums, Array#minmax as [min, max], and
+ /// Enumerable#zip with an Integer Range worked out from the index.
+ ///
+ public void Kernel_ConversionsInjectZip1() {
+ TestOutput(@"
+require 'ruby4'
+def t(label)
+ r = begin; yield; rescue => e; [e.class, e.message]; end
+ puts ""#{label}: #{r.inspect}""
+end
+['123', ' 0x1A ', '0b101', '1_000', '1__0', '', ' ', 'x', '12x', ""1\0"", '-0o17', '0d19', '08', '+5', 'ff'].each do |s|
+ t(""Integer #{s.inspect}"") { [Integer(s, exception: false), Integer(s, 16, exception: false), Float(s, exception: false)] }
+end
+['1.5', '1e3', '.5', '1_0.5', 'Infinity', 'NaN', '0x1p3', ' 2.5 ', '1e', ""1\0""].each do |s|
+ t(""Float #{s.inspect}"") { Float(s, exception: false) }
+ t(""Float! #{s.inspect}"") { Float(s) }
+end
+t('radix 99') { Integer('1', 99, exception: false) }
+u = '12'.encode('UTF-16LE')
+t('utf16') { [(Integer(u) rescue $!.class), (Integer(u, exception: false) rescue $!.class), (Float(u, exception: false) rescue $!.class)] }
+t('nil') { Integer(nil, exception: false) }
+[1.5, -2.9, 1e20, Float::NAN, Float::INFINITY].each { |f| t(""Integer(#{f})"") { [(Integer(f) rescue $!.class), Integer(f, exception: false)] } }
+a = [1, 2, 3, 2**62, 2**62]
+t('inject +') { [a.inject(:+), a.inject(10, :+), [].inject(:+), [].inject(5, :+), [1, 2.5, 3].inject(:+), ([1, 'a'].inject(:+) rescue $!.class)] }
+t('inject sym') { [[2, 3, 4].inject(:*), [1, 2].inject('+'), %w[a b c].inject(:+), (1..4).inject(:+), [3].inject(:+)] }
+t('inject block') { [[1, 2, 3].inject { |s, x| s * 10 + x }, [1, 2, 3].inject(100) { |s, x| s - x }, [1, 2, 3].inject { |s, x| break s * 7 if x == 3; s + x }] }
+t('inject subclass') { Class.new(Array) { def each; yield 9; end }.new([1, 2]).inject(:+) }
+t('minmax') { [[3, 1, 2].minmax, [].minmax, %w[b a].minmax, [1.5, 1, 2**70].minmax, [3, 1, 2].minmax { |x, y| y <=> x }, ([1, nil].minmax rescue $!.class)] }
+t('zip') { [(1..5).zip(1..3), (1..5).zip(2...4), (1..4).zip(10..), (1..3).zip(5..1), (1..3).zip(2**64..2**64 + 1), (1..3).zip('a'..'b')] }
+t('zip block') { r = []; [(1..3).zip(4..6) { |x| r << x }, r] }
+t('zip subclass') { (1..2).zip(Class.new(Range) { def each; yield 42; end }.new(1, 5)) }
+t('zip singleton') { r = (1..3).dup; def r.each; yield 42; end; (1..2).zip(r) }
+t('zip readers') { [(r = (1..3).dup; def r.begin; 10; end; (0..1).zip(r)), (r = (1..3).dup; def r.end; 1; end; (0..2).zip(r)), (r = (1..3).dup; def r.exclude_end?; true; end; (0..3).zip(r))] }
+", @"
+Integer ""123"": [123, 291, 123.0]
+Integer "" 0x1A "": [26, 26, 26.0]
+Integer ""0b101"": [5, 45313, nil]
+Integer ""1_000"": [1000, 4096, 1000.0]
+Integer ""1__0"": [nil, nil, nil]
+Integer """": [nil, nil, nil]
+Integer "" "": [nil, nil, nil]
+Integer ""x"": [nil, nil, nil]
+Integer ""12x"": [nil, nil, nil]
+Integer ""1\u0000"": [nil, nil, nil]
+Integer ""-0o17"": [-15, nil, nil]
+Integer ""0d19"": [19, 3353, nil]
+Integer ""08"": [nil, 8, 8.0]
+Integer ""+5"": [5, 5, 5.0]
+Integer ""ff"": [nil, 255, nil]
+Float ""1.5"": 1.5
+Float! ""1.5"": 1.5
+Float ""1e3"": 1000.0
+Float! ""1e3"": 1000.0
+Float "".5"": 0.5
+Float! "".5"": 0.5
+Float ""1_0.5"": 10.5
+Float! ""1_0.5"": 10.5
+Float ""Infinity"": nil
+Float! ""Infinity"": [ArgumentError, ""invalid value for Float(): \""Infinity\""""]
+Float ""NaN"": nil
+Float! ""NaN"": [ArgumentError, ""invalid value for Float(): \""NaN\""""]
+Float ""0x1p3"": 8.0
+Float! ""0x1p3"": 8.0
+Float "" 2.5 "": 2.5
+Float! "" 2.5 "": 2.5
+Float ""1e"": nil
+Float! ""1e"": [ArgumentError, ""invalid value for Float(): \""1e\""""]
+Float ""1\u0000"": nil
+Float! ""1\u0000"": [ArgumentError, ""string for Float contains null byte""]
+radix 99: [ArgumentError, ""invalid radix 99""]
+utf16: [Encoding::CompatibilityError, Encoding::CompatibilityError, Encoding::CompatibilityError]
+nil: nil
+Integer(1.5): [1, 1]
+Integer(-2.9): [-2, -2]
+Integer(1.0e+20): [100000000000000000000, 100000000000000000000]
+Integer(NaN): [FloatDomainError, nil]
+Integer(Infinity): [FloatDomainError, nil]
+inject +: [9223372036854775814, 9223372036854775824, nil, 5, 6.5, TypeError]
+inject sym: [24, 3, ""abc"", 10, 3]
+inject block: [123, 94, 21]
+inject subclass: 9
+minmax: [[1, 3], [nil, nil], [""a"", ""b""], [1, 1180591620717411303424], [3, 1], ArgumentError]
+zip: [[[1, 1], [2, 2], [3, 3], [4, nil], [5, nil]], [[1, 2], [2, 3], [3, nil], [4, nil], [5, nil]], [[1, 10], [2, 11], [3, 12], [4, 13]], [[1, nil], [2, nil], [3, nil]], [[1, 18446744073709551616], [2, 18446744073709551617], [3, nil]], [[1, ""a""], [2, ""b""], [3, nil]]]
+zip block: [nil, [[1, 4], [2, 5], [3, 6]]]
+zip subclass: [[1, 42], [2, nil]]
+zip singleton: [[1, 42], [2, nil]]
+zip readers: [[[0, 1], [1, 2]], [[0, 1], [1, 2], [2, 3]], [[0, 1], [1, 2], [2, 3], [3, nil]]]
+");
+ }
+
+ ///
+ /// StringIO's line readers after the bare gets got an overload of its own, a one-byte
+ /// separator is looked for as a byte, and chomp: cuts the line where it is; lineno counts
+ /// the lines read and not the nil at the end. Also the byte side of a fresh literal, which
+ /// learns it is ASCII rather than being switched to bytes to find out.
+ ///
+ public void StringIO_LineReaders1() {
+ TestOutput(@"
+require 'ruby4'
+require 'stringio'
+def t(label)
+ r = begin; yield; rescue => e; [e.class, e.message]; end
+ puts ""#{label}: #{r.inspect}""
+end
+src = ""one\r\ntwo\n\n\nthréeéx\nlast""
+[[], [""\n""], [""e""], [""é""], [""éx""], [""""], [nil], [3], [""o"", 2], [""\r\n""]].each do |a|
+ t(""gets #{a.inspect}"") { s = StringIO.new(src); r = []; while l = s.gets(*a); r << l; end; [r, $_, s.lineno] }
+ t(""gets chomp #{a.inspect}"") { s = StringIO.new(src); r = []; while l = s.gets(*a, chomp: true); r << l; end; r }
+ t(""readlines chomp #{a.inspect}"") { StringIO.new(src).readlines(*a, chomp: true) }
+ t(""each_line chomp #{a.inspect}"") { StringIO.new(src).each_line(*a, chomp: true).to_a }
+end
+t(""binary sep"") { StringIO.new(""a\xFFb\xFFc"".b).readlines(""\xFF"".b, chomp: true) }
+t(""gets bare"") { s = StringIO.new(""x\ny""); [s.gets, s.gets, s.gets, $_] }
+t(""frozen line chomp"") { s = StringIO.new(""ab\n"".freeze); l = s.gets(chomp: true); [l, l.frozen?, s.string] }
+t(""chomp keeps source"") { str = +""a\nb\n""; s = StringIO.new(str); s.readlines(chomp: true); str }
+# literal byte access
+[""hello"", ""héllo"", """", ""\u{1F600}x""].each do |lit|
+ t(""bytes #{lit.inspect}"") { x = lit.dup; [x.bytesize, x.getbyte(0), x.getbyte(1), x.bytes, x.size, x.ascii_only?, x.b, x.byteslice(1, 2)] }
+end
+t(""iso literal"") { x = ""caf\xE9"".force_encoding(""iso-8859-1""); [x.bytesize, x.getbyte(3), x.bytes] }
+t(""utf16"") { x = ""ab"".encode(""UTF-16LE""); [x.bytesize, x.getbyte(0), x.getbyte(1), x.bytes] }
+t(""io write literal"") { s = StringIO.new(+""""); s.write(""abc""); s.write(""dé""); [s.string, s.string.bytesize, s.pos] }
+t(""string << then bytes"") { x = +""ab""; x << ""c""; [x.bytesize, x.getbyte(2), x.bytes] }
+", @"
+gets []: [[""one\r\n"", ""two\n"", ""\n"", ""\n"", ""thréeéx\n"", ""last""], nil, 6]
+gets chomp []: [""one"", ""two"", """", """", ""thréeéx"", ""last""]
+readlines chomp []: [""one"", ""two"", """", """", ""thréeéx"", ""last""]
+each_line chomp []: [""one"", ""two"", """", """", ""thréeéx"", ""last""]
+gets [""\n""]: [[""one\r\n"", ""two\n"", ""\n"", ""\n"", ""thréeéx\n"", ""last""], nil, 6]
+gets chomp [""\n""]: [""one"", ""two"", """", """", ""thréeéx"", ""last""]
+readlines chomp [""\n""]: [""one"", ""two"", """", """", ""thréeéx"", ""last""]
+each_line chomp [""\n""]: [""one"", ""two"", """", """", ""thréeéx"", ""last""]
+gets [""e""]: [[""one"", ""\r\ntwo\n\n\nthrée"", ""éx\nlast""], nil, 3]
+gets chomp [""e""]: [""on"", ""\r\ntwo\n\n\nthré"", ""éx\nlast""]
+readlines chomp [""e""]: [""on"", ""\r\ntwo\n\n\nthré"", ""éx\nlast""]
+each_line chomp [""e""]: [""on"", ""\r\ntwo\n\n\nthré"", ""éx\nlast""]
+gets [""é""]: [[""one\r\ntwo\n\n\nthré"", ""eé"", ""x\nlast""], nil, 3]
+gets chomp [""é""]: [""one\r\ntwo\n\n\nthr"", ""e"", ""x\nlast""]
+readlines chomp [""é""]: [""one\r\ntwo\n\n\nthr"", ""e"", ""x\nlast""]
+each_line chomp [""é""]: [""one\r\ntwo\n\n\nthr"", ""e"", ""x\nlast""]
+gets [""éx""]: [[""one\r\ntwo\n\n\nthréeéx"", ""\nlast""], nil, 2]
+gets chomp [""éx""]: [""one\r\ntwo\n\n\nthrée"", ""\nlast""]
+readlines chomp [""éx""]: [""one\r\ntwo\n\n\nthrée"", ""\nlast""]
+each_line chomp [""éx""]: [""one\r\ntwo\n\n\nthrée"", ""\nlast""]
+gets [""""]: [[""one\r\ntwo\n\n\n"", ""thréeéx\nlast""], nil, 2]
+gets chomp [""""]: [""one\r\ntwo"", ""thréeéx\nlast""]
+readlines chomp [""""]: [""one\r\ntwo"", ""thréeéx\nlast""]
+each_line chomp [""""]: [""one\r\ntwo"", ""thréeéx\nlast""]
+gets [nil]: [[""one\r\ntwo\n\n\nthréeéx\nlast""], nil, 1]
+gets chomp [nil]: [""one\r\ntwo\n\n\nthréeéx\nlast""]
+readlines chomp [nil]: [""one\r\ntwo\n\n\nthréeéx\nlast""]
+each_line chomp [nil]: [""one\r\ntwo\n\n\nthréeéx\nlast""]
+gets [3]: [[""one"", ""\r\n"", ""two"", ""\n"", ""\n"", ""\n"", ""thr"", ""ée"", ""éx"", ""\n"", ""las"", ""t""], nil, 12]
+gets chomp [3]: [""one"", """", ""two"", """", """", """", ""thr"", ""ée"", ""éx"", """", ""las"", ""t""]
+readlines chomp [3]: [""one"", """", ""two"", """", """", """", ""thr"", ""ée"", ""éx"", """", ""las"", ""t""]
+each_line chomp [3]: [""one"", """", ""two"", """", """", """", ""thr"", ""ée"", ""éx"", """", ""las"", ""t""]
+gets [""o"", 2]: [[""o"", ""ne"", ""\r\n"", ""tw"", ""o"", ""\n\n"", ""\nt"", ""hr"", ""é"", ""eé"", ""x\n"", ""la"", ""st""], nil, 13]
+gets chomp [""o"", 2]: ["""", ""ne"", ""\r\n"", ""tw"", """", ""\n\n"", ""\nt"", ""hr"", ""é"", ""eé"", ""x\n"", ""la"", ""st""]
+readlines chomp [""o"", 2]: ["""", ""ne"", ""\r\n"", ""tw"", """", ""\n\n"", ""\nt"", ""hr"", ""é"", ""eé"", ""x\n"", ""la"", ""st""]
+each_line chomp [""o"", 2]: ["""", ""ne"", ""\r\n"", ""tw"", """", ""\n\n"", ""\nt"", ""hr"", ""é"", ""eé"", ""x\n"", ""la"", ""st""]
+gets [""\r\n""]: [[""one\r\n"", ""two\n\n\nthréeéx\nlast""], nil, 2]
+gets chomp [""\r\n""]: [""one"", ""two\n\n\nthréeéx\nlast""]
+readlines chomp [""\r\n""]: [""one"", ""two\n\n\nthréeéx\nlast""]
+each_line chomp [""\r\n""]: [""one"", ""two\n\n\nthréeéx\nlast""]
+binary sep: [""a"", ""b"", ""c""]
+gets bare: [""x\n"", ""y"", nil, nil]
+frozen line chomp: [""ab"", false, ""ab\n""]
+chomp keeps source: ""a\nb\n""
+bytes ""hello"": [5, 104, 101, [104, 101, 108, 108, 111], 5, true, ""hello"", ""el""]
+bytes ""héllo"": [6, 104, 195, [104, 195, 169, 108, 108, 111], 5, false, ""h\xC3\xA9llo"", ""é""]
+bytes """": [0, nil, nil, [], 0, true, """", nil]
+bytes ""😀x"": [5, 240, 159, [240, 159, 152, 128, 120], 2, false, ""\xF0\x9F\x98\x80x"", ""\x9F\x98""]
+iso literal: [4, 233, [99, 97, 102, 233]]
+utf16: [4, 97, 0, [97, 0, 98, 0]]
+io write literal: [""abcdé"", 6, 6]
+string << then bytes: [3, 99, [97, 98, 99]]
+");
+ }
+
///
/// Text mode's universal newline decorator - "\r\n" and a lone "\r" are folded before the
/// line is split - and the newline options that are decorators for the way out only
diff --git a/Src/Libraries/Builtins/Enumerable.cs b/Src/Libraries/Builtins/Enumerable.cs
index 2f792366..b63492d7 100644
--- a/Src/Libraries/Builtins/Enumerable.cs
+++ b/Src/Libraries/Builtins/Enumerable.cs
@@ -591,6 +591,11 @@ internal static object Inject(CallSiteStorage/*!*/ each, BlockParam op
operatorName, new RubyCallSignature(1, RubyCallFlags.HasScope | RubyCallFlags.HasImplicitSelf)
);
+ var list = self as RubyArray;
+ if (list != null && IsPlainList(each.Context, self)) {
+ return InjectList(list, operation, scope, operatorName, site, initial);
+ }
+
object result = initial;
Each(each, self, Proc.Create(each.Context, 0, delegate(BlockParam/*!*/ selfBlock, object _, object[] __, RubyArray/*!*/ args) {
Debug.Assert(__.Length == 0);
@@ -616,6 +621,42 @@ internal static object Inject(CallSiteStorage/*!*/ each, BlockParam op
return result != Missing.Value ? result : null;
}
+ ///
+ /// #inject over a plain list (see IsPlainList): the elements are taken from the list,
+ /// with no #each call, Proc or argument array for each one. inject(:+) over Fixnums adds
+ /// them here while Integer#+ is the built-in, as MRI's ary_inject_op does, and goes back to
+ /// calling #+ at the first element that is not a Fixnum.
+ ///
+ private static object InjectList(RubyArray/*!*/ list, BlockParam operation, RubyScope scope, string operatorName,
+ CallSite> site, object initial) {
+
+ object result = initial;
+ int i = 0;
+ if (result == Missing.Value) {
+ if (list.Count == 0) {
+ return null;
+ }
+ result = list[0];
+ i = 1;
+ }
+
+ if (site != null && operatorName == "+" && IListOps.IsBuiltinMethod(scope.RubyContext.IntegerClass, "+")) {
+ for (; i < list.Count && result is int && list[i] is int; i++) {
+ result = ClrInteger.Add((int)result, (int)list[i]);
+ }
+ }
+
+ for (; i < list.Count; i++) {
+ object value = list[i];
+ if (site != null) {
+ result = site.Target(site, scope, result, value);
+ } else if (operation.Yield(result, value, out result)) {
+ return result;
+ }
+ }
+ return result;
+ }
+
#endregion
#region min, max, minmax
diff --git a/Src/Libraries/Builtins/KernelOps.cs b/Src/Libraries/Builtins/KernelOps.cs
index e75823d7..b0bada6c 100644
--- a/Src/Libraries/Builtins/KernelOps.cs
+++ b/Src/Libraries/Builtins/KernelOps.cs
@@ -129,6 +129,25 @@ public static object ToFloat(ConversionStorage/*!*/ floatConversion, obj
return Protocols.CastToFloat(floatConversion, value);
}
+ // A String has to be readable as a number at all: in an ASCII-compatible encoding - MRI
+ // checks that before anything else, and exception: false does not change it - and, for
+ // Float, without a NUL, which MRI names rather than calling the value invalid.
+ [RubyMethod("Float", RubyMethodAttributes.PrivateInstance)]
+ [RubyMethod("Float", RubyMethodAttributes.PublicSingleton)]
+ public static object ToFloat(RubyContext/*!*/ context, object self, [NotNull]MutableString/*!*/ value) {
+ RequireAsciiCompatible(value);
+ if (value.IndexOf('\0') >= 0) {
+ throw RubyExceptions.CreateArgumentError("string for Float contains null byte");
+ }
+ return RubyOps.ConvertMutableStringToFloat(context, value);
+ }
+
+ private static void RequireAsciiCompatible(MutableString/*!*/ value) {
+ if (!value.Encoding.IsAsciiIdentity) {
+ throw new EncodingCompatibilityError("ASCII incompatible encoding: " + value.Encoding.Name);
+ }
+ }
+
[RubyMethod("Integer", RubyMethodAttributes.PrivateInstance)]
[RubyMethod("Integer", RubyMethodAttributes.PublicSingleton)]
public static object/*!*/ ToInteger(object self, [NotNull]MutableString/*!*/ value) {
@@ -138,6 +157,7 @@ public static object ToFloat(ConversionStorage/*!*/ floatConversion, obj
[RubyMethod("Integer", RubyMethodAttributes.PrivateInstance)]
[RubyMethod("Integer", RubyMethodAttributes.PublicSingleton)]
public static object/*!*/ ToInteger(object self, [NotNull]MutableString/*!*/ value, [DefaultProtocol]int radix) {
+ RequireAsciiCompatible(value);
var str = value.ConvertToString();
// A negative radix means "this is only a hint, a prefix wins"; zero and the
@@ -162,6 +182,40 @@ public static object ToFloat(ConversionStorage/*!*/ floatConversion, obj
throw InvalidIntegerValue(str);
}
+ // Kernel#Integer and #Float with exception: false (ruby4.rb): a String that does not read
+ // as a number answers nil here, rather than raising an ArgumentError for the prelude to
+ // rescue - which cost ~55 us a failure. An invalid radix still raises, as it does in MRI
+ // whatever exception: says.
+
+ [RubyMethod("__ir_try_Integer__", RubyMethodAttributes.PrivateInstance)]
+ [RubyMethod("__ir_try_Integer__", RubyMethodAttributes.PublicSingleton)]
+ public static object TryToInteger(object self, [NotNull]MutableString/*!*/ value) {
+ return TryToInteger(self, value, 0);
+ }
+
+ [RubyMethod("__ir_try_Integer__", RubyMethodAttributes.PrivateInstance)]
+ [RubyMethod("__ir_try_Integer__", RubyMethodAttributes.PublicSingleton)]
+ public static object TryToInteger(object self, [NotNull]MutableString/*!*/ value, [DefaultProtocol]int radix) {
+ RequireAsciiCompatible(value);
+ var str = value.ConvertToString();
+ if (!HasIntegerDigits(str)) {
+ return null;
+ }
+ if (radix > 0 && (radix < 2 || radix > 36)) {
+ throw RubyExceptions.CreateArgumentError("invalid radix {0}", radix);
+ }
+ object result;
+ return TryParseRubyInteger(str, radix, out result) ? result : null;
+ }
+
+ [RubyMethod("__ir_try_Float__", RubyMethodAttributes.PrivateInstance)]
+ [RubyMethod("__ir_try_Float__", RubyMethodAttributes.PublicSingleton)]
+ public static object TryToFloat(object self, [NotNull]MutableString/*!*/ value) {
+ RequireAsciiCompatible(value);
+ double result;
+ return RubyOps.TryParseRubyFloat(value.ConvertToString(), out result) ? (object)result : null;
+ }
+
#region Kernel#Integer string grammar
// Kernel#Integer is stricter than String#to_i: it consumes the whole string or fails.
diff --git a/Src/Libraries/Extensions/IListOps.cs b/Src/Libraries/Extensions/IListOps.cs
index b50f101c..4f31d754 100644
--- a/Src/Libraries/Extensions/IListOps.cs
+++ b/Src/Libraries/Extensions/IListOps.cs
@@ -2771,6 +2771,22 @@ public static object GetMaximum(ConversionStorage/*!*/ fixnumCast, Comparis
return GetExtremes(fixnumCast, comparisonStorage, comparer, self, count, +1);
}
+ ///
+ /// Array#minmax is [min, max] - MRI's ary_minmax calls the two, so a custom <=> sees
+ /// the same comparisons - and gets their Fixnum fast paths. With a block it is
+ /// Enumerable#minmax, as MRI hands it to super.
+ ///
+ [RubyMethod("minmax")]
+ public static object/*!*/ GetMinMax(CallSiteStorage>/*!*/ each,
+ ComparisonStorage/*!*/ comparisonStorage, BlockParam comparer, IList/*!*/ self) {
+ if (comparer != null) {
+ return Enumerable.GetExtremes(each, comparisonStorage, comparer, self);
+ }
+ object min = GetExtreme(comparisonStorage, null, self, -1);
+ object max = GetExtreme(comparisonStorage, null, self, +1);
+ return new RubyArray(2) { min, max };
+ }
+
private static bool IsBuiltinIntegerComparison(RubyContext/*!*/ context) {
return IsBuiltinMethod(context.IntegerClass, "<=>");
}
diff --git a/Src/Libraries/Initializers.Generated.cs b/Src/Libraries/Initializers.Generated.cs
index f1fe1128..fe84aab7 100644
--- a/Src/Libraries/Initializers.Generated.cs
+++ b/Src/Libraries/Initializers.Generated.cs
@@ -4763,6 +4763,17 @@ private static void LoadKernel_Instance(IronRuby.Builtins.RubyModule/*!*/ module
new Func(IronRuby.Builtins.KernelOps.SetHiddenInstanceVariable)
);
+ DefineLibraryMethod(module, "__ir_try_Float__", 0x52,
+ 0x00000002U,
+ new Func(IronRuby.Builtins.KernelOps.TryToFloat)
+ );
+
+ DefineLibraryMethod(module, "__ir_try_Integer__", 0x52,
+ 0x00000002U, 0x00020002U,
+ new Func(IronRuby.Builtins.KernelOps.TryToInteger),
+ new Func(IronRuby.Builtins.KernelOps.TryToInteger)
+ );
+
DefineLibraryMethod(module, "__method__", 0x52,
0x00000000U,
new Func(IronRuby.Builtins.KernelOps.GetCurrentMethodName)
@@ -4918,8 +4929,9 @@ private static void LoadKernel_Instance(IronRuby.Builtins.RubyModule/*!*/ module
);
DefineLibraryMethod(module, "Float", 0x52,
- 0x00000000U,
- new Func, System.Object, System.Object, System.Object>(IronRuby.Builtins.KernelOps.ToFloat)
+ 0x00000000U, 0x00000004U,
+ new Func, System.Object, System.Object, System.Object>(IronRuby.Builtins.KernelOps.ToFloat),
+ new Func(IronRuby.Builtins.KernelOps.ToFloat)
);
DefineLibraryMethod(module, "fork", 0x52,
@@ -5313,6 +5325,17 @@ private static void LoadKernel_Class(IronRuby.Builtins.RubyModule/*!*/ module) {
new Func(IronRuby.Builtins.KernelOps.GetCurrentCalleeName)
);
+ DefineLibraryMethod(module, "__ir_try_Float__", 0x61,
+ 0x00000002U,
+ new Func(IronRuby.Builtins.KernelOps.TryToFloat)
+ );
+
+ DefineLibraryMethod(module, "__ir_try_Integer__", 0x61,
+ 0x00000002U, 0x00020002U,
+ new Func(IronRuby.Builtins.KernelOps.TryToInteger),
+ new Func(IronRuby.Builtins.KernelOps.TryToInteger)
+ );
+
DefineLibraryMethod(module, "__method__", 0x61,
0x00000000U,
new Func(IronRuby.Builtins.KernelOps.GetCurrentMethodName)
@@ -5394,8 +5417,9 @@ private static void LoadKernel_Class(IronRuby.Builtins.RubyModule/*!*/ module) {
);
DefineLibraryMethod(module, "Float", 0x61,
- 0x00000000U,
- new Func, System.Object, System.Object, System.Object>(IronRuby.Builtins.KernelOps.ToFloat)
+ 0x00000000U, 0x00000004U,
+ new Func, System.Object, System.Object, System.Object>(IronRuby.Builtins.KernelOps.ToFloat),
+ new Func(IronRuby.Builtins.KernelOps.ToFloat)
);
DefineLibraryMethod(module, "fork", 0x61,
@@ -9524,6 +9548,11 @@ private static void LoadSystem__Collections__IList_Instance(IronRuby.Builtins.Ru
new Func, IronRuby.Runtime.ComparisonStorage, IronRuby.Runtime.BlockParam, System.Collections.IList, System.Object, System.Object>(IronRuby.Builtins.IListOps.GetMinimum)
);
+ DefineLibraryMethod(module, "minmax", 0x51,
+ 0x00000000U,
+ new Func>, IronRuby.Runtime.ComparisonStorage, IronRuby.Runtime.BlockParam, System.Collections.IList, System.Object>(IronRuby.Builtins.IListOps.GetMinMax)
+ );
+
DefineLibraryMethod(module, "nitems", 0x51,
0x00000000U,
new Func(IronRuby.Builtins.IListOps.NumberOfNonNilItems)
@@ -13471,7 +13500,8 @@ private static void LoadStringIO_Instance(IronRuby.Builtins.RubyModule/*!*/ modu
);
DefineLibraryMethod(module, "gets", 0x11,
- 0x00400000U,
+ 0x00000000U, 0x00400000U,
+ new Func(IronRuby.StandardLibrary.StringIO.StringIO.Gets),
new Func>, IronRuby.Runtime.ConversionStorage, IronRuby.Runtime.ConversionStorage, IronRuby.Runtime.RubyScope, IronRuby.StandardLibrary.StringIO.StringIO, System.Object, System.Object, System.Collections.Generic.IDictionary, IronRuby.Builtins.MutableString>(IronRuby.StandardLibrary.StringIO.StringIO.Gets)
);
diff --git a/Src/Libraries/StringIO/StringIO.cs b/Src/Libraries/StringIO/StringIO.cs
index e418315b..bd53aa8b 100644
--- a/Src/Libraries/StringIO/StringIO.cs
+++ b/Src/Libraries/StringIO/StringIO.cs
@@ -1010,9 +1010,17 @@ private static MutableString Chomp(MutableString line, MutableString separator,
}
int separatorLength = separator.GetByteCount();
- if (separatorLength == 0 || length < separatorLength || !line.EndsWith(separator)) {
+ if (separatorLength == 0 || length < separatorLength) {
return line;
}
+ // The separator is compared as bytes - the line is a slice of the content and its
+ // separator was found as bytes - and the line, which nothing else holds yet, is cut
+ // where it is rather than sliced into another string.
+ for (int i = 0; i < separatorLength; i++) {
+ if (line.GetByte(length - separatorLength + i) != separator.GetByte(i)) {
+ return line;
+ }
+ }
int cut = length - separatorLength;
// A line ended by "\n" gives up the "\r" in front of it too - MRI chomps the pair -
@@ -1020,22 +1028,17 @@ private static MutableString Chomp(MutableString line, MutableString separator,
if (separatorLength == 1 && separator.GetByte(0) == (byte)'\n' && cut > 0 && line.GetByte(cut - 1) == (byte)'\r') {
cut--;
}
- return line.GetSlice(0, cut);
+ line.SetByteCount(cut);
+ return line;
}
+ // The bare call - nearly every gets - binds without the optional arguments' conversions.
[RubyMethod("gets")]
- public static MutableString Gets(ConversionStorage>/*!*/ toHash,
- ConversionStorage/*!*/ toStr, ConversionStorage/*!*/ toInt,
- RubyScope/*!*/ scope, StringIO/*!*/ self,
- [Optional]object separatorOrLimit, [Optional]object limitOrOptions,
- [DefaultParameterValue(null), DefaultProtocol]IDictionary