konpeito 0.2.3 → 0.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +16 -0
  3. data/lib/konpeito/codegen/cruby_backend.rb +7 -0
  4. data/lib/konpeito/codegen/jvm_generator.rb +160 -40
  5. data/lib/konpeito/codegen/llvm_generator.rb +236 -58
  6. data/lib/konpeito/hir/builder.rb +61 -27
  7. data/lib/konpeito/hir/nodes.rb +2 -0
  8. data/lib/konpeito/version.rb +1 -1
  9. data/tools/konpeito-asm/build.sh +1 -0
  10. data/tools/konpeito-asm/src/konpeito/runtime/KHash.java +12 -0
  11. data/tools/konpeito-asm/src/konpeito/runtime/KMatchData.java +50 -1
  12. data/tools/konpeito-asm/src/konpeito/runtime/RubyDispatch.java +721 -8
  13. metadata +1 -20
  14. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KArray.class +0 -0
  15. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KCompression.class +0 -0
  16. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KConditionVariable.class +0 -0
  17. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KCrypto.class +0 -0
  18. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KFiber.class +0 -0
  19. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KFile.class +0 -0
  20. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KHTTP.class +0 -0
  21. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KHash.class +0 -0
  22. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KJSON$Parser.class +0 -0
  23. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KJSON.class +0 -0
  24. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KMatchData.class +0 -0
  25. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KMath.class +0 -0
  26. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KRactor.class +0 -0
  27. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KRactorPort.class +0 -0
  28. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KRubyException.class +0 -0
  29. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KSizedQueue.class +0 -0
  30. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KThread.class +0 -0
  31. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KTime.class +0 -0
  32. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/RubyDispatch.class +0 -0
@@ -26,6 +26,11 @@ public class RubyDispatch {
26
26
  * jvm_method_name() converts: [] → op_aref, []= → op_aset, << → op_lshift,
27
27
  * empty? → empty_q, include? → include_q, etc.
28
28
  */
29
+ // Track frozen state for strings (Java strings are always immutable, but Ruby semantics
30
+ // distinguish between unfrozen and frozen strings). Uses identity comparison.
31
+ private static final java.util.Map<String, Boolean> FROZEN_STRINGS =
32
+ java.util.Collections.synchronizedMap(new java.util.IdentityHashMap<>());
33
+
29
34
  private static final java.util.Map<String, String[]> RUBY_NAME_ALIASES = new java.util.HashMap<>();
30
35
  static {
31
36
  // Operator aliases
@@ -160,6 +165,13 @@ public class RubyDispatch {
160
165
  }
161
166
  return Math.random();
162
167
  }
168
+ // Kernel#sprintf / Kernel#format
169
+ if ((methodName.equals("sprintf") || methodName.equals("format")) && methodArgs.length >= 1) {
170
+ String fmt = String.valueOf(methodArgs[0]);
171
+ Object[] fmtArgs = new Object[methodArgs.length - 1];
172
+ System.arraycopy(methodArgs, 1, fmtArgs, 0, fmtArgs.length);
173
+ return rubyFormatString(fmt, fmtArgs);
174
+ }
163
175
  throw new NullPointerException("Method '" + methodName + "' called on null receiver");
164
176
  }
165
177
 
@@ -474,8 +486,34 @@ public class RubyDispatch {
474
486
  }
475
487
  return result;
476
488
  }
489
+ case "succ": case "next": return Long.valueOf(lv + 1);
490
+ case "pred": return Long.valueOf(lv - 1);
491
+ case "bit_length": return Long.valueOf(lv == 0 ? 0 : 64 - Long.numberOfLeadingZeros(Math.abs(lv)));
477
492
  }
478
493
  }
494
+ // Integer#upto(limit) { |i| ... }
495
+ if (args.length == 2 && args[0] instanceof Number && methodName.equals("upto")) {
496
+ long limit = ((Number) args[0]).longValue();
497
+ for (long i = lv; i <= limit; i++) invokeBlock(args[1], Long.valueOf(i));
498
+ return receiver;
499
+ }
500
+ // Integer#downto(limit) { |i| ... }
501
+ if (args.length == 2 && args[0] instanceof Number && methodName.equals("downto")) {
502
+ long limit = ((Number) args[0]).longValue();
503
+ for (long i = lv; i >= limit; i--) invokeBlock(args[1], Long.valueOf(i));
504
+ return receiver;
505
+ }
506
+ // Integer#step(limit, step) { |i| ... }
507
+ if (args.length == 3 && args[0] instanceof Number && args[1] instanceof Number && methodName.equals("step")) {
508
+ long limit = ((Number) args[0]).longValue();
509
+ long stepSize = ((Number) args[1]).longValue();
510
+ if (stepSize > 0) {
511
+ for (long i = lv; i <= limit; i += stepSize) invokeBlock(args[2], Long.valueOf(i));
512
+ } else if (stepSize < 0) {
513
+ for (long i = lv; i >= limit; i += stepSize) invokeBlock(args[2], Long.valueOf(i));
514
+ }
515
+ return receiver;
516
+ }
479
517
  }
480
518
 
481
519
  // Double (Float) arithmetic operators
