Browse Source

Implementing stable sort (#809)

Fixes #808
Sébastien Ros 4 years ago
parent
commit
37ae0f84f8

+ 40 - 0
Jint.Tests/Runtime/ArrayTests.cs

@@ -74,5 +74,45 @@ namespace Jint.Tests.Runtime
             var length = (int) array.Length;
             var length = (int) array.Length;
             Assert.Equal(0, length);
             Assert.Equal(0, length);
         }
         }
+
+        [Fact]
+        public void ArraySortIsStable()
+        {
+            const string code = @"
+                var items = [
+                    { name: 'Edward', value: 0 },
+                    { name: 'Sharpe', value: 0 },
+                    { name: 'And', value: 0 },
+                    { name: 'The', value: 1 },
+                    { name: 'Magnetic', value: 0 },
+                    { name: 'Zeros', value: 0 }
+                ];
+
+                // sort by value
+                function compare(a, b) {
+                    return a.value - b.value;
+                }
+
+                var a = items.sort();
+
+                assert(a[0].name == 'Edward');
+                assert(a[1].name == 'Sharpe');
+                assert(a[2].name == 'And');
+                assert(a[3].name == 'The');
+                assert(a[4].name == 'Magnetic');
+                assert(a[5].name == 'Zeros');
+
+                var a = items.sort(compare);
+
+                assert(a[0].name == 'Edward');
+                assert(a[1].name == 'Sharpe');
+                assert(a[2].name == 'And');
+                assert(a[3].name == 'Magnetic');
+                assert(a[4].name == 'Zeros');
+                assert(a[5].name == 'The');
+            ";
+
+            _engine.Execute(code);
+        }
     }
     }
 }
 }

+ 66 - 1
Jint/Native/Array/ArrayOperations.cs

@@ -1,3 +1,5 @@
+using System.Collections;
+using System.Collections.Generic;
 using Jint.Native.Number;
 using Jint.Native.Number;
 using Jint.Native.Object;
 using Jint.Native.Object;
 using Jint.Runtime;
 using Jint.Runtime;
