Skip to content

Commit aad9567

Browse files
committed
Add the 'combine' builtin method
1 parent e847254 commit aad9567

File tree

16 files changed

+178
-0
lines changed

16 files changed

+178
-0
lines changed

lkql_jit/language/src/main/java/com/adacore/lkql_jit/built_ins/BuiltInFunctions.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import com.adacore.lkql_jit.annotations.*;
1111
import com.adacore.lkql_jit.exception.LKQLRuntimeException;
1212
import com.adacore.lkql_jit.nodes.utils.ConcatenationNode;
13+
import com.adacore.lkql_jit.nodes.utils.ValueCombiner;
1314
import com.adacore.lkql_jit.runtime.values.*;
1415
import com.adacore.lkql_jit.runtime.values.bases.BasicLKQLValue;
1516
import com.adacore.lkql_jit.runtime.values.interfaces.Indexable;
@@ -295,6 +296,26 @@ protected LKQLList onEmptyList(@SuppressWarnings("unused") LKQLList list) {
295296
}
296297
}
297298

299+
@BuiltInMethod(
300+
name = "combine",
301+
doc = "Combine two LKQL values if possible and return the result, recursively if required",
302+
targetTypes = {
303+
LKQLTypesHelper.LKQL_OBJECT, LKQLTypesHelper.LKQL_LIST, LKQLTypesHelper.LKQL_STRING,
304+
}
305+
)
306+
abstract static class CombineExpr extends BuiltInBody {
307+
308+
@Specialization
309+
protected Object onAll(
310+
Object left,
311+
Object right,
312+
@DefaultVal("true") boolean recursive,
313+
@Cached ValueCombiner combiner
314+
) {
315+
return combiner.execute(left, right, recursive, this.callNode);
316+
}
317+
}
318+
298319
@BuiltInFunction(name = "map", doc = "Given a collection, a mapping function")
299320
abstract static class MapExpr extends BuiltInBody {
300321

lkql_jit/language/src/main/java/com/adacore/lkql_jit/exception/LKQLRuntimeException.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import com.adacore.lkql_jit.utils.source_location.SourceLocation;
1212
import com.adacore.lkql_jit.utils.source_location.SourceSectionWrapper;
1313
import com.oracle.truffle.api.CompilerDirectives;
14+
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
1415
import com.oracle.truffle.api.exception.AbstractTruffleException;
1516
import com.oracle.truffle.api.nodes.Node;
1617
import com.oracle.truffle.api.source.Source;
@@ -386,6 +387,18 @@ public static LKQLRuntimeException nullReceiver(Node location) {
386387
return LKQLRuntimeException.fromMessage("Null receiver in dot access", location);
387388
}
388389

390+
/** Create an exception when there is a collision during an object combination. */
391+
@TruffleBoundary
392+
public static LKQLRuntimeException objectCombiningCollision(
393+
String collidingMember,
394+
Node location
395+
) {
396+
return LKQLRuntimeException.fromMessage(
397+
"Cannot combine objects, both define the \"" + collidingMember + "\" member",
398+
location
399+
);
400+
}
401+
389402
// --- Argument exception
390403

391404
/**
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
//
2+
// Copyright (C) 2005-2025, AdaCore
3+
// SPDX-License-Identifier: GPL-3.0-or-later
4+
//
5+
6+
package com.adacore.lkql_jit.nodes.utils;
7+
8+
import com.adacore.lkql_jit.exception.LKQLRuntimeException;
9+
import com.adacore.lkql_jit.nodes.LKQLNode;
10+
import com.adacore.lkql_jit.runtime.values.LKQLObject;
11+
import com.adacore.lkql_jit.utils.Constants;
12+
import com.oracle.truffle.api.dsl.Cached;
13+
import com.oracle.truffle.api.dsl.Fallback;
14+
import com.oracle.truffle.api.dsl.Specialization;
15+
import com.oracle.truffle.api.library.CachedLibrary;
16+
import com.oracle.truffle.api.nodes.Node;
17+
import com.oracle.truffle.api.object.DynamicObjectLibrary;
18+
19+
public abstract class ValueCombiner extends Node {
20+
21+
// ----- Execution methods -----
22+
23+
/** Combine two values. */
24+
public abstract Object execute(Object left, Object right, boolean recursive, LKQLNode caller);
25+
26+
// ----- Specializations -----
27+
28+
@Specialization(limit = Constants.SPECIALIZED_LIB_LIMIT)
29+
protected LKQLObject onObjects(
30+
LKQLObject left,
31+
LKQLObject right,
32+
boolean recursive,
33+
LKQLNode caller,
34+
@CachedLibrary("left") DynamicObjectLibrary leftLib,
35+
@CachedLibrary("right") DynamicObjectLibrary rightLib,
36+
@CachedLibrary(limit = Constants.DISPATCHED_LIB_LIMIT) DynamicObjectLibrary resLib,
37+
@Cached ValueCombiner recursiveCombiner
38+
) {
39+
// Create the result object
40+
LKQLObject res = new LKQLObject(LKQLObject.emptyShape());
41+
42+
// Insert all keys of the left object in the result, resolving conflicts by combining
43+
// values.
44+
for (var key : leftLib.getKeyArray(left)) {
45+
if (!rightLib.containsKey(right, key)) {
46+
resLib.put(res, key, leftLib.getOrDefault(left, key, null));
47+
} else if (recursive) {
48+
resLib.put(
49+
res,
50+
key,
51+
recursiveCombiner.execute(
52+
leftLib.getOrDefault(left, key, null),
53+
rightLib.getOrDefault(right, key, null),
54+
recursive,
55+
caller
56+
)
57+
);
58+
} else {
59+
throw LKQLRuntimeException.objectCombiningCollision((String) key, caller);
60+
}
61+
}
62+
63+
// Insert keys from the right object that aren't in the resulting object
64+
for (var key : rightLib.getKeyArray(right)) {
65+
if (!resLib.containsKey(res, key)) {
66+
resLib.put(res, key, rightLib.getOrDefault(right, key, null));
67+
}
68+
}
69+
70+
// Return the resulting object
71+
return res;
72+
}
73+
74+
@Fallback
75+
protected Object onOthers(
76+
Object left,
77+
Object right,
78+
boolean recursive,
79+
LKQLNode caller,
80+
@Cached ConcatenationNode concatNode
81+
) {
82+
return concatNode.execute(left, right, caller);
83+
}
84+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# Combining strings
2+
print("a".combine("b"))
3+
4+
# Combining lists
5+
print([1, 2].combine([3, 4]))
6+
print([1, 2].combine([2, 3]))
7+
8+
# Combining objects
9+
print({a: 1}.combine({b: 2}))
10+
print({a: "A"}.combine({a: "B"}))
11+
print({a: [1, 2], b: "hello"}.combine({a: [3, 4], c: "world"}))
12+
13+
# Combining complex values
14+
print([[[[[1, 2]]]]].combine([[[[[3, 4]]]]]))
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
ab
2+
[1, 2, 3, 4]
3+
[1, 2, 2, 3]
4+
{"a": 1, "b": 2}
5+
{"a": "AB"}
6+
{"a": [1, 2, 3, 4], "b": "hello", "c": "world"}
7+
[[[[[1, 2]]]], [[[[3, 4]]]]]
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
driver: 'interpreter'
2+
project: 'default_project/default.gpr'
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
print({a: 1}.combine({b: 2}, recursive=false))
2+
print({a: "A"}.combine({a: "B"}, recursive=false))
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{"a": 1, "b": 2}
2+
script.lkql:2:7: error: Cannot combine objects, both define the "a" member
3+
2 | print({a: "A"}.combine({a: "B"}, recursive=false))
4+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
5+
in combine (called at script.lkql:2:7)
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
driver: 'interpreter'
2+
project: 'default_project/default.gpr'
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# Combine an object with integer values
2+
print({a: 1}.combine({a: 2}))

0 commit comments

Comments
 (0)