PageRenderTime 58ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

/Languages/Ruby/Libraries/Builtins/Enumerable.cs

http://github.com/IronLanguages/main
C# | 1064 lines | 795 code | 210 blank | 59 comment | 147 complexity | 648e1590aaabc2d340a282eeb3f81980 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.Reflection;
  20. using System.Runtime.CompilerServices;
  21. using System.Runtime.InteropServices;
  22. using IronRuby.Runtime;
  23. using Microsoft.Scripting.Generation;
  24. using Microsoft.Scripting.Runtime;
  25. using Microsoft.Scripting.Utils;
  26. using IronRuby.Runtime.Calls;
  27. namespace IronRuby.Builtins {
  28. using EachSite = Func<CallSite, object, Proc, object>;
  29. using EachSiteN = Func<CallSite, object, Proc, IList, object>;
  30. [RubyModule("Enumerable")]
  31. public static class Enumerable {
  32. internal static object Each(CallSiteStorage<EachSite>/*!*/ each, object self, Proc/*!*/ block) {
  33. var site = each.GetCallSite("each", new RubyCallSignature(0, RubyCallFlags.HasImplicitSelf | RubyCallFlags.HasBlock));
  34. return site.Target(site, self, block);
  35. }
  36. internal static object Each(CallSiteStorage<EachSiteN>/*!*/ each, object self, IList/*!*/ args, Proc/*!*/ block) {
  37. var site = each.GetCallSite("each", new RubyCallSignature(0, RubyCallFlags.HasImplicitSelf | RubyCallFlags.HasBlock | RubyCallFlags.HasSplattedArgument));
  38. return site.Target(site, self, block, args);
  39. }
  40. #region all?, any?, none?
  41. [RubyMethod("all?")]
  42. public static object TrueForAll(CallSiteStorage<EachSite>/*!*/ each, BlockParam predicate, object self) {
  43. return TrueForItems(each, predicate, self, false, false);
  44. }
  45. [RubyMethod("none?")]
  46. public static object TrueForNone(CallSiteStorage<EachSite>/*!*/ each, BlockParam predicate, object self) {
  47. return TrueForItems(each, predicate, self, true, false);
  48. }
  49. [RubyMethod("any?")]
  50. public static object TrueForAny(CallSiteStorage<EachSite>/*!*/ each, BlockParam predicate, object self) {
  51. return TrueForItems(each, predicate, self, true, true);
  52. }
  53. private static object TrueForItems(CallSiteStorage<EachSite>/*!*/ each, BlockParam predicate, object self, bool stop, bool positiveResult) {
  54. object result = ScriptingRuntimeHelpers.BooleanToObject(!positiveResult);
  55. Each(each, self, Proc.Create(each.Context, delegate(BlockParam/*!*/ selfBlock, object _, object item) {
  56. if (predicate != null) {
  57. object blockResult;
  58. if (predicate.Yield(item, out blockResult)) {
  59. result = blockResult;
  60. return selfBlock.PropagateFlow(predicate, blockResult);
  61. }
  62. item = blockResult;
  63. }
  64. bool isTrue = Protocols.IsTrue(item);
  65. if (isTrue == stop) {
  66. result = ScriptingRuntimeHelpers.BooleanToObject(positiveResult);
  67. return selfBlock.Break(result);
  68. }
  69. return null;
  70. }));
  71. return result;
  72. }
  73. #endregion
  74. #region collect, map
  75. [RubyMethod("collect")]
  76. [RubyMethod("map")]
  77. public static Enumerator/*!*/ GetMapEnumerator(CallSiteStorage<EachSite>/*!*/ each, BlockParam collector, object self) {
  78. return new Enumerator((_, block) => Map(each, block, self));
  79. }
  80. /// <summary>
  81. /// <code>
  82. /// def map
  83. /// result = []
  84. /// each do |*args|
  85. /// result.push yield(*args)
  86. /// end
  87. /// result
  88. /// end
  89. /// </code>
  90. /// </summary>
  91. [RubyMethod("collect")]
  92. [RubyMethod("map")]
  93. public static object Map(CallSiteStorage<EachSite>/*!*/ each, [NotNull]BlockParam/*!*/ collector, object self) {
  94. RubyArray resultArray = new RubyArray();
  95. object result = resultArray;
  96. if (collector.Proc.Dispatcher.ParameterCount <= 1 && !collector.Proc.Dispatcher.HasUnsplatParameter && !collector.Proc.Dispatcher.HasProcParameter) {
  97. // optimize for a block with a single parameter:
  98. Each(each, self, Proc.Create(each.Context, delegate(BlockParam/*!*/ selfBlock, object _, object item) {
  99. object blockResult;
  100. if (collector.Yield(item, out blockResult)) {
  101. result = blockResult;
  102. return selfBlock.PropagateFlow(collector, blockResult);
  103. }
  104. resultArray.Add(blockResult);
  105. return null;
  106. }));
  107. } else {
  108. // general case:
  109. Each(each, self, Proc.Create(each.Context, 0, delegate(BlockParam/*!*/ selfBlock, object _, object[] __, RubyArray args) {
  110. Debug.Assert(__.Length == 0);
  111. object blockResult;
  112. if (collector.YieldSplat(args, out blockResult)) {
  113. result = blockResult;
  114. return selfBlock.PropagateFlow(collector, blockResult);
  115. }
  116. resultArray.Add(blockResult);
  117. return null;
  118. }));
  119. }
  120. return result;
  121. }
  122. #endregion
  123. #region detect, find, find_index
  124. [RubyMethod("detect")]
  125. [RubyMethod("find")]
  126. public static Enumerator/*!*/ GetFindEnumerator(CallSiteStorage<EachSite>/*!*/ each, CallSiteStorage<Func<CallSite, object, object>>/*!*/ callStorage,
  127. BlockParam predicate, object self, [Optional]object ifNone) {
  128. return new Enumerator((_, block) => Find(each, callStorage, block, self, ifNone));
  129. }
  130. [RubyMethod("detect")]
  131. [RubyMethod("find")]
  132. public static object Find(CallSiteStorage<EachSite>/*!*/ each, CallSiteStorage<Func<CallSite, object, object>>/*!*/ callStorage,
  133. [NotNull]BlockParam/*!*/ predicate, object self, [Optional]object ifNone) {
  134. object result = Missing.Value;
  135. Each(each, self, Proc.Create(each.Context, delegate(BlockParam/*!*/ selfBlock, object _, object item) {
  136. object blockResult;
  137. if (predicate.Yield(item, out blockResult)) {
  138. result = blockResult;
  139. return selfBlock.PropagateFlow(predicate, blockResult);
  140. }
  141. if (Protocols.IsTrue(blockResult)) {
  142. result = item;
  143. return selfBlock.Break(item);
  144. }
  145. return null;
  146. }));
  147. if (result == Missing.Value) {
  148. if (ifNone == Missing.Value || ifNone == null) {
  149. return null;
  150. }
  151. var site = callStorage.GetCallSite("call", 0);
  152. result = site.Target(site, ifNone);
  153. }
  154. return result;
  155. }
  156. [RubyMethod("find_index")]
  157. public static Enumerator/*!*/ GetFindIndexEnumerator(CallSiteStorage<EachSite>/*!*/ each, BlockParam predicate, object self) {
  158. return new Enumerator((_, block) => FindIndex(each, block, self));
  159. }
  160. [RubyMethod("find_index")]
  161. public static object FindIndex(CallSiteStorage<EachSite>/*!*/ each, [NotNull]BlockParam/*!*/ predicate, object self) {
  162. int index = 0;
  163. object result = null;
  164. Each(each, self, Proc.Create(each.Context, delegate(BlockParam/*!*/ selfBlock, object _, object item) {
  165. object blockResult;
  166. if (predicate.Yield(item, out blockResult)) {
  167. result = blockResult;
  168. return selfBlock.PropagateFlow(predicate, blockResult);
  169. }
  170. if (Protocols.IsTrue(blockResult)) {
  171. result = ScriptingRuntimeHelpers.Int32ToObject(index);
  172. return selfBlock.Break(null);
  173. }
  174. index++;
  175. return null;
  176. }));
  177. return result;
  178. }
  179. [RubyMethod("find_index")]
  180. public static object FindIndex(CallSiteStorage<EachSite>/*!*/ each, BinaryOpStorage/*!*/ equals, BlockParam predicate, object self, object value) {
  181. if (predicate != null) {
  182. each.Context.ReportWarning("given block not used");
  183. }
  184. int index = 0;
  185. object result = null;
  186. Each(each, self, Proc.Create(each.Context, delegate(BlockParam/*!*/ selfBlock, object _, object item) {
  187. if (Protocols.IsEqual(equals, item, value)) {
  188. result = ScriptingRuntimeHelpers.Int32ToObject(index);
  189. return selfBlock.Break(null);
  190. }
  191. index++;
  192. return null;
  193. }));
  194. return result;
  195. }
  196. #endregion
  197. #region each_with_index
  198. [RubyMethod("each_with_index")]
  199. public static Enumerator/*!*/ GetEachWithIndexEnumerator(CallSiteStorage<EachSite>/*!*/ each, BlockParam block, object self) {
  200. return new Enumerator((_, innerBlock) => EachWithIndex(each, innerBlock, self));
  201. }
  202. [RubyMethod("each_with_index")]
  203. public static object EachWithIndex(CallSiteStorage<EachSite>/*!*/ each, [NotNull]BlockParam/*!*/ block, object self) {
  204. int index = 0;
  205. object result = self;
  206. Each(each, self, Proc.Create(each.Context, delegate(BlockParam/*!*/ selfBlock, object _, object item) {
  207. object blockResult;
  208. if (block.Yield(item, index, out blockResult)) {
  209. result = blockResult;
  210. return selfBlock.PropagateFlow(block, blockResult);
  211. }
  212. index += 1;
  213. return null;
  214. }));
  215. return result;
  216. }
  217. #endregion
  218. #region entries, to_a
  219. [RubyMethod("entries")]
  220. [RubyMethod("to_a")]
  221. public static RubyArray/*!*/ ToArray(CallSiteStorage<EachSite>/*!*/ each, object self) {
  222. RubyArray data = new RubyArray();
  223. Each(each, self, Proc.Create(each.Context, delegate(BlockParam/*!*/ selfBlock, object _, object item) {
  224. data.Add(item);
  225. return null;
  226. }));
  227. return data;
  228. }
  229. [RubyMethod("entries")]
  230. [RubyMethod("to_a")]
  231. public static RubyArray/*!*/ ToArray(CallSiteStorage<EachSiteN>/*!*/ each, object self, params object[] args) {
  232. RubyArray data = new RubyArray();
  233. Each(each, self, args, Proc.Create(each.Context, delegate(BlockParam/*!*/ selfBlock, object _, object item) {
  234. data.Add(item);
  235. return null;
  236. }));
  237. return data;
  238. }
  239. #endregion
  240. #region find_all, select, reject
  241. [RubyMethod("find_all")]
  242. [RubyMethod("select")]
  243. public static object Select(CallSiteStorage<EachSite>/*!*/ each, BlockParam predicate, object self) {
  244. return (predicate != null) ? FilterImpl(each, predicate, self, true) : FilterEnum(each, predicate, self, true);
  245. }
  246. [RubyMethod("reject")]
  247. public static object Reject(CallSiteStorage<EachSite>/*!*/ each, BlockParam predicate, object self) {
  248. return (predicate != null) ? FilterImpl(each, predicate, self, false) : FilterEnum(each, predicate, self, false);
  249. }
  250. private static Enumerator/*!*/ FilterEnum(CallSiteStorage<EachSite>/*!*/ each, BlockParam predicate, object self, bool acceptingValue) {
  251. return new Enumerator((_, block) => FilterImpl(each, block, self, acceptingValue));
  252. }
  253. private static object FilterImpl(CallSiteStorage<EachSite>/*!*/ each, BlockParam/*!*/ predicate, object self, bool acceptingValue) {
  254. RubyArray resultArray = new RubyArray();
  255. object result = resultArray;
  256. Each(each, self, Proc.Create(each.Context, delegate(BlockParam/*!*/ selfBlock, object _, object item) {
  257. object blockResult;
  258. if (predicate.Yield(item, out blockResult)) {
  259. result = blockResult;
  260. return selfBlock.PropagateFlow(predicate, blockResult);
  261. }
  262. // Check if the result is what we expect (use true to select, false to reject)
  263. if (Protocols.IsTrue(blockResult) == acceptingValue) {
  264. resultArray.Add(item);
  265. }
  266. return null;
  267. }));
  268. return result;
  269. }
  270. #endregion
  271. #region grep
  272. [RubyMethod("grep")]
  273. public static object Grep(CallSiteStorage<EachSite>/*!*/ each, BinaryOpStorage/*!*/ caseEquals,
  274. BlockParam action, object self, object pattern) {
  275. RubyArray resultArray = new RubyArray();
  276. object result = resultArray;
  277. var site = caseEquals.GetCallSite("===");
  278. Each(each, self, Proc.Create(each.Context, delegate(BlockParam/*!*/ selfBlock, object _, object item) {
  279. if (RubyOps.IsTrue(site.Target(site, pattern, item))) {
  280. if (action != null && action.Yield(item, out item)) {
  281. result = item;
  282. return selfBlock.PropagateFlow(action, item);
  283. }
  284. resultArray.Add(item);
  285. }
  286. return null;
  287. }));
  288. return result;
  289. }
  290. #endregion
  291. #region include?, member?
  292. [RubyMethod("include?")]
  293. [RubyMethod("member?")]
  294. public static object Contains(CallSiteStorage<EachSite>/*!*/ each, BinaryOpStorage/*!*/ equals, object self, object value) {
  295. object result = ScriptingRuntimeHelpers.BooleanToObject(false);
  296. Each(each, self, Proc.Create(each.Context, delegate(BlockParam/*!*/ selfBlock, object _, object item) {
  297. if (Protocols.IsEqual(equals, item, value)) {
  298. result = ScriptingRuntimeHelpers.BooleanToObject(true);
  299. return selfBlock.Break(result);
  300. }
  301. return null;
  302. }));
  303. return result;
  304. }
  305. #endregion
  306. #region inject/reduce
  307. [RubyMethod("reduce")]
  308. [RubyMethod("inject")]
  309. public static object Inject(CallSiteStorage<EachSite>/*!*/ each, [NotNull]BlockParam/*!*/ operation, object self, [Optional]object initial) {
  310. return Inject(each, operation, null, null, self, initial);
  311. }
  312. [RubyMethod("reduce")]
  313. [RubyMethod("inject")]
  314. public static object Inject(CallSiteStorage<EachSite>/*!*/ each, RubyScope/*!*/ scope, object self, [Optional]object initial,
  315. [DefaultProtocol, NotNull]string/*!*/ operatorName) {
  316. return Inject(each, null, scope, operatorName, self, initial);
  317. }
  318. // def inject(result = Undefined)
  319. // each do |*args|
  320. // arg = args.size <= 1 ? args[0] : args
  321. // if result == Undefined
  322. // result = arg
  323. // else
  324. // result = yield(result, arg)
  325. // end
  326. // end
  327. // result
  328. // end
  329. internal static object Inject(CallSiteStorage<EachSite>/*!*/ each, BlockParam operation, RubyScope scope, string operatorName, object self, object initial) {
  330. Debug.Assert(operation != null ^ (scope != null && operatorName != null));
  331. var site = (operatorName == null) ? null : each.Context.GetOrCreateSendSite<Func<CallSite, RubyScope, object, object, object>>(
  332. operatorName, new RubyCallSignature(1, RubyCallFlags.HasScope | RubyCallFlags.HasImplicitSelf)
  333. );
  334. object result = initial;
  335. Each(each, self, Proc.Create(each.Context, 0, delegate(BlockParam/*!*/ selfBlock, object _, object[] __, RubyArray/*!*/ args) {
  336. Debug.Assert(__.Length == 0);
  337. // TODO: this is weird but is actually exploited in Rack::Utils::HeaderHash
  338. // TODO: Can we optimize (special dispatcher)? We allocate splatte array for each iteration.
  339. object value = args.Count == 0 ? null : args.Count == 1 ? args[0] : args;
  340. if (result == Missing.Value) {
  341. result = value;
  342. return null;
  343. }
  344. if (site != null) {
  345. result = site.Target(site, scope, result, value);
  346. } else if (operation.Yield(result, value, out result)) {
  347. return selfBlock.PropagateFlow(operation, result);
  348. }
  349. return null;
  350. }));
  351. return result != Missing.Value ? result : null;
  352. }
  353. #endregion
  354. #region min, max, minmax
  355. [RubyMethod("max")]
  356. public static object GetMaximum(CallSiteStorage<EachSite>/*!*/ each, ComparisonStorage/*!*/ comparisonStorage, BlockParam comparer, object self) {
  357. return GetExtreme(each, comparisonStorage, comparer, self, +1);
  358. }
  359. [RubyMethod("min")]
  360. public static object GetMinimum(CallSiteStorage<EachSite>/*!*/ each, ComparisonStorage/*!*/ comparisonStorage, BlockParam comparer, object self) {
  361. return GetExtreme(each, comparisonStorage, comparer, self, -1);
  362. }
  363. private static object GetExtreme(CallSiteStorage<EachSite>/*!*/ each, ComparisonStorage/*!*/ comparisonStorage, BlockParam comparer, object self, int comparisonValue) {
  364. bool firstItem = true;
  365. object result = null;
  366. Each(each, self, Proc.Create(each.Context, delegate(BlockParam/*!*/ selfBlock, object _, object item) {
  367. if (firstItem) {
  368. result = item;
  369. firstItem = false;
  370. return null;
  371. }
  372. object blockResult;
  373. int? compareResult = CompareItems(comparisonStorage, item, result, comparer, out blockResult);
  374. if (compareResult == null) {
  375. result = blockResult;
  376. return selfBlock.PropagateFlow(comparer, blockResult);
  377. }
  378. // Check if we have found the new minimum or maximum (+1 to select max, -1 to select min)
  379. if (compareResult == comparisonValue) {
  380. result = item;
  381. }
  382. return null;
  383. }));
  384. return result;
  385. }
  386. [RubyMethod("minmax")]
  387. public static object GetExtremes(CallSiteStorage<EachSite>/*!*/ each, ComparisonStorage/*!*/ comparisonStorage, BlockParam comparer, object self) {
  388. bool hasOddItem = false, hasMinMax = false, blockJumped = false;
  389. object oddItem = null;
  390. object blockResult = null;
  391. object min = null, max = null;
  392. Func<IronRuby.Runtime.BlockParam,object,object,object> blockProc = delegate(BlockParam/*!*/ selfBlock, object _, object item) {
  393. if (hasOddItem) {
  394. hasOddItem = false;
  395. int? compareResult = CompareItems(comparisonStorage, oddItem, item, comparer, out blockResult);
  396. if (compareResult == null) {
  397. goto BlockJumped;
  398. }
  399. if (compareResult > 0) {
  400. // oddItem > item
  401. object obj = item;
  402. item = oddItem;
  403. oddItem = obj;
  404. }
  405. // oddItem <= item
  406. if (hasMinMax) {
  407. compareResult = CompareItems(comparisonStorage, oddItem, min, comparer, out blockResult);
  408. if (compareResult == null) {
  409. goto BlockJumped;
  410. }
  411. if (compareResult < 0) {
  412. // oddItem < min
  413. min = oddItem;
  414. }
  415. compareResult = CompareItems(comparisonStorage, item, max, comparer, out blockResult);
  416. if (compareResult == null) {
  417. goto BlockJumped;
  418. }
  419. if (compareResult > 0) {
  420. // item > max
  421. max = item;
  422. }
  423. } else {
  424. min = oddItem;
  425. max = item;
  426. hasMinMax = true;
  427. }
  428. } else {
  429. hasOddItem = true;
  430. oddItem = item;
  431. }
  432. return null;
  433. BlockJumped:
  434. blockJumped = true;
  435. return selfBlock.PropagateFlow(comparer, blockResult);
  436. };
  437. Each(each, self, Proc.Create(each.Context, blockProc));
  438. if (blockJumped) {
  439. return blockResult;
  440. }
  441. if (!hasMinMax) {
  442. return hasOddItem ? new RubyArray(2) { oddItem, oddItem } : new RubyArray(2) { null, null };
  443. }
  444. if (hasOddItem) {
  445. int? compareResult = CompareItems(comparisonStorage, oddItem, min, comparer, out blockResult);
  446. if (compareResult == null) {
  447. return blockResult;
  448. }
  449. if (compareResult < 0) {
  450. min = oddItem;
  451. }
  452. compareResult = CompareItems(comparisonStorage, oddItem, max, comparer, out blockResult);
  453. if (compareResult == null) {
  454. return blockResult;
  455. }
  456. if (compareResult > 0) {
  457. max = oddItem;
  458. }
  459. }
  460. return new RubyArray(2) { min, max };
  461. }
  462. private static int? CompareItems(ComparisonStorage/*!*/ comparisonStorage, object left, object right, BlockParam comparer, out object blockResult) {
  463. if (comparer != null) {
  464. if (comparer.Yield(left, right, out blockResult)) {
  465. return null;
  466. }
  467. if (blockResult == null) {
  468. throw RubyExceptions.MakeComparisonError(comparisonStorage.Context, left, right);
  469. }
  470. return Protocols.ConvertCompareResult(comparisonStorage, blockResult);
  471. } else {
  472. blockResult = null;
  473. return Protocols.Compare(comparisonStorage, left, right);
  474. }
  475. }
  476. #endregion
  477. #region TODO: min_by, max_by, minmax_by
  478. #endregion
  479. #region partition
  480. [RubyMethod("partition")]
  481. public static Enumerator/*!*/ GetPartitionEnumerator(CallSiteStorage<EachSite>/*!*/ each, BlockParam predicate, object self) {
  482. return new Enumerator((_, block) => Partition(each, block, self));
  483. }
  484. [RubyMethod("partition")]
  485. public static object Partition(CallSiteStorage<EachSite>/*!*/ each, [NotNull]BlockParam/*!*/ predicate, object self) {
  486. RubyArray trueSet = new RubyArray();
  487. RubyArray falseSet = new RubyArray();
  488. RubyArray pair = new RubyArray(2);
  489. pair.Add(trueSet);
  490. pair.Add(falseSet);
  491. object result = pair;
  492. Each(each, self, Proc.Create(each.Context, delegate(BlockParam/*!*/ selfBlock, object _, object item) {
  493. object blockResult;
  494. if (predicate.Yield(item, out blockResult)) {
  495. result = blockResult;
  496. return selfBlock.PropagateFlow(predicate, blockResult);
  497. }
  498. if (Protocols.IsTrue(blockResult)) {
  499. trueSet.Add(item);
  500. } else {
  501. falseSet.Add(item);
  502. }
  503. return null;
  504. }));
  505. return result;
  506. }
  507. #endregion
  508. #region sort, sort_by
  509. [RubyMethod("sort")]
  510. public static object Sort(CallSiteStorage<EachSite>/*!*/ each, ComparisonStorage/*!*/ comparisonStorage, BlockParam keySelector, object self) {
  511. return ArrayOps.SortInPlace(comparisonStorage, keySelector, ToArray(each, self));
  512. }
  513. [RubyMethod("sort_by")]
  514. public static object SortBy(CallSiteStorage<EachSite>/*!*/ each, ComparisonStorage/*!*/ comparisonStorage, BlockParam keySelector, object self) {
  515. // collect key, value pairs
  516. List<KeyValuePair<object, object>> keyValuePairs = new List<KeyValuePair<object, object>>();
  517. object result = null;
  518. // Collect the key, value pairs
  519. Each(each, self, Proc.Create(each.Context, delegate(BlockParam/*!*/ selfBlock, object _, object item) {
  520. if (keySelector == null) {
  521. throw RubyExceptions.NoBlockGiven();
  522. }
  523. object key;
  524. if (keySelector.Yield(item, out key)) {
  525. keyValuePairs = null;
  526. result = key;
  527. return selfBlock.PropagateFlow(keySelector, key);
  528. }
  529. keyValuePairs.Add(new KeyValuePair<object, object>(key, item));
  530. return null;
  531. }));
  532. if (keyValuePairs == null) {
  533. return result;
  534. }
  535. // sort by keys
  536. keyValuePairs.Sort(delegate(KeyValuePair<object, object> x, KeyValuePair<object, object> y) {
  537. return Protocols.Compare(comparisonStorage, x.Key, y.Key);
  538. });
  539. // return values
  540. RubyArray results = new RubyArray(keyValuePairs.Count);
  541. foreach (KeyValuePair<object, object> pair in keyValuePairs) {
  542. results.Add(pair.Value);
  543. }
  544. return results;
  545. }
  546. #endregion
  547. #region zip
  548. [RubyMethod("zip")]
  549. public static object Zip(CallSiteStorage<EachSite>/*!*/ each, BlockParam block, object self, [DefaultProtocol, NotNullItems]params IList/*!*/[]/*!*/ args) {
  550. RubyArray results = (block == null) ? new RubyArray() : null;
  551. object result = results;
  552. int index = 0;
  553. Each(each, self, Proc.Create(each.Context, delegate(BlockParam/*!*/ selfBlock, object _, object item) {
  554. // Collect items
  555. RubyArray array = new RubyArray(args.Length + 1);
  556. array.Add(item);
  557. foreach (IList otherArray in args) {
  558. if (index < otherArray.Count) {
  559. array.Add(otherArray[index]);
  560. } else {
  561. array.Add(null);
  562. }
  563. }
  564. index += 1;
  565. if (block != null) {
  566. object blockResult;
  567. if (block.Yield(array, out blockResult)) {
  568. result = blockResult;
  569. return selfBlock.PropagateFlow(block, blockResult);
  570. }
  571. } else {
  572. results.Add(array);
  573. }
  574. return null;
  575. }));
  576. return result;
  577. }
  578. #endregion
  579. #region count, one?
  580. [RubyMethod("count")]
  581. public static int Count(CallSiteStorage<EachSite>/*!*/ each, object self) {
  582. int result = 0;
  583. Each(each, self, Proc.Create(each.Context, delegate(BlockParam/*!*/ selfBlock, object _, object item) {
  584. result++;
  585. return null;
  586. }));
  587. return result;
  588. }
  589. [RubyMethod("count")]
  590. public static int Count(CallSiteStorage<EachSite>/*!*/ each, BinaryOpStorage/*!*/ equals, BlockParam comparer, object self, object value) {
  591. if (comparer != null) {
  592. each.Context.ReportWarning("given block not used");
  593. }
  594. int result = 0;
  595. Each(each, self, Proc.Create(each.Context, delegate(BlockParam/*!*/ selfBlock, object _, object item) {
  596. if (Protocols.IsEqual(equals, item, value)) {
  597. result++;
  598. }
  599. return null;
  600. }));
  601. return result;
  602. }
  603. [RubyMethod("count")]
  604. public static object Count(CallSiteStorage<EachSite>/*!*/ each, BinaryOpStorage/*!*/ equals, [NotNull]BlockParam/*!*/ comparer, object self) {
  605. int count = 0;
  606. object result = null;
  607. Each(each, self, Proc.Create(each.Context, delegate(BlockParam/*!*/ selfBlock, object _, object item) {
  608. object blockResult;
  609. if (comparer.Yield(item, out blockResult)) {
  610. count = -1;
  611. result = blockResult;
  612. return selfBlock.PropagateFlow(comparer, blockResult);
  613. }
  614. if (Protocols.IsTrue(blockResult)) {
  615. count++;
  616. }
  617. return null;
  618. }));
  619. return (count >= 0) ? ScriptingRuntimeHelpers.Int32ToObject(count) : result;
  620. }
  621. [RubyMethod("one?")]
  622. public static object One(CallSiteStorage<EachSite>/*!*/ each, BinaryOpStorage/*!*/ equals, BlockParam comparer, object self) {
  623. int count = 0;
  624. object result = null;
  625. Each(each, self, Proc.Create(each.Context, delegate(BlockParam/*!*/ selfBlock, object _, object item) {
  626. object blockResult;
  627. if (comparer == null) {
  628. blockResult = item;
  629. } else if (comparer.Yield(item, out blockResult)) {
  630. count = -1;
  631. result = blockResult;
  632. return selfBlock.PropagateFlow(comparer, blockResult);
  633. }
  634. if (Protocols.IsTrue(blockResult) && ++count > 1) {
  635. selfBlock.Break(null);
  636. }
  637. return null;
  638. }));
  639. return (count >= 0) ? ScriptingRuntimeHelpers.BooleanToObject(count == 1) : result;
  640. }
  641. #endregion
  642. #region first, take, take_while, drop, drop_while, cycle
  643. [RubyMethod("first")]
  644. public static object First(CallSiteStorage<EachSite>/*!*/ each, object self) {
  645. object result = null;
  646. Each(each, self, Proc.Create(each.Context, delegate(BlockParam/*!*/ selfBlock, object _, object item) {
  647. result = item;
  648. selfBlock.Break(null);
  649. return null;
  650. }));
  651. return result;
  652. }
  653. [RubyMethod("first")]
  654. [RubyMethod("take")]
  655. public static RubyArray/*!*/ Take(CallSiteStorage<EachSite>/*!*/ each, object self, [DefaultProtocol]int count) {
  656. if (count < 0) {
  657. throw RubyExceptions.CreateArgumentError("attempt to take negative size");
  658. }
  659. var result = new RubyArray(count);
  660. if (count == 0) {
  661. return result;
  662. }
  663. Each(each, self, Proc.Create(each.Context, delegate(BlockParam/*!*/ selfBlock, object _, object item) {
  664. result.Add(item);
  665. if (--count == 0) {
  666. selfBlock.Break(null);
  667. }
  668. return null;
  669. }));
  670. return result;
  671. }
  672. [RubyMethod("take_while")]
  673. public static Enumerator/*!*/ GetTakeWhileEnumerator(CallSiteStorage<EachSite>/*!*/ each, BlockParam predicate, object self) {
  674. return new Enumerator((_, block) => TakeWhile(each, block, self));
  675. }
  676. [RubyMethod("take_while")]
  677. public static object TakeWhile(CallSiteStorage<EachSite>/*!*/ each, [NotNull]BlockParam/*!*/ predicate, object self) {
  678. RubyArray resultArray = new RubyArray();
  679. object result = resultArray;
  680. Each(each, self, Proc.Create(each.Context, delegate(BlockParam/*!*/ selfBlock, object _, object item) {
  681. object blockResult;
  682. if (predicate.Yield(item, out blockResult)) {
  683. result = blockResult;
  684. return selfBlock.PropagateFlow(predicate, blockResult);
  685. }
  686. if (Protocols.IsTrue(blockResult)) {
  687. resultArray.Add(item);
  688. } else {
  689. selfBlock.Break(null);
  690. }
  691. return null;
  692. }));
  693. return result;
  694. }
  695. [RubyMethod("drop")]
  696. public static RubyArray/*!*/ Drop(CallSiteStorage<EachSite>/*!*/ each, object self, [DefaultProtocol]int count) {
  697. if (count < 0) {
  698. throw RubyExceptions.CreateArgumentError("attempt to drop negative size");
  699. }
  700. var result = new RubyArray();
  701. Each(each, self, Proc.Create(each.Context, delegate(BlockParam/*!*/ selfBlock, object _, object item) {
  702. if (count > 0) {
  703. count--;
  704. } else {
  705. result.Add(item);
  706. }
  707. return null;
  708. }));
  709. return result;
  710. }
  711. [RubyMethod("drop_while")]
  712. public static Enumerator/*!*/ GetDropWhileEnumerator(CallSiteStorage<EachSite>/*!*/ each, BlockParam predicate, object self) {
  713. return new Enumerator((_, block) => DropWhile(each, block, self));
  714. }
  715. [RubyMethod("drop_while")]
  716. public static object DropWhile(CallSiteStorage<EachSite>/*!*/ each, [NotNull]BlockParam/*!*/ predicate, object self) {
  717. RubyArray resultArray = new RubyArray();
  718. bool dropping = true;
  719. object result = resultArray;
  720. Each(each, self, Proc.Create(each.Context, delegate(BlockParam/*!*/ selfBlock, object _, object item) {
  721. if (dropping) {
  722. object blockResult;
  723. if (predicate.Yield(item, out blockResult)) {
  724. result = blockResult;
  725. return selfBlock.PropagateFlow(predicate, blockResult);
  726. }
  727. dropping = Protocols.IsTrue(blockResult);
  728. }
  729. if (!dropping) {
  730. resultArray.Add(item);
  731. }
  732. return null;
  733. }));
  734. return result;
  735. }
  736. [RubyMethod("cycle")]
  737. public static Enumerator/*!*/ GetCycleEnumerator(CallSiteStorage<EachSite>/*!*/ each, BlockParam block, object self,
  738. [DefaultProtocol, DefaultParameterValue(Int32.MaxValue)]int iterations) {
  739. return new Enumerator((_, innerBlock) => Cycle(each, innerBlock, self, iterations));
  740. }
  741. [RubyMethod("cycle")]
  742. public static object Cycle(CallSiteStorage<EachSite>/*!*/ each, BlockParam block, object self, DynamicNull iterations) {
  743. return (block != null) ? Cycle(each, block, self, Int32.MaxValue) : GetCycleEnumerator(each, block, self, Int32.MaxValue);
  744. }
  745. [RubyMethod("cycle")]
  746. public static object Cycle(CallSiteStorage<EachSite>/*!*/ each, [NotNull]BlockParam/*!*/ block, object self,
  747. [DefaultProtocol, DefaultParameterValue(Int32.MaxValue)]int iterations) {
  748. if (iterations <= 0) {
  749. return null;
  750. }
  751. List<object> items = (iterations > 1) ? new List<object>() : null;
  752. // call "each" only in the first iteration:
  753. object result = null;
  754. Each(each, self, Proc.Create(each.Context, delegate(BlockParam/*!*/ selfBlock, object _, object item) {
  755. if (block.Yield(item, out result)) {
  756. iterations = -1;
  757. return selfBlock.PropagateFlow(block, result);
  758. }
  759. if (items != null) {
  760. items.Add(item);
  761. }
  762. return null;
  763. }));
  764. if (items == null) {
  765. return result;
  766. }
  767. // the rest of the iterations read from cached values:
  768. while (iterations == Int32.MaxValue || --iterations > 0) {
  769. foreach (var item in items) {
  770. if (block.Yield(item, out result)) {
  771. return result;
  772. }
  773. }
  774. }
  775. return result;
  776. }
  777. #endregion
  778. #region each_cons, each_slice
  779. [RubyMethod("each_cons")]
  780. public static Enumerator/*!*/ GetEachConsEnumerator(CallSiteStorage<EachSite>/*!*/ each, BlockParam block, object self, [DefaultProtocol]int sliceSize) {
  781. return new Enumerator((_, innerBlock) => EachCons(each, innerBlock, self, sliceSize));
  782. }
  783. [RubyMethod("each_cons")]
  784. public static object EachCons(CallSiteStorage<EachSite>/*!*/ each, [NotNull]BlockParam/*!*/ block, object self, [DefaultProtocol]int sliceSize) {
  785. return EachSlice(each, block, self, sliceSize, false, (slice) => {
  786. RubyArray newSlice = new RubyArray(slice.Count);
  787. for (int i = 1; i < slice.Count; i++) {
  788. newSlice.Add(slice[i]);
  789. }
  790. return newSlice;
  791. });
  792. }
  793. [RubyMethod("each_slice")]
  794. public static Enumerator/*!*/ GetEachSliceEnumerator(CallSiteStorage<EachSite>/*!*/ each, BlockParam block, object self, [DefaultProtocol]int sliceSize) {
  795. return new Enumerator((_, innerBlock) => EachSlice(each, innerBlock, self, sliceSize));
  796. }
  797. [RubyMethod("each_slice")]
  798. public static object EachSlice(CallSiteStorage<EachSite>/*!*/ each, [NotNull]BlockParam/*!*/ block, object self, [DefaultProtocol]int sliceSize) {
  799. return EachSlice(each, block, self, sliceSize, true, (slice) => null);
  800. }
  801. private static object EachSlice(CallSiteStorage<EachSite>/*!*/ each, BlockParam/*!*/ block, object self, int sliceSize,
  802. bool includeIncomplete, Func<RubyArray/*!*/, RubyArray>/*!*/ newSliceFactory) {
  803. if (sliceSize <= 0) {
  804. throw RubyExceptions.CreateArgumentError("invalid slice size");
  805. }
  806. RubyArray slice = null;
  807. object result = null;
  808. Each(each, self, Proc.Create(each.Context, delegate(BlockParam/*!*/ selfBlock, object _, object item) {
  809. if (slice == null) {
  810. slice = new RubyArray(sliceSize);
  811. }
  812. slice.Add(item);
  813. if (slice.Count == sliceSize) {
  814. var completeSlice = slice;
  815. slice = newSliceFactory(slice);
  816. object blockResult;
  817. if (block.Yield(completeSlice, out blockResult)) {
  818. result = blockResult;
  819. return selfBlock.PropagateFlow(block, blockResult);
  820. }
  821. }
  822. return null;
  823. }));
  824. if (slice != null && includeIncomplete) {
  825. object blockResult;
  826. if (block.Yield(slice, out blockResult)) {
  827. return blockResult;
  828. }
  829. }
  830. return result;
  831. }
  832. #endregion
  833. #region enum_cons, enum_slice, enum_with_index
  834. [RubyMethod("enum_cons")]
  835. public static Enumerator/*!*/ GetConsEnumerator(object self, [DefaultProtocol]int sliceSize) {
  836. return new Enumerator(self, "each_cons", sliceSize);
  837. }
  838. [RubyMethod("enum_slice")]
  839. public static Enumerator/*!*/ GetSliceEnumerator(object self, [DefaultProtocol]int sliceSize) {
  840. return new Enumerator(self, "each_slice", sliceSize);
  841. }
  842. [RubyMethod("enum_with_index")]
  843. public static Enumerator/*!*/ GetEnumeratorWithIndex(object self) {
  844. return new Enumerator(self, "each_with_index", null);
  845. }
  846. #endregion
  847. // TODO:
  848. // chunk
  849. // collect_concat
  850. // reverse_each
  851. // flat_map
  852. // join
  853. }
  854. }