Skip to content

Commit 89e6f76

Browse files
committed
Implement object concatenation in LKQL
1 parent f0d7595 commit 89e6f76

File tree

7 files changed

+95
-4
lines changed

7 files changed

+95
-4
lines changed

lkql_jit/language/src/main/java/com/adacore/lkql_jit/nodes/expressions/literals/ObjectLiteral.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ public final class ObjectLiteral extends Expr {
5252
public ObjectLiteral(final SourceSection location, final String[] keys, final Expr[] values) {
5353
super(location);
5454
this.keys = keys;
55-
this.shape = Shape.newBuilder().build();
55+
this.shape = LKQLObject.emptyShape();
5656
this.values = values;
5757
this.objectLibrary = DynamicObjectLibrary.getUncached();
5858
}

lkql_jit/language/src/main/java/com/adacore/lkql_jit/nodes/utils/ConcatenationNode.java

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,17 @@
77

88
import com.adacore.lkql_jit.exception.LKQLRuntimeException;
99
import com.adacore.lkql_jit.nodes.LKQLNode;
10+
import com.adacore.lkql_jit.runtime.values.LKQLObject;
1011
import com.adacore.lkql_jit.runtime.values.lists.LKQLList;
12+
import com.adacore.lkql_jit.utils.Constants;
1113
import com.adacore.lkql_jit.utils.LKQLTypesHelper;
1214
import com.adacore.lkql_jit.utils.functions.StringUtils;
15+
import com.oracle.truffle.api.dsl.Cached;
1316
import com.oracle.truffle.api.dsl.Fallback;
1417
import com.oracle.truffle.api.dsl.Specialization;
18+
import com.oracle.truffle.api.library.CachedLibrary;
1519
import com.oracle.truffle.api.nodes.Node;
20+
import com.oracle.truffle.api.object.DynamicObjectLibrary;
1621

1722
public abstract class ConcatenationNode extends Node {
1823

@@ -38,6 +43,48 @@ protected LKQLList doLists(LKQLList left, LKQLList right, LKQLNode caller) {
3843
return new LKQLList(resContent);
3944
}
4045

46+
@Specialization(limit = Constants.SPECIALIZED_LIB_LIMIT)
47+
protected LKQLObject doObjects(
48+
LKQLObject left,
49+
LKQLObject right,
50+
LKQLNode caller,
51+
@CachedLibrary("left") DynamicObjectLibrary leftLib,
52+
@CachedLibrary("right") DynamicObjectLibrary rightLib,
53+
@CachedLibrary(limit = Constants.DISPATCHED_LIB_LIMIT) DynamicObjectLibrary resLib,
54+
@Cached ConcatenationNode innerConcat
55+
) {
56+
// Create the result object
57+
LKQLObject res = new LKQLObject(LKQLObject.emptyShape());
58+
59+
// Insert all keys of the left object in the result, resolving conflicts by merging
60+
// values.
61+
for (var key : leftLib.getKeyArray(left)) {
62+
if (!rightLib.containsKey(right, key)) {
63+
resLib.put(res, key, leftLib.getOrDefault(left, key, null));
64+
} else {
65+
resLib.put(
66+
res,
67+
key,
68+
innerConcat.execute(
69+
leftLib.getOrDefault(left, key, null),
70+
rightLib.getOrDefault(right, key, null),
71+
caller
72+
)
73+
);
74+
}
75+
}
76+
77+
// Insert keys from the right object that aren't in the resulting object
78+
for (var key : rightLib.getKeyArray(right)) {
79+
if (!resLib.containsKey(res, key)) {
80+
resLib.put(res, key, rightLib.getOrDefault(right, key, null));
81+
}
82+
}
83+
84+
// Return the resulting object
85+
return res;
86+
}
87+
4188
@Fallback
4289
protected void nonConcatenable(Object left, Object right, LKQLNode caller) {
4390
throw LKQLRuntimeException.unsupportedOperation(

lkql_jit/language/src/main/java/com/adacore/lkql_jit/runtime/values/LKQLObject.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@
2424
@ExportLibrary(InteropLibrary.class)
2525
public final class LKQLObject extends ObjectLKQLValue implements LKQLValue {
2626

27+
// ----- Attributes -----*
28+
29+
private static final Shape.Builder shapeBuilder = Shape.newBuilder();
30+
2731
// ----- Constructors -----
2832

2933
/** Create a new LKQL object with its dynamic shape. */
@@ -38,13 +42,19 @@ public LKQLObject(final Shape shape) {
3842
* library so this is not designed for performance critical usages.
3943
*/
4044
public static LKQLObject createUncached(final Object[] keys, final Object[] values) {
41-
final LKQLObject res = new LKQLObject(Shape.newBuilder().build());
45+
final LKQLObject res = new LKQLObject(emptyShape());
4246
for (int i = 0; i < keys.length; i++) {
4347
uncachedObjectLibrary.put(res, keys[i], values[i]);
4448
}
4549
return res;
4650
}
4751

52+
/** Get an empty dynamic shape. */
53+
@CompilerDirectives.TruffleBoundary
54+
public static Shape emptyShape() {
55+
return shapeBuilder.build();
56+
}
57+
4858
// ----- Value methods -----
4959

5060
/** Exported message to compare two LKQL objects. */
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
print({a: 1} & {b: 2})
2+
print({a: "A"} & {a: "B"})
3+
print({a: [1, 2], b: "hello"} & {a: [3, 4], c: "world"})
4+
print(concat([{a: 1}, {b: 2}, {c: 3}]))
5+
print({a: 1} & {a: 2})
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{"a": 1, "b": 2}
2+
{"a": "AB"}
3+
{"a": [1, 2, 3, 4], "b": "hello", "c": "world"}
4+
{"a": 1, "b": 2, "c": 3}
5+
script.lkql:5:7: error: Unsupported operation: Int & Int
6+
5 | print({a: 1} & {a: 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'

user_manual/source/language_reference.rst

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,8 @@ Composite Data Types
113113

114114
``Object``
115115
Objects are heterogeneous records that can contain any number of key to value
116-
mappings, where keys are labels and values are any valid LKQL value.
116+
mappings, where keys are labels and values are any valid LKQL value. Objects
117+
support concatenation.
117118

118119

119120

@@ -740,16 +741,35 @@ LKQL has a few built-in operators available:
740741
741742
true and false or (a == b) and (not c)
742743
743-
- String and list concatenation
744+
- Value concatenation
744745

745746
.. code-block:: lkql
746747
748+
# Strings concatenation
747749
"Hello " & name
748750
749751
.. code-block:: lkql
750752
753+
# Lists concatenation
751754
[1, 2, 3] & [4, 5, 6]
752755
756+
.. code-block:: lkql
757+
758+
# Objects concatenation
759+
{a: 1} & {b: 2}
760+
761+
.. note::
762+
763+
When concatenation object values, keys are combined in the result. In case of
764+
conflicting keys, their values are recursively concatenated, as it is shown
765+
in the following example:
766+
767+
.. code-block:: lkql
768+
769+
{a: 1} & {b: 2} # Result: {a: 1, b: 2}
770+
{a: [1, 2]} & {a: [3, 4]} # Result: {a: [1, 2, 3, 4]}
771+
{a: 1} & {a: 2} # Runtime error! Impossible to concatenate `Int`s
772+
753773
754774
Module Importation
755775
^^^^^^^^^^^^^^^^^^

0 commit comments

Comments
 (0)