PageRenderTime 46ms CodeModel.GetById 14ms RepoModel.GetById 1ms app.codeStats 0ms

/Languages/Ruby/Ruby/Builtins/RubyArray.cs

http://github.com/IronLanguages/main
C# | 703 lines | 528 code | 120 blank | 55 comment | 94 complexity | 7091aff6052592bc141a94e126dac532 MD5 | raw file
Possible License(s): CPL-1.0, BSD-3-Clause, ISC, GPL-2.0, MPL-2.0-no-copyleft-exception
  1. /* ****************************************************************************
  2. *
  3. * Copyright (c) Microsoft Corporation.
  4. *
  5. * This source code is subject to terms and conditions of the Apache License, Version 2.0. A
  6. * copy of the license can be found in the License.html file at the root of this distribution. If
  7. * you cannot locate the Apache License, Version 2.0, please send an email to
  8. * ironruby@microsoft.com. By using this source code in any fashion, you are agreeing to be bound
  9. * by the terms of the Apache License, Version 2.0.
  10. *
  11. * You must not remove this notice, or any other, from this software.
  12. *
  13. *
  14. * ***************************************************************************/
  15. using System;
  16. using System.Collections;
  17. using System.Collections.Generic;
  18. using System.Diagnostics;
  19. using IronRuby.Runtime;
  20. using Microsoft.Scripting;
  21. using Microsoft.Scripting.Utils;
  22. using IronRuby.Runtime.Calls;
  23. using IronRuby.Runtime.Conversions;
  24. using System.Runtime.Serialization;
  25. namespace IronRuby.Builtins {
  26. /// <summary>
  27. /// Implements Ruby array.
  28. /// Not thread safe (even when frozen).
  29. /// </summary>
  30. [Serializable]
  31. [DebuggerDisplay("{GetDebugView()}")]
  32. public partial class RubyArray : IList<object>, IList, IRubyObjectState, IDuplicable {
  33. private object[]/*!*/ _content;
  34. private int _start;
  35. private int _count;
  36. private uint _flags;
  37. private const uint IsFrozenFlag = 1;
  38. private const uint IsTaintedFlag = 2;
  39. private const uint IsUntrustedFlag = 4;
  40. [Conditional("DEBUG")]
  41. private void ObjectInvariant() {
  42. Debug.Assert(_start >= 0 && _count >= 0);
  43. Debug.Assert(_start + _count <= _content.Length);
  44. Debug.Assert(_content.Length == 0 || _content.Length >= Utils.MinListSize);
  45. }
  46. [Conditional("DEBUG")]
  47. internal void RequireNullEmptySlots() {
  48. for (int i = 0; i < _content.Length; i++) {
  49. if (i < _start || i >= _start + _count) {
  50. Debug.Assert(_content[i] == null);
  51. }
  52. }
  53. }
  54. #region Construction
  55. internal RubyArray(object[]/*!*/ content, int start, int count) {
  56. _start = start;
  57. _content = content;
  58. _count = count;
  59. ObjectInvariant();
  60. }
  61. public RubyArray()
  62. : this(ArrayUtils.EmptyObjects, 0, 0) {
  63. }
  64. public RubyArray(int capacity)
  65. : this(new object[Math.Max(capacity, Utils.MinListSize)], 0, 0) {
  66. }
  67. public RubyArray(RubyArray/*!*/ items)
  68. : this(items, 0, items.Count) {
  69. }
  70. public RubyArray(RubyArray/*!*/ items, int start, int count)
  71. : this(count) {
  72. ContractUtils.RequiresNotNull(items, "items");
  73. ContractUtils.RequiresArrayRange(items.Count, start, count, "start", "count");
  74. AddVector(items._content, items._start + start, count);
  75. }
  76. public RubyArray(IList/*!*/ items)
  77. : this(items, 0, items.Count) {
  78. }
  79. public RubyArray(IList/*!*/ items, int start, int count)
  80. : this(count) {
  81. AddRange(items, start, count);
  82. }
  83. public RubyArray(ICollection/*!*/ items)
  84. : this(items.Count) {
  85. AddCollection(items);
  86. }
  87. public RubyArray(IEnumerable/*!*/ items)
  88. : this() {
  89. AddRange(items);
  90. }
  91. public static RubyArray/*!*/ Create(object item) {
  92. var content = new object[Utils.MinListSize];
  93. content[0] = item;
  94. return new RubyArray(content, 0, 1);
  95. }
  96. /// <summary>
  97. /// Creates a blank instance of a RubyArray or its subclass given the Ruby class object.
  98. /// </summary>
  99. public static RubyArray/*!*/ CreateInstance(RubyClass/*!*/ rubyClass) {
  100. return (rubyClass.GetUnderlyingSystemType() == typeof(RubyArray)) ? new RubyArray() : new RubyArray.Subclass(rubyClass);
  101. }
  102. /// <summary>
  103. /// Creates an empty instance.
  104. /// Doesn't copy instance data.
  105. /// Preserves the class of the Array.
  106. /// </summary>
  107. public virtual RubyArray/*!*/ CreateInstance() {
  108. return new RubyArray();
  109. }
  110. object IDuplicable.Duplicate(RubyContext/*!*/ context, bool copySingletonMembers) {
  111. var result = CreateInstance();
  112. context.CopyInstanceData(this, result, copySingletonMembers);
  113. return result;
  114. }
  115. #endregion
  116. #region Flags
  117. public void RequireNotFrozen() {
  118. if ((_flags & IsFrozenFlag) != 0) {
  119. // throw in a separate method to allow inlining of the current one
  120. ThrowObjectFrozenException();
  121. }
  122. }
  123. private static void ThrowObjectFrozenException() {
  124. throw RubyExceptions.CreateObjectFrozenError();
  125. }
  126. private void Mutate() {
  127. RequireNotFrozen();
  128. }
  129. public bool IsTainted {
  130. get {
  131. return (_flags & IsTaintedFlag) != 0;
  132. }
  133. set {
  134. Mutate();
  135. _flags = (_flags & ~IsTaintedFlag) | (value ? IsTaintedFlag : 0);
  136. }
  137. }
  138. public bool IsUntrusted {
  139. get {
  140. return (_flags & IsUntrustedFlag) != 0;
  141. }
  142. set {
  143. Mutate();
  144. _flags = (_flags & ~IsUntrustedFlag) | (value ? IsUntrustedFlag : 0);
  145. }
  146. }
  147. public bool IsFrozen {
  148. get {
  149. return (_flags & IsFrozenFlag) != 0;
  150. }
  151. }
  152. void IRubyObjectState.Freeze() {
  153. Freeze();
  154. }
  155. public RubyArray/*!*/ Freeze() {
  156. _flags |= IsFrozenFlag;
  157. return this;
  158. }
  159. #endregion
  160. #region HashCode, Equality
  161. [MultiRuntimeAware]
  162. private static RubyUtils.RecursionTracker _HashTracker = new RubyUtils.RecursionTracker();
  163. public static int GetHashCode(UnaryOpStorage/*!*/ hashStorage, ConversionStorage<int>/*!*/ fixnumCast, IList/*!*/ self) {
  164. int hash = self.Count;
  165. using (IDisposable handle = _HashTracker.TrackObject(self)) {
  166. if (handle == null) {
  167. // hashing of recursive array
  168. return 0;
  169. }
  170. var hashSite = hashStorage.GetCallSite("hash");
  171. var toIntSite = fixnumCast.GetSite(ConvertToFixnumAction.Make(fixnumCast.Context));
  172. foreach (object item in self) {
  173. hash = (hash << 1) ^ toIntSite.Target(toIntSite, hashSite.Target(hashSite, item));
  174. }
  175. }
  176. return hash;
  177. }
  178. [MultiRuntimeAware]
  179. private static RubyUtils.RecursionTracker _EqualsTracker = new RubyUtils.RecursionTracker();
  180. public static bool Equals(BinaryOpStorage/*!*/ eqlStorage, IList/*!*/ self, object obj) {
  181. if (ReferenceEquals(self, obj)) {
  182. return true;
  183. }
  184. IList other = obj as IList;
  185. if (other == null || self.Count != other.Count) {
  186. return false;
  187. }
  188. using (IDisposable handleSelf = _EqualsTracker.TrackObject(self), handleOther = _EqualsTracker.TrackObject(other)) {
  189. if (handleSelf == null && handleOther == null) {
  190. // both arrays went recursive:
  191. return true;
  192. }
  193. var site = eqlStorage.GetCallSite("eql?");
  194. for (int i = 0; i < self.Count; i++) {
  195. if (!Protocols.IsEqual(site, self[i], other[i])) {
  196. return false;
  197. }
  198. }
  199. }
  200. return true;
  201. }
  202. #endregion
  203. #region Add, AddRange
  204. /// <summary>
  205. /// Adds count to _count and resizes the storage to make space for count elements if necessary.
  206. /// </summary>
  207. /// <param name="additionalCount">The number of elements that will be appended.</param>
  208. /// <param name="clear">
  209. /// True if the slots should be cleared (null initialized).
  210. /// The caller isn't filling the slots with non-null values.
  211. /// </param>
  212. /// <returns>The index of the first slot that will contain the new elements.</returns>
  213. private int ResizeForAppend(int additionalCount, bool clear) {
  214. int oldCount = _count;
  215. _count += additionalCount;
  216. if (_start + oldCount > _content.Length - additionalCount) {
  217. // not enough space for additional items:
  218. object[] newContent;
  219. if (_count > _content.Length) {
  220. newContent = new object[Utils.GetExpandedSize(_content, _count)];
  221. } else {
  222. newContent = _content;
  223. }
  224. Array.Copy(_content, _start, newContent, 0, oldCount);
  225. if (newContent == _content && (additionalCount < _start || clear)) {
  226. if (_start < oldCount) {
  227. Utils.Fill(newContent, oldCount, null, _start);
  228. } else {
  229. Utils.Fill(newContent, _start, null, oldCount);
  230. }
  231. }
  232. _content = newContent;
  233. _start = 0;
  234. }
  235. ObjectInvariant();
  236. return _start + oldCount;
  237. }
  238. public void Add(object item) {
  239. Mutate();
  240. int index = ResizeForAppend(1, false);
  241. _content[index] = item;
  242. }
  243. int IList.Add(object value) {
  244. int result = _count;
  245. Add(value);
  246. return result;
  247. }
  248. /// <summary>
  249. /// Ensures that the underlying storage is prepared to store at least the current number plus capacity items.
  250. /// </summary>
  251. /// <param name="capacity">Additional capacity.</param>
  252. /// <returns>Self.</returns>
  253. /// <remarks>
  254. /// Use to avoid reallocation of the underlying storage if the number of elements that will eventually by added to this array is known.
  255. /// Doesn't increase the number of elements in the array.
  256. /// Call <see cref="AddMultiple"/> to add multiple (possibly null) elements into the array.
  257. /// </remarks>
  258. public RubyArray/*!*/ AddCapacity(int capacity) {
  259. if (capacity < 0) {
  260. throw new ArgumentOutOfRangeException("capacity");
  261. }
  262. Mutate();
  263. int oldCount = _count;
  264. // We're not writing to the slots, so require them to be cleared so that we don't
  265. // accidentally keep objects alive whose references might have been in the slots.
  266. // (the resize could've just copied the content left to make space for new elements).
  267. ResizeForAppend(capacity, true);
  268. _count = oldCount;
  269. return this;
  270. }
  271. public RubyArray/*!*/ AddMultiple(int count, object value) {
  272. Mutate();
  273. if (value != null) {
  274. int start = ResizeForAppend(count, false);
  275. int end = start + count;
  276. for (int i = start; i < end; i++) {
  277. _content[i] = value;
  278. }
  279. } else {
  280. ResizeForAppend(count, true);
  281. }
  282. return this;
  283. }
  284. public RubyArray/*!*/ AddRange(IList/*!*/ items, int start, int count) {
  285. ContractUtils.RequiresNotNull(items, "items");
  286. ContractUtils.RequiresArrayRange(items.Count, start, count, "start", "count");
  287. Mutate();
  288. RubyArray array;
  289. object[] vector;
  290. if ((array = items as RubyArray) != null) {
  291. AddVector(array._content, array._start + start, count);
  292. } else if ((vector = items as object[]) != null) {
  293. AddVector(vector, start, count);
  294. } else {
  295. AddList(items, start, count);
  296. }
  297. return this;
  298. }
  299. public RubyArray/*!*/ AddRange(IEnumerable/*!*/ items) {
  300. ContractUtils.RequiresNotNull(items, "items");
  301. Mutate();
  302. RubyArray array;
  303. ICollection collection;
  304. object[] vector;
  305. if ((array = items as RubyArray) != null) {
  306. AddVector(array._content, array._start, array._count);
  307. } else if ((vector = items as object[]) != null) {
  308. AddVector(vector, 0, vector.Length);
  309. } else if ((collection = items as ICollection) != null) {
  310. AddCollection(collection);
  311. } else {
  312. AddSequence(items);
  313. }
  314. return this;
  315. }
  316. private void AddList(IList/*!*/ items, int start, int count) {
  317. int s = ResizeForAppend(count, false);
  318. for (int i = 0; i < count; i++) {
  319. _content[s + i] = items[start + i];
  320. }
  321. }
  322. internal void AddVector(object[]/*!*/ items, int start, int count) {
  323. int s = ResizeForAppend(count, false);
  324. Array.Copy(items, start, _content, s, count);
  325. }
  326. private void AddCollection(ICollection/*!*/ items) {
  327. int i = ResizeForAppend(items.Count, false);
  328. foreach (object item in items) {
  329. _content[i++] = item;
  330. }
  331. }
  332. private void AddSequence(IEnumerable/*!*/ items) {
  333. foreach (var item in items) {
  334. Add(item);
  335. }
  336. }
  337. #endregion
  338. #region Insert, InsertRange
  339. private int ResizeForInsertion(int index, int size) {
  340. if (_count + size > _content.Length) {
  341. object[] newContent = new object[Utils.GetExpandedSize(_content, _count + size)];
  342. Array.Copy(_content, _start, newContent, 0, index);
  343. Array.Copy(_content, _start + index, newContent, index + size, _count - index);
  344. _count += size;
  345. _content = newContent;
  346. return index;
  347. }
  348. int rindex = _start + index;
  349. int result = rindex;
  350. int spaceRight = _content.Length - _start - _count;
  351. int shiftLeft = 0, shiftRight = 0;
  352. if (_start >= size) {
  353. if (spaceRight >= size) {
  354. if (index < _count / 2) {
  355. shiftLeft = size;
  356. result -= size;
  357. } else {
  358. shiftRight = size;
  359. }
  360. } else {
  361. shiftLeft = size;
  362. result -= size;
  363. }
  364. } else if (spaceRight >= size) {
  365. shiftRight = size;
  366. } else {
  367. shiftLeft = _start;
  368. shiftRight = size - shiftLeft;
  369. result -= shiftLeft;
  370. }
  371. if (shiftLeft > 0) {
  372. var newStart = _start - shiftLeft;
  373. Array.Copy(_content, _start, _content, newStart, index);
  374. _start = newStart;
  375. }
  376. if (shiftRight > 0) {
  377. Array.Copy(_content, rindex, _content, rindex + shiftRight, _count - index);
  378. }
  379. _count += size;
  380. return result;
  381. }
  382. public void Insert(int index, object item) {
  383. ContractUtils.RequiresArrayInsertIndex(_count, index, "index");
  384. Mutate();
  385. int i = ResizeForInsertion(index, 1);
  386. _content[i] = item;
  387. ObjectInvariant();
  388. }
  389. public void InsertRange(int index, IList/*!*/ items, int start, int count) {
  390. ContractUtils.RequiresNotNull(items, "items");
  391. ContractUtils.RequiresArrayInsertIndex(_count, index, "index");
  392. ContractUtils.RequiresArrayRange(items.Count, start, count, "start", "count");
  393. Mutate();
  394. RubyArray array;
  395. object[] vector;
  396. if ((array = items as RubyArray) != null) {
  397. InsertVector(index, array._content, start, count);
  398. } else if ((vector = items as object[]) != null) {
  399. InsertVector(index, vector, start, count);
  400. } else {
  401. InsertList(index, items, start, count);
  402. }
  403. }
  404. public void InsertRange(int index, IEnumerable/*!*/ items) {
  405. ContractUtils.RequiresNotNull(items, "items");
  406. ContractUtils.RequiresArrayInsertIndex(_count, index, "index");
  407. Mutate();
  408. RubyArray array;
  409. IList list;
  410. object[] vector;
  411. if ((array = items as RubyArray) != null) {
  412. InsertArray(index, array);
  413. } else if ((vector = items as object[]) != null) {
  414. InsertVector(index, vector, 0, vector.Length);
  415. } else if ((list = items as IList) != null) {
  416. InsertList(index, list, 0, list.Count);
  417. } else {
  418. InsertArray(index, new RubyArray(items));
  419. }
  420. }
  421. private void InsertArray(int index, RubyArray/*!*/ array) {
  422. InsertVector(index, array._content, array._start, array._count);
  423. }
  424. private void InsertVector(int index, object[]/*!*/ items, int start, int count) {
  425. int s = ResizeForInsertion(index, count);
  426. Array.Copy(items, start, _content, s, count);
  427. }
  428. private void InsertList(int index, IList/*!*/ items, int start, int count) {
  429. int s = ResizeForInsertion(index, count);
  430. for (int i = 0; i < count; i++) {
  431. _content[s + i] = items[start + i];
  432. }
  433. }
  434. #endregion
  435. #region RemoveAt, RemoveRange, Remove, Clear
  436. public void RemoveRange(int index, int size) {
  437. ContractUtils.RequiresArrayRange(_count, index, size, "index", "size");
  438. Mutate();
  439. int newCount = _count - size;
  440. int length = _content.Length;
  441. int newLength = 2 * newCount - 1;
  442. if (newLength <= length / 2) {
  443. // resize:
  444. object[] newContent = new object[Math.Max(Utils.MinListSize, newLength)];
  445. Array.Copy(_content, _start, newContent, 0, index);
  446. Array.Copy(_content, _start + index + size, newContent, index, newCount - index);
  447. _content = newContent;
  448. _start = 0;
  449. } else if (index <= newCount / 2) {
  450. // shift right:
  451. int newStart = _start + size;
  452. Array.Copy(_content, _start, _content, newStart, index);
  453. Utils.Fill(_content, _start, null, size);
  454. _start = newStart;
  455. } else {
  456. // shift left:
  457. Array.Copy(_content, _start + index + size, _content, _start + index, newCount - index);
  458. Utils.Fill(_content, _start + newCount, null, size);
  459. }
  460. _count = newCount;
  461. ObjectInvariant();
  462. }
  463. public void RemoveAt(int index) {
  464. RemoveRange(index, 1);
  465. }
  466. public bool Remove(object item) {
  467. int index = IndexOf(item);
  468. if (index >= 0) {
  469. RemoveAt(index);
  470. return true;
  471. }
  472. return false;
  473. }
  474. void IList.Remove(object value) {
  475. Remove(value);
  476. }
  477. public void Clear() {
  478. Mutate();
  479. _content = ArrayUtils.EmptyObjects;
  480. _start = _count = 0;
  481. }
  482. #endregion
  483. #region CopyTo, ToArray
  484. public void CopyTo(object[]/*!*/ array, int index) {
  485. Array.Copy(_content, _start, array, index, _count);
  486. }
  487. void ICollection.CopyTo(Array array, int index) {
  488. Array.Copy(_content, _start, array, index, _count);
  489. }
  490. public object[]/*!*/ ToArray() {
  491. object[] result = new object[_count];
  492. CopyTo(result, 0);
  493. return result;
  494. }
  495. #endregion
  496. #region IndexOf, Contains, FindIndex, GetEnumerator
  497. public int IndexOf(object item) {
  498. return IndexOf(item, 0, _count);
  499. }
  500. public int IndexOf(object item, int startIndex) {
  501. return Array.IndexOf(_content, item, startIndex, _count - startIndex);
  502. }
  503. public int IndexOf(object item, int startIndex, int count) {
  504. return Array.IndexOf(_content, item, _start + startIndex, count);
  505. }
  506. #if !SILVERLIGHT && !WP75 // TODO: replace by linq
  507. public int FindIndex(Predicate<object> match) {
  508. return FindIndex(0, _count, match);
  509. }
  510. public int FindIndex(int startIndex, Predicate<object>/*!*/ match) {
  511. return FindIndex(startIndex, _count - startIndex, match);
  512. }
  513. public int FindIndex(int startIndex, int count, Predicate<object>/*!*/ match) {
  514. return Array.FindIndex(_content, _start + startIndex, count, match);
  515. }
  516. #endif
  517. public bool Contains(object item) {
  518. return IndexOf(item) >= 0;
  519. }
  520. public IEnumerator<object>/*!*/ GetEnumerator() {
  521. for (int i = 0, start = _start, count = _count; i < count; i++) {
  522. yield return _content[start + i];
  523. }
  524. }
  525. IEnumerator/*!*/ IEnumerable.GetEnumerator() {
  526. return ((IEnumerable<object>)this).GetEnumerator();
  527. }
  528. #endregion
  529. #region Misc
  530. public int Count {
  531. get { return _count; }
  532. }
  533. public bool IsReadOnly {
  534. get { return IsFrozen; }
  535. }
  536. bool IList.IsFixedSize {
  537. get { return IsReadOnly; }
  538. }
  539. bool ICollection.IsSynchronized {
  540. get { return false; }
  541. }
  542. object ICollection.SyncRoot {
  543. get { return this; }
  544. }
  545. public int Capacity {
  546. get { return _content.Length; }
  547. }
  548. public object this[int index] {
  549. get {
  550. if (index < 0 || index >= _count) {
  551. throw new IndexOutOfRangeException();
  552. }
  553. return _content[_start + index];
  554. }
  555. set {
  556. Mutate();
  557. int delta = index - _count;
  558. if (delta >= 0) {
  559. ResizeForAppend(delta + 1, true);
  560. }
  561. _content[_start + index] = value;
  562. }
  563. }
  564. public void Reverse() {
  565. Mutate();
  566. Array.Reverse(_content, _start, _count);
  567. }
  568. public void Sort() {
  569. Mutate();
  570. Array.Sort(_content, _start, _count);
  571. }
  572. public void Sort(Comparison<object>/*!*/ comparison) {
  573. Mutate();
  574. Array.Sort(_content, _start, _count, ArrayUtils.ToComparer(comparison));
  575. }
  576. #endregion
  577. #region DebugView
  578. internal string/*!*/ GetDebugView() {
  579. return RubyContext._Default != null ? RubyContext._Default.Inspect(this).ToString() : ToString();
  580. }
  581. #endregion
  582. }
  583. }