Skip to content

Commit f71ce6d

Browse files
authored
Merge pull request #36 from DanexCodr/copilot/extend-natural-arrays-nd-support
Add ND-style natural array indexing, shape-aware array broadcasting, and array literal methods
2 parents b14f1f0 + f0fa80a commit f71ce6d

6 files changed

Lines changed: 678 additions & 50 deletions

File tree

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,40 @@
11
unit test
22

33
Broadcaster {
4+
share main() {
5+
out("=== Array Broadcasting (not unit broadcast) ===")
6+
7+
a := [1 to 5]
8+
b := [1 to 5]
9+
c := a * b
10+
out("c[0] = " + c[0] + " (expect 1)")
11+
out("c[4] = " + c[4] + " (expect 25)")
12+
13+
matrix := [[1, 2, 3], [4, 5, 6]]
14+
vector := [10, 20, 30]
15+
sum := matrix + vector
16+
out("sum[0,0] = " + sum[0,0] + " (expect 11)")
17+
out("sum[1,2] = " + sum[1,2] + " (expect 36)")
18+
19+
matrix[1,2] = 99
20+
out("matrix[1,2] = " + matrix[1,2] + " (expect 99)")
21+
22+
naturalGrid := [[1 to 3], [4 to 6]]
23+
naturalGrid[0,1] = 777
24+
out("naturalGrid[0,1] = " + naturalGrid[0,1] + " (expect 777)")
25+
26+
nd := [1 to 2, 1 to 3]
27+
out("nd[1,2] = " + nd[1,2] + " (expect 3)")
28+
29+
mapped := [1, 2, 3].map("+", 10)
30+
filtered := [1, 2, 3, 4].filter(">=", 3)
31+
reduced := [1, 2, 3, 4].reduce("+", 0)
32+
33+
out("mapped[0] = " + mapped[0] + " (expect 11)")
34+
out("mapped[2] = " + mapped[2] + " (expect 13)")
35+
out("filtered[0] = " + filtered[0] + " (expect 3)")
36+
out("filtered[1] = " + filtered[1] + " (expect 4)")
37+
out("reduced = " + reduced + " (expect 10)")
38+
}
439

540
}

src/main/java/cod/interpreter/InterpreterVisitor.java

Lines changed: 118 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1229,6 +1229,21 @@ public Object visit(PropertyAccessNode node) {
12291229
}
12301230
}
12311231

