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;
}
}