@@ -632,12 +670,27 @@ public class RubyDispatch {
632
670
  if (methodName.equals("include_q") && args.length == 1) {
633
671
  return rangeInclude(sv, args[0]);
634
672
  }
673
+ if ((methodName.equals("cover_q") || methodName.equals("member_q")) && args.length == 1) {
674
+ return rangeInclude(sv, args[0]);
675
+ }
635
676
  if (methodName.equals("size") && args.length == 0) {
636
677
  return rangeSize(sv);
637
678
  }
638
679
  if (methodName.equals("length") && args.length == 0) {
639
680
  return rangeSize(sv);
640
681
  }
682
+ if (methodName.equals("count") && args.length == 0) {
683
+ return rangeSize(sv);
684
+ }
685
+ if (methodName.equals("count") && args.length == 1) {
686
+ // count with block
687
+ KArray<Object> elems = rangeToArray(sv);
688
+ long count = 0;
689
+ for (Object elem : elems) {
690
+ if (isTruthy(invokeBlock(args[0], elem))) count++;
691
+ }
692
+ return Long.valueOf(count);
693
+ }
641
694
  if (methodName.equals("min") && args.length == 0) {
642
695
  return rangeMin(sv);
643
696
  }
@@ -647,21 +700,101 @@ public class RubyDispatch {
647
700
  if (methodName.equals("first") && args.length == 0) {
648
701
  return rangeMin(sv);
649
702
  }
703
+ if (methodName.equals("first") && args.length == 1 && args[0] instanceof Number) {
704
+ int n = ((Number) args[0]).intValue();
705
+ KArray<Object> all = rangeToArray(sv);
706
+ KArray<Object> result = new KArray<>();
707
+ for (int i = 0; i < Math.min(n, all.size()); i++) result.push(all.get(i));
708
+ return result;
709
+ }
650
710
  if (methodName.equals("last") && args.length == 0) {
651
711
  return rangeMax(sv);
652
712
  }
713
+ if (methodName.equals("last") && args.length == 1 && args[0] instanceof Number) {
714
+ int n = ((Number) args[0]).intValue();
715
+ KArray<Object> all = rangeToArray(sv);
716
+ KArray<Object> result = new KArray<>();
717
+ int start = Math.max(0, all.size() - n);
718
+ for (int i = start; i < all.size(); i++) result.push(all.get(i));
719
+ return result;
720
+ }
653
721
  if (methodName.equals("k_class") && args.length == 0) {
654
722
  return "Range";
655
723
  }
724
+ if (methodName.equals("nil_q") && args.length == 0) {
725
+ return Boolean.FALSE;
726
+ }
656
727
  if (methodName.equals("to_s") && args.length == 0) {
657
728
  return sv; // Range#to_s returns "start..end"
658
729
  }
659
730
  if (methodName.equals("inspect") && args.length == 0) {
660
731
  return sv;
661
732
  }
733
+ if (methodName.equals("to_a") && args.length == 0) {
734
+ return rangeToArray(sv);
735
+ }
662
736
  if (methodName.equals("each") && args.length == 0) {
663
737
  return rangeToArray(sv);
664
738
  }
739
+ if (methodName.equals("each") && args.length == 1) {
740
+ KArray<Object> elems = rangeToArray(sv);
741
+ for (Object elem : elems) invokeBlock(args[0], elem);
742
+ return receiver;
743
+ }
744
+ if (methodName.equals("map") && args.length == 1) {
745
+ KArray<Object> elems = rangeToArray(sv);
746
+ KArray<Object> result = new KArray<>();
747
+ for (Object elem : elems) result.push(invokeBlock(args[0], elem));
748
+ return result;
749
+ }
750
+ if (methodName.equals("select") && args.length == 1) {
751
+ KArray<Object> elems = rangeToArray(sv);
752
+ KArray<Object> result = new KArray<>();
753
+ for (Object elem : elems) {
754
+ if (isTruthy(invokeBlock(args[0], elem))) result.push(elem);
755
+ }
756
+ return result;
757
+ }
758
+ if (methodName.equals("reduce") || methodName.equals("inject")) {
759
+ KArray<Object> elems = rangeToArray(sv);
760
+ if (args.length == 1) {
761
+ if (elems.isEmpty()) return null;
762
+ Object acc = elems.get(0);
763
+ for (int i = 1; i < elems.size(); i++) acc = invokeBlock(args[0], acc, elems.get(i));
764
+ return acc;
765
+ }
766
+ if (args.length == 2) {
767
+ Object acc = args[0];
768
+ for (Object elem : elems) acc = invokeBlock(args[1], acc, elem);
769
+ return acc;
770
+ }
771
+ }
772
+ if (methodName.equals("step") && args.length == 2 && args[0] instanceof Number) {
773
+ // step(n, block) — iterate with step
774
+ long stepSize = ((Number) args[0]).longValue();
775
+ Object[] parsed = parseRangeString(sv);
776
+ try {
777
+ long start = Long.parseLong((String) parsed[0]);
778
+ long end = Long.parseLong((String) parsed[1]);
779
+ boolean exclusive = (Boolean) parsed[2];
780
+ for (long i = start; exclusive ? i < end : i <= end; i += stepSize) {
781
+ invokeBlock(args[1], Long.valueOf(i));
782
+ }
783
+ } catch (NumberFormatException e) { /* ignore */ }
784
+ return receiver;
785
+ }
786
+ if (methodName.equals("is_a_q") || methodName.equals("kind_of_q")) {
787
+ if (args.length == 1) {
788
+ String cn = classNameOf(args[0]);
789
+ if ("Range".equals(cn) || "Enumerable".equals(cn) || "Object".equals(cn) || "BasicObject".equals(cn))
790
+ return Boolean.TRUE;
791
+ return Boolean.FALSE;
792
+ }
793
+ }
794
+ }
795
+ // Proc.new { block } — when receiver is the String constant "Proc"
796
+ if ("Proc".equals(sv) && methodName.equals("new") && args.length == 1) {
797
+ return args[0]; // the block IS the proc in JVM backend
665
798
  }
666
799
 
667
800
  // String comparison and concatenation operators
@@ -705,6 +838,42 @@ public class RubyDispatch {
705
838
  return idx >= 0 ? Long.valueOf(idx) : null;
706
839
  }
707
840
  case "encode": return sv; // Simplified: return self for encoding
841
+ case "casecmp": return Long.valueOf(sv.compareToIgnoreCase(rv));
842
+ case "casecmp_q": return Boolean.valueOf(sv.equalsIgnoreCase(rv));
843
+ case "partition": {
844
+ int idx = sv.indexOf(rv);
845
+ if (idx < 0) {
846
+ KArray<Object> r = new KArray<>(); r.push(sv); r.push(""); r.push(""); return r;
847
+ }
848
+ KArray<Object> r = new KArray<>();
849
+ r.push(sv.substring(0, idx));
850
+ r.push(rv);
851
+ r.push(sv.substring(idx + rv.length()));
852
+ return r;
853
+ }
854
+ case "rpartition": {
855
+ int idx = sv.lastIndexOf(rv);
856
+ if (idx < 0) {
857
+ KArray<Object> r = new KArray<>(); r.push(""); r.push(""); r.push(sv); return r;
858
+ }
859
+ KArray<Object> r = new KArray<>();
860
+ r.push(sv.substring(0, idx));
861
+ r.push(rv);
862
+ r.push(sv.substring(idx + rv.length()));
863
+ return r;
864
+ }
865
+ }
866
+ }
867
+
868
+ // String#% — format string
869
+ if (methodName.equals("op_mod") && args.length == 1) {
870
+ if (args[0] instanceof KArray) {
871
+ KArray<?> fmtArr = (KArray<?>) args[0];
872
+ Object[] fmtArgs = new Object[fmtArr.size()];
873
+ for (int i = 0; i < fmtArr.size(); i++) fmtArgs[i] = fmtArr.get(i);
874
+ return rubyFormatString(sv, fmtArgs);
875
+ } else {
876
+ return rubyFormatString(sv, new Object[]{args[0]});
708
877
  }
709
878
  }
