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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +16 -0
- data/lib/konpeito/codegen/cruby_backend.rb +7 -0
- data/lib/konpeito/codegen/jvm_generator.rb +160 -40
- data/lib/konpeito/codegen/llvm_generator.rb +236 -58
- data/lib/konpeito/hir/builder.rb +61 -27
- data/lib/konpeito/hir/nodes.rb +2 -0
- data/lib/konpeito/version.rb +1 -1
- data/tools/konpeito-asm/build.sh +1 -0
- data/tools/konpeito-asm/src/konpeito/runtime/KHash.java +12 -0
- data/tools/konpeito-asm/src/konpeito/runtime/KMatchData.java +50 -1
- data/tools/konpeito-asm/src/konpeito/runtime/RubyDispatch.java +721 -8
- metadata +1 -20
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KArray.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KCompression.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KConditionVariable.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KCrypto.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KFiber.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KFile.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KHTTP.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KHash.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KJSON$Parser.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KJSON.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KMatchData.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KMath.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KRactor.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KRactorPort.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KRubyException.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KSizedQueue.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KThread.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KTime.class +0 -0
- 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
|
-
|
|
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;
|
|
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;
|
|
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
|
-
|
|
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.
|
|
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
|
}
|