SetPrototype.cs 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511
  1. using Jint.Native.Object;
  2. using Jint.Native.Symbol;
  3. using Jint.Runtime;
  4. using Jint.Runtime.Descriptors;
  5. using Jint.Runtime.Interop;
  6. namespace Jint.Native.Set;
  7. /// <summary>
  8. /// https://www.ecma-international.org/ecma-262/6.0/#sec-set-objects
  9. /// </summary>
  10. internal sealed class SetPrototype : Prototype
  11. {
  12. private readonly SetConstructor _constructor;
  13. internal SetPrototype(
  14. Engine engine,
  15. Realm realm,
  16. SetConstructor setConstructor,
  17. ObjectPrototype objectPrototype) : base(engine, realm)
  18. {
  19. _prototype = objectPrototype;
  20. _constructor = setConstructor;
  21. }
  22. protected override void Initialize()
  23. {
  24. var properties = new PropertyDictionary(12, checkExistingKeys: false)
  25. {
  26. ["length"] = new(0, PropertyFlag.Configurable),
  27. ["constructor"] = new(_constructor, PropertyFlag.NonEnumerable),
  28. ["add"] = new(new ClrFunction(Engine, "add", Add, 1, PropertyFlag.Configurable), PropertyFlag.NonEnumerable),
  29. ["clear"] = new(new ClrFunction(Engine, "clear", Clear, 0, PropertyFlag.Configurable), PropertyFlag.NonEnumerable),
  30. ["delete"] = new(new ClrFunction(Engine, "delete", Delete, 1, PropertyFlag.Configurable), PropertyFlag.NonEnumerable),
  31. ["difference"] = new(new ClrFunction(Engine, "difference", Difference, 1, PropertyFlag.Configurable), PropertyFlag.NonEnumerable),
  32. ["entries"] = new(new ClrFunction(Engine, "entries", Entries, 0, PropertyFlag.Configurable), PropertyFlag.NonEnumerable),
  33. ["forEach"] = new(new ClrFunction(Engine, "forEach", ForEach, 1, PropertyFlag.Configurable), PropertyFlag.NonEnumerable),
  34. ["has"] = new(new ClrFunction(Engine, "has", Has, 1, PropertyFlag.Configurable), PropertyFlag.NonEnumerable),
  35. ["intersection"] = new(new ClrFunction(Engine, "intersection", Intersection, 1, PropertyFlag.Configurable), PropertyFlag.NonEnumerable),
  36. ["isDisjointFrom"] = new(new ClrFunction(Engine, "isDisjointFrom", IsDisjointFrom, 1, PropertyFlag.Configurable), PropertyFlag.NonEnumerable),
  37. ["isSubsetOf"] = new(new ClrFunction(Engine, "isSubsetOf", IsSubsetOf, 1, PropertyFlag.Configurable), PropertyFlag.NonEnumerable),
  38. ["isSupersetOf"] = new(new ClrFunction(Engine, "isSupersetOf", IsSupersetOf, 1, PropertyFlag.Configurable), PropertyFlag.NonEnumerable),
  39. ["keys"] = new(new ClrFunction(Engine, "keys", Values, 0, PropertyFlag.Configurable), PropertyFlag.NonEnumerable),
  40. ["values"] = new(new ClrFunction(Engine, "values", Values, 0, PropertyFlag.Configurable), PropertyFlag.NonEnumerable),
  41. ["size"] = new GetSetPropertyDescriptor(get: new ClrFunction(Engine, "get size", Size, 0, PropertyFlag.Configurable), set: null, PropertyFlag.Configurable),
  42. ["symmetricDifference"] = new(new ClrFunction(Engine, "symmetricDifference", SymmetricDifference, 1, PropertyFlag.Configurable), PropertyFlag.NonEnumerable),
  43. ["union"] = new(new ClrFunction(Engine, "union", Union, 1, PropertyFlag.Configurable), PropertyFlag.NonEnumerable)
  44. };
  45. SetProperties(properties);
  46. var symbols = new SymbolDictionary(2)
  47. {
  48. [GlobalSymbolRegistry.Iterator] = new(new ClrFunction(Engine, "iterator", Values, 1, PropertyFlag.Configurable), true, false, true),
  49. [GlobalSymbolRegistry.ToStringTag] = new("Set", false, false, true)
  50. };
  51. SetSymbols(symbols);
  52. }
  53. private JsNumber Size(JsValue thisObject, JsCallArguments arguments)
  54. {
  55. AssertSetInstance(thisObject);
  56. return JsNumber.Create(0);
  57. }
  58. private JsValue Add(JsValue thisObject, JsCallArguments arguments)
  59. {
  60. var set = AssertSetInstance(thisObject);
  61. var value = arguments.At(0);
  62. if (value is JsNumber number && number.IsNegativeZero())
  63. {
  64. value = JsNumber.PositiveZero;
  65. }
  66. set.Add(value);
  67. return thisObject;
  68. }
  69. private JsValue Clear(JsValue thisObject, JsCallArguments arguments)
  70. {
  71. var set = AssertSetInstance(thisObject);
  72. set.Clear();
  73. return Undefined;
  74. }
  75. private JsBoolean Delete(JsValue thisObject, JsCallArguments arguments)
  76. {
  77. var set = AssertSetInstance(thisObject);
  78. return set.Delete(arguments.At(0))
  79. ? JsBoolean.True
  80. : JsBoolean.False;
  81. }
  82. private JsSet Difference(JsValue thisObject, JsCallArguments arguments)
  83. {
  84. var set = AssertSetInstance(thisObject);
  85. var other = arguments.At(0);
  86. var otherRec = GetSetRecord(other);
  87. var resultSetData = new JsSet(_engine, new OrderedSet<JsValue>(set._set._set));
  88. if (set.Size <= otherRec.Size)
  89. {
  90. if (other is JsSet otherSet)
  91. {
  92. // fast path
  93. var result = new HashSet<JsValue>(set._set._set, SameValueZeroComparer.Instance);
  94. result.ExceptWith(otherSet._set._set);
  95. return new JsSet(_engine, new OrderedSet<JsValue>(result));
  96. }
  97. var index = 0;
  98. var args = new JsValue[1];
  99. while (index < set.Size)
  100. {
  101. var e = resultSetData[index];
  102. if (e is not null)
  103. {
  104. args[0] = e;
  105. var inOther = TypeConverter.ToBoolean(otherRec.Has.Call(otherRec.Set, args));
  106. if (inOther)
  107. {
  108. resultSetData.Delete(e);
  109. index--;
  110. }
  111. }
  112. index++;
  113. }
  114. return resultSetData;
  115. }
  116. var keysIter = otherRec.Set.GetIteratorFromMethod(_realm, otherRec.Keys);
  117. while (true)
  118. {
  119. if (!keysIter.TryIteratorStep(out var next))
  120. {
  121. break;
  122. }
  123. var nextValue = next.Get(CommonProperties.Value);
  124. if (nextValue == JsNumber.NegativeZero)
  125. {
  126. nextValue = JsNumber.PositiveZero;
  127. }
  128. resultSetData.Delete(nextValue);
  129. }
  130. return resultSetData;
  131. }
  132. private JsBoolean IsDisjointFrom(JsValue thisObject, JsCallArguments arguments)
  133. {
  134. var set = AssertSetInstance(thisObject);
  135. var other = arguments.At(0);
  136. var otherRec = GetSetRecord(other);
  137. var resultSetData = new JsSet(_engine, new OrderedSet<JsValue>(set._set._set));
  138. if (set.Size <= otherRec.Size)
  139. {
  140. if (other is JsSet otherSet)
  141. {
  142. // fast path
  143. return set._set._set.Overlaps(otherSet._set._set) ? JsBoolean.False : JsBoolean.True;
  144. }
  145. var index = 0;
  146. var args = new JsValue[1];
  147. while (index < set.Size)
  148. {
  149. var e = resultSetData[index];
  150. index++;
  151. if (e is not null)
  152. {
  153. args[0] = e;
  154. var inOther = TypeConverter.ToBoolean(otherRec.Has.Call(otherRec.Set, args));
  155. if (inOther)
  156. {
  157. return JsBoolean.False;
  158. }
  159. }
  160. }
  161. return JsBoolean.True;
  162. }
  163. var keysIter = otherRec.Set.GetIteratorFromMethod(_realm, otherRec.Keys);
  164. while (true)
  165. {
  166. if (!keysIter.TryIteratorStep(out var next))
  167. {
  168. break;
  169. }
  170. var nextValue = next.Get(CommonProperties.Value);
  171. if (set.Has(nextValue))
  172. {
  173. keysIter.Close(CompletionType.Normal);
  174. return JsBoolean.False;
  175. }
  176. }
  177. return JsBoolean.True;
  178. }
  179. private JsSet Intersection(JsValue thisObject, JsCallArguments arguments)
  180. {
  181. var set = AssertSetInstance(thisObject);
  182. var other = arguments.At(0);
  183. var otherRec = GetSetRecord(other);
  184. var resultSetData = new JsSet(_engine);
  185. var thisSize = set.Size;
  186. if (thisSize <= otherRec.Size)
  187. {
  188. if (other is JsSet otherSet)
  189. {
  190. // fast path
  191. var result = new HashSet<JsValue>(set._set._set, SameValueZeroComparer.Instance);
  192. result.IntersectWith(otherSet._set._set);
  193. return new JsSet(_engine, new OrderedSet<JsValue>(result));
  194. }
  195. var index = 0;
  196. var args = new JsValue[1];
  197. while (index < thisSize)
  198. {
  199. var e = set[index];
  200. index++;
  201. if (e is not null)
  202. {
  203. args[0] = e;
  204. var inOther = TypeConverter.ToBoolean(otherRec.Has.Call(otherRec.Set, args));
  205. if (inOther)
  206. {
  207. var alreadyInResult = resultSetData.Has(e);
  208. if (!alreadyInResult)
  209. {
  210. resultSetData.Add(e);
  211. }
  212. }
  213. thisSize = set.Size;
  214. }
  215. }
  216. return resultSetData;
  217. }
  218. var keysIter = otherRec.Set.GetIteratorFromMethod(_realm, otherRec.Keys);
  219. while (true)
  220. {
  221. if (!keysIter.TryIteratorStep(out var next))
  222. {
  223. break;
  224. }
  225. var nextValue = next.Get(CommonProperties.Value);
  226. if (nextValue == JsNumber.NegativeZero)
  227. {
  228. nextValue = JsNumber.PositiveZero;
  229. }
  230. var alreadyInResult = resultSetData.Has(nextValue);
  231. var inThis = set.Has(nextValue);
  232. if (!alreadyInResult && inThis)
  233. {
  234. resultSetData.Add(nextValue);
  235. }
  236. }
  237. return resultSetData;
  238. }
  239. private JsSet SymmetricDifference(JsValue thisObject, JsCallArguments arguments)
  240. {
  241. var set = AssertSetInstance(thisObject);
  242. var other = arguments.At(0);
  243. if (other is JsSet otherSet)
  244. {
  245. // fast path
  246. var result = new HashSet<JsValue>(set._set._set, SameValueZeroComparer.Instance);
  247. result.SymmetricExceptWith(otherSet._set._set);
  248. return new JsSet(_engine, new OrderedSet<JsValue>(result));
  249. }
  250. var otherRec = GetSetRecord(other);
  251. var keysIter = otherRec.Set.GetIteratorFromMethod(_realm, otherRec.Keys);
  252. var resultSetData = new JsSet(_engine, new OrderedSet<JsValue>(set._set._set));
  253. while (true)
  254. {
  255. if (!keysIter.TryIteratorStep(out var next))
  256. {
  257. break;
  258. }
  259. var nextValue = next.Get(CommonProperties.Value);
  260. if (nextValue == JsNumber.NegativeZero)
  261. {
  262. nextValue = JsNumber.PositiveZero;
  263. }
  264. var inResult = resultSetData.Has(nextValue);
  265. if (set.Has(nextValue))
  266. {
  267. if (inResult)
  268. {
  269. resultSetData.Delete(nextValue);
  270. }
  271. }
  272. else
  273. {
  274. if (!inResult)
  275. {
  276. resultSetData.Add(nextValue);
  277. }
  278. }
  279. }
  280. return resultSetData;
  281. }
  282. private JsBoolean IsSubsetOf(JsValue thisObject, JsCallArguments arguments)
  283. {
  284. var set = AssertSetInstance(thisObject);
  285. var other = arguments.At(0);
  286. if (other is JsSet otherSet)
  287. {
  288. // fast path
  289. return set._set._set.IsSubsetOf(otherSet._set._set) ? JsBoolean.True : JsBoolean.False;
  290. }
  291. var otherRec = GetSetRecord(other);
  292. var resultSetData = new JsSet(_engine, new OrderedSet<JsValue>(set._set._set));
  293. var thisSize = set.Size;
  294. if (thisSize > otherRec.Size)
  295. {
  296. return JsBoolean.False;
  297. }
  298. if (thisSize <= otherRec.Size)
  299. {
  300. var index = 0;
  301. var args = new JsValue[1];
  302. while (index < thisSize)
  303. {
  304. var e = resultSetData[index];
  305. if (e is not null)
  306. {
  307. args[0] = e;
  308. var inOther = TypeConverter.ToBoolean(otherRec.Has.Call(otherRec.Set, args));
  309. if (!inOther)
  310. {
  311. return JsBoolean.False;
  312. }
  313. }
  314. thisSize = set.Size;
  315. index++;
  316. }
  317. }
  318. return JsBoolean.True;
  319. }
  320. private JsBoolean IsSupersetOf(JsValue thisObject, JsCallArguments arguments)
  321. {
  322. var set = AssertSetInstance(thisObject);
  323. var other = arguments.At(0);
  324. if (other is JsSet otherSet)
  325. {
  326. // fast path
  327. var result = new HashSet<JsValue>(set._set._set, SameValueZeroComparer.Instance);
  328. return result.IsSupersetOf(otherSet._set._set) ? JsBoolean.True : JsBoolean.False;
  329. }
  330. var thisSize = set.Size;
  331. var otherRec = GetSetRecord(other);
  332. if (thisSize < otherRec.Size)
  333. {
  334. return JsBoolean.False;
  335. }
  336. var keysIter = otherRec.Set.GetIteratorFromMethod(_realm, otherRec.Keys);
  337. while (true)
  338. {
  339. if (!keysIter.TryIteratorStep(out var next))
  340. {
  341. break;
  342. }
  343. var nextValue = next.Get(CommonProperties.Value);
  344. if (!set.Has(nextValue))
  345. {
  346. keysIter.Close(CompletionType.Normal);
  347. return JsBoolean.False;
  348. }
  349. }
  350. return JsBoolean.True;
  351. }
  352. private JsBoolean Has(JsValue thisObject, JsCallArguments arguments)
  353. {
  354. var set = AssertSetInstance(thisObject);
  355. return set.Has(arguments.At(0))
  356. ? JsBoolean.True
  357. : JsBoolean.False;
  358. }
  359. private ObjectInstance Entries(JsValue thisObject, JsCallArguments arguments)
  360. {
  361. var set = AssertSetInstance(thisObject);
  362. return set.Entries();
  363. }
  364. private JsValue ForEach(JsValue thisObject, JsCallArguments arguments)
  365. {
  366. var callbackfn = arguments.At(0);
  367. var thisArg = arguments.At(1);
  368. var set = AssertSetInstance(thisObject);
  369. var callable = GetCallable(callbackfn);
  370. set.ForEach(callable, thisArg);
  371. return Undefined;
  372. }
  373. private JsSet Union(JsValue thisObject, JsCallArguments arguments)
  374. {
  375. var set = AssertSetInstance(thisObject);
  376. var other = arguments.At(0);
  377. var otherRec = GetSetRecord(other);
  378. var keysIter = otherRec.Set.GetIteratorFromMethod(_realm, otherRec.Keys);
  379. var resultSetData = set._set.Clone();
  380. while (keysIter.TryIteratorStep(out var next))
  381. {
  382. var nextValue = next.Get(CommonProperties.Value);
  383. if (nextValue == JsNumber.NegativeZero)
  384. {
  385. nextValue = JsNumber.PositiveZero;
  386. }
  387. resultSetData.Add(nextValue);
  388. }
  389. var result = new JsSet(_engine, resultSetData);
  390. return result;
  391. }
  392. private readonly record struct SetRecord(JsValue Set, double Size, ICallable Has, ICallable Keys);
  393. private SetRecord GetSetRecord(JsValue obj)
  394. {
  395. if (obj is not ObjectInstance)
  396. {
  397. Throw.TypeError(_realm);
  398. }
  399. var rawSize = obj.Get(CommonProperties.Size);
  400. var numSize = TypeConverter.ToNumber(rawSize);
  401. if (double.IsNaN(numSize))
  402. {
  403. Throw.TypeError(_realm);
  404. }
  405. var intSize = TypeConverter.ToIntegerOrInfinity(numSize);
  406. if (intSize < 0)
  407. {
  408. Throw.RangeError(_realm);
  409. }
  410. var has = obj.Get(CommonProperties.Has);
  411. if (!has.IsCallable)
  412. {
  413. Throw.TypeError(_realm);
  414. }
  415. var keys = obj.Get(CommonProperties.Keys);
  416. if (!keys.IsCallable)
  417. {
  418. Throw.TypeError(_realm);
  419. }
  420. return new SetRecord(Set: obj, Size: intSize, Has: (ICallable) has, Keys: (ICallable) keys);
  421. }
  422. private ObjectInstance Values(JsValue thisObject, JsCallArguments arguments)
  423. {
  424. var set = AssertSetInstance(thisObject);
  425. return set.Values();
  426. }
  427. private JsSet AssertSetInstance(JsValue thisObject)
  428. {
  429. if (thisObject is JsSet set)
  430. {
  431. return set;
  432. }
  433. Throw.TypeError(_realm, "object must be a Set");
  434. return default;
  435. }
  436. }