710
879
 
@@ -823,7 +992,16 @@ public class RubyDispatch {
823
992
  for (int i = 1; i <= m.groupCount(); i++) {
824
993
  groups.push(m.group(i));
825
994
  }
826
- return new KMatchData(groups, sv);
995
+ java.util.Map<String, String> namedGrps = new java.util.LinkedHashMap<>();
996
+ try {
997
+ java.util.regex.Matcher nm = p.matcher(sv);
998
+ if (nm.find()) {
999
+ for (String gname : namedGroupNames(p)) {
1000
+ namedGrps.put(gname, nm.group(gname));
1001
+ }
1002
+ }
1003
+ } catch (Exception ignored) {}
1004
+ return new KMatchData(groups, sv, m.start(), m.end(), namedGrps);
827
1005
  }
828
1006
  return null;
829
1007
  }
@@ -975,12 +1153,15 @@ public class RubyDispatch {
975
1153
  }
976
1154
  return result;
977
1155
  }
978
- case "freeze": return sv; // Strings are immutable in Java
979
- case "dup": return sv;
1156
+ case "freeze": FROZEN_STRINGS.put(sv, Boolean.TRUE); return sv;
1157
+ case "dup": return sv; // dup returns unfrozen copy (same string in JVM)
980
1158
  case "clone": return sv;
1159
+ case "ord": return Long.valueOf(sv.isEmpty() ? 0L : sv.codePointAt(0));
1160
+ case "succ": case "next": return stringSucc(sv);
981
1161
  case "k_class": return "String";
982
1162
  case "nil_q": return Boolean.FALSE;
983
- case "frozen_q": return Boolean.FALSE; // Strings are not frozen by default in Ruby
1163
+ case "frozen_q": return FROZEN_STRINGS.getOrDefault(sv, Boolean.FALSE);
1164
+ case "encoding": return "UTF-8";
984
1165
  case "to_a": {
985
1166
  // Range#to_a — ranges are stored as strings "start..end" or "start...end"
986
1167
  return rangeToArray(sv);
@@ -1106,7 +1287,11 @@ public class RubyDispatch {
1106
1287
  for (int gi = 1; gi <= m.groupCount(); gi++) {
1107
1288
  groups.push(m.group(gi));
1108
1289
  }
1109
- return new KMatchData(groups, str);
1290
+ java.util.Map<String, String> ng = new java.util.LinkedHashMap<>();
1291
+ try {
1292
+ for (String gn : namedGroupNames(pat)) { ng.put(gn, m.group(gn)); }
1293
+ } catch (Exception ignored) {}
1294
+ return new KMatchData(groups, str, m.start(), m.end(), ng);
1110
1295
  }
1111
1296
  return null;
1112
1297
  }
@@ -1137,7 +1322,14 @@ public class RubyDispatch {
1137
1322
  if (args.length == 1 && args[0] instanceof Long) {
1138
1323
  return md.get(((Long) args[0]).intValue());
1139
1324
  }
1325
+ // Named capture access: md[:name] or md["name"]
1326
+ if (args.length == 1 && args[0] instanceof String) {
1327
+ return md.getByName((String) args[0]);
1328
+ }
1140
1329
  break;
1330
+ case "pre_match": return md.pre_match();
1331
+ case "post_match": return md.post_match();
1332
+ case "named_captures": return md.named_captures();
1141
1333
  case "to_s": return md.toString();
1142
1334
  case "string": return md.string();
1143
1335
  case "captures": return md.captures();
@@ -1564,6 +1756,162 @@ public class RubyDispatch {
1564
1756
  }
1565
1757
  return Long.valueOf(arr.size());
1566
1758
  }
