konpeito 0.1.0

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 (180) hide show
  1. checksums.yaml +7 -0
  2. data/.ruby-version +1 -0
  3. data/CHANGELOG.md +75 -0
  4. data/CONTRIBUTING.md +123 -0
  5. data/LICENSE +21 -0
  6. data/README.md +257 -0
  7. data/Rakefile +11 -0
  8. data/bin/konpeito +6 -0
  9. data/konpeito.gemspec +43 -0
  10. data/lib/konpeito/ast/typed_ast.rb +620 -0
  11. data/lib/konpeito/ast/visitor.rb +78 -0
  12. data/lib/konpeito/cache/cache_manager.rb +230 -0
  13. data/lib/konpeito/cache/dependency_graph.rb +192 -0
  14. data/lib/konpeito/cache.rb +8 -0
  15. data/lib/konpeito/cli/base_command.rb +187 -0
  16. data/lib/konpeito/cli/build_command.rb +220 -0
  17. data/lib/konpeito/cli/check_command.rb +104 -0
  18. data/lib/konpeito/cli/config.rb +231 -0
  19. data/lib/konpeito/cli/deps_command.rb +128 -0
  20. data/lib/konpeito/cli/doctor_command.rb +340 -0
  21. data/lib/konpeito/cli/fmt_command.rb +199 -0
  22. data/lib/konpeito/cli/init_command.rb +312 -0
  23. data/lib/konpeito/cli/lsp_command.rb +40 -0
  24. data/lib/konpeito/cli/run_command.rb +150 -0
  25. data/lib/konpeito/cli/test_command.rb +248 -0
  26. data/lib/konpeito/cli/watch_command.rb +212 -0
  27. data/lib/konpeito/cli.rb +301 -0
  28. data/lib/konpeito/codegen/builtin_methods.rb +229 -0
  29. data/lib/konpeito/codegen/cruby_backend.rb +1090 -0
  30. data/lib/konpeito/codegen/debug_info.rb +352 -0
  31. data/lib/konpeito/codegen/inliner.rb +486 -0
  32. data/lib/konpeito/codegen/jvm_backend.rb +197 -0
  33. data/lib/konpeito/codegen/jvm_generator.rb +13412 -0
  34. data/lib/konpeito/codegen/llvm_generator.rb +13191 -0
  35. data/lib/konpeito/codegen/loop_optimizer.rb +363 -0
  36. data/lib/konpeito/codegen/monomorphizer.rb +359 -0
  37. data/lib/konpeito/codegen/profile_runtime.c +341 -0
  38. data/lib/konpeito/codegen/profiler.rb +99 -0
  39. data/lib/konpeito/compiler.rb +592 -0
  40. data/lib/konpeito/dependency_resolver.rb +296 -0
  41. data/lib/konpeito/diagnostics/collector.rb +127 -0
  42. data/lib/konpeito/diagnostics/diagnostic.rb +237 -0
  43. data/lib/konpeito/diagnostics/renderer.rb +144 -0
  44. data/lib/konpeito/formatter/formatter.rb +1214 -0
  45. data/lib/konpeito/hir/builder.rb +7167 -0
  46. data/lib/konpeito/hir/nodes.rb +2465 -0
  47. data/lib/konpeito/lsp/document_manager.rb +820 -0
  48. data/lib/konpeito/lsp/server.rb +183 -0
  49. data/lib/konpeito/lsp/transport.rb +38 -0
  50. data/lib/konpeito/parser/prism_adapter.rb +65 -0
  51. data/lib/konpeito/platform.rb +103 -0
  52. data/lib/konpeito/profile/report.rb +136 -0
  53. data/lib/konpeito/rbs_inline/preprocessor.rb +199 -0
  54. data/lib/konpeito/stdlib/compression/compression.rb +72 -0
  55. data/lib/konpeito/stdlib/compression/compression.rbs +60 -0
  56. data/lib/konpeito/stdlib/compression/compression_native.c +415 -0
  57. data/lib/konpeito/stdlib/compression/extconf.rb +19 -0
  58. data/lib/konpeito/stdlib/crypto/crypto.rb +85 -0
  59. data/lib/konpeito/stdlib/crypto/crypto.rbs +74 -0
  60. data/lib/konpeito/stdlib/crypto/crypto_native.c +312 -0
  61. data/lib/konpeito/stdlib/crypto/extconf.rb +40 -0
  62. data/lib/konpeito/stdlib/http/extconf.rb +19 -0
  63. data/lib/konpeito/stdlib/http/http.rb +125 -0
  64. data/lib/konpeito/stdlib/http/http.rbs +57 -0
  65. data/lib/konpeito/stdlib/http/http_native.c +440 -0
  66. data/lib/konpeito/stdlib/json/extconf.rb +17 -0
  67. data/lib/konpeito/stdlib/json/json.rb +44 -0
  68. data/lib/konpeito/stdlib/json/json.rbs +33 -0
  69. data/lib/konpeito/stdlib/json/json_native.c +286 -0
  70. data/lib/konpeito/stdlib/ui/extconf.rb +216 -0
  71. data/lib/konpeito/stdlib/ui/konpeito_ui_native.cpp +1625 -0
  72. data/lib/konpeito/stdlib/ui/konpeito_ui_native.h +162 -0
  73. data/lib/konpeito/stdlib/ui/ui.rb +318 -0
  74. data/lib/konpeito/stdlib/ui/ui.rbs +247 -0
  75. data/lib/konpeito/type_checker/annotation_parser.rb +67 -0
  76. data/lib/konpeito/type_checker/hm_inferrer.rb +2565 -0
  77. data/lib/konpeito/type_checker/inferrer.rb +565 -0
  78. data/lib/konpeito/type_checker/rbs_loader.rb +1621 -0
  79. data/lib/konpeito/type_checker/type_resolver.rb +276 -0
  80. data/lib/konpeito/type_checker/types.rb +1434 -0
  81. data/lib/konpeito/type_checker/unification.rb +323 -0
  82. data/lib/konpeito/ui/animation/animated_state.rb +80 -0
  83. data/lib/konpeito/ui/animation/easing.rb +59 -0
  84. data/lib/konpeito/ui/animation/value_tween.rb +66 -0
  85. data/lib/konpeito/ui/app.rb +379 -0
  86. data/lib/konpeito/ui/box.rb +38 -0
  87. data/lib/konpeito/ui/castella.rb +70 -0
  88. data/lib/konpeito/ui/castella_native.rb +76 -0
  89. data/lib/konpeito/ui/chart/area_chart.rb +305 -0
  90. data/lib/konpeito/ui/chart/bar_chart.rb +288 -0
  91. data/lib/konpeito/ui/chart/base_chart.rb +210 -0
  92. data/lib/konpeito/ui/chart/chart_helpers.rb +79 -0
  93. data/lib/konpeito/ui/chart/gauge_chart.rb +171 -0
  94. data/lib/konpeito/ui/chart/heatmap_chart.rb +222 -0
  95. data/lib/konpeito/ui/chart/line_chart.rb +289 -0
  96. data/lib/konpeito/ui/chart/pie_chart.rb +219 -0
  97. data/lib/konpeito/ui/chart/scales.rb +77 -0
  98. data/lib/konpeito/ui/chart/scatter_chart.rb +303 -0
  99. data/lib/konpeito/ui/chart/stacked_bar_chart.rb +276 -0
  100. data/lib/konpeito/ui/column.rb +271 -0
  101. data/lib/konpeito/ui/core.rb +2199 -0
  102. data/lib/konpeito/ui/dsl.rb +443 -0
  103. data/lib/konpeito/ui/frame.rb +171 -0
  104. data/lib/konpeito/ui/frame_native.rb +494 -0
  105. data/lib/konpeito/ui/markdown/ast.rb +124 -0
  106. data/lib/konpeito/ui/markdown/mermaid/layout.rb +387 -0
  107. data/lib/konpeito/ui/markdown/mermaid/models.rb +232 -0
  108. data/lib/konpeito/ui/markdown/mermaid/parser.rb +519 -0
  109. data/lib/konpeito/ui/markdown/mermaid/renderer.rb +336 -0
  110. data/lib/konpeito/ui/markdown/parser.rb +805 -0
  111. data/lib/konpeito/ui/markdown/renderer.rb +639 -0
  112. data/lib/konpeito/ui/markdown/theme.rb +165 -0
  113. data/lib/konpeito/ui/render_node.rb +260 -0
  114. data/lib/konpeito/ui/row.rb +207 -0
  115. data/lib/konpeito/ui/spacer.rb +18 -0
  116. data/lib/konpeito/ui/style.rb +799 -0
  117. data/lib/konpeito/ui/theme.rb +563 -0
  118. data/lib/konpeito/ui/themes/material.rb +35 -0
  119. data/lib/konpeito/ui/themes/tokyo_night.rb +6 -0
  120. data/lib/konpeito/ui/widgets/button.rb +103 -0
  121. data/lib/konpeito/ui/widgets/calendar.rb +1034 -0
  122. data/lib/konpeito/ui/widgets/checkbox.rb +119 -0
  123. data/lib/konpeito/ui/widgets/container.rb +91 -0
  124. data/lib/konpeito/ui/widgets/data_table.rb +667 -0
  125. data/lib/konpeito/ui/widgets/divider.rb +29 -0
  126. data/lib/konpeito/ui/widgets/image.rb +105 -0
  127. data/lib/konpeito/ui/widgets/input.rb +485 -0
  128. data/lib/konpeito/ui/widgets/markdown.rb +57 -0
  129. data/lib/konpeito/ui/widgets/modal.rb +163 -0
  130. data/lib/konpeito/ui/widgets/multiline_input.rb +968 -0
  131. data/lib/konpeito/ui/widgets/multiline_text.rb +180 -0
  132. data/lib/konpeito/ui/widgets/net_image.rb +100 -0
  133. data/lib/konpeito/ui/widgets/progress_bar.rb +70 -0
  134. data/lib/konpeito/ui/widgets/radio_buttons.rb +93 -0
  135. data/lib/konpeito/ui/widgets/slider.rb +133 -0
  136. data/lib/konpeito/ui/widgets/switch.rb +84 -0
  137. data/lib/konpeito/ui/widgets/tabs.rb +157 -0
  138. data/lib/konpeito/ui/widgets/text.rb +110 -0
  139. data/lib/konpeito/ui/widgets/tree.rb +426 -0
  140. data/lib/konpeito/version.rb +5 -0
  141. data/lib/konpeito.rb +109 -0
  142. data/test_native_array.rb +172 -0
  143. data/test_native_array_class.rb +197 -0
  144. data/test_native_class.rb +151 -0
  145. data/tools/konpeito-asm/build.sh +65 -0
  146. data/tools/konpeito-asm/lib/asm-9.7.1.jar +0 -0
  147. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KArray.class +0 -0
  148. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KCompression.class +0 -0
  149. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KConditionVariable.class +0 -0
  150. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KCrypto.class +0 -0
  151. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KFile.class +0 -0
  152. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KHTTP.class +0 -0
  153. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KHash.class +0 -0
  154. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KJSON$Parser.class +0 -0
  155. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KJSON.class +0 -0
  156. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KMath.class +0 -0
  157. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KRactor.class +0 -0
  158. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KRactorPort.class +0 -0
  159. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KSizedQueue.class +0 -0
  160. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KThread.class +0 -0
  161. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KTime.class +0 -0
  162. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/RubyDispatch.class +0 -0
  163. data/tools/konpeito-asm/src/ClassIntrospector.java +312 -0
  164. data/tools/konpeito-asm/src/KonpeitoAssembler.java +659 -0
  165. data/tools/konpeito-asm/src/konpeito/runtime/KArray.java +390 -0
  166. data/tools/konpeito-asm/src/konpeito/runtime/KCompression.java +168 -0
  167. data/tools/konpeito-asm/src/konpeito/runtime/KConditionVariable.java +48 -0
  168. data/tools/konpeito-asm/src/konpeito/runtime/KCrypto.java +151 -0
  169. data/tools/konpeito-asm/src/konpeito/runtime/KFile.java +100 -0
  170. data/tools/konpeito-asm/src/konpeito/runtime/KHTTP.java +113 -0
  171. data/tools/konpeito-asm/src/konpeito/runtime/KHash.java +228 -0
  172. data/tools/konpeito-asm/src/konpeito/runtime/KJSON.java +405 -0
  173. data/tools/konpeito-asm/src/konpeito/runtime/KMath.java +54 -0
  174. data/tools/konpeito-asm/src/konpeito/runtime/KRactor.java +244 -0
  175. data/tools/konpeito-asm/src/konpeito/runtime/KRactorPort.java +53 -0
  176. data/tools/konpeito-asm/src/konpeito/runtime/KSizedQueue.java +49 -0
  177. data/tools/konpeito-asm/src/konpeito/runtime/KThread.java +49 -0
  178. data/tools/konpeito-asm/src/konpeito/runtime/KTime.java +53 -0
  179. data/tools/konpeito-asm/src/konpeito/runtime/RubyDispatch.java +416 -0
  180. metadata +267 -0
