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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +18 -0
- data/lib/konpeito/codegen/builtin_methods.rb +4 -2
- data/lib/konpeito/codegen/cruby_backend.rb +122 -10
- data/lib/konpeito/codegen/inliner.rb +9 -3
- data/lib/konpeito/codegen/jvm_generator.rb +3986 -919
- data/lib/konpeito/codegen/llvm_generator.rb +334 -45
- data/lib/konpeito/codegen/monomorphizer.rb +14 -2
- data/lib/konpeito/hir/builder.rb +150 -20
- data/lib/konpeito/hir/nodes.rb +16 -0
- data/lib/konpeito/type_checker/hm_inferrer.rb +100 -41
- data/lib/konpeito/type_checker/types.rb +6 -6
- data/lib/konpeito/type_checker/unification.rb +8 -1
- data/lib/konpeito/version.rb +1 -1
- data/tools/konpeito-asm/build.sh +1 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KArray.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KConditionVariable.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KFiber.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KHash.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KMatchData.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/RubyDispatch.class +0 -0
- data/tools/konpeito-asm/src/KonpeitoAssembler.java +17 -1
- data/tools/konpeito-asm/src/konpeito/runtime/KArray.java +97 -4
- data/tools/konpeito-asm/src/konpeito/runtime/KConditionVariable.java +20 -25
- data/tools/konpeito-asm/src/konpeito/runtime/KFiber.java +112 -0
- data/tools/konpeito-asm/src/konpeito/runtime/KHash.java +67 -0
- data/tools/konpeito-asm/src/konpeito/runtime/KMatchData.java +55 -0
- data/tools/konpeito-asm/src/konpeito/runtime/KRubyException.java +79 -0
- data/tools/konpeito-asm/src/konpeito/runtime/KSizedQueue.java +5 -0
- data/tools/konpeito-asm/src/konpeito/runtime/RubyDispatch.java +285 -19
- 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) ->
|
|
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
|
|
226
|
+
/** Ruby: arr.delete(value) — remove all occurrences, return value or nil */
|
|
192
227
|
public T deleteValue(T value) {
|
|
193
|
-
boolean found =
|
|
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
|
|
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
|
|
10
|
-
*
|
|
11
|
-
*
|
|
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
|
|
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
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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
|
-
|
|
32
|
-
|
|
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
|
-
|
|
42
|
-
|
|
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
|
+
}
|