1759
+ case "each_cons": {
1760
+ if (args.length == 2 && args[0] instanceof Number) {
1761
+ int n = ((Number) args[0]).intValue();
1762
+ Object blk = args[1];
1763
+ for (int i = 0; i <= arr.size() - n; i++) {
1764
+ KArray<Object> cons = new KArray<>();
1765
+ for (int j = i; j < i + n; j++) cons.push(arr.get(j));
1766
+ invokeBlock(blk, cons);
1767
+ }
1768
+ return null;
1769
+ }
1770
+ break;
1771
+ }
1772
+ case "each_slice": {
1773
+ if (args.length == 2 && args[0] instanceof Number) {
1774
+ int n = ((Number) args[0]).intValue();
1775
+ Object blk = args[1];
1776
+ for (int i = 0; i < arr.size(); i += n) {
1777
+ KArray<Object> slice = new KArray<>();
1778
+ for (int j = i; j < Math.min(i + n, arr.size()); j++) slice.push(arr.get(j));
1779
+ invokeBlock(blk, slice);
1780
+ }
1781
+ return null;
1782
+ }
1783
+ break;
1784
+ }
1785
+ case "combination": {
1786
+ if (args.length == 2 && args[0] instanceof Number) {
1787
+ int n = ((Number) args[0]).intValue();
1788
+ Object blk = args[1];
1789
+ int[] indices = new int[n];
1790
+ for (int i = 0; i < n; i++) indices[i] = i;
1791
+ while (indices.length > 0 && indices[0] <= arr.size() - n) {
1792
+ KArray<Object> combo = new KArray<>();
1793
+ for (int idx : indices) combo.push(arr.get(idx));
1794
+ invokeBlock(blk, combo);
1795
+ int i = n - 1;
1796
+ while (i >= 0 && indices[i] == arr.size() - n + i) i--;
1797
+ if (i < 0) break;
1798
+ indices[i]++;
1799
+ for (int j = i + 1; j < n; j++) indices[j] = indices[j - 1] + 1;
1800
+ }
1801
+ return arr;
1802
+ }
1803
+ if (args.length == 1 && args[0] instanceof Number) {
1804
+ int n = ((Number) args[0]).intValue();
1805
+ KArray<Object> result = new KArray<>();
1806
+ int[] indices = new int[n];
1807
+ for (int i = 0; i < n; i++) indices[i] = i;
1808
+ while (indices.length > 0 && indices[0] <= arr.size() - n) {
1809
+ KArray<Object> combo = new KArray<>();
1810
+ for (int idx : indices) combo.push(arr.get(idx));
1811
+ result.push(combo);
1812
+ int i = n - 1;
1813
+ while (i >= 0 && indices[i] == arr.size() - n + i) i--;
1814
+ if (i < 0) break;
1815
+ indices[i]++;
1816
+ for (int j = i + 1; j < n; j++) indices[j] = indices[j - 1] + 1;
1817
+ }
1818
+ return result;
1819
+ }
1820
+ break;
1821
+ }
1822
+ case "permutation": {
1823
+ int n = (args.length >= 1 && args[0] instanceof Number) ? ((Number) args[0]).intValue() : arr.size();
1824
+ Object blk = (args.length >= 2) ? args[1] : (args.length == 1 && isCallable(args[0]) ? args[0] : null);
1825
+ // Generate all permutations using Heap's algorithm approach
1826
+ int[] idxs = new int[arr.size()];
1827
+ boolean[] used = new boolean[arr.size()];
1828
+ KArray<Object> perms = new KArray<>();
1829
+ generatePerms(arr, n, idxs, used, 0, perms);
1830
+ if (blk != null) {
1831
+ for (Object p2 : perms) invokeBlock(blk, p2);
1832
+ return arr;
1833
+ }
1834
+ return perms;
1835
+ }
1836
+ case "product": {
1837
+ if (args.length >= 1) {
1838
+ Object blk = null;
1839
+ java.util.List<KArray<?>> others = new java.util.ArrayList<>();
1840
+ for (Object a : args) {
1841
+ if (a instanceof KArray) others.add((KArray<?>) a);
1842
+ else if (isCallable(a)) blk = a;
1843
+ }
1844
+ // Start with single-element arrays for each element of arr
1845
+ KArray<Object> result = new KArray<>();
1846
+ for (Object elem : arr) {
1847
+ KArray<Object> wrap = new KArray<>();
1848
+ wrap.push(elem);
1849
+ result.push(wrap);
1850
+ }
1851
+ for (KArray<?> other : others) {
1852
+ KArray<Object> newResult = new KArray<>();
1853
+ for (Object left : result) {
1854
+ @SuppressWarnings("unchecked")
1855
+ KArray<Object> leftArr = (KArray<Object>) left;
1856
+ for (Object right : other) {
1857
+ KArray<Object> combo = new KArray<>(leftArr);
1858
+ combo.push(right);
1859
+ newResult.push(combo);
1860
+ }
1861
+ }
1862
+ result = newResult;
1863
+ }
1864
+ if (blk != null) {
1865
+ for (Object p2 : result) invokeBlock(blk, p2);
1866
+ return arr;
1867
+ }
1868
+ return result;
1869
+ }
1870
+ break;
1871
+ }
1872
+ case "uniq": {
1873
+ if (args.length == 0) {
1874
+ KArray<Object> result = new KArray<>();
1875
+ java.util.LinkedHashSet<Object> seen = new java.util.LinkedHashSet<>();
1876
+ for (Object elem : arr) {
1877
+ if (seen.add(elem)) result.push(elem);
1878
+ }
1879
+ return result;
1880
+ }
1881
+ if (args.length == 1) {
1882
+ KArray<Object> result = new KArray<>();
1883
+ java.util.LinkedHashSet<Object> seen = new java.util.LinkedHashSet<>();
1884
+ for (Object elem : arr) {
1885
+ Object key = invokeBlock(args[0], elem);
1886
+ if (seen.add(key)) result.push(elem);
1887
+ }
1888
+ return result;
1889
+ }
1890
+ break;
1891
+ }
1892
+ case "to_h": {
1893
+ // Array of [k, v] pairs → Hash
1894
+ @SuppressWarnings("unchecked")
1895
+ KHash<Object, Object> result = new KHash<>();
1896
+ if (args.length == 0) {
1897
+ for (Object elem : arr) {
1898
+ if (elem instanceof KArray) {
1899
+ KArray<?> pair = (KArray<?>) elem;
1900
+ if (pair.size() >= 2) result.put(pair.get(0), pair.get(1));
1901
+ }
1902
+ }
1903
+ } else if (args.length == 1) {
1904
+ // with block: each element → block returns [k, v]
1905
+ for (Object elem : arr) {
1906
+ Object blockResult = invokeBlock(args[0], elem);
1907
+ if (blockResult instanceof KArray) {
1908
+ KArray<?> pair = (KArray<?>) blockResult;
1909
+ if (pair.size() >= 2) result.put(pair.get(0), pair.get(1));
1910
+ }
1911
+ }
1912
+ }
1913
+ return result;
1914
+ }
1567
1915
  case "frozen_q": return Boolean.valueOf(arr.isFrozen());
