PageRenderTime 40ms CodeModel.GetById 12ms RepoModel.GetById 1ms app.codeStats 0ms

/Languages/Ruby/Libraries/Builtins/ArrayOps.cs

http://github.com/IronLanguages/main
C# | 292 lines | 214 code | 50 blank | 28 comment | 36 complexity | cb12c663b23c7eb68d2c5d54213a8d0b 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 System.Dynamic;
  20. using System.IO;
  21. using System.Runtime.CompilerServices;
  22. using System.Text;
  23. using IronRuby.Compiler;
  24. using IronRuby.Runtime;
  25. using IronRuby.Runtime.Calls;
  26. using IronRuby.Runtime.Conversions;
  27. using Microsoft.Scripting.Actions;
  28. using Microsoft.Scripting.Generation;
  29. using Microsoft.Scripting.Runtime;
  30. using Microsoft.Scripting.Utils;
  31. using Microsoft.Scripting.Math;
  32. namespace IronRuby.Builtins {
  33. /// <summary>
  34. /// Array inherits from Object, mixes in Enumerable.
  35. /// Ruby array is basically List{object}.
  36. /// </summary>
  37. [RubyClass("Array", Extends = typeof(RubyArray), Inherits = typeof(object)), Includes(typeof(IList), Copy = true)]
  38. public static class ArrayOps {
  39. #region Constructors
  40. [RubyConstructor]
  41. public static RubyArray/*!*/ CreateArray(RubyClass/*!*/ self) {
  42. return new RubyArray();
  43. }
  44. // Reinitialization. Not called when a factory/non-default ctor is called.
  45. [RubyMethod("initialize", RubyMethodAttributes.PrivateInstance)]
  46. public static RubyArray/*!*/ Reinitialize(RubyContext/*!*/ context, RubyArray/*!*/ self) {
  47. self.Clear();
  48. return self;
  49. }
  50. [RubyConstructor]
  51. public static object CreateArray(ConversionStorage<Union<IList, int>>/*!*/ toAryToInt,
  52. BlockParam block, RubyClass/*!*/ self, [NotNull]object/*!*/ arrayOrSize) {
  53. var site = toAryToInt.GetSite(CompositeConversionAction.Make(toAryToInt.Context, CompositeConversion.ToAryToInt));
  54. var union = site.Target(site, arrayOrSize);
  55. if (union.First != null) {
  56. // block ignored
  57. // TODO: implement copy-on-write
  58. return new RubyArray(union.First);
  59. } else if (block != null) {
  60. return CreateArray(block, union.Second);
  61. } else {
  62. return CreateArray(self, union.Second, null);
  63. }
  64. }
  65. [RubyMethod("initialize", RubyMethodAttributes.PrivateInstance)]
  66. public static object Reinitialize(ConversionStorage<Union<IList, int>>/*!*/ toAryToInt,
  67. BlockParam block, RubyArray/*!*/ self, [NotNull]object/*!*/ arrayOrSize) {
  68. var context = toAryToInt.Context;
  69. var site = toAryToInt.GetSite(CompositeConversionAction.Make(context, CompositeConversion.ToAryToInt));
  70. var union = site.Target(site, arrayOrSize);
  71. if (union.First != null) {
  72. // block ignored
  73. return Reinitialize(self, union.First);
  74. } else if (block != null) {
  75. return Reinitialize(block, self, union.Second);
  76. } else {
  77. return ReinitializeByRepeatedValue(context, self, union.Second, null);
  78. }
  79. }
  80. private static RubyArray/*!*/ Reinitialize(RubyArray/*!*/ self, IList/*!*/ other) {
  81. Assert.NotNull(self, other);
  82. if (other != self) {
  83. self.Clear();
  84. IListOps.AddRange(self, other);
  85. }
  86. return self;
  87. }
  88. private static object CreateArray(BlockParam/*!*/ block, int size) {
  89. return Reinitialize(block, new RubyArray(), size);
  90. }
  91. [RubyConstructor]
  92. public static RubyArray/*!*/ CreateArray(BlockParam/*!*/ block, RubyClass/*!*/ self, [DefaultProtocol]int size, object value) {
  93. return Reinitialize(block, new RubyArray(), size, value);
  94. }
  95. [RubyMethod("initialize", RubyMethodAttributes.PrivateInstance)]
  96. public static RubyArray/*!*/ Reinitialize(BlockParam/*!*/ block, RubyArray/*!*/ self, int size, object value) {
  97. block.RubyContext.ReportWarning("block supersedes default value argument");
  98. Reinitialize(block, self, size);
  99. return self;
  100. }
  101. private static object Reinitialize(BlockParam/*!*/ block, RubyArray/*!*/ self, int size) {
  102. CheckArraySize(size);
  103. self.Clear();
  104. for (int i = 0; i < size; i++) {
  105. object item;
  106. if (block.Yield(i, out item)) {
  107. return item;
  108. }
  109. self.Add(item);
  110. }
  111. return self;
  112. }
  113. [RubyConstructor]
  114. public static RubyArray/*!*/ CreateArray(RubyClass/*!*/ self, [DefaultProtocol]int size, object value) {
  115. CheckArraySize(size);
  116. return new RubyArray().AddMultiple(size, value);
  117. }
  118. // Reinitialization. Not called when a factory/non-default ctor is called.
  119. [RubyMethod("initialize", RubyMethodAttributes.PrivateInstance)]
  120. public static RubyArray/*!*/ ReinitializeByRepeatedValue(RubyContext/*!*/ context, RubyArray/*!*/ self, [DefaultProtocol]int size, object value) {
  121. CheckArraySize(size);
  122. self.Clear();
  123. self.AddMultiple(size, value);
  124. return self;
  125. }
  126. private static void CheckArraySize(int size) {
  127. if (size < 0) {
  128. throw RubyExceptions.CreateArgumentError("negative array size");
  129. }
  130. if (IntPtr.Size == 4 && size > Int32.MaxValue / 4) {
  131. throw RubyExceptions.CreateArgumentError("array size too big");
  132. }
  133. }
  134. [RubyMethod("[]", RubyMethodAttributes.PublicSingleton)]
  135. public static RubyArray/*!*/ MakeArray(RubyClass/*!*/ self, params object[] args) {
  136. // neither "new" nor "initialize" is called:
  137. RubyArray result = RubyArray.CreateInstance(self);
  138. foreach (object obj in args) {
  139. result.Add(obj);
  140. }
  141. return result;
  142. }
  143. #endregion
  144. #region to_a, to_ary, try_convert
  145. [RubyMethod("to_a")]
  146. public static RubyArray/*!*/ ToArray(RubyArray/*!*/ self) {
  147. return self is RubyArray.Subclass ? new RubyArray(self) : self;
  148. }
  149. [RubyMethod("to_ary")]
  150. public static RubyArray/*!*/ ToExplicitArray(RubyArray/*!*/ self) {
  151. return self;
  152. }
  153. [RubyMethod("try_convert", RubyMethodAttributes.PublicSingleton)]
  154. public static IList TryConvert(ConversionStorage<IList>/*!*/ toAry, RubyClass/*!*/ self, object obj) {
  155. var site = toAry.GetSite(TryConvertToArrayAction.Make(toAry.Context));
  156. return site.Target(site, obj);
  157. }
  158. #endregion
  159. #region pack
  160. [RubyMethod("pack")]
  161. public static MutableString/*!*/ Pack(
  162. ConversionStorage<IntegerValue>/*!*/ integerConversion,
  163. ConversionStorage<double>/*!*/ floatConversion,
  164. ConversionStorage<MutableString>/*!*/ stringCast,
  165. ConversionStorage<MutableString>/*!*/ tosConversion,
  166. RubyArray/*!*/ self, [DefaultProtocol, NotNull]MutableString/*!*/ format) {
  167. return RubyEncoder.Pack(integerConversion, floatConversion, stringCast, tosConversion, self, format);
  168. }
  169. #endregion
  170. #region sort!, sort
  171. private sealed class BreakException : Exception {
  172. }
  173. [RubyMethod("sort")]
  174. public static object Sort(ComparisonStorage/*!*/ comparisonStorage, BlockParam block, RubyArray/*!*/ self) {
  175. RubyArray result = self.CreateInstance();
  176. IListOps.Replace(result, self);
  177. return SortInPlace(comparisonStorage, block, result);
  178. }
  179. [RubyMethod("sort!")]
  180. public static object SortInPlace(ComparisonStorage/*!*/ comparisonStorage, BlockParam block, RubyArray/*!*/ self) {
  181. StrongBox<object> breakResult;
  182. RubyArray result = SortInPlace(comparisonStorage, block, self, out breakResult);
  183. if (breakResult != null) {
  184. return breakResult.Value;
  185. } else {
  186. return result;
  187. }
  188. }
  189. public static RubyArray/*!*/ SortInPlace(ComparisonStorage/*!*/ comparisonStorage, RubyArray/*!*/ self) {
  190. StrongBox<object> breakResult;
  191. RubyArray result = SortInPlace(comparisonStorage, null, self, out breakResult);
  192. Debug.Assert(result != null && breakResult == null);
  193. return result;
  194. }
  195. internal static RubyArray SortInPlace(ComparisonStorage/*!*/ comparisonStorage, BlockParam block, RubyArray/*!*/ self, out StrongBox<object> breakResult) {
  196. breakResult = null;
  197. var context = comparisonStorage.Context;
  198. // TODO: this does more comparisons (and in a different order) than
  199. // Ruby's sort. Also, control flow won't work because List<T>.Sort wraps
  200. // exceptions from the comparer & rethrows. We need to rewrite a version of quicksort
  201. // that behaves like Ruby's sort.
  202. if (block == null) {
  203. self.Sort((x, y) => Protocols.Compare(comparisonStorage, x, y));
  204. } else {
  205. object nonRefBreakResult = null;
  206. try {
  207. self.Sort((x, y) =>
  208. {
  209. object result = null;
  210. if (block.Yield(x, y, out result)) {
  211. nonRefBreakResult = result;
  212. throw new BreakException();
  213. }
  214. if (result == null) {
  215. throw RubyExceptions.MakeComparisonError(context, x, y);
  216. }
  217. return Protocols.ConvertCompareResult(comparisonStorage, result);
  218. });
  219. } catch (InvalidOperationException e) {
  220. if (e.InnerException == null) {
  221. throw;
  222. }
  223. if (e.InnerException is BreakException) {
  224. breakResult = new StrongBox<object>(nonRefBreakResult);
  225. return null;
  226. } else {
  227. throw e.InnerException;
  228. }
  229. }
  230. }
  231. return self;
  232. }
  233. #endregion
  234. #region reverse!
  235. [RubyMethod("reverse!")]
  236. public static RubyArray/*!*/ InPlaceReverse(RubyContext/*!*/ context, RubyArray/*!*/ self) {
  237. self.Reverse();
  238. return self;
  239. }
  240. #endregion
  241. }
  242. }