diff --git a/Jint.Tests.Test262/Test262Harness.settings.json b/Jint.Tests.Test262/Test262Harness.settings.json index 767943d29b..63d84c6d58 100644 --- a/Jint.Tests.Test262/Test262Harness.settings.json +++ b/Jint.Tests.Test262/Test262Harness.settings.json @@ -1,16 +1,18 @@ { - "SuiteGitSha": "9704d7f22f6342d6c4753ab9a8d62d6725de8c4e", + "SuiteGitSha": "9437cab774ab2f22c5cb971b11b8512eca705721", //"SuiteDirectory": "//mnt/c/work/test262", "TargetPath": "./Generated", "Namespace": "Jint.Tests.Test262", "Parallel": true, "ExcludedFeatures": [ "Array.fromAsync", + "arraybuffer-transfer", "async-iteration", "Atomics", "decorators", "generators", "import-assertions", + "iterator-helpers", "regexp-duplicate-named-groups", "regexp-lookbehind", "regexp-unicode-property-escapes", diff --git a/Jint/JsValueExtensions.cs b/Jint/JsValueExtensions.cs index 76d7abdb51..d84007e989 100644 --- a/Jint/JsValueExtensions.cs +++ b/Jint/JsValueExtensions.cs @@ -582,5 +582,16 @@ internal static BigInteger ToBigInteger(this JsValue value, Engine engine) return default; } } + + internal static ICallable GetCallable(this JsValue source, Realm realm) + { + if (source is ICallable callable) + { + return callable; + } + + ExceptionHelper.ThrowTypeError(realm, "Argument must be callable"); + return null; + } } } diff --git a/Jint/Native/Array/ArrayPrototype.cs b/Jint/Native/Array/ArrayPrototype.cs index 7c0ce6ed90..299223088a 100644 --- a/Jint/Native/Array/ArrayPrototype.cs +++ b/Jint/Native/Array/ArrayPrototype.cs @@ -1,7 +1,6 @@ using System.Linq; using Jint.Collections; using Jint.Native.Iterator; -using Jint.Native.Map; using Jint.Native.Number; using Jint.Native.Object; using Jint.Native.Symbol; @@ -37,7 +36,7 @@ internal ArrayPrototype( protected override void Initialize() { const PropertyFlag PropertyFlags = PropertyFlag.Writable | PropertyFlag.Configurable; - var properties = new PropertyDictionary(40, checkExistingKeys: false) + var properties = new PropertyDictionary(38, checkExistingKeys: false) { ["constructor"] = new PropertyDescriptor(_constructor, PropertyFlag.NonEnumerable), @@ -55,8 +54,6 @@ protected override void Initialize() ["flat"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "flat", Flat, 0, PropertyFlag.Configurable), PropertyFlags), ["flatMap"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "flatMap", FlatMap, 1, PropertyFlag.Configurable), PropertyFlags), ["forEach"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "forEach", ForEach, 1, PropertyFlag.Configurable), PropertyFlags), - ["group"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "group", Group, 1, PropertyFlag.Configurable), PropertyFlags), - ["groupToMap"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "groupToMap", GroupToMap, 1, PropertyFlag.Configurable), PropertyFlags), ["includes"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "includes", Includes, 1, PropertyFlag.Configurable), PropertyFlags), ["indexOf"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "indexOf", IndexOf, 1, PropertyFlag.Configurable), PropertyFlags), ["join"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "join", Join, 1, PropertyFlag.Configurable), PropertyFlags), @@ -105,10 +102,6 @@ protected override void Initialize() unscopables.FastSetDataProperty("findLastIndex", JsBoolean.True); unscopables.FastSetDataProperty("flat", JsBoolean.True); unscopables.FastSetDataProperty("flatMap", JsBoolean.True); - unscopables.FastSetDataProperty("group", JsBoolean.True); - unscopables.FastSetDataProperty("groupBy", JsBoolean.True); - unscopables.FastSetDataProperty("groupByToMap", JsBoolean.True); - unscopables.FastSetDataProperty("groupToMap", JsBoolean.True); unscopables.FastSetDataProperty("includes", JsBoolean.True); unscopables.FastSetDataProperty("keys", JsBoolean.True); unscopables.FastSetDataProperty("toReversed", JsBoolean.True); @@ -1655,76 +1648,6 @@ public JsValue Pop(JsValue thisObject, JsValue[] arguments) return element; } - /// - /// https://tc39.es/proposal-array-grouping/#sec-array.prototype.group - /// - private JsValue Group(JsValue thisObject, JsValue[] arguments) - { - var grouping = BuildArrayGrouping(thisObject, arguments, mapMode: false); - - var obj = OrdinaryObjectCreate(_engine, null); - foreach (var pair in grouping) - { - obj.FastSetProperty(pair.Key, new PropertyDescriptor(pair.Value, PropertyFlag.ConfigurableEnumerableWritable)); - } - - return obj; - } - - /// - /// https://tc39.es/proposal-array-grouping/#sec-array.prototype.grouptomap - /// - private JsValue GroupToMap(JsValue thisObject, JsValue[] arguments) - { - var grouping = BuildArrayGrouping(thisObject, arguments, mapMode: true); - var map = (MapInstance) Construct(_realm.Intrinsics.Map); - foreach (var pair in grouping) - { - map.MapSet(pair.Key, pair.Value); - } - - return map; - } - - private Dictionary BuildArrayGrouping(JsValue thisObject, JsValue[] arguments, bool mapMode) - { - var o = ArrayOperations.For(_realm, thisObject); - var len = o.GetLongLength(); - var callbackfn = arguments.At(0); - var callable = GetCallable(callbackfn); - var thisArg = arguments.At(1); - - var result = new Dictionary(); - var args = _engine._jsValueArrayPool.RentArray(3); - args[2] = o.Target; - for (uint k = 0; k < len; k++) - { - var kValue = o.Get(k); - args[0] = kValue; - args[1] = k; - - var value = callable.Call(thisArg, args); - JsValue key; - if (mapMode) - { - key = (value as JsNumber)?.IsNegativeZero() == true ? JsNumber.PositiveZero : value; - } - else - { - key = TypeConverter.ToPropertyKey(value); - } - if (!result.TryGetValue(key, out var list)) - { - result[key] = list = new JsArray(_engine); - } - - list.SetIndexValue(list.GetLength(), kValue, updateLength: true); - } - - _engine._jsValueArrayPool.ReturnArray(args); - return result; - } - private object[] CreateBackingArray(ulong length) { ValidateArrayLength(length); diff --git a/Jint/Native/GroupByHelper.cs b/Jint/Native/GroupByHelper.cs new file mode 100644 index 0000000000..252e82132b --- /dev/null +++ b/Jint/Native/GroupByHelper.cs @@ -0,0 +1,75 @@ +using Jint.Native.Array; +using Jint.Native.Iterator; +using Jint.Runtime; + +namespace Jint.Native; + +internal static class GroupByHelper +{ + internal static Dictionary GroupBy( + Engine engine, + Realm realm, + JsValue items, + JsValue callbackfn, + bool mapMode) + { + var callable = callbackfn.GetCallable(realm); + var groups = new Dictionary(); + var iteratorRecord = items.GetIterator(realm); + new GroupByProtocol(engine, groups, iteratorRecord, callable, mapMode).Execute(); + return groups; + } + + private sealed class GroupByProtocol : IteratorProtocol + { + private readonly Engine _engine; + private readonly Dictionary _result; + private readonly ICallable _callable; + private readonly bool _mapMode; + private ulong _k; + private readonly JsValue[] _callArgs = new JsValue[2]; + + public GroupByProtocol( + Engine engine, + Dictionary result, + IteratorInstance iterator, + ICallable callable, + bool mapMode) : base(engine, iterator, 0) + { + _engine = engine; + _result = result; + _callable = callable; + _mapMode = mapMode; + } + + protected override void ProcessItem(JsValue[] args, JsValue currentValue) + { + if (_k >= ArrayOperations.MaxArrayLength) + { + ExceptionHelper.ThrowTypeError(_engine.Realm); + } + + _callArgs[0] = currentValue; + _callArgs[1] = _k; + + var value = _callable.Call(JsValue.Undefined, _callArgs); + JsValue key; + if (_mapMode) + { + key = (value as JsNumber)?.IsNegativeZero() == true ? JsNumber.PositiveZero : value; + } + else + { + key = TypeConverter.ToPropertyKey(value); + } + + if (!_result.TryGetValue(key, out var list)) + { + _result[key] = list = new JsArray(_engine); + } + + list.Push(currentValue); + _k++; + } + } +} diff --git a/Jint/Native/Map/MapConstructor.cs b/Jint/Native/Map/MapConstructor.cs index 648379be6b..bc26e46aef 100644 --- a/Jint/Native/Map/MapConstructor.cs +++ b/Jint/Native/Map/MapConstructor.cs @@ -30,6 +30,13 @@ internal MapConstructor( protected override void Initialize() { + const PropertyFlag PropertyFlags = PropertyFlag.Writable | PropertyFlag.Configurable; + var properties = new PropertyDictionary(1, checkExistingKeys: false) + { + ["groupBy"] = new(new ClrFunctionInstance(Engine, "groupBy", GroupBy, 2, PropertyFlag.Configurable), PropertyFlags), + }; + SetProperties(properties); + var symbols = new SymbolDictionary(1) { [GlobalSymbolRegistry.Species] = new GetSetPropertyDescriptor(get: new ClrFunctionInstance(_engine, "get [Symbol.species]", Species, 0, PropertyFlag.Configurable), set: Undefined, PropertyFlag.Configurable) @@ -67,4 +74,22 @@ public override ObjectInstance Construct(JsValue[] arguments, JsValue newTarget) return map; } + + + /// + /// https://tc39.es/proposal-array-grouping/#sec-map.groupby + /// + private JsValue GroupBy(JsValue thisObject, JsValue[] arguments) + { + var items = arguments.At(0); + var callbackfn = arguments.At(1); + var grouping = GroupByHelper.GroupBy(_engine, _realm, items, callbackfn, mapMode: true); + var map = (MapInstance) Construct(_realm.Intrinsics.Map); + foreach (var pair in grouping) + { + map.MapSet(pair.Key, pair.Value); + } + + return map; + } } diff --git a/Jint/Native/Object/ObjectConstructor.cs b/Jint/Native/Object/ObjectConstructor.cs index 485bad39cc..0b9a17513c 100644 --- a/Jint/Native/Object/ObjectConstructor.cs +++ b/Jint/Native/Object/ObjectConstructor.cs @@ -26,32 +26,33 @@ protected override void Initialize() { _prototype = _realm.Intrinsics.Function.PrototypeObject; - const PropertyFlag propertyFlags = PropertyFlag.Configurable | PropertyFlag.Writable; - const PropertyFlag lengthFlags = PropertyFlag.Configurable; - var properties = new PropertyDictionary(15, checkExistingKeys: false) + const PropertyFlag PropertyFlags = PropertyFlag.Configurable | PropertyFlag.Writable; + const PropertyFlag LengthFlags = PropertyFlag.Configurable; + var properties = new PropertyDictionary(16, checkExistingKeys: false) { - ["assign"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "assign", Assign, 2, lengthFlags), propertyFlags), - ["entries"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "entries", Entries, 1, lengthFlags), propertyFlags), - ["fromEntries"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "fromEntries", FromEntries, 1, lengthFlags), propertyFlags), - ["getPrototypeOf"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "getPrototypeOf", GetPrototypeOf, 1), propertyFlags), - ["getOwnPropertyDescriptor"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "getOwnPropertyDescriptor", GetOwnPropertyDescriptor, 2, lengthFlags), propertyFlags), - ["getOwnPropertyDescriptors"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "getOwnPropertyDescriptors", GetOwnPropertyDescriptors, 1, lengthFlags), propertyFlags), - ["getOwnPropertyNames"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "getOwnPropertyNames", GetOwnPropertyNames, 1), propertyFlags), - ["getOwnPropertySymbols"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "getOwnPropertySymbols", GetOwnPropertySymbols, 1, lengthFlags), propertyFlags), - ["create"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "create", Create, 2), propertyFlags), - ["defineProperty"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "defineProperty", DefineProperty, 3), propertyFlags), - ["defineProperties"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "defineProperties", DefineProperties, 2), propertyFlags), - ["is"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "is", Is, 2, lengthFlags), propertyFlags), - ["seal"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "seal", Seal, 1, lengthFlags), propertyFlags), - ["freeze"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "freeze", Freeze, 1), propertyFlags), - ["preventExtensions"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "preventExtensions", PreventExtensions, 1), propertyFlags), - ["isSealed"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "isSealed", IsSealed, 1), propertyFlags), - ["isFrozen"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "isFrozen", IsFrozen, 1), propertyFlags), - ["isExtensible"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "isExtensible", IsExtensible, 1), propertyFlags), - ["keys"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "keys", Keys, 1, lengthFlags), propertyFlags), - ["values"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "values", Values, 1, lengthFlags), propertyFlags), - ["setPrototypeOf"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "setPrototypeOf", SetPrototypeOf, 2, lengthFlags), propertyFlags), - ["hasOwn"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "hasOwn", HasOwn, 2, lengthFlags), propertyFlags), + ["assign"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "assign", Assign, 2, LengthFlags), PropertyFlags), + ["entries"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "entries", Entries, 1, LengthFlags), PropertyFlags), + ["fromEntries"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "fromEntries", FromEntries, 1, LengthFlags), PropertyFlags), + ["getPrototypeOf"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "getPrototypeOf", GetPrototypeOf, 1), PropertyFlags), + ["getOwnPropertyDescriptor"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "getOwnPropertyDescriptor", GetOwnPropertyDescriptor, 2, LengthFlags), PropertyFlags), + ["getOwnPropertyDescriptors"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "getOwnPropertyDescriptors", GetOwnPropertyDescriptors, 1, LengthFlags), PropertyFlags), + ["getOwnPropertyNames"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "getOwnPropertyNames", GetOwnPropertyNames, 1), PropertyFlags), + ["getOwnPropertySymbols"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "getOwnPropertySymbols", GetOwnPropertySymbols, 1, LengthFlags), PropertyFlags), + ["groupBy"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "groupBy", GroupBy, 2, PropertyFlag.Configurable), PropertyFlags), + ["create"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "create", Create, 2), PropertyFlags), + ["defineProperty"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "defineProperty", DefineProperty, 3), PropertyFlags), + ["defineProperties"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "defineProperties", DefineProperties, 2), PropertyFlags), + ["is"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "is", Is, 2, LengthFlags), PropertyFlags), + ["seal"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "seal", Seal, 1, LengthFlags), PropertyFlags), + ["freeze"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "freeze", Freeze, 1), PropertyFlags), + ["preventExtensions"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "preventExtensions", PreventExtensions, 1), PropertyFlags), + ["isSealed"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "isSealed", IsSealed, 1), PropertyFlags), + ["isFrozen"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "isFrozen", IsFrozen, 1), PropertyFlags), + ["isExtensible"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "isExtensible", IsExtensible, 1), PropertyFlags), + ["keys"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "keys", Keys, 1, LengthFlags), PropertyFlags), + ["values"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "values", Values, 1, LengthFlags), PropertyFlags), + ["setPrototypeOf"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "setPrototypeOf", SetPrototypeOf, 2, LengthFlags), PropertyFlags), + ["hasOwn"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "hasOwn", HasOwn, 2, LengthFlags), PropertyFlags), }; SetProperties(properties); } @@ -525,6 +526,24 @@ private JsValue Values(JsValue thisObject, JsValue[] arguments) return o.EnumerableOwnPropertyNames(EnumerableOwnPropertyNamesKind.Value); } + /// + /// https://tc39.es/proposal-array-grouping/#sec-object.groupby + /// + private JsValue GroupBy(JsValue thisObject, JsValue[] arguments) + { + var items = arguments.At(0); + var callbackfn = arguments.At(1); + var grouping = GroupByHelper.GroupBy(_engine, _realm, items, callbackfn, mapMode: false); + + var obj = OrdinaryObjectCreate(_engine, null); + foreach (var pair in grouping) + { + obj.FastSetProperty(pair.Key, new PropertyDescriptor(pair.Value, PropertyFlag.ConfigurableEnumerableWritable)); + } + + return obj; + } + private sealed class CreateDataPropertyOnObject : ICallable { internal static readonly CreateDataPropertyOnObject Instance = new(); diff --git a/Jint/Native/Object/ObjectInstance.cs b/Jint/Native/Object/ObjectInstance.cs index 8db1094998..84d25dec63 100644 --- a/Jint/Native/Object/ObjectInstance.cs +++ b/Jint/Native/Object/ObjectInstance.cs @@ -1173,16 +1173,7 @@ bool TryGetValue(ulong idx, out JsValue jsValue) return false; } - internal ICallable GetCallable(JsValue source) - { - if (source is ICallable callable) - { - return callable; - } - - ExceptionHelper.ThrowTypeError(_engine.Realm, "Argument must be callable"); - return null; - } + internal ICallable GetCallable(JsValue source) => source.GetCallable(_engine.Realm); internal bool IsConcatSpreadable { diff --git a/NuGet.config b/NuGet.config index 32b42fc44c..c9af0eb814 100644 --- a/NuGet.config +++ b/NuGet.config @@ -3,7 +3,9 @@ + diff --git a/README.md b/README.md index 55e8215b80..edaaf5a8df 100644 --- a/README.md +++ b/README.md @@ -108,7 +108,7 @@ Following features are supported in version 3.x. #### ECMAScript Stage 3 (no version yet) -- ✔ Array.group and Array.groupToMap +- ✔ Array Grouping - `Object.groupBy` and `Map.groupBy` - ✔ ShadowRealm #### Other