1568
1916
  case "freeze": { arr.freeze(); return arr; }
1569
1917
  }
@@ -1611,9 +1959,18 @@ public class RubyDispatch {
1611
1959
  case "fetch": {
1612
1960
  if (args.length == 1) {
1613
1961
  Object val = hash.get(args[0]);
1614
- if (val != null) return val;
1962
+ if (val != null || hash.containsKey(args[0])) return val;
1615
1963
  throw new RuntimeException("KeyError: key not found: " + args[0]);
1616
1964
  }
1965
+ if (args.length == 2) {
1966
+ Object val = hash.get(args[0]);
1967
+ if (val != null || hash.containsKey(args[0])) return val;
1968
+ // args[1] is a default value or block
1969
+ if (isCallable(args[1])) {
1970
+ return invokeBlock(args[1], args[0]);
1971
+ }
1972
+ return args[1];
1973
+ }
1617
1974
  break;
1618
1975
  }
1619
1976
  case "any_q": {
@@ -1736,8 +2093,8 @@ public class RubyDispatch {
1736
2093
  }
1737
2094
  return Long.valueOf(hash.size());
1738
2095
  }
1739
- case "frozen_q": return Boolean.FALSE;
1740
- case "freeze": return hash;
2096
+ case "frozen_q": return Boolean.valueOf(hash.isFrozen());
2097
+ case "freeze": { hash.freeze(); return hash; }
1741
2098
  case "to_a": {
1742
2099
  KArray<Object> result = new KArray<>();
1743
2100
  for (Map.Entry<?, ?> entry : hash.entrySet()) {
@@ -1829,6 +2186,85 @@ public class RubyDispatch {
1829
2186
  }
1830
2187
  break;
1831
2188
  }
2189
+ case "transform_keys": {
2190
+ if (args.length == 1) {
2191
+ @SuppressWarnings("unchecked")
2192
+ KHash<Object, Object> result = new KHash<>();
2193
+ for (Map.Entry<?, ?> entry : hash.entrySet()) {
2194
+ Object newKey = invokeBlock(args[0], entry.getKey());
2195
+ result.put(newKey, entry.getValue());
2196
+ }
2197
+ return result;
2198
+ }
2199
+ break;
2200
+ }
2201
+ case "transform_values": {
2202
+ if (args.length == 1) {
2203
+ @SuppressWarnings("unchecked")
2204
+ KHash<Object, Object> result = new KHash<>();
2205
+ for (Map.Entry<?, ?> entry : hash.entrySet()) {
2206
+ Object newVal = invokeBlock(args[0], entry.getValue());
2207
+ result.put(entry.getKey(), newVal);
2208
+ }
2209
+ return result;
2210
+ }
2211
+ break;
2212
+ }
2213
+ case "compact": {
2214
+ if (args.length == 0) {
2215
+ @SuppressWarnings("unchecked")
2216
+ KHash<Object, Object> result = new KHash<>();
2217
+ for (Map.Entry<?, ?> entry : hash.entrySet()) {
2218
+ if (entry.getValue() != null) result.put(entry.getKey(), entry.getValue());
2219
+ }
2220
+ return result;
2221
+ }
2222
+ break;
2223
+ }
2224
+ case "except": {
2225
+ @SuppressWarnings("unchecked")
2226
+ KHash<Object, Object> result = new KHash<>(hash);
2227
+ for (Object k : args) result.remove(k);
2228
+ return result;
2229
+ }
2230
+ case "slice": {
2231
+ @SuppressWarnings("unchecked")
2232
+ KHash<Object, Object> result = new KHash<>();
2233
+ for (Object k : args) {
2234
+ Object v = hash.get(k);
2235
+ if (v != null || hash.containsKey(k)) result.put(k, v);
2236
+ }
2237
+ return result;
2238
+ }
2239
+ case "invert": {
2240
+ if (args.length == 0) {
2241
+ @SuppressWarnings("unchecked")
2242
+ KHash<Object, Object> result = new KHash<>();
2243
+ for (Map.Entry<?, ?> entry : hash.entrySet()) {
2244
+ result.put(entry.getValue(), entry.getKey());
2245
+ }
2246
+ return result;
2247
+ }
2248
+ break;
2249
+ }
2250
+ case "dig": {
2251
+ if (args.length >= 1) {
2252
+ Object val = hash.get(args[0]);
2253
+ for (int i = 1; i < args.length; i++) {
2254
+ if (val == null) return null;
2255
+ if (val instanceof KHash) {
2256
+ val = ((KHash<?, ?>) val).get(args[i]);
2257
+ } else if (val instanceof KArray) {
2258
+ int idx = ((Number) args[i]).intValue();
2259
+ val = ((KArray<?>) val).get(idx);
2260
+ } else {
2261
+ return null;
2262
+ }
2263
+ }
2264
+ return val;
2265
+ }
2266
+ break;
2267
+ }
1832
2268
  }
1833
2269
  }