@@ -0,0 +1,53 @@
1
+ package konpeito.runtime;
2
+
3
+ import java.util.concurrent.LinkedBlockingQueue;
4
+
5
+ /**
6
+ * KRactorPort - Ruby 4.0 Ractor::Port implementation for JVM backend.
7
+ *
8
+ * Standalone message queue for inter-Ractor communication.
9
+ */
10
+ public class KRactorPort {
11
+ private final LinkedBlockingQueue<Object> queue;
12
+ private volatile boolean closed;
13
+
14
+ /** Ruby: Ractor::Port.new */
15
+ public KRactorPort() {
16
+ this.queue = new LinkedBlockingQueue<>();
17
+ this.closed = false;
18
+ }
19
+
20
+ /** Ruby: port.send(msg) or port << msg */
21
+ public void send(Object msg) {
22
+ if (closed) {
23
+ throw new RuntimeException("can't send to a closed port");
24
+ }
25
+ queue.offer(msg);
26
+ }
27
+
28
+ /** Ruby: port.receive */
29
+ public Object receive() {
30
+ try {
31
+ return queue.take();
32
+ } catch (InterruptedException e) {
33
+ Thread.currentThread().interrupt();
34
+ return null;
35
+ }
36
+ }
37
+
38
+ /** Ruby: port.close */
39
+ public void close() {
40
+ closed = true;
41
+ queue.clear();
42
+ }
43
+
44
+ /** Ruby: port.closed? */
45
+ public boolean isClosed() {
46
+ return closed;
47
+ }
48
+
49
+ /** Package-private poll for Ractor.select */
50
+ Object poll() {
51
+ return queue.poll();
52
+ }
53
+ }
@@ -0,0 +1,49 @@
1
+ package konpeito.runtime;
2
+
3
+ import java.util.concurrent.ArrayBlockingQueue;
4
+
5
+ /**
6
+ * KSizedQueue - Ruby SizedQueue implementation for JVM backend.
7
+ *
8
+ * Wraps ArrayBlockingQueue to provide Ruby's SizedQueue semantics:
9
+ * - push blocks when queue is full
10
+ * - pop blocks when queue is empty
11
+ */
12
+ public class KSizedQueue {
13
+ private final ArrayBlockingQueue<Object> queue;
14
+ private final int maxSize;
15
+
16
+ public KSizedQueue(int max) {
17
+ this.maxSize = max;
18
+ this.queue = new ArrayBlockingQueue<>(max);
19
+ }
20
+
21
+ /** Ruby: sq.push(value) — blocks if full */
22
+ public void push(Object value) {
23
+ try {
24
+ queue.put(value);
25
+ } catch (InterruptedException e) {
26
+ Thread.currentThread().interrupt();
27
+ }
28
+ }
29
+
30
+ /** Ruby: sq.pop — blocks if empty */
31
+ public Object pop() {
32
+ try {
33
+ return queue.take();
34
+ } catch (InterruptedException e) {
35
+ Thread.currentThread().interrupt();
36
+ return null;
37
+ }
38
+ }
39
+
40
+ /** Ruby: sq.max — returns maximum queue size */
41
+ public int max() {
42
+ return maxSize;
43
+ }
44
+
45
+ /** Ruby: sq.size — returns current queue size */
46
+ public int size() {
47
+ return queue.size();
48
+ }
49
+ }
@@ -0,0 +1,49 @@
1
+ package konpeito.runtime;
2
+
3
+ import java.util.concurrent.Callable;
4
+
5
+ /**
6
+ * KThread - Ruby Thread implementation for JVM backend using Java 21 Virtual Threads.
7
+ *
8
+ * Maps Ruby's Thread.new { } to Thread.ofVirtual().start().
9
+ * Supports thread.value (join + return value) and thread.join.
10
+ */
11
+ public class KThread {
12
+ private final Thread thread;
13
+ private volatile Object result;
14
+ private volatile Throwable error;
15
+
16
+ public KThread(Callable<Object> task) {
17
+ this.thread = Thread.ofVirtual().start(() -> {
18
+ try {
19
+ result = task.call();
20
+ } catch (Throwable t) {
21
+ error = t;
22
+ }
23
+ });
24
+ }
25
+
26
+ /** Ruby: thread.join — waits for thread completion, returns self */
27
+ public KThread join() {
28
+ try {
29
+ thread.join();
30
+ } catch (InterruptedException e) {
31
+ Thread.currentThread().interrupt();
32
+ }
33
+ return this;
34
+ }
35
+
36
+ /** Ruby: thread.value — waits for thread completion and returns result */
37
+ public Object getValue() {
38
+ join();
39
+ if (error != null) {
40
+ throw new RuntimeException(error);
41
+ }
42
+ return result;
43
+ }
44
+
45
+ /** Ruby: thread.alive? */
46
+ public boolean isAlive() {
47
+ return thread.isAlive();
48
+ }
49
+ }
@@ -0,0 +1,53 @@
1
+ package konpeito.runtime;
2
+
3
+ import java.time.Instant;
4
+ import java.time.LocalDateTime;
5
+ import java.time.ZoneId;
6
+ import java.time.ZonedDateTime;
7
+ import java.time.format.DateTimeFormatter;
8
+
9
+ /**
10
+ * KTime - Time operations for Konpeito JVM backend.
11
+ * Maps to KonpeitoTime Ruby module (JVM-only).
12
+ * Uses java.time.* (Java 8+).
13
+ */
14
+ public class KTime {
15
+
16
+ /** KonpeitoTime.now -> String (ISO 8601) */
17
+ public static String now() {
18
+ return Instant.now().toString();
19
+ }
20
+
21
+ /** KonpeitoTime.epoch_millis -> long */
22
+ public static long epochMillis() {
23
+ return System.currentTimeMillis();
24
+ }
25
+
26
+ /** KonpeitoTime.epoch_nanos -> long (monotonic, for benchmarking) */
27
+ public static long epochNanos() {
28
+ return System.nanoTime();
29
+ }
30
+
31
+ /** KonpeitoTime.format(epoch_millis, pattern) -> String */
32
+ public static String format(long epochMillis, String pattern) {
33
+ try {
34
+ Instant instant = Instant.ofEpochMilli(epochMillis);
35
+ ZonedDateTime zdt = instant.atZone(ZoneId.systemDefault());
36
+ DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern);
37
+ return zdt.format(formatter);
38
+ } catch (Exception e) {
39
+ throw new RuntimeException("Time format failed: " + e.getMessage(), e);
40
+ }
41
+ }
42
+
43
+ /** KonpeitoTime.parse(str, pattern) -> long (epoch millis) */
44
+ public static long parse(String str, String pattern) {
45
+ try {
46
+ DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern);
47
+ LocalDateTime ldt = LocalDateTime.parse(str, formatter);
48
+ return ldt.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
49
+ } catch (Exception e) {
50
+ throw new RuntimeException("Time parse failed: " + e.getMessage(), e);
51
+ }
52
+ }
53
+ }
@@ -0,0 +1,416 @@
1
+ package konpeito.runtime;
2
+
3
+ import java.lang.invoke.*;
4
+ import java.lang.reflect.Method;
5
+
6
+ /**
7
+ * RubyDispatch - Bootstrap method for invokedynamic-based Ruby method dispatch.
8
+ *
9
+ * When the Konpeito AOT compiler cannot statically resolve a method call
10
+ * (receiver type is unknown at compile time), it emits an invokedynamic
11
+ * instruction that delegates to this class. At runtime, the JVM calls
12
+ * the bootstrap method once per call site, which returns a CallSite
13
+ * wrapping a generic dispatch method. The dispatch method uses reflection
14
+ * to find and invoke the correct method on the actual receiver object.
15
+ *
16
+ * This is analogous to Objective-C's objc_msgSend: AOT compilation with
17
+ * dynamic dispatch for unresolved calls. The JVM's JIT compiler can
18
+ * further optimize these call sites based on runtime type profiles.
19
+ */
20
+ public class RubyDispatch {
21
+
22
+ /**
23
+ * Ruby operator/method name → Java method name aliases.
24
+ * When the sanitized Ruby name doesn't match a Java method, try these fallbacks.
25
+ * jvm_method_name() converts: [] → op_aref, []= → op_aset, << → op_lshift,
26
+ * empty? → empty_q, include? → include_q, etc.
27
+ */
28
+ private static final java.util.Map<String, String[]> RUBY_NAME_ALIASES = new java.util.HashMap<>();
29
+ static {
30
+ // Operator aliases
31
+ RUBY_NAME_ALIASES.put("op_aref", new String[]{"get"});
32
+ RUBY_NAME_ALIASES.put("op_aset", new String[]{"set", "put"});
33
+ RUBY_NAME_ALIASES.put("op_lshift", new String[]{"push", "add"});
34
+
35
+ // Ruby ? methods (sanitized with _q suffix)
36
+ RUBY_NAME_ALIASES.put("empty_q", new String[]{"isEmpty_", "isEmpty"});
37
+ RUBY_NAME_ALIASES.put("include_q", new String[]{"includes", "contains"});
38
+ RUBY_NAME_ALIASES.put("has_key_q", new String[]{"hasKey", "containsKey"});
39
+ RUBY_NAME_ALIASES.put("has_value_q", new String[]{"hasValue", "containsValue"});
40
+ RUBY_NAME_ALIASES.put("nil_q", new String[]{"isNil"});
41
+ RUBY_NAME_ALIASES.put("alive_q", new String[]{"isAlive"});
42
+
43
+ // Ruby ! methods (sanitized with _bang suffix)
44
+ RUBY_NAME_ALIASES.put("merge_bang", new String[]{"mergeInPlace"});
45
+
46
+ // Underscore methods mapped to camelCase
47
+ RUBY_NAME_ALIASES.put("delete_at", new String[]{"deleteAt"});
48
+ RUBY_NAME_ALIASES.put("find_index", new String[]{"findIndex"});
49
+ RUBY_NAME_ALIASES.put("each_key", new String[]{"eachKeys"});
50
+ RUBY_NAME_ALIASES.put("each_value", new String[]{"eachValues"});
51
+ RUBY_NAME_ALIASES.put("sum_long", new String[]{"sumLong"});
52
+ RUBY_NAME_ALIASES.put("sum_double", new String[]{"sumDouble"});
53
+ RUBY_NAME_ALIASES.put("delete_value", new String[]{"deleteValue"});
54
+
55
+ // Standard Ruby → Java mappings
56
+ RUBY_NAME_ALIASES.put("to_s", new String[]{"toString"});
57
+ RUBY_NAME_ALIASES.put("to_a", new String[]{"toArray_"});
58
+
59
+ // Hash: keys/values → Ruby-specific methods returning KArray
60
+ RUBY_NAME_ALIASES.put("keys", new String[]{"rubyKeys"});
61
+ RUBY_NAME_ALIASES.put("values", new String[]{"rubyValues"});
62
+ }
63
+
64
+ /**
65
+ * Bootstrap method — called once per call site by the JVM.
66
+ *
67
+ * The invokedynamic instruction's name is the Ruby method name,
68
+ * and the type describes (Object receiver, Object arg1, ...) -> Object.
69
+ */
70
+ public static CallSite bootstrap(MethodHandles.Lookup lookup, String methodName,
71
+ MethodType type) throws Exception {
72
+ MethodHandle dispatch = lookup.findStatic(
73
+ RubyDispatch.class, "dispatch",
74
+ MethodType.methodType(Object.class, String.class, Object[].class));
75
+
76
+ // Bind the method name as the first argument
77
+ dispatch = MethodHandles.insertArguments(dispatch, 0, methodName);
78
+
79
+ // Collect all invokedynamic params into Object[] for dispatch
80
+ dispatch = dispatch.asCollector(Object[].class, type.parameterCount());
81
+
82
+ // Adapt to match the call site type
83
+ dispatch = dispatch.asType(type);
84
+
85
+ return new ConstantCallSite(dispatch);
86
+ }
87
+
88
+ /**
89
+ * Generic dispatch — called at every invocation.
90
+ * args[0] = receiver, args[1..] = method arguments
91
+ */
92
+ public static Object dispatch(String methodName, Object[] args) throws Throwable {
93
+ if (args.length == 0 || args[0] == null) {
94
+ throw new NullPointerException("Method '" + methodName + "' called on null receiver");
95
+ }
96
+
97
+ Object receiver = args[0];
98
+ Object[] methodArgs = new Object[args.length - 1];
99
+ System.arraycopy(args, 1, methodArgs, 0, methodArgs.length);
100
+
101
+ Class<?> clazz = receiver.getClass();
102
+
103
+ // Built-in operator handling for primitive boxed types (Long, Double, Boolean, String)
104
+ // These don't have op_* methods, so handle them directly
105
+ Object builtinResult = tryBuiltinOperator(methodName, receiver, methodArgs);
106
+ if (builtinResult != SENTINEL) {
107
+ return builtinResult;
108
+ }
109
+
110
+ // Find method by name and arity (Ruby doesn't have overloading by type)
111
+ Method found = findMethod(clazz, methodName, methodArgs.length);
112
+
113
+ // If not found, try Ruby name aliases (op_aref → get, empty_q → isEmpty_, etc.)
114
+ if (found == null) {
115
+ String[] aliases = RUBY_NAME_ALIASES.get(methodName);
116
+ if (aliases != null) {
117
+ for (String alias : aliases) {
118
+ found = findMethod(clazz, alias, methodArgs.length);
119
+ if (found != null) break;
120
+ }
121
+ }
122
+ }
123
+
124
+ // If still not found, try snake_case → camelCase conversion (Ruby → Java convention)
125
+ if (found == null && methodName.contains("_")) {
126
+ String camelName = snakeToCamel(methodName);
127
+ found = findMethod(clazz, camelName, methodArgs.length);
128
+ }
129
+
130
+ if (found != null) {
131
+ found.setAccessible(true);
132
+ Object[] adapted = adaptArgs(found, methodArgs);
133
+ Object result = found.invoke(receiver, adapted);
134
+ return boxResult(result, found.getReturnType());
135
+ }
136
+
137
+ // Handle array indexing: Object[].op_aref(index)
138
+ if (receiver.getClass().isArray() && methodName.equals("op_aref") && methodArgs.length == 1) {
139
+ int idx = ((Number) methodArgs[0]).intValue();
140
+ Object[] arr = (Object[]) receiver;
141
+ if (idx < 0) idx = arr.length + idx;
142
+ return arr[idx];
143
+ }
144
+
145
+ throw new NoSuchMethodError(
146
+ clazz.getName() + "." + methodName + " (arity " + methodArgs.length + ")");
147
+ }
148
+
149
+ // Sentinel object to indicate "no built-in handler found"
150
+ private static final Object SENTINEL = new Object();
151
+
152
+ /**
153
+ * Handle built-in operators on Long, Double, Boolean, String.
154
+ * Returns SENTINEL if no built-in handler applies.
155
+ */
156
+ private static Object tryBuiltinOperator(String methodName, Object receiver, Object[] args) {
157
+ // Long (Integer) arithmetic operators
158
+ if (receiver instanceof Long) {
159
+ long lv = (Long) receiver;
160
+ if (args.length == 1 && args[0] instanceof Number) {
161
+ long rv = ((Number) args[0]).longValue();
162
+ switch (methodName) {
163
+ case "op_plus": return Long.valueOf(lv + rv);
164
+ case "op_minus": return Long.valueOf(lv - rv);
165
+ case "op_mul": return Long.valueOf(lv * rv);
166
+ case "op_div": return Long.valueOf(lv / rv);
167
+ case "op_mod": return Long.valueOf(lv % rv);
168
+ case "op_and": return Long.valueOf(lv & rv);
169
+ case "op_or": return Long.valueOf(lv | rv);
170
+ case "op_xor": return Long.valueOf(lv ^ rv);
171
+ case "op_lshift": return Long.valueOf(lv << rv);
172
+ case "op_rshift": return Long.valueOf(lv >> rv);
173
+ case "op_eq": return Boolean.valueOf(lv == rv);
174
+ case "op_neq": return Boolean.valueOf(lv != rv);
175
+ case "op_lt": return Boolean.valueOf(lv < rv);
176
+ case "op_gt": return Boolean.valueOf(lv > rv);
177
+ case "op_le": return Boolean.valueOf(lv <= rv);
178
+ case "op_ge": return Boolean.valueOf(lv >= rv);
179
+ case "op_spaceship": case "op_cmp": return Long.valueOf(Long.compare(lv, rv));
180
+ }
181
+ }
182
+ if (args.length == 0) {
183
+ switch (methodName) {
184
+ case "to_s": return Long.toString(lv);
185
+ case "to_f": return Double.valueOf((double) lv);
186
+ case "abs": return Long.valueOf(Math.abs(lv));
187
+ case "even_q": return Boolean.valueOf(lv % 2 == 0);
188
+ case "odd_q": return Boolean.valueOf(lv % 2 != 0);
189
+ case "zero_q": return Boolean.valueOf(lv == 0);
190
+ case "positive_q": return Boolean.valueOf(lv > 0);
191
+ case "negative_q": return Boolean.valueOf(lv < 0);
192
+ }
193
+ }
194
+ }
195
+
196
+ // Double (Float) arithmetic operators
197
+ if (receiver instanceof Double) {
198
+ double dv = (Double) receiver;
199
+ if (args.length == 1 && args[0] instanceof Number) {
200
+ double rv = ((Number) args[0]).doubleValue();
201
+ switch (methodName) {
202
+ case "op_plus": return Double.valueOf(dv + rv);
203
+ case "op_minus": return Double.valueOf(dv - rv);
204
+ case "op_mul": return Double.valueOf(dv * rv);
205
+ case "op_div": return Double.valueOf(dv / rv);
206
+ case "op_eq": return Boolean.valueOf(dv == rv);
207
+ case "op_neq": return Boolean.valueOf(dv != rv);
208
+ case "op_lt": return Boolean.valueOf(dv < rv);
209
+ case "op_gt": return Boolean.valueOf(dv > rv);
210
+ case "op_le": return Boolean.valueOf(dv <= rv);
211
+ case "op_ge": return Boolean.valueOf(dv >= rv);
212
+ case "op_spaceship": case "op_cmp": return Long.valueOf(Double.compare(dv, rv));
213
+ }
214
+ }
215
+ if (args.length == 0) {
216
+ switch (methodName) {
217
+ case "to_s": return Double.toString(dv);
218
+ case "to_i": return Long.valueOf((long) dv);
219
+ case "abs": return Double.valueOf(Math.abs(dv));
220
+ case "zero_q": return Boolean.valueOf(dv == 0.0);
221
+ case "positive_q": return Boolean.valueOf(dv > 0.0);
222
+ case "negative_q": return Boolean.valueOf(dv < 0.0);
223
+ }
224
+ }
225
+ }
226
+
227
+ // Boolean operators
228
+ if (receiver instanceof Boolean) {
229
+ boolean bv = (Boolean) receiver;
230
+ if (args.length == 0) {
231
+ switch (methodName) {
232
+ case "to_s": return Boolean.toString(bv);
233
+ case "op_not": return Boolean.valueOf(!bv);
234
+ }
235
+ }
236
+ if (args.length == 1) {
237
+ switch (methodName) {
238
+ case "op_eq": return Boolean.valueOf(bv == Boolean.TRUE.equals(args[0]));
239
+ case "op_neq": return Boolean.valueOf(bv != Boolean.TRUE.equals(args[0]));
240
+ }
241
+ }
242
+ }
243
+
244
+ // String methods
245
+ if (receiver instanceof String) {
246
+ String sv = (String) receiver;
247
+
248
+ // String comparison and concatenation operators
249
+ if (args.length == 1 && args[0] instanceof String) {
250
+ String rv = (String) args[0];
251
+ switch (methodName) {
252
+ case "op_eq": return Boolean.valueOf(sv.equals(rv));
253
+ case "op_neq": return Boolean.valueOf(!sv.equals(rv));
254
+ case "op_spaceship": case "op_cmp": return Long.valueOf(sv.compareTo(rv));
255
+ case "op_plus": return sv + rv;
256
+ case "split": {
257
+ // Return KArray instead of String[]
258
+ String[] parts = sv.split(java.util.regex.Pattern.quote(rv), -1);
259
+ KArray arr = new KArray();
260
+ for (String part : parts) arr.push(part);
261
+ return arr;
262
+ }
263
+ case "include_q": return Boolean.valueOf(sv.contains(rv));
264
+ case "start_with_q": return Boolean.valueOf(sv.startsWith(rv));
265
+ case "end_with_q": return Boolean.valueOf(sv.endsWith(rv));
266
+ }
267
+ }
268
+
269
+ // String#[](start, length) → substring
270
+ if (methodName.equals("op_aref")) {
271
+ if (args.length == 2 && args[0] instanceof Number && args[1] instanceof Number) {
272
+ int start = ((Number) args[0]).intValue();
273
+ int len = ((Number) args[1]).intValue();
274
+ if (start < 0) start = sv.length() + start;
275
+ if (start < 0 || start >= sv.length()) return "";
276
+ int end = Math.min(start + len, sv.length());
277
+ return sv.substring(start, end);
278
+ }
279
+ if (args.length == 1 && args[0] instanceof Number) {
280
+ int idx = ((Number) args[0]).intValue();
281
+ if (idx < 0) idx = sv.length() + idx;
282
+ if (idx < 0 || idx >= sv.length()) return null;
283
+ return String.valueOf(sv.charAt(idx));
284
+ }
285
+ }
286
+
287
+ // No-arg String methods
288
+ if (args.length == 0) {
289
+ switch (methodName) {
290
+ case "length": return Long.valueOf(sv.length());
291
+ case "size": return Long.valueOf(sv.length());
292
+ case "strip": return sv.strip();
293
+ case "upcase": return sv.toUpperCase();
294
+ case "downcase": return sv.toLowerCase();
295
+ case "reverse": return new StringBuilder(sv).reverse().toString();
296
+ case "empty_q": return Boolean.valueOf(sv.isEmpty());
297
+ case "to_s": return sv;
298
+ case "to_i": {
299
+ try { return Long.parseLong(sv.strip()); }
300
+ catch (NumberFormatException e) { return Long.valueOf(0); }
301
+ }
302
+ case "to_f": {
303
+ try { return Double.parseDouble(sv.strip()); }
304
+ catch (NumberFormatException e) { return Double.valueOf(0.0); }
305
+ }
306
+ }
307
+ }
308
+
309
+ // String + non-String (convert to string first)
310
+ if (args.length == 1 && methodName.equals("op_plus")) {
311
+ return sv + String.valueOf(args[0]);
312
+ }
313
+ }
314
+
315
+ return SENTINEL;
316
+ }
317
+
318
+ /**
319
+ * Convert snake_case to camelCase.
320
+ * "clip_rect" → "clipRect", "get_width" → "getWidth"
321
+ */
322
+ private static String snakeToCamel(String snake) {
323
+ StringBuilder sb = new StringBuilder();
324
+ boolean capitalizeNext = false;
325
+ for (int i = 0; i < snake.length(); i++) {
326
+ char c = snake.charAt(i);
327
+ if (c == '_') {
328
+ capitalizeNext = true;
329
+ } else {
330
+ if (capitalizeNext) {
331
+ sb.append(Character.toUpperCase(c));
332
+ capitalizeNext = false;
333
+ } else {
334
+ sb.append(c);
335
+ }
336
+ }
337
+ }
338
+ return sb.toString();
339
+ }
340
+
341
+ /**
342
+ * Find a method by name and arity on the given class.
343
+ * Returns null if not found.
344
+ */
345
+ private static Method findMethod(Class<?> clazz, String name, int arity) {
346
+ for (Method m : clazz.getMethods()) {
347
+ if (m.getName().equals(name) && m.getParameterCount() == arity) {
348
+ return m;
349
+ }
350
+ }
351
+ return null;
352
+ }
353
+
354
+ /**
355
+ * Convert Object args to match method parameter types.
356
+ * Handles Long↔int/long, Double↔double conversions needed because
357
+ * Konpeito uses long for Integer and double for Float, but Java
358
+ * methods may use int parameters.
359
+ */
360
+ private static Object[] adaptArgs(Method method, Object[] args) {
361
+ Class<?>[] paramTypes = method.getParameterTypes();
362
+ Object[] adapted = new Object[args.length];
363
+ for (int i = 0; i < args.length; i++) {
364
+ if (i < paramTypes.length && args[i] != null) {
365
+ adapted[i] = convertArg(args[i], paramTypes[i]);
366
+ } else {
367
+ adapted[i] = args[i];
368
+ }
369
+ }
370
+ return adapted;
371
+ }
372
+
373
+ private static Object convertArg(Object arg, Class<?> target) {
374
+ if (target == long.class) {
375
+ if (arg instanceof Long) return arg;
376
+ if (arg instanceof Number) return ((Number) arg).longValue();
377
+ }
378
+ if (target == int.class && arg instanceof Number) {
379
+ return ((Number) arg).intValue();
380
+ }
381
+ if (target == double.class) {
382
+ if (arg instanceof Double) return arg;
383
+ if (arg instanceof Number) return ((Number) arg).doubleValue();
384
+ }
385
+ if (target == float.class && arg instanceof Number) {
386
+ return ((Number) arg).floatValue();
387
+ }
388
+ if (target == boolean.class && arg instanceof Boolean) {
389
+ return arg;
390
+ }
391
+ return arg;
392
+ }
393
+
394
+ /**
395
+ * Box primitive return values.
396
+ * Converts Java int returns to long (Konpeito convention: Integer = long).
397
+ * Wraps array returns in KArray for Ruby compatibility.
398
+ */
399
+ private static Object boxResult(Object result, Class<?> returnType) {
400
+ if (returnType == void.class) return null;
401
+ if (returnType == int.class && result instanceof Integer) {
402
+ return Long.valueOf(((Integer) result).longValue());
403
+ }
404
+ // Wrap array results in KArray (e.g. String.split returns String[])
405
+ if (result != null && result.getClass().isArray()) {
406
+ KArray arr = new KArray();
407
+ if (result instanceof Object[]) {
408
+ for (Object item : (Object[]) result) {
409
+ arr.push(item);
410
+ }
411
+ }
412
+ return arr;
413
+ }
414
+ return result;
415
+ }
416
+ }