1232+
if (node.right instanceof MethodCallNode) {
1233+
MethodCallNode literalMethod = (MethodCallNode) node.right;
1234+
String methodName = literalMethod.name;
1235+
if (literalRegistry.hasMethod(leftObj, methodName)) {
1236+
List<Object> evaluatedArgs = new ArrayList<Object>();
1237+
if (literalMethod.arguments != null) {
1238+
for (ExprNode arg : literalMethod.arguments) {
1239+
Object argValue = dispatch(arg);
1240+
evaluatedArgs.add(typeSystem.unwrap(argValue));
1241+
}
1242+
}
1243+
return literalRegistry.handleMethod(leftObj, methodName, evaluatedArgs, ctx);
1244+
}
1245+
}
1246+
12321247
if (leftObj instanceof NaturalArray) {
12331248
NaturalArray natural = (NaturalArray) leftObj;
12341249
if (natural.hasPendingUpdates()) {
@@ -1395,23 +1410,42 @@ public Object visit(TypeCastNode node) {
13951410

13961411
@SuppressWarnings("unchecked")
13971412
@Override
1398-
public Object visit(MethodCallNode node) {
1413+
public Object visit(MethodCallNode node) {
13991414
if (node == null) {
14001415
throw new InternalError("visit(MethodCallNode) called with null node");
14011416
}
14021417

1403-
try {
1404-
// Handle super calls first
1405-
if (node.isSuperCall) {
1406-
return handleSuperMethodCall(node);
1407-
}
1408-
1409-
// Evaluate all arguments first
1410-
List<Object> evaluatedArgs = new ArrayList<Object>();
1411-
for (ExprNode arg : node.arguments) {
1412-
Object argValue = dispatch(arg);
1413-
evaluatedArgs.add(typeSystem.unwrap(argValue));
1414-
}
1418+
try {
1419+
// Handle super calls first
1420+
if (node.isSuperCall) {
1421+
return handleSuperMethodCall(node);
1422+
}
1423+
1424+
ExecutionContext ctx = getCurrentContext();
1425+
if (ctx != null && node.qualifiedName != null && node.qualifiedName.contains(".")) {
1426+
String[] parts = node.qualifiedName.split("\\.");
1427+
if (parts.length == 2) {
1428+
String receiverName = parts[0];
1429+
String methodName = parts[1];
1430+
Object receiverValue = ctx.getVariable(receiverName);
1431+
receiverValue = typeSystem.unwrap(receiverValue);
1432+
if (literalRegistry.hasMethod(receiverValue, methodName)) {
1433+
List<Object> evaluatedArgs = new ArrayList<Object>();
1434+
for (ExprNode arg : node.arguments) {
1435+
Object argValue = dispatch(arg);
1436+
evaluatedArgs.add(typeSystem.unwrap(argValue));
1437+
}
1438+
return literalRegistry.handleMethod(receiverValue, methodName, evaluatedArgs, ctx);
1439+
}
1440+
}
1441+
}
1442+
1443+
// Evaluate all arguments first
1444+
List<Object> evaluatedArgs = new ArrayList<Object>();
1445+
for (ExprNode arg : node.arguments) {
1446+
Object argValue = dispatch(arg);
1447+
evaluatedArgs.add(typeSystem.unwrap(argValue));
1448+
}
14151449

14161450
// ========== CHECK GLOBAL FUNCTIONS FIRST ==========
14171451
// This must come BEFORE any other resolution to ensure out(), in(), etc.
@@ -1425,9 +1459,7 @@ public Object visit(MethodCallNode node) {
14251459
// ========== END GLOBAL CHECK ==========
14261460

14271461
// Try to find method in current class hierarchy
1428-
ExecutionContext ctx = getCurrentContext();
14291462
MethodNode method = null;
1430-
14311463
if (ctx.currentClass != null) {
14321464
method = interpreter
14331465
.getConstructorResolver()
@@ -1693,6 +1725,10 @@ public Object visit(ArrayNode node) {
16931725
return new NaturalArray(range, this, getCurrentContext());
16941726
}
16951727
}
1728+
1729+
if (node.elements.size() > 1 && allElementsAreRanges(node.elements)) {
1730+
return buildDimensionArray(node.elements, 0);
1731+
}
16961732

16971733
// Regular array literal handling
16981734
List<Object> result = new ArrayList<Object>();
@@ -1728,6 +1764,35 @@ public Object visit(ArrayNode node) {
17281764
throw new InternalError("Array creation failed", e);
17291765
}
17301766
}
1767+
1768+
private boolean allElementsAreRanges(List<ExprNode> elements) {
1769+
if (elements == null || elements.isEmpty()) return false;
1770+
for (ExprNode element : elements) {
1771+
if (!(element instanceof RangeNode)) {
1772+
return false;
1773+
}
1774+
}
1775+
return true;
1776+
}
1777+
1778+
private Object buildDimensionArray(List<ExprNode> ranges, int dimension) {
1779+
RangeNode currentRange = (RangeNode) ranges.get(dimension);
1780+
NaturalArray currentNatural = new NaturalArray(currentRange, this, getCurrentContext());
1781+
if (dimension == ranges.size() - 1) {
1782+
return currentNatural;
1783+
}
1784+
1785+
long length = currentNatural.size();
1786+
if (length > Integer.MAX_VALUE) {
1787+
throw new ProgramError("Dimension size too large for nested ND array literal: " + length + " (max " + Integer.MAX_VALUE + ")");
1788+
}
1789+
1790+
List<Object> result = new ArrayList<Object>((int) length);
1791+
for (int i = 0; i < (int) length; i++) {
1792+
result.add(buildDimensionArray(ranges, dimension + 1));
1793+
}
1794+
return result;
1795+
}
17311796

17321797
@SuppressWarnings("unchecked")
17331798
@Override
@@ -1750,6 +1815,10 @@ public Object visit(IndexAccessNode node) {
17501815

17511816
Object indexObj = dispatch(node.index);
17521817
indexObj = typeSystem.unwrap(indexObj);
1818+
1819+
if (indexObj instanceof List) {
1820+
return applyTupleIndices(arrayObj, (List<?>) indexObj);
1821+
}
17531822

17541823
if (indexObj instanceof RangeSpec) {
17551824
return applyRangeIndex(arrayObj, (RangeSpec) indexObj);
@@ -1984,6 +2053,40 @@ private Object applyMultiRangeIndex(Object array, MultiRangeSpec multiRange) {
19842053
(array != null ? array.getClass().getSimpleName() : "null"));
19852054
}
19862055

2056+
@SuppressWarnings("unchecked")
2057+
private Object applyTupleIndices(Object array, List<?> indices) {
2058+
Object current = array;
2059+
for (Object rawIndex : indices) {
2060+
Object indexObj = typeSystem.unwrap(rawIndex);
2061+
if (indexObj instanceof RangeSpec) {
2062+
current = applyRangeIndex(current, (RangeSpec) indexObj);
2063+
continue;
2064+
}
2065+
if (indexObj instanceof MultiRangeSpec) {
2066+
current = applyMultiRangeIndex(current, (MultiRangeSpec) indexObj);
2067+
continue;
2068+
}
2069+
if (current instanceof NaturalArray) {
2070+
NaturalArray natural = (NaturalArray) current;
2071+
long idx = expressionHandler.toLongIndex(indexObj);
2072+
current = natural.needsConversion() ? natural.get(idx, true) : natural.get(idx);
2073+
continue;
2074+
}
2075+
if (current instanceof List) {
2076+
List<Object> list = (List<Object>) current;
2077+
int idx = expressionHandler.toIntIndex(indexObj);
2078+
if (idx < 0 || idx >= list.size()) {
2079+
throw new ProgramError("Index out of bounds: " + idx + " for array of size " + list.size());
2080+
}
2081+
current = list.get(idx);
2082+
continue;
2083+
}
2084+
throw new ProgramError("Invalid array access during multidimensional indexing: expected NaturalArray or List, got "
2085+
+ (current != null ? current.getClass().getSimpleName() : "null"));
2086+
}
2087+
return current;
2088+
}
2089+
19872090
private List<Object> getListRange(List<Object> list, RangeSpec range) {
19882091
try {
19892092
long start, end;

src/main/java/cod/interpreter/handler/AssignmentHandler.java

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,10 @@ private Object handleIndexAssignment(IndexAccessNode indexAccess, Object newValu
174174
Object indexObj = dispatcher.dispatch(indexAccess.index);
175175
indexObj = typeSystem.unwrap(indexObj);
176176

177+
if (indexObj instanceof List) {
178+
return assignTupleIndex(arrayObj, (List<?>) indexObj, newValue);
179+
}
180+
177181
if (indexObj instanceof RangeSpec) {
178182
return assignRange(arrayObj, (RangeSpec) indexObj, newValue);
179183
}
@@ -204,6 +208,130 @@ private Object handleIndexAssignment(IndexAccessNode indexAccess, Object newValu
204208
throw new InternalError("Index assignment failed", e);
205209
}
206210
}
211+
212+
@SuppressWarnings("unchecked")
213+
private Object assignTupleIndex(Object arrayObj, List<?> tupleIndices, Object newValue) {
214+
if (tupleIndices == null || tupleIndices.isEmpty()) {
215+
throw new ProgramError("Invalid multidimensional assignment: empty index tuple");
216+
}
217+
218+
Object current = arrayObj;
219+
for (int i = 0; i < tupleIndices.size() - 1; i++) {
220+
Object idxObj = typeSystem.unwrap(tupleIndices.get(i));
221+
if (idxObj instanceof RangeSpec) {
222+
current = extractRange(current, (RangeSpec) idxObj);
223+
continue;
224+
}
225+
if (idxObj instanceof MultiRangeSpec) {
226+
current = extractMultiRange(current, (MultiRangeSpec) idxObj);
227+
continue;
228+
}
229+
if (current instanceof NaturalArray) {
230+
NaturalArray natural = (NaturalArray) current;
231+
long idx = expressionHandler.toLongIndex(idxObj);
232+
current = natural.get(idx);
233+
continue;
234+
}
235+
if (current instanceof List) {
236+
List<Object> list = (List<Object>) current;
237+
int idx = expressionHandler.toIntIndex(idxObj);
238+
if (idx < 0 || idx >= list.size()) {
239+
throw new ProgramError("Index out of bounds: " + idx + " for array of size " + list.size());
240+
}
241+
current = list.get(idx);
242+
continue;
243+
}
244+
throw new ProgramError("Invalid multidimensional assignment path: expected NaturalArray or List, got "
245+
+ (current != null ? current.getClass().getSimpleName() : "null"));
246+
}
247+
248+
Object lastIdxObj = typeSystem.unwrap(tupleIndices.get(tupleIndices.size() - 1));
249+
if (lastIdxObj instanceof RangeSpec) {
250+
return assignRange(current, (RangeSpec) lastIdxObj, newValue);
251+
}
252+
if (lastIdxObj instanceof MultiRangeSpec) {
253+
return assignMultiRange(current, (MultiRangeSpec) lastIdxObj, newValue);
254+
}
255+
if (current instanceof NaturalArray) {
256+
NaturalArray natural = (NaturalArray) current;
257+
long idx = expressionHandler.toLongIndex(lastIdxObj);
258+
natural.set(idx, newValue);
259+
return newValue;
260+
}
261+
if (current instanceof List) {
262+
List<Object> list = (List<Object>) current;
263+
int idx = expressionHandler.toIntIndex(lastIdxObj);
264+
list.set(idx, newValue);
265+
return newValue;
266+
}
267+
268+
throw new ProgramError("Invalid array assignment target in multidimensional assignment: " +
269+
(current != null ? current.getClass().getSimpleName() : "null"));
270+
}
271+
272+
@SuppressWarnings("unchecked")
273+
private Object extractRange(Object array, RangeSpec range) {
274+
if (array instanceof NaturalArray) {
275+
return ((NaturalArray) array).getRange(range);
276+
}
277+
if (array instanceof List) {
278+
List<Object> list = (List<Object>) array;
279+
List<Object> result = new ArrayList<Object>();
280+
long start = expressionHandler.toLongIndex(range.start);
281+
long end = expressionHandler.toLongIndex(range.end);
282+
long step = expressionHandler.calculateStep(range);
283+
start = normalizeListIndex(start, list.size());
284+
end = normalizeListIndex(end, list.size());
285+
286+
if (start < 0 || start >= list.size()) {
287+
throw new ProgramError("Range start index out of bounds: " + start + " for array of size " + list.size());
288+
}
289+
if (end < 0 || end >= list.size()) {
290+
throw new ProgramError("Range end index out of bounds: " + end + " for array of size " + list.size());
291+
}
292+
if (step > 0) {
293+
for (long i = start; i <= end && i < list.size(); i += step) {
294+
result.add(list.get((int) i));
295+
}
296+
} else if (step < 0) {
297+
for (long i = start; i >= end && i >= 0; i += step) {
298+
result.add(list.get((int) i));
299+
}
300+
} else {
301+
throw new ProgramError("Range step cannot be zero");
302+
}
303+
return result;
304+
}
305+
throw new ProgramError("Cannot apply range index during multidimensional assignment to " +
306+
(array != null ? array.getClass().getSimpleName() : "null"));
307+
}
308+
309+
@SuppressWarnings("unchecked")
310+
private Object extractMultiRange(Object array, MultiRangeSpec multiRange) {
311+
if (array instanceof NaturalArray) {
312+
return ((NaturalArray) array).getMultiRange(multiRange);
313+
}
314+
if (array instanceof List) {
315+
List<Object> list = (List<Object>) array;
316+
List<Object> result = new ArrayList<Object>();
317+
for (RangeSpec range : multiRange.ranges) {
318+
Object sub = extractRange(list, range);
319+
if (sub instanceof List) {
320+
result.addAll((List<Object>) sub);
321+
}
322+
}
323+
return result;
324+
}
325+
throw new ProgramError("Cannot apply multi-range index during multidimensional assignment to " +
326+
(array != null ? array.getClass().getSimpleName() : "null"));
327+
}
328+
329+
private long normalizeListIndex(long index, int size) {
330+
if (index < 0) {
331+
return size + index;
332+
}
333+
return index;
334+
}
207335

208336
private Object handleVariableAssignment(ExprNode target, Object newValue, ExecutionContext ctx) {
209337
try {

0 commit comments

Comments
 (0)