1834
2270
 
@@ -1847,6 +2283,10 @@ public class RubyDispatch {
1847
2283
  if (className.equals(receiverClassName) || className.equals("Object") || className.equals("BasicObject")) {
1848
2284
  return Boolean.TRUE;
1849
2285
  }
2286
+ // Callable/lambda/proc receivers respond to is_a?(Proc)
2287
+ if (className.equals("Proc") && isCallable(receiver)) {
2288
+ return Boolean.TRUE;
2289
+ }
1850
2290
  // Check if the receiver's class is a subclass (via Java inheritance)
1851
2291
  try {
1852
2292
  Class<?> targetClass = Class.forName(receiver.getClass().getPackage().getName() + "." + className);
@@ -1905,13 +2345,110 @@ public class RubyDispatch {
1905
2345
  case "each": case "map": case "select": case "reject": case "reduce":
1906
2346
  case "find": case "any_q": case "all_q": case "none_q": case "join":
1907
2347
  case "min": case "max": case "count": case "delete": case "index":
2348
+ case "freeze": case "frozen_q": case "tally": case "zip": case "each_cons":
2349
+ case "each_slice": case "each_with_index": case "each_with_object":
2350
+ case "sort_by": case "min_by": case "max_by": case "group_by":
2351
+ case "flat_map": case "take": case "drop": case "combination": case "permutation":
2352
+ case "product": case "to_h":
2353
+ return Boolean.TRUE;
2354
+ }
2355
+ }
2356
+ if (receiver instanceof String) {
2357
+ switch (checkName) {
2358
+ case "upcase": case "downcase": case "length": case "size": case "strip":
2359
+ case "chomp": case "chop": case "reverse": case "chars": case "bytes":
2360
+ case "split": case "include_q": case "start_with_q": case "end_with_q":
2361
+ case "gsub": case "sub": case "match": case "replace": case "to_i": case "to_f":
2362
+ case "to_s": case "empty_q": case "frozen_q": case "freeze": case "ord":
2363
+ case "succ": case "next": case "swapcase": case "capitalize": case "lstrip":
2364
+ case "rstrip": case "index": case "tr": case "count": case "delete":
2365
+ case "encode": case "force_encoding": case "encoding": case "bytesize":
2366
+ case "center": case "ljust": case "rjust": case "casecmp": case "casecmp_q":
2367
+ case "partition": case "rpartition": case "scan":
2368
+ return Boolean.TRUE;
2369
+ }
2370
+ }
2371
+ if (receiver instanceof Long || receiver instanceof Double) {
2372
+ switch (checkName) {
2373
+ case "to_i": case "to_f": case "to_s": case "abs": case "zero_q":
2374
+ case "positive_q": case "negative_q": case "even_q": case "odd_q":
2375
+ case "times": case "upto": case "downto": case "succ": case "next": case "pred":
2376
+ case "frozen_q": case "ceil": case "floor": case "round":
2377
+ return Boolean.TRUE;
2378
+ }
2379
+ }
2380
+ if (receiver instanceof KHash) {
2381
+ switch (checkName) {
2382
+ case "keys": case "values": case "each": case "map": case "select":
2383
+ case "reject": case "merge": case "store": case "delete": case "fetch":
2384
+ case "include_q": case "key_q": case "has_key_q": case "size": case "length":
2385
+ case "empty_q": case "to_a": case "flatten": case "any_q": case "all_q":
2386
+ case "none_q": case "count": case "transform_keys": case "transform_values":
2387
+ case "compact": case "except": case "slice": case "invert": case "dig":
2388
+ case "min_by": case "max_by": case "sort_by": case "group_by":
1908
2389
  case "freeze": case "frozen_q":
1909
2390
  return Boolean.TRUE;
1910
2391
  }
1911
2392
  }
2393
+ if (isCallable(receiver)) {
2394
+ switch (checkName) {
2395
+ case "call": case "arity": case "lambda_q": case "curry":
2396
+ return Boolean.TRUE;
2397
+ }
2398
+ }
1912
2399
  return Boolean.FALSE;
1913
2400
  }
1914
2401
 
