konpeito 0.2.2 → 0.2.3

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 (33) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +18 -0
  3. data/lib/konpeito/codegen/builtin_methods.rb +4 -2
  4. data/lib/konpeito/codegen/cruby_backend.rb +122 -10
  5. data/lib/konpeito/codegen/inliner.rb +9 -3
  6. data/lib/konpeito/codegen/jvm_generator.rb +3986 -919
  7. data/lib/konpeito/codegen/llvm_generator.rb +334 -45
  8. data/lib/konpeito/codegen/monomorphizer.rb +14 -2
  9. data/lib/konpeito/hir/builder.rb +150 -20
  10. data/lib/konpeito/hir/nodes.rb +16 -0
  11. data/lib/konpeito/type_checker/hm_inferrer.rb +100 -41
  12. data/lib/konpeito/type_checker/types.rb +6 -6
  13. data/lib/konpeito/type_checker/unification.rb +8 -1
  14. data/lib/konpeito/version.rb +1 -1
  15. data/tools/konpeito-asm/build.sh +1 -0
  16. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KArray.class +0 -0
  17. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KConditionVariable.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/KHash.class +0 -0
  20. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KMatchData.class +0 -0
  21. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KRubyException.class +0 -0
  22. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KSizedQueue.class +0 -0
  23. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/RubyDispatch.class +0 -0
  24. data/tools/konpeito-asm/src/KonpeitoAssembler.java +17 -1
  25. data/tools/konpeito-asm/src/konpeito/runtime/KArray.java +97 -4
  26. data/tools/konpeito-asm/src/konpeito/runtime/KConditionVariable.java +20 -25
  27. data/tools/konpeito-asm/src/konpeito/runtime/KFiber.java +112 -0
  28. data/tools/konpeito-asm/src/konpeito/runtime/KHash.java +67 -0
  29. data/tools/konpeito-asm/src/konpeito/runtime/KMatchData.java +55 -0
  30. data/tools/konpeito-asm/src/konpeito/runtime/KRubyException.java +79 -0
  31. data/tools/konpeito-asm/src/konpeito/runtime/KSizedQueue.java +5 -0
  32. data/tools/konpeito-asm/src/konpeito/runtime/RubyDispatch.java +285 -19
  33. metadata +7 -1
@@ -12,6 +12,13 @@ import java.util.*;
12
12
  */