@@ -5,7 +7,7 @@ using Jint.Runtime.Descriptors;
 
 
 namespace Jint.Native.Array
 namespace Jint.Native.Array
 {
 {
-    internal abstract class ArrayOperations
+    internal abstract class ArrayOperations : IEnumerable<JsValue>
     {
     {
         protected internal const ulong MaxArrayLength = 4294967295;
         protected internal const ulong MaxArrayLength = 4294967295;
         protected internal const ulong MaxArrayLikeLength = NumberConstructor.MaxSafeInteger;
         protected internal const ulong MaxArrayLikeLength = NumberConstructor.MaxSafeInteger;
@@ -68,6 +70,69 @@ namespace Jint.Native.Array
 
 
         public abstract void DeletePropertyOrThrow(ulong index);
         public abstract void DeletePropertyOrThrow(ulong index);
 
 
+        internal ArrayLikeIterator GetEnumerator() => new ArrayLikeIterator(this);
+
+        IEnumerator<JsValue> IEnumerable<JsValue>.GetEnumerator() => new ArrayLikeIterator(this);
+
+        IEnumerator IEnumerable.GetEnumerator() => new ArrayLikeIterator(this);
+
+        internal sealed class ArrayLikeIterator : IEnumerator<JsValue>
+        {
+            private readonly ArrayOperations _obj;
+            private ulong _current;
+            private bool _initialized;
+            private readonly uint _length;
+
+            public ArrayLikeIterator(ArrayOperations obj)
+            {
+                _obj = obj;
+                _length = obj.GetLength();
+
+                Reset();
+            }
+
+            public JsValue Current 
+            {
+                get 
+                {
+                    if (!_initialized)
+                    {
+                        return null;
+                    }
+                    else
+                    {
+                        return _obj.TryGetValue(_current, out var temp) ? temp : null;
+                    }
+                }
+            }
+
+            object IEnumerator.Current => Current;
+
+            public void Dispose()
+            {
+            }
+
+            public bool MoveNext()
+            {
+                if (!_initialized)
+                {
+                    _initialized = true;
+                }
+                else
+                {
+                    _current++;
+                }
+
+                return _current < _length;
+            }
+
+            public void Reset()
+            {
+                _initialized = false;
+                _current = 0;
+            }
+        }
+
         private sealed class ObjectInstanceOperations : ArrayOperations<ObjectInstance>
         private sealed class ObjectInstanceOperations : ArrayOperations<ObjectInstance>
         {
         {
             public ObjectInstanceOperations(ObjectInstance target) : base(target)
             public ObjectInstanceOperations(ObjectInstance target) : base(target)

+ 101 - 70
Jint/Native/Array/ArrayPrototype.cs

@@ -1,5 +1,6 @@
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
+using System.Linq;
 using Jint.Collections;
 using Jint.Collections;
 using Jint.Native.Object;
 using Jint.Native.Object;
 using Jint.Native.Symbol;
 using Jint.Native.Symbol;
@@ -801,91 +802,30 @@ namespace Jint.Native.Array
             if (len <= 1)
             if (len <= 1)
             {
             {
                 return obj.Target;
                 return obj.Target;
-            }
+            }            
 
 
-            int Comparer(JsValue x, JsValue y)
+            // don't eat inner exceptions
+            try
             {
             {
-                if (ReferenceEquals(x, null))
-                {
-                    return 1;
-                }
-
-                if (ReferenceEquals(y, null))
-                {
-                    return -1;
-                }
+                var array = obj.OrderBy(x => x, ArrayComparer.WithFunction(compareFn)).ToArray();
 
 
-                var xUndefined = x.IsUndefined();
-                var yUndefined = y.IsUndefined();
-                if (xUndefined && yUndefined)
+                for (uint i = 0; i < (uint) array.Length; ++i)
                 {
                 {
-                    return 0;
-                }
-
-                if (xUndefined)
-                {
-                    return 1;
-                }
-
-                if (yUndefined)
-                {
-                    return -1;
-                }
-
-                if (compareFn != null)
-                {
-                    var s = TypeConverter.ToNumber(compareFn.Call(Undefined, new[] {x, y}));
-                    if (s < 0)
+                    if (!ReferenceEquals(array[i], null))
                     {
                     {
-                        return -1;
+                        obj.Set(i, array[i], updateLength: false, throwOnError: false);
                     }
                     }
-
-                    if (s > 0)
+                    else
                     {
                     {
-                        return 1;
+                        obj.DeletePropertyOrThrow(i);
                     }
                     }
-
-                    return 0;
-                }
-
-                var xString = TypeConverter.ToString(x);
-                var yString = TypeConverter.ToString(y);
-
-                var r = CompareOrdinal(xString, yString);
-                return r;
             }
             }
-
-            var array = new JsValue[len];
-            for (uint i = 0; i < (uint) array.Length; ++i)
-            {
-                var value = obj.TryGetValue(i, out var temp)
-                    ? temp
-                    : null;
-                array[i] = value;
-            }
-
-            // don't eat inner exceptions
-            try
-            {
-                System.Array.Sort(array, Comparer);
             }
             }
             catch (InvalidOperationException e)
             catch (InvalidOperationException e)
             {
             {
                 throw e.InnerException;
                 throw e.InnerException;
             }
             }
 
 
-            for (uint i = 0; i < (uint) array.Length; ++i)
-            {
-                if (!ReferenceEquals(array[i], null))
-                {
-                    obj.Set(i, array[i], updateLength: false, throwOnError: false);
-                }
-                else
-                {
-                    obj.DeletePropertyOrThrow(i);
-                }
-            }
-
             return obj.Target;
             return obj.Target;
         }
         }
 
 
@@ -1290,5 +1230,96 @@ namespace Jint.Native.Array
             o.SetLength(len);
             o.SetLength(len);
             return element;
             return element;
         }
         }
+
+        private sealed class ArrayComparer : IComparer<JsValue>
+        {
+            /// <summary>
+            /// Default instance without any compare function.
+            /// </summary>
+            public static ArrayComparer Default = new ArrayComparer(null);
+            public static ArrayComparer WithFunction(ICallable compare)
+            {
+                if (compare == null)
+                {
+                    return Default;
+                }
+
+                return new ArrayComparer(compare);
+            }
+
+            private readonly ICallable _compare;
+            private readonly JsValue[] _comparableArray = new JsValue[2];
+
+            private ArrayComparer(ICallable compare)
+            {
+                _compare = compare;
+            }
+
+            public int Compare(JsValue x, JsValue y)
+            {
+                var xIsNull = ReferenceEquals(x, null);
+                var yIsNull = ReferenceEquals(y, null);
+
+                if (xIsNull)
+                {
+                    if (yIsNull)
+                    {
+                        return 0;
+                    }
+                    
+                    return 1;
+                }
+                else
+                {
+                    if (yIsNull)
+                    {
+                        return -1;
+                    }
+                }
+
+                var xUndefined = x.IsUndefined();
+                var yUndefined = y.IsUndefined();
+                if (xUndefined && yUndefined)
+                {
+                    return 0;
+                }
+
+                if (xUndefined)
+                {
+                    return 1;
+                }
+
+                if (yUndefined)
+                {
+                    return -1;
+                }
+
+                if (_compare != null)
+                {
+                    _comparableArray[0] = x;
+                    _comparableArray[1] = y;
+
+                    var s = TypeConverter.ToNumber(_compare.Call(Undefined, _comparableArray));
+                    if (s < 0)
+                    {
+                        return -1;
+                    }
+
+                    if (s > 0)
+                    {
+                        return 1;
+                    }
+
+                    return 0;
+                }
+
+                var xString = TypeConverter.ToString(x);
+                var yString = TypeConverter.ToString(y);
+
+                var r = CompareOrdinal(xString, yString);
+                return r;
+            }
+        }
     }
     }
+
 }
 }