2402
+ // Callable/Block/Lambda methods: arity, lambda?, is_a?(Proc), call
2403
+ if (isCallable(receiver)) {
2404
+ if (methodName.equals("arity") && args.length == 0) {
2405
+ for (java.lang.reflect.Method m : receiver.getClass().getMethods()) {
2406
+ if (m.getName().equals("call") && !m.isBridge()) {
2407
+ return Long.valueOf(m.getParameterCount());
2408
+ }
2409
+ }
2410
+ return 0L;
2411
+ }
2412
+ if ((methodName.equals("lambda_q")) && args.length == 0) {
2413
+ return Boolean.TRUE;
2414
+ }
2415
+ if ((methodName.equals("is_a_q") || methodName.equals("kind_of_q") || methodName.equals("instance_of_q")) && args.length == 1) {
2416
+ String cn = classNameOf(args[0]);
2417
+ if ("Proc".equals(cn) || "Object".equals(cn) || "BasicObject".equals(cn)) return Boolean.TRUE;
2418
+ return Boolean.FALSE;
2419
+ }
2420
+ if (methodName.equals("call")) {
2421
+ return invokeBlock(receiver, args);
2422
+ }
2423
+ if (methodName.equals("k_class") && args.length == 0) {
2424
+ return "Proc";
2425
+ }
2426
+ if (methodName.equals("nil_q") && args.length == 0) {
2427
+ return Boolean.FALSE;
2428
+ }
2429
+ }
2430
+
2431
+ // Universal Object methods: tap, then/yield_self, send
2432
+ if (methodName.equals("tap") && args.length == 1) {
2433
+ invokeBlock(args[0], receiver);
2434
+ return receiver;
2435
+ }
2436
+ if ((methodName.equals("then") || methodName.equals("yield_self")) && args.length == 1) {
2437
+ return invokeBlock(args[0], receiver);
2438
+ }
2439
+ if (methodName.equals("send") || methodName.equals("__send__")) {
2440
+ if (args.length >= 1) {
2441
+ String sendMethod = String.valueOf(args[0]);
2442
+ // dispatch(methodName, allArgs) where allArgs[0] = receiver
2443
+ Object[] allArgs = new Object[args.length];
2444
+ allArgs[0] = receiver;
2445
+ System.arraycopy(args, 1, allArgs, 1, args.length - 1);
2446
+ try { return dispatch(sendMethod, allArgs); } catch (Throwable t) {
2447
+ throw new RuntimeException(t.getMessage(), t);
2448
+ }
2449
+ }
2450
+ }
2451
+
1915
2452
  return SENTINEL;
1916
2453
  }
1917
2454
 
@@ -2350,6 +2887,21 @@ public class RubyDispatch {
2350
2887
  throw new RuntimeException("Block has no callable 'call' method for " + blockArgs.length + " args");
2351
2888
  }
2352
2889
 
2890
+ /**
2891
+ * Check if an object is a callable block (has a "call" method).
2892
+ */
2893
+ private static boolean isCallable(Object o) {
2894
+ if (o == null) return false;
2895
+ if (o instanceof Number || o instanceof String || o instanceof Boolean
2896
+ || o instanceof KArray || o instanceof KHash || o instanceof java.util.regex.Pattern) {
2897
+ return false;
2898
+ }
2899
+ for (java.lang.reflect.Method m : o.getClass().getMethods()) {
2900
+ if (m.getName().equals("call")) return true;
2901
+ }
2902
+ return false;
2903
+ }
2904
+
2353
2905
  /**
2354
2906
  * Ruby truthiness check: everything is truthy except null (nil) and false.
2355
2907
  */
@@ -2497,4 +3049,165 @@ public class RubyDispatch {
2497
3049
  if (o == null) return false;
2498
3050
  return ((Boolean) o).booleanValue();
2499
3051
  }
