ArrayPrototype.cs 37 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142
  1. using System;
  2. using System.Collections.Generic;
  3. using Jint.Native.Object;
  4. using Jint.Native.Symbol;
  5. using Jint.Runtime;
  6. using Jint.Runtime.Descriptors;
  7. using Jint.Runtime.Interop;
  8. namespace Jint.Native.Array
  9. {
  10. /// <summary>
  11. /// http://www.ecma-international.org/ecma-262/5.1/#sec-15.4.4
  12. /// </summary>
  13. public sealed class ArrayPrototype : ArrayInstance
  14. {
  15. private ArrayPrototype(Engine engine) : base(engine)
  16. {
  17. }
  18. public static ArrayPrototype CreatePrototypeObject(Engine engine, ArrayConstructor arrayConstructor)
  19. {
  20. var obj = new ArrayPrototype(engine)
  21. {
  22. Extensible = true,
  23. Prototype = engine.Object.PrototypeObject
  24. };
  25. obj.FastAddProperty("length", 0, true, false, false);
  26. obj.SetOwnProperty("constructor", new PropertyDescriptor(arrayConstructor, PropertyFlag.NonEnumerable));
  27. return obj;
  28. }
  29. public void Configure()
  30. {
  31. FastAddProperty("toString", new ClrFunctionInstance(Engine, "toString", ToString, 0), true, false, true);
  32. FastAddProperty("toLocaleString", new ClrFunctionInstance(Engine, "toLocaleString", ToLocaleString), true, false, true);
  33. FastAddProperty("concat", new ClrFunctionInstance(Engine, "concat", Concat, 1), true, false, true);
  34. FastAddProperty("copyWithin", new ClrFunctionInstance(Engine, "copyWithin", CopyWithin, 1), true, false, true);
  35. FastAddProperty("entries", new ClrFunctionInstance(Engine, "entries", Iterator, 0, PropertyFlag.Configurable), true, false, true);
  36. FastAddProperty("fill", new ClrFunctionInstance(Engine, "fill", Fill, 1, PropertyFlag.Configurable), true, false, true);
  37. FastAddProperty("join", new ClrFunctionInstance(Engine, "join", Join, 1), true, false, true);
  38. FastAddProperty("pop", new ClrFunctionInstance(Engine, "pop", Pop), true, false, true);
  39. FastAddProperty("push", new ClrFunctionInstance(Engine, "push", Push, 1), true, false, true);
  40. FastAddProperty("reverse", new ClrFunctionInstance(Engine, "reverse", Reverse), true, false, true);
  41. FastAddProperty("shift", new ClrFunctionInstance(Engine, "shift", Shift), true, false, true);
  42. FastAddProperty("slice", new ClrFunctionInstance(Engine, "slice", Slice, 2), true, false, true);
  43. FastAddProperty("sort", new ClrFunctionInstance(Engine, "sort", Sort, 1), true, false, true);
  44. FastAddProperty("splice", new ClrFunctionInstance(Engine, "splice", Splice, 2), true, false, true);
  45. FastAddProperty("unshift", new ClrFunctionInstance(Engine, "unshift", Unshift, 1), true, false, true);
  46. FastAddProperty("indexOf", new ClrFunctionInstance(Engine, "indexOf", IndexOf, 1), true, false, true);
  47. FastAddProperty("lastIndexOf", new ClrFunctionInstance(Engine, "lastIndexOf", LastIndexOf, 1), true, false, true);
  48. FastAddProperty("every", new ClrFunctionInstance(Engine, "every", Every, 1, PropertyFlag.Configurable), true, false, true);
  49. FastAddProperty("some", new ClrFunctionInstance(Engine, "some", Some, 1), true, false, true);
  50. FastAddProperty("forEach", new ClrFunctionInstance(Engine, "forEach", ForEach, 1), true, false, true);
  51. FastAddProperty("map", new ClrFunctionInstance(Engine, "map", Map, 1), true, false, true);
  52. FastAddProperty("filter", new ClrFunctionInstance(Engine, "filter", Filter, 1, PropertyFlag.Configurable), true, false, true);
  53. FastAddProperty("reduce", new ClrFunctionInstance(Engine, "reduce", Reduce, 1), true, false, true);
  54. FastAddProperty("reduceRight", new ClrFunctionInstance(Engine, "reduceRight", ReduceRight, 1), true, false, true);
  55. FastAddProperty("find", new ClrFunctionInstance(Engine, "find", Find, 1, PropertyFlag.Configurable), true, false, true);
  56. FastAddProperty("findIndex", new ClrFunctionInstance(Engine, "findIndex", FindIndex, 1), true, false, true);
  57. FastAddProperty(GlobalSymbolRegistry.Iterator._value, new ClrFunctionInstance(Engine, "iterator", Iterator, 1), true, false, true);
  58. }
  59. private ObjectInstance Iterator(JsValue thisObj, JsValue[] arguments)
  60. {
  61. var array = thisObj as ArrayInstance;
  62. return _engine.Iterator.Construct(array);
  63. }
  64. private JsValue Fill(JsValue thisObj, JsValue[] arguments)
  65. {
  66. var target = thisObj as ObjectInstance;
  67. var operations = ArrayOperations.For(target);
  68. var length = operations.GetLength();
  69. var value = arguments.At(0);
  70. var start = arguments.At(1, 0).AsNumber();
  71. var relativeStart = TypeConverter.ToInteger(start);
  72. uint actualStart;
  73. if (relativeStart < 0)
  74. {
  75. actualStart = (uint) System.Math.Max(length + relativeStart, 0);
  76. }
  77. else
  78. {
  79. actualStart = (uint) System.Math.Min(relativeStart, length);
  80. }
  81. var end = arguments.At(2, length).AsNumber();
  82. var relativeEnd = TypeConverter.ToInteger(end);
  83. uint actualEnd;
  84. if (relativeEnd < 0)
  85. {
  86. actualEnd = (uint) System.Math.Max(length + relativeEnd, 0);
  87. }
  88. else
  89. {
  90. actualEnd = (uint) System.Math.Min(relativeEnd, length);
  91. }
  92. for (var i = actualStart; i < actualEnd; ++i)
  93. {
  94. operations.Put(i, value, false);
  95. }
  96. return target;
  97. }
  98. private JsValue CopyWithin(JsValue thisObj, JsValue[] arguments)
  99. {
  100. return Undefined;
  101. }
  102. private JsValue LastIndexOf(JsValue thisObj, JsValue[] arguments)
  103. {
  104. var o = ArrayOperations.For(Engine, thisObj);
  105. var len = o.GetLength();
  106. if (len == 0)
  107. {
  108. return -1;
  109. }
  110. var n = arguments.Length > 1 ? TypeConverter.ToInteger(arguments[1]) : len - 1;
  111. double k;
  112. if (n >= 0)
  113. {
  114. k = System.Math.Min(n, len - 1); // min
  115. }
  116. else
  117. {
  118. k = len - System.Math.Abs(n);
  119. }
  120. if (k < 0 || k > uint.MaxValue)
  121. {
  122. return -1;
  123. }
  124. var searchElement = arguments.At(0);
  125. var i = (uint) k;
  126. for (;; i--)
  127. {
  128. if (o.TryGetValue(i, out var value))
  129. {
  130. var same = ExpressionInterpreter.StrictlyEqual(value, searchElement);
  131. if (same)
  132. {
  133. return i;
  134. }
  135. }
  136. if (i == 0)
  137. {
  138. break;
  139. }
  140. }
  141. return -1;
  142. }
  143. private JsValue Reduce(JsValue thisObj, JsValue[] arguments)
  144. {
  145. var callbackfn = arguments.At(0);
  146. var initialValue = arguments.At(1);
  147. var o = ArrayOperations.For(Engine, thisObj);
  148. var len = o.GetLength();
  149. var callable = GetCallable(callbackfn);
  150. if (len == 0 && arguments.Length < 2)
  151. {
  152. ExceptionHelper.ThrowTypeError(Engine);
  153. }
  154. var k = 0;
  155. JsValue accumulator = Undefined;
  156. if (arguments.Length > 1)
  157. {
  158. accumulator = initialValue;
  159. }
  160. else
  161. {
  162. var kPresent = false;
  163. while (kPresent == false && k < len)
  164. {
  165. if (kPresent = o.TryGetValue((uint) k, out var temp))
  166. {
  167. accumulator = temp;
  168. }
  169. k++;
  170. }
  171. if (kPresent == false)
  172. {
  173. ExceptionHelper.ThrowTypeError(Engine);
  174. }
  175. }
  176. var args = new JsValue[4];
  177. while (k < len)
  178. {
  179. var i = (uint) k;
  180. if (o.TryGetValue(i, out var kvalue))
  181. {
  182. args[0] = accumulator;
  183. args[1] = kvalue;
  184. args[2] = i;
  185. args[3] = o.Target;
  186. accumulator = callable.Call(Undefined, args);
  187. }
  188. k++;
  189. }
  190. return accumulator;
  191. }
  192. private JsValue Filter(JsValue thisObj, JsValue[] arguments)
  193. {
  194. var callbackfn = arguments.At(0);
  195. var thisArg = arguments.At(1);
  196. var o = ArrayOperations.For(Engine, thisObj);
  197. var len = o.GetLength();
  198. var callable = GetCallable(callbackfn);
  199. var a = Engine.Array.ConstructFast(0);
  200. uint to = 0;
  201. var args = _engine._jsValueArrayPool.RentArray(3);
  202. args[2] = o.Target;
  203. for (uint k = 0; k < len; k++)
  204. {
  205. if (o.TryGetValue(k, out var kvalue))
  206. {
  207. args[0] = kvalue;
  208. args[1] = k;
  209. var selected = callable.Call(thisArg, args);
  210. if (TypeConverter.ToBoolean(selected))
  211. {
  212. a.SetIndexValue(to, kvalue, updateLength: false);
  213. to++;
  214. }
  215. }
  216. }
  217. a.SetLength(to);
  218. _engine._jsValueArrayPool.ReturnArray(args);
  219. return a;
  220. }
  221. private JsValue Map(JsValue thisObj, JsValue[] arguments)
  222. {
  223. if (thisObj is ArrayInstance arrayInstance)
  224. {
  225. return arrayInstance.Map(arguments);
  226. }
  227. var o = ArrayOperations.For(Engine, thisObj);
  228. var len = o.GetLength();
  229. var callbackfn = arguments.At(0);
  230. var thisArg = arguments.At(1);
  231. var callable = GetCallable(callbackfn);
  232. var a = Engine.Array.ConstructFast(len);
  233. var args = _engine._jsValueArrayPool.RentArray(3);
  234. args[2] = o.Target;
  235. for (uint k = 0; k < len; k++)
  236. {
  237. if (o.TryGetValue(k, out var kvalue))
  238. {
  239. args[0] = kvalue;
  240. args[1] = k;
  241. var mappedValue = callable.Call(thisArg, args);
  242. a.SetIndexValue(k, mappedValue, updateLength: false);
  243. }
  244. }
  245. _engine._jsValueArrayPool.ReturnArray(args);
  246. return a;
  247. }
  248. private JsValue ForEach(JsValue thisObj, JsValue[] arguments)
  249. {
  250. var callbackfn = arguments.At(0);
  251. var thisArg = arguments.At(1);
  252. var o = ArrayOperations.For(Engine, thisObj);
  253. var len = o.GetLength();
  254. var callable = GetCallable(callbackfn);
  255. var args = _engine._jsValueArrayPool.RentArray(3);
  256. args[2] = o.Target;
  257. for (uint k = 0; k < len; k++)
  258. {
  259. if (o.TryGetValue(k, out var kvalue))
  260. {
  261. args[0] = kvalue;
  262. args[1] = k;
  263. callable.Call(thisArg, args);
  264. }
  265. }
  266. _engine._jsValueArrayPool.ReturnArray(args);
  267. return Undefined;
  268. }
  269. private JsValue Some(JsValue thisObj, JsValue[] arguments)
  270. {
  271. var target = TypeConverter.ToObject(Engine, thisObj);
  272. return target.FindWithCallback(arguments, out _, out _);
  273. }
  274. private JsValue Every(JsValue thisObj, JsValue[] arguments)
  275. {
  276. var o = ArrayOperations.For(Engine, thisObj);
  277. uint len;
  278. if (thisObj is ArrayInstance arrayInstance)
  279. {
  280. len = arrayInstance.GetLength();
  281. }
  282. else
  283. {
  284. var intValue = ((ArrayOperations.ObjectInstanceOperations) o).GetIntegerLength();
  285. len = intValue < 0 ? 0 : (uint) intValue;
  286. }
  287. if (len == 0)
  288. {
  289. return JsBoolean.True;
  290. }
  291. var callbackfn = arguments.At(0);
  292. var thisArg = arguments.At(1);
  293. var callable = GetCallable(callbackfn);
  294. var args = _engine._jsValueArrayPool.RentArray(3);
  295. args[2] = o.Target;
  296. for (uint k = 0; k < len; k++)
  297. {
  298. if (o.TryGetValue(k, out var kvalue))
  299. {
  300. args[0] = kvalue;
  301. args[1] = k;
  302. var testResult = callable.Call(thisArg, args);
  303. if (false == TypeConverter.ToBoolean(testResult))
  304. {
  305. return JsBoolean.False;
  306. }
  307. }
  308. }
  309. _engine._jsValueArrayPool.ReturnArray(args);
  310. return JsBoolean.True;
  311. }
  312. private JsValue IndexOf(JsValue thisObj, JsValue[] arguments)
  313. {
  314. var o = ArrayOperations.For(Engine, thisObj);
  315. var len = o.GetLength();
  316. if (len == 0)
  317. {
  318. return -1;
  319. }
  320. var startIndex = arguments.Length > 1 ? TypeConverter.ToInteger(arguments[1]) : 0;
  321. if (startIndex > uint.MaxValue)
  322. {
  323. return -1;
  324. }
  325. uint k;
  326. if (startIndex < 0)
  327. {
  328. var abs = System.Math.Abs(startIndex);
  329. long temp = len - (uint) abs;
  330. if (abs > len || temp < 0)
  331. {
  332. temp = 0;
  333. }
  334. k = (uint) temp;
  335. }
  336. else
  337. {
  338. k = (uint) startIndex;
  339. }
  340. if (k >= len)
  341. {
  342. return -1;
  343. }
  344. uint smallestIndex = o.GetSmallestIndex();
  345. if (smallestIndex > k)
  346. {
  347. k = smallestIndex;
  348. }
  349. var searchElement = arguments.At(0);
  350. for (; k < len; k++)
  351. {
  352. if (o.TryGetValue(k, out var elementK))
  353. {
  354. var same = ExpressionInterpreter.StrictlyEqual(elementK, searchElement);
  355. if (same)
  356. {
  357. return k;
  358. }
  359. }
  360. }
  361. return -1;
  362. }
  363. private JsValue Find(JsValue thisObj, JsValue[] arguments)
  364. {
  365. var target = TypeConverter.ToObject(Engine, thisObj);
  366. target.FindWithCallback(arguments, out _, out var value);
  367. return value;
  368. }
  369. private JsValue FindIndex(JsValue thisObj, JsValue[] arguments)
  370. {
  371. var target = TypeConverter.ToObject(Engine, thisObj);
  372. if (target.FindWithCallback(arguments, out var index, out _))
  373. {
  374. return index;
  375. }
  376. return -1;
  377. }
  378. private JsValue Splice(JsValue thisObj, JsValue[] arguments)
  379. {
  380. var start = arguments.At(0);
  381. var deleteCount = arguments.At(1);
  382. var o = ArrayOperations.For(Engine, thisObj);
  383. var len = o.GetLength();
  384. var relativeStart = TypeConverter.ToInteger(start);
  385. uint actualStart;
  386. if (relativeStart < 0)
  387. {
  388. actualStart = (uint) System.Math.Max(len + relativeStart, 0);
  389. }
  390. else
  391. {
  392. actualStart = (uint) System.Math.Min(relativeStart, len);
  393. }
  394. var actualDeleteCount = (uint) System.Math.Min(System.Math.Max(TypeConverter.ToInteger(deleteCount), 0), len - actualStart);
  395. var a = Engine.Array.ConstructFast(actualDeleteCount);
  396. for (uint k = 0; k < actualDeleteCount; k++)
  397. {
  398. if (o.TryGetValue(actualStart + k, out var fromValue))
  399. {
  400. a.SetIndexValue(k, fromValue, updateLength: false);
  401. }
  402. }
  403. a.SetLength(actualDeleteCount);
  404. var items = System.Array.Empty<JsValue>();
  405. if (arguments.Length > 2)
  406. {
  407. items = new JsValue[arguments.Length - 2];
  408. System.Array.Copy(arguments, 2, items, 0, items.Length);
  409. }
  410. var length = len - actualDeleteCount + (uint) items.Length;
  411. o.EnsureCapacity(length);
  412. if (items.Length < actualDeleteCount)
  413. {
  414. for (uint k = actualStart; k < len - actualDeleteCount; k++)
  415. {
  416. var from = k + actualDeleteCount;
  417. var to = (uint) (k + items.Length);
  418. if (o.TryGetValue(from, out var fromValue))
  419. {
  420. o.Put(to, fromValue, true);
  421. }
  422. else
  423. {
  424. o.DeleteAt(to);
  425. }
  426. }
  427. for (var k = len; k > len - actualDeleteCount + items.Length; k--)
  428. {
  429. o.DeleteAt(k - 1);
  430. }
  431. }
  432. else if (items.Length > actualDeleteCount)
  433. {
  434. for (var k = len - actualDeleteCount; k > actualStart; k--)
  435. {
  436. var from = k + actualDeleteCount - 1;
  437. uint to = (uint) (k + items.Length - 1);
  438. if (o.TryGetValue(from, out var fromValue))
  439. {
  440. o.Put(to, fromValue, true);
  441. }
  442. else
  443. {
  444. o.DeleteAt(to);
  445. }
  446. }
  447. }
  448. for (uint k = 0; k < items.Length; k++)
  449. {
  450. var e = items[k];
  451. o.Put(k + actualStart, e, true);
  452. }
  453. o.SetLength(length);
  454. return a;
  455. }
  456. private JsValue Unshift(JsValue thisObj, JsValue[] arguments)
  457. {
  458. var o = ArrayOperations.For(Engine, thisObj);
  459. var len = o.GetLength();
  460. var argCount = (uint) arguments.Length;
  461. o.EnsureCapacity(len + argCount);
  462. for (var k = len; k > 0; k--)
  463. {
  464. var from = k - 1;
  465. var to = k + argCount - 1;
  466. if (o.TryGetValue(from, out var fromValue))
  467. {
  468. o.Put(to, fromValue, true);
  469. }
  470. else
  471. {
  472. o.DeleteAt(to);
  473. }
  474. }
  475. for (uint j = 0; j < argCount; j++)
  476. {
  477. o.Put(j, arguments[j], true);
  478. }
  479. o.SetLength(len + argCount);
  480. return len + argCount;
  481. }
  482. private JsValue Sort(JsValue thisObj, JsValue[] arguments)
  483. {
  484. if (!thisObj.IsObject())
  485. {
  486. ExceptionHelper.ThrowTypeError(_engine, "Array.prorotype.sort can only be applied on objects");
  487. }
  488. var obj = ArrayOperations.For(thisObj.AsObject());
  489. var len = obj.GetLength();
  490. if (len <= 1)
  491. {
  492. return obj.Target;
  493. }
  494. var compareArg = arguments.At(0);
  495. ICallable compareFn = null;
  496. if (!compareArg.IsUndefined())
  497. {
  498. compareFn = compareArg.TryCast<ICallable>(x => ExceptionHelper.ThrowTypeError(_engine, "The sort argument must be a function"));
  499. }
  500. int Comparer(JsValue x, JsValue y)
  501. {
  502. var xUndefined = x.IsUndefined();
  503. var yUndefined = y.IsUndefined();
  504. if (xUndefined && yUndefined)
  505. {
  506. return 0;
  507. }
  508. if (xUndefined)
  509. {
  510. return 1;
  511. }
  512. if (yUndefined)
  513. {
  514. return -1;
  515. }
  516. if (compareFn != null)
  517. {
  518. var s = TypeConverter.ToNumber(compareFn.Call(Undefined, new[] {x, y}));
  519. if (s < 0)
  520. {
  521. return -1;
  522. }
  523. if (s > 0)
  524. {
  525. return 1;
  526. }
  527. return 0;
  528. }
  529. var xString = TypeConverter.ToString(x);
  530. var yString = TypeConverter.ToString(y);
  531. var r = System.String.CompareOrdinal(xString, yString);
  532. return r;
  533. }
  534. var array = new JsValue[len];
  535. for (uint i = 0; i < len; ++i)
  536. {
  537. array[i] = obj.Get(i);
  538. }
  539. // don't eat inner exceptions
  540. try
  541. {
  542. System.Array.Sort(array, Comparer);
  543. }
  544. catch (InvalidOperationException e)
  545. {
  546. throw e.InnerException;
  547. }
  548. for (uint i = 0; i < len; ++i)
  549. {
  550. obj.Put(i, array[i], false);
  551. }
  552. return obj.Target;
  553. }
  554. private JsValue Slice(JsValue thisObj, JsValue[] arguments)
  555. {
  556. var start = arguments.At(0);
  557. var end = arguments.At(1);
  558. var o = ArrayOperations.For(Engine, thisObj);
  559. var len = o.GetLength();
  560. var relativeStart = TypeConverter.ToInteger(start);
  561. uint k;
  562. if (relativeStart < 0)
  563. {
  564. k = (uint) System.Math.Max(len + relativeStart, 0);
  565. }
  566. else
  567. {
  568. k = (uint) System.Math.Min(TypeConverter.ToInteger(start), len);
  569. }
  570. uint final;
  571. if (end.IsUndefined())
  572. {
  573. final = TypeConverter.ToUint32(len);
  574. }
  575. else
  576. {
  577. double relativeEnd = TypeConverter.ToInteger(end);
  578. if (relativeEnd < 0)
  579. {
  580. final = (uint) System.Math.Max(len + relativeEnd, 0);
  581. }
  582. else
  583. {
  584. final = (uint) System.Math.Min(TypeConverter.ToInteger(relativeEnd), len);
  585. }
  586. }
  587. var a = Engine.Array.Construct(final - k);
  588. uint n = 0;
  589. for (; k < final; k++)
  590. {
  591. if (o.TryGetValue(k, out var kValue))
  592. {
  593. a.SetIndexValue(n, kValue, updateLength: true);
  594. }
  595. n++;
  596. }
  597. return a;
  598. }
  599. private JsValue Shift(JsValue thisObj, JsValue[] arg2)
  600. {
  601. var o = ArrayOperations.For(Engine, thisObj);
  602. var len = o.GetLength();
  603. if (len == 0)
  604. {
  605. o.SetLength(0);
  606. return Undefined;
  607. }
  608. var first = o.Get(0);
  609. for (uint k = 1; k < len; k++)
  610. {
  611. var to = k - 1;
  612. if (o.TryGetValue(k, out var fromVal))
  613. {
  614. o.Put(to, fromVal, true);
  615. }
  616. else
  617. {
  618. o.DeleteAt(to);
  619. }
  620. }
  621. o.DeleteAt(len - 1);
  622. o.SetLength(len - 1);
  623. return first;
  624. }
  625. private JsValue Reverse(JsValue thisObj, JsValue[] arguments)
  626. {
  627. var o = ArrayOperations.For(Engine, thisObj);
  628. var len = o.GetLength();
  629. var middle = (uint) System.Math.Floor(len / 2.0);
  630. uint lower = 0;
  631. while (lower != middle)
  632. {
  633. var upper = len - lower - 1;
  634. var lowerExists = o.TryGetValue(lower, out var lowerValue);
  635. var upperExists = o.TryGetValue(upper, out var upperValue);
  636. if (lowerExists && upperExists)
  637. {
  638. o.Put(lower, upperValue, true);
  639. o.Put(upper, lowerValue, true);
  640. }
  641. if (!lowerExists && upperExists)
  642. {
  643. o.Put(lower, upperValue, true);
  644. o.DeleteAt(upper);
  645. }
  646. if (lowerExists && !upperExists)
  647. {
  648. o.DeleteAt(lower);
  649. o.Put(upper, lowerValue, true);
  650. }
  651. lower++;
  652. }
  653. return o.Target;
  654. }
  655. private JsValue Join(JsValue thisObj, JsValue[] arguments)
  656. {
  657. var separator = arguments.At(0);
  658. var o = ArrayOperations.For(Engine, thisObj);
  659. var len = o.GetLength();
  660. if (separator.IsUndefined())
  661. {
  662. separator = ",";
  663. }
  664. var sep = TypeConverter.ToString(separator);
  665. // as per the spec, this has to be called after ToString(separator)
  666. if (len == 0)
  667. {
  668. return "";
  669. }
  670. string StringFromJsValue(JsValue value)
  671. {
  672. return value.IsUndefined() || value.IsNull()
  673. ? ""
  674. : TypeConverter.ToString(value);
  675. }
  676. var s = StringFromJsValue(o.Get(0));
  677. if (len == 1)
  678. {
  679. return s;
  680. }
  681. var sb = ArrayExecutionContext.Current.StringBuilder;
  682. sb.Clear();
  683. sb.Append(s);
  684. for (uint k = 1; k < len; k++)
  685. {
  686. sb.Append(sep);
  687. sb.Append(StringFromJsValue(o.Get(k)));
  688. }
  689. return sb.ToString();
  690. }
  691. private JsValue ToLocaleString(JsValue thisObj, JsValue[] arguments)
  692. {
  693. var array = ArrayOperations.For(Engine, thisObj);
  694. var len = array.GetLength();
  695. const string separator = ",";
  696. if (len == 0)
  697. {
  698. return "";
  699. }
  700. JsValue r;
  701. if (!array.TryGetValue(0, out var firstElement) || firstElement.IsNull() || firstElement.IsUndefined())
  702. {
  703. r = "";
  704. }
  705. else
  706. {
  707. var elementObj = TypeConverter.ToObject(Engine, firstElement);
  708. var func = elementObj.Get("toLocaleString") as ICallable ?? ExceptionHelper.ThrowTypeError<ICallable>(_engine);
  709. r = func.Call(elementObj, Arguments.Empty);
  710. }
  711. for (uint k = 1; k < len; k++)
  712. {
  713. string s = r + separator;
  714. if (!array.TryGetValue(k, out var nextElement) || nextElement.IsNull())
  715. {
  716. r = "";
  717. }
  718. else
  719. {
  720. var elementObj = TypeConverter.ToObject(Engine, nextElement);
  721. var func = elementObj.Get("toLocaleString") as ICallable ?? ExceptionHelper.ThrowTypeError<ICallable>(_engine);
  722. r = func.Call(elementObj, Arguments.Empty);
  723. }
  724. r = s + r;
  725. }
  726. return r;
  727. }
  728. private JsValue Concat(JsValue thisObj, JsValue[] arguments)
  729. {
  730. var o = TypeConverter.ToObject(Engine, thisObj);
  731. uint n = 0;
  732. var items = new List<JsValue>(arguments.Length + 1) {o};
  733. items.AddRange(arguments);
  734. // try to find best capacity
  735. uint capacity = 0;
  736. for (var i = 0; i < items.Count; i++)
  737. {
  738. var eArray = items[i] as ArrayInstance;
  739. capacity += eArray?.GetLength() ?? (uint) 1;
  740. }
  741. var a = Engine.Array.ConstructFast(capacity);
  742. for (var i = 0; i < items.Count; i++)
  743. {
  744. var e = items[i];
  745. if (e is ArrayInstance eArray)
  746. {
  747. var len = eArray.GetLength();
  748. for (uint k = 0; k < len; k++)
  749. {
  750. if (eArray.TryGetValue(k, out var subElement))
  751. {
  752. a.SetIndexValue(n, subElement, updateLength: false);
  753. }
  754. n++;
  755. }
  756. }
  757. else
  758. {
  759. a.SetIndexValue(n, e, updateLength: false);
  760. n++;
  761. }
  762. }
  763. // this is not in the specs, but is necessary in case the last element of the last
  764. // array doesn't exist, and thus the length would not be incremented
  765. a.DefineOwnProperty("length", new PropertyDescriptor(n, PropertyFlag.None), false);
  766. return a;
  767. }
  768. private JsValue ToString(JsValue thisObj, JsValue[] arguments)
  769. {
  770. var array = TypeConverter.ToObject(Engine, thisObj);
  771. ICallable func;
  772. func = array.Get("join").TryCast<ICallable>(x =>
  773. {
  774. func = Engine.Object.PrototypeObject.Get("toString").TryCast<ICallable>(y => ExceptionHelper.ThrowArgumentException());
  775. });
  776. return func.Call(array, Arguments.Empty);
  777. }
  778. private JsValue ReduceRight(JsValue thisObj, JsValue[] arguments)
  779. {
  780. var callbackfn = arguments.At(0);
  781. var initialValue = arguments.At(1);
  782. var o = TypeConverter.ToObject(Engine, thisObj);
  783. var lenValue = o.Get("length");
  784. var len = TypeConverter.ToUint32(lenValue);
  785. var callable = GetCallable(callbackfn);
  786. if (len == 0 && arguments.Length < 2)
  787. {
  788. ExceptionHelper.ThrowTypeError(Engine);
  789. }
  790. int k = (int) len - 1;
  791. JsValue accumulator = Undefined;
  792. if (arguments.Length > 1)
  793. {
  794. accumulator = initialValue;
  795. }
  796. else
  797. {
  798. var kPresent = false;
  799. while (kPresent == false && k >= 0)
  800. {
  801. var pk = TypeConverter.ToString(k);
  802. kPresent = o.HasProperty(pk);
  803. if (kPresent)
  804. {
  805. accumulator = o.Get(pk);
  806. }
  807. k--;
  808. }
  809. if (kPresent == false)
  810. {
  811. ExceptionHelper.ThrowTypeError(Engine);
  812. }
  813. }
  814. var jsValues = new JsValue[4];
  815. for (; k >= 0; k--)
  816. {
  817. var pk = TypeConverter.ToString(k);
  818. var kPresent = o.HasProperty(pk);
  819. if (kPresent)
  820. {
  821. var kvalue = o.Get(pk);
  822. jsValues[0] = accumulator;
  823. jsValues[1] = kvalue;
  824. jsValues[2] = k;
  825. jsValues[3] = o;
  826. accumulator = callable.Call(Undefined, jsValues);
  827. }
  828. }
  829. return accumulator;
  830. }
  831. public JsValue Push(JsValue thisObject, JsValue[] arguments)
  832. {
  833. if (thisObject is ArrayInstance arrayInstance)
  834. {
  835. return arrayInstance.Push(arguments);
  836. }
  837. var o = TypeConverter.ToObject(Engine, thisObject);
  838. var lenVal = TypeConverter.ToNumber(o.Get("length"));
  839. // cast to double as we need to prevent an overflow
  840. double n = TypeConverter.ToUint32(lenVal);
  841. for (var i = 0; i < arguments.Length; i++)
  842. {
  843. o.Put(TypeConverter.ToString(n), arguments[i], true);
  844. n++;
  845. }
  846. o.Put("length", n, true);
  847. return n;
  848. }
  849. public JsValue Pop(JsValue thisObject, JsValue[] arguments)
  850. {
  851. var o = ArrayOperations.For(Engine, thisObject);
  852. var lenVal = TypeConverter.ToNumber(o.Target.Get("length"));
  853. uint len = TypeConverter.ToUint32(lenVal);
  854. if (len == 0)
  855. {
  856. o.SetLength(0);
  857. return Undefined;
  858. }
  859. len = len - 1;
  860. string indx = TypeConverter.ToString(len);
  861. JsValue element = o.Target.Get(indx);
  862. o.Target.Delete(indx, true);
  863. o.Target.Put("length", len, true);
  864. return element;
  865. }
  866. /// <summary>
  867. /// Adapter to use optimized array operations when possible.
  868. /// Gaps the difference between ArgumensInstance and ArrayInstance.
  869. /// </summary>
  870. internal abstract class ArrayOperations
  871. {
  872. public abstract ObjectInstance Target { get; }
  873. public virtual uint GetSmallestIndex() => 0;
  874. public abstract uint GetLength();
  875. public abstract void SetLength(uint length);
  876. public virtual void EnsureCapacity(uint capacity)
  877. {
  878. }
  879. public abstract JsValue Get(uint index);
  880. public abstract bool TryGetValue(uint index, out JsValue value);
  881. public abstract void Put(uint index, JsValue value, bool throwOnError);
  882. public abstract void DeleteAt(uint index);
  883. public static ArrayOperations For(Engine engine, JsValue thisObj)
  884. {
  885. var instance = TypeConverter.ToObject(engine, thisObj);
  886. return For(instance);
  887. }
  888. public static ArrayOperations For(ObjectInstance instance)
  889. {
  890. if (instance is ArrayInstance arrayInstance)
  891. {
  892. return new ArrayInstanceOperations(arrayInstance);
  893. }
  894. return new ObjectInstanceOperations(instance);
  895. }
  896. internal class ObjectInstanceOperations : ArrayOperations
  897. {
  898. private readonly ObjectInstance _instance;
  899. public ObjectInstanceOperations(ObjectInstance instance)
  900. {
  901. _instance = instance;
  902. }
  903. public override ObjectInstance Target => _instance;
  904. internal double GetIntegerLength()
  905. {
  906. var desc = _instance.GetProperty("length");
  907. var descValue = desc.Value;
  908. if (desc.IsDataDescriptor() && !ReferenceEquals(descValue, null))
  909. {
  910. return TypeConverter.ToUint32(descValue);
  911. }
  912. var getter = desc.Get ?? Undefined;
  913. if (getter.IsUndefined())
  914. {
  915. return 0;
  916. }
  917. // if getter is not undefined it must be ICallable
  918. var callable = (ICallable) getter;
  919. var value = callable.Call(_instance, Arguments.Empty);
  920. return (uint) TypeConverter.ToInteger(value);
  921. }
  922. public override uint GetLength()
  923. {
  924. return (uint) GetIntegerLength();
  925. }
  926. public override void SetLength(uint length) => _instance.Put("length", length, true);
  927. public override JsValue Get(uint index) => _instance.Get(TypeConverter.ToString(index));
  928. public override bool TryGetValue(uint index, out JsValue value)
  929. {
  930. var property = TypeConverter.ToString(index);
  931. var kPresent = _instance.HasProperty(property);
  932. value = kPresent ? _instance.Get(property) : Undefined;
  933. return kPresent;
  934. }
  935. public override void Put(uint index, JsValue value, bool throwOnError) => _instance.Put(TypeConverter.ToString(index), value, throwOnError);
  936. public override void DeleteAt(uint index) => _instance.Delete(TypeConverter.ToString(index), true);
  937. }
  938. private class ArrayInstanceOperations : ArrayOperations
  939. {
  940. private readonly ArrayInstance _array;
  941. public ArrayInstanceOperations(ArrayInstance array)
  942. {
  943. _array = array;
  944. }
  945. public override ObjectInstance Target => _array;
  946. public override uint GetSmallestIndex() => _array.GetSmallestIndex();
  947. public override uint GetLength() => (uint) ((JsNumber) _array._length._value)._value;
  948. public override void SetLength(uint length) => _array.Put("length", length, true);
  949. public override void EnsureCapacity(uint capacity)
  950. {
  951. _array.EnsureCapacity(capacity);
  952. }
  953. public override bool TryGetValue(uint index, out JsValue value)
  954. {
  955. return _array.TryGetValue(index, out value);
  956. }
  957. public override JsValue Get(uint index) => _array.Get(TypeConverter.ToString(index));
  958. public override void DeleteAt(uint index) => _array.DeleteAt(index);
  959. public override void Put(uint index, JsValue value, bool throwOnError) => _array.SetIndexValue(index, value, throwOnError);
  960. }
  961. }
  962. }
  963. }