13
13
  public class KArray<T> implements List<T> {
14
14
  private final ArrayList<T> data;
15
+ private boolean frozen = false;
16
+
17
+ /** Ruby: arr.freeze */
18
+ public void freeze() { frozen = true; }
19
+
20
+ /** Ruby: arr.frozen? */
21
+ public boolean isFrozen() { return frozen; }
15
22
 
16
23
  // ========================================================================
17
24
  // Constructors
@@ -127,7 +134,26 @@ public class KArray<T> implements List<T> {
127
134
  @SuppressWarnings("unchecked")
128
135
  public KArray<T> sort() {
129
136
  KArray<T> result = new KArray<>(data);
130
- result.data.sort((a, b) -> ((Comparable<T>) a).compareTo(b));
137
+ result.data.sort((a, b) -> {
138
+ // Try Java Comparable first
139
+ if (a instanceof Comparable) {
140
+ try {
141
+ return ((Comparable<T>) a).compareTo(b);
142
+ } catch (ClassCastException e) {
143
+ // Fall through to op_cmp
144
+ }
145
+ }
146
+ // Try Ruby <=> (op_cmp) via reflection
147
+ try {
148
+ java.lang.reflect.Method cmp = a.getClass().getMethod("op_cmp", Object.class);
149
+ Object res = cmp.invoke(a, b);
150
+ if (res instanceof Long) return ((Long) res).intValue();
151
+ if (res instanceof Integer) return (Integer) res;
152
+ return 0;
153
+ } catch (Exception e) {
154
+ throw new ClassCastException("Cannot compare " + a.getClass().getName());
155
+ }
156
+ });
131
157
  return result;
132
158
  }
133
159
 
@@ -181,6 +207,15 @@ public class KArray<T> implements List<T> {
181
207
  return this;
182
208
  }
183
209
 
210
+ /** Ruby: arr.concat(other) — append all elements from other array, return self */
211
+ @SuppressWarnings("unchecked")
212
+ public KArray<T> concat(Object other) {
213
+ if (other instanceof KArray) {
214
+ data.addAll(((KArray<T>) other).data);
215
+ }
216
+ return this;
217
+ }
218
+
184
219
  /** Ruby: arr.delete_at(index) — remove and return element at index */
185
220
  public T deleteAt(int index) {
186
221
  int idx = index < 0 ? data.size() + index : index;
@@ -188,12 +223,54 @@ public class KArray<T> implements List<T> {
188
223
  return data.remove(idx);
189
224
  }
190
225
 
191
- /** Ruby: arr.delete(value) — remove all occurrences, return last removed or nil */
226
+ /** Ruby: arr.delete(value) — remove all occurrences, return value or nil */
192
227
  public T deleteValue(T value) {
193
- boolean found = data.remove(value);
228
+ boolean found = false;
229
+ java.util.Iterator<T> it = data.iterator();
230
+ while (it.hasNext()) {
231
+ if (java.util.Objects.equals(it.next(), value)) {
232
+ it.remove();
233
+ found = true;
234
+ }
235
+ }
236
+ return found ? value : null;
237
+ }
238
+
239
+ /** Ruby: arr.delete(value) — alias used by invokedynamic dispatch */
240
+ public Object delete(Object value) {
241
+ boolean found = false;
242
+ java.util.Iterator<T> it = data.iterator();
243
+ while (it.hasNext()) {
244
+ if (java.util.Objects.equals(it.next(), value)) {
245
+ it.remove();
246
+ found = true;
247
+ }
248
+ }
194
249
  return found ? value : null;
195
250
  }
196
251
 
252
+ /** Ruby: arr.rotate / arr.rotate(n) — returns new rotated array */
253
+ public KArray<T> rotate() {
254
+ return rotate(1);
255
+ }
256
+
257
+ /** Ruby: arr.rotate(n) — returns new rotated array by n positions */
258
+ public KArray<T> rotate(int n) {
259
+ int size = data.size();
260
+ if (size == 0) return new KArray<>();
261
+ int shift = ((n % size) + size) % size; // normalize negative
262
+ KArray<T> result = new KArray<>(size);
263
+ for (int i = 0; i < size; i++) {
264
+ result.data.add(data.get((i + shift) % size));
265
+ }
266
+ return result;
267
+ }
268
+
269
+ /** Ruby: arr.rotate(n) — overload accepting long for JVM compat */
270
+ public KArray<T> rotate(long n) {
271
+ return rotate((int) n);
272
+ }
273
+
197
274
  /** Ruby: arr.sum — sum all elements (for numeric arrays) */
198
275
  @SuppressWarnings("unchecked")
199
276
  public long sumLong() {
@@ -216,12 +293,18 @@ public class KArray<T> implements List<T> {
216
293
  return total;
217
294
  }
218
295
 
219
- /** Ruby: arr.find_index(value) — returns index or -1 */
296
+ /** Ruby: arr.find_index(value) / arr.index(value) — returns index or nil */
220
297
  public long findIndex(T value) {
221
298
  int idx = data.indexOf(value);
222
299
  return idx;
223
300
  }
224
301
 
302
+ /** Ruby: arr.index(value) — alias for findIndex, returns Long index or null (nil) */
303
+ public Object index(Object value) {
304
+ int idx = data.indexOf(value);
305
+ return idx >= 0 ? Long.valueOf(idx) : null;
306
+ }
307
+
225
308
  /** Ruby: arr.first(n) — returns first n elements */
226
309
  public KArray<T> first(int n) {
227
310
  KArray<T> result = new KArray<>();
@@ -256,6 +339,16 @@ public class KArray<T> implements List<T> {
256
339
  return result;
257
340
  }
258
341
 
342
+ /** Multi-assign splat: collect elements from startIndex to size()-endOffset */
343
+ public KArray<T> splatSlice(int startIndex, int endOffset) {
344
+ KArray<T> result = new KArray<>();
345
+ int end = data.size() - endOffset;
346
+ for (int i = startIndex; i < end; i++) {
347
+ result.data.add(data.get(i));
348
+ }
349
+ return result;
350
+ }
351
+
259
352
  /** Ruby: arr.zip(other) — pairs elements from two arrays */
260
353
  @SuppressWarnings("unchecked")
261
354
  public KArray<KArray<Object>> zip(KArray<?> other) {
@@ -1,48 +1,43 @@
1
1
  package konpeito.runtime;
2
2
 
3
- import java.util.concurrent.locks.Condition;
4
3
  import java.util.concurrent.locks.ReentrantLock;
5
4
 
6
5
  /**
7
6
  * KConditionVariable - Ruby ConditionVariable implementation for JVM backend.
8
7
  *
9
- * Uses an internal ReentrantLock + Condition pair.
10
- * Note: In Ruby, ConditionVariable can be used with any Mutex.
11
- * This simplified implementation uses its own internal lock.
8
+ * Uses an Object monitor internally. When await(mutex) is called, the caller's
9
+ * mutex is released (matching Ruby cv.wait(mutex) semantics), then we wait on
10
+ * the internal monitor, and reacquire the mutex before returning.
12
11
  */
13
12
  public class KConditionVariable {
14
- private final ReentrantLock lock = new ReentrantLock();
15
- private final Condition condition = lock.newCondition();
13
+ private final Object monitor = new Object();
16
14
 
17
- /** Ruby: cv.wait(mutex) — waits for signal */
18
- public void await() {
19
- lock.lock();
20
- try {
21
- condition.await();
22
- } catch (InterruptedException e) {
23
- Thread.currentThread().interrupt();
24
- } finally {
25
- lock.unlock();
15
+ /** Ruby: cv.wait(mutex) — releases mutex, waits for signal, reacquires mutex */
16
+ public void await(ReentrantLock mutex) {
17
+ // Release the caller's mutex (Ruby CV#wait semantics)
18
+ mutex.unlock();
19
+ synchronized (monitor) {
20
+ try {
21
+ monitor.wait();
22
+ } catch (InterruptedException e) {
23
+ Thread.currentThread().interrupt();
24
+ }
26
25
  }
26
+ // Reacquire the caller's mutex before returning
27
+ mutex.lock();
27
28
  }
28
29
 
29
30
  /** Ruby: cv.signal — wakes one waiting thread */
30
31
  public void signal() {
31
- lock.lock();
32
- try {
33
- condition.signal();
34
- } finally {
35
- lock.unlock();
32
+ synchronized (monitor) {
33
+ monitor.notify();
36
34
  }
37
35
  }
38
36
 
39
37
  /** Ruby: cv.broadcast — wakes all waiting threads */
40
38
  public void broadcast() {
41
- lock.lock();
42
- try {
43
- condition.signalAll();
44
- } finally {
45
- lock.unlock();
39
+ synchronized (monitor) {
40
+ monitor.notifyAll();
46
41
  }
47
42
  }
48
43
  }
@@ -0,0 +1,112 @@
1
+ package konpeito.runtime;
2
+
3
+ import java.util.concurrent.Callable;
4
+ import java.util.concurrent.SynchronousQueue;
5
+
6
+ /**
7
+ * KFiber - Ruby Fiber implementation for JVM backend using Java 21 Virtual Threads.
8
+ *
9
+ * Uses a pair of SynchronousQueues for handshake between caller and fiber:
10
+ * - resumeQueue: caller sends resume value to fiber
11
+ * - yieldQueue: fiber sends yielded value to caller
12
+ *
13
+ * ThreadLocal tracks the current fiber for Fiber.yield().
14
+ */
15
+ public class KFiber {
16
+ private static final ThreadLocal<KFiber> currentFiber = new ThreadLocal<>();
17
+
18
+ // Sentinel object to distinguish null/nil yield values from "no value"
19
+ private static final Object SENTINEL = new Object();
20
+
21
+ private final Callable<Object> body;
22
+ private final SynchronousQueue<Object> resumeQueue = new SynchronousQueue<>();
23
+ private final SynchronousQueue<Object> yieldQueue = new SynchronousQueue<>();
24
+ private Thread thread;
25
+ private volatile boolean alive = true;
26
+ private volatile boolean started = false;
27
+
28
+ public KFiber(Callable<Object> body) {
29
+ this.body = body;
30
+ }
31
+
32
+ /**
33
+ * Ruby: fiber.resume or fiber.resume(value)
34
+ * Starts the fiber on first call, resumes it on subsequent calls.
35
+ * Returns the value passed to Fiber.yield or the fiber's final return value.
36
+ */
37
+ public Object resume(Object value) {
38
+ try {
39
+ if (!started) {
40
+ started = true;
41
+ thread = Thread.ofVirtual().start(() -> {
42
+ currentFiber.set(this);
43
+ try {
44
+ Object result = body.call();
45
+ // Fiber completed: send final result
46
+ yieldQueue.put(result != null ? result : SENTINEL);
47
+ } catch (Exception e) {
48
+ try {
49
+ yieldQueue.put(SENTINEL);
50
+ } catch (InterruptedException ie) {
51
+ Thread.currentThread().interrupt();
52
+ }
53
+ } finally {
54
+ alive = false;
55
+ currentFiber.remove();
56
+ }
57
+ });
58
+ } else {
59
+ // Send resume value to fiber
60
+ resumeQueue.put(value != null ? value : SENTINEL);
61
+ }
62
+ // Wait for fiber to yield or complete
63
+ Object result = yieldQueue.take();
64
+ return result == SENTINEL ? null : result;
65
+ } catch (InterruptedException e) {
66
+ Thread.currentThread().interrupt();
67
+ return null;
68
+ }
69
+ }
70
+
71
+ /** Ruby: fiber.resume (no args) */
72
+ public Object resume() {
73
+ return resume(null);
74
+ }
75
+
76
+ /**
77
+ * Ruby: Fiber.yield(value)
78
+ * Called from within a fiber to suspend execution and send a value to the caller.
79
+ * Returns the value passed to the next resume() call.
80
+ */
81
+ public static Object fiberYield(Object value) {
82
+ KFiber fiber = currentFiber.get();
83
+ if (fiber == null) {
84
+ throw new RuntimeException("Fiber.yield called outside of a fiber");
85
+ }
86
+ try {
87
+ // Send yielded value to caller
88
+ fiber.yieldQueue.put(value != null ? value : SENTINEL);
89
+ // Wait for next resume
90
+ Object resumeValue = fiber.resumeQueue.take();
91
+ return resumeValue == SENTINEL ? null : resumeValue;
92
+ } catch (InterruptedException e) {
93
+ Thread.currentThread().interrupt();
94
+ return null;
95
+ }
96
+ }
97
+
98
+ /** Ruby: Fiber.yield (no args) */
99
+ public static Object fiberYield() {
100
+ return fiberYield(null);
101
+ }
102
+
103
+ /** Ruby: fiber.alive? */
104
+ public boolean isAlive() {
105
+ return alive;
106
+ }
107
+
108
+ /** Ruby: Fiber.current */
109
+ public static KFiber current() {
110
+ return currentFiber.get();
111
+ }
112
+ }
@@ -116,6 +116,73 @@ public class KHash<K, V> implements Map<K, V> {
116
116
  return new KArray<>(data.values());
117
117
  }
118
118
 
119
+ /** Helper for group_by: adds elem to the array at hash[key], creating it if absent */
120
+ @SuppressWarnings("unchecked")
121
+ public void groupByAdd(Object key, Object elem) {
122
+ Object existing = data.get((K) key);
123
+ KArray<Object> group;
124
+ if (existing instanceof KArray) {
125
+ group = (KArray<Object>) existing;
126
+ } else {
127
+ group = new KArray<>();
128
+ data.put((K) key, (V) group);
129
+ }
130
+ group.add(elem);
131
+ }
132
+
133
+ /** Ruby: hash.sort_by { |pair| pair[index] } — sort by element at index in each [k,v] pair.
134
+ * Returns KArray of KArray pairs sorted by the element at the given index. */
135
+ @SuppressWarnings("unchecked")
136
+ public KArray<KArray<Object>> sortByIndex(int index) {
137
+ KArray<KArray<Object>> pairs = toArray_();
138
+ // Selection sort by element at given index
139
+ int n = pairs.size();
140
+ for (int i = 0; i < n - 1; i++) {
141
+ int minIdx = i;
142
+ for (int j = i + 1; j < n; j++) {
143
+ Comparable<Object> sj = (Comparable<Object>) pairs.get(j).get(index);
144
+ Comparable<Object> sm = (Comparable<Object>) pairs.get(minIdx).get(index);
145
+ if (sj.compareTo((Object) sm) < 0) {
146
+ minIdx = j;
147
+ }
148
+ }
149
+ if (minIdx != i) {
150
+ KArray<Object> tmp = pairs.get(i);
151
+ pairs.set(i, pairs.get(minIdx));
152
+ pairs.set(minIdx, tmp);
153
+ }
154
+ }
155
+ return pairs;
156
+ }
157
+
158
+ /** Sort pairs by scores — used by JVM codegen for sort_by.
159
+ * pairs and scores are parallel KArrays. Sorts both in-place by scores ascending. */
160
+ @SuppressWarnings("unchecked")
161
+ public static void sortPairsByScores(KArray<Object> pairs, KArray<Object> scores) {
162
+ int n = pairs.size();
163
+ // Selection sort — simple and O(n²) is fine for small hashes
164
+ for (int i = 0; i < n - 1; i++) {
165
+ int minIdx = i;
166
+ for (int j = i + 1; j < n; j++) {
167
+ Comparable<Object> sj = (Comparable<Object>) scores.get(j);
168
+ Comparable<Object> sm = (Comparable<Object>) scores.get(minIdx);
169
+ if (sj.compareTo((Object) sm) < 0) {
170
+ minIdx = j;
171
+ }
172
+ }
173
+ if (minIdx != i) {
174
+ // Swap pairs
175
+ Object tmp = pairs.get(i);
176
+ pairs.set(i, pairs.get(minIdx));
177
+ pairs.set(minIdx, tmp);
178
+ // Swap scores
179
+ Object stmp = scores.get(i);
180
+ scores.set(i, scores.get(minIdx));
181
+ scores.set(minIdx, stmp);
182
+ }
183
+ }
184
+ }
185
+
119
186
  // ========================================================================
120
187
  // Map<K,V> interface delegation
121
188
  // ========================================================================
@@ -0,0 +1,55 @@
1
+ package konpeito.runtime;
2
+
3
+ /**
4
+ * KMatchData - Ruby MatchData implementation for JVM backend.
5
+ *
6
+ * Wraps regex match results with group access and the original input string.
7
+ */
8
+ public class KMatchData {
9
+ private final KArray<String> groups;
10
+ private final String inputString;
11
+
12
+ public KMatchData(KArray<String> groups, String inputString) {
13
+ this.groups = groups;
14
+ this.inputString = inputString;
15
+ }
16
+
17
+ /** MatchData#[](index) — returns the nth match group (0 = full match) */
18
+ public Object get(int index) {
19
+ if (index < 0 || index >= groups.size()) return null;
20
+ return groups.get(index);
21
+ }
22
+
23
+ /** MatchData#to_s — returns the entire matched string (group 0) */
24
+ public String toString() {
25
+ if (groups.isEmpty()) return "";
26
+ return groups.get(0) != null ? groups.get(0) : "";
27
+ }
28
+
29
+ /** MatchData#string — returns a copy of the match string (input) */
30
+ public String string() {
31
+ return inputString;
32
+ }
33
+
34
+ /** MatchData#captures — returns capture groups (excluding group 0) */
35
+ public KArray<String> captures() {
36
+ KArray<String> result = new KArray<>();
37
+ for (int i = 1; i < groups.size(); i++) {
38
+ result.push(groups.get(i));
39
+ }
40
+ return result;
41
+ }
42
+
43
+ /** MatchData#size / MatchData#length — number of elements (including full match) */
44
+ public long length() {
45
+ return groups.size();
46
+ }
47
+
48
+ /** MatchData#pre_match — returns the string before the match */
49
+ // Note: would need start index to implement fully
50
+
51
+ /** MatchData#class */
52
+ public String k_class() {
53
+ return "MatchData";
54
+ }
55
+ }
@@ -0,0 +1,79 @@
1
+ package konpeito.runtime;
2
+
3
+ /**
4
+ * KRubyException - Wrapper for Ruby exceptions on JVM.
5
+ *
6
+ * Carries the Ruby exception class name and supports hierarchy checking
7
+ * for rescue clause matching.
8
+ */
9
+ public class KRubyException extends RuntimeException {
10
+ private final String rubyClassName;
11
+
12
+ // Ruby exception hierarchy (simplified)
13
+ private static final java.util.Map<String, String> PARENT_MAP = new java.util.HashMap<>();
14
+ static {
15
+ // Standard exception hierarchy
16
+ PARENT_MAP.put("RuntimeError", "StandardError");
17
+ PARENT_MAP.put("ArgumentError", "StandardError");
18
+ PARENT_MAP.put("TypeError", "StandardError");
19
+ PARENT_MAP.put("NameError", "StandardError");
20
+ PARENT_MAP.put("NoMethodError", "NameError");
21
+ PARENT_MAP.put("ZeroDivisionError", "StandardError");
22
+ PARENT_MAP.put("RangeError", "StandardError");
23
+ PARENT_MAP.put("IOError", "StandardError");
24
+ PARENT_MAP.put("StopIteration", "StandardError");
25
+ PARENT_MAP.put("KeyError", "StandardError");
26
+ PARENT_MAP.put("IndexError", "StandardError");
27
+ PARENT_MAP.put("NotImplementedError", "StandardError");
28
+ PARENT_MAP.put("StandardError", "Exception");
29
+ PARENT_MAP.put("Exception", null);
30
+ }
31
+
32
+ public KRubyException(String rubyClassName, String message) {
33
+ super(message);
34
+ this.rubyClassName = rubyClassName;
35
+ }
36
+
37
+ public KRubyException(String rubyClassName) {
38
+ super(rubyClassName);
39
+ this.rubyClassName = rubyClassName;
40
+ }
41
+
42
+ public String getRubyClassName() {
43
+ return rubyClassName;
44
+ }
45
+
46
+ /**
47
+ * Register a custom exception class in the hierarchy.
48
+ * Called during class initialization for user-defined exception classes.
49
+ */
50
+ public static void registerExceptionClass(String className, String parentClassName) {
51
+ PARENT_MAP.put(className, parentClassName);
52
+ }
53
+
54
+ /**
55
+ * Check if this exception matches the given rescue class name.
56
+ * Walks up the hierarchy to find a match.
57
+ */
58
+ public boolean matchesRescueClass(String rescueClassName) {
59
+ if (rescueClassName == null || rescueClassName.equals("StandardError")) {
60
+ // bare rescue or StandardError catches all standard errors
61
+ return isSubclassOf(rubyClassName, "StandardError");
62
+ }
63
+ return isSubclassOf(rubyClassName, rescueClassName);
64
+ }
65
+
66
+ private static boolean isSubclassOf(String className, String targetClass) {
67
+ String current = className;
68
+ while (current != null) {
69
+ if (current.equals(targetClass)) return true;
70
+ current = PARENT_MAP.get(current);
71
+ }
72
+ // If class not in hierarchy, assume it's a subclass of StandardError
73
+ // (user-defined exceptions without explicit registration)
74
+ if (className != null && !PARENT_MAP.containsKey(className)) {
75
+ return targetClass.equals("StandardError") || targetClass.equals("Exception");
76
+ }
77
+ return false;
78
+ }
79
+ }
@@ -46,4 +46,9 @@ public class KSizedQueue {
46
46
  public int size() {
47
47
  return queue.size();
48
48
  }
49
+
50
+ /** Ruby: sq.empty? — returns true if queue is empty */
51
+ public boolean empty_q() {
52
+ return queue.isEmpty();
53
+ }
49
54
  }