3052
+
3053
+ /**
3054
+ * Ruby sprintf/format: converts Ruby-style format string to Java format.
3055
+ */
3056
+ private static String rubyFormatString(String fmt, Object[] args) {
3057
+ StringBuilder result = new StringBuilder();
3058
+ int argIdx = 0;
3059
+ int i = 0;
3060
+ while (i < fmt.length()) {
3061
+ char c = fmt.charAt(i);
3062
+ if (c != '%') { result.append(c); i++; continue; }
3063
+ i++;
3064
+ if (i >= fmt.length()) { result.append('%'); break; }
3065
+ if (fmt.charAt(i) == '%') { result.append('%'); i++; continue; }
3066
+ StringBuilder spec = new StringBuilder("%");
3067
+ while (i < fmt.length() && "-+ #0".indexOf(fmt.charAt(i)) >= 0) {
3068
+ spec.append(fmt.charAt(i++));
3069
+ }
3070
+ while (i < fmt.length() && Character.isDigit(fmt.charAt(i))) {
3071
+ spec.append(fmt.charAt(i++));
3072
+ }
3073
+ if (i < fmt.length() && fmt.charAt(i) == '.') {
3074
+ spec.append(fmt.charAt(i++));
3075
+ while (i < fmt.length() && Character.isDigit(fmt.charAt(i))) {
3076
+ spec.append(fmt.charAt(i++));
3077
+ }
3078
+ }
3079
+ if (i >= fmt.length()) break;
3080
+ char type = fmt.charAt(i++);
3081
+ Object arg = argIdx < args.length ? args[argIdx++] : null;
3082
+ switch (type) {
3083
+ case 'd': case 'i': {
3084
+ spec.append('d');
3085
+ long lv2 = (arg instanceof Number) ? ((Number) arg).longValue() : 0L;
3086
+ result.append(String.format(spec.toString(), lv2));
3087
+ break;
3088
+ }
3089
+ case 'f': {
3090
+ spec.append('f');
3091
+ double dv2 = (arg instanceof Number) ? ((Number) arg).doubleValue() : 0.0;
3092
+ result.append(String.format(spec.toString(), dv2));
3093
+ break;
3094
+ }
3095
+ case 'e': {
3096
+ spec.append('e');
3097
+ double ev2 = (arg instanceof Number) ? ((Number) arg).doubleValue() : 0.0;
3098
+ result.append(String.format(spec.toString(), ev2));
3099
+ break;
3100
+ }
3101
+ case 'g': {
3102
+ spec.append('g');
3103
+ double gv2 = (arg instanceof Number) ? ((Number) arg).doubleValue() : 0.0;
3104
+ result.append(String.format(spec.toString(), gv2));
3105
+ break;
3106
+ }
3107
+ case 's': {
3108
+ spec.append('s');
3109
+ result.append(String.format(spec.toString(), String.valueOf(arg)));
3110
+ break;
3111
+ }
3112
+ case 'x': {
3113
+ spec.append('x');
3114
+ long xv2 = (arg instanceof Number) ? ((Number) arg).longValue() : 0L;
3115
+ result.append(String.format(spec.toString(), xv2));
3116
+ break;
3117
+ }
3118
+ case 'X': {
3119
+ spec.append('X');
3120
+ long Xv2 = (arg instanceof Number) ? ((Number) arg).longValue() : 0L;
3121
+ result.append(String.format(spec.toString(), Xv2));
3122
+ break;
3123
+ }
3124
+ case 'o': {
3125
+ spec.append('o');
3126
+ long ov2 = (arg instanceof Number) ? ((Number) arg).longValue() : 0L;
3127
+ result.append(String.format(spec.toString(), ov2));
3128
+ break;
3129
+ }
3130
+ case 'b': {
3131
+ long bv2 = (arg instanceof Number) ? ((Number) arg).longValue() : 0L;
3132
+ result.append(Long.toBinaryString(bv2));
3133
+ break;
3134
+ }
3135
+ case 'c': {
3136
+ long cv2 = (arg instanceof Number) ? ((Number) arg).longValue() : 0L;
3137
+ result.append((char) cv2);
3138
+ break;
3139
+ }
3140
+ default:
3141
+ result.append('%').append(type);
3142
+ }
3143
+ }
3144
+ return result.toString();
3145
+ }
3146
+
3147
+ /**
3148
+ * Ruby String#succ/next: returns the next string in sequence.
3149
+ */
3150
+ private static String stringSucc(String s) {
3151
+ if (s.isEmpty()) return "";
3152
+ char[] chars = s.toCharArray();
3153
+ int i = chars.length - 1;
3154
+ boolean carry = true;
3155
+ while (i >= 0 && carry) {
3156
+ char c = chars[i];
3157
+ if (c >= 'a' && c <= 'z') {
3158
+ chars[i] = (char)(c == 'z' ? 'a' : c + 1);
3159
+ carry = (c == 'z');
3160
+ } else if (c >= 'A' && c <= 'Z') {
3161
+ chars[i] = (char)(c == 'Z' ? 'A' : c + 1);
3162
+ carry = (c == 'Z');
3163
+ } else if (c >= '0' && c <= '9') {
3164
+ chars[i] = (char)(c == '9' ? '0' : c + 1);
3165
+ carry = (c == '9');
3166
+ } else {
3167
+ chars[i] = (char)(c + 1);
3168
+ carry = false;
3169
+ }
3170
+ i--;
3171
+ }
3172
+ if (carry) {
3173
+ char first = chars[0];
3174
+ char prefix;
3175
+ if (first >= 'a' && first <= 'z') prefix = 'a';
3176
+ else if (first >= 'A' && first <= 'Z') prefix = 'A';
3177
+ else if (first >= '0' && first <= '9') prefix = '1';
3178
+ else prefix = (char)(first + 1);
3179
+ return prefix + new String(chars);
3180
+ }
3181
+ return new String(chars);
3182
+ }
3183
+
3184
+ /**
3185
+ * Extract named group names from a regex Pattern by scanning its pattern string.
3186
+ */
3187
+ private static java.util.List<String> namedGroupNames(java.util.regex.Pattern pat) {
3188
+ java.util.List<String> names = new java.util.ArrayList<>();
3189
+ java.util.regex.Matcher nm = java.util.regex.Pattern.compile("\\(\\?<([a-zA-Z][a-zA-Z0-9]*)>").matcher(pat.pattern());
3190
+ while (nm.find()) names.add(nm.group(1));
3191
+ return names;
3192
+ }
3193
+
3194
+ /**
3195
+ * Generate all r-permutations of arr into result.
3196
+ */
3197
+ @SuppressWarnings("unchecked")
3198
+ private static void generatePerms(KArray<?> arr, int r, int[] indices, boolean[] used, int depth, KArray<Object> result) {
3199
+ if (depth == r) {
3200
+ KArray<Object> perm = new KArray<>();
3201
+ for (int pi = 0; pi < r; pi++) perm.push(arr.get(indices[pi]));
3202
+ result.push(perm);
3203
+ return;
3204
+ }
3205
+ for (int idx = 0; idx < arr.size(); idx++) {
3206
+ if (used[idx]) continue;
3207
+ used[idx] = true;
3208
+ indices[depth] = idx;
3209
+ generatePerms(arr, r, indices, used, depth + 1, result);
3210
+ used[idx] = false;
3211
+ }
3212
+ }
2500
3213
  }