using Jint.Native.Object; using Jint.Native.Symbol; using Jint.Runtime; using Jint.Runtime.Descriptors; using Jint.Runtime.Interop; namespace Jint.Native.Set; /// /// https://www.ecma-international.org/ecma-262/6.0/#sec-set-objects /// internal sealed class SetPrototype : Prototype { private readonly SetConstructor _constructor; internal SetPrototype( Engine engine, Realm realm, SetConstructor setConstructor, ObjectPrototype objectPrototype) : base(engine, realm) { _prototype = objectPrototype; _constructor = setConstructor; } protected override void Initialize() { var properties = new PropertyDictionary(12, checkExistingKeys: false) { ["length"] = new(0, PropertyFlag.Configurable), ["constructor"] = new(_constructor, PropertyFlag.NonEnumerable), ["add"] = new(new ClrFunction(Engine, "add", Add, 1, PropertyFlag.Configurable), PropertyFlag.NonEnumerable), ["clear"] = new(new ClrFunction(Engine, "clear", Clear, 0, PropertyFlag.Configurable), PropertyFlag.NonEnumerable), ["delete"] = new(new ClrFunction(Engine, "delete", Delete, 1, PropertyFlag.Configurable), PropertyFlag.NonEnumerable), ["difference"] = new(new ClrFunction(Engine, "difference", Difference, 1, PropertyFlag.Configurable), PropertyFlag.NonEnumerable), ["entries"] = new(new ClrFunction(Engine, "entries", Entries, 0, PropertyFlag.Configurable), PropertyFlag.NonEnumerable), ["forEach"] = new(new ClrFunction(Engine, "forEach", ForEach, 1, PropertyFlag.Configurable), PropertyFlag.NonEnumerable), ["has"] = new(new ClrFunction(Engine, "has", Has, 1, PropertyFlag.Configurable), PropertyFlag.NonEnumerable), ["intersection"] = new(new ClrFunction(Engine, "intersection", Intersection, 1, PropertyFlag.Configurable), PropertyFlag.NonEnumerable), ["isDisjointFrom"] = new(new ClrFunction(Engine, "isDisjointFrom", IsDisjointFrom, 1, PropertyFlag.Configurable), PropertyFlag.NonEnumerable), ["isSubsetOf"] = new(new ClrFunction(Engine, "isSubsetOf", IsSubsetOf, 1, PropertyFlag.Configurable), PropertyFlag.NonEnumerable), ["isSupersetOf"] = new(new ClrFunction(Engine, "isSupersetOf", IsSupersetOf, 1, PropertyFlag.Configurable), PropertyFlag.NonEnumerable), ["keys"] = new(new ClrFunction(Engine, "keys", Values, 0, PropertyFlag.Configurable), PropertyFlag.NonEnumerable), ["values"] = new(new ClrFunction(Engine, "values", Values, 0, PropertyFlag.Configurable), PropertyFlag.NonEnumerable), ["size"] = new GetSetPropertyDescriptor(get: new ClrFunction(Engine, "get size", Size, 0, PropertyFlag.Configurable), set: null, PropertyFlag.Configurable), ["symmetricDifference"] = new(new ClrFunction(Engine, "symmetricDifference", SymmetricDifference, 1, PropertyFlag.Configurable), PropertyFlag.NonEnumerable), ["union"] = new(new ClrFunction(Engine, "union", Union, 1, PropertyFlag.Configurable), PropertyFlag.NonEnumerable) }; SetProperties(properties); var symbols = new SymbolDictionary(2) { [GlobalSymbolRegistry.Iterator] = new(new ClrFunction(Engine, "iterator", Values, 1, PropertyFlag.Configurable), true, false, true), [GlobalSymbolRegistry.ToStringTag] = new("Set", false, false, true) }; SetSymbols(symbols); } private JsNumber Size(JsValue thisObject, JsCallArguments arguments) { AssertSetInstance(thisObject); return JsNumber.Create(0); } private JsValue Add(JsValue thisObject, JsCallArguments arguments) { var set = AssertSetInstance(thisObject); var value = arguments.At(0); if (value is JsNumber number && number.IsNegativeZero()) { value = JsNumber.PositiveZero; } set.Add(value); return thisObject; } private JsValue Clear(JsValue thisObject, JsCallArguments arguments) { var set = AssertSetInstance(thisObject); set.Clear(); return Undefined; } private JsBoolean Delete(JsValue thisObject, JsCallArguments arguments) { var set = AssertSetInstance(thisObject); return set.Delete(arguments.At(0)) ? JsBoolean.True : JsBoolean.False; } private JsSet Difference(JsValue thisObject, JsCallArguments arguments) { var set = AssertSetInstance(thisObject); var other = arguments.At(0); var otherRec = GetSetRecord(other); var resultSetData = new JsSet(_engine, new OrderedSet(set._set._set)); if (set.Size <= otherRec.Size) { if (other is JsSet otherSet) { // fast path var result = new HashSet(set._set._set, SameValueZeroComparer.Instance); result.ExceptWith(otherSet._set._set); return new JsSet(_engine, new OrderedSet(result)); } var index = 0; var args = new JsValue[1]; while (index < set.Size) { var e = resultSetData[index]; if (e is not null) { args[0] = e; var inOther = TypeConverter.ToBoolean(otherRec.Has.Call(otherRec.Set, args)); if (inOther) { resultSetData.Delete(e); index--; } } index++; } return resultSetData; } var keysIter = otherRec.Set.GetIteratorFromMethod(_realm, otherRec.Keys); while (true) { if (!keysIter.TryIteratorStep(out var next)) { break; } var nextValue = next.Get(CommonProperties.Value); if (nextValue == JsNumber.NegativeZero) { nextValue = JsNumber.PositiveZero; } resultSetData.Delete(nextValue); } return resultSetData; } private JsBoolean IsDisjointFrom(JsValue thisObject, JsCallArguments arguments) { var set = AssertSetInstance(thisObject); var other = arguments.At(0); var otherRec = GetSetRecord(other); var resultSetData = new JsSet(_engine, new OrderedSet(set._set._set)); if (set.Size <= otherRec.Size) { if (other is JsSet otherSet) { // fast path return set._set._set.Overlaps(otherSet._set._set) ? JsBoolean.False : JsBoolean.True; } var index = 0; var args = new JsValue[1]; while (index < set.Size) { var e = resultSetData[index]; index++; if (e is not null) { args[0] = e; var inOther = TypeConverter.ToBoolean(otherRec.Has.Call(otherRec.Set, args)); if (inOther) { return JsBoolean.False; } } } return JsBoolean.True; } var keysIter = otherRec.Set.GetIteratorFromMethod(_realm, otherRec.Keys); while (true) { if (!keysIter.TryIteratorStep(out var next)) { break; } var nextValue = next.Get(CommonProperties.Value); if (set.Has(nextValue)) { keysIter.Close(CompletionType.Normal); return JsBoolean.False; } } return JsBoolean.True; } private JsSet Intersection(JsValue thisObject, JsCallArguments arguments) { var set = AssertSetInstance(thisObject); var other = arguments.At(0); var otherRec = GetSetRecord(other); var resultSetData = new JsSet(_engine); var thisSize = set.Size; if (thisSize <= otherRec.Size) { if (other is JsSet otherSet) { // fast path var result = new HashSet(set._set._set, SameValueZeroComparer.Instance); result.IntersectWith(otherSet._set._set); return new JsSet(_engine, new OrderedSet(result)); } var index = 0; var args = new JsValue[1]; while (index < thisSize) { var e = set[index]; index++; if (e is not null) { args[0] = e; var inOther = TypeConverter.ToBoolean(otherRec.Has.Call(otherRec.Set, args)); if (inOther) { var alreadyInResult = resultSetData.Has(e); if (!alreadyInResult) { resultSetData.Add(e); } } thisSize = set.Size; } } return resultSetData; } var keysIter = otherRec.Set.GetIteratorFromMethod(_realm, otherRec.Keys); while (true) { if (!keysIter.TryIteratorStep(out var next)) { break; } var nextValue = next.Get(CommonProperties.Value); if (nextValue == JsNumber.NegativeZero) { nextValue = JsNumber.PositiveZero; } var alreadyInResult = resultSetData.Has(nextValue); var inThis = set.Has(nextValue); if (!alreadyInResult && inThis) { resultSetData.Add(nextValue); } } return resultSetData; } private JsSet SymmetricDifference(JsValue thisObject, JsCallArguments arguments) { var set = AssertSetInstance(thisObject); var other = arguments.At(0); if (other is JsSet otherSet) { // fast path var result = new HashSet(set._set._set, SameValueZeroComparer.Instance); result.SymmetricExceptWith(otherSet._set._set); return new JsSet(_engine, new OrderedSet(result)); } var otherRec = GetSetRecord(other); var keysIter = otherRec.Set.GetIteratorFromMethod(_realm, otherRec.Keys); var resultSetData = new JsSet(_engine, new OrderedSet(set._set._set)); while (true) { if (!keysIter.TryIteratorStep(out var next)) { break; } var nextValue = next.Get(CommonProperties.Value); if (nextValue == JsNumber.NegativeZero) { nextValue = JsNumber.PositiveZero; } var inResult = resultSetData.Has(nextValue); if (set.Has(nextValue)) { if (inResult) { resultSetData.Delete(nextValue); } } else { if (!inResult) { resultSetData.Add(nextValue); } } } return resultSetData; } private JsBoolean IsSubsetOf(JsValue thisObject, JsCallArguments arguments) { var set = AssertSetInstance(thisObject); var other = arguments.At(0); if (other is JsSet otherSet) { // fast path return set._set._set.IsSubsetOf(otherSet._set._set) ? JsBoolean.True : JsBoolean.False; } var otherRec = GetSetRecord(other); var resultSetData = new JsSet(_engine, new OrderedSet(set._set._set)); var thisSize = set.Size; if (thisSize > otherRec.Size) { return JsBoolean.False; } if (thisSize <= otherRec.Size) { var index = 0; var args = new JsValue[1]; while (index < thisSize) { var e = resultSetData[index]; if (e is not null) { args[0] = e; var inOther = TypeConverter.ToBoolean(otherRec.Has.Call(otherRec.Set, args)); if (!inOther) { return JsBoolean.False; } } thisSize = set.Size; index++; } } return JsBoolean.True; } private JsBoolean IsSupersetOf(JsValue thisObject, JsCallArguments arguments) { var set = AssertSetInstance(thisObject); var other = arguments.At(0); if (other is JsSet otherSet) { // fast path var result = new HashSet(set._set._set, SameValueZeroComparer.Instance); return result.IsSupersetOf(otherSet._set._set) ? JsBoolean.True : JsBoolean.False; } var thisSize = set.Size; var otherRec = GetSetRecord(other); if (thisSize < otherRec.Size) { return JsBoolean.False; } var keysIter = otherRec.Set.GetIteratorFromMethod(_realm, otherRec.Keys); while (true) { if (!keysIter.TryIteratorStep(out var next)) { break; } var nextValue = next.Get(CommonProperties.Value); if (!set.Has(nextValue)) { keysIter.Close(CompletionType.Normal); return JsBoolean.False; } } return JsBoolean.True; } private JsBoolean Has(JsValue thisObject, JsCallArguments arguments) { var set = AssertSetInstance(thisObject); return set.Has(arguments.At(0)) ? JsBoolean.True : JsBoolean.False; } private ObjectInstance Entries(JsValue thisObject, JsCallArguments arguments) { var set = AssertSetInstance(thisObject); return set.Entries(); } private JsValue ForEach(JsValue thisObject, JsCallArguments arguments) { var callbackfn = arguments.At(0); var thisArg = arguments.At(1); var set = AssertSetInstance(thisObject); var callable = GetCallable(callbackfn); set.ForEach(callable, thisArg); return Undefined; } private JsSet Union(JsValue thisObject, JsCallArguments arguments) { var set = AssertSetInstance(thisObject); var other = arguments.At(0); var otherRec = GetSetRecord(other); var keysIter = otherRec.Set.GetIteratorFromMethod(_realm, otherRec.Keys); var resultSetData = set._set.Clone(); while (keysIter.TryIteratorStep(out var next)) { var nextValue = next.Get(CommonProperties.Value); if (nextValue == JsNumber.NegativeZero) { nextValue = JsNumber.PositiveZero; } resultSetData.Add(nextValue); } var result = new JsSet(_engine, resultSetData); return result; } private readonly record struct SetRecord(JsValue Set, double Size, ICallable Has, ICallable Keys); private SetRecord GetSetRecord(JsValue obj) { if (obj is not ObjectInstance) { Throw.TypeError(_realm); } var rawSize = obj.Get(CommonProperties.Size); var numSize = TypeConverter.ToNumber(rawSize); if (double.IsNaN(numSize)) { Throw.TypeError(_realm); } var intSize = TypeConverter.ToIntegerOrInfinity(numSize); if (intSize < 0) { Throw.RangeError(_realm); } var has = obj.Get(CommonProperties.Has); if (!has.IsCallable) { Throw.TypeError(_realm); } var keys = obj.Get(CommonProperties.Keys); if (!keys.IsCallable) { Throw.TypeError(_realm); } return new SetRecord(Set: obj, Size: intSize, Has: (ICallable) has, Keys: (ICallable) keys); } private ObjectInstance Values(JsValue thisObject, JsCallArguments arguments) { var set = AssertSetInstance(thisObject); return set.Values(); } private JsSet AssertSetInstance(JsValue thisObject) { if (thisObject is JsSet set) { return set; } Throw.TypeError(_realm, "object must be a